Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -264,6 +264,21 @@ match(decl(isExpansionInMainFile()), ND, ND.getASTContext()).empty(); } +std::string getQualifiedName(const NamedDecl &ND) { + std::string QName; + llvm::raw_string_ostream OS(QName); + PrintingPolicy Policy(ND.getASTContext().getLangOpts()); + // Note that inline namespaces are treated as transparent scopes. This + // reflects the way they're most commonly used for lookup. Ideally we'd + // include them, but at query time it's hard to find all the inline + // namespaces to query: the preamble doesn't have a dedicated list. + Policy.SuppressUnwrittenScope = true; + ND.printQualifiedName(OS, Policy); + OS.flush(); + assert(!StringRef(QName).startswith("::")); + return QName; +} + } // namespace SymbolCollector::SymbolCollector(Options Opts) : Opts(std::move(Opts)) {} @@ -341,21 +356,29 @@ auto &Ctx = ND.getASTContext(); auto &SM = Ctx.getSourceManager(); - std::string QName; - llvm::raw_string_ostream OS(QName); - PrintingPolicy Policy(ASTCtx->getLangOpts()); - // Note that inline namespaces are treated as transparent scopes. This - // reflects the way they're most commonly used for lookup. Ideally we'd - // include them, but at query time it's hard to find all the inline - // namespaces to query: the preamble doesn't have a dedicated list. - Policy.SuppressUnwrittenScope = true; - ND.printQualifiedName(OS, Policy); - OS.flush(); - assert(!StringRef(QName).startswith("::")); + std::string QName = getQualifiedName(ND); Symbol S; S.ID = std::move(ID); std::tie(S.Scope, S.Name) = splitQualifiedName(QName); + + using namespace clang::ast_matchers; + // For enumerators in unscoped enums that have names, even if they are not + // scoped, we add the enum name to the scope so that users can find the + // enumerators when fully qualifying them, for example: MyEnum::Enumerator. + auto InUnscopedEnum = + match(decl(hasDeclContext(enumDecl(unless(isScoped())).bind("enum"))), ND, + *ASTCtx); + std::string EnumQName; + if (!InUnscopedEnum.empty()) { + auto Enum = InUnscopedEnum[0].getNodeAs("enum"); + if (Enum->getDeclName()) { + EnumQName = getQualifiedName(*Enum); + EnumQName += "::"; + S.Scope = EnumQName; + } + } + S.SymInfo = index::getSymbolInfo(&ND); std::string FileURI; if (auto DeclLoc = Index: unittests/clangd/SymbolCollectorTests.cpp =================================================================== --- unittests/clangd/SymbolCollectorTests.cpp +++ unittests/clangd/SymbolCollectorTests.cpp @@ -343,9 +343,9 @@ } )"; 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("Color::Green"), + QName("Color2"), QName("ns"), QName("ns::Black"))); } TEST_F(SymbolCollectorTest, IgnoreNamelessSymbols) { @@ -726,10 +726,10 @@ bool operator<(const TopLevel &, const TopLevel &); })"; runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre(QName("nx"), QName("nx::TopLevel"), - QName("nx::Kind"), QName("nx::KIND_OK"), - QName("nx::operator<"))); + EXPECT_THAT(Symbols, UnorderedElementsAre(QName("nx"), QName("nx::TopLevel"), + QName("nx::Kind"), + QName("nx::Kind::KIND_OK"), + QName("nx::operator<"))); } TEST_F(SymbolCollectorTest, DoubleCheckProtoHeaderComment) { @@ -743,9 +743,9 @@ } )"; runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre(QName("nx"), QName("nx::Top_Level"), - QName("nx::Kind"), QName("nx::Kind_Fine"))); + EXPECT_THAT(Symbols, UnorderedElementsAre(QName("nx"), QName("nx::Top_Level"), + QName("nx::Kind"), + QName("nx::Kind::Kind_Fine"))); } } // namespace