Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -9,6 +9,7 @@ #include "SymbolCollector.h" #include "../CodeCompletionStrings.h" +#include "Logger.h" #include "clang/AST/DeclCXX.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/SourceManager.h" @@ -63,14 +64,33 @@ return AbsolutePath.str(); } -// "a::b::c", return {"a::b::", "c"}. Scope is empty if there's no qualifier. -std::pair -splitQualifiedName(llvm::StringRef QName) { - assert(!QName.startswith("::") && "Qualified names should not start with ::"); - size_t Pos = QName.rfind("::"); - if (Pos == llvm::StringRef::npos) - return {StringRef(), QName}; - return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)}; +// For a symbol "a::b::c", return "a::b::". Scope is empty if there's no +// qualifier. Inline namespaces and unscoped enums are skipped. +llvm::Expected getScope(const NamedDecl *ND) { + llvm::SmallVector Contexts; + for (const auto *Context = ND->getDeclContext(); Context; + Context = Context->getParent()) { + if (llvm::isa(Context) || + llvm::isa(Context)) + break; + + if (const auto *NSD = dyn_cast(Context)) { + if (!NSD->isInlineNamespace()) + Contexts.push_back(NSD->getName()); + } else if (const auto *ED = dyn_cast(Context)) { + if (ED->isScoped()) + Contexts.push_back(ED->getName()); + } else { + return llvm::make_error( + llvm::Twine("Unexpected context type ") + Context->getDeclKindName() + + " for symbol " + ND->getQualifiedNameAsString(), + llvm::inconvertibleErrorCode()); + } + } + std::string Scope = llvm::join(Contexts.rbegin(), Contexts.rend(), "::"); + if (!Scope.empty()) + Scope.append("::"); + return Scope; } bool shouldFilterDecl(const NamedDecl *ND, ASTContext *ASTCtx, @@ -172,6 +192,8 @@ if (shouldFilterDecl(ND, ASTCtx, Opts)) return true; llvm::SmallString<128> USR; + if (ND->getIdentifier() == nullptr) + return true; if (index::generateUSRForDecl(ND, USR)) return true; @@ -180,11 +202,17 @@ return true; auto &SM = ND->getASTContext().getSourceManager(); - std::string QName = ND->getQualifiedNameAsString(); + auto Scope = getScope(ND); + if (!Scope) { + log(llvm::toString(Scope.takeError())); + return true; + } + std::string Name = ND->getName(); Symbol S; S.ID = std::move(ID); - std::tie(S.Scope, S.Name) = splitQualifiedName(QName); + S.Scope = *Scope; + S.Name = Name; S.SymInfo = index::getSymbolInfo(D); std::string FilePath; S.CanonicalDeclaration = GetSymbolLocation(ND, SM, Opts.FallbackDir, FilePath); Index: unittests/clangd/SymbolCollectorTests.cpp =================================================================== --- unittests/clangd/SymbolCollectorTests.cpp +++ unittests/clangd/SymbolCollectorTests.cpp @@ -201,8 +201,7 @@ runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Red"), QName("Color"), QName("Green"), QName("Color2"), - QName("ns"), - QName("ns::Black"))); + QName("ns"), QName("ns::Black"))); } TEST_F(SymbolCollectorTest, IgnoreNamelessSymbols) { @@ -324,6 +323,41 @@ EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"))); } +TEST_F(SymbolCollectorTest, Scopes) { + const std::string Header = R"( + namespace na { + class Foo {}; + namespace nb { + class Bar {}; + } + } + )"; + runSymbolCollector(Header, /*Main=*/""); + EXPECT_THAT(Symbols, + UnorderedElementsAre(QName("na"), QName("na::nb"), + QName("na::Foo"), QName("na::nb::Bar"))); +} + +TEST_F(SymbolCollectorTest, SkipInlineNamespace) { + const std::string Header = R"( + namespace na { + inline namespace nb { + class Foo {}; + } + } + namespace na { + // This is still inlined. + namespace nb { + class Bar {}; + } + } + )"; + runSymbolCollector(Header, /*Main=*/""); + EXPECT_THAT(Symbols, + UnorderedElementsAre(QName("na"), QName("na::nb"), + QName("na::Foo"), QName("na::Bar"))); +} + TEST_F(SymbolCollectorTest, SymbolWithDocumentation) { const std::string Header = R"( namespace nx {