Skip to content

Commit ab42032

Browse files
authored
Merge pull request #59944 from ahoppen/pr/no-leaveclosurebodiesunchecked
[CodeCompletion] Eliminate LeaveClosureBodiesUnchecked for solver-based code completion
2 parents 2e8ae40 + 754e9d7 commit ab42032

14 files changed

+307
-15
lines changed

include/swift/IDE/PostfixCompletion.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class PostfixCompletionCallback : public TypeCheckCompletionCallback {
3535
/// Whether the surrounding context is async and thus calling async
3636
/// functions is supported.
3737
bool IsInAsyncContext;
38+
39+
/// Actor isolations that were determined during constraint solving but that
40+
/// haven't been saved to the AST.
41+
llvm::DenseMap<AbstractClosureExpr *, ClosureActorIsolation>
42+
ClosureActorIsolations;
3843
};
3944

4045
CodeCompletionExpr *CompletionExpr;

lib/IDE/ArgumentCompletion.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) {
8888
while (ParentCall && ParentCall->getArgs() == nullptr) {
8989
ParentCall = CS.getParentExpr(ParentCall);
9090
}
91+
if (auto TV = S.getType(CompletionExpr)->getAs<TypeVariableType>()) {
92+
auto Locator = TV->getImpl().getLocator();
93+
if (Locator->isLastElement<LocatorPathElt::PatternMatch>()) {
94+
// The code completion token is inside a pattern, which got rewritten from
95+
// a call by ResolvePattern. Thus, we aren't actually inside a call.
96+
// Rest 'ParentCall' to nullptr to reflect that.
97+
ParentCall = nullptr;
98+
}
99+
}
91100

92101
if (!ParentCall || ParentCall == CompletionExpr) {
93102
// We might not have a call that contains the code completion expression if

lib/IDE/PostfixCompletion.cpp

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,32 @@ void PostfixCompletionCallback::fallbackTypeCheck(DeclContext *DC) {
4444
[&](const Solution &S) { sawSolution(S); });
4545
}
4646

47+
static ClosureActorIsolation
48+
getClosureActorIsolation(const Solution &S, AbstractClosureExpr *ACE) {
49+
auto getType = [&S](Expr *E) -> Type {
50+
// Prefer the contextual type of the closure because it might be 'weaker'
51+
// than the type determined for the closure by the constraints system. E.g.
52+
// the contextual type might have a global actor attribute but because no
53+
// methods from that global actor are called in the closure, the closure has
54+
// a non-actor type.
55+
auto target = S.solutionApplicationTargets.find(dyn_cast<ClosureExpr>(E));
56+
if (target != S.solutionApplicationTargets.end()) {
57+
if (auto Ty = target->second.getClosureContextualType()) {
58+
return Ty;
59+
}
60+
}
61+
if (!S.hasType(E)) {
62+
return Type();
63+
}
64+
return getTypeForCompletion(S, E);
65+
};
66+
auto getClosureActorIsolationThunk = [&S](AbstractClosureExpr *ACE) {
67+
return getClosureActorIsolation(S, ACE);
68+
};
69+
return determineClosureActorIsolation(ACE, getType,
70+
getClosureActorIsolationThunk);
71+
}
72+
4773
void PostfixCompletionCallback::sawSolutionImpl(
4874
const constraints::Solution &S) {
4975
auto &CS = S.getConstraintSystem();
@@ -60,29 +86,45 @@ void PostfixCompletionCallback::sawSolutionImpl(
6086
auto *Locator = CS.getConstraintLocator(SemanticExpr);
6187
Type ExpectedTy = getTypeForCompletion(S, CompletionExpr);
6288
Expr *ParentExpr = CS.getParentExpr(CompletionExpr);
63-
if (!ParentExpr)
89+
if (!ParentExpr && !ExpectedTy)
6490
ExpectedTy = CS.getContextualType(CompletionExpr, /*forConstraint=*/false);
6591

6692
auto *CalleeLocator = S.getCalleeLocator(Locator);
6793
ValueDecl *ReferencedDecl = nullptr;
6894
if (auto SelectedOverload = S.getOverloadChoiceIfAvailable(CalleeLocator))
6995
ReferencedDecl = SelectedOverload->choice.getDeclOrNull();
7096

97+
llvm::DenseMap<AbstractClosureExpr *, ClosureActorIsolation>
98+
ClosureActorIsolations;
7199
bool IsAsync = isContextAsync(S, DC);
100+
for (auto SAT : S.solutionApplicationTargets) {
101+
if (auto ACE = getAsExpr<AbstractClosureExpr>(SAT.second.getAsASTNode())) {
102+
ClosureActorIsolations[ACE] = getClosureActorIsolation(S, ACE);
103+
}
104+
}
72105

73106
auto Key = std::make_pair(BaseTy, ReferencedDecl);
74107
auto Ret = BaseToSolutionIdx.insert({Key, Results.size()});
75108
if (Ret.second) {
76109
bool ISDMT = S.isStaticallyDerivedMetatype(ParsedExpr);
77110
bool ImplicitReturn = isImplicitSingleExpressionReturn(CS, CompletionExpr);
78-
bool DisallowVoid = ExpectedTy
79-
? !ExpectedTy->isVoid()
80-
: !ParentExpr && CS.getContextualTypePurpose(
81-
CompletionExpr) != CTP_Unused;
111+
bool DisallowVoid = false;
112+
DisallowVoid |= ExpectedTy && !ExpectedTy->isVoid();
113+
DisallowVoid |= !ParentExpr &&
114+
CS.getContextualTypePurpose(CompletionExpr) != CTP_Unused;
115+
for (auto SAT : S.solutionApplicationTargets) {
116+
if (DisallowVoid) {
117+
// DisallowVoid is already set. No need to iterate further.
118+
break;
119+
}
120+
if (SAT.second.getAsExpr() == CompletionExpr) {
121+
DisallowVoid |= SAT.second.getExprContextualTypePurpose() != CTP_Unused;
122+
}
123+
}
82124

83125
Results.push_back({BaseTy, ReferencedDecl,
84126
/*ExpectedTypes=*/{}, DisallowVoid, ISDMT,
85-
ImplicitReturn, IsAsync});
127+
ImplicitReturn, IsAsync, ClosureActorIsolations});
86128
if (ExpectedTy) {
87129
Results.back().ExpectedTypes.push_back(ExpectedTy);
88130
}
@@ -122,6 +164,7 @@ void PostfixCompletionCallback::deliverResults(
122164
Lookup.shouldCheckForDuplicates(Results.size() > 1);
123165
for (auto &Result : Results) {
124166
Lookup.setCanCurrDeclContextHandleAsync(Result.IsInAsyncContext);
167+
Lookup.setClosureActorIsolations(Result.ClosureActorIsolations);
125168
Lookup.setIsStaticMetatype(Result.BaseIsStaticMetaType);
126169
Lookup.getPostfixKeywordCompletions(Result.BaseTy, BaseExpr);
127170
Lookup.setExpectedTypes(Result.ExpectedTypes,

lib/Parse/ParseExpr.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,7 @@ Parser::parseExprPostfixSuffix(ParserResult<Expr> Result, bool isExprBasic,
11751175
// [.foo(), <HERE> .bar()]
11761176
// '.bar()' is probably not a part of the inserting element. Moreover,
11771177
// having suffixes doesn't help type inference in any way.
1178+
11781179
return Result;
11791180
}
11801181

lib/Parse/ParsePattern.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,19 @@ ParserResult<Pattern> Parser::parseMatchingPattern(bool isExprBasic) {
13031303
if (subExpr.isNull())
13041304
return status;
13051305

1306+
if (isa<CodeCompletionExpr>(subExpr.get()) && Tok.isFollowingLParen()) {
1307+
// We are in the case like the following of parsing a pattern with the code
1308+
// completion token as base and associated value matches:
1309+
// #^COMPLETE^#(let a)
1310+
// We will have not consumed the `(let a)` in `parseExprPostfixSuffix`
1311+
// because usually suffixes don't influence the code completion's type and
1312+
// the suffix might be unrelated. But the trailing `(let a)` that is left
1313+
// prevents us from forming a valid pattern.
1314+
// Consume and discard the `(let a)`, which just leaves us with the base
1315+
// of the pattern.
1316+
(void)parseExprCallSuffix(subExpr, isExprBasic);
1317+
}
1318+
13061319
// The most common case here is to parse something that was a lexically
13071320
// obvious pattern, which will come back wrapped in an immediate
13081321
// UnresolvedPatternExpr. Transform this now to simplify later code.

lib/Sema/CSGen.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2393,6 +2393,12 @@ namespace {
23932393
// function, to set the type of the pattern.
23942394
auto setType = [&](Type type) {
23952395
CS.setType(pattern, type);
2396+
if (auto PE = dyn_cast<ExprPattern>(pattern)) {
2397+
// Set the type of the pattern's sub-expression as well, so code
2398+
// completion can retrieve the expression's type in case it is a code
2399+
// completion token.
2400+
CS.setType(PE->getSubExpr(), type);
2401+
}
23962402
return type;
23972403
};
23982404

lib/Sema/CSSyntacticElement.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,23 @@ class SyntacticElementConstraintGenerator
10301030

10311031
SmallVector<ElementInfo, 4> elements;
10321032
for (auto element : braceStmt->getElements()) {
1033+
if (cs.isForCodeCompletion() &&
1034+
!cs.containsIDEInspectionTarget(element)) {
1035+
// Statements and expressions can't influence the expresion that
1036+
// contains the code completion token. To improve performance, skip
1037+
// type checking them entirely.
1038+
if (element.is<Expr *>() && !element.isExpr(ExprKind::TypeJoin)) {
1039+
// Type join expressions are not really pure expressions, they kind of
1040+
// declare new type variables and are important to a result builder's
1041+
// structure. Don't skip them.
1042+
continue;
1043+
} else if (element.is<Stmt *>() && !element.isStmt(StmtKind::Guard)) {
1044+
// Guard statements might define variables that are used in the code
1045+
// completion expression. Don't skip them.
1046+
continue;
1047+
}
1048+
}
1049+
10331050
if (auto *decl = element.dyn_cast<Decl *>()) {
10341051
if (auto *PDB = dyn_cast<PatternBindingDecl>(decl)) {
10351052
visitPatternBinding(PDB, elements);

lib/Sema/TypeCheckCodeCompletion.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,9 @@ getTypeOfExpressionWithoutApplying(Expr *&expr, DeclContext *dc,
307307

308308
ConstraintSystemOptions options;
309309
options |= ConstraintSystemFlags::SuppressDiagnostics;
310-
options |= ConstraintSystemFlags::LeaveClosureBodyUnchecked;
310+
if (!Context.CompletionCallback) {
311+
options |= ConstraintSystemFlags::LeaveClosureBodyUnchecked;
312+
}
311313

312314
// Construct a constraint system from this expression.
313315
ConstraintSystem cs(dc, options);
@@ -400,7 +402,6 @@ getTypeOfCompletionOperatorImpl(DeclContext *DC, Expr *expr,
400402

401403
ConstraintSystemOptions options;
402404
options |= ConstraintSystemFlags::SuppressDiagnostics;
403-
options |= ConstraintSystemFlags::LeaveClosureBodyUnchecked;
404405

405406
// Construct a constraint system from this expression.
406407
ConstraintSystem CS(DC, options);
@@ -612,8 +613,9 @@ bool TypeChecker::typeCheckForCodeCompletion(
612613
options |= ConstraintSystemFlags::AllowFixes;
613614
options |= ConstraintSystemFlags::SuppressDiagnostics;
614615
options |= ConstraintSystemFlags::ForCodeCompletion;
615-
options |= ConstraintSystemFlags::LeaveClosureBodyUnchecked;
616-
616+
if (!Context.CompletionCallback) {
617+
options |= ConstraintSystemFlags::LeaveClosureBodyUnchecked;
618+
}
617619

618620
ConstraintSystem cs(DC, options);
619621

lib/Sema/TypeCheckStmt.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2393,6 +2393,8 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
23932393
}
23942394
}
23952395

2396+
bool LeaveBodyUnchecked = !ctx.CompletionCallback;
2397+
23962398
// The enclosing closure might be a single expression closure or a function
23972399
// builder closure. In such cases, the body elements are type checked with
23982400
// the closure itself. So we need to try type checking the enclosing closure
@@ -2416,13 +2418,17 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
24162418
auto ActorIsolation = determineClosureActorIsolation(
24172419
CE, __Expr_getType, __AbstractClosureExpr_getActorIsolation);
24182420
CE->setActorIsolation(ActorIsolation);
2421+
if (!LeaveBodyUnchecked) {
2422+
// Type checking the parent closure also type checked this node.
2423+
// Nothing to do anymore.
2424+
return false;
2425+
}
24192426
if (CE->getBodyState() != ClosureExpr::BodyState::ReadyForTypeChecking)
24202427
return false;
24212428
}
24222429
}
24232430

2424-
TypeChecker::typeCheckASTNode(finder.getRef(), DC,
2425-
/*LeaveBodyUnchecked=*/true);
2431+
TypeChecker::typeCheckASTNode(finder.getRef(), DC, LeaveBodyUnchecked);
24262432
return false;
24272433
}
24282434

test/IDE/complete_in_closures.swift

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ struct NestedStructWithClosureMember1 {
104104
struct StructWithClosureMemberAndLocal {
105105
var c = {
106106
var x = 0
107-
#^DELAYED_10?check=WITH_GLOBAL_DECLS_AND_LOCAL1;xfail=https://github.com/apple/swift/issues/58273^#
107+
#^DELAYED_10?check=WITH_GLOBAL_DECLS_AND_LOCAL1^#
108108
}
109109
}
110110

@@ -416,3 +416,83 @@ func testClosureInPatternBindingInit() {
416416
// CLOSURE_IN_PATTERN_BINDING: End completion
417417

418418
}
419+
420+
func testSwitchInClosure() {
421+
func executeClosure(closure: () -> Void) {}
422+
423+
struct Boredom {
424+
static func doNothing() {}
425+
}
426+
427+
enum MyEnum {
428+
case first
429+
case second
430+
}
431+
432+
let item: MyEnum? = nil
433+
executeClosure(closure: {
434+
switch item {
435+
case .#^SWITCH_IN_CLOSURE^#first:
436+
break
437+
case .second:
438+
Boredom.doNothing()
439+
}
440+
})
441+
442+
// SWITCH_IN_CLOSURE: Begin completions
443+
// SWITCH_IN_CLOSURE-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: first[#MyEnum#];
444+
// SWITCH_IN_CLOSURE-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: second[#MyEnum#];
445+
// SWITCH_IN_CLOSURE: End completions
446+
}
447+
448+
func testSwitchWithAssociatedValueInClosure() {
449+
func executeClosure(closure: () -> Void) {}
450+
451+
struct Boredom {
452+
static func doNothing() {}
453+
}
454+
455+
enum MyEnum {
456+
case first(String)
457+
}
458+
459+
let item: MyEnum? = nil
460+
executeClosure(closure: {
461+
switch item {
462+
case .#^SWITCH_WITH_ASSOC_IN_CLOSURE^#first(_):
463+
break
464+
}
465+
})
466+
467+
// SWITCH_WITH_ASSOC_IN_CLOSURE: Begin completions
468+
// SWITCH_WITH_ASSOC_IN_CLOSURE-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: first({#String#})[#MyEnum#];
469+
// SWITCH_WITH_ASSOC_IN_CLOSURE: End completions
470+
}
471+
472+
func testCompleteInMatchOfAssociatedValueInSwitchCase() {
473+
func testSwitchWithAssociatedValueInClosure() {
474+
func executeClosure(closure: () -> Void) {}
475+
476+
struct Boredom {
477+
static func doNothing() {}
478+
}
479+
480+
enum MyEnum {
481+
case first(Bool, String)
482+
}
483+
484+
let item: MyEnum? = nil
485+
let str = "hi"
486+
executeClosure(closure: {
487+
switch item {
488+
case .first(true, #^IN_ASSOC_OF_CASE_IN_CLOSURE^#str):
489+
break
490+
}
491+
})
492+
493+
// IN_ASSOC_OF_CASE_IN_CLOSURE: Begin completions
494+
// IN_ASSOC_OF_CASE_IN_CLOSURE-DAG: Decl[LocalVar]/Local: str[#String#]; name=str
495+
// IN_ASSOC_OF_CASE_IN_CLOSURE: End completions
496+
}
497+
498+
}

0 commit comments

Comments
 (0)