Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -309,20 +309,52 @@ llvm_unreachable("unknown CodeCompletionResult kind"); } -/// \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; - - llvm::StringRef forIndex() { - llvm::StringRef Chosen = Resolved.empty() ? Written : Resolved; - return Chosen.trim(':'); +StringRef normalizeScope(StringRef Q) { + return Q.trim(':'); +} + +/// \brief Scopes being queried in indexes for the qualified-id code completion +/// (e.g. "ns::ab^"). +// +// FIXME: we might want to make Sema code completion smarter on handling +// unresolved qualified-id completion. +// +// E.g. foo::ns1::ns2:: +// ^ ^ unresolved qualifier +// ^~~ resolved qualifier +// Instead of getting the whole unresolved qualifier "foo::ns1::ns2::", a better +// way is to return an unresolved qualifier "ns1::ns2" with all scopes that are +// accessible in "foo". +struct QualifiedScopes { + // All scopes that are accessible in the completion scope qualifier. + // + // If the completion scope qualifier is: + // * resolved by Sema, it contains fully qualified names of all accessible + // scopes (namespace) in the resolved qualifier; + // * unresolved by Sema, global namespace "::" is the only accessible scope. + // + // See examples: + // "::vec" => {"::"} + // "using namespace std; ::vec^" => {"::", "std"} + // "namespace ns {using namespace std;} ns::^" => {"ns", "std"} + // "std::vec^" => {"::"} // unresolved "std::" + std::vector AccessibleScopes; + // The scope qualifier that is not resolved in Sema, it is the user-written + // qualifiers. + llvm::Optional UnresolvedQualifier; + + std::vector forIndex() { + std::vector Results; + for (llvm::StringRef VS : AccessibleScopes) { + std::string QueryScope = + (normalizeScope(VS) + + (UnresolvedQualifier + ? "::" + normalizeScope(*UnresolvedQualifier) + : "")) + .str(); + Results.push_back(normalizeScope(QueryScope)); + } + return Results; } }; @@ -625,22 +657,27 @@ return true; } -SpecifiedScope getSpecifiedScope(Sema &S, const CXXScopeSpec &SS) { - SpecifiedScope Info; - auto &SM = S.getSourceManager(); - auto SpecifierRange = SS.getRange(); - Info.Written = Lexer::getSourceText( - CharSourceRange::getCharRange(SpecifierRange), SM, clang::LangOptions()); - if (SS.isValid()) { - DeclContext *DC = S.computeDeclContext(SS); - if (auto *NS = llvm::dyn_cast(DC)) { - Info.Resolved = 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 = "::"; +QualifiedScopes getQualifiedScopes(Sema &S, const CXXScopeSpec &SS, + const CodeCompletionContext &CCContext) { + QualifiedScopes Info; + if (SS.isValid()) { // Resolved qualifier. + // FIXME: Disable Sema typo correction dureing code completion. + // The resolved qualifier might not perfectly match the written qualifier. + // e.g. "namespace clang { clangd::^ }", we will get "clang" declaration + // for completion "clangd::". + // + // Add all namespaces that are accessible from `DC` (including `DC` itself). + for (const auto* Ctx : CCContext.getVisitedContexts()) { + if (isa(Ctx)) + Info.AccessibleScopes.push_back("::"); // global namespace + else if (const auto* VNS = dyn_cast(Ctx)) + Info.AccessibleScopes.push_back(VNS->getQualifiedNameAsString()); } + } else { // Unresolved qualifier. + Info.AccessibleScopes.push_back("::"); // global namespace + Info.UnresolvedQualifier = + Lexer::getSourceText(CharSourceRange::getCharRange( + SS.getRange()), S.getSourceManager(), clang::LangOptions()); } return Info; } @@ -805,15 +842,33 @@ FuzzyFindRequest Req; Req.Query = Filter->pattern(); // If the user typed a scope, e.g. a::b::xxx(), restrict to that scope. - // FIXME(ioeric): add scopes based on using directives and enclosing ns. - if (auto SS = Recorder.CCContext.getCXXScopeSpecifier()) - Req.Scopes = {getSpecifiedScope(*Recorder.CCSema, **SS).forIndex()}; - else - // Unless the user typed a ns qualifier, complete in global scope only. - // FIXME: once we know what namespaces are in scope (D42073), use those. + if (auto SS = Recorder.CCContext.getCXXScopeSpecifier()) { + Req.Scopes = + getQualifiedScopes(*Recorder.CCSema, **SS, Recorder.CCContext) + .forIndex(); + } else { + // Unqualified code completion. Use the containing namespace and all + // accessible namespaces to query symbols from index, for examples: + // "vec^" => {"::"} + // "using namespace std; vec^" => {"::", "std"} + // "using namespace std; namespace ns { vec^ }" => {"ns", "std", "::"} + // // FIXME: once we can insert namespace qualifiers and use the in-scope // namespaces for scoring, search in all namespaces. - Req.Scopes = {""}; + // FIXME: Capture scopes and use for scoring, for example, + // "using namespace std; namespace foo {v^}" => + // foo::value > std::vector > boost::variant + for (auto* Context : Recorder.CCContext.getVisitedContexts()) { + if (isa(Context)) + Req.Scopes.push_back(normalizeScope("::")); // global namespace + else if (const auto*NS = dyn_cast(Context)) + Req.Scopes.push_back(normalizeScope(NS->getQualifiedNameAsString())); + } + } + log(Ctx, "Query scopes: ["); + for (auto &R : Req.Scopes) + log(Ctx, R); + log(Ctx, "]"); // Run the query against the index. Incomplete |= !Opts.Index->fuzzyFind( Ctx, Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); }); Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -58,6 +58,7 @@ using ::testing::ElementsAre; using ::testing::Not; using ::testing::UnorderedElementsAre; +using ::testing::Field; class IgnoreDiagnostics : public DiagnosticsConsumer { void @@ -676,6 +677,123 @@ 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; +}; + +TEST(CompletionTest, UnqualifiedIdQuery) { + clangd::CodeCompleteOptions Opts; + IndexRequestCollector Requests; + Opts.Index = &Requests; + + auto Results = completions(R"cpp( + namespace std {} + using namespace std; + namespace ns { + void f() { + vec^ + } + } + )cpp", {}, Opts); + + EXPECT_THAT(Requests.allRequests(), + ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("", "ns", "std")))); +} + +TEST(CompletionTest, ResolvedQualifiedIdQuery) { + clangd::CodeCompleteOptions Opts; + IndexRequestCollector Requests; + Opts.Index = &Requests; + + auto Results = completions(R"cpp( + namespace ns1 {} + namespace ns2 {} // ignore + namespace ns3 { namespace nns3 {} } + namespace foo { + using namespace ns1; + using namespace ns3::nns3; + } + namespace ns { + void f() { + foo::^ + } + } + )cpp", {}, Opts); + + EXPECT_THAT( + Requests.allRequests(), + ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("foo", "ns1", "ns3::nns3")))); +} + +TEST(CompletionTest, UnresolvedQualifierIdQuery) { + clangd::CodeCompleteOptions Opts; + IndexRequestCollector Requests; + Opts.Index = &Requests; + + auto Results = completions(R"cpp( + namespace a {} + using namespace a; + namespace ns { + void f() { + bar::^ + } + } // namespace ns + )cpp", {}, Opts); + + EXPECT_THAT(Requests.allRequests(), + ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("bar")))); +} + +TEST(CompletionTest, EmptyQualifiedQuery) { + clangd::CodeCompleteOptions Opts; + IndexRequestCollector Requests; + Opts.Index = &Requests; + + auto Results = completions(R"cpp( + namespace ns { + void f() { + ^ + } + } // namespace ns + )cpp", {}, Opts); + + EXPECT_THAT(Requests.allRequests(), + ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("", "ns")))); +} + +TEST(CompletionTest, GlobalQualifiedQuery) { + clangd::CodeCompleteOptions Opts; + IndexRequestCollector Requests; + Opts.Index = &Requests; + + auto Results = completions(R"cpp( + namespace ns { + void f() { + ::^ + } + } // namespace ns + )cpp", {}, Opts); + + EXPECT_THAT( + Requests.allRequests(), + ElementsAre(Field(&FuzzyFindRequest::Scopes, UnorderedElementsAre("")))); +} + } // namespace } // namespace clangd } // namespace clang