Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -929,6 +929,9 @@ if (Opts.Limit) Req.MaxCandidateCount = Opts.Limit; Req.Query = Filter->pattern(); + Req.DeclContexts = {Decl::Kind::Namespace, Decl::Kind::TranslationUnit, + Decl::Kind::LinkageSpec, Decl::Kind::Enum}; + Req.IncludeInScopedEnum = false; 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 @@ -10,6 +10,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_H +#include "clang/AST/DeclBase.h" #include "clang/Index/IndexSymbol.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/DenseMap.h" @@ -155,9 +156,12 @@ // 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; - + /// The Decl::Kind for the context of the symbol, i.e. what contains it. + Decl::Kind DeclContextKind; + /// Whether or not this is an enumerator inside a scoped enum (C++11). + bool InScopedEnum = 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 +277,11 @@ /// \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 false, enumerators inside scoped Enums will be skipped. + bool IncludeInScopedEnum = true; + /// Filter results to only those in specified contexts. If empty all context + /// are considered. + std::vector DeclContexts; }; struct LookupRequest { Index: clangd/index/MemIndex.cpp =================================================================== --- clangd/index/MemIndex.cpp +++ clangd/index/MemIndex.cpp @@ -10,6 +10,7 @@ #include "MemIndex.h" #include "../FuzzyMatch.h" #include "../Logger.h" +#include #include namespace clang { @@ -34,6 +35,10 @@ assert(!StringRef(Req.Query).contains("::") && "There must be no :: in query."); + std::bitset ContextFilter; + for (Decl::Kind Kind : Req.DeclContexts) + ContextFilter.set(static_cast(Kind)); + std::priority_queue> Top; FuzzyMatcher Filter(Req.Query); bool More = false; @@ -45,6 +50,11 @@ // Exact match against all possible scopes. if (!Req.Scopes.empty() && !llvm::is_contained(Req.Scopes, Sym->Scope)) continue; + if (!Req.DeclContexts.empty() && + !ContextFilter.test(Sym->DeclContextKind)) + continue; + if (!Req.IncludeInScopedEnum && Sym->InScopedEnum) + 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 @@ -110,21 +110,12 @@ 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. + // 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())))), unless(IsSpecialization))), *ND, *ASTCtx) .empty()) @@ -319,6 +310,11 @@ getSymbolLocation(ND, SM, Opts, ASTCtx->getLangOpts(), FileURI)) S.CanonicalDeclaration = *DeclLoc; + using namespace clang::ast_matchers; + S.DeclContextKind = ND.getDeclContext()->getDeclKind(); + S.InScopedEnum = + !match(decl(hasDeclContext(enumDecl(isScoped()))), ND, *ASTCtx).empty(); + // Add completion info. // FIXME: we may want to choose a different redecl, or combine from several. assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set."); Index: clangd/index/SymbolYAML.cpp =================================================================== --- clangd/index/SymbolYAML.cpp +++ clangd/index/SymbolYAML.cpp @@ -9,6 +9,7 @@ #include "SymbolYAML.h" #include "Index.h" +#include "clang/AST/DeclBase.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/Errc.h" #include "llvm/Support/MemoryBuffer.h" @@ -95,6 +96,26 @@ llvm::Optional Opt; }; +template <> struct ScalarTraits { + static void output(const clang::Decl::Kind &Value, void *Ctx, + raw_ostream &OS) { + return ScalarTraits::output(static_cast(Value), Ctx, + OS); + } + + static StringRef input(StringRef Scalar, void *Ctx, + clang::Decl::Kind &Value) { + unsigned Val; + auto Err = ScalarTraits::input(Scalar, Ctx, Val); + Value = static_cast(Val); + return Err; + } + + static QuotingType mustQuote(StringRef Scalar) { + return ScalarTraits::mustQuote(Scalar); + } +}; + template <> struct MappingTraits { static void mapping(IO &IO, Symbol &Sym) { MappingNormalization NSymbolID(IO, Sym.ID); @@ -108,6 +129,9 @@ SymbolLocation()); IO.mapOptional("Definition", Sym.Definition, SymbolLocation()); IO.mapOptional("References", Sym.References, 0u); + IO.mapOptional("InScopedEnum", Sym.InScopedEnum, false); + IO.mapOptional("DeclContextKind", Sym.DeclContextKind, + static_cast(0)); 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; @@ -115,7 +116,8 @@ // Helpers to produce fake index symbols for memIndex() or completions(). // USRFormat is a regex replacement string for the unqualified part of the USR. -Symbol sym(StringRef QName, index::SymbolKind Kind, StringRef USRFormat) { +Symbol sym(StringRef QName, index::SymbolKind Kind, StringRef USRFormat, + Decl::Kind ContextKind) { Symbol Sym; std::string USR = "c:"; // We synthesize a few simple cases of USRs by hand! size_t Pos = QName.rfind("::"); @@ -133,19 +135,27 @@ Sym.CompletionSnippetInsertText = Sym.Name; Sym.CompletionLabel = Sym.Name; Sym.SymInfo.Kind = Kind; + Sym.DeclContextKind = ContextKind; return Sym; } -Symbol func(StringRef Name) { // Assumes the function has no args. - return sym(Name, index::SymbolKind::Function, "@F@\\0#"); // no args +Symbol +func(StringRef Name, + Decl::Kind ContextKind = + Decl::Kind::TranslationUnit) { // Assumes the function has no args. + return sym(Name, index::SymbolKind::Function, "@F@\\0#", + ContextKind); // no args } -Symbol cls(StringRef Name) { - return sym(Name, index::SymbolKind::Class, "@S@\\0@S@\\0"); +Symbol cls(StringRef Name, + Decl::Kind ContextKind = Decl::Kind::TranslationUnit) { + return sym(Name, index::SymbolKind::Class, "@S@\\0@S@\\0", ContextKind); } -Symbol var(StringRef Name) { - return sym(Name, index::SymbolKind::Variable, "@\\0"); +Symbol var(StringRef Name, + Decl::Kind ContextKind = Decl::Kind::TranslationUnit) { + return sym(Name, index::SymbolKind::Variable, "@\\0", ContextKind); } -Symbol ns(StringRef Name) { - return sym(Name, index::SymbolKind::Namespace, "@N@\\0"); +Symbol ns(StringRef Name, + Decl::Kind ContextKind = Decl::Kind::TranslationUnit) { + return sym(Name, index::SymbolKind::Namespace, "@N@\\0", ContextKind); } Symbol withReferences(int N, Symbol S) { S.References = N; @@ -436,7 +446,7 @@ namespace fake { int Babble, Ball; }; int main() { fake::bb^ } ")cpp", - {var("fake::BigBang")}); + {var("fake::BigBang", Decl::Kind::Namespace)}); EXPECT_THAT(Results.items, ElementsAre(Named("BigBang"), Named("Babble"))); } @@ -445,7 +455,8 @@ R"cpp( void f() { ns::x^ } )cpp", - {cls("ns::XYZ"), func("ns::foo")}); + {cls("ns::XYZ", Decl::Kind::Namespace), + func("ns::foo", Decl::Kind::Namespace)}); EXPECT_THAT(Results.items, UnorderedElementsAre(AllOf(Named("XYZ"), Filter("XYZ")))); } @@ -474,7 +485,7 @@ namespace ns { void bar(); } void f() { ::ns::^ } )cpp", - {cls("ns::XYZ")}); + {cls("ns::XYZ", Decl::Kind::Namespace)}); EXPECT_THAT(Results.items, AllOf(Has("XYZ", CompletionItemKind::Class), Has("bar", CompletionItemKind::Function))); } @@ -485,7 +496,8 @@ namespace ns { int local; void both(); } void f() { ::ns::^ } )cpp", - {func("ns::both"), cls("ns::Index")}); + {func("ns::both", Decl::Kind::Namespace), + cls("ns::Index", Decl::Kind::Namespace)}); // We get results from both index and sema, with no duplicates. EXPECT_THAT( Results.items, @@ -500,7 +512,9 @@ namespace ns { int local; void both(); } void f() { ::ns::^ } )cpp", - {func("ns::both"), cls("ns::Index")}, Opts); + {func("ns::both", Decl::Kind::Namespace), + cls("ns::Index", Decl::Kind::Namespace)}, + Opts); EXPECT_EQ(Results.items.size(), Opts.Limit); EXPECT_TRUE(Results.isIncomplete); } @@ -523,7 +537,7 @@ runAddDocument(Server, File, Test.code()); clangd::CodeCompleteOptions Opts = {}; - auto I = memIndex({var("ns::index")}); + auto I = memIndex({var("ns::index", Decl::Kind::Namespace)}); Opts.Index = I.get(); auto WithIndex = cantFail(runCodeComplete(Server, File, Test.point(), Opts)); EXPECT_THAT(WithIndex.items, @@ -576,6 +590,33 @@ Doc("Doooc"), Detail("void")))); } +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; } @@ -633,6 +674,142 @@ 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; + CDB.ExtraClangFlags.push_back("-std=c++11"); + 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 @@ -170,7 +170,7 @@ EXPECT_THAT(match(M, FuzzyFindRequest()), UnorderedElementsAre()); } -TEST(FileIndexTest, IgnoreClassMembers) { +TEST(FileIndexTest, ClassMembers) { FileIndex M; M.update("f1", build("f1", "class X { static int m1; int m2; static void f(); };") @@ -178,7 +178,8 @@ 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) { 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(InScopedEnum, InScopedEnum, "") { + return arg.InScopedEnum == InScopedEnum; +} namespace clang { namespace clangd { @@ -198,25 +201,28 @@ } // 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::f"), 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, Locations) { @@ -334,7 +340,7 @@ Green }; enum class Color2 { - Yellow // ignore + Yellow }; namespace ns { enum { @@ -343,20 +349,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 +424,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 +439,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 +541,7 @@ End: Line: 1 Column: 1 +InScopedEnum: true CompletionLabel: 'Foo1-label' CompletionFilterText: 'filter' CompletionPlainInsertText: 'plain' @@ -555,6 +566,7 @@ End: Line: 1 Column: 1 +InScopedEnum: false CompletionLabel: 'Foo2-label' CompletionFilterText: 'filter' CompletionPlainInsertText: 'plain' @@ -565,13 +577,15 @@ auto Symbols1 = SymbolsFromYAML(YAML1); EXPECT_THAT(Symbols1, - UnorderedElementsAre(AllOf( - QName("clang::Foo1"), Labeled("Foo1-label"), Doc("Foo doc"), - Detail("int"), DeclURI("file:///path/foo.h")))); + UnorderedElementsAre( + AllOf(QName("clang::Foo1"), Labeled("Foo1-label"), + Doc("Foo doc"), Detail("int"), + DeclURI("file:///path/foo.h"), InScopedEnum(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"), InScopedEnum(false)))); std::string ConcatenatedYAML; { @@ -662,23 +676,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) {