mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Encapsulate uses of the variables in FrontendInputs with intention-describing functions. Move some code that sets these variables into FrontendInputs and FrontendOptions classes. Create new FrontendInputs class to encapsulate InputFilenames, InputBuffers and PrimaryInput, which were formerly in Frontend. Includes one change in SwiftEditor.cpp to resolve a merge conflict.
455 lines
15 KiB
C++
455 lines
15 KiB
C++
//===--- Migrator.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/Basic/Diff.h"
|
|
#include "swift/Frontend/Frontend.h"
|
|
#include "swift/Migrator/ASTMigratorPass.h"
|
|
#include "swift/Migrator/EditorAdapter.h"
|
|
#include "swift/Migrator/FixitApplyDiagnosticConsumer.h"
|
|
#include "swift/Migrator/Migrator.h"
|
|
#include "swift/Migrator/RewriteBufferEditsReceiver.h"
|
|
#include "clang/Basic/Diagnostic.h"
|
|
#include "clang/Basic/FileManager.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Edit/EditedSource.h"
|
|
#include "clang/Rewrite/Core/RewriteBuffer.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::migrator;
|
|
|
|
bool migrator::updateCodeAndEmitRemap(CompilerInstance *Instance,
|
|
const CompilerInvocation &Invocation) {
|
|
// Delete the remap file, in case someone is re-running the Migrator. If the
|
|
// file fails to compile and we don't get a chance to overwrite it, the old
|
|
// changes may get picked up.
|
|
llvm::sys::fs::remove(Invocation.getMigratorOptions().EmitRemapFilePath);
|
|
|
|
Migrator M { Instance, Invocation }; // Provide inputs and configuration
|
|
|
|
// Phase 1: Pre Fix-it passes
|
|
// These uses the initial frontend invocation to apply any obvious fix-its
|
|
// to see if we can get an error-free AST to get to Phase 2.
|
|
std::unique_ptr<swift::CompilerInstance> PreFixItInstance;
|
|
if (Instance->getASTContext().hadError()) {
|
|
PreFixItInstance = M.repeatFixitMigrations(2,
|
|
Invocation.getLangOptions().EffectiveLanguageVersion);
|
|
|
|
// If we still couldn't fix all of the errors, give up.
|
|
if (PreFixItInstance == nullptr ||
|
|
!PreFixItInstance->hasASTContext() ||
|
|
PreFixItInstance->getASTContext().hadError()) {
|
|
return true;
|
|
}
|
|
M.StartInstance = PreFixItInstance.get();
|
|
}
|
|
|
|
// Phase 2: Syntactic Transformations
|
|
if (Invocation.getLangOptions().EffectiveLanguageVersion[0] < 4) {
|
|
auto FailedSyntacticPasses = M.performSyntacticPasses();
|
|
if (FailedSyntacticPasses) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Phase 3: Post Fix-it Passes
|
|
// Perform fix-it based migrations on the compiler, some number of times in
|
|
// order to give the compiler an opportunity to
|
|
// take its time reaching a fixed point.
|
|
// This is the end of the pipeline, so we throw away the compiler instance(s)
|
|
// we used in these fix-it runs.
|
|
|
|
if (M.getMigratorOptions().EnableMigratorFixits) {
|
|
M.repeatFixitMigrations(Migrator::MaxCompilerFixitPassIterations,
|
|
{4, 0, 0});
|
|
}
|
|
|
|
// OK, we have a final resulting text. Now we compare against the input
|
|
// to calculate a replacement map describing the changes to the input
|
|
// necessary to get the output.
|
|
// TODO: Document replacement map format.
|
|
|
|
|
|
auto EmitRemapFailed = M.emitRemap();
|
|
auto EmitMigratedFailed = M.emitMigratedFile();
|
|
auto DumpMigrationStatesFailed = M.dumpStates();
|
|
return EmitRemapFailed || EmitMigratedFailed || DumpMigrationStatesFailed;
|
|
}
|
|
|
|
Migrator::Migrator(CompilerInstance *StartInstance,
|
|
const CompilerInvocation &StartInvocation)
|
|
: StartInstance(StartInstance), StartInvocation(StartInvocation) {
|
|
|
|
auto ErrorOrStartBuffer = llvm::MemoryBuffer::getFile(getInputFilename());
|
|
auto &StartBuffer = ErrorOrStartBuffer.get();
|
|
auto StartBufferID = SrcMgr.addNewSourceBuffer(std::move(StartBuffer));
|
|
States.push_back(MigrationState::start(SrcMgr, StartBufferID));
|
|
}
|
|
|
|
std::unique_ptr<swift::CompilerInstance>
|
|
Migrator::repeatFixitMigrations(const unsigned Iterations,
|
|
version::Version SwiftLanguageVersion) {
|
|
for (unsigned i = 0; i < Iterations; ++i) {
|
|
auto ThisInstance = performAFixItMigration(SwiftLanguageVersion);
|
|
if (ThisInstance == nullptr) {
|
|
break;
|
|
} else {
|
|
if (States.back()->noChangesOccurred()) {
|
|
return ThisInstance;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<swift::CompilerInstance>
|
|
Migrator::performAFixItMigration(version::Version SwiftLanguageVersion) {
|
|
auto InputState = States.back();
|
|
auto InputText = InputState->getOutputText();
|
|
auto InputBuffer =
|
|
llvm::MemoryBuffer::getMemBufferCopy(InputText, getInputFilename());
|
|
|
|
CompilerInvocation Invocation { StartInvocation };
|
|
Invocation.clearInputs();
|
|
Invocation.getLangOptions().EffectiveLanguageVersion = SwiftLanguageVersion;
|
|
auto &LLVMArgs = Invocation.getFrontendOptions().LLVMArgs;
|
|
auto aarch64_use_tbi = std::find(LLVMArgs.begin(), LLVMArgs.end(),
|
|
"-aarch64-use-tbi");
|
|
if (aarch64_use_tbi != LLVMArgs.end()) {
|
|
LLVMArgs.erase(aarch64_use_tbi);
|
|
}
|
|
|
|
// SE-0160: When migrating, always use the Swift 3 @objc inference rules,
|
|
// which drives warnings with the "@objc" Fix-Its.
|
|
Invocation.getLangOptions().EnableSwift3ObjCInference = true;
|
|
|
|
// The default behavior of the migrator, referred to as "minimal" migration
|
|
// in SE-0160, only adds @objc Fix-Its to those cases where the Objective-C
|
|
// entry point is explicitly used somewhere in the source code. The user
|
|
// may also select a workflow that adds @objc for every declaration that
|
|
// would infer @objc under the Swift 3 rules but would no longer infer
|
|
// @objc in Swift 4.
|
|
Invocation.getLangOptions().WarnSwift3ObjCInference =
|
|
getMigratorOptions().KeepObjcVisibility
|
|
? Swift3ObjCInferenceWarnings::Complete
|
|
: Swift3ObjCInferenceWarnings::Minimal;
|
|
|
|
const auto &OrigFrontendOpts = StartInvocation.getFrontendOptions();
|
|
|
|
auto InputBuffers = OrigFrontendOpts.Inputs.getInputBuffers();
|
|
auto InputFilenames = OrigFrontendOpts.Inputs.getInputFilenames();
|
|
|
|
for (const auto &Buffer : InputBuffers) {
|
|
Invocation.addInputBuffer(Buffer);
|
|
}
|
|
|
|
for (const auto &Filename : InputFilenames) {
|
|
Invocation.addInputFilename(Filename);
|
|
}
|
|
|
|
const unsigned PrimaryIndex =
|
|
Invocation.getFrontendOptions().Inputs.getInputBuffers().size();
|
|
|
|
Invocation.addInputBuffer(InputBuffer.get());
|
|
Invocation.getFrontendOptions().Inputs.setPrimaryInput( {
|
|
PrimaryIndex, SelectedInput::InputKind::Buffer
|
|
});
|
|
|
|
auto Instance = llvm::make_unique<swift::CompilerInstance>();
|
|
if (Instance->setup(Invocation)) {
|
|
return nullptr;
|
|
}
|
|
|
|
FixitApplyDiagnosticConsumer FixitApplyConsumer {
|
|
InputText,
|
|
getInputFilename(),
|
|
};
|
|
Instance->addDiagnosticConsumer(&FixitApplyConsumer);
|
|
|
|
Instance->performSema();
|
|
|
|
StringRef ResultText = InputText;
|
|
unsigned ResultBufferID = InputState->getOutputBufferID();
|
|
|
|
if (FixitApplyConsumer.getNumFixitsApplied() > 0) {
|
|
SmallString<4096> Scratch;
|
|
llvm::raw_svector_ostream OS(Scratch);
|
|
FixitApplyConsumer.printResult(OS);
|
|
auto ResultBuffer = llvm::MemoryBuffer::getMemBufferCopy(OS.str());
|
|
ResultText = ResultBuffer->getBuffer();
|
|
ResultBufferID = SrcMgr.addNewSourceBuffer(std::move(ResultBuffer));
|
|
}
|
|
|
|
States.push_back(MigrationState::make(MigrationKind::CompilerFixits,
|
|
SrcMgr, InputState->getOutputBufferID(),
|
|
ResultBufferID));
|
|
return Instance;
|
|
}
|
|
|
|
bool Migrator::performSyntacticPasses() {
|
|
clang::FileSystemOptions ClangFileSystemOptions;
|
|
clang::FileManager ClangFileManager { ClangFileSystemOptions };
|
|
|
|
llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> DummyClangDiagIDs {
|
|
new clang::DiagnosticIDs()
|
|
};
|
|
auto ClangDiags =
|
|
llvm::make_unique<clang::DiagnosticsEngine>(DummyClangDiagIDs,
|
|
new clang::DiagnosticOptions,
|
|
new clang::DiagnosticConsumer(),
|
|
/*ShouldOwnClient=*/true);
|
|
|
|
clang::SourceManager ClangSourceManager { *ClangDiags, ClangFileManager };
|
|
clang::LangOptions ClangLangOpts;
|
|
clang::edit::EditedSource Edits { ClangSourceManager, ClangLangOpts };
|
|
|
|
auto InputState = States.back();
|
|
auto InputText = InputState->getOutputText();
|
|
|
|
EditorAdapter Editor { StartInstance->getSourceMgr(), ClangSourceManager };
|
|
|
|
runAPIDiffMigratorPass(Editor, StartInstance->getPrimarySourceFile(),
|
|
getMigratorOptions());
|
|
runTupleSplatMigratorPass(Editor, StartInstance->getPrimarySourceFile(),
|
|
getMigratorOptions());
|
|
runTypeOfMigratorPass(Editor, StartInstance->getPrimarySourceFile(),
|
|
getMigratorOptions());
|
|
|
|
Edits.commit(Editor.getEdits());
|
|
|
|
RewriteBufferEditsReceiver Rewriter {
|
|
ClangSourceManager,
|
|
Editor.getClangFileIDForSwiftBufferID(
|
|
StartInstance->getPrimarySourceFile()->getBufferID().getValue()),
|
|
InputState->getOutputText()
|
|
};
|
|
|
|
Edits.applyRewrites(Rewriter);
|
|
|
|
SmallString<1024> Scratch;
|
|
llvm::raw_svector_ostream OS(Scratch);
|
|
Rewriter.printResult(OS);
|
|
auto ResultBuffer = this->SrcMgr.addMemBufferCopy(OS.str());
|
|
|
|
States.push_back(
|
|
MigrationState::make(MigrationKind::Syntactic,
|
|
this->SrcMgr,
|
|
States.back()->getInputBufferID(),
|
|
ResultBuffer));
|
|
return false;
|
|
}
|
|
|
|
namespace {
|
|
/// Print a replacement from a diff edit scriptto the given output stream.
|
|
///
|
|
/// \param Filename The filename of the original file
|
|
/// \param Rep The Replacement to print
|
|
/// \param OS The output stream
|
|
void printReplacement(const StringRef Filename,
|
|
const Replacement &Rep,
|
|
llvm::raw_ostream &OS) {
|
|
assert(!Filename.empty());
|
|
if (Rep.Remove == 0 && Rep.Text.empty()) {
|
|
return;
|
|
}
|
|
OS << " {\n";
|
|
|
|
OS << " \"file\": \"";
|
|
OS.write_escaped(Filename);
|
|
OS << "\",\n";
|
|
|
|
OS << " \"offset\": " << Rep.Offset;
|
|
if (Rep.Remove > 0) {
|
|
OS << ",\n";
|
|
OS << " \"remove\": " << Rep.Remove;
|
|
}
|
|
if (!Rep.Text.empty()) {
|
|
OS << ",\n";
|
|
OS << " \"text\": \"";
|
|
OS.write_escaped(Rep.Text);
|
|
OS << "\"\n";
|
|
} else {
|
|
OS << "\n";
|
|
}
|
|
OS << " }";
|
|
}
|
|
|
|
/// Print a remap file to the given output stream.
|
|
///
|
|
/// \param OriginalFilename The filename of the file that was edited
|
|
/// not the output file for printing here.
|
|
/// \param InputText The input text without any changes.
|
|
/// \param OutputText The result text after any changes.
|
|
/// \param OS The output stream.
|
|
void printRemap(const StringRef OriginalFilename,
|
|
const StringRef InputText,
|
|
const StringRef OutputText,
|
|
llvm::raw_ostream &OS) {
|
|
assert(!OriginalFilename.empty());
|
|
|
|
diff_match_patch<std::string> DMP;
|
|
const auto Diffs = DMP.diff_main(InputText, OutputText, /*checkLines=*/false);
|
|
|
|
OS << "[";
|
|
|
|
size_t Offset = 0;
|
|
|
|
llvm::SmallVector<Replacement, 32> Replacements;
|
|
|
|
for (const auto &Diff : Diffs) {
|
|
size_t OffsetIncrement = 0;
|
|
switch (Diff.operation) {
|
|
case decltype(DMP)::EQUAL:
|
|
OffsetIncrement += Diff.text.size();
|
|
break;
|
|
case decltype(DMP)::INSERT:
|
|
Replacements.push_back({ Offset, 0, Diff.text });
|
|
break;
|
|
case decltype(DMP)::DELETE:
|
|
Replacements.push_back({ Offset, Diff.text.size(), "" });
|
|
OffsetIncrement = Diff.text.size();
|
|
break;
|
|
}
|
|
Offset += OffsetIncrement;
|
|
}
|
|
|
|
assert(Offset == InputText.size());
|
|
|
|
// Combine removal edits with previous edits that are consecutive.
|
|
for (unsigned i = 1; i < Replacements.size();) {
|
|
auto &Previous = Replacements[i-1];
|
|
auto &Current = Replacements[i];
|
|
assert(Current.Offset >= Previous.Offset + Previous.Remove);
|
|
unsigned Distance = Current.Offset-(Previous.Offset + Previous.Remove);
|
|
if (Distance > 0) {
|
|
++i;
|
|
continue;
|
|
}
|
|
if (!Current.Text.empty()) {
|
|
++i;
|
|
continue;
|
|
}
|
|
Previous.Remove += Current.Remove;
|
|
Replacements.erase(Replacements.begin() + i);
|
|
}
|
|
|
|
// Combine removal edits with next edits that are consecutive.
|
|
for (unsigned i = 0; i + 1 < Replacements.size();) {
|
|
auto &Current = Replacements[i];
|
|
auto &nextRep = Replacements[i + 1];
|
|
assert(nextRep.Offset >= Current.Offset + Current.Remove);
|
|
unsigned Distance = nextRep.Offset - (Current.Offset + Current.Remove);
|
|
if (Distance > 0) {
|
|
++i;
|
|
continue;
|
|
}
|
|
if (!Current.Text.empty()) {
|
|
++i;
|
|
continue;
|
|
}
|
|
nextRep.Offset -= Current.Remove;
|
|
nextRep.Remove += Current.Remove;
|
|
Replacements.erase(Replacements.begin() + i);
|
|
}
|
|
|
|
// For remaining removal diffs, include the byte adjacent to the range on the
|
|
// left. libclang applies the diffs as byte diffs, so it doesn't matter if the
|
|
// byte is part of a multi-byte UTF8 character.
|
|
for (unsigned i = 0; i < Replacements.size(); ++i) {
|
|
auto &Current = Replacements[i];
|
|
if (!Current.Text.empty())
|
|
continue;
|
|
if (Current.Offset == 0)
|
|
continue;
|
|
Current.Offset -= 1;
|
|
Current.Remove += 1;
|
|
Current.Text = InputText.substr(Current.Offset, 1);
|
|
}
|
|
|
|
for (auto Rep = Replacements.begin(); Rep != Replacements.end(); ++Rep) {
|
|
if (Rep != Replacements.begin()) {
|
|
OS << ",\n";
|
|
} else {
|
|
OS << "\n";
|
|
}
|
|
printReplacement(OriginalFilename, *Rep, OS);
|
|
}
|
|
|
|
OS << "\n]";
|
|
}
|
|
|
|
} // end anonymous namespace
|
|
|
|
bool Migrator::emitRemap() const {
|
|
const auto &RemapPath = getMigratorOptions().EmitRemapFilePath;
|
|
if (RemapPath.empty()) {
|
|
return false;
|
|
}
|
|
|
|
std::error_code Error;
|
|
llvm::raw_fd_ostream FileOS(RemapPath,
|
|
Error, llvm::sys::fs::F_Text);
|
|
if (FileOS.has_error()) {
|
|
return true;
|
|
}
|
|
|
|
auto InputText = States.front()->getOutputText();
|
|
auto OutputText = States.back()->getOutputText();
|
|
printRemap(getInputFilename(), InputText, OutputText, FileOS);
|
|
|
|
FileOS.flush();
|
|
return FileOS.has_error();
|
|
}
|
|
|
|
bool Migrator::emitMigratedFile() const {
|
|
const auto &OutFilename = getMigratorOptions().EmitMigratedFilePath;
|
|
if (OutFilename.empty()) {
|
|
return false;
|
|
}
|
|
|
|
std::error_code Error;
|
|
llvm::raw_fd_ostream FileOS(OutFilename,
|
|
Error, llvm::sys::fs::F_Text);
|
|
if (FileOS.has_error()) {
|
|
return true;
|
|
}
|
|
|
|
FileOS << States.back()->getOutputText();
|
|
|
|
FileOS.flush();
|
|
|
|
return FileOS.has_error();
|
|
}
|
|
|
|
bool Migrator::dumpStates() const {
|
|
const auto &OutDir = getMigratorOptions().DumpMigrationStatesDir;
|
|
if (OutDir.empty()) {
|
|
return false;
|
|
}
|
|
|
|
auto Failed = false;
|
|
for (size_t i = 0; i < States.size(); ++i) {
|
|
Failed |= States[i]->print(i, OutDir);
|
|
}
|
|
|
|
return Failed;
|
|
}
|
|
|
|
const MigratorOptions &Migrator::getMigratorOptions() const {
|
|
return StartInvocation.getMigratorOptions();
|
|
}
|
|
|
|
const StringRef Migrator::getInputFilename() const {
|
|
auto PrimaryInput =
|
|
StartInvocation.getFrontendOptions().Inputs.getPrimaryInput().getValue();
|
|
return StartInvocation.getFrontendOptions().Inputs.getInputFilenames()[PrimaryInput.Index];
|
|
}
|