diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -18,6 +18,9 @@ #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" #include "clang/Index/IndexingAction.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" @@ -38,6 +41,37 @@ } }; +// Returns true if \p Query can be found as a sub-scope inside \p Scope. +bool approximateScopeMatch(llvm::StringRef Scope, + llvm::ArrayRef Query) { + if (Query.empty()) + return true; + + llvm::SmallVector Scopes; + Scope.split(Scopes, "::"); + + llvm::ArrayRef ScopesRef(Scopes); + // We perform a naive string search below, assuming scopes as our characters. + while (ScopesRef.size() >= Query.size()) { + // Trim Scopes from left until we find the first scope of Query. E.g: + // Scopes: a::b::c:: + // Query: b::c:: + // This will trim `a::` from Scopes. + ScopesRef = ScopesRef.drop_while( + [FirstQueryScope = Query.front()](llvm::StringRef Scope) { + return Scope != FirstQueryScope; + }); + if (ScopesRef.size() < Query.size()) + break; + // Check if ScopesRef prefix matches Query. + if (ScopesRef.take_front(Query.size()).equals(Query)) + return true; + // If not try with the rest. + ScopesRef = ScopesRef.drop_front(); + } + return false; +} + } // namespace llvm::Expected indexToLSPLocation(const SymbolLocation &Loc, @@ -71,25 +105,48 @@ if (Query.empty() || !Index) return Result; + // Lookup for qualified names are performed as: + // - Exact namespaces are boosted by the index. + // - Approximate matches are (sub-scope match) included via AnyScope logic. + // - Non-matching namespaces (no sub-scope match) are post-filtered. auto Names = splitQualifiedName(Query); FuzzyFindRequest Req; Req.Query = std::string(Names.second); - // FuzzyFind doesn't want leading :: qualifier - bool IsGlobalQuery = Names.first.consume_front("::"); - // Restrict results to the scope in the query string if present (global or - // not). - if (IsGlobalQuery || !Names.first.empty()) + // FuzzyFind doesn't want leading :: qualifier. + auto HasLeadingColons = Names.first.consume_front("::"); + // Limit the query to specific namespace if it is fully-qualified. + Req.AnyScope = !HasLeadingColons; + // Boost symbols from desired namespace. + if (HasLeadingColons || !Names.first.empty()) Req.Scopes = {std::string(Names.first)}; - else - Req.AnyScope = true; - if (Limit) + if (Limit) { Req.Limit = Limit; + // If we are boosting a specific scope allow more results to be retrieved, + // since some symbols from preferred namespaces might not make the cut. + if (Req.AnyScope && !Req.Scopes.empty()) + *Req.Limit *= 5; + } TopN Top( Req.Limit ? *Req.Limit : std::numeric_limits::max()); FuzzyMatcher Filter(Req.Query); - Index->fuzzyFind(Req, [HintPath, &Top, &Filter](const Symbol &Sym) { + + llvm::SmallVector ReqScopes; + Names.first.consume_back("::"); + if (!Names.first.empty()) + Names.first.split(ReqScopes, "::"); + Index->fuzzyFind(Req, [HintPath, &Top, &Filter, &ReqScopes, + AnyScope = Req.AnyScope, + ReqScope = Names.first](const Symbol &Sym) { + std::string Scope = std::string(Sym.Scope); + llvm::StringRef ScopeRef = Scope; + ScopeRef.consume_back("::"); + // Fuzzyfind might return symbols from irrelevant namespaces if query was + // not fully-qualified, drop those. + if (AnyScope && !approximateScopeMatch(ScopeRef, ReqScopes)) + return; + auto Loc = symbolToLocation(Sym, HintPath); if (!Loc) { log("Workspace symbols: {0}", Loc.takeError()); @@ -97,9 +154,6 @@ } SymbolKind SK = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind); - std::string Scope = std::string(Sym.Scope); - llvm::StringRef ScopeRef = Scope; - ScopeRef.consume_back("::"); SymbolInformation Info = {(Sym.Name + Sym.TemplateSpecializationArgs).str(), SK, *Loc, std::string(ScopeRef)}; @@ -108,6 +162,8 @@ SymbolRelevanceSignals Relevance; Relevance.Name = Sym.Name; Relevance.Query = SymbolRelevanceSignals::Generic; + // If symbol and request scopes do not match exactly, apply a penalty. + Relevance.InBaseClass = AnyScope && ScopeRef != ReqScope; if (auto NameMatch = Filter.match(Sym.Name)) Relevance.NameMatch = *NameMatch; else { diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp --- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp @@ -141,7 +141,10 @@ EXPECT_THAT(getSymbols(TU, "::"), ElementsAre(QName("ans1"))); EXPECT_THAT(getSymbols(TU, "::a"), ElementsAre(QName("ans1"))); EXPECT_THAT(getSymbols(TU, "ans1::"), - UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2"))); + UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2"), + QName("ans1::ans2::ai2"))); + EXPECT_THAT(getSymbols(TU, "ans2::"), + UnorderedElementsAre(QName("ans1::ans2::ai2"))); EXPECT_THAT(getSymbols(TU, "::ans1"), ElementsAre(QName("ans1"))); EXPECT_THAT(getSymbols(TU, "::ans1::"), UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2"))); @@ -256,6 +259,17 @@ EXPECT_THAT(getSymbols(TU, "::"), ElementsAre(QName("func"), QName("ns"))); } +TEST(WorkspaceSymbols, RankingPartilNamespace) { + TestTU TU; + TU.Code = R"cpp( + namespace ns1 { + namespace ns2 { struct Foo {}; } + } + namespace ns2 { struct FooB {}; })cpp"; + EXPECT_THAT(getSymbols(TU, "ns2::f"), + ElementsAre(QName("ns2::FooB"), QName("ns1::ns2::Foo"))); +} + TEST(WorkspaceSymbols, WithLimit) { TestTU TU; TU.AdditionalFiles["foo.h"] = R"cpp(