Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -933,6 +933,7 @@ if (Opts.Limit) Req.MaxCandidateCount = Opts.Limit; Req.Query = Filter->pattern(); + Req.GlobalCodeCompletionOnly = true; Req.Scopes = getQueryScopes(Recorder->CCContext, Recorder->CCSema->getSourceManager()); log(llvm::formatv("Code complete: fuzzyFind(\"{0}\", scopes=[{1}])", Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -155,9 +155,10 @@ // The number of translation units that reference this symbol from their main // file. This number is only meaningful if aggregated in an index. unsigned References = 0; - + /// Whether or not this symbol is meant to be used for the global completion. + bool SupportGlobalCompletion = false; /// A brief description of the symbol that can be displayed in the completion - /// candidate list. For example, "Foo(X x, Y y) const" is a labal for a + /// candidate list. For example, "Foo(X x, Y y) const" is a label for a /// function. llvm::StringRef CompletionLabel; /// The piece of text that the user is expected to type to match the @@ -273,6 +274,9 @@ /// \brief The number of top candidates to return. The index may choose to /// return more than this, e.g. if it doesn't know which candidates are best. size_t MaxCandidateCount = UINT_MAX; + /// If set to true, only symbols with global completion support will be + /// considered. + bool GlobalCodeCompletionOnly = false; }; struct LookupRequest { Index: clangd/index/MemIndex.cpp =================================================================== --- clangd/index/MemIndex.cpp +++ clangd/index/MemIndex.cpp @@ -45,6 +45,8 @@ // Exact match against all possible scopes. if (!Req.Scopes.empty() && !llvm::is_contained(Req.Scopes, Sym->Scope)) continue; + if (Req.GlobalCodeCompletionOnly && !Sym->SupportGlobalCompletion) + continue; if (auto Score = Filter.match(Sym->Name)) { Top.emplace(-*Score * quality(*Sym), Sym); Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -149,21 +149,20 @@ if (ND->isInAnonymousNamespace()) return true; - // We only want: - // * symbols in namespaces or translation unit scopes (e.g. no class - // members) - // * enum constants in unscoped enum decl (e.g. "red" in "enum {red};") - auto InTopLevelScope = hasDeclContext( - anyOf(namespaceDecl(), translationUnitDecl(), linkageSpecDecl())); - // Don't index template specializations. + // We want most things but not "local" symbols such as symbols inside + // FunctionDecl, BlockDecl, ObjCMethodDecl and OMPDeclareReductionDecl. + // FIXME: Need a matcher for ExportDecl in order to include symbols declared + // within an export. + auto InNonLocalContext = hasDeclContext(anyOf( + translationUnitDecl(), namespaceDecl(), linkageSpecDecl(), recordDecl(), + enumDecl(), objcProtocolDecl(), objcInterfaceDecl(), objcCategoryDecl(), + objcCategoryImplDecl(), objcImplementationDecl())); + // Don't index template specializations and expansions in main files. auto IsSpecialization = anyOf(functionDecl(isExplicitTemplateSpecialization()), cxxRecordDecl(isExplicitTemplateSpecialization()), varDecl(isExplicitTemplateSpecialization())); - if (match(decl(allOf(unless(isExpansionInMainFile()), - anyOf(InTopLevelScope, - hasDeclContext(enumDecl(InTopLevelScope, - unless(isScoped())))), + if (match(decl(allOf(unless(isExpansionInMainFile()), InNonLocalContext, unless(IsSpecialization))), *ND, *ASTCtx) .empty()) @@ -272,6 +271,24 @@ match(decl(isExpansionInMainFile()), ND, ND.getASTContext()).empty(); } +// For global completion, we only want: +// * symbols in namespaces or translation unit scopes (e.g. no class +// members) +// * enum constants in unscoped enum decl (e.g. "red" in "enum {red};") +// For the other cases, we let Clang do the completion because it does not +// need any non-local information and it will be much better at following +// lookup rules. +bool DeclSupportsGlobalCompletion(const NamedDecl &ND, ASTContext &ASTCtx) { + using namespace clang::ast_matchers; + auto InTopLevelScope = hasDeclContext( + anyOf(namespaceDecl(), translationUnitDecl(), linkageSpecDecl())); + return !match(decl(anyOf(InTopLevelScope, + hasDeclContext( + enumDecl(InTopLevelScope, unless(isScoped()))))), + ND, ASTCtx) + .empty(); +} + } // namespace SymbolCollector::SymbolCollector(Options Opts) : Opts(std::move(Opts)) {} @@ -364,6 +381,8 @@ Symbol S; S.ID = std::move(ID); std::tie(S.Scope, S.Name) = splitQualifiedName(QName); + + S.SupportGlobalCompletion = DeclSupportsGlobalCompletion(ND, Ctx); S.SymInfo = index::getSymbolInfo(&ND); std::string FileURI; if (auto DeclLoc = Index: clangd/index/SymbolYAML.cpp =================================================================== --- clangd/index/SymbolYAML.cpp +++ clangd/index/SymbolYAML.cpp @@ -108,6 +108,8 @@ SymbolLocation()); IO.mapOptional("Definition", Sym.Definition, SymbolLocation()); IO.mapOptional("References", Sym.References, 0u); + IO.mapOptional("SupportGlobalCompletion", Sym.SupportGlobalCompletion, + false); IO.mapRequired("CompletionLabel", Sym.CompletionLabel); IO.mapRequired("CompletionFilterText", Sym.CompletionFilterText); IO.mapRequired("CompletionPlainInsertText", Sym.CompletionPlainInsertText); Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -32,6 +32,7 @@ using ::testing::Each; using ::testing::ElementsAre; using ::testing::Field; +using ::testing::IsEmpty; using ::testing::Not; using ::testing::UnorderedElementsAre; @@ -153,6 +154,7 @@ Sym.CompletionSnippetInsertText = Sym.Name; Sym.CompletionLabel = Sym.Name; Sym.SymInfo.Kind = Kind; + Sym.SupportGlobalCompletion = true; return Sym; } Symbol func(StringRef Name) { // Assumes the function has no args. @@ -654,6 +656,20 @@ Contains(AllOf(Named("baz"), Doc("Multi-line\nblock comment")))); } +TEST(CompletionTest, GlobalCompletionFiltering) { + + Symbol Class = cls("XYZ"); + Class.SupportGlobalCompletion = false; + Symbol Func = func("XYZ::foooo"); + Func.SupportGlobalCompletion = false; + + auto Results = completions(R"(// void f() { + XYZ::foooo^ + })", + {Class, Func}); + EXPECT_THAT(Results.items, IsEmpty()); +} + TEST(CodeCompleteTest, DisableTypoCorrection) { auto Results = completions(R"cpp( namespace clang { int v; } Index: unittests/clangd/FileIndexTests.cpp =================================================================== --- unittests/clangd/FileIndexTests.cpp +++ unittests/clangd/FileIndexTests.cpp @@ -144,13 +144,14 @@ EXPECT_THAT(match(M, FuzzyFindRequest()), UnorderedElementsAre()); } -TEST(FileIndexTest, IgnoreClassMembers) { +TEST(FileIndexTest, ClassMembers) { FileIndex M; update(M, "f1", "class X { static int m1; int m2; static void f(); };"); FuzzyFindRequest Req; Req.Query = ""; - EXPECT_THAT(match(M, Req), UnorderedElementsAre("X")); + EXPECT_THAT(match(M, Req), + UnorderedElementsAre("X", "X::m1", "X::m2", "X::f")); } TEST(FileIndexTest, NoIncludeCollected) { Index: unittests/clangd/FindSymbolsTests.cpp =================================================================== --- unittests/clangd/FindSymbolsTests.cpp +++ unittests/clangd/FindSymbolsTests.cpp @@ -120,7 +120,10 @@ EXPECT_THAT(getSymbols("UnnamedStruct"), ElementsAre(AllOf(Named("UnnamedStruct"), WithKind(SymbolKind::Variable)))); - EXPECT_THAT(getSymbols("InUnnamed"), IsEmpty()); + EXPECT_THAT( + getSymbols("InUnnamed"), + ElementsAre(AllOf(Named("InUnnamed"), InContainer("(anonymous struct)"), + WithKind(SymbolKind::Field)))); } TEST_F(WorkspaceSymbolsTest, InMainFile) { @@ -223,6 +226,44 @@ EXPECT_THAT(getSymbols(""), IsEmpty()); } +TEST_F(WorkspaceSymbolsTest, Enums) { + addFile("foo.h", R"cpp( + enum { + Red + }; + enum Color { + Green + }; + enum class Color2 { + Yellow + }; + namespace ns { + enum { + Black + }; + enum Color3 { + Blue + }; + enum class Color4 { + White + }; + } + )cpp"); + addFile("foo.cpp", R"cpp( + #include "foo.h" + )cpp"); + EXPECT_THAT(getSymbols("Red"), ElementsAre(Named("Red"))); + EXPECT_THAT(getSymbols("::Red"), ElementsAre(Named("Red"))); + EXPECT_THAT(getSymbols("Green"), ElementsAre(Named("Green"))); + EXPECT_THAT(getSymbols("Green"), ElementsAre(Named("Green"))); + EXPECT_THAT(getSymbols("Color2::Yellow"), ElementsAre(Named("Yellow"))); + EXPECT_THAT(getSymbols("Yellow"), ElementsAre(Named("Yellow"))); + + EXPECT_THAT(getSymbols("ns::Black"), ElementsAre(Named("Black"))); + EXPECT_THAT(getSymbols("ns::Blue"), ElementsAre(Named("Blue"))); + EXPECT_THAT(getSymbols("ns::Color4::White"), ElementsAre(Named("White"))); +} + TEST_F(WorkspaceSymbolsTest, WithLimit) { addFile("foo.h", R"cpp( int foo; Index: unittests/clangd/SymbolCollectorTests.cpp =================================================================== --- unittests/clangd/SymbolCollectorTests.cpp +++ unittests/clangd/SymbolCollectorTests.cpp @@ -67,6 +67,9 @@ Pos.end.character); } MATCHER_P(Refs, R, "") { return int(arg.References) == R; } +MATCHER_P(Global, SupportGlobalCompletion, "") { + return arg.SupportGlobalCompletion == SupportGlobalCompletion; +} namespace clang { namespace clangd { @@ -132,9 +135,11 @@ CollectorOpts, PragmaHandler.get()); std::vector Args = { - "symbol_collector", "-fsyntax-only", "-xc++", "-std=c++11", - "-include", TestHeaderName, TestFileName}; + "symbol_collector", "-fsyntax-only", "-xc++", + "-std=c++11", "-include", TestHeaderName}; Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); + Args.push_back(TestFileName); + tooling::ToolInvocation Invocation( Args, Factory->create(), Files.get(), @@ -163,8 +168,20 @@ TEST_F(SymbolCollectorTest, CollectSymbols) { const std::string Header = R"( class Foo { + Foo() {} + Foo(int a) {} void f(); + friend void f1(); + friend class Friend; + Foo& operator=(const Foo&); + ~Foo(); + class Nested { + void f(); + }; + }; + class Friend { }; + void f1(); inline void f2() {} static const int KInt = 2; @@ -198,25 +215,79 @@ } // namespace foo )"; runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAreArray( - {QName("Foo"), QName("f1"), QName("f2"), QName("KInt"), - QName("kStr"), QName("foo"), QName("foo::bar"), - QName("foo::int32"), QName("foo::int32_t"), QName("foo::v1"), - QName("foo::bar::v2"), QName("foo::baz")})); + EXPECT_THAT(Symbols, UnorderedElementsAreArray( + {AllOf(QName("Foo"), Global(true)), + AllOf(QName("Foo::Foo"), Global(false)), + AllOf(QName("Foo::Foo"), Global(false)), + AllOf(QName("Foo::f"), Global(false)), + AllOf(QName("Foo::~Foo"), Global(false)), + AllOf(QName("Foo::operator="), Global(false)), + AllOf(QName("Foo::Nested"), Global(false)), + AllOf(QName("Foo::Nested::f"), Global(false)), + + AllOf(QName("Friend"), Global(true)), + AllOf(QName("f1"), Global(true)), + AllOf(QName("f2"), Global(true)), + AllOf(QName("KInt"), Global(true)), + AllOf(QName("kStr"), Global(true)), + AllOf(QName("foo"), Global(true)), + AllOf(QName("foo::bar"), Global(true)), + AllOf(QName("foo::int32"), Global(true)), + AllOf(QName("foo::int32_t"), Global(true)), + AllOf(QName("foo::v1"), Global(true)), + AllOf(QName("foo::bar::v2"), Global(true)), + AllOf(QName("foo::baz"), Global(true))})); } TEST_F(SymbolCollectorTest, Template) { Annotations Header(R"( // Template is indexed, specialization and instantiation is not. - template struct [[Tmpl]] {T x = 0;}; + template struct [[Tmpl]] {T $xdecl[[x]] = 0;}; template <> struct Tmpl {}; extern template struct Tmpl; template struct Tmpl; )"); runSymbolCollector(Header.code(), /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAreArray({AllOf( - QName("Tmpl"), DeclRange(Header.range()))})); + EXPECT_THAT(Symbols, + UnorderedElementsAreArray( + {AllOf(QName("Tmpl"), DeclRange(Header.range())), + AllOf(QName("Tmpl::x"), DeclRange(Header.range("xdecl")))})); +} + +TEST_F(SymbolCollectorTest, ObjCSymbols) { + const std::string Header = R"( + @interface Person + - (void)someMethodName:(void*)name1 lastName:(void*)lName; + @end + + @implementation Person + - (void)someMethodName:(void*)name1 lastName:(void*)lName{ + int foo; + ^(int param){ int bar; }; + } + @end + + @interface Person (MyCategory) + - (void)someMethodName2:(void*)name2; + @end + + @implementation Person (MyCategory) + - (void)someMethodName2:(void*)name2 { + int foo2; + } + @end + + @protocol MyProtocol + - (void)someMethodName3:(void*)name3; + @end + )"; + TestFileName = "test.m"; + runSymbolCollector(Header, /*Main=*/"", {"-fblocks", "-xobjective-c++"}); + EXPECT_THAT(Symbols, + UnorderedElementsAre( + QName("Person"), QName("Person::someMethodName:lastName:"), + QName("MyCategory"), QName("Person::someMethodName2:"), + QName("MyProtocol"), QName("MyProtocol::someMethodName3:"))); } TEST_F(SymbolCollectorTest, Locations) { @@ -334,7 +405,7 @@ Green }; enum class Color2 { - Yellow // ignore + Yellow }; namespace ns { enum { @@ -343,20 +414,25 @@ } )"; runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Red"), QName("Color"), - QName("Green"), QName("Color2"), - QName("ns"), QName("ns::Black"))); + EXPECT_THAT(Symbols, UnorderedElementsAre( + AllOf(QName("Red"), Global(true)), + AllOf(QName("Color"), Global(true)), + AllOf(QName("Green"), Global(true)), + AllOf(QName("Color2"), Global(true)), + AllOf(QName("Color2::Yellow"), Global(false)), + AllOf(QName("ns"), Global(true)), + AllOf(QName("ns::Black"), Global(true)))); } -TEST_F(SymbolCollectorTest, IgnoreNamelessSymbols) { +TEST_F(SymbolCollectorTest, NamelessSymbols) { const std::string Header = R"( struct { int a; } Foo; )"; runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre(QName("Foo"))); + EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"), + QName("(anonymous struct)::a"))); } TEST_F(SymbolCollectorTest, SymbolFormedFromMacro) { @@ -417,7 +493,7 @@ UnorderedElementsAre(QName("Foo"), QName("f1"), QName("f2"))); } -TEST_F(SymbolCollectorTest, IgnoreClassMembers) { +TEST_F(SymbolCollectorTest, ClassMembers) { const std::string Header = R"( class Foo { void f() {} @@ -432,7 +508,10 @@ void Foo::ssf() {} )"; runSymbolCollector(Header, Main); - EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"))); + EXPECT_THAT(Symbols, + UnorderedElementsAre(QName("Foo"), QName("Foo::f"), + QName("Foo::g"), QName("Foo::sf"), + QName("Foo::ssf"), QName("Foo::x"))); } TEST_F(SymbolCollectorTest, Scopes) { @@ -531,6 +610,7 @@ End: Line: 1 Column: 1 +SupportGlobalCompletion: true CompletionLabel: 'Foo1-label' CompletionFilterText: 'filter' CompletionPlainInsertText: 'plain' @@ -555,6 +635,7 @@ End: Line: 1 Column: 1 +SupportGlobalCompletion: false CompletionLabel: 'Foo2-label' CompletionFilterText: 'filter' CompletionPlainInsertText: 'plain' @@ -567,11 +648,12 @@ EXPECT_THAT(Symbols1, UnorderedElementsAre(AllOf( QName("clang::Foo1"), Labeled("Foo1-label"), Doc("Foo doc"), - Detail("int"), DeclURI("file:///path/foo.h")))); + Detail("int"), DeclURI("file:///path/foo.h"), Global(true)))); auto Symbols2 = SymbolsFromYAML(YAML2); - EXPECT_THAT(Symbols2, UnorderedElementsAre(AllOf( - QName("clang::Foo2"), Labeled("Foo2-label"), - Not(HasDetail()), DeclURI("file:///path/bar.h")))); + EXPECT_THAT(Symbols2, + UnorderedElementsAre(AllOf( + QName("clang::Foo2"), Labeled("Foo2-label"), Not(HasDetail()), + DeclURI("file:///path/bar.h"), Global(false)))); std::string ConcatenatedYAML; { @@ -741,23 +823,27 @@ // Canonical declarations. class $cdecl[[C]] {}; struct $sdecl[[S]] {}; - union $udecl[[U]] {int x; bool y;}; + union $udecl[[U]] {int $xdecl[[x]]; bool $ydecl[[y]];}; )"); runSymbolCollector(Header.code(), /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("C"), DeclURI(TestHeaderURI), - DeclRange(Header.range("cdecl")), - IncludeHeader(TestHeaderURI), DefURI(TestHeaderURI), - DefRange(Header.range("cdecl"))), - AllOf(QName("S"), DeclURI(TestHeaderURI), - DeclRange(Header.range("sdecl")), - IncludeHeader(TestHeaderURI), DefURI(TestHeaderURI), - DefRange(Header.range("sdecl"))), - AllOf(QName("U"), DeclURI(TestHeaderURI), - DeclRange(Header.range("udecl")), - IncludeHeader(TestHeaderURI), DefURI(TestHeaderURI), - DefRange(Header.range("udecl"))))); + EXPECT_THAT( + Symbols, + UnorderedElementsAre( + AllOf(QName("C"), DeclURI(TestHeaderURI), + DeclRange(Header.range("cdecl")), IncludeHeader(TestHeaderURI), + DefURI(TestHeaderURI), DefRange(Header.range("cdecl"))), + AllOf(QName("S"), DeclURI(TestHeaderURI), + DeclRange(Header.range("sdecl")), IncludeHeader(TestHeaderURI), + DefURI(TestHeaderURI), DefRange(Header.range("sdecl"))), + AllOf(QName("U"), DeclURI(TestHeaderURI), + DeclRange(Header.range("udecl")), IncludeHeader(TestHeaderURI), + DefURI(TestHeaderURI), DefRange(Header.range("udecl"))), + AllOf(QName("U::x"), DeclURI(TestHeaderURI), + DeclRange(Header.range("xdecl")), DefURI(TestHeaderURI), + DefRange(Header.range("xdecl"))), + AllOf(QName("U::y"), DeclURI(TestHeaderURI), + DeclRange(Header.range("ydecl")), DefURI(TestHeaderURI), + DefRange(Header.range("ydecl"))))); } TEST_F(SymbolCollectorTest, ClassForwardDeclarationIsCanonical) {