[clangd] Use accessible scopes to query indexes for global code completion.
Summary: * For qualified completion (foo::a^) * unresolved qualifier - use global namespace ("::") * resolved qualifier - use all accessible namespaces inside the resolved qualifier. * For unqualified completion (vec^), use scopes that are accessible from the scope from which code completion occurs. Reviewers: sammccall, ilya-biryukov Reviewed By: sammccall Subscribers: jkorous-apple, ioeric, klimek, cfe-commits Differential Revision: https://reviews.llvm.org/D42073 llvm-svn: 323189
This commit is contained in:
parent
55c23a10c2
commit
061c73eb28
|
@ -309,23 +309,100 @@ llvm::Optional<SymbolID> getSymbolID(const CodeCompletionResult &R) {
|
||||||
llvm_unreachable("unknown CodeCompletionResult kind");
|
llvm_unreachable("unknown CodeCompletionResult kind");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \brief Information about the scope specifier in the qualified-id code
|
// Scopes of the paritial identifier we're trying to complete.
|
||||||
/// completion (e.g. "ns::ab?").
|
// It is used when we query the index for more completion results.
|
||||||
struct SpecifiedScope {
|
struct SpecifiedScope {
|
||||||
/// The scope specifier as written. For example, for completion "ns::ab?", the
|
// The scopes we should look in, determined by Sema.
|
||||||
/// written scope specifier is "ns::".
|
//
|
||||||
std::string Written;
|
// If the qualifier was fully resolved, we look for completions in these
|
||||||
// If this scope specifier is recognized in Sema (e.g. as a namespace
|
// scopes; if there is an unresolved part of the qualifier, it should be
|
||||||
// context), this will be set to the fully qualfied name of the corresponding
|
// resolved within these scopes.
|
||||||
// context.
|
//
|
||||||
std::string Resolved;
|
// 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<std::string> 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<std::string> UnresolvedQualifier;
|
||||||
|
|
||||||
llvm::StringRef forIndex() {
|
// Construct scopes being queried in indexes.
|
||||||
return Resolved.empty() ? StringRef(Written).ltrim("::")
|
// This method format the scopes to match the index request representation.
|
||||||
: StringRef(Resolved);
|
std::vector<std::string> scopesForIndexQuery() {
|
||||||
|
std::vector<std::string> 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<std::string> getQueryScopes(CodeCompletionContext &CCContext,
|
||||||
|
const SourceManager& SM) {
|
||||||
|
auto GetAllAccessibleScopes = [](CodeCompletionContext& CCContext) {
|
||||||
|
SpecifiedScope Info;
|
||||||
|
for (auto* Context : CCContext.getVisitedContexts()) {
|
||||||
|
if (isa<TranslationUnitDecl>(Context))
|
||||||
|
Info.AccessibleScopes.push_back(""); // global namespace
|
||||||
|
else if (const auto*NS = dyn_cast<NamespaceDecl>(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.
|
// The CompletionRecorder captures Sema code-complete output, including context.
|
||||||
// It filters out ignored results (but doesn't apply fuzzy-filtering yet).
|
// 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
|
// It doesn't do scoring or conversion to CompletionItem yet, as we want to
|
||||||
|
@ -629,25 +706,6 @@ bool semaCodeComplete(const Context &Ctx,
|
||||||
return true;
|
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<NamespaceDecl>(DC)) {
|
|
||||||
Info.Resolved = NS->getQualifiedNameAsString() + "::";
|
|
||||||
} else if (llvm::dyn_cast<TranslationUnitDecl>(DC) != nullptr) {
|
|
||||||
Info.Resolved = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Info;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should we perform index-based completion in this context?
|
// Should we perform index-based completion in this context?
|
||||||
// FIXME: consider allowing completion, but restricting the result types.
|
// FIXME: consider allowing completion, but restricting the result types.
|
||||||
bool allowIndex(enum CodeCompletionContext::Kind K) {
|
bool allowIndex(enum CodeCompletionContext::Kind K) {
|
||||||
|
@ -807,16 +865,11 @@ private:
|
||||||
// Build the query.
|
// Build the query.
|
||||||
FuzzyFindRequest Req;
|
FuzzyFindRequest Req;
|
||||||
Req.Query = Filter->pattern();
|
Req.Query = Filter->pattern();
|
||||||
// If the user typed a scope, e.g. a::b::xxx(), restrict to that scope.
|
Req.Scopes =
|
||||||
// FIXME(ioeric): add scopes based on using directives and enclosing ns.
|
getQueryScopes(Recorder.CCContext, Recorder.CCSema->getSourceManager());
|
||||||
if (auto SS = Recorder.CCContext.getCXXScopeSpecifier())
|
log(Ctx, llvm::formatv(
|
||||||
Req.Scopes = {getSpecifiedScope(*Recorder.CCSema, **SS).forIndex()};
|
"Code complete: fuzzyFind(\"{0}\", Scopes: [{1}]", Req.Query,
|
||||||
else
|
llvm::join(Req.Scopes.begin(), Req.Scopes.end(), ",")));
|
||||||
// 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 = {""};
|
|
||||||
// Run the query against the index.
|
// Run the query against the index.
|
||||||
Incomplete |= !Opts.Index->fuzzyFind(
|
Incomplete |= !Opts.Index->fuzzyFind(
|
||||||
Ctx, Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); });
|
Ctx, Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); });
|
||||||
|
|
|
@ -58,6 +58,7 @@ using ::testing::Each;
|
||||||
using ::testing::ElementsAre;
|
using ::testing::ElementsAre;
|
||||||
using ::testing::Not;
|
using ::testing::Not;
|
||||||
using ::testing::UnorderedElementsAre;
|
using ::testing::UnorderedElementsAre;
|
||||||
|
using ::testing::Field;
|
||||||
|
|
||||||
class IgnoreDiagnostics : public DiagnosticsConsumer {
|
class IgnoreDiagnostics : public DiagnosticsConsumer {
|
||||||
void
|
void
|
||||||
|
@ -676,6 +677,123 @@ TEST(SignatureHelpTest, ActiveArg) {
|
||||||
EXPECT_EQ(1, Results.activeParameter);
|
EXPECT_EQ(1, Results.activeParameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IndexRequestCollector : public SymbolIndex {
|
||||||
|
public:
|
||||||
|
bool
|
||||||
|
fuzzyFind(const Context &Ctx, const FuzzyFindRequest &Req,
|
||||||
|
llvm::function_ref<void(const Symbol &)> Callback) const override {
|
||||||
|
Requests.push_back(Req);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<FuzzyFindRequest> allRequests() const { return Requests; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::vector<FuzzyFindRequest> Requests;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<FuzzyFindRequest> 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
|
||||||
} // namespace clangd
|
} // namespace clangd
|
||||||
} // namespace clang
|
} // namespace clang
|
||||||
|
|
Loading…
Reference in New Issue