Skip to content

Commit bb66d45

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 79263cb commit bb66d45

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
@@ -1183,6 +1183,9 @@ WARNING(unlabeled_trailing_closure_changed_behavior_same_param_name,none,
11831183
NOTE(trailing_closure_select_parameter,none,
11841184
"label the argument with %0 to %select{retain the pre-Swift 5.3 behavior|silence this warning for Swift 5.3 and newer}1",
11851185
(Identifier, unsigned))
1186+
WARNING(unlabeled_trailing_closure_deprecated,none,
1187+
"backward matching of the unlabeled trailing closure is deprecated; label the argument with %0 to suppress this warning",
1188+
(Identifier))
11861189
NOTE(decl_multiple_defaulted_closure_parameters,none,
11871190
"%0 contains defaulted closure parameters %1 and %2",
11881191
(DeclName, Identifier, Identifier))

lib/Sema/CSApply.cpp

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

5540+
/// Attach a Fix-It to the given diagnostic to give the trailing closure
5541+
/// argument a label.
5542+
static void labelTrailingClosureArgument(
5543+
ASTContext &ctx, Expr *fn, Expr *arg, Identifier paramName,
5544+
Expr *trailingClosure, InFlightDiagnostic &diag) {
5545+
// Dig out source locations.
5546+
SourceLoc existingRParenLoc;
5547+
SourceLoc leadingCommaLoc;
5548+
if (auto tupleExpr = dyn_cast<TupleExpr>(arg)) {
5549+
existingRParenLoc = tupleExpr->getRParenLoc();
5550+
assert(tupleExpr->getNumElements() >= 2 && "Should be a ParenExpr?");
5551+
leadingCommaLoc = Lexer::getLocForEndOfToken(
5552+
ctx.SourceMgr,
5553+
tupleExpr->getElements()[tupleExpr->getNumElements()-2]->getEndLoc());
5554+
} else {
5555+
auto parenExpr = cast<ParenExpr>(arg);
5556+
existingRParenLoc = parenExpr->getRParenLoc();
5557+
}
5558+
5559+
// Figure out the text to be inserted before the trailing closure.
5560+
SmallString<16> insertionText;
5561+
SourceLoc insertionLoc;
5562+
if (leadingCommaLoc.isValid()) {
5563+
insertionText += ", ";
5564+
assert(existingRParenLoc.isValid());
5565+
insertionLoc = leadingCommaLoc;
5566+
} else if (existingRParenLoc.isInvalid()) {
5567+
insertionText += "(";
5568+
insertionLoc = Lexer::getLocForEndOfToken(
5569+
ctx.SourceMgr, fn->getEndLoc());
5570+
} else {
5571+
insertionLoc = existingRParenLoc;
5572+
}
5573+
5574+
// Add the label, if there is one.
5575+
if (!paramName.empty()) {
5576+
insertionText += paramName.str();
5577+
insertionText += ": ";
5578+
}
5579+
5580+
// If there is an existing right parentheses, remove it while we
5581+
// insert the new text.
5582+
if (existingRParenLoc.isValid()) {
5583+
SourceLoc afterExistingRParenLoc = Lexer::getLocForEndOfToken(
5584+
ctx.SourceMgr, existingRParenLoc);
5585+
diag.fixItReplaceChars(
5586+
insertionLoc, afterExistingRParenLoc, insertionText);
5587+
} else {
5588+
// Insert the appropriate prefix.
5589+
diag.fixItInsert(insertionLoc, insertionText);
5590+
}
5591+
5592+
// Insert a right parenthesis after the closing '}' of the trailing closure;
5593+
SourceLoc newRParenLoc = Lexer::getLocForEndOfToken(
5594+
ctx.SourceMgr, trailingClosure->getEndLoc());
5595+
diag.fixItInsert(newRParenLoc, ")");
5596+
}
5597+
5598+
/// Find the trailing closure argument of a tuple or parenthesized expression.
5599+
///
5600+
/// Due to a quirk of the backward scan that could allow reordering of
5601+
/// arguments in the presence of a trailing closure, it might not be the last
5602+
/// argument in the tuple.
5603+
static Expr *findTrailingClosureArgument(Expr *arg) {
5604+
if (auto parenExpr = dyn_cast<ParenExpr>(arg)) {
5605+
return parenExpr->getSubExpr();
5606+
}
5607+
5608+
auto tupleExpr = cast<TupleExpr>(arg);
5609+
SourceLoc endLoc = tupleExpr->getEndLoc();
5610+
for (Expr *elt : llvm::reverse(tupleExpr->getElements())) {
5611+
if (elt->getEndLoc() == endLoc)
5612+
return elt;
5613+
}
5614+
5615+
return tupleExpr->getElements().back();
5616+
}
5617+
5618+
/// Find the index of the parameter that binds the given argument.
5619+
static unsigned findParamBindingArgument(
5620+
ArrayRef<ParamBinding> parameterBindings, unsigned argIndex) {
5621+
for (unsigned paramIdx : indices(parameterBindings)) {
5622+
if (llvm::find(parameterBindings[paramIdx], argIndex)
5623+
!= parameterBindings[paramIdx].end())
5624+
return paramIdx;
5625+
}
5626+
5627+
llvm_unreachable("No parameter binds the argument?");
5628+
}
5629+
55405630
/// SE-0286 changed the direction in which the unlabeled trailing closure
55415631
/// argument is matched to a parameter, from backward (the pre-Swift 5.3
55425632
/// semantics) to forward (after SE-0286). Identify cases where this may
@@ -5558,13 +5648,8 @@ static void maybeWarnAboutTrailingClosureBindingChange(
55585648
return;
55595649

55605650
// Find the parameter that bound the unlabeled trailing closure argument.
5561-
unsigned paramIdx;
5562-
for (paramIdx = 0; paramIdx != parameterBindings.size(); ++paramIdx) {
5563-
if (llvm::find(parameterBindings[paramIdx], *unlabeledTrailingClosureIndex)
5564-
!= parameterBindings[paramIdx].end())
5565-
break;
5566-
}
5567-
assert(paramIdx != parameterBindings.size() && "Unbound trailing closure!");
5651+
unsigned paramIdx = findParamBindingArgument(
5652+
parameterBindings, *unlabeledTrailingClosureIndex);
55685653

55695654
// If this parameter requires an argument, it would have been unfilled
55705655
// prior to SE-2086; there is nothing to diagnose.
@@ -5594,30 +5679,16 @@ static void maybeWarnAboutTrailingClosureBindingChange(
55945679
functionParameterArity(params[*matchingBackwardParamIdx]))
55955680
return;
55965681

5597-
// Compute the various source locations where diagnostics will occur.
5598-
ASTContext &ctx = params[paramIdx].getPlainType()->getASTContext();
5599-
Expr *trailingClosure;
5600-
SourceLoc existingRParenLoc;
5601-
SourceLoc leadingCommaLoc;
5602-
if (auto tupleExpr = dyn_cast<TupleExpr>(arg)) {
5603-
trailingClosure = tupleExpr->getElements().back();
5604-
existingRParenLoc = tupleExpr->getRParenLoc();
5605-
assert(tupleExpr->getNumElements() >= 2 && "Should be a ParenExpr?");
5606-
leadingCommaLoc = Lexer::getLocForEndOfToken(
5607-
ctx.SourceMgr,
5608-
tupleExpr->getElements()[tupleExpr->getNumElements()-2]->getEndLoc());
5609-
} else {
5610-
auto parenExpr = cast<ParenExpr>(arg);
5611-
trailingClosure = parenExpr->getSubExpr();
5612-
existingRParenLoc = parenExpr->getRParenLoc();
5613-
}
5682+
// Dig out the trailing closure.
5683+
Expr *trailingClosure = findTrailingClosureArgument(arg);
56145684

56155685
// Determine the names of the parameters that would be matched by the
56165686
// forward and backward scans.
56175687
Identifier paramName = params[paramIdx].getLabel();
56185688
Identifier backwardParamName = params[*matchingBackwardParamIdx].getLabel();
56195689

56205690
// Produce a diagnostic referencing the callee.
5691+
ASTContext &ctx = params[paramIdx].getPlainType()->getASTContext();
56215692
auto noteCallee = [&] {
56225693
auto decl = callee.getDecl();
56235694
if (!decl)
@@ -5653,45 +5724,8 @@ static void maybeWarnAboutTrailingClosureBindingChange(
56535724
auto diag = ctx.Diags.diagnose(
56545725
trailingClosure->getStartLoc(), diag::trailing_closure_select_parameter,
56555726
paramName, which);
5656-
5657-
// Figure out the text to be inserted before the trailing closure.
5658-
SmallString<16> insertionText;
5659-
SourceLoc insertionLoc;
5660-
if (leadingCommaLoc.isValid()) {
5661-
insertionText += ", ";
5662-
assert(existingRParenLoc.isValid());
5663-
insertionLoc = leadingCommaLoc;
5664-
} else if (existingRParenLoc.isInvalid()) {
5665-
insertionText += "(";
5666-
insertionLoc = Lexer::getLocForEndOfToken(
5667-
ctx.SourceMgr, fn->getEndLoc());
5668-
} else {
5669-
insertionLoc = existingRParenLoc;
5670-
}
5671-
5672-
// Add the label, if there is one.
5673-
if (!paramName.empty()) {
5674-
insertionText += paramName.str();
5675-
insertionText += ": ";
5676-
}
5677-
5678-
// If there is an existing right parentheses, remove it while we
5679-
// insert the new text.
5680-
if (existingRParenLoc.isValid()) {
5681-
SourceLoc afterExistingRParenLoc = Lexer::getLocForEndOfToken(
5682-
ctx.SourceMgr, existingRParenLoc);
5683-
diag.fixItReplaceChars(
5684-
insertionLoc, afterExistingRParenLoc, insertionText);
5685-
} else {
5686-
// Insert the appropriate prefix.
5687-
diag.fixItInsert(insertionLoc, insertionText);
5688-
}
5689-
5690-
5691-
// Insert a right parenthesis after the closing '}' of the trailing closure;
5692-
SourceLoc newRParenLoc = Lexer::getLocForEndOfToken(
5693-
ctx.SourceMgr, trailingClosure->getEndLoc());
5694-
diag.fixItInsert(newRParenLoc, ")");
5727+
labelTrailingClosureArgument(
5728+
ctx, fn, arg, paramName, trailingClosure, diag);
56955729
};
56965730

56975731
diagResolution(backwardParamName, 0);
@@ -5700,6 +5734,34 @@ static void maybeWarnAboutTrailingClosureBindingChange(
57005734
noteCallee();
57015735
}
57025736

5737+
/// Warn about the use of the deprecated "backward" scan for matching the
5738+
/// unlabeled trailing closure. It was needed to properly type check, but
5739+
/// this code will break with a future version of Swift.
5740+
static void warnAboutTrailingClosureBackwardScan(
5741+
ConcreteDeclRef callee, Expr *fn, Expr *arg,
5742+
ArrayRef<AnyFunctionType::Param> params,
5743+
Optional<unsigned> unlabeledTrailingClosureIndex,
5744+
ArrayRef<ParamBinding> parameterBindings) {
5745+
5746+
Expr *trailingClosure = findTrailingClosureArgument(arg);
5747+
unsigned paramIdx = findParamBindingArgument(
5748+
parameterBindings, *unlabeledTrailingClosureIndex);
5749+
ASTContext &ctx = params[paramIdx].getPlainType()->getASTContext();
5750+
Identifier paramName = params[paramIdx].getLabel();
5751+
5752+
{
5753+
auto diag = ctx.Diags.diagnose(
5754+
trailingClosure->getStartLoc(),
5755+
diag::unlabeled_trailing_closure_deprecated, paramName);
5756+
labelTrailingClosureArgument(
5757+
ctx, fn, arg, paramName, trailingClosure, diag);
5758+
}
5759+
5760+
if (auto decl = callee.getDecl()) {
5761+
ctx.Diags.diagnose(decl, diag::decl_declared_here, decl->getName());
5762+
}
5763+
}
5764+
57035765
Expr *ExprRewriter::coerceCallArguments(
57045766
Expr *arg, AnyFunctionType *funcType,
57055767
ConcreteDeclRef callee,
@@ -5790,9 +5852,15 @@ Expr *ExprRewriter::coerceCallArguments(
57905852

57915853
// Warn if there was a recent change in trailing closure binding semantics
57925854
// that might have lead to a silent change in behavior.
5793-
maybeWarnAboutTrailingClosureBindingChange(
5794-
callee, apply ? apply->getFn() : nullptr, arg, args, params, paramInfo,
5795-
unlabeledTrailingClosureIndex, parameterBindings);
5855+
if (trailingClosureMatching == TrailingClosureMatching::Forward) {
5856+
maybeWarnAboutTrailingClosureBindingChange(
5857+
callee, apply ? apply->getFn() : nullptr, arg, args, params, paramInfo,
5858+
unlabeledTrailingClosureIndex, parameterBindings);
5859+
} else {
5860+
warnAboutTrailingClosureBackwardScan(
5861+
callee, apply ? apply->getFn() : nullptr, arg, params,
5862+
unlabeledTrailingClosureIndex, parameterBindings);
5863+
}
57965864

57975865
SourceLoc lParenLoc, rParenLoc;
57985866
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)