Skip to content

Commit 63b40bd

Browse files
author
Nathan Hawes
committed
[Migrator] Migrate references to shorthand closure params (e.g. $0, $1) where affected by SE110
In Swift 3: 1) a closure referring to $1 or above type checked against a function type that takes a single tuple argument with that arity 2) a closure referring only to $0 (or $0.1 etc) type checked against a function type that takes multiple arguments but neither compiles in Swift 4. This patch migrates shorthand references for 1) by prefixing "0." in front of the existing references, e.g. "$1" to "$0.1" 2) by removing the leading 0. if one existing or substituting a tuple of the correct arity if it doesn't, e.g. "$0.1" to "$1" and "$0" to "($0, $1, $2)" Resolves rdar://problem/31969538
1 parent 4a7dccd commit 63b40bd

File tree

3 files changed

+132
-4
lines changed

3 files changed

+132
-4
lines changed

lib/Migrator/TupleSplatMigratorPass.cpp

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,115 @@ using namespace swift;
2222
using namespace swift::migrator;
2323

2424
namespace {
25+
26+
class ShorthandFinder: public ASTWalker {
27+
private:
28+
llvm::DenseMap<ParamDecl*, std::vector<Expr*>> References;
29+
30+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
31+
Expr *ParentElementExpr = nullptr;
32+
Expr *OrigE = E;
33+
34+
if (auto *TupleElem = dyn_cast<TupleElementExpr>(E)) {
35+
ParentElementExpr = TupleElem;
36+
E = TupleElem->getBase()->getSemanticsProvidingExpr();
37+
}
38+
39+
if (auto *DeclRef = dyn_cast<DeclRefExpr>(E)) {
40+
ParamDecl *Decl = dyn_cast<ParamDecl>(DeclRef->getDecl());
41+
Expr *Reference = ParentElementExpr? ParentElementExpr : DeclRef;
42+
if (References.count(Decl) && !Reference->isImplicit()) {
43+
References[Decl].push_back(Reference);
44+
return { false, OrigE };
45+
}
46+
}
47+
return { true, OrigE };
48+
}
49+
50+
public:
51+
ShorthandFinder(ClosureExpr *Expr) {
52+
if (!Expr->hasAnonymousClosureVars())
53+
return;
54+
References.clear();
55+
for(auto *Param: *Expr->getParameters()) {
56+
References[Param] = {};
57+
}
58+
Expr->walk(*this);
59+
}
60+
61+
void forEachReference(llvm::function_ref<void(Expr*, ParamDecl*)> Callback) {
62+
for(auto Entry: References) {
63+
for(auto *Expr : Entry.getSecond()) {
64+
Callback(Expr, Entry.getFirst());
65+
}
66+
}
67+
}
68+
};
2569

2670
struct TupleSplatMigratorPass : public ASTMigratorPass,
2771
public SourceEntityWalker {
72+
73+
bool handleClosureShorthandMismatch(FunctionConversionExpr *FC) {
74+
if (!SF->getASTContext().LangOpts.isSwiftVersion3() || !FC->isImplicit() ||
75+
!isa<ClosureExpr>(FC->getSubExpr())) {
76+
return false;
77+
}
78+
79+
auto *Closure = cast<ClosureExpr>(FC->getSubExpr());
80+
if (Closure->getInLoc().isValid())
81+
return false;
82+
83+
FunctionType *FuncTy = FC->getType()->getAs<FunctionType>();
84+
85+
unsigned NativeArity = 0;
86+
if (isa<ParenType>(FuncTy->getInput().getPointer())) {
87+
NativeArity = 1;
88+
} else if (auto TT = FuncTy->getInput()->getAs<TupleType>()) {
89+
NativeArity = TT->getNumElements();
90+
}
91+
92+
unsigned ClosureArity = Closure->getParameters()->size();
93+
if (NativeArity == ClosureArity)
94+
return false;
95+
96+
ShorthandFinder Finder(Closure);
97+
if (NativeArity == 1 && ClosureArity > 1) {
98+
// Prepend $0. to existing references
99+
Finder.forEachReference([this](Expr *Ref, ParamDecl* Def) {
100+
if (auto *TE = dyn_cast<TupleElementExpr>(Ref))
101+
Ref = TE->getBase();
102+
SourceLoc AfterDollar = Ref->getStartLoc().getAdvancedLoc(1);
103+
Editor.insert(AfterDollar, "0.");
104+
});
105+
return true;
106+
}
107+
108+
if (ClosureArity == 1 && NativeArity > 1) {
109+
// Remove $0. from existing references or if it's only $0, replace it
110+
// with a tuple of the native arity, e.g. ($0, $1, $2)
111+
Finder.forEachReference([this, NativeArity](Expr *Ref, ParamDecl *Def) {
112+
if (auto *TE = dyn_cast<TupleElementExpr>(Ref)) {
113+
SourceLoc Start = TE->getStartLoc();
114+
SourceLoc End = TE->getLoc();
115+
Editor.replace(CharSourceRange(SM, Start, End), "$");
116+
} else {
117+
std::string TupleText;
118+
{
119+
llvm::raw_string_ostream OS(TupleText);
120+
for (size_t i = 1; i != NativeArity; ++i) {
121+
OS << ", ";
122+
OS << "$" << i;
123+
}
124+
OS << ")";
125+
}
126+
Editor.insert(Ref->getStartLoc(), "(");
127+
Editor.insertAfterToken(Ref->getEndLoc(), TupleText);
128+
}
129+
});
130+
return true;
131+
}
132+
return false;
133+
}
28134

29135
/// Migrates code that compiles fine in Swift 3 but breaks in Swift 4 due to
30136
/// changes in how the typechecker handles tuple arguments.
@@ -86,7 +192,7 @@ struct TupleSplatMigratorPass : public ASTMigratorPass,
86192
auto parenT = dyn_cast<ParenType>(fnTy2->getInput().getPointer());
87193
if (!parenT)
88194
return false;
89-
auto tupleInFn = dyn_cast<TupleType>(parenT->getUnderlyingType().getPointer());
195+
auto tupleInFn = parenT->getAs<TupleType>();
90196
if (!tupleInFn)
91197
return false;
92198
if (!E->getArg())
@@ -197,7 +303,9 @@ struct TupleSplatMigratorPass : public ASTMigratorPass,
197303
}
198304

199305
bool walkToExprPre(Expr *E) override {
200-
if (auto *CE = dyn_cast<CallExpr>(E)) {
306+
if (auto *FCE = dyn_cast<FunctionConversionExpr>(E)) {
307+
handleClosureShorthandMismatch(FCE);
308+
} else if (auto *CE = dyn_cast<CallExpr>(E)) {
201309
handleTupleArgumentMismatches(CE);
202310
}
203311
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)