Skip to content

Commit c98e515

Browse files
authored
[QoI] Improvements to function call & closure diagnostics (#7224)
1 parent 1b141d3 commit c98e515

19 files changed

+174
-136
lines changed

include/swift/AST/DiagnosticsCommon.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ NOTE(previous_decldef,none,
5252
"previous %select{declaration|definition}0 of %1 is here",
5353
(bool, Identifier))
5454

55+
NOTE(brace_stmt_suggest_do,none,
56+
"did you mean to use a 'do' statement?", ())
5557

5658
// Generic disambiguation
5759
NOTE(while_parsing_as_left_angle_bracket,none,

include/swift/AST/DiagnosticsParse.def

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -806,17 +806,12 @@ ERROR(illegal_top_level_expr,none,
806806
ERROR(illegal_semi_stmt,none,
807807
"';' statements are not allowed", ())
808808
ERROR(statement_begins_with_closure,none,
809-
"statement cannot begin with a closure expression", ())
809+
"top-level statement cannot begin with a closure expression", ())
810810
ERROR(statement_same_line_without_semi,none,
811811
"consecutive statements on a line must be separated by ';'", ())
812-
ERROR(brace_stmt_invalid,none,
813-
"braced block of statements is an unused closure", ())
814812
ERROR(invalid_label_on_stmt,none,
815813
"labels are only valid on loops, if, and switch statements", ())
816814

817-
NOTE(discard_result_of_closure,none,
818-
"explicitly discard the result of the closure by assigning to '_'", ())
819-
820815
ERROR(snake_case_deprecated,none,
821816
"%0 has been replaced with %1 in Swift 3",
822817
(StringRef, StringRef))
@@ -1065,10 +1060,10 @@ ERROR(unexpected_tokens_before_closure_in,none,
10651060
ERROR(expected_closure_rbrace,none,
10661061
"expected '}' at end of closure", ())
10671062

1068-
WARNING(trailing_closure_excess_newlines,none,
1069-
"trailing closure is separated from call site by multiple newlines", ())
1070-
NOTE(trailing_closure_call_here,none,
1071-
"parsing trailing closure for this call", ())
1063+
WARNING(trailing_closure_after_newlines,none,
1064+
"braces here form a trailing closure separated from its callee by multiple newlines", ())
1065+
NOTE(trailing_closure_callee_here,none,
1066+
"callee is here", ())
10721067

10731068
ERROR(string_literal_no_atsign,none,
10741069
"string literals in Swift are not preceded by an '@' sign", ())

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,6 @@ ERROR(cannot_call_with_params, none,
231231
NOTE(pointer_init_to_type,none,
232232
"Pointer conversion restricted: use '.assumingMemoryBound(to:)' or '.bindMemory(to:capacity:)' to view memory as a type.", ())
233233

234-
ERROR(expected_do_in_statement,none,
235-
"expected 'do' keyword to designate a block of statements", ())
236-
237234
ERROR(cannot_call_non_function_value,none,
238235
"cannot call value of non-function type %0", (Type))
239236

@@ -2590,6 +2587,8 @@ WARNING(guard_always_succeeds,none,
25902587
"'guard' condition is always true, body is unreachable", ())
25912588

25922589

2590+
ERROR(expression_unused_closure,none,
2591+
"closure expression is unused", ())
25932592
ERROR(expression_unused_function,none,
25942593
"expression resolves to an unused function", ())
25952594
ERROR(expression_unused_lvalue,none,

lib/Parse/ParseExpr.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2645,13 +2645,21 @@ ParserResult<Expr> Parser::parseTrailingClosure(SourceRange calleeRange) {
26452645
if (closure.isNull())
26462646
return makeParserError();
26472647

2648-
// Track the original end location of the expression we're trailing so
2649-
// we can warn about excess newlines.
2650-
auto origLineCol = SourceMgr.getLineAndColumn(calleeRange.End);
2651-
auto braceLineCol = SourceMgr.getLineAndColumn(braceLoc);
2652-
if (((int)braceLineCol.first - (int)origLineCol.first) > 1) {
2653-
diagnose(braceLoc, diag::trailing_closure_excess_newlines);
2654-
diagnose(calleeRange.Start, diag::trailing_closure_call_here);
2648+
// Warn if the trailing closure is separated from its callee by more than
2649+
// one line. A single-line separation is acceptable for a trailing closure
2650+
// call, and will be diagnosed later only if the call fails to typecheck.
2651+
auto origLine = SourceMgr.getLineNumber(calleeRange.End);
2652+
auto braceLine = SourceMgr.getLineNumber(braceLoc);
2653+
if (braceLine > origLine + 1) {
2654+
diagnose(braceLoc, diag::trailing_closure_after_newlines);
2655+
diagnose(calleeRange.Start, diag::trailing_closure_callee_here);
2656+
2657+
auto *CE = dyn_cast<ClosureExpr>(closure.get());
2658+
if (CE && CE->hasAnonymousClosureVars() &&
2659+
CE->getParameters()->size() == 0) {
2660+
diagnose(braceLoc, diag::brace_stmt_suggest_do)
2661+
.fixItInsert(braceLoc, "do ");
2662+
}
26552663
}
26562664

26572665
return closure;

lib/Parse/ParseStmt.cpp

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -189,27 +189,6 @@ void Parser::consumeTopLevelDecl(ParserPosition BeginParserPosition,
189189
skipUntil(tok::eof);
190190
}
191191

192-
static void diagnoseDiscardedClosure(Parser &P, ASTNode &Result) {
193-
// If we parsed a bare closure as an expression, it will be a discarded value
194-
// expression and the type checker will complain.
195-
196-
if (isa<AbstractClosureExpr>(P.CurDeclContext))
197-
// Inside a closure expression, an expression which syntactically looks
198-
// like a discarded value expression, can become the return value of the
199-
// closure. Don't attempt recovery.
200-
return;
201-
202-
if (auto *E = Result.dyn_cast<Expr *>()) {
203-
if (auto *CE = dyn_cast<ClosureExpr>(E)) {
204-
if (!CE->hasAnonymousClosureVars())
205-
// Parameters are explicitly specified, and could be used in the body,
206-
// don't attempt recovery.
207-
return;
208-
P.diagnose(CE->getBody()->getLBraceLoc(), diag::brace_stmt_invalid);
209-
}
210-
}
211-
}
212-
213192
/// brace-item:
214193
/// decl
215194
/// expr
@@ -369,8 +348,6 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
369348
// This prevents potential ambiguities with trailing closure syntax.
370349
if (Tok.is(tok::l_brace)) {
371350
diagnose(Tok, diag::statement_begins_with_closure);
372-
diagnose(Tok, diag::discard_result_of_closure)
373-
.fixItInsert(Tok.getLoc(), "_ = ");
374351
}
375352

376353
ParserStatus Status = parseExprOrStmt(Result);
@@ -388,7 +365,7 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
388365
Result.is<Stmt*>() ? diag::illegal_top_level_stmt
389366
: diag::illegal_top_level_expr);
390367
}
391-
diagnoseDiscardedClosure(*this, Result);
368+
392369
if (!Result.isNull()) {
393370
// NOTE: this is a 'virtual' brace statement which does not have
394371
// explicit '{' or '}', so the start and end locations should be
@@ -411,7 +388,6 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
411388
BraceItemsStatus |= ExprOrStmtStatus;
412389
if (ExprOrStmtStatus.isError())
413390
NeedParseErrorRecovery = true;
414-
diagnoseDiscardedClosure(*this, Result);
415391
if (!Result.isNull())
416392
Entries.push_back(Result);
417393
}
@@ -484,8 +460,6 @@ void Parser::parseTopLevelCodeDeclDelayed() {
484460
// prevents potential ambiguities with trailing closure syntax.
485461
if (Tok.is(tok::l_brace)) {
486462
diagnose(Tok, diag::statement_begins_with_closure);
487-
diagnose(Tok, diag::discard_result_of_closure)
488-
.fixItInsert(Tok.getLoc(), "_ = ");
489463
}
490464

491465
parseExprOrStmt(Result);

lib/Sema/CSDiag.cpp

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5861,34 +5861,41 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
58615861
if (!isUnresolvedOrTypeVarType(fnType) &&
58625862
!fnType->is<AnyFunctionType>() && !fnType->is<MetatypeType>()) {
58635863

5864-
// If the argument is a trailing ClosureExpr (i.e. {....}) and it is on a
5865-
// different line than the callee, then the "real" issue is that the user
5866-
// forgot to write "do" before their brace stmt.
5867-
if (auto *PE = dyn_cast<ParenExpr>(callExpr->getArg()))
5864+
auto arg = callExpr->getArg();
5865+
5866+
{
5867+
auto diag = diagnose(arg->getStartLoc(),
5868+
diag::cannot_call_non_function_value,
5869+
fnExpr->getType());
5870+
diag.highlight(fnExpr->getSourceRange());
5871+
5872+
// If the argument is an empty tuple, then offer a
5873+
// fix-it to remove the empty tuple and use the value
5874+
// directly.
5875+
if (auto tuple = dyn_cast<TupleExpr>(arg)) {
5876+
if (tuple->getNumElements() == 0) {
5877+
diag.fixItRemove(arg->getSourceRange());
5878+
}
5879+
}
5880+
}
5881+
5882+
// If the argument is a trailing ClosureExpr (i.e. {....}) and it is on
5883+
// the line after the callee, then it's likely the user forgot to
5884+
// write "do" before their brace stmt.
5885+
// Note that line differences of more than 1 are diagnosed during parsing.
5886+
if (auto *PE = dyn_cast<ParenExpr>(arg))
58685887
if (PE->hasTrailingClosure() && isa<ClosureExpr>(PE->getSubExpr())) {
5888+
auto *closure = cast<ClosureExpr>(PE->getSubExpr());
58695889
auto &SM = CS->getASTContext().SourceMgr;
5870-
if (SM.getLineNumber(callExpr->getFn()->getEndLoc()) !=
5871-
SM.getLineNumber(PE->getStartLoc())) {
5872-
diagnose(PE->getStartLoc(), diag::expected_do_in_statement)
5873-
.fixItInsert(PE->getStartLoc(), "do ");
5874-
return true;
5890+
if (closure->hasAnonymousClosureVars() &&
5891+
closure->getParameters()->size() == 0 &&
5892+
1 + SM.getLineNumber(callExpr->getFn()->getEndLoc()) ==
5893+
SM.getLineNumber(closure->getStartLoc())) {
5894+
diagnose(closure->getStartLoc(), diag::brace_stmt_suggest_do)
5895+
.fixItInsert(closure->getStartLoc(), "do ");
58755896
}
58765897
}
5877-
5878-
auto arg = callExpr->getArg();
5879-
auto diag = diagnose(arg->getStartLoc(),
5880-
diag::cannot_call_non_function_value,
5881-
fnExpr->getType());
5882-
diag.highlight(fnExpr->getSourceRange());
5883-
5884-
// If the argument is an empty tuple, then offer a
5885-
// fix-it to remove the empty tuple and use the value
5886-
// directly.
5887-
if (auto tuple = dyn_cast<TupleExpr>(arg)) {
5888-
if (tuple->getNumElements() == 0) {
5889-
diag.fixItRemove(arg->getSourceRange());
5890-
}
5891-
}
5898+
58925899
return true;
58935900
}
58945901

lib/Sema/TypeCheckStmt.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1248,8 +1248,21 @@ Stmt *StmtChecker::visitBraceStmt(BraceStmt *BS) {
12481248

12491249
bool hadTypeError = TC.typeCheckExpression(SubExpr, DC, TypeLoc(),
12501250
CTP_Unused, options);
1251-
if (isDiscarded && !hadTypeError)
1251+
1252+
// If a closure expression is unused, the user might have intended
1253+
// to write "do { ... }".
1254+
auto *CE = dyn_cast<ClosureExpr>(SubExpr);
1255+
if (CE || isa<CaptureListExpr>(SubExpr)) {
1256+
TC.diagnose(SubExpr->getLoc(), diag::expression_unused_closure);
1257+
1258+
if (CE && CE->hasAnonymousClosureVars() &&
1259+
CE->getParameters()->size() == 0) {
1260+
TC.diagnose(CE->getStartLoc(), diag::brace_stmt_suggest_do)
1261+
.fixItInsert(CE->getStartLoc(), "do ");
1262+
}
1263+
} else if (isDiscarded && !hadTypeError)
12521264
TC.checkIgnoredExpr(SubExpr);
1265+
12531266
elem = SubExpr;
12541267
continue;
12551268
}

test/Constraints/closures.swift

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,88 @@ func test13811882() {
6666
// <rdar://problem/21544303> QoI: "Unexpected trailing closure" should have a fixit to insert a 'do' statement
6767
// <https://bugs.swift.org/browse/SR-3671>
6868
func r21544303() {
69-
let val = true
70-
var inSubcall = val
71-
{ // expected-error {{expected 'do' keyword to designate a block of statements}} {{3-3=do }}
72-
}
69+
var inSubcall = true
70+
{
71+
} // expected-error {{computed property must have accessors specified}}
7372
inSubcall = false
7473

7574
// This is a problem, but isn't clear what was intended.
76-
var somethingElse = val { // expected-error {{cannot call value of non-function type 'Bool'}}
77-
}
75+
var somethingElse = true {
76+
} // expected-error {{computed property must have accessors specified}}
7877
inSubcall = false
7978

8079
var v2 : Bool = false
81-
v2 = val
82-
{ // expected-error {{expected 'do' keyword to designate a block of statements}}
80+
v2 = inSubcall
81+
{ // expected-error {{cannot call value of non-function type 'Bool'}} expected-note {{did you mean to use a 'do' statement?}} {{3-3=do }}
8382
}
8483
}
8584

8685

86+
// <https://bugs.swift.org/browse/SR-3671>
87+
func SR3671() {
88+
let n = 42
89+
func consume(_ x: Int) {}
90+
91+
{ consume($0) }(42)
92+
;
93+
94+
({ $0(42) } { consume($0) }) // expected-note {{callee is here}}
95+
96+
{ print(42) } // expected-warning {{braces here form a trailing closure separated from its callee by multiple newlines}} expected-note {{did you mean to use a 'do' statement?}} {{3-3=do }} expected-error {{cannot call value of non-function type '()'}}
97+
;
98+
99+
({ $0(42) } { consume($0) }) // expected-note {{callee is here}}
100+
101+
{ print($0) } // expected-warning {{braces here form a trailing closure separated from its callee by multiple newlines}} expected-error {{cannot call value of non-function type '()'}}
102+
;
103+
104+
({ $0(42) } { consume($0) }) // expected-note {{callee is here}}
105+
106+
{ [n] in print(42) } // expected-warning {{braces here form a trailing closure separated from its callee by multiple newlines}} expected-error {{cannot call value of non-function type '()'}}
107+
;
108+
109+
({ $0(42) } { consume($0) }) // expected-note {{callee is here}}
110+
111+
{ consume($0) }(42) // expected-warning {{braces here form a trailing closure separated from its callee by multiple newlines}} expected-error {{cannot call value of non-function type '()'}}
112+
;
113+
114+
({ $0(42) } { consume($0) }) // expected-note {{callee is here}}
115+
116+
{ (x: Int) in consume(x) }(42) // expected-warning {{braces here form a trailing closure separated from its callee by multiple newlines}} expected-error {{cannot call value of non-function type '()'}}
117+
;
118+
119+
// This is technically a valid call, so nothing goes wrong until (42)
120+
121+
{ $0(3) }
122+
{ consume($0) }(42) // expected-error {{cannot call value of non-function type '()'}}
123+
;
124+
({ $0(42) })
125+
{ consume($0) }(42) // expected-error {{cannot call value of non-function type '()'}}
126+
;
127+
{ $0(3) }
128+
{ [n] in consume($0) }(42) // expected-error {{cannot call value of non-function type '()'}}
129+
;
130+
({ $0(42) })
131+
{ [n] in consume($0) }(42) // expected-error {{cannot call value of non-function type '()'}}
132+
;
133+
134+
// Equivalent but more obviously unintended.
135+
136+
{ $0(3) } // expected-note {{callee is here}}
137+
138+
{ consume($0) }(42) // expected-error {{cannot call value of non-function type '()'}}
139+
// expected-warning@-1 {{braces here form a trailing closure separated from its callee by multiple newlines}}
140+
141+
({ $0(3) }) // expected-note {{callee is here}}
142+
143+
{ consume($0) }(42) // expected-error {{cannot call value of non-function type '()'}}
144+
// expected-warning@-1 {{braces here form a trailing closure separated from its callee by multiple newlines}}
145+
;
146+
147+
// Also a valid call (!!)
148+
{ $0 { $0 } } { $0 { 1 } } // expected-error {{expression resolves to an unused function}}
149+
consume(111)
150+
}
87151

88152

89153
// <rdar://problem/22162441> Crash from failing to diagnose nonexistent method access inside closure

test/Parse/availability_query.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ if #available(OSX 10.51, *) && #available(OSX 10.52, *) { // expected-error {{ex
2121
}
2222

2323

24-
if #available { // expected-error {{expected availability condition}} expected-error {{braced block of statements is an unused closure}} expected-error {{statement cannot begin with a closure expression}} expected-note {{explicitly discard the result of the closure by assigning to '_'}} {{15-15=_ = }} expected-error {{expression resolves to an unused function}}
24+
if #available { // expected-error {{expected availability condition}} expected-error {{closure expression is unused}} expected-error {{top-level statement cannot begin with a closure expression}} expected-note {{did you mean to use a 'do' statement?}} {{15-15=do }}
2525
}
2626

2727
if #available( { // expected-error {{expected platform name}} expected-error {{expected ')'}} expected-note {{to match this opening '('}}

test/Parse/errors.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func illformed() throws {
102102
_ = try genError()
103103

104104
// TODO: this recovery is terrible
105-
} catch MSV.CarriesInt(let i) where i == genError()) { // expected-error {{call can throw, but errors cannot be thrown out of a catch guard expression}} expected-error {{expected '{'}} expected-error {{braced block of statements is an unused closure}} expected-error {{expression resolves to an unused function}}
105+
} catch MSV.CarriesInt(let i) where i == genError()) { // expected-error {{call can throw, but errors cannot be thrown out of a catch guard expression}} expected-error {{expected '{'}} expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{58-58=do }}
106106
}
107107
}
108108

0 commit comments

Comments
 (0)