Index: clangd/AST.h =================================================================== --- clangd/AST.h +++ clangd/AST.h @@ -14,6 +14,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H_ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H_ +#include "clang/AST/Decl.h" #include "clang/Basic/SourceLocation.h" namespace clang { @@ -28,6 +29,10 @@ /// decl occurs in the code. SourceLocation findNameLoc(const clang::Decl *D); +/// Returns the qualified name of ND. The scope doesn't contain unwritten scopes +/// like inline namespaces. +std::string printQualifiedName(const NamedDecl &ND); + } // namespace clangd } // namespace clang Index: clangd/AST.cpp =================================================================== --- clangd/AST.cpp +++ clangd/AST.cpp @@ -38,5 +38,20 @@ return SpellingLoc; } +std::string printQualifiedName(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 clangd } // namespace clang Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -19,6 +19,7 @@ //===---------------------------------------------------------------------===// #include "CodeComplete.h" +#include "AST.h" #include "CodeCompletionStrings.h" #include "Compiler.h" #include "FuzzyMatch.h" @@ -243,6 +244,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) { @@ -253,8 +256,14 @@ I.filterText = Text; 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(printQualifiedName(*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/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -363,20 +363,9 @@ 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::string QName = printQualifiedName(ND); std::tie(S.Scope, S.Name) = splitQualifiedName(QName); // FIXME: this returns foo:bar: for objective-C methods, we prefer only foo: // for consistency with CodeCompletionString and a clean name/signature split. Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -44,6 +44,7 @@ // GMock helpers for matching completion items. MATCHER_P(Named, Name, "") { return arg.insertText == Name; } +MATCHER_P(Scope, Name, "") { return arg.SymbolScope == Name; } MATCHER_P(Labeled, Label, "") { std::string Indented; if (!StringRef(Label).startswith( @@ -1251,6 +1252,17 @@ 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(Scope("ns::"), Scope("ns::"), + Scope("ns::"))); +} } // namespace } // namespace clangd