Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -23,6 +23,7 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Sema/CodeCompleteConsumer.h" +#include "clang/Sema/Lookup.h" #include "clang/Sema/Sema.h" #include "llvm/Support/Format.h" #include @@ -258,16 +259,34 @@ } }; -/// \brief Information about the scope specifier in the qualified-id code -/// completion (e.g. "ns::ab?"). -struct SpecifiedScope { - /// The scope specifier as written. For example, for completion "ns::ab?", the - /// written scope specifier is "ns". - std::string Written; - // If this scope specifier is recognized in Sema (e.g. as a namespace - // context), this will be set to the fully qualfied name of the corresponding - // context. - std::string Resolved; +/// \brief Scopes being queried in indexes for the qualified-id code completion +/// (e.g. "ns::ab?"). +struct QueryScopes { + /// All scopes being queried in indexes. Each scope should meet the + /// restrictions described in FuzzyFindRequest::Scopes. + /// + /// The scopes are constructed from the written scope specifier as well as all + /// namespace scopes which are visible to the qualified-id completion token. + std::vector Scopes; +}; + +class VisibleNamespaceFinder : public VisibleDeclConsumer { +public: + void FoundDecl(NamedDecl *ND, NamedDecl *Hiding, DeclContext *Ctx, + bool InBaseClass) override {} + + void BeginVisitContext(DeclContext *Ctx) override { + // We only interest in namespace declarations. + if (const auto *NN = dyn_cast(Ctx)) + VisibleNamespaces.insert(NN->getQualifiedNameAsString()); + } + + const std::set &getVisibleNamespaces() const { + return VisibleNamespaces; + } + +private: + std::set VisibleNamespaces; }; /// \brief Information from sema about (parital) symbol names to be completed. @@ -278,10 +297,11 @@ std::string Filter; /// This is set if the completion is for qualified IDs, e.g. "abc::x^". - llvm::Optional SSInfo; + llvm::Optional QScopes; }; -SpecifiedScope extraCompletionScope(Sema &S, const CXXScopeSpec &SS); +QueryScopes extraCompletionScope( + Sema &S, const CodeCompletionContext::QualifiedCompletionContext &QCC); class CompletionItemsCollector : public CodeCompleteConsumer { public: @@ -298,8 +318,11 @@ CodeCompletionResult *Results, unsigned NumResults) override final { FuzzyMatcher Filter(S.getPreprocessor().getCodeCompletionFilter()); - if (auto SS = Context.getCXXScopeSpecifier()) - CompletedName.SSInfo = extraCompletionScope(S, **SS); + if (auto QualifiedCompletionContext = + Context.getQualifiedCompletionContext()) { + CompletedName.QScopes = + extraCompletionScope(S, **QualifiedCompletionContext); + } CompletedName.Filter = S.getPreprocessor().getCodeCompletionFilter(); std::priority_queue Candidates; @@ -557,7 +580,6 @@ } CompletionItem indexCompletionItem(const Symbol &Sym, llvm::StringRef Filter, - const SpecifiedScope &SSInfo, llvm::StringRef DebuggingLabel = "") { CompletionItem Item; Item.kind = toCompletionItemKind(Sym.SymInfo.Kind); @@ -597,38 +619,53 @@ } void completeWithIndex(const Context &Ctx, const SymbolIndex &Index, - llvm::StringRef Code, const SpecifiedScope &SSInfo, + llvm::StringRef Code, const QueryScopes &QScopes, llvm::StringRef Filter, CompletionList *Items, llvm::StringRef DebuggingLabel = "") { FuzzyFindRequest Req; Req.Query = Filter; - // FIXME(ioeric): add more possible scopes based on using namespaces and - // containing namespaces. - StringRef Scope = SSInfo.Resolved.empty() ? SSInfo.Written : SSInfo.Resolved; - Req.Scopes = {Scope.trim(':').str()}; - + Req.Scopes = QScopes.Scopes, log(Ctx, "Query scopes: ["); + for (auto &R : Req.Scopes) + log(Ctx, R); + log(Ctx, "]"); Items->isIncomplete |= !Index.fuzzyFind(Ctx, Req, [&](const Symbol &Sym) { - Items->items.push_back( - indexCompletionItem(Sym, Filter, SSInfo, DebuggingLabel)); + Items->items.push_back(indexCompletionItem(Sym, Filter, DebuggingLabel)); }); } -SpecifiedScope extraCompletionScope(Sema &S, const CXXScopeSpec &SS) { - SpecifiedScope Info; +QueryScopes extraCompletionScope( + Sema &S, const CodeCompletionContext::QualifiedCompletionContext &QCC) { + QueryScopes Info; auto &SM = S.getSourceManager(); - auto SpecifierRange = SS.getRange(); - Info.Written = Lexer::getSourceText( - CharSourceRange::getCharRange(SpecifierRange), SM, clang::LangOptions()); + const auto &SS = QCC.ScopeSpecifier; if (SS.isValid()) { + // FIXME: because of Sema typo correction, the namespace decl returned from + // Sema might not perfectly match the written qualifier. For example, a user + // types "clangd::" while there is a mere `namespace clang {}` in the + // current file, we will get `clang` namespace declaration here. We might + // want to disable the typo correction in Sema during code completion. + // + // FIXME: Add possible namespace scopes which are visible in DC. DeclContext *DC = S.computeDeclContext(SS); - if (auto *NS = llvm::dyn_cast(DC)) { - Info.Resolved = NS->getQualifiedNameAsString(); + if (auto *NS = dyn_cast(DC)) { + Info.Scopes.push_back(NS->getQualifiedNameAsString()); } else if (llvm::dyn_cast(DC) != nullptr) { - Info.Resolved = "::"; - // Sema does not include the suffix "::" in the range of SS, so we add - // it back here. - Info.Written = "::"; + // No explicit "::" for global namespace. + Info.Scopes.push_back(""); } + } else { + VisibleNamespaceFinder Finder; + S.LookupVisibleDecls(QCC.ClosestScope, + clang::Sema::LookupNameKind::LookupOrdinaryName, + Finder, true); + auto SpecifierRange = SS.getRange(); + StringRef WrittenScope = + Lexer::getSourceText(CharSourceRange::getCharRange(SpecifierRange), SM, + clang::LangOptions()) + .trim(':'); + Info.Scopes.push_back(WrittenScope); + for (auto &It : Finder.getVisibleNamespaces()) + Info.Scopes.push_back(It + "::" + WrittenScope.str()); } return Info; } @@ -668,8 +705,8 @@ // Got scope specifier (ns::f^) for code completion from sema, try to query // global symbols from indexes. // FIXME: merge with Sema results, and respect limits. - if (CompletedName.SSInfo && Opts.Index) - completeWithIndex(Ctx, *Opts.Index, Contents, *CompletedName.SSInfo, + if (CompletedName.QScopes && Opts.Index) + completeWithIndex(Ctx, *Opts.Index, Contents, *CompletedName.QScopes, CompletedName.Filter, &Results, /*DebuggingLabel=*/"I"); return Results; } Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -58,6 +58,7 @@ using ::testing::Each; using ::testing::ElementsAre; using ::testing::Not; +using ::testing::Field; using ::testing::UnorderedElementsAre; class IgnoreDiagnostics : public DiagnosticsConsumer { @@ -481,6 +482,21 @@ EXPECT_EQ(1, Results.activeParameter); } +class IndexRequestCollector : public SymbolIndex { +public: + bool + fuzzyFind(const Context &Ctx, const FuzzyFindRequest &Req, + llvm::function_ref Callback) const override { + Requests.push_back(Req); + return false; + } + + const std::vector allRequests() const { return Requests; } + +private: + mutable std::vector Requests; +}; + std::unique_ptr simpleIndexFromSymbols( std::vector> Symbols) { SymbolSlab::Builder Slab; @@ -660,6 +676,48 @@ Doc("Doooc"), Detail("void")))); } +TEST(CompletionTest, AllVisibleScopesChain) { + clangd::CodeCompleteOptions Opts; + IndexRequestCollector Requests; + Opts.Index = &Requests; + + auto Results = completions(R"cpp( + namespace ns1 {} + namespace ns2 {} // ignore + namespace ns3 {} + + namespace ns4 { using namespace ns3; } + using namespace ns1; + namespace ns5 { using namespace ns4; } + namespace ns5 { bar::^ } + )cpp", + Opts); + + EXPECT_THAT( + Requests.allRequests(), + ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("bar", "ns4::bar", "ns3::bar", + "ns5::bar", "ns1::bar")))); +} + +TEST(CompletionTest, AllVisibleScopesNested) { + clangd::CodeCompleteOptions Opts; + IndexRequestCollector Requests; + Opts.Index = &Requests; + + auto Results = completions(R"cpp( + namespace ns1 { namespace ns4 {} } + namespace ns1 { namespace ns2 { namespace ns3 { bar::^ } } } + )cpp", + Opts); + + EXPECT_THAT( + Requests.allRequests(), + ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("bar", "ns1::bar", "ns1::ns2::bar", + "ns1::ns2::ns3::bar")))); +} + } // namespace } // namespace clangd } // namespace clang