Skip to content

Commit 859b638

Browse files
committed
[Trailing closures] Warn about use of deprecated "backward" scan.
Whenever we form a call that relies on the deprecated "backward" scan, produce a warning to note the deprecation along with a Fix-It to label the parameter appropriately (and suppress the warning). For example: warning: backward matching of the unlabeled trailing closure is deprecated; label the argument with 'g' to suppress this warning trailingClosureEitherDirection { $0 * $1 } ^ (g: )
1 parent 17669d7 commit 859b638

File tree

5 files changed

+148
-76
lines changed

5 files changed

+148
-76
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,9 @@ WARNING(unlabeled_trailing_closure_changed_behavior_same_param_name,none,
11951195
NOTE(trailing_closure_select_parameter,none,
11961196
"label the argument with %0 to %select{retain the pre-Swift 5.3 behavior|silence this warning for Swift 5.3 and newer}1",
11971197
(Identifier, unsigned))
1198+
WARNING(unlabeled_trailing_closure_deprecated,none,
1199+
"backward matching of the unlabeled trailing closure is deprecated; label the argument with %0 to suppress this warning",
1200+
(Identifier))
11981201
NOTE(decl_multiple_defaulted_closure_parameters,none,
11991202
"%0 contains defaulted closure parameters %1 and %2",
12001203
(DeclName, Identifier, Identifier))

lib/Sema/CSApply.cpp

Lines changed: 134 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5471,6 +5471,96 @@ static unsigned functionParameterArity(AnyFunctionType::Param param) {
54715471
return paramType->castTo<AnyFunctionType>()->getNumParams();
54725472
}
54735473

5474+
/// Attach a Fix-It to the given diagnostic to give the trailing closure
5475+
/// argument a label.
5476+
static void labelTrailingClosureArgument(
5477+
ASTContext &ctx, Expr *fn, Expr *arg, Identifier paramName,
5478+
Expr *trailingClosure, InFlightDiagnostic &diag) {
5479+
// Dig out source locations.
5480+
SourceLoc existingRParenLoc;
5481+
SourceLoc leadingCommaLoc;
5482+
if (auto tupleExpr = dyn_cast<TupleExpr>(arg)) {
5483+
existingRParenLoc = tupleExpr->getRParenLoc();
5484+
assert(tupleExpr->getNumElements() >= 2 && "Should be a ParenExpr?");
5485+
leadingCommaLoc = Lexer::getLocForEndOfToken(
5486+
ctx.SourceMgr,
5487+
tupleExpr->getElements()[tupleExpr->getNumElements()-2]->getEndLoc());
5488+
} else {
5489+
auto parenExpr = cast<ParenExpr>(arg);
5490+
existingRParenLoc = parenExpr->getRParenLoc();
5491+
}
5492+
5493+
// Figure out the text to be inserted before the trailing closure.
5494+
SmallString<16> insertionText;
5495+
SourceLoc insertionLoc;
5496+
if (leadingCommaLoc.isValid()) {
5497+
insertionText += ", ";
5498+
assert(existingRParenLoc.isValid());
5499+
insertionLoc = leadingCommaLoc;
5500+
} else if (existingRParenLoc.isInvalid()) {
5501+
insertionText += "(";
5502+
insertionLoc = Lexer::getLocForEndOfToken(
5503+
ctx.SourceMgr, fn->getEndLoc());
5504+
} else {
5505+
insertionLoc = existingRParenLoc;
5506+
}
5507+
5508+
// Add the label, if there is one.
5509+
if (!paramName.empty()) {
5510+
insertionText += paramName.str();
5511+
insertionText += ": ";
5512+
}
5513+
5514+
// If there is an existing right parentheses, remove it while we
5515+
// insert the new text.
5516+
if (existingRParenLoc.isValid()) {
5517+
SourceLoc afterExistingRParenLoc = Lexer::getLocForEndOfToken(
5518+
ctx.SourceMgr, existingRParenLoc);
5519+
diag.fixItReplaceChars(
5520+
insertionLoc, afterExistingRParenLoc, insertionText);
5521+
} else {
5522+
// Insert the appropriate prefix.
5523+
diag.fixItInsert(insertionLoc, insertionText);
5524+
}
5525+
5526+
// Insert a right parenthesis after the closing '}' of the trailing closure;
5527+
SourceLoc newRParenLoc = Lexer::getLocForEndOfToken(
5528+
ctx.SourceMgr, trailingClosure->getEndLoc());
5529+
diag.fixItInsert(newRParenLoc, ")");
5530+
}
5531+
5532+
/// Find the trailing closure argument of a tuple or parenthesized expression.
5533+
///
5534+
/// Due to a quirk of the backward scan that could allow reordering of
5535+
/// arguments in the presence of a trailing closure, it might not be the last
5536+
/// argument in the tuple.
5537+
static Expr *findTrailingClosureArgument(Expr *arg) {
5538+
if (auto parenExpr = dyn_cast<ParenExpr>(arg)) {
5539+
return parenExpr->getSubExpr();
5540+
}
5541+
5542+
auto tupleExpr = cast<TupleExpr>(arg);
5543+
SourceLoc endLoc = tupleExpr->getEndLoc();
5544+
for (Expr *elt : llvm::reverse(tupleExpr->getElements())) {
5545+
if (elt->getEndLoc() == endLoc)
5546+
return elt;
5547+
}
5548+
5549+
return tupleExpr->getElements().back();
5550+
}
5551+
5552+
/// Find the index of the parameter that binds the given argument.
5553+
static unsigned findParamBindingArgument(
5554+
ArrayRef<ParamBinding> parameterBindings, unsigned argIndex) {
5555+
for (unsigned paramIdx : indices(parameterBindings)) {
5556+
if (llvm::find(parameterBindings[paramIdx], argIndex)
5557+
!= parameterBindings[paramIdx].end())
5558+
return paramIdx;
5559+
}
5560+
5561+
llvm_unreachable("No parameter binds the argument?");
5562+
}
5563+
54745564
/// SE-0286 changed the direction in which the unlabeled trailing closure
54755565
/// argument is matched to a parameter, from backward (the pre-Swift 5.3
54765566
/// semantics) to forward (after SE-0286). Identify cases where this may
@@ -5492,13 +5582,8 @@ static void maybeWarnAboutTrailingClosureBindingChange(
54925582
return;
54935583

54945584
// Find the parameter that bound the unlabeled trailing closure argument.
5495-
unsigned paramIdx;
5496-
for (paramIdx = 0; paramIdx != parameterBindings.size(); ++paramIdx) {
5497-
if (llvm::find(parameterBindings[paramIdx], *unlabeledTrailingClosureIndex)
5498-
!= parameterBindings[paramIdx].end())
5499-
break;
5500-
}
5501-
assert(paramIdx != parameterBindings.size() && "Unbound trailing closure!");
5585+
unsigned paramIdx = findParamBindingArgument(
5586+
parameterBindings, *unlabeledTrailingClosureIndex);
55025587

55035588
// If this parameter requires an argument, it would have been unfilled
55045589
// prior to SE-2086; there is nothing to diagnose.
@@ -5528,30 +5613,16 @@ static void maybeWarnAboutTrailingClosureBindingChange(
55285613
functionParameterArity(params[*matchingBackwardParamIdx]))
55295614
return;
55305615

5531-
// Compute the various source locations where diagnostics will occur.
5532-
ASTContext &ctx = params[paramIdx].getPlainType()->getASTContext();
5533-
Expr *trailingClosure;
5534-
SourceLoc existingRParenLoc;
5535-
SourceLoc leadingCommaLoc;
5536-
if (auto tupleExpr = dyn_cast<TupleExpr>(arg)) {
5537-
trailingClosure = tupleExpr->getElements().back();
5538-
existingRParenLoc = tupleExpr->getRParenLoc();
5539-
assert(tupleExpr->getNumElements() >= 2 && "Should be a ParenExpr?");
5540-
leadingCommaLoc = Lexer::getLocForEndOfToken(
5541-
ctx.SourceMgr,
5542-
tupleExpr->getElements()[tupleExpr->getNumElements()-2]->getEndLoc());
5543-
} else {
5544-
auto parenExpr = cast<ParenExpr>(arg);
5545-
trailingClosure = parenExpr->getSubExpr();
5546-
existingRParenLoc = parenExpr->getRParenLoc();
5547-
}
5616+
// Dig out the trailing closure.
5617+
Expr *trailingClosure = findTrailingClosureArgument(arg);
55485618

55495619
// Determine the names of the parameters that would be matched by the
55505620
// forward and backward scans.
55515621
Identifier paramName = params[paramIdx].getLabel();
55525622
Identifier backwardParamName = params[*matchingBackwardParamIdx].getLabel();
55535623

55545624
// Produce a diagnostic referencing the callee.
5625+
ASTContext &ctx = params[paramIdx].getPlainType()->getASTContext();
55555626
auto noteCallee = [&] {
55565627
auto decl = callee.getDecl();
55575628
if (!decl)
@@ -5587,45 +5658,8 @@ static void maybeWarnAboutTrailingClosureBindingChange(
55875658
auto diag = ctx.Diags.diagnose(
55885659
trailingClosure->getStartLoc(), diag::trailing_closure_select_parameter,
55895660
paramName, which);
5590-
5591-
// Figure out the text to be inserted before the trailing closure.
5592-
SmallString<16> insertionText;
5593-
SourceLoc insertionLoc;
5594-
if (leadingCommaLoc.isValid()) {
5595-
insertionText += ", ";
5596-
assert(existingRParenLoc.isValid());
5597-
insertionLoc = leadingCommaLoc;
5598-
} else if (existingRParenLoc.isInvalid()) {
5599-
insertionText += "(";
5600-
insertionLoc = Lexer::getLocForEndOfToken(
5601-
ctx.SourceMgr, fn->getEndLoc());
5602-
} else {
5603-
insertionLoc = existingRParenLoc;
5604-
}
5605-
5606-
// Add the label, if there is one.
5607-
if (!paramName.empty()) {
5608-
insertionText += paramName.str();
5609-
insertionText += ": ";
5610-
}
5611-
5612-
// If there is an existing right parentheses, remove it while we
5613-
// insert the new text.
5614-
if (existingRParenLoc.isValid()) {
5615-
SourceLoc afterExistingRParenLoc = Lexer::getLocForEndOfToken(
5616-
ctx.SourceMgr, existingRParenLoc);
5617-
diag.fixItReplaceChars(
5618-
insertionLoc, afterExistingRParenLoc, insertionText);
5619-
} else {
5620-
// Insert the appropriate prefix.
5621-
diag.fixItInsert(insertionLoc, insertionText);
5622-
}
5623-
5624-
5625-
// Insert a right parenthesis after the closing '}' of the trailing closure;
5626-
SourceLoc newRParenLoc = Lexer::getLocForEndOfToken(
5627-
ctx.SourceMgr, trailingClosure->getEndLoc());
5628-
diag.fixItInsert(newRParenLoc, ")");
5661+
labelTrailingClosureArgument(
5662+
ctx, fn, arg, paramName, trailingClosure, diag);
56295663
};
56305664

56315665
diagResolution(backwardParamName, 0);
@@ -5634,6 +5668,34 @@ static void maybeWarnAboutTrailingClosureBindingChange(
56345668
noteCallee();
56355669
}
56365670

5671+
/// Warn about the use of the deprecated "backward" scan for matching the
5672+
/// unlabeled trailing closure. It was needed to properly type check, but
5673+
/// this code will break with a future version of Swift.
5674+
static void warnAboutTrailingClosureBackwardScan(
5675+
ConcreteDeclRef callee, Expr *fn, Expr *arg,
5676+
ArrayRef<AnyFunctionType::Param> params,
5677+
Optional<unsigned> unlabeledTrailingClosureIndex,
5678+
ArrayRef<ParamBinding> parameterBindings) {
5679+
5680+
Expr *trailingClosure = findTrailingClosureArgument(arg);
5681+
unsigned paramIdx = findParamBindingArgument(
5682+
parameterBindings, *unlabeledTrailingClosureIndex);
5683+
ASTContext &ctx = params[paramIdx].getPlainType()->getASTContext();
5684+
Identifier paramName = params[paramIdx].getLabel();
5685+
5686+
{
5687+
auto diag = ctx.Diags.diagnose(
5688+
trailingClosure->getStartLoc(),
5689+
diag::unlabeled_trailing_closure_deprecated, paramName);
5690+
labelTrailingClosureArgument(
5691+
ctx, fn, arg, paramName, trailingClosure, diag);
5692+
}
5693+
5694+
if (auto decl = callee.getDecl()) {
5695+
ctx.Diags.diagnose(decl, diag::decl_declared_here, decl->getName());
5696+
}
5697+
}
5698+
56375699
Expr *ExprRewriter::coerceCallArguments(
56385700
Expr *arg, AnyFunctionType *funcType,
56395701
ConcreteDeclRef callee,
@@ -5727,9 +5789,15 @@ Expr *ExprRewriter::coerceCallArguments(
57275789

57285790
// Warn if there was a recent change in trailing closure binding semantics
57295791
// that might have lead to a silent change in behavior.
5730-
maybeWarnAboutTrailingClosureBindingChange(
5731-
callee, apply ? apply->getFn() : nullptr, arg, args, params, paramInfo,
5732-
unlabeledTrailingClosureIndex, parameterBindings);
5792+
if (trailingClosureMatching == TrailingClosureMatching::Forward) {
5793+
maybeWarnAboutTrailingClosureBindingChange(
5794+
callee, apply ? apply->getFn() : nullptr, arg, args, params, paramInfo,
5795+
unlabeledTrailingClosureIndex, parameterBindings);
5796+
} else {
5797+
warnAboutTrailingClosureBackwardScan(
5798+
callee, apply ? apply->getFn() : nullptr, arg, params,
5799+
unlabeledTrailingClosureIndex, parameterBindings);
5800+
}
57335801

57345802
SourceLoc lParenLoc, rParenLoc;
57355803
if (argTuple) {

test/expr/closure/trailing.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ func takeValueAndFunc(_ value: Int, _ f: (Int) -> Int) {}
66
func takeTwoFuncs(_ f: (Int) -> Int, _ g: (Int) -> Int) {}
77
func takeFuncWithDefault(f : ((Int) -> Int)? = nil) {}
88
func takeTwoFuncsWithDefaults(f1 : ((Int) -> Int)? = nil, f2 : ((String) -> String)? = nil) {} // expected-note{{contains defaulted closure parameters 'f1' and 'f2'}}
9+
// expected-note@-1{{'takeTwoFuncsWithDefaults(f1:f2:)' declared here}}
910

1011
struct X {
1112
func takeFunc(_ f: (Int) -> Int) {}
@@ -105,8 +106,8 @@ func labeledArgumentAndTrailingClosure() {
105106
takeFuncWithDefault(f: { $0 + 1 })
106107

107108
// Trailing closure binds to first parameter... unless only the second matches.
108-
takeTwoFuncsWithDefaults { "Hello, " + $0 }
109-
takeTwoFuncsWithDefaults { $0 + 1 } // expected-warning{{rather than}}
109+
takeTwoFuncsWithDefaults { "Hello, " + $0 } // expected-warning{{backward matching of the unlabeled trailing closure is deprecated; label the argument with 'f2' to suppress this warning}}
110+
takeTwoFuncsWithDefaults { $0 + 1 } // expected-warning{{rather than}}
110111
// expected-note@-1 2{{label the argument}}
111112
takeTwoFuncsWithDefaults(f1: {$0 + 1 })
112113
}

test/expr/postfix/call/forward_trailing_closure_ambiguity.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func testNotAmbiguous2() {
8888
}
8989

9090
// Not ambiguous because of a missing default argument.
91-
func notAmbiguous3(
91+
func notAmbiguous3( // expected-note{{'notAmbiguous3(x:a:y:b:_:c:)' declared here}}
9292
x: (Int) -> Int = { $0 },
9393
a: Int = 5,
9494
y: (Int) -> Int = { $0 },
@@ -98,5 +98,5 @@ func notAmbiguous3(
9898
) { }
9999

100100
func testNotAmbiguous3() {
101-
notAmbiguous3 { $0 }
101+
notAmbiguous3 { $0 } // expected-warning{{backward matching of the unlabeled trailing closure is deprecated; label the argument with '_' to suppress this warning}}
102102
}

test/expr/postfix/call/forward_trailing_closure_fuzzy.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// RUN: %target-typecheck-verify-swift
22

3-
func doSomething(onError: ((Error) -> Void)? = nil, onCompletion: (Int) -> Void) { }
3+
func doSomething(onError: ((Error) -> Void)? = nil, onCompletion: (Int) -> Void) { } // expected-note{{'doSomething(onError:onCompletion:)' declared here}}
44

55
func testDoSomething() {
6-
doSomething { x in
6+
doSomething { x in // expected-warning{{backward matching of the unlabeled trailing closure is deprecated; label the argument with 'onCompletion' to suppress this warning}}
77
print(x)
88
}
99

@@ -31,13 +31,13 @@ func testTrailingClosures() {
3131

3232
// Ensure that we can match either with the forward or the backward rule,
3333
// depending on additional type check information.
34-
func trailingClosureEitherDirection(
34+
func trailingClosureEitherDirection( // expected-note{{'trailingClosureEitherDirection(f:g:)' declared here}}
3535
f: (Int) -> Int = { $0 }, g: (Int, Int) -> Int = { $0 + $1 }
3636
) { }
3737

3838
func testTrailingClosureEitherDirection() {
3939
trailingClosureEitherDirection { -$0 }
40-
trailingClosureEitherDirection { $0 * $1 }
40+
trailingClosureEitherDirection { $0 * $1 } // expected-warning{{backward matching of the unlabeled trailing closure is deprecated; label the argument with 'g' to suppress this warning}}{{33-33=(g: }}{{45-45=)}}
4141
}
4242

4343
// Check that we resolve ambiguities when both directions can be matched.
@@ -51,11 +51,11 @@ trailingClosureBothDirections { $0 * $1 } // expected-warning{{since Swift 5.3,
5151

5252
// Check an amusing quirk of the "backward" rule that allows the order of
5353
// arguments to be swapped.
54-
struct AccidentalReorder {
54+
struct AccidentalReorder { // expected-note{{'init(content:optionalInt:)' declared here}}
5555
let content: () -> Int
5656
var optionalInt: Int?
5757
}
5858

5959
func testAccidentalReorder() {
60-
_ = AccidentalReorder(optionalInt: 17) { 42 }
60+
_ = AccidentalReorder(optionalInt: 17) { 42 } // expected-warning{{backward matching of the unlabeled trailing closure is deprecated; label the argument with 'content' to suppress this warning}}
6161
}

0 commit comments

Comments
 (0)