Index: clang-tools-extra/trunk/clangd/CodeComplete.cpp =================================================================== --- clang-tools-extra/trunk/clangd/CodeComplete.cpp +++ clang-tools-extra/trunk/clangd/CodeComplete.cpp @@ -309,23 +309,100 @@ llvm_unreachable("unknown CodeCompletionResult kind"); } -/// \brief Information about the scope specifier in the qualified-id code -/// completion (e.g. "ns::ab?"). +// Scopes of the paritial identifier we're trying to complete. +// It is used when we query the index for more completion results. 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() { - return Resolved.empty() ? StringRef(Written).ltrim("::") - : StringRef(Resolved); + // The scopes we should look in, determined by Sema. + // + // If the qualifier was fully resolved, we look for completions in these + // scopes; if there is an unresolved part of the qualifier, it should be + // resolved within these scopes. + // + // Examples of qualified completion: + // + // "::vec" => {""} + // "using namespace std; ::vec^" => {"", "std::"} + // "namespace ns {using namespace std;} ns::^" => {"ns::", "std::"} + // "std::vec^" => {""} // "std" unresolved + // + // Examples of unqualified completion: + // + // "vec^" => {""} + // "using namespace std; vec^" => {"", "std::"} + // "using namespace std; namespace ns { vec^ }" => {"ns::", "std::", ""} + // + // "" for global namespace, "ns::" for normal namespace. + std::vector AccessibleScopes; + // The full scope qualifier as typed by the user (without the leading "::"). + // Set if the qualifier is not fully resolved by Sema. + llvm::Optional UnresolvedQualifier; + + // Construct scopes being queried in indexes. + // This method format the scopes to match the index request representation. + std::vector scopesForIndexQuery() { + std::vector Results; + for (llvm::StringRef AS : AccessibleScopes) { + Results.push_back(AS); + if (UnresolvedQualifier) + Results.back() += *UnresolvedQualifier; + } + return Results; } }; +// Get all scopes that will be queried in indexes. +std::vector getQueryScopes(CodeCompletionContext &CCContext, + const SourceManager& SM) { + auto GetAllAccessibleScopes = [](CodeCompletionContext& CCContext) { + SpecifiedScope Info; + for (auto* Context : CCContext.getVisitedContexts()) { + if (isa(Context)) + Info.AccessibleScopes.push_back(""); // global namespace + else if (const auto*NS = dyn_cast(Context)) + Info.AccessibleScopes.push_back(NS->getQualifiedNameAsString() + "::"); + } + return Info; + }; + + auto SS = CCContext.getCXXScopeSpecifier(); + + // Unqualified completion (e.g. "vec^"). + if (!SS) { + // FIXME: Once we can insert namespace qualifiers and use the in-scope + // namespaces for scoring, search in all namespaces. + // FIXME: Capture scopes and use for scoring, for example, + // "using namespace std; namespace foo {v^}" => + // foo::value > std::vector > boost::variant + return GetAllAccessibleScopes(CCContext).scopesForIndexQuery(); + } + + // Qualified completion ("std::vec^"), we have two cases depending on whether + // the qualifier can be resolved by Sema. + if ((*SS)->isValid()) { // Resolved qualifier. + // FIXME: Disable Sema typo correction during 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::". + return GetAllAccessibleScopes(CCContext).scopesForIndexQuery(); + } + + // Unresolved qualifier. + // FIXME: When Sema can resolve part of a scope chain (e.g. + // "known::unknown::id"), we should expand the known part ("known::") rather + // than treating the whole thing as unknown. + SpecifiedScope Info; + Info.AccessibleScopes.push_back(""); // global namespace + + Info.UnresolvedQualifier = + Lexer::getSourceText(CharSourceRange::getCharRange((*SS)->getRange()), + SM, clang::LangOptions()).ltrim("::"); + // Sema excludes the trailing "::". + if (!Info.UnresolvedQualifier->empty()) + *Info.UnresolvedQualifier += "::"; + + return Info.scopesForIndexQuery(); +} + // The CompletionRecorder captures Sema code-complete output, including context. // It filters out ignored results (but doesn't apply fuzzy-filtering yet). // It doesn't do scoring or conversion to CompletionItem yet, as we want to @@ -629,25 +706,6 @@ 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 (!Info.Written.empty()) - Info.Written += "::"; // Sema excludes the trailing ::. - 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 = ""; - } - } - return Info; -} - // Should we perform index-based completion in this context? // FIXME: consider allowing completion, but restricting the result types. bool allowIndex(enum CodeCompletionContext::Kind K) { @@ -807,16 +865,11 @@ // Build the query. 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. - // FIXME: once we can insert namespace qualifiers and use the in-scope - // namespaces for scoring, search in all namespaces. - Req.Scopes = {""}; + Req.Scopes = + getQueryScopes(Recorder.CCContext, Recorder.CCSema->getSourceManager()); + log(Ctx, llvm::formatv( + "Code complete: fuzzyFind(\"{0}\", Scopes: [{1}]", Req.Query, + llvm::join(Req.Scopes.begin(), Req.Scopes.end(), ","))); // Run the query against the index. Incomplete |= !Opts.Index->fuzzyFind( Ctx, Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); }); Index: clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp +++ clang-tools-extra/trunk/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; +}; + +std::vector captureIndexRequests(llvm::StringRef Code) { + clangd::CodeCompleteOptions Opts; + IndexRequestCollector Requests; + Opts.Index = &Requests; + completions(Code, {}, Opts); + return Requests.allRequests(); +} + +TEST(CompletionTest, UnqualifiedIdQuery) { + auto Requests = captureIndexRequests(R"cpp( + namespace std {} + using namespace std; + namespace ns { + void f() { + vec^ + } + } + )cpp"); + + EXPECT_THAT(Requests, + ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("", "ns::", "std::")))); +} + +TEST(CompletionTest, ResolvedQualifiedIdQuery) { + auto Requests = captureIndexRequests(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"); + + EXPECT_THAT(Requests, + ElementsAre(Field( + &FuzzyFindRequest::Scopes, + UnorderedElementsAre("foo::", "ns1::", "ns3::nns3::")))); +} + +TEST(CompletionTest, UnresolvedQualifierIdQuery) { + auto Requests = captureIndexRequests(R"cpp( + namespace a {} + using namespace a; + namespace ns { + void f() { + bar::^ + } + } // namespace ns + )cpp"); + + EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("bar::")))); +} + +TEST(CompletionTest, UnresolvedNestedQualifierIdQuery) { + auto Requests = captureIndexRequests(R"cpp( + namespace a {} + using namespace a; + namespace ns { + void f() { + ::a::bar::^ + } + } // namespace ns + )cpp"); + + EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("a::bar::")))); +} + +TEST(CompletionTest, EmptyQualifiedQuery) { + auto Requests = captureIndexRequests(R"cpp( + namespace ns { + void f() { + ^ + } + } // namespace ns + )cpp"); + + EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("", "ns::")))); +} + +TEST(CompletionTest, GlobalQualifiedQuery) { + auto Requests = captureIndexRequests(R"cpp( + namespace ns { + void f() { + ::^ + } + } // namespace ns + )cpp"); + + EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes, + UnorderedElementsAre("")))); +} + } // namespace } // namespace clangd } // namespace clang