Skip to content

Commit 7785b9d

Browse files
committed
[Diagnostic] Improve diagnostic for trailing closures in statement conditions
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 381cf59 commit 7785b9d

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
@@ -1045,23 +1045,40 @@ static bool isValidTrailingClosure(bool isExprBasic, Parser &P){
10451045
// the token after the { is on the same line as the {.
10461046
if (P.peekToken().isAtStartOfLine())
10471047
return false;
1048-
1049-
1048+
10501049
// Determine if the {} goes with the expression by eating it, and looking
1051-
// to see if it is immediately followed by '{', 'where', or comma. If so,
1052-
// we consider it to be part of the proceeding expression.
1050+
// to see if it is immediately followed by a token which indicates we should
1051+
// consider it part of the preceding expression
10531052
Parser::BacktrackingScope backtrack(P);
10541053
P.consumeToken(tok::l_brace);
10551054
P.skipUntil(tok::r_brace);
10561055
SourceLoc endLoc;
1057-
if (!P.consumeIf(tok::r_brace, endLoc) ||
1058-
P.Tok.isNot(tok::l_brace, tok::kw_where, tok::comma)) {
1056+
if (!P.consumeIf(tok::r_brace, endLoc))
10591057
return false;
1060-
}
10611058

1062-
// Recoverable case. Just return true here and Sema will emit a diagnostic
1063-
// later. see: Sema/MiscDiagnostics.cpp#checkStmtConditionTrailingClosure
1064-
return true;
1059+
switch (P.Tok.getKind()) {
1060+
case tok::l_brace:
1061+
case tok::kw_where:
1062+
case tok::comma:
1063+
return true;
1064+
case tok::l_square:
1065+
case tok::l_paren:
1066+
case tok::period:
1067+
case tok::period_prefix:
1068+
case tok::kw_is:
1069+
case tok::kw_as:
1070+
case tok::question_postfix:
1071+
case tok::question_infix:
1072+
case tok::exclaim_postfix:
1073+
case tok::colon:
1074+
case tok::equal:
1075+
case tok::oper_postfix:
1076+
case tok::oper_binary_spaced:
1077+
case tok::oper_binary_unspaced:
1078+
return !P.Tok.isAtStartOfLine();
1079+
default:
1080+
return false;
1081+
}
10651082
}
10661083

10671084

lib/Sema/MiscDiagnostics.cpp

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

2960-
// Shallow walker. just dig into implicit expression.
2960+
// Walk into expressions which might have invalid trailing closures
29612961
class DiagnoseWalker : public ASTWalker {
29622962
TypeChecker &TC;
29632963

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

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

@@ -3018,9 +3027,12 @@ static void checkStmtConditionTrailingClosure(TypeChecker &TC, const Expr *E) {
30183027
static void checkStmtConditionTrailingClosure(TypeChecker &TC, const Stmt *S) {
30193028
if (auto LCS = dyn_cast<LabeledConditionalStmt>(S)) {
30203029
for (auto elt : LCS->getCond()) {
3021-
if (elt.getKind() == StmtConditionElement::CK_PatternBinding)
3030+
if (elt.getKind() == StmtConditionElement::CK_PatternBinding) {
30223031
checkStmtConditionTrailingClosure(TC, elt.getInitializer());
3023-
else if (elt.getKind() == StmtConditionElement::CK_Boolean)
3032+
if (auto *exprPattern = dyn_cast<ExprPattern>(elt.getPattern())) {
3033+
checkStmtConditionTrailingClosure(TC, exprPattern->getMatchExpr());
3034+
}
3035+
} else if (elt.getKind() == StmtConditionElement::CK_Boolean)
30243036
checkStmtConditionTrailingClosure(TC, elt.getBoolean());
30253037
// No trailing closure for CK_Availability: e.g. `if #available() {}`.
30263038
}

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)