Skip to content

Commit b37330d

Browse files
committed
[Fixit] Add a fixit for converting non-trailing closures to trailing closures.
Reminded by Ben's last commit of code completion, I noticed that some users do not use trailing closures when helping migrating their code. To enhance the discoverability of this feature, this fix helps users to convert non-trailing closures to trailing closures. To ensure disambiguity after conversion, we check name conflict before adding the note.
1 parent 9af888c commit b37330d

File tree

3 files changed

+196
-0
lines changed

3 files changed

+196
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,10 @@ ERROR(string_literal_broken_proto,none,
20822082
ERROR(bool_type_broken,none,
20832083
"could not find a Bool type defined for 'is'", ())
20842084

2085+
NOTE(convert_to_trailing_closure,none,
2086+
"use trailing closure to simplify arguments", ())
2087+
2088+
20852089
// Array literals
20862090
ERROR(array_protocol_broken,none,
20872091
"ArrayLiteralConvertible protocol definition is broken", ())

lib/Sema/MiscDiagnostics.cpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1984,6 +1984,141 @@ static void diagAvailability(TypeChecker &TC, const Expr *E,
19841984
const_cast<Expr*>(E)->walk(walker);
19851985
}
19861986

1987+
namespace {
1988+
1989+
static ClosureExpr* getLastArgAsClosure(Expr* Arg) {
1990+
if (auto Paren = dyn_cast<ParenExpr>(Arg)) {
1991+
1992+
// If the argument is paren expression, get the sub expression as closure.
1993+
if (!Paren->hasTrailingClosure())
1994+
return dyn_cast_or_null<ClosureExpr>(Paren->getSubExpr());
1995+
} else if (auto Tuple = dyn_cast<TupleExpr>(Arg)) {
1996+
1997+
// If the argument is tuple expression, get the last expression in the tuple
1998+
// as closure.
1999+
if (!Tuple->hasTrailingClosure() && Tuple->getNumElements() > 0)
2000+
return dyn_cast<ClosureExpr>(Tuple->getElement(Tuple->getNumElements() - 1));
2001+
}
2002+
return nullptr;
2003+
}
2004+
2005+
static bool areIdentifiersSame(Identifier Left, Identifier Right) {
2006+
2007+
// If both are anonymous, returns true.
2008+
if (Left.empty() && Right.empty())
2009+
return true;
2010+
2011+
// If one is anonymous and the other is not, returns false.
2012+
if (Left.empty() || Right.empty())
2013+
return false;
2014+
2015+
// If both are not anonymous, compare the content.
2016+
return Left.str() == Right.str();
2017+
}
2018+
2019+
static bool willCauseAmbiguity(TypeChecker &TC, FuncDecl *SelectedFD) {
2020+
auto SelectedArgNames = SelectedFD->getEffectiveFullName().getArgumentNames();
2021+
auto SelectedArgNameExcludingClosure = SelectedArgNames.slice(0,
2022+
SelectedArgNames.size() - 1);
2023+
2024+
// Consider all decls from the same context with the simple
2025+
// identifier with SelectedFD.
2026+
for (ValueDecl *VD : TC.lookupUnqualified(SelectedFD->getDeclContext(),
2027+
SelectedFD->getName(), SourceLoc())) {
2028+
2029+
// Make sure the overload is not the selected function.
2030+
if (VD == SelectedFD)
2031+
continue;
2032+
2033+
// Make sure the overload is a function.
2034+
FuncDecl *FD = dyn_cast<FuncDecl>(VD);
2035+
if (!FD)
2036+
continue;
2037+
auto ArgNames = FD->getEffectiveFullName().getArgumentNames();
2038+
2039+
// If the overload requires more arguments than the selected; there are no
2040+
// chance of conflicts.
2041+
if (ArgNames.size() < SelectedArgNames.size())
2042+
continue;
2043+
2044+
// Assuming there is a conflict.
2045+
bool Conflict = true;
2046+
for (unsigned I = 0; I < SelectedArgNameExcludingClosure.size(); I ++) {
2047+
if (!areIdentifiersSame(ArgNames[I], SelectedArgNameExcludingClosure[I])) {
2048+
// Different argument names disprove our assumption.
2049+
Conflict = false;
2050+
}
2051+
}
2052+
if (Conflict)
2053+
return true;
2054+
}
2055+
return false;
2056+
}
2057+
2058+
static FuncDecl* digForFuncDecl(Expr *Fn) {
2059+
auto FD = dyn_cast_or_null<FuncDecl>(Fn->getReferencedDecl().getDecl());
2060+
if (FD)
2061+
return FD;
2062+
if (auto DE = dyn_cast<DotSyntaxCallExpr>(Fn)) {
2063+
return digForFuncDecl(DE->getFn());
2064+
}
2065+
return nullptr;
2066+
}
2067+
2068+
static void
2069+
applyConvertToTrailingClosureFixit(TypeChecker &TC, SourceManager &SM,
2070+
ClosureExpr *Closure, Expr *Arg) {
2071+
if (auto *Paren = dyn_cast<ParenExpr>(Arg)) {
2072+
SourceRange LRemove(Paren->getLParenLoc(),
2073+
Paren->getLParenLoc().getAdvancedLoc(1));
2074+
SourceRange RRemove(Paren->getRParenLoc(),
2075+
Paren->getRParenLoc().getAdvancedLoc(1));
2076+
2077+
// Remove the left and right paren.
2078+
TC.diagnose(Closure->getStartLoc(), diag::convert_to_trailing_closure).
2079+
fixItRemove(LRemove).fixItRemove(RRemove);
2080+
} else if (auto *Tuple = dyn_cast<TupleExpr>(Arg)) {
2081+
if (Tuple->getNumElements() > 1) {
2082+
SourceLoc ReplaceStart = Lexer::getLocForEndOfToken(SM,
2083+
Tuple->getElement(Tuple->getNumElements() - 2)->getEndLoc());
2084+
SourceRange Replace(ReplaceStart, Closure->getStartLoc());
2085+
SourceRange ToRemove(Tuple->getRParenLoc(),
2086+
Tuple->getRParenLoc().getAdvancedLoc(1));
2087+
2088+
// Closing the tuple before the closure; and remove the right paren after the
2089+
// closure.
2090+
TC.diagnose(Closure->getStartLoc(), diag::convert_to_trailing_closure).
2091+
fixItReplace(Replace, ") ").fixItRemove(ToRemove);
2092+
} else {
2093+
SourceRange LRemove(Tuple->getLParenLoc(), Closure->getStartLoc());
2094+
SourceRange RRemove(Lexer::getLocForEndOfToken(SM,Closure->getEndLoc()),
2095+
Lexer::getLocForEndOfToken(SM, Tuple->getEndLoc()));
2096+
TC.diagnose(Closure->getStartLoc(), diag::convert_to_trailing_closure).
2097+
fixItRemove(LRemove).fixItRemove(RRemove);
2098+
}
2099+
}
2100+
}
2101+
}
2102+
2103+
static void diagConvertToTrailingClosure(TypeChecker &TC, const Expr *E,
2104+
DeclContext *DC) {
2105+
auto* AE = dyn_cast<ApplyExpr>(E);
2106+
if (!AE)
2107+
return;
2108+
2109+
auto* Closure = getLastArgAsClosure(AE->getArg());
2110+
if (!Closure)
2111+
return;
2112+
if (auto* FD = digForFuncDecl(AE->getFn())) {
2113+
// Check the condition whether converting will cause ambiguity.
2114+
if (!willCauseAmbiguity(TC, FD))
2115+
// Without ambiguity, we can proceed to fix.
2116+
applyConvertToTrailingClosureFixit(TC, DC->getASTContext().SourceMgr,
2117+
Closure, AE->getArg());
2118+
}
2119+
}
2120+
2121+
19872122
//===----------------------------------------------------------------------===//
19882123
// Per func/init diagnostics
19892124
//===----------------------------------------------------------------------===//
@@ -3210,6 +3345,7 @@ void swift::performSyntacticExprDiagnostics(TypeChecker &TC, const Expr *E,
32103345
diagRecursivePropertyAccess(TC, E, DC);
32113346
diagnoseImplicitSelfUseInClosure(TC, E, DC);
32123347
diagAvailability(TC, E, const_cast<DeclContext*>(DC));
3348+
diagConvertToTrailingClosure(TC, E, const_cast<DeclContext*>(DC));
32133349
if (TC.Context.LangOpts.EnableObjCInterop)
32143350
diagDeprecatedObjCSelectors(TC, DC, E);
32153351
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// RUN: %target-parse-verify-swift
2+
3+
func bar(v : Int, c1 : () ->Void) {}
4+
func bar1(_ v : () ->Void) {}
5+
func bar2(v : Int, c1 : () ->Void) {}
6+
func bar2(v : Int, c2 : () ->Void) {}
7+
func bar3(v1 : Int, c1 : (Int) ->Void) {}
8+
func bar3(v2 : Int, c1 : (Int) ->Void) {}
9+
func bar4(c1 : (Int, Int, Int) ->Void) {}
10+
func bar4(c2: (Int, Int, Int) ->Void) {}
11+
func bar5(c1: (Int, Int, Int) ->Void) {}
12+
func bar6(c1 : () ->Void, v : Int) {}
13+
14+
class C {
15+
func bar(v : Int, c1 : () ->Void) {}
16+
func bar1(_ v : () ->Void) {}
17+
func bar2(v : Int, c1 : () ->Void) {}
18+
func bar2(v : Int, c2 : () ->Void) {}
19+
func bar3(v1 : Int, c1 : (Int) ->Void) {}
20+
func bar3(v2 : Int, c1 : (Int) ->Void) {}
21+
}
22+
23+
struct S {
24+
func bar(v : Int, c1 : () ->Void) {}
25+
func bar1(_ v : () ->Void) {}
26+
func bar2(v : Int, c1 : () ->Void) {}
27+
func bar2(v : Int, c2 : () ->Void) {}
28+
func bar3(v1 : Int, c1 : (Int) ->Void) {}
29+
func bar3(v2 : Int, c1 : (Int) ->Void) {}
30+
}
31+
32+
func foo(c : C, s : S) {
33+
bar(v : 3, c1: {}) // expected-note {{use trailing closure to simplify arguments}} {{12-19=) }} {{20-110=}}
34+
bar1 ({}) // expected-note {{use trailing closure to simplify arguments}} {{8-10=}} {{11-97=}}
35+
bar2(v : 3, c1: {}) // {{none}}
36+
bar2(v : 3, c2: {}) // {{none}}
37+
bar3(v1: 3, c1 : { (i) in }) // expected-note {{use trailing closure to simplify arguments}} {{13-21=) }} {{30-120=}}
38+
bar3(v2: 3, c1 : { (i) in }) // expected-note {{use trailing closure to simplify arguments}} {{13-21=) }} {{30-120=}}
39+
bar4(c1 : { (i, j, k) in }) // {{none}}
40+
bar5(c1 : { (i, j, k) in }) // expected-note {{use trailing closure to simplify arguments}} {{7-14=}} {{29-116=}}
41+
bar6(c1: {}, v: 3) // {{none}}
42+
43+
c.bar(v : 3, c1: {}) // expected-note {{use trailing closure to simplify arguments}} {{14-21=) }} {{22-112=}}
44+
c.bar1({}) // expected-note {{use trailing closure to simplify arguments}} {{9-11=}} {{12-98=}}
45+
c.bar2(v : 3, c1: {}) // {{none}}
46+
c.bar2(v : 3, c2: {}) // {{none}}
47+
c.bar3(v1: 3, c1 : { (i) in }) // expected-note {{use trailing closure to simplify arguments}} {{15-23=) }} {{32-122=}}
48+
c.bar3(v2: 3, c1 : { (i) in }) // expected-note {{use trailing closure to simplify arguments}} {{15-23=) }} {{32-122=}}
49+
50+
s.bar(v : 3, c1: {}) // expected-note {{use trailing closure to simplify arguments}} {{14-21=) }} {{22-112=}}
51+
s.bar1({}) // expected-note {{use trailing closure to simplify arguments}} {{9-11=}} {{12-98=}}
52+
s.bar2(v : 3, c1: {}) // {{none}}
53+
s.bar2(v : 3, c2: {}) // {{none}}
54+
s.bar3(v1: 3, c1 : { (i) in }) // expected-note {{use trailing closure to simplify arguments}} {{15-23=) }} {{32-122=}}
55+
s.bar3(v2: 3, c1 : { (i) in }) // expected-note {{use trailing closure to simplify arguments}} {{15-23=) }} {{32-122=}}
56+
}

0 commit comments

Comments
 (0)