Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -927,6 +927,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,11 @@ // 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 is 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 +275,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()) @@ -356,6 +355,25 @@ Symbol S; S.ID = std::move(ID); std::tie(S.Scope, S.Name) = splitQualifiedName(QName); + + // 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. + using namespace clang::ast_matchers; + auto InTopLevelScope = hasDeclContext( + anyOf(namespaceDecl(), translationUnitDecl(), linkageSpecDecl())); + // Don't index template specializations. + S.SupportGlobalCompletion = + !match(decl(anyOf(InTopLevelScope, + hasDeclContext( + enumDecl(InTopLevelScope, unless(isScoped()))))), + ND, *ASTCtx) + .empty(); + 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 @@ -30,6 +30,7 @@ using ::testing::Each; using ::testing::ElementsAre; using ::testing::Field; +using ::testing::IsEmpty; using ::testing::Not; using ::testing::UnorderedElementsAre; @@ -151,6 +152,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. @@ -652,6 +654,33 @@ Contains(AllOf(Named("baz"), Doc("Multi-line\nblock comment")))); } +TEST(CompletionTest, DynamicIndexMultiFileMembers) { + MockFSProvider FS; + MockCompilationDatabase CDB; + IgnoreDiagnostics DiagConsumer; + auto Opts = ClangdServer::optsForTest(); + Opts.BuildDynamicSymbolIndex = true; + ClangdServer Server(CDB, FS, DiagConsumer, Opts); + + FS.Files[testPath("foo.h")] = R"cpp( + struct XYZ {void fooooo() {} }; + )cpp"; + runAddDocument(Server, testPath("foo.cpp"), R"cpp( + #include "foo.h" + )cpp"); + + auto File = testPath("bar.cpp"); + Annotations Test(R"cpp( + void f() { + XYZ::foooo^ + } + )cpp"); + runAddDocument(Server, File, Test.code()); + + auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {})); + EXPECT_THAT(Results.items, IsEmpty()); +} + TEST(CodeCompleteTest, DisableTypoCorrection) { auto Results = completions(R"cpp( namespace clang { int v; } @@ -709,6 +738,141 @@ EXPECT_THAT(Results.items, Not(Contains(Labeled("param_in_bar")))); } +TEST(CompletionTest, Enums) { + EXPECT_THAT(completions(R"cpp( + enum class Color2 { + Yellow + }; + void foo() { + Color2::^ + })cpp") + .items, + Has("Yellow", CompletionItemKind::Value)); + EXPECT_THAT(completions(R"cpp( + enum { + Red + }; + void foo() { + Re^ + })cpp") + .items, + Has("Red", CompletionItemKind::Value)); + EXPECT_THAT(completions(R"cpp( + enum Color { + Green + }; + void foo() { + Gr^ + })cpp") + .items, + Has("Green", CompletionItemKind::Value)); + EXPECT_THAT(completions(R"cpp( + namespace ns { + enum { + Black + }; + } + void foo() { + ns::B^ + })cpp") + .items, + Has("Black", CompletionItemKind::Value)); + EXPECT_THAT(completions(R"cpp( + void foo() { + ns::B^ + })cpp") + .items, + IsEmpty()); + + MockFSProvider FS; + MockCompilationDatabase CDB; + IgnoreDiagnostics DiagConsumer; + auto Opts = ClangdServer::optsForTest(); + Opts.BuildDynamicSymbolIndex = true; + ClangdServer Server(CDB, FS, DiagConsumer, Opts); + auto Code = R"( + enum class Color { + Yellow + }; + )"; + + auto File = testPath("foo.h"); + Server.addDocument(File, Code); + FS.Files[File] = Code; + + Server.addDocument(testPath("bar.cpp"), R"( + #include "foo.h" + )"); + + File = testPath("bar2.cpp"); + Annotations Test(R"( + void foo() { + Color::Y^ + } + )"); + Server.addDocument(File, Test.code()); + + ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; + auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {})); + EXPECT_THAT(Results.items, IsEmpty()); +} + +TEST(CompletionTest, AnonymousNamespace) { + + MockFSProvider FS; + MockCompilationDatabase CDB; + IgnoreDiagnostics DiagConsumer; + auto Opts = ClangdServer::optsForTest(); + Opts.BuildDynamicSymbolIndex = true; + ClangdServer Server(CDB, FS, DiagConsumer, Opts); + auto File = testPath("bar.cpp"); + Server.addDocument(File, R"( + namespace { + void inAnymous() { + } + } // namespace + )"); + + File = testPath("bar2.cpp"); + Annotations Test(R"( + void bar() { + inAnym^ + } + )"); + + Server.addDocument(File, Test.code()); + ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; + auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {})); + EXPECT_THAT(Results.items, IsEmpty()); +} + +TEST(CompletionTest, InMainFile) { + + MockFSProvider FS; + MockCompilationDatabase CDB; + IgnoreDiagnostics DiagConsumer; + auto Opts = ClangdServer::optsForTest(); + Opts.BuildDynamicSymbolIndex = true; + ClangdServer Server(CDB, FS, DiagConsumer, Opts); + auto File = testPath("main.cpp"); + Server.addDocument(File, R"( + void funcInMain() { + } + )"); + + File = testPath("bar.cpp"); + Annotations Test(R"( + void bar() { + funcInMa^ + } + )"); + + Server.addDocument(File, Test.code()); + ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; + auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {})); + EXPECT_THAT(Results.items, IsEmpty()); +} + SignatureHelp signatures(StringRef Text) { MockFSProvider FS; MockCompilationDatabase CDB; Index: unittests/clangd/FileIndexTests.cpp =================================================================== --- unittests/clangd/FileIndexTests.cpp +++ unittests/clangd/FileIndexTests.cpp @@ -139,13 +139,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(SupportGlobalCompletion, SupportGlobalCompletion, "") { + return arg.SupportGlobalCompletion == SupportGlobalCompletion; +} namespace clang { namespace clangd { @@ -163,8 +166,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 +213,68 @@ } // 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( + {QName("Foo"), QName("Foo::Foo"), + QName("Foo::Foo"), QName("Foo::f"), + QName("Foo::~Foo"), QName("Foo::operator="), + QName("Foo::Nested"), QName("Foo::Nested::f"), + QName("Friend"), 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")})); } 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"}); + 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 +392,7 @@ Green }; enum class Color2 { - Yellow // ignore + Yellow }; namespace ns { enum { @@ -343,20 +401,21 @@ } )"; runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Red"), QName("Color"), - QName("Green"), QName("Color2"), - QName("ns"), QName("ns::Black"))); + EXPECT_THAT(Symbols, + UnorderedElementsAre(QName("Red"), QName("Color"), QName("Green"), + QName("Color2"), QName("Color2::Yellow"), + QName("ns"), QName("ns::Black"))); } -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 +476,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 +491,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 +593,7 @@ End: Line: 1 Column: 1 +SupportGlobalCompletion: true CompletionLabel: 'Foo1-label' CompletionFilterText: 'filter' CompletionPlainInsertText: 'plain' @@ -555,6 +618,7 @@ End: Line: 1 Column: 1 +SupportGlobalCompletion: false CompletionLabel: 'Foo2-label' CompletionFilterText: 'filter' CompletionPlainInsertText: 'plain' @@ -567,11 +631,13 @@ 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"), + SupportGlobalCompletion(true)))); auto Symbols2 = SymbolsFromYAML(YAML2); EXPECT_THAT(Symbols2, UnorderedElementsAre(AllOf( QName("clang::Foo2"), Labeled("Foo2-label"), - Not(HasDetail()), DeclURI("file:///path/bar.h")))); + Not(HasDetail()), DeclURI("file:///path/bar.h"), + SupportGlobalCompletion(false)))); std::string ConcatenatedYAML; { @@ -677,23 +743,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) {