Skip to content

Commit 2629035

Browse files
committed
[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 clangd/clangd#274 Reviewers: kadircet, kbobyrev Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D73960
1 parent 2f4c4d0 commit 2629035

File tree

5 files changed

+45
-6
lines changed

5 files changed

+45
-6
lines changed

clang-tools-extra/clangd/AST.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,5 +473,12 @@ std::string getQualification(ASTContext &Context,
473473
});
474474
}
475475

476+
bool hasUnstableLinkage(const Decl *D) {
477+
// Linkage of a ValueDecl depends on the type.
478+
// If that's not deduced yet, deducing it may change the linkage.
479+
auto *VD = llvm::dyn_cast_or_null<ValueDecl>(D);
480+
return VD && !VD->getType().isNull() && VD->getType()->isUndeducedType();
481+
}
482+
476483
} // namespace clangd
477484
} // namespace clang

clang-tools-extra/clangd/AST.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,21 @@ std::string getQualification(ASTContext &Context,
148148
const NamedDecl *ND,
149149
llvm::ArrayRef<std::string> VisibleNamespaces);
150150

151+
/// Whether we must avoid computing linkage for D during code completion.
152+
/// Clang aggressively caches linkage computation, which is stable after the AST
153+
/// is built. Unfortunately the AST is incomplete during code completion, so
154+
/// linkage may still change.
155+
///
156+
/// Example: `auto x = []{^}` at file scope.
157+
/// During code completion, the initializer for x hasn't been parsed yet.
158+
/// x has type `undeduced auto`, and external linkage.
159+
/// If we compute linkage at this point, the external linkage will be cached.
160+
///
161+
/// After code completion the initializer is attached, and x has a lambda type.
162+
/// This means x has "unique external" linkage. If we computed linkage above,
163+
/// the cached value is incorrect. (clang catches this with an assertion).
164+
bool hasUnstableLinkage(const Decl *D);
165+
151166
} // namespace clangd
152167
} // namespace clang
153168

clang-tools-extra/clangd/CodeComplete.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,9 @@ llvm::Optional<SymbolID> getSymbolID(const CodeCompletionResult &R,
489489
switch (R.Kind) {
490490
case CodeCompletionResult::RK_Declaration:
491491
case CodeCompletionResult::RK_Pattern: {
492+
// Computing USR caches linkage, which may change after code completion.
493+
if (hasUnstableLinkage(R.Declaration))
494+
return llvm::None;
492495
return clang::clangd::getSymbolID(R.Declaration);
493496
}
494497
case CodeCompletionResult::RK_Macro:
@@ -1001,10 +1004,12 @@ class SignatureHelpCollector final : public CodeCompleteConsumer {
10011004
ScoredSignature Result;
10021005
Result.Signature = std::move(Signature);
10031006
Result.Quality = Signal;
1004-
Result.IDForDoc =
1005-
Result.Signature.documentation.empty() && Candidate.getFunction()
1006-
? clangd::getSymbolID(Candidate.getFunction())
1007-
: None;
1007+
const FunctionDecl *Func = Candidate.getFunction();
1008+
if (Func && Result.Signature.documentation.empty()) {
1009+
// Computing USR caches linkage, which may change after code completion.
1010+
if (!hasUnstableLinkage(Func))
1011+
Result.IDForDoc = clangd::getSymbolID(Func);
1012+
}
10081013
return Result;
10091014
}
10101015

clang-tools-extra/clangd/Quality.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,9 @@ computeScope(const NamedDecl *D) {
275275
}
276276
if (InClass)
277277
return SymbolRelevanceSignals::ClassScope;
278-
// This threshold could be tweaked, e.g. to treat module-visible as global.
279-
if (D->getLinkageInternal() < ExternalLinkage)
278+
// ExternalLinkage threshold could be tweaked, e.g. module-visible as global.
279+
// Avoid caching linkage if it may change after enclosing code completion.
280+
if (hasUnstableLinkage(D) || D->getLinkageInternal() < ExternalLinkage)
280281
return SymbolRelevanceSignals::FileScope;
281282
return SymbolRelevanceSignals::GlobalScope;
282283
}

clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,6 +2664,17 @@ TEST(CompletionTest, DerivedMethodsAreAlwaysVisible) {
26642664
ElementsAre(AllOf(ReturnType("int"), Named("size"))));
26652665
}
26662666

2667+
TEST(CompletionTest, NoCrashWithIncompleteLambda) {
2668+
auto Completions = completions("auto&& x = []{^").Completions;
2669+
// The completion of x itself can cause a problem: in the code completion
2670+
// callback, its type is not known, which affects the linkage calculation.
2671+
// A bad linkage value gets cached, and subsequently updated.
2672+
EXPECT_THAT(Completions, Contains(Named("x")));
2673+
2674+
auto Signatures = signatures("auto x() { x(^").signatures;
2675+
EXPECT_THAT(Signatures, Contains(Sig("x() -> auto")));
2676+
}
2677+
26672678
TEST(NoCompileCompletionTest, Basic) {
26682679
auto Results = completionsNoCompile(R"cpp(
26692680
void func() {

0 commit comments

Comments
 (0)