Skip to content

Commit a57199c

Browse files
committed
[refactoring] Implement "Convert to Trailing Closure" refactoring action
1 parent 53e5145 commit a57199c

File tree

12 files changed

+336
-9
lines changed

12 files changed

+336
-9
lines changed

include/swift/AST/Expr.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2712,6 +2712,12 @@ class ImplicitConversionExpr : public Expr {
27122712
Expr *getSubExpr() const { return SubExpr; }
27132713
void setSubExpr(Expr *e) { SubExpr = e; }
27142714

2715+
Expr *getSyntacticSubExpr() const {
2716+
if (auto *ICE = dyn_cast<ImplicitConversionExpr>(SubExpr))
2717+
return ICE->getSyntacticSubExpr();
2718+
return SubExpr;
2719+
}
2720+
27152721
static bool classof(const Expr *E) {
27162722
return E->getKind() >= ExprKind::First_ImplicitConversionExpr &&
27172723
E->getKind() <= ExprKind::Last_ImplicitConversionExpr;

include/swift/IDE/RefactoringKinds.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ CURSOR_REFACTORING(CollapseNestedIfExpr, "Collapse Nested If Expression", collap
4444

4545
CURSOR_REFACTORING(ConvertToDoCatch, "Convert To Do/Catch", convert.do.catch)
4646

47+
CURSOR_REFACTORING(TrailingClosure, "Convert To Trailing Closure", trailingclosure)
48+
4749
RANGE_REFACTORING(ExtractExpr, "Extract Expression", extract.expr)
4850

4951
RANGE_REFACTORING(ExtractFunction, "Extract Method", extract.function)

lib/IDE/Refactoring.cpp

Lines changed: 119 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,28 @@ class ContextFinder : public SourceEntityWalker {
4040
SourceFile &SF;
4141
ASTContext &Ctx;
4242
SourceManager &SM;
43-
ASTNode Target;
43+
SourceRange Target;
4444
llvm::function_ref<bool(ASTNode)> IsContext;
4545
SmallVector<ASTNode, 4> AllContexts;
4646
bool contains(ASTNode Enclosing) {
47-
auto Result = SM.rangeContains(Enclosing.getSourceRange(),
48-
Target.getSourceRange());
47+
auto Result = SM.rangeContains(Enclosing.getSourceRange(), Target);
4948
if (Result && IsContext(Enclosing))
5049
AllContexts.push_back(Enclosing);
5150
return Result;
5251
}
5352
public:
54-
ContextFinder(SourceFile &SF, ASTNode Target,
53+
ContextFinder(SourceFile &SF, ASTNode TargetNode,
5554
llvm::function_ref<bool(ASTNode)> IsContext =
56-
[](ASTNode N) { return true; }) : SF(SF),
57-
Ctx(SF.getASTContext()), SM(Ctx.SourceMgr), Target(Target),
58-
IsContext(IsContext) {}
55+
[](ASTNode N) { return true; }) :
56+
SF(SF), Ctx(SF.getASTContext()), SM(Ctx.SourceMgr),
57+
Target(TargetNode.getSourceRange()), IsContext(IsContext) {}
58+
ContextFinder(SourceFile &SF, SourceLoc TargetLoc,
59+
llvm::function_ref<bool(ASTNode)> IsContext =
60+
[](ASTNode N) { return true; }) :
61+
SF(SF), Ctx(SF.getASTContext()), SM(Ctx.SourceMgr),
62+
Target(TargetLoc), IsContext(IsContext) {
63+
assert(TargetLoc.isValid() && "Invalid loc to find");
64+
}
5965
bool walkToDeclPre(Decl *D, CharSourceRange Range) override { return contains(D); }
6066
bool walkToStmtPre(Stmt *S) override { return contains(S); }
6167
bool walkToExprPre(Expr *E) override { return contains(E); }
@@ -1722,7 +1728,8 @@ class FillProtocolStubContext {
17221728

17231729
FillProtocolStubContext FillProtocolStubContext::
17241730
getContextFromCursorInfo(ResolvedCursorInfo CursorInfo) {
1725-
assert(CursorInfo.isValid());
1731+
if(!CursorInfo.isValid())
1732+
return FillProtocolStubContext();
17261733
if (!CursorInfo.IsRef) {
17271734
// If the type name is on the declared nominal, e.g. "class A {}"
17281735
if (auto ND = dyn_cast<NominalTypeDecl>(CursorInfo.ValueD)) {
@@ -2127,6 +2134,110 @@ bool RefactoringActionSimplifyNumberLiteral::performChange() {
21272134
return true;
21282135
}
21292136

2137+
static CallExpr *findTrailingClosureTarget(SourceManager &SM,
2138+
ResolvedCursorInfo CursorInfo) {
2139+
if (CursorInfo.Kind == CursorInfoKind::StmtStart)
2140+
// StmtStart postion can't be a part of CallExpr.
2141+
return nullptr;
2142+
2143+
// Find inner most CallExpr
2144+
ContextFinder
2145+
Finder(*CursorInfo.SF, CursorInfo.Loc,
2146+
[](ASTNode N) {
2147+
return N.isStmt(StmtKind::Brace) || N.isExpr(ExprKind::Call);
2148+
});
2149+
Finder.resolve();
2150+
if (Finder.getContexts().empty()
2151+
|| !Finder.getContexts().back().is<Expr*>())
2152+
return nullptr;
2153+
CallExpr *CE = cast<CallExpr>(Finder.getContexts().back().get<Expr*>());
2154+
2155+
// The last arugment is a closure?
2156+
Expr *Args = CE->getArg();
2157+
if (!Args)
2158+
return nullptr;
2159+
Expr *LastArg;
2160+
if (auto *TSE = dyn_cast<TupleShuffleExpr>(Args))
2161+
Args = TSE->getSubExpr();
2162+
if (auto *PE = dyn_cast<ParenExpr>(Args)) {
2163+
LastArg = PE->getSubExpr();
2164+
} else {
2165+
auto *TE = cast<TupleExpr>(Args);
2166+
if (TE->getNumElements() == 0)
2167+
return nullptr;
2168+
LastArg = TE->getElements().back();
2169+
}
2170+
2171+
if (auto *ICE = dyn_cast<ImplicitConversionExpr>(LastArg))
2172+
LastArg = ICE->getSyntacticSubExpr();
2173+
2174+
if (isa<ClosureExpr>(LastArg) || isa<CaptureListExpr>(LastArg))
2175+
return CE;
2176+
return nullptr;
2177+
}
2178+
2179+
bool RefactoringActionTrailingClosure::
2180+
isApplicable(ResolvedCursorInfo CursorInfo, DiagnosticEngine &Diag) {
2181+
SourceManager &SM = CursorInfo.SF->getASTContext().SourceMgr;
2182+
return findTrailingClosureTarget(SM, CursorInfo);
2183+
}
2184+
2185+
bool RefactoringActionTrailingClosure::performChange() {
2186+
auto *CE = findTrailingClosureTarget(SM, CursorInfo);
2187+
if (!CE)
2188+
return true;
2189+
Expr *Args = CE->getArg();
2190+
if (auto *TSE = dyn_cast<TupleShuffleExpr>(Args))
2191+
Args = TSE;
2192+
2193+
Expr *ClosureArg = nullptr;
2194+
Expr *PrevArg = nullptr;
2195+
SourceLoc LPLoc, RPLoc;
2196+
2197+
if (auto *PE = dyn_cast<ParenExpr>(Args)) {
2198+
ClosureArg = PE->getSubExpr();
2199+
LPLoc = PE->getLParenLoc();
2200+
RPLoc = PE->getRParenLoc();
2201+
} else {
2202+
auto *TE = cast<TupleExpr>(Args);
2203+
auto NumArgs = TE->getNumElements();
2204+
if (NumArgs == 0)
2205+
return true;
2206+
LPLoc = TE->getLParenLoc();
2207+
RPLoc = TE->getRParenLoc();
2208+
ClosureArg = TE->getElement(NumArgs - 1);
2209+
if (NumArgs > 1)
2210+
PrevArg = TE->getElement(NumArgs - 2);
2211+
}
2212+
if (auto *ICE = dyn_cast<ImplicitConversionExpr>(ClosureArg))
2213+
ClosureArg = ICE->getSyntacticSubExpr();
2214+
2215+
if (LPLoc.isInvalid() || RPLoc.isInvalid())
2216+
return true;
2217+
2218+
// Replace:
2219+
// * Open paren with ' ' if the closure is sole argument.
2220+
// * Comma with ') ' otherwise.
2221+
if (PrevArg) {
2222+
CharSourceRange PreRange(
2223+
SM,
2224+
Lexer::getLocForEndOfToken(SM, PrevArg->getEndLoc()),
2225+
ClosureArg->getStartLoc());
2226+
EditConsumer.accept(SM, PreRange, ") ");
2227+
} else {
2228+
CharSourceRange PreRange(
2229+
SM, LPLoc, ClosureArg->getStartLoc());
2230+
EditConsumer.accept(SM, PreRange, " ");
2231+
}
2232+
// Remove original closing paren.
2233+
CharSourceRange PostRange(
2234+
SM,
2235+
Lexer::getLocForEndOfToken(SM, ClosureArg->getEndLoc()),
2236+
Lexer::getLocForEndOfToken(SM, RPLoc));
2237+
EditConsumer.remove(SM, PostRange);
2238+
return false;
2239+
}
2240+
21302241
static bool rangeStartMayNeedRename(ResolvedRangeInfo Info) {
21312242
switch(Info.Kind) {
21322243
case RangeKind::SingleExpression: {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
struct Foo {
2+
static func foo(a: () -> Int) {}
3+
func qux(x: Int, y: () -> Int ) {}
4+
}
5+
6+
func testTrailingClosure() -> String {
7+
Foo.foo(a: { 1 })
8+
Foo.bar(a: { print(3); return 1 })
9+
Foo().qux(x: 1, y: { 1 })
10+
let _ = Foo().quux(x: 1, y: { 1 })
11+
12+
[1,2,3]
13+
.filter({ $0 % 2 == 0 })
14+
.map({ $0 + 1 })
15+
}
16+
17+
// RUN: %refactor -source-filename %s -pos=7:3 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
18+
// RUN: %refactor -source-filename %s -pos=7:6 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
19+
// RUN: %refactor -source-filename %s -pos=7:7 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
20+
// RUN: %refactor -source-filename %s -pos=7:10 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
21+
// RUN: %refactor -source-filename %s -pos=7:11 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
22+
// RUN: %refactor -source-filename %s -pos=7:12 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
23+
// RUN: %refactor -source-filename %s -pos=7:14 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
24+
// RUN: %refactor -source-filename %s -pos=7:16 | %FileCheck %s -check-prefix=CHECK-NO-TRAILING-CLOSURE
25+
// RUN: %refactor -source-filename %s -pos=7:18 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
26+
// RUN: %refactor -source-filename %s -pos=7:19 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
27+
28+
// RUN: %refactor -source-filename %s -pos=8:3 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
29+
// RUN: %refactor -source-filename %s -pos=8:11 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
30+
31+
// RUN: %refactor -source-filename %s -pos=9:3 | %FileCheck %s -check-prefix=CHECK-NO-TRAILING-CLOSURE
32+
// RUN: %refactor -source-filename %s -pos=9:8 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
33+
34+
// RUN: %refactor -source-filename %s -pos=10:3 | %FileCheck %s -check-prefix=CHECK-NO-TRAILING-CLOSURE
35+
// RUN: %refactor -source-filename %s -pos=10:9 | %FileCheck %s -check-prefix=CHECK-NO-TRAILING-CLOSURE
36+
// RUN: %refactor -source-filename %s -pos=10:17 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
37+
38+
// RUN: %refactor -source-filename %s -pos=12:4 | %FileCheck %s -check-prefix=CHECK-NO-TRAILING-CLOSURE
39+
// RUN: %refactor -source-filename %s -pos=13:5 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
40+
// RUN: %refactor -source-filename %s -pos=14:5 | %FileCheck %s -check-prefix=CHECK-TRAILING-CLOSURE
41+
42+
// CHECK-TRAILING-CLOSURE: Convert To Trailing Closure
43+
44+
// CHECK-NO-TRAILING-CLOSURE: Action begins
45+
// CHECK-NO-TRAILING-CLOSURE-NOT: Convert To Trailing Closure
46+
// CHECK-NO-TRAILING-CLOSURE: Action ends
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
struct Foo {
2+
static func foo(a: () -> Int) {}
3+
func qux(x: Int, y: () -> Int ) {}
4+
}
5+
6+
func testTrailingClosure() -> String {
7+
Foo.foo(a: { 1 })
8+
Foo.bar(a: { print(3); return 1 })
9+
Foo().qux(x: 1, y: { 1 })
10+
let _ = Foo().quux(x: 1) { 1 }
11+
12+
[1,2,3]
13+
.filter({ $0 % 2 == 0 })
14+
.map({ $0 + 1 })
15+
}
16+
17+
18+
19+
20+
21+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
struct Foo {
2+
static func foo(a: () -> Int) {}
3+
func qux(x: Int, y: () -> Int ) {}
4+
}
5+
6+
func testTrailingClosure() -> String {
7+
Foo.foo(a: { 1 })
8+
Foo.bar(a: { print(3); return 1 })
9+
Foo().qux(x: 1, y: { 1 })
10+
let _ = Foo().quux(x: 1, y: { 1 })
11+
12+
[1,2,3]
13+
.filter { $0 % 2 == 0 }
14+
.map({ $0 + 1 })
15+
}
16+
17+
18+
19+
20+
21+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
struct Foo {
2+
static func foo(a: () -> Int) {}
3+
func qux(x: Int, y: () -> Int ) {}
4+
}
5+
6+
func testTrailingClosure() -> String {
7+
Foo.foo(a: { 1 })
8+
Foo.bar(a: { print(3); return 1 })
9+
Foo().qux(x: 1, y: { 1 })
10+
let _ = Foo().quux(x: 1, y: { 1 })
11+
12+
[1,2,3]
13+
.filter({ $0 % 2 == 0 })
14+
.map { $0 + 1 }
15+
}
16+
17+
18+
19+
20+
21+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
struct Foo {
2+
static func foo(a: () -> Int) {}
3+
func qux(x: Int, y: () -> Int ) {}
4+
}
5+
6+
func testTrailingClosure() -> String {
7+
Foo.foo { 1 }
8+
Foo.bar(a: { print(3); return 1 })
9+
Foo().qux(x: 1, y: { 1 })
10+
let _ = Foo().quux(x: 1, y: { 1 })
11+
12+
[1,2,3]
13+
.filter({ $0 % 2 == 0 })
14+
.map({ $0 + 1 })
15+
}
16+
17+
18+
19+
20+
21+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
struct Foo {
2+
static func foo(a: () -> Int) {}
3+
func qux(x: Int, y: () -> Int ) {}
4+
}
5+
6+
func testTrailingClosure() -> String {
7+
Foo.foo(a: { 1 })
8+
Foo.bar { print(3); return 1 }
9+
Foo().qux(x: 1, y: { 1 })
10+
let _ = Foo().quux(x: 1, y: { 1 })
11+
12+
[1,2,3]
13+
.filter({ $0 % 2 == 0 })
14+
.map({ $0 + 1 })
15+
}
16+
17+
18+
19+
20+
21+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
struct Foo {
2+
static func foo(a: () -> Int) {}
3+
func qux(x: Int, y: () -> Int ) {}
4+
}
5+
6+
func testTrailingClosure() -> String {
7+
Foo.foo(a: { 1 })
8+
Foo.bar(a: { print(3); return 1 })
9+
Foo().qux(x: 1) { 1 }
10+
let _ = Foo().quux(x: 1, y: { 1 })
11+
12+
[1,2,3]
13+
.filter({ $0 % 2 == 0 })
14+
.map({ $0 + 1 })
15+
}
16+
17+
18+
19+
20+
21+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
struct Foo {
2+
static func foo(a: () -> Int) {}
3+
func qux(x: Int, y: () -> Int ) {}
4+
}
5+
6+
func testTrailingClosure() -> String {
7+
Foo.foo(a: { 1 })
8+
Foo.bar(a: { print(3); return 1 })
9+
Foo().qux(x: 1, y: { 1 })
10+
let _ = Foo().quux(x: 1, y: { 1 })
11+
12+
[1,2,3]
13+
.filter({ $0 % 2 == 0 })
14+
.map({ $0 + 1 })
15+
}
16+
// RUN: rm -rf %t.result && mkdir -p %t.result
17+
18+
// RUN: %refactor -trailingclosure -source-filename %s -pos=7:3 > %t.result/L7.swift
19+
// RUN: diff -u %S/Outputs/basic/L7.swift.expected %t.result/L7.swift
20+
21+
// RUN: %refactor -trailingclosure -source-filename %s -pos=8:11 > %t.result/L8.swift
22+
// RUN: diff -u %S/Outputs/basic/L8.swift.expected %t.result/L8.swift
23+
24+
// RUN: %refactor -trailingclosure -source-filename %s -pos=9:8 > %t.result/L9.swift
25+
// RUN: diff -u %S/Outputs/basic/L9.swift.expected %t.result/L9.swift
26+
27+
// RUN: %refactor -trailingclosure -source-filename %s -pos=10:17 > %t.result/L10.swift
28+
// RUN: diff -u %S/Outputs/basic/L10.swift.expected %t.result/L10.swift
29+
30+
// RUN: %refactor -trailingclosure -source-filename %s -pos=13:5 > %t.result/L13.swift
31+
// RUN: diff -u %S/Outputs/basic/L13.swift.expected %t.result/L13.swift
32+
// RUN: %refactor -trailingclosure -source-filename %s -pos=14:5 > %t.result/L14.swift
33+
// RUN: diff -u %S/Outputs/basic/L14.swift.expected %t.result/L14.swift
34+

0 commit comments

Comments
 (0)