Skip to content

[migrator] Add an AST pass to handle tuple mismatches #9281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions lib/Migrator/SyntacticMigratorPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,179 @@ struct SyntacticMigratorPass::Implementation : public SourceEntityWalker {
}
}

/// Migrates code that compiles fine in Swift 3 but breaks in Swift 4 due to
/// changes in how the typechecker handles tuple arguments.
void handleTupleArgumentMismatches(const CallExpr *E) {
if (!SF->getASTContext().LangOpts.isSwiftVersion3())
return;
if (E->isImplicit())
return;

// Handles such kind of cases:
// \code
// func test(_: ()) {}
// test()
// \endcode
// This compiles fine in Swift 3 but Swift 4 complains with
// error: missing argument for parameter #1 in call
//
// It will fix the code to "test(())".
//
auto handleCallsToEmptyTuple = [&](const CallExpr *E) -> bool {
auto fnTy = E->getFn()->getType()->getAs<FunctionType>();
if (!fnTy)
return false;
auto parenT = dyn_cast<ParenType>(fnTy->getInput().getPointer());
if (!parenT)
return false;
auto inp = dyn_cast<TupleType>(parenT->getUnderlyingType().getPointer());
if (!inp)
return false;
if (inp->getNumElements() != 0)
return false;
auto argTupleT = dyn_cast<TupleType>(E->getArg()->getType().getPointer());
if (!argTupleT)
return false;
if (argTupleT->getNumElements() != 0)
return false;
Editor.insertWrap("(", E->getArg()->getSourceRange(), ")");
return true;
};

// Handles such kind of cases:
// \code
// func test(_: ((Int, Int)) -> ()) {}
// test({ (x,y) in })
// \endcode
// This compiles fine in Swift 3 but Swift 4 complains with
// error: cannot convert value of type '(_, _) -> ()' to expected argument type '((Int, Int)) -> ()'
//
// It will fix the code to "test({ let (x,y) = $0; })".
//
auto handleTupleMapToClosureArgs = [&](const CallExpr *E) -> bool {
auto fnTy = E->getFn()->getType()->getAs<FunctionType>();
if (!fnTy)
return false;
auto fnTy2 = fnTy->getInput()->getAs<FunctionType>();
if (!fnTy2)
return false;
auto parenT = dyn_cast<ParenType>(fnTy2->getInput().getPointer());
if (!parenT)
return false;
auto tupleInFn = dyn_cast<TupleType>(parenT->getUnderlyingType().getPointer());
if (!tupleInFn)
return false;
if (!E->getArg())
return false;
auto argE = E->getArg()->getSemanticsProvidingExpr();
while (auto *ICE = dyn_cast<ImplicitConversionExpr>(argE))
argE = ICE->getSubExpr();
argE = argE->getSemanticsProvidingExpr();
auto closureE = dyn_cast<ClosureExpr>(argE);
if (!closureE)
return false;
if (closureE->getInLoc().isInvalid())
return false;
auto paramList = closureE->getParameters();
if (!paramList ||
paramList->getLParenLoc().isInvalid() || paramList->getRParenLoc().isInvalid())
return false;
if (paramList->size() != tupleInFn->getNumElements())
return false;
if (paramList->size() == 0)
return false;

auto hasParamListWithNoTypes = [&]() {
if (closureE->hasExplicitResultType())
return false;
for (auto *param : *paramList) {
auto tyLoc = param->getTypeLoc();
if (!tyLoc.isNull())
return false;
}
return true;
};

if (hasParamListWithNoTypes()) {
// Simpler form depending on type inference.
// Change "(x, y) in " to "let (x, y) = $0;".

Editor.insert(paramList->getLParenLoc(), "let ");
for (auto *param : *paramList) {
// If the argument list is like "(_ x, _ y)", remove the underscores.
if (param->getArgumentNameLoc().isValid()) {
Editor.remove(CharSourceRange(SM, param->getArgumentNameLoc(),
param->getNameLoc()));
}
// If the argument list has type annotations, remove them.
auto tyLoc = param->getTypeLoc();
if (!tyLoc.isNull() && !tyLoc.getSourceRange().isInvalid()) {
auto nameRange = CharSourceRange(param->getNameLoc(),
param->getNameStr().size());
auto tyRange = Lexer::getCharSourceRangeFromSourceRange(SM,
tyLoc.getSourceRange());
Editor.remove(CharSourceRange(SM, nameRange.getEnd(),
tyRange.getEnd()));
}
}

Editor.replaceToken(closureE->getInLoc(), "= $0;");
return true;
}

// Includes types in the closure signature. The following will do a
// more complicated edit than the above:
// (x: Int, y: Int) -> Int in
// to
// (__val:(Int, Int)) -> Int in let (x,y) = __val;

std::string paramListText;
{
llvm::raw_string_ostream OS(paramListText);
OS << "(__val:(";
for (size_t i = 0, e = paramList->size(); i != e; ++i) {
if (i != 0)
OS << ", ";
auto param = paramList->get(i);
auto tyLoc = param->getTypeLoc();
if (!tyLoc.isNull() && !tyLoc.getSourceRange().isInvalid()) {
OS << SM.extractText(
Lexer::getCharSourceRangeFromSourceRange(SM,
tyLoc.getSourceRange()));
} else {
param->getType().print(OS);
}
}
OS << "))";
}
std::string varBindText;
{
llvm::raw_string_ostream OS(varBindText);
OS << " let (";
for (size_t i = 0, e = paramList->size(); i != e; ++i) {
if (i != 0)
OS << ",";
auto param = paramList->get(i);
OS << param->getNameStr();
}
OS << ") = __val; ";
}

Editor.replace(paramList->getSourceRange(), paramListText);
Editor.insertAfterToken(closureE->getInLoc(), varBindText);
return true;
};

if (handleCallsToEmptyTuple(E))
return;
if (handleTupleMapToClosureArgs(E))
return;
}

bool walkToExprPre(Expr *E) override {
if (auto *CE = dyn_cast<CallExpr>(E)) {
handleTupleArgumentMismatches(CE);

auto Fn = CE->getFn();
auto Args = CE->getArg();
switch (Fn->getKind()) {
Expand Down
41 changes: 41 additions & 0 deletions test/Migrator/tuple-arguments.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// FIXME: Figure out why this is not working on linux
// XFAIL: linux

// RUN: %target-swift-frontend -typecheck %s -swift-version 3
// RUN: %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t.result -disable-migrator-fixits -swift-version 3
// RUN: diff -u %s.expected %t.result
// RUN: %target-swift-frontend -typecheck %s.expected -swift-version 4

func test1(_: ()) {}
test1(())
test1()
func test2() {}
test2()

enum Result<T> {
case success(T)
}
func test3(_: Result<()>) {}
test3(.success())

func test4(_: (Int, Int) -> ()) {}
test4({ (x,y) in })
func test5(_: (Int, Int, Int) -> ()) {}
test5({ (x,y,z) in })

func test6(_: ((Int, Int)) -> ()) {}
test6({ (x,y) in })
func test7(_: ((Int, Int, Int)) -> ()) {}
test7({ (x,y,z) in })
test6({ (_ x, _ y) in })
test6({ (_, _) in })
test6({ (x:Int, y:Int) in })
test6({ (_, _) ->() in })

func toString(indexes: Int?...) -> String {
let _ = indexes.enumerated().flatMap({ (i: Int, index: Int?) -> String? in
let _: Int = i
if index != nil {}
return ""
})
}
41 changes: 41 additions & 0 deletions test/Migrator/tuple-arguments.swift.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// FIXME: Figure out why this is not working on linux
// XFAIL: linux

// RUN: %target-swift-frontend -typecheck %s -swift-version 3
// RUN: %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t.result -disable-migrator-fixits -swift-version 3
// RUN: diff -u %s.expected %t.result
// RUN: %target-swift-frontend -typecheck %s.expected -swift-version 4

func test1(_: ()) {}
test1(())
test1(())
func test2() {}
test2()

enum Result<T> {
case success(T)
}
func test3(_: Result<()>) {}
test3(.success(()))

func test4(_: (Int, Int) -> ()) {}
test4({ (x,y) in })
func test5(_: (Int, Int, Int) -> ()) {}
test5({ (x,y,z) in })

func test6(_: ((Int, Int)) -> ()) {}
test6({ let (x,y) = $0; })
func test7(_: ((Int, Int, Int)) -> ()) {}
test7({ let (x,y,z) = $0; })
test6({ let (x, y) = $0; })
test6({ let (_, _) = $0; })
test6({ (__val:(Int, Int)) in let (x,y) = __val; })
test6({ (__val:(Int, Int)) ->() in let (_,_) = __val; })

func toString(indexes: Int?...) -> String {
let _ = indexes.enumerated().flatMap({ (__val:(Int, Int?)) -> String? in let (i,index) = __val;
let _: Int = i
if index != nil {}
return ""
})
}