Skip to content

Commit 504c2df

Browse files
author
Nathan Hawes
authored
Merge pull request #13247 from nathawes/rdar32489893-migrate-dollar-arg-destructure
[migrator] Fix missed dollar arg migration in closures relying on implicit destructuring of tuple type arg
2 parents 1a7f191 + f3e9685 commit 504c2df

File tree

3 files changed

+77
-27
lines changed

3 files changed

+77
-27
lines changed

lib/Migrator/TupleSplatMigratorPass.cpp

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -76,53 +76,94 @@ class ShorthandFinder: public ASTWalker {
7676

7777
struct TupleSplatMigratorPass : public ASTMigratorPass,
7878
public SourceEntityWalker {
79-
80-
bool handleClosureShorthandMismatch(FunctionConversionExpr *FC) {
81-
if (!SF->getASTContext().LangOpts.isSwiftVersion3() || !FC->isImplicit() ||
82-
!isa<ClosureExpr>(FC->getSubExpr())) {
83-
return false;
79+
80+
llvm::DenseSet<FunctionConversionExpr*> CallArgFuncConversions;
81+
82+
void blacklistFuncConversionArgs(CallExpr *CE) {
83+
if (CE->isImplicit() || !SF->getASTContext().LangOpts.isSwiftVersion3())
84+
return;
85+
86+
Expr *Arg = CE->getArg();
87+
if (auto *Shuffle = dyn_cast<TupleShuffleExpr>(Arg))
88+
Arg = Shuffle->getSubExpr();
89+
90+
if (auto *Paren = dyn_cast<ParenExpr>(Arg)) {
91+
if (auto FC = dyn_cast_or_null<FunctionConversionExpr>(Paren->getSubExpr()))
92+
CallArgFuncConversions.insert(FC);
93+
} else if (auto *Tuple = dyn_cast<TupleExpr>(Arg)){
94+
for (auto Elem : Tuple->getElements()) {
95+
if (auto *FC = dyn_cast_or_null<FunctionConversionExpr>(Elem))
96+
CallArgFuncConversions.insert(FC);
97+
}
98+
}
99+
}
100+
101+
ClosureExpr *getShorthandClosure(Expr *E) {
102+
if (auto *Closure = dyn_cast_or_null<ClosureExpr>(E)) {
103+
if (Closure->hasAnonymousClosureVars())
104+
return Closure;
84105
}
106+
return nullptr;
107+
}
108+
109+
bool handleClosureShorthandMismatch(const FunctionConversionExpr *FC) {
110+
if (!SF->getASTContext().LangOpts.isSwiftVersion3() || !FC->isImplicit())
111+
return false;
85112

86-
auto *Closure = cast<ClosureExpr>(FC->getSubExpr());
87-
if (Closure->getInLoc().isValid())
113+
ClosureExpr *Closure = getShorthandClosure(FC->getSubExpr());
114+
if (!Closure)
88115
return false;
89116

90117
FunctionType *FuncTy = FC->getType()->getAs<FunctionType>();
91118

92119
unsigned NativeArity = FuncTy->getParams().size();
93120
unsigned ClosureArity = Closure->getParameters()->size();
94-
if (NativeArity <= ClosureArity)
121+
if (NativeArity == ClosureArity)
95122
return false;
96123

97-
ShorthandFinder Finder(Closure);
98-
99124
if (ClosureArity == 1 && NativeArity > 1) {
100125
// Remove $0. from existing references or if it's only $0, replace it
101126
// with a tuple of the native arity, e.g. ($0, $1, $2)
102-
Finder.forEachReference([this, NativeArity](Expr *Ref, ParamDecl *Def) {
103-
if (auto *TE = dyn_cast<TupleElementExpr>(Ref)) {
104-
SourceLoc Start = TE->getStartLoc();
105-
SourceLoc End = TE->getLoc();
106-
Editor.replace(CharSourceRange(SM, Start, End), "$");
107-
} else {
108-
std::string TupleText;
109-
{
110-
llvm::raw_string_ostream OS(TupleText);
111-
for (size_t i = 1; i < NativeArity; ++i) {
112-
OS << ", $" << i;
127+
ShorthandFinder(Closure)
128+
.forEachReference([this, NativeArity](Expr *Ref, ParamDecl *Def) {
129+
if (auto *TE = dyn_cast<TupleElementExpr>(Ref)) {
130+
SourceLoc Start = TE->getStartLoc();
131+
SourceLoc End = TE->getLoc();
132+
Editor.replace(CharSourceRange(SM, Start, End), "$");
133+
} else {
134+
std::string TupleText;
135+
{
136+
llvm::raw_string_ostream OS(TupleText);
137+
for (size_t i = 1; i < NativeArity; ++i) {
138+
OS << ", $" << i;
139+
}
140+
OS << ")";
113141
}
114-
OS << ")";
142+
Editor.insert(Ref->getStartLoc(), "(");
143+
Editor.insertAfterToken(Ref->getEndLoc(), TupleText);
115144
}
116-
Editor.insert(Ref->getStartLoc(), "(");
117-
Editor.insertAfterToken(Ref->getEndLoc(), TupleText);
118-
}
119-
});
145+
});
146+
return true;
147+
}
148+
149+
// This direction is only needed if not passed as a call argument. e.g.
150+
// someFunc({ $0 > $1 }) // doesn't need migration
151+
// let x: ((Int, Int)) -> Bool = { $0 > $1 } // needs migration
152+
if (NativeArity == 1 && ClosureArity > 1 && !CallArgFuncConversions.count(FC)) {
153+
// Prepend $0. to existing references
154+
ShorthandFinder(Closure)
155+
.forEachReference([this](Expr *Ref, ParamDecl *Def) {
156+
if (auto *TE = dyn_cast<TupleElementExpr>(Ref))
157+
Ref = TE->getBase();
158+
SourceLoc AfterDollar = Ref->getStartLoc().getAdvancedLoc(1);
159+
Editor.insert(AfterDollar, "0.");
160+
});
120161
return true;
121162
}
122163
return false;
123164
}
124165

125-
/// Migrates code that compiles fine in Swift 3 but breaks in Swift 4 due to
166+
/// Migrates code that compiles fine in Swift 3 but breaks in Swift 4 due to
126167
/// changes in how the typechecker handles tuple arguments.
127168
void handleTupleArgumentMismatches(const CallExpr *E) {
128169
if (!SF->getASTContext().LangOpts.isSwiftVersion3())
@@ -169,6 +210,7 @@ struct TupleSplatMigratorPass : public ASTMigratorPass,
169210
if (auto *FCE = dyn_cast<FunctionConversionExpr>(E)) {
170211
handleClosureShorthandMismatch(FCE);
171212
} else if (auto *CE = dyn_cast<CallExpr>(E)) {
213+
blacklistFuncConversionArgs(CE);
172214
handleTupleArgumentMismatches(CE);
173215
}
174216
return true;

test/Migrator/tuple-arguments.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,7 @@ extension Dictionary {
5959

6060
let dictionary: [String: String] = [:]
6161
_ = dictionary.first { (column, value) in true }!.value
62+
63+
func doit(_ x: Int) -> Bool { return x > 0 }
64+
let _: ((String, Int)) -> [String:Bool] = { [$0: doit($1)] }
65+
func returnClosure() -> ((Int, Int)) -> Bool { return {$1 > $0} }

test/Migrator/tuple-arguments.swift.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,7 @@ extension Dictionary {
5959

6060
let dictionary: [String: String] = [:]
6161
_ = dictionary.first { (column, value) in true }!.value
62+
63+
func doit(_ x: Int) -> Bool { return x > 0 }
64+
let _: ((String, Int)) -> [String:Bool] = { [$0.0: doit($0.1)] }
65+
func returnClosure() -> ((Int, Int)) -> Bool { return {$0.1 > $0.0} }

0 commit comments

Comments
 (0)