Skip to content

Commit 4da49fc

Browse files
owenvjrose-apple
authored andcommitted
[Diagnostic] Improve diagnostic for trailing closures in statement conditions (#25165)
This improves the diagnostic for trailing closures in statement conditions to catch cases where one or more trailing closures are used in a chain composed of multiple calls
1 parent 946587c commit 4da49fc

File tree

3 files changed

+126
-21
lines changed

3 files changed

+126
-21
lines changed

lib/Parse/ParseExpr.cpp

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -984,23 +984,40 @@ static bool isValidTrailingClosure(bool isExprBasic, Parser &P){
984984
// the token after the { is on the same line as the {.
985985
if (P.peekToken().isAtStartOfLine())
986986
return false;
987-
988-
987+
989988
// Determine if the {} goes with the expression by eating it, and looking
990-
// to see if it is immediately followed by '{', 'where', or comma. If so,
991-
// we consider it to be part of the proceeding expression.
989+
// to see if it is immediately followed by a token which indicates we should
990+
// consider it part of the preceding expression
992991
Parser::BacktrackingScope backtrack(P);
993992
P.consumeToken(tok::l_brace);
994993
P.skipUntil(tok::r_brace);
995994
SourceLoc endLoc;
996-
if (!P.consumeIf(tok::r_brace, endLoc) ||
997-
P.Tok.isNot(tok::l_brace, tok::kw_where, tok::comma)) {
995+
if (!P.consumeIf(tok::r_brace, endLoc))
998996
return false;
999-
}
1000997

1001-
// Recoverable case. Just return true here and Sema will emit a diagnostic
1002-
// later. see: Sema/MiscDiagnostics.cpp#checkStmtConditionTrailingClosure
1003-
return true;
998+
switch (P.Tok.getKind()) {
999+
case tok::l_brace:
1000+
case tok::kw_where:
1001+
case tok::comma:
1002+
return true;
1003+
case tok::l_square:
1004+
case tok::l_paren:
1005+
case tok::period:
1006+
case tok::period_prefix:
1007+
case tok::kw_is:
1008+
case tok::kw_as:
1009+
case tok::question_postfix:
1010+
case tok::question_infix:
1011+
case tok::exclaim_postfix:
1012+
case tok::colon:
1013+
case tok::equal:
1014+
case tok::oper_postfix:
1015+
case tok::oper_binary_spaced:
1016+
case tok::oper_binary_unspaced:
1017+
return !P.Tok.isAtStartOfLine();
1018+
default:
1019+
return false;
1020+
}
10041021
}
10051022

10061023

lib/Sema/MiscDiagnostics.cpp

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2963,7 +2963,7 @@ void swift::fixItEncloseTrailingClosure(TypeChecker &TC,
29632963
static void checkStmtConditionTrailingClosure(TypeChecker &TC, const Expr *E) {
29642964
if (E == nullptr || isa<ErrorExpr>(E)) return;
29652965

2966-
// Shallow walker. just dig into implicit expression.
2966+
// Walk into expressions which might have invalid trailing closures
29672967
class DiagnoseWalker : public ASTWalker {
29682968
TypeChecker &TC;
29692969

@@ -2998,13 +2998,22 @@ static void checkStmtConditionTrailingClosure(TypeChecker &TC, const Expr *E) {
29982998
bool shouldWalkIntoNonSingleExpressionClosure() override { return false; }
29992999

30003000
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
3001-
// Dig into implicit expression.
3002-
if (E->isImplicit()) return { true, E };
3003-
// Diagnose call expression.
3004-
if (auto CE = dyn_cast<CallExpr>(E))
3005-
diagnoseIt(CE);
3006-
// Don't dig any further.
3007-
return { false, E };
3001+
switch (E->getKind()) {
3002+
case ExprKind::Paren:
3003+
case ExprKind::Tuple:
3004+
case ExprKind::Array:
3005+
case ExprKind::Dictionary:
3006+
case ExprKind::InterpolatedStringLiteral:
3007+
// If a trailing closure appears as a child of one of these types of
3008+
// expression, don't diagnose it as there is no ambiguity.
3009+
return {E->isImplicit(), E};
3010+
case ExprKind::Call:
3011+
diagnoseIt(cast<CallExpr>(E));
3012+
break;
3013+
default:
3014+
break;
3015+
}
3016+
return {true, E};
30083017
}
30093018
};
30103019

@@ -3024,9 +3033,12 @@ static void checkStmtConditionTrailingClosure(TypeChecker &TC, const Expr *E) {
30243033
static void checkStmtConditionTrailingClosure(TypeChecker &TC, const Stmt *S) {
30253034
if (auto LCS = dyn_cast<LabeledConditionalStmt>(S)) {
30263035
for (auto elt : LCS->getCond()) {
3027-
if (elt.getKind() == StmtConditionElement::CK_PatternBinding)
3036+
if (elt.getKind() == StmtConditionElement::CK_PatternBinding) {
30283037
checkStmtConditionTrailingClosure(TC, elt.getInitializer());
3029-
else if (elt.getKind() == StmtConditionElement::CK_Boolean)
3038+
if (auto *exprPattern = dyn_cast<ExprPattern>(elt.getPattern())) {
3039+
checkStmtConditionTrailingClosure(TC, exprPattern->getMatchExpr());
3040+
}
3041+
} else if (elt.getKind() == StmtConditionElement::CK_Boolean)
30303042
checkStmtConditionTrailingClosure(TC, elt.getBoolean());
30313043
// No trailing closure for CK_Availability: e.g. `if #available() {}`.
30323044
}

test/expr/closure/trailing.swift

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ limitXY(someInt, toGamut: intArray) {} // expected-error{{cannot invoke 'limitX
138138
// <rdar://problem/23036383> QoI: Invalid trailing closures in stmt-conditions produce lowsy diagnostics
139139
func retBool(x: () -> Int) -> Bool {}
140140
func maybeInt(_: () -> Int) -> Int? {}
141+
func twoClosureArgs(_:()->Void, _:()->Void) -> Bool {}
141142
class Foo23036383 {
142143
init() {}
143144
func map(_: (Int) -> Int) -> Int? {}
@@ -202,7 +203,82 @@ func r23036383(foo: Foo23036383?, obj: Foo23036383) {
202203

203204
if let _ = maybeInt { 1 }, retBool { 1 } { }
204205
// expected-error@-1 {{trailing closure requires parentheses for disambiguation in this context}} {{22-23=(}} {{28-28=)}}
205-
// expected-error@-2 {{trailing closure requires parentheses for disambiguation in this context}} {{37-38=(x: }} {{43-43=)}}
206+
// expected-error@-2 {{trailing closure requires parentheses for disambiguation in this context}} {{37-38=(x: }} {{43-43=)}}
207+
208+
if let _ = foo?.map {$0+1}.map {$0+1} {} // expected-error {{trailing closure requires parentheses for disambiguation in this context}} {{33-34=(}} {{40-40=)}}
209+
// expected-error@-1 {{trailing closure requires parentheses for disambiguation in this context}} {{22-23=(}} {{29-29=)}}
210+
211+
if let _ = foo?.map {$0+1}.map({$0+1}) {} // expected-error {{trailing closure requires parentheses for disambiguation in this context}} {{22-23=(}} {{29-29=)}}
212+
213+
if let _ = foo?.map {$0+1}.map({$0+1}).map{$0+1} {} // expected-error {{trailing closure requires parentheses for disambiguation in this context}} {{45-45=(}} {{51-51=)}}
214+
// expected-error@-1 {{trailing closure requires parentheses for disambiguation in this context}} {{22-23=(}} {{29-29=)}}
215+
216+
if twoClosureArgs({}) {} {} // expected-error {{trailing closure requires parentheses for disambiguation in this context}} {{23-25=, }} {{27-27=)}}
217+
218+
if let _ = (foo?.map {$0+1}.map({$0+1}).map{$0+1}) {} // OK
219+
220+
if let _ = (foo?.map {$0+1}.map({$0+1})).map({$0+1}) {} // OK
221+
}
222+
223+
func id<T>(fn: () -> T) -> T { return fn() }
224+
func any<T>(fn: () -> T) -> Any { return fn() }
225+
226+
func testSR8736() {
227+
if !id { true } { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
228+
229+
if id { true } == true { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
230+
231+
if true == id { true } { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
232+
233+
if id { true } ? true : false { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
234+
235+
if true ? id { true } : false { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
236+
237+
if true ? true : id { false } { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
238+
239+
if id { [false,true] }[0] { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
240+
241+
if id { { true } } () { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
242+
243+
if any { true } as! Bool { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
244+
245+
if let _ = any { "test" } as? Int { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
246+
247+
if any { "test" } is Int { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
248+
249+
if let _ = id { [] as [Int]? }?.first { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
250+
251+
if id { true as Bool? }! { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
252+
253+
if case id { 1 } = 1 { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
254+
255+
if case 1 = id { 1 } { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
256+
257+
if case 1 = id { 1 } /*comment*/ { return } // expected-error {{trailing closure requires parentheses for disambiguation in this context}}
258+
259+
if case (id { 1 }) = 1 { return } // OK
260+
261+
if case 1 = (id { 1 }) { return } // OK
262+
263+
if [id { true }].count == 0 { return } // OK
264+
265+
if [id { true } : "test"].keys.count == 0 { return } // OK
266+
267+
if "\(id { true })" == "foo" { return } // OK
268+
269+
if (id { true }) { return } // OK
270+
271+
if (id { true }) { }
272+
[1, 2, 3].count // expected-warning {{expression of type 'Int' is unused}}
273+
274+
if true { }
275+
() // OK
276+
277+
if true
278+
{
279+
280+
}
281+
() // OK
206282
}
207283

208284
func overloadOnLabel(a: () -> Void) {}

0 commit comments

Comments
 (0)