Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -243,6 +243,8 @@ const IncludeInserter &Includes, llvm::StringRef SemaDocComment) const { assert(bool(SemaResult) == bool(SemaCCS)); + assert(SemaResult || IndexResult); + CompletionItem I; bool InsertingInclude = false; // Whether a new #include will be added. if (SemaResult) { @@ -252,8 +254,14 @@ I.filterText = getFilterText(*SemaCCS); I.documentation = formatDocumentation(*SemaCCS, SemaDocComment); I.detail = getDetail(*SemaCCS); + if (SemaResult->Kind == CodeCompletionResult::RK_Declaration) + if (const auto *D = SemaResult->getDeclaration()) + if (const auto *ND = llvm::dyn_cast(D)) + I.SymbolScope = splitQualifiedName(*ND).first; } if (IndexResult) { + if (I.SymbolScope.empty()) + I.SymbolScope = IndexResult->Scope; if (I.kind == CompletionItemKind::Missing) I.kind = toCompletionItemKind(IndexResult->SymInfo.Kind); // FIXME: reintroduce a way to show the index source for debugging. Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -734,6 +734,12 @@ // // data?: any - A data entry field that is preserved on a completion item // between a completion and a completion resolve request. + + // C++ extension that is only expected to be used by users of ClangdServer's + // C++ API. Not serialized from/to json. + /// The containing scope (e.g. namespace) of the symbol this item corresponds + /// to, e.g. "" (global scope), "ns::" (top-level namespace). + std::string SymbolScope; }; json::Expr toJSON(const CompletionItem &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const CompletionItem &); Index: clangd/SourceCode.h =================================================================== --- clangd/SourceCode.h +++ clangd/SourceCode.h @@ -14,6 +14,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H #include "Protocol.h" +#include "clang/AST/Decl.h" #include "clang/Basic/SourceLocation.h" #include "clang/Tooling/Core/Replacement.h" @@ -56,6 +57,10 @@ std::pair splitQualifiedName(llvm::StringRef QName); +/// Splits the qualified name of ND. The scope doesn't contain unwritten scopes +/// like inline namespaces. +std::pair splitQualifiedName(const NamedDecl &ND); + TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R); std::vector replacementsToEdits(StringRef Code, Index: clangd/SourceCode.cpp =================================================================== --- clangd/SourceCode.cpp +++ clangd/SourceCode.cpp @@ -8,6 +8,9 @@ //===----------------------------------------------------------------------===// #include "SourceCode.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/PrettyPrinter.h" #include "clang/Basic/SourceManager.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" @@ -166,6 +169,23 @@ return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)}; } +std::pair +splitQualifiedName(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 splitQualifiedName(QName); +} + TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R) { Range ReplacementRange = { offsetToPosition(Code, R.getOffset()), Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -363,21 +363,10 @@ 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("::")); - Symbol S; S.ID = std::move(ID); - std::tie(S.Scope, S.Name) = splitQualifiedName(QName); + auto ScopeAndName = splitQualifiedName(ND); + std::tie(S.Scope, S.Name) = ScopeAndName; S.IsIndexedForCodeCompletion = isIndexedForCodeCompletion(ND, Ctx); S.SymInfo = index::getSymbolInfo(&ND); @@ -413,8 +402,9 @@ if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { // Use the expansion location to get the #include header since this is // where the symbol is exposed. - if (auto Header = getIncludeHeader( - QName, SM, SM.getExpansionLoc(ND.getLocation()), Opts)) + if (auto Header = + getIncludeHeader(ScopeAndName.first + ScopeAndName.second, SM, + SM.getExpansionLoc(ND.getLocation()), Opts)) Include = std::move(*Header); } S.CompletionFilterText = FilterText; Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -44,6 +44,9 @@ // GMock helpers for matching completion items. MATCHER_P(Named, Name, "") { return arg.insertText == Name; } +MATCHER_P(QName, Name, "") { + return (arg.SymbolScope + arg.filterText) == Name; +} MATCHER_P(Labeled, Label, "") { std::string Indented; if (!StringRef(Label).startswith( @@ -1251,6 +1254,18 @@ Failed()); } +TEST(CompletionTest, QualifiedNames) { + auto Results = completions( + R"cpp( + namespace ns { int local; void both(); } + void f() { ::ns::^ } + )cpp", + {func("ns::both"), cls("ns::Index")}); + // We get results from both index and sema, with no duplicates. + EXPECT_THAT(Results.items, + UnorderedElementsAre(QName("ns::local"), QName("ns::Index"), + QName("ns::both"))); +} } // namespace } // namespace clangd Index: unittests/clangd/SourceCodeTests.cpp =================================================================== --- unittests/clangd/SourceCodeTests.cpp +++ unittests/clangd/SourceCodeTests.cpp @@ -7,11 +7,13 @@ // //===----------------------------------------------------------------------===// #include "SourceCode.h" +#include "TestTU.h" #include "llvm/Support/Error.h" #include "llvm/Support/raw_os_ostream.h" #include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include namespace clang{ namespace clangd { @@ -119,6 +121,24 @@ EXPECT_THAT(offsetToPosition(File, 30), Pos(2, 9)) << "out of bounds"; } +std::pair makePair(StringRef x, StringRef y) { + return std::make_pair(x, y); +} + +TEST(SplitQualifiedName, SplitQName) { + auto TU = TestTU::withCode("namespace nx { class X; }"); + auto AST = TU.build(); + EXPECT_EQ(makePair("", "nx"), splitQualifiedName(findDecl(AST, "nx"))); + EXPECT_EQ(makePair("nx::", "X"), splitQualifiedName(findDecl(AST, "nx::X"))); +} + +TEST(SplitQualifiedName, NoInlineNamespace) { + auto TU = + TestTU::withCode("namespace nx { inline namespace ny { class X; } }"); + auto AST = TU.build(); + EXPECT_EQ(makePair("nx::", "X"), splitQualifiedName(findAnyDecl(AST, "X"))); +} + } // namespace } // namespace clangd } // namespace clang