Files
swift-mirror/lib/Migrator/TupleSplatMigratorPass.cpp
David Farler 0774db030f [Migrator] Separate AST Pass implementations
The SyntacticMigratorPass is getting Too Big To Fail and covers
multiple migrations. There was already an early exit to not run
the pass if APIDigesterDataStorePath wasn't supplied, so SE-0110
tuple splat fix-its weren't getting run. This manifested in Migrator
tests not printing migrated contents on Linux.

New: ASTMigratorPass
Renamed: SyntacticMigratorPass -> APIDiffMigratorPass
New: TupleSplatMigratorPass

These implementations are entirely hidden and can only be invoked
by a swift::migrator function.

rdar://problem/32025974
2017-05-05 23:35:13 -07:00

220 lines
7.4 KiB
C++

//===--- 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);
}