Skip to content

Commit d7a1a2d

Browse files
committed
[CodeCompletion] Fix issue when completing inside a switch in a closure
1 parent 8acc94b commit d7a1a2d

File tree

8 files changed

+163
-2
lines changed

8 files changed

+163
-2
lines changed

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/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
@@ -2390,6 +2390,12 @@ namespace {
23902390
// function, to set the type of the pattern.
23912391
auto setType = [&](Type type) {
23922392
CS.setType(pattern, type);
2393+
if (auto PE = dyn_cast<ExprPattern>(pattern)) {
2394+
// Set the type of the pattern's sub-expression as well, so code
2395+
// completion can retrieve the expression's type in case it is a code
2396+
// completion token.
2397+
CS.setType(PE->getSubExpr(), type);
2398+
}
23932399
return type;
23942400
};
23952401

lib/Sema/TypeCheckStmt.cpp

Lines changed: 7 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,16 @@ 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-
bool LeaveBodyUnchecked = !ctx.CompletionCallback;
2425-
24262431
TypeChecker::typeCheckASTNode(finder.getRef(), DC, LeaveBodyUnchecked);
24272432
return false;
24282433
}

test/IDE/complete_in_closures.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

test/IDE/complete_in_result_builder.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,39 @@ func testInStringLiteralInResultBuilder() {
329329
// IN_STRING_LITERAL_IN_RESULT_BUILDER-DAG: Decl[InstanceVar]/CurrNominal: bar[#Int#]; name=bar
330330
// IN_STRING_LITERAL_IN_RESULT_BUILDER: End completions
331331
}
332+
333+
func testSwitchInResultBuilder() {
334+
@resultBuilder
335+
enum ReducerBuilder2<Action> {
336+
static func buildBlock(_ r: Reduce2<Action>) -> Reduce2<Action> { r }
337+
static func buildBlock(_ r0: Reduce2<Action>, _ r1: Reduce2<Action>) -> Reduce2<Action> { r0 }
338+
static func buildExpression(_ r: Reduce2<Action>) -> Reduce2<Action> { r }
339+
}
340+
341+
enum Action {
342+
case alertDismissed
343+
}
344+
345+
struct Reduce2<Action> {
346+
init() {}
347+
348+
init(_ reduce: (Action) -> Int) {}
349+
}
350+
351+
struct Login2 {
352+
@ReducerBuilder2<Action>
353+
var body: Reduce2<Action> {
354+
Reduce2()
355+
Reduce2 { action in
356+
switch action {
357+
case .#^SWITCH_IN_RESULT_BUILDER^# alertDismissed:
358+
return 0
359+
}
360+
}
361+
}
362+
}
363+
// SWITCH_IN_RESULT_BUILDER: Begin completions, 2 items
364+
// SWITCH_IN_RESULT_BUILDER-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: alertDismissed[#Action#];
365+
// SWITCH_IN_RESULT_BUILDER-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): Action#})[#(into: inout Hasher) -> Void#];
366+
// SWITCH_IN_RESULT_BUILDER: End completions
367+
}

test/IDE/complete_unresolved_members.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,3 +732,14 @@ func testSameType() {
732732
// SUGAR_TYPE: End completions
733733
}
734734

735+
struct DispatchTime {
736+
static func now() -> DispatchTime { .init() }
737+
}
738+
func +(_ x: DispatchTime, _ y: Double) -> DispatchTime { return x }
739+
740+
let _: DispatchTime = .#^UNRESOLVED_FUNCTION_CALL^#now() + 0.2
741+
742+
// UNRESOLVED_FUNCTION_CALL: Begin completions, 2 items
743+
// UNRESOLVED_FUNCTION_CALL-DAG: Decl[StaticMethod]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: now()[#DispatchTime#];
744+
// UNRESOLVED_FUNCTION_CALL-DAG: Decl[Constructor]/CurrNominal/TypeRelation[Convertible]: init()[#DispatchTime#];
745+
// UNRESOLVED_FUNCTION_CALL: End completions

0 commit comments

Comments
 (0)