Skip to content

Commit 1b6e001

Browse files
committed
[migrator] Add an AST pass to handle tuple mismatches
Migrates code that compiles fine in Swift 3 but breaks in Swift 4 due to changes in how the typechecker handles tuple arguments.
1 parent 7bb2064 commit 1b6e001

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed

lib/Migrator/SyntacticMigratorPass.cpp

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,179 @@ struct SyntacticMigratorPass::Implementation : public SourceEntityWalker {
367367
}
368368
}
369369

370+
/// Migrates code that compiles fine in Swift 3 but breaks in Swift 4 due to
371+
/// changes in how the typechecker handles tuple arguments.
372+
void handleTupleArgumentMismatches(const CallExpr *E) {
373+
if (!SF->getASTContext().LangOpts.isSwiftVersion3())
374+
return;
375+
if (E->isImplicit())
376+
return;
377+
378+
// Handles such kind of cases:
379+
// \code
380+
// func test(_: ()) {}
381+
// test()
382+
// \endcode
383+
// This compiles fine in Swift 3 but Swift 4 complains with
384+
// error: missing argument for parameter #1 in call
385+
//
386+
// It will fix the code to "test(())".
387+
//
388+
auto handleCallsToEmptyTuple = [&](const CallExpr *E) -> bool {
389+
auto fnTy = E->getFn()->getType()->getAs<FunctionType>();
390+
if (!fnTy)
391+
return false;
392+
auto parenT = dyn_cast<ParenType>(fnTy->getInput().getPointer());
393+
if (!parenT)
394+
return false;
395+
auto inp = dyn_cast<TupleType>(parenT->getUnderlyingType().getPointer());
396+
if (!inp)
397+
return false;
398+
if (inp->getNumElements() != 0)
399+
return false;
400+
auto argTupleT = dyn_cast<TupleType>(E->getArg()->getType().getPointer());
401+
if (!argTupleT)
402+
return false;
403+
if (argTupleT->getNumElements() != 0)
404+
return false;
405+
Editor.insertWrap("(", E->getArg()->getSourceRange(), ")");
406+
return true;
407+
};
408+
409+
// Handles such kind of cases:
410+
// \code
411+
// func test(_: ((Int, Int)) -> ()) {}
412+
// test({ (x,y) in })
413+
// \endcode
414+
// This compiles fine in Swift 3 but Swift 4 complains with
415+
// error: cannot convert value of type '(_, _) -> ()' to expected argument type '((Int, Int)) -> ()'
416+
//
417+
// It will fix the code to "test({ let (x,y) = $0; })".
418+
//
419+
auto handleTupleMapToClosureArgs = [&](const CallExpr *E) -> bool {
420+
auto fnTy = E->getFn()->getType()->getAs<FunctionType>();
421+
if (!fnTy)
422+
return false;
423+
auto fnTy2 = fnTy->getInput()->getAs<FunctionType>();
424+
if (!fnTy2)
425+
return false;
426+
auto parenT = dyn_cast<ParenType>(fnTy2->getInput().getPointer());
427+
if (!parenT)
428+
return false;
429+
auto tupleInFn = dyn_cast<TupleType>(parenT->getUnderlyingType().getPointer());
430+
if (!tupleInFn)
431+
return false;
432+
if (!E->getArg())
433+
return false;
434+
auto argE = E->getArg()->getSemanticsProvidingExpr();
435+
while (auto *ICE = dyn_cast<ImplicitConversionExpr>(argE))
436+
argE = ICE->getSubExpr();
437+
argE = argE->getSemanticsProvidingExpr();
438+
auto closureE = dyn_cast<ClosureExpr>(argE);
439+
if (!closureE)
440+
return false;
441+
if (closureE->getInLoc().isInvalid())
442+
return false;
443+
auto paramList = closureE->getParameters();
444+
if (!paramList ||
445+
paramList->getLParenLoc().isInvalid() || paramList->getRParenLoc().isInvalid())
446+
return false;
447+
if (paramList->size() != tupleInFn->getNumElements())
448+
return false;
449+
if (paramList->size() == 0)
450+
return false;
451+
452+
auto hasParamListWithNoTypes = [&]() {
453+
if (closureE->hasExplicitResultType())
454+
return false;
455+
for (auto *param : *paramList) {
456+
auto tyLoc = param->getTypeLoc();
457+
if (!tyLoc.isNull())
458+
return false;
459+
}
460+
return true;
461+
};
462+
463+
if (hasParamListWithNoTypes()) {
464+
// Simpler form depending on type inference.
465+
// Change "(x, y) in " to "let (x, y) = $0;".
466+
467+
Editor.insert(paramList->getLParenLoc(), "let ");
468+
for (auto *param : *paramList) {
469+
// If the argument list is like "(_ x, _ y)", remove the underscores.
470+
if (param->getArgumentNameLoc().isValid()) {
471+
Editor.remove(CharSourceRange(SM, param->getArgumentNameLoc(),
472+
param->getNameLoc()));
473+
}
474+
// If the argument list has type annotations, remove them.
475+
auto tyLoc = param->getTypeLoc();
476+
if (!tyLoc.isNull() && !tyLoc.getSourceRange().isInvalid()) {
477+
auto nameRange = CharSourceRange(param->getNameLoc(),
478+
param->getNameStr().size());
479+
auto tyRange = Lexer::getCharSourceRangeFromSourceRange(SM,
480+
tyLoc.getSourceRange());
481+
Editor.remove(CharSourceRange(SM, nameRange.getEnd(),
482+
tyRange.getEnd()));
483+
}
484+
}
485+
486+
Editor.replaceToken(closureE->getInLoc(), "= $0;");
487+
return true;
488+
}
489+
490+
// Includes types in the closure signature. The following will do a
491+
// more complicated edit than the above:
492+
// (x: Int, y: Int) -> Int in
493+
// to
494+
// (__val:(Int, Int)) -> Int in let (x,y) = __val;
495+
496+
std::string paramListText;
497+
{
498+
llvm::raw_string_ostream OS(paramListText);
499+
OS << "(__val:(";
500+
for (size_t i = 0, e = paramList->size(); i != e; ++i) {
501+
if (i != 0)
502+
OS << ", ";
503+
auto param = paramList->get(i);
504+
auto tyLoc = param->getTypeLoc();
505+
if (!tyLoc.isNull() && !tyLoc.getSourceRange().isInvalid()) {
506+
OS << SM.extractText(
507+
Lexer::getCharSourceRangeFromSourceRange(SM,
508+
tyLoc.getSourceRange()));
509+
} else {
510+
param->getType().print(OS);
511+
}
512+
}
513+
OS << "))";
514+
}
515+
std::string varBindText;
516+
{
517+
llvm::raw_string_ostream OS(varBindText);
518+
OS << " let (";
519+
for (size_t i = 0, e = paramList->size(); i != e; ++i) {
520+
if (i != 0)
521+
OS << ",";
522+
auto param = paramList->get(i);
523+
OS << param->getNameStr();
524+
}
525+
OS << ") = __val; ";
526+
}
527+
528+
Editor.replace(paramList->getSourceRange(), paramListText);
529+
Editor.insertAfterToken(closureE->getInLoc(), varBindText);
530+
return true;
531+
};
532+
533+
if (handleCallsToEmptyTuple(E))
534+
return;
535+
if (handleTupleMapToClosureArgs(E))
536+
return;
537+
}
538+
370539
bool walkToExprPre(Expr *E) override {
371540
if (auto *CE = dyn_cast<CallExpr>(E)) {
541+
handleTupleArgumentMismatches(CE);
542+
372543
auto Fn = CE->getFn();
373544
auto Args = CE->getArg();
374545
switch (Fn->getKind()) {

test/Migrator/tuple-arguments.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// RUN: %target-swift-frontend -typecheck %s -swift-version 3
2+
// RUN: %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t.result -disable-migrator-fixits -swift-version 3
3+
// RUN: diff -u %s.expected %t.result
4+
// RUN: %target-swift-frontend -typecheck %s.expected -swift-version 4
5+
6+
func test1(_: ()) {}
7+
test1(())
8+
test1()
9+
func test2() {}
10+
test2()
11+
12+
enum Result<T> {
13+
case success(T)
14+
}
15+
func test3(_: Result<()>) {}
16+
test3(.success())
17+
18+
func test4(_: (Int, Int) -> ()) {}
19+
test4({ (x,y) in })
20+
func test5(_: (Int, Int, Int) -> ()) {}
21+
test5({ (x,y,z) in })
22+
23+
func test6(_: ((Int, Int)) -> ()) {}
24+
test6({ (x,y) in })
25+
func test7(_: ((Int, Int, Int)) -> ()) {}
26+
test7({ (x,y,z) in })
27+
test6({ (_ x, _ y) in })
28+
test6({ (_, _) in })
29+
test6({ (x:Int, y:Int) in })
30+
test6({ (_, _) ->() in })
31+
32+
func toString(indexes: Int?...) -> String {
33+
let _ = indexes.enumerated().flatMap({ (i: Int, index: Int?) -> String? in
34+
let _: Int = i
35+
if index != nil {}
36+
return ""
37+
})
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// RUN: %target-swift-frontend -typecheck %s -swift-version 3
2+
// RUN: %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t.result -disable-migrator-fixits -swift-version 3
3+
// RUN: diff -u %s.expected %t.result
4+
// RUN: %target-swift-frontend -typecheck %s.expected -swift-version 4
5+
6+
func test1(_: ()) {}
7+
test1(())
8+
test1(())
9+
func test2() {}
10+
test2()
11+
12+
enum Result<T> {
13+
case success(T)
14+
}
15+
func test3(_: Result<()>) {}
16+
test3(.success(()))
17+
18+
func test4(_: (Int, Int) -> ()) {}
19+
test4({ (x,y) in })
20+
func test5(_: (Int, Int, Int) -> ()) {}
21+
test5({ (x,y,z) in })
22+
23+
func test6(_: ((Int, Int)) -> ()) {}
24+
test6({ let (x,y) = $0; })
25+
func test7(_: ((Int, Int, Int)) -> ()) {}
26+
test7({ let (x,y,z) = $0; })
27+
test6({ let (x, y) = $0; })
28+
test6({ let (_, _) = $0; })
29+
test6({ (__val:(Int, Int)) in let (x,y) = __val; })
30+
test6({ (__val:(Int, Int)) ->() in let (_,_) = __val; })
31+
32+
func toString(indexes: Int?...) -> String {
33+
let _ = indexes.enumerated().flatMap({ (__val:(Int, Int?)) -> String? in let (i,index) = __val;
34+
let _: Int = i
35+
if index != nil {}
36+
return ""
37+
})
38+
}

0 commit comments

Comments
 (0)