Skip to content

Commit 33e78be

Browse files
author
Nathan Hawes
authored
Merge pull request #9711 from nathawes/rdar31969538-closure-body-migration
[Migrator] Migrate references to shorthand closure params (e.g. $0, $1) where affected by SE110
2 parents 2705068 + 5d60bfc commit 33e78be

File tree

3 files changed

+137
-4
lines changed

3 files changed

+137
-4
lines changed

lib/Migrator/TupleSplatMigratorPass.cpp

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,119 @@ using namespace swift::migrator;
2323

2424
namespace {
2525

26+
/// Builds a mapping from each ParamDecl of a ClosureExpr to its references in
27+
/// in the closure body. This is used below to rewrite shorthand param
28+
/// references from $0.1 to $1 and vice versa.
29+
class ShorthandFinder: public ASTWalker {
30+
private:
31+
/// A mapping from each ParamDecl of the supplied ClosureExpr to a list of
32+
/// each referencing DeclRefExpr (e.g. $0) or its immediately containing
33+
/// TupleElementExpr (e.g $0.1) if one exists
34+
llvm::DenseMap<ParamDecl*, std::vector<Expr*>> References;
35+
36+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
37+
Expr *ParentElementExpr = nullptr;
38+
Expr *OrigE = E;
39+
40+
if (auto *TupleElem = dyn_cast<TupleElementExpr>(E)) {
41+
ParentElementExpr = TupleElem;
42+
E = TupleElem->getBase()->getSemanticsProvidingExpr();
43+
}
44+
45+
if (auto *DeclRef = dyn_cast<DeclRefExpr>(E)) {
46+
ParamDecl *Decl = dyn_cast<ParamDecl>(DeclRef->getDecl());
47+
Expr *Reference = ParentElementExpr? ParentElementExpr : DeclRef;
48+
if (References.count(Decl) && !Reference->isImplicit()) {
49+
References[Decl].push_back(Reference);
50+
return { false, OrigE };
51+
}
52+
}
53+
return { true, OrigE };
54+
}
55+
56+
public:
57+
ShorthandFinder(ClosureExpr *Expr) {
58+
if (!Expr->hasAnonymousClosureVars())
59+
return;
60+
References.clear();
61+
for(auto *Param: *Expr->getParameters()) {
62+
References[Param] = {};
63+
}
64+
Expr->walk(*this);
65+
}
66+
67+
void forEachReference(llvm::function_ref<void(Expr*, ParamDecl*)> Callback) {
68+
for(auto Entry: References) {
69+
for(auto *Expr : Entry.getSecond()) {
70+
Callback(Expr, Entry.getFirst());
71+
}
72+
}
73+
}
74+
};
75+
2676
struct TupleSplatMigratorPass : public ASTMigratorPass,
2777
public SourceEntityWalker {
78+
79+
bool handleClosureShorthandMismatch(FunctionConversionExpr *FC) {
80+
if (!SF->getASTContext().LangOpts.isSwiftVersion3() || !FC->isImplicit() ||
81+
!isa<ClosureExpr>(FC->getSubExpr())) {
82+
return false;
83+
}
84+
85+
auto *Closure = cast<ClosureExpr>(FC->getSubExpr());
86+
if (Closure->getInLoc().isValid())
87+
return false;
88+
89+
FunctionType *FuncTy = FC->getType()->getAs<FunctionType>();
90+
91+
unsigned NativeArity = 0;
92+
if (isa<ParenType>(FuncTy->getInput().getPointer())) {
93+
NativeArity = 1;
94+
} else if (auto TT = FuncTy->getInput()->getAs<TupleType>()) {
95+
NativeArity = TT->getNumElements();
96+
}
97+
98+
unsigned ClosureArity = Closure->getParameters()->size();
99+
if (NativeArity == ClosureArity)
100+
return false;
101+
102+
ShorthandFinder Finder(Closure);
103+
if (NativeArity == 1 && ClosureArity > 1) {
104+
// Prepend $0. to existing references
105+
Finder.forEachReference([this](Expr *Ref, ParamDecl* Def) {
106+
if (auto *TE = dyn_cast<TupleElementExpr>(Ref))
107+
Ref = TE->getBase();
108+
SourceLoc AfterDollar = Ref->getStartLoc().getAdvancedLoc(1);
109+
Editor.insert(AfterDollar, "0.");
110+
});
111+
return true;
112+
}
113+
114+
if (ClosureArity == 1 && NativeArity > 1) {
115+
// Remove $0. from existing references or if it's only $0, replace it
116+
// with a tuple of the native arity, e.g. ($0, $1, $2)
117+
Finder.forEachReference([this, NativeArity](Expr *Ref, ParamDecl *Def) {
118+
if (auto *TE = dyn_cast<TupleElementExpr>(Ref)) {
119+
SourceLoc Start = TE->getStartLoc();
120+
SourceLoc End = TE->getLoc();
121+
Editor.replace(CharSourceRange(SM, Start, End), "$");
122+
} else {
123+
std::string TupleText;
124+
{
125+
llvm::raw_string_ostream OS(TupleText);
126+
for (size_t i = 1; i < NativeArity; ++i) {
127+
OS << ", $" << i;
128+
}
129+
OS << ")";
130+
}
131+
Editor.insert(Ref->getStartLoc(), "(");
132+
Editor.insertAfterToken(Ref->getEndLoc(), TupleText);
133+
}
134+
});
135+
return true;
136+
}
137+
return false;
138+
}
28139

29140
/// Migrates code that compiles fine in Swift 3 but breaks in Swift 4 due to
30141
/// changes in how the typechecker handles tuple arguments.
@@ -86,7 +197,7 @@ struct TupleSplatMigratorPass : public ASTMigratorPass,
86197
auto parenT = dyn_cast<ParenType>(fnTy2->getInput().getPointer());
87198
if (!parenT)
88199
return false;
89-
auto tupleInFn = dyn_cast<TupleType>(parenT->getUnderlyingType().getPointer());
200+
auto tupleInFn = parenT->getAs<TupleType>();
90201
if (!tupleInFn)
91202
return false;
92203
if (!E->getArg())
@@ -197,7 +308,9 @@ struct TupleSplatMigratorPass : public ASTMigratorPass,
197308
}
198309

199310
bool walkToExprPre(Expr *E) override {
200-
if (auto *CE = dyn_cast<CallExpr>(E)) {
311+
if (auto *FCE = dyn_cast<FunctionConversionExpr>(E)) {
312+
handleClosureShorthandMismatch(FCE);
313+
} else if (auto *CE = dyn_cast<CallExpr>(E)) {
201314
handleTupleArgumentMismatches(CE);
202315
}
203316
return true;

test/Migrator/tuple-arguments.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// RUN: %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t.result -disable-migrator-fixits -swift-version 3
33
// RUN: diff -u %s.expected %t.result
44
// RUN: %target-swift-frontend -typecheck %s.expected -swift-version 4
5-
// rdar://
6-
// XFAIL: *
75

86
func test1(_: ()) {}
97
test1(())
@@ -37,4 +35,15 @@ func toString(indexes: Int?...) -> String {
3735
if index != nil {}
3836
return ""
3937
})
38+
let _ = indexes.reduce(0) { print($0); return $0.0 + ($0.1 ?? 0)}
39+
let _ = indexes.reduce(0) { (true ? $0 : (1, 2)).0 + ($0.1 ?? 0) }
40+
let _ = [(1, 2)].contains { $0 != $1 }
41+
_ = ["Hello", "Foo"].sorted { print($0); return $0.0.characters.count > ($0).1.characters.count }
42+
_ = ["Hello" : 2].map { ($0, ($1)) }
43+
}
44+
45+
extension Dictionary {
46+
public mutating func merge(with dictionary: Dictionary) {
47+
dictionary.forEach { updateValue($1, forKey: $0) }
48+
}
4049
}

test/Migrator/tuple-arguments.swift.expected

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,15 @@ func toString(indexes: Int?...) -> String {
3535
if index != nil {}
3636
return ""
3737
})
38+
let _ = indexes.reduce(0) { print(($0, $1)); return $0 + ($1 ?? 0)}
39+
let _ = indexes.reduce(0) { (true ? ($0, $1) : (1, 2)).0 + ($1 ?? 0) }
40+
let _ = [(1, 2)].contains { $0.0 != $0.1 }
41+
_ = ["Hello", "Foo"].sorted { print(($0, $1)); return $0.characters.count > $1.characters.count }
42+
_ = ["Hello" : 2].map { ($0.0, ($0.1)) }
43+
}
44+
45+
extension Dictionary {
46+
public mutating func merge(with dictionary: Dictionary) {
47+
dictionary.forEach { updateValue($0.1, forKey: $0.0) }
48+
}
3849
}

0 commit comments

Comments
 (0)