[clangd] Don't assert when completing a lambda variable inside the lambda.

Summary:
This is a fairly ugly hack - we back off several features for any variable
whose type isn't deduced, to avoid computing/caching linkage.
Better suggestions welcome.

Fixes https://github.com/clangd/clangd/issues/274

Reviewers: kadircet, kbobyrev

Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D73960
This commit is contained in:
Sam McCall 2020-02-04 14:42:06 +01:00
parent 2f4c4d0a78
commit 2629035a00
5 changed files with 45 additions and 6 deletions

View File

@ -473,5 +473,12 @@ std::string getQualification(ASTContext &Context,
});
}
bool hasUnstableLinkage(const Decl *D) {
// Linkage of a ValueDecl depends on the type.
// If that's not deduced yet, deducing it may change the linkage.
auto *VD = llvm::dyn_cast_or_null<ValueDecl>(D);
return VD && !VD->getType().isNull() && VD->getType()->isUndeducedType();
}
} // namespace clangd
} // namespace clang

View File

@ -148,6 +148,21 @@ std::string getQualification(ASTContext &Context,
const NamedDecl *ND,
llvm::ArrayRef<std::string> VisibleNamespaces);
/// Whether we must avoid computing linkage for D during code completion.
/// Clang aggressively caches linkage computation, which is stable after the AST
/// is built. Unfortunately the AST is incomplete during code completion, so
/// linkage may still change.
///
/// Example: `auto x = []{^}` at file scope.
/// During code completion, the initializer for x hasn't been parsed yet.
/// x has type `undeduced auto`, and external linkage.
/// If we compute linkage at this point, the external linkage will be cached.
///
/// After code completion the initializer is attached, and x has a lambda type.
/// This means x has "unique external" linkage. If we computed linkage above,
/// the cached value is incorrect. (clang catches this with an assertion).
bool hasUnstableLinkage(const Decl *D);
} // namespace clangd
} // namespace clang

View File

@ -489,6 +489,9 @@ llvm::Optional<SymbolID> getSymbolID(const CodeCompletionResult &R,
switch (R.Kind) {
case CodeCompletionResult::RK_Declaration:
case CodeCompletionResult::RK_Pattern: {
// Computing USR caches linkage, which may change after code completion.
if (hasUnstableLinkage(R.Declaration))
return llvm::None;
return clang::clangd::getSymbolID(R.Declaration);
}
case CodeCompletionResult::RK_Macro:
@ -1001,10 +1004,12 @@ private:
ScoredSignature Result;
Result.Signature = std::move(Signature);
Result.Quality = Signal;
Result.IDForDoc =
Result.Signature.documentation.empty() && Candidate.getFunction()
? clangd::getSymbolID(Candidate.getFunction())
: None;
const FunctionDecl *Func = Candidate.getFunction();
if (Func && Result.Signature.documentation.empty()) {
// Computing USR caches linkage, which may change after code completion.
if (!hasUnstableLinkage(Func))
Result.IDForDoc = clangd::getSymbolID(Func);
}
return Result;
}

View File

@ -275,8 +275,9 @@ computeScope(const NamedDecl *D) {
}
if (InClass)
return SymbolRelevanceSignals::ClassScope;
// This threshold could be tweaked, e.g. to treat module-visible as global.
if (D->getLinkageInternal() < ExternalLinkage)
// ExternalLinkage threshold could be tweaked, e.g. module-visible as global.
// Avoid caching linkage if it may change after enclosing code completion.
if (hasUnstableLinkage(D) || D->getLinkageInternal() < ExternalLinkage)
return SymbolRelevanceSignals::FileScope;
return SymbolRelevanceSignals::GlobalScope;
}

View File

@ -2664,6 +2664,17 @@ TEST(CompletionTest, DerivedMethodsAreAlwaysVisible) {
ElementsAre(AllOf(ReturnType("int"), Named("size"))));
}
TEST(CompletionTest, NoCrashWithIncompleteLambda) {
auto Completions = completions("auto&& x = []{^").Completions;
// The completion of x itself can cause a problem: in the code completion
// callback, its type is not known, which affects the linkage calculation.
// A bad linkage value gets cached, and subsequently updated.
EXPECT_THAT(Completions, Contains(Named("x")));
auto Signatures = signatures("auto x() { x(^").signatures;
EXPECT_THAT(Signatures, Contains(Sig("x() -> auto")));
}
TEST(NoCompileCompletionTest, Basic) {
auto Results = completionsNoCompile(R"cpp(
void func() {