blob: 1cc2b092e21990d266b38290f82c8dc5d243f23c [file] [log] [blame]
//===--- TupleSplatMigratorPass.cpp ---------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "swift/AST/ASTVisitor.h"
#include "swift/AST/Expr.h"
#include "swift/AST/Module.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/Types.h"
#include "swift/Migrator/ASTMigratorPass.h"
#include "swift/Parse/Lexer.h"
using namespace swift;
using namespace swift::migrator;
namespace {
struct TupleSplatMigratorPass : public ASTMigratorPass,
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);
}
return true;
}
public:
TupleSplatMigratorPass(EditorAdapter &Editor,
SourceFile *SF,
const MigratorOptions &Opts)
: ASTMigratorPass(Editor, SF, Opts) {}
};
} // end anonymous namespace
void migrator::runTupleSplatMigratorPass(EditorAdapter &Editor,
SourceFile *SF,
const MigratorOptions &Opts) {
TupleSplatMigratorPass { Editor, SF, Opts }.walk(SF);
}