blob: 5d39842627d92edbce28c8cfda7f6154f4198a44 [file] [log] [blame]
//===--- Refactoring.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/IDE/Refactoring.h"
#include "swift/IDE/IDERequests.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticsRefactoring.h"
#include "swift/AST/Expr.h"
#include "swift/AST/NameLookup.h"
#include "swift/AST/Pattern.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/Stmt.h"
#include "swift/AST/Types.h"
#include "swift/AST/USRGeneration.h"
#include "swift/Basic/Edit.h"
#include "swift/Basic/StringExtras.h"
#include "swift/Frontend/Frontend.h"
#include "swift/Index/Index.h"
#include "swift/Parse/Lexer.h"
#include "swift/Sema/IDETypeChecking.h"
#include "swift/Subsystems.h"
#include "clang/Rewrite/Core/RewriteBuffer.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/SmallPtrSet.h"
using namespace swift;
using namespace swift::ide;
using namespace swift::index;
namespace {
class ContextFinder : public SourceEntityWalker {
SourceFile &SF;
ASTContext &Ctx;
SourceManager &SM;
SourceRange Target;
llvm::function_ref<bool(ASTNode)> IsContext;
SmallVector<ASTNode, 4> AllContexts;
bool contains(ASTNode Enclosing) {
auto Result = SM.rangeContains(Enclosing.getSourceRange(), Target);
if (Result && IsContext(Enclosing))
AllContexts.push_back(Enclosing);
return Result;
}
public:
ContextFinder(SourceFile &SF, ASTNode TargetNode,
llvm::function_ref<bool(ASTNode)> IsContext =
[](ASTNode N) { return true; }) :
SF(SF), Ctx(SF.getASTContext()), SM(Ctx.SourceMgr),
Target(TargetNode.getSourceRange()), IsContext(IsContext) {}
ContextFinder(SourceFile &SF, SourceLoc TargetLoc,
llvm::function_ref<bool(ASTNode)> IsContext =
[](ASTNode N) { return true; }) :
SF(SF), Ctx(SF.getASTContext()), SM(Ctx.SourceMgr),
Target(TargetLoc), IsContext(IsContext) {
assert(TargetLoc.isValid() && "Invalid loc to find");
}
bool walkToDeclPre(Decl *D, CharSourceRange Range) override { return contains(D); }
bool walkToStmtPre(Stmt *S) override { return contains(S); }
bool walkToExprPre(Expr *E) override { return contains(E); }
void resolve() { walk(SF); }
llvm::ArrayRef<ASTNode> getContexts() const {
return llvm::makeArrayRef(AllContexts);
}
};
class Renamer {
protected:
const SourceManager &SM;
protected:
Renamer(const SourceManager &SM, StringRef OldName) : SM(SM), Old(OldName) {}
// Implementor's interface.
virtual void doRenameLabel(CharSourceRange Label,
RefactoringRangeKind RangeKind,
unsigned NameIndex) = 0;
virtual void doRenameBase(CharSourceRange Range,
RefactoringRangeKind RangeKind) = 0;
public:
const DeclNameViewer Old;
public:
virtual ~Renamer() {}
/// Adds a replacement to rename the given base name range
/// \return true if the given range does not match the old name
bool renameBase(CharSourceRange Range, RefactoringRangeKind RangeKind) {
assert(Range.isValid());
if (stripBackticks(Range).str() != Old.base())
return true;
doRenameBase(Range, RangeKind);
return false;
}
/// Adds replacements to rename the given label ranges
/// \return true if the label ranges do not match the old name
bool renameLabels(ArrayRef<CharSourceRange> LabelRanges,
Optional<unsigned> FirstTrailingLabel,
LabelRangeType RangeType, bool isCallSite) {
if (isCallSite)
return renameLabelsLenient(LabelRanges, FirstTrailingLabel, RangeType);
assert(!FirstTrailingLabel);
ArrayRef<StringRef> OldLabels = Old.args();
if (OldLabels.size() != LabelRanges.size())
return true;
size_t Index = 0;
for (const auto &LabelRange : LabelRanges) {
assert(LabelRange.isValid());
if (!labelRangeMatches(LabelRange, RangeType, OldLabels[Index]))
return true;
splitAndRenameLabel(LabelRange, RangeType, Index++);
}
return false;
}
bool isOperator() const { return Lexer::isOperator(Old.base()); }
private:
/// Returns the range of the (possibly escaped) identifier at the start of
/// \p Range and updates \p IsEscaped to indicate whether it's escaped or not.
CharSourceRange getLeadingIdentifierRange(CharSourceRange Range, bool &IsEscaped) {
assert(Range.isValid() && Range.getByteLength());
IsEscaped = Range.str().front() == '`';
SourceLoc Start = Range.getStart();
if (IsEscaped)
Start = Start.getAdvancedLoc(1);
return Lexer::getCharSourceRangeFromSourceRange(SM, Start);
}
CharSourceRange stripBackticks(CharSourceRange Range) {
StringRef Content = Range.str();
if (Content.size() < 3 || Content.front() != '`' || Content.back() != '`') {
return Range;
}
return CharSourceRange(Range.getStart().getAdvancedLoc(1),
Range.getByteLength() - 2);
}
void splitAndRenameLabel(CharSourceRange Range, LabelRangeType RangeType,
size_t NameIndex) {
switch (RangeType) {
case LabelRangeType::CallArg:
return splitAndRenameCallArg(Range, NameIndex);
case LabelRangeType::Param:
return splitAndRenameParamLabel(Range, NameIndex, /*IsCollapsible=*/true);
case LabelRangeType::NoncollapsibleParam:
return splitAndRenameParamLabel(Range, NameIndex, /*IsCollapsible=*/false);
case LabelRangeType::Selector:
return doRenameLabel(
Range, RefactoringRangeKind::SelectorArgumentLabel, NameIndex);
case LabelRangeType::None:
llvm_unreachable("expected a label range");
}
}
void splitAndRenameParamLabel(CharSourceRange Range, size_t NameIndex, bool IsCollapsible) {
// Split parameter range foo([a b]: Int) into decl argument label [a] and
// parameter name [b] or noncollapsible parameter name [b] if IsCollapsible
// is false (as for subscript decls). If we have only foo([a]: Int), then we
// add an empty range for the local name, or for the decl argument label if
// IsCollapsible is false.
StringRef Content = Range.str();
size_t ExternalNameEnd = Content.find_first_of(" \t\n\v\f\r/");
if (ExternalNameEnd == StringRef::npos) { // foo([a]: Int)
if (IsCollapsible) {
doRenameLabel(Range, RefactoringRangeKind::DeclArgumentLabel, NameIndex);
doRenameLabel(CharSourceRange{Range.getEnd(), 0},
RefactoringRangeKind::ParameterName, NameIndex);
} else {
doRenameLabel(CharSourceRange{Range.getStart(), 0},
RefactoringRangeKind::DeclArgumentLabel, NameIndex);
doRenameLabel(Range, RefactoringRangeKind::NoncollapsibleParameterName,
NameIndex);
}
} else { // foo([a b]: Int)
CharSourceRange Ext{Range.getStart(), unsigned(ExternalNameEnd)};
// Note: we consider the leading whitespace part of the parameter name
// if the parameter is collapsible, since if the parameter is collapsed
// into a matching argument label, we want to remove the whitespace too.
// FIXME: handle comments foo(a /*...*/b: Int).
size_t LocalNameStart = Content.find_last_of(" \t\n\v\f\r/");
assert(LocalNameStart != StringRef::npos);
if (!IsCollapsible)
++LocalNameStart;
auto LocalLoc = Range.getStart().getAdvancedLocOrInvalid(LocalNameStart);
CharSourceRange Local{LocalLoc, unsigned(Content.size() - LocalNameStart)};
doRenameLabel(Ext, RefactoringRangeKind::DeclArgumentLabel, NameIndex);
if (IsCollapsible) {
doRenameLabel(Local, RefactoringRangeKind::ParameterName, NameIndex);
} else {
doRenameLabel(Local, RefactoringRangeKind::NoncollapsibleParameterName, NameIndex);
}
}
}
void splitAndRenameCallArg(CharSourceRange Range, size_t NameIndex) {
// Split call argument foo([a: ]1) into argument name [a] and the remainder
// [: ].
StringRef Content = Range.str();
size_t Colon = Content.find(':'); // FIXME: leading whitespace?
if (Colon == StringRef::npos) {
assert(Content.empty());
doRenameLabel(Range, RefactoringRangeKind::CallArgumentCombined,
NameIndex);
return;
}
// Include any whitespace before the ':'.
assert(Colon == Content.substr(0, Colon).size());
Colon = Content.substr(0, Colon).rtrim().size();
CharSourceRange Arg{Range.getStart(), unsigned(Colon)};
doRenameLabel(Arg, RefactoringRangeKind::CallArgumentLabel, NameIndex);
auto ColonLoc = Range.getStart().getAdvancedLocOrInvalid(Colon);
assert(ColonLoc.isValid());
CharSourceRange Rest{ColonLoc, unsigned(Content.size() - Colon)};
doRenameLabel(Rest, RefactoringRangeKind::CallArgumentColon, NameIndex);
}
bool labelRangeMatches(CharSourceRange Range, LabelRangeType RangeType, StringRef Expected) {
if (Range.getByteLength()) {
bool IsEscaped = false;
CharSourceRange ExistingLabelRange = getLeadingIdentifierRange(Range, IsEscaped);
StringRef ExistingLabel = ExistingLabelRange.str();
bool IsSingleName = Range == ExistingLabelRange ||
(IsEscaped && Range.getByteLength() == ExistingLabel.size() + 2);
switch (RangeType) {
case LabelRangeType::NoncollapsibleParam:
if (IsSingleName && Expected.empty()) // subscript([x]: Int)
return true;
LLVM_FALLTHROUGH;
case LabelRangeType::CallArg:
case LabelRangeType::Param:
case LabelRangeType::Selector:
return ExistingLabel == (Expected.empty() ? "_" : Expected);
case LabelRangeType::None:
llvm_unreachable("Unhandled label range type");
}
}
return Expected.empty();
}
bool renameLabelsLenient(ArrayRef<CharSourceRange> LabelRanges,
Optional<unsigned> FirstTrailingLabel,
LabelRangeType RangeType) {
ArrayRef<StringRef> OldNames = Old.args();
// First, match trailing closure arguments in reverse
if (FirstTrailingLabel) {
auto TrailingLabels = LabelRanges.drop_front(*FirstTrailingLabel);
LabelRanges = LabelRanges.take_front(*FirstTrailingLabel);
for (auto LabelIndex: llvm::reverse(indices(TrailingLabels))) {
CharSourceRange Label = TrailingLabels[LabelIndex];
if (Label.getByteLength()) {
if (OldNames.empty())
return true;
while (!labelRangeMatches(Label, LabelRangeType::Selector,
OldNames.back())) {
if ((OldNames = OldNames.drop_back()).empty())
return true;
}
splitAndRenameLabel(Label, LabelRangeType::Selector,
OldNames.size() - 1);
OldNames = OldNames.drop_back();
continue;
}
// empty labelled trailing closure label
if (LabelIndex) {
if (OldNames.empty())
return true;
while (!OldNames.back().empty()) {
if ((OldNames = OldNames.drop_back()).empty())
return true;
}
splitAndRenameLabel(Label, LabelRangeType::Selector,
OldNames.size() - 1);
OldNames = OldNames.drop_back();
continue;
}
// unlabelled trailing closure label
OldNames = OldNames.drop_back();
continue;
}
}
// Next, match the non-trailing arguments.
size_t NameIndex = 0;
for (CharSourceRange Label : LabelRanges) {
// empty label
if (!Label.getByteLength()) {
// first name pos
if (!NameIndex) {
while (!OldNames[NameIndex].empty()) {
if (++NameIndex >= OldNames.size())
return true;
}
splitAndRenameLabel(Label, RangeType, NameIndex++);
continue;
}
// other name pos
if (NameIndex >= OldNames.size() || !OldNames[NameIndex].empty()) {
// FIXME: only allow one variadic param
continue; // allow for variadic
}
splitAndRenameLabel(Label, RangeType, NameIndex++);
continue;
}
// non-empty label
if (NameIndex >= OldNames.size())
return true;
while (!labelRangeMatches(Label, RangeType, OldNames[NameIndex])) {
if (++NameIndex >= OldNames.size())
return true;
};
splitAndRenameLabel(Label, RangeType, NameIndex++);
}
return false;
}
static RegionType getSyntacticRenameRegionType(const ResolvedLoc &Resolved) {
if (Resolved.Node.isNull())
return RegionType::Comment;
if (Expr *E = Resolved.Node.getAsExpr()) {
if (isa<StringLiteralExpr>(E))
return RegionType::String;
}
if (Resolved.IsInSelector)
return RegionType::Selector;
if (Resolved.IsActive)
return RegionType::ActiveCode;
return RegionType::InactiveCode;
}
public:
RegionType addSyntacticRenameRanges(const ResolvedLoc &Resolved,
const RenameLoc &Config) {
if (!Resolved.Range.isValid())
return RegionType::Unmatched;
auto RegionKind = getSyntacticRenameRegionType(Resolved);
// Don't include unknown references coming from active code; if we don't
// have a semantic NameUsage for them, then they're likely unrelated symbols
// that happen to have the same name.
if (RegionKind == RegionType::ActiveCode &&
Config.Usage == NameUsage::Unknown)
return RegionType::Unmatched;
assert(Config.Usage != NameUsage::Call || Config.IsFunctionLike);
// FIXME: handle escaped keyword names `init`
bool IsSubscript = Old.base() == "subscript" && Config.IsFunctionLike;
bool IsInit = Old.base() == "init" && Config.IsFunctionLike;
// FIXME: this should only be treated specially for instance methods.
bool IsCallAsFunction = Old.base() == "callAsFunction" &&
Config.IsFunctionLike;
bool IsSpecialBase = IsInit || IsSubscript || IsCallAsFunction;
// Filter out non-semantic special basename locations with no labels.
// We've already filtered out those in active code, so these are
// any appearance of just 'init', 'subscript', or 'callAsFunction' in
// strings, comments, and inactive code.
if (IsSpecialBase && (Config.Usage == NameUsage::Unknown &&
Resolved.LabelType == LabelRangeType::None))
return RegionType::Unmatched;
if (!Config.IsFunctionLike || !IsSpecialBase) {
if (renameBase(Resolved.Range, RefactoringRangeKind::BaseName))
return RegionType::Mismatch;
} else if (IsInit || IsCallAsFunction) {
if (renameBase(Resolved.Range, RefactoringRangeKind::KeywordBaseName)) {
// The base name doesn't need to match (but may) for calls, but
// it should for definitions and references.
if (Config.Usage == NameUsage::Definition ||
Config.Usage == NameUsage::Reference) {
return RegionType::Mismatch;
}
}
} else if (IsSubscript && Config.Usage == NameUsage::Definition) {
if (renameBase(Resolved.Range, RefactoringRangeKind::KeywordBaseName))
return RegionType::Mismatch;
}
bool HandleLabels = false;
if (Config.IsFunctionLike) {
switch (Config.Usage) {
case NameUsage::Call:
HandleLabels = !isOperator();
break;
case NameUsage::Definition:
HandleLabels = true;
break;
case NameUsage::Reference:
HandleLabels = Resolved.LabelType == LabelRangeType::Selector || IsSubscript;
break;
case NameUsage::Unknown:
HandleLabels = Resolved.LabelType != LabelRangeType::None;
break;
}
} else if (Resolved.LabelType != LabelRangeType::None &&
!Config.IsNonProtocolType &&
// FIXME: Workaround for enum case labels until we support them
Config.Usage != NameUsage::Definition) {
return RegionType::Mismatch;
}
if (HandleLabels) {
bool isCallSite = Config.Usage != NameUsage::Definition &&
(Config.Usage != NameUsage::Reference || IsSubscript) &&
Resolved.LabelType == LabelRangeType::CallArg;
if (renameLabels(Resolved.LabelRanges, Resolved.FirstTrailingLabel,
Resolved.LabelType, isCallSite))
return Config.Usage == NameUsage::Unknown ?
RegionType::Unmatched : RegionType::Mismatch;
}
return RegionKind;
}
};
class RenameRangeDetailCollector : public Renamer {
void doRenameLabel(CharSourceRange Label, RefactoringRangeKind RangeKind,
unsigned NameIndex) override {
Ranges.push_back({Label, RangeKind, NameIndex});
}
void doRenameBase(CharSourceRange Range,
RefactoringRangeKind RangeKind) override {
Ranges.push_back({Range, RangeKind, None});
}
public:
RenameRangeDetailCollector(const SourceManager &SM, StringRef OldName)
: Renamer(SM, OldName) {}
std::vector<RenameRangeDetail> Ranges;
};
class TextReplacementsRenamer : public Renamer {
llvm::StringSet<> &ReplaceTextContext;
std::vector<Replacement> Replacements;
public:
const DeclNameViewer New;
private:
StringRef registerText(StringRef Text) {
if (Text.empty())
return Text;
return ReplaceTextContext.insert(Text).first->getKey();
}
StringRef getCallArgLabelReplacement(StringRef OldLabelRange,
StringRef NewLabel) {
return NewLabel.empty() ? "" : NewLabel;
}
StringRef getCallArgColonReplacement(StringRef OldLabelRange,
StringRef NewLabel) {
// Expected OldLabelRange: foo( []3, a[: ]2, b[ : ]3 ...)
// FIXME: Preserve comments: foo([a/*:*/ : /*:*/ ]2, ...)
if (NewLabel.empty())
return "";
if (OldLabelRange.empty())
return ": ";
return registerText(OldLabelRange);
}
StringRef getCallArgCombinedReplacement(StringRef OldArgLabel,
StringRef NewArgLabel) {
// This case only happens when going from foo([]1) to foo([a: ]1).
assert(OldArgLabel.empty());
if (NewArgLabel.empty())
return "";
return registerText((llvm::Twine(NewArgLabel) + ": ").str());
}
StringRef getParamNameReplacement(StringRef OldParam, StringRef OldArgLabel,
StringRef NewArgLabel) {
// We don't want to get foo(a a: Int), so drop the parameter name if the
// argument label will match the original name.
// Note: the leading whitespace is part of the parameter range.
if (!NewArgLabel.empty() && OldParam.ltrim() == NewArgLabel)
return "";
// If we're renaming foo(x: Int) to foo(_:), then use the original argument
// label as the parameter name so as to not break references in the body.
if (NewArgLabel.empty() && !OldArgLabel.empty() && OldParam.empty())
return registerText((llvm::Twine(" ") + OldArgLabel).str());
return registerText(OldParam);
}
StringRef getDeclArgumentLabelReplacement(StringRef OldLabelRange,
StringRef NewArgLabel) {
// OldLabelRange is subscript([]a: Int), foo([a]: Int) or foo([a] b: Int)
if (NewArgLabel.empty())
return OldLabelRange.empty() ? "" : "_";
if (OldLabelRange.empty())
return registerText((llvm::Twine(NewArgLabel) + " ").str());
return registerText(NewArgLabel);
}
StringRef getReplacementText(StringRef LabelRange,
RefactoringRangeKind RangeKind,
StringRef OldLabel, StringRef NewLabel) {
switch (RangeKind) {
case RefactoringRangeKind::CallArgumentLabel:
return getCallArgLabelReplacement(LabelRange, NewLabel);
case RefactoringRangeKind::CallArgumentColon:
return getCallArgColonReplacement(LabelRange, NewLabel);
case RefactoringRangeKind::CallArgumentCombined:
return getCallArgCombinedReplacement(LabelRange, NewLabel);
case RefactoringRangeKind::ParameterName:
return getParamNameReplacement(LabelRange, OldLabel, NewLabel);
case RefactoringRangeKind::NoncollapsibleParameterName:
return LabelRange;
case RefactoringRangeKind::DeclArgumentLabel:
return getDeclArgumentLabelReplacement(LabelRange, NewLabel);
case RefactoringRangeKind::SelectorArgumentLabel:
return NewLabel.empty() ? "_" : registerText(NewLabel);
default:
llvm_unreachable("label range type is none but there are labels");
}
}
void addReplacement(CharSourceRange LabelRange,
RefactoringRangeKind RangeKind, StringRef OldLabel,
StringRef NewLabel) {
StringRef ExistingLabel = LabelRange.str();
StringRef Text =
getReplacementText(ExistingLabel, RangeKind, OldLabel, NewLabel);
if (Text != ExistingLabel)
Replacements.push_back({LabelRange, Text, {}});
}
void doRenameLabel(CharSourceRange Label, RefactoringRangeKind RangeKind,
unsigned NameIndex) override {
addReplacement(Label, RangeKind, Old.args()[NameIndex],
New.args()[NameIndex]);
}
void doRenameBase(CharSourceRange Range, RefactoringRangeKind) override {
if (Old.base() != New.base())
Replacements.push_back({Range, registerText(New.base()), {}});
}
public:
TextReplacementsRenamer(const SourceManager &SM, StringRef OldName,
StringRef NewName,
llvm::StringSet<> &ReplaceTextContext)
: Renamer(SM, OldName), ReplaceTextContext(ReplaceTextContext),
New(NewName) {
assert(Old.isValid() && New.isValid());
assert(Old.partsCount() == New.partsCount());
}
std::vector<Replacement> getReplacements() const {
return std::move(Replacements);
}
};
static const ValueDecl *getRelatedSystemDecl(const ValueDecl *VD) {
if (VD->getModuleContext()->isSystemModule())
return VD;
for (auto *Req : VD->getSatisfiedProtocolRequirements()) {
if (Req->getModuleContext()->isSystemModule())
return Req;
}
for (auto Over = VD->getOverriddenDecl(); Over;
Over = Over->getOverriddenDecl()) {
if (Over->getModuleContext()->isSystemModule())
return Over;
}
return nullptr;
}
static Optional<RefactoringKind>
getAvailableRenameForDecl(const ValueDecl *VD,
Optional<RenameRefInfo> RefInfo) {
std::vector<RenameAvailabiliyInfo> Scratch;
for (auto &Info : collectRenameAvailabilityInfo(VD, RefInfo, Scratch)) {
if (Info.AvailableKind == RenameAvailableKind::Available)
return Info.Kind;
}
return None;
}
class RenameRangeCollector : public IndexDataConsumer {
public:
RenameRangeCollector(StringRef USR, StringRef newName)
: USR(USR.str()), newName(newName.str()) {}
RenameRangeCollector(const ValueDecl *D, StringRef newName)
: newName(newName.str()) {
llvm::raw_string_ostream OS(USR);
printValueDeclUSR(D, OS);
}
ArrayRef<RenameLoc> results() const { return locations; }
private:
bool indexLocals() override { return true; }
void failed(StringRef error) override {}
bool startDependency(StringRef name, StringRef path, bool isClangModule, bool isSystem) override {
return true;
}
bool finishDependency(bool isClangModule) override { return true; }
Action startSourceEntity(const IndexSymbol &symbol) override {
if (symbol.USR == USR) {
if (auto loc = indexSymbolToRenameLoc(symbol, newName)) {
locations.push_back(std::move(*loc));
}
}
return IndexDataConsumer::Continue;
}
bool finishSourceEntity(SymbolInfo symInfo, SymbolRoleSet roles) override {
return true;
}
Optional<RenameLoc> indexSymbolToRenameLoc(const index::IndexSymbol &symbol,
StringRef NewName);
private:
std::string USR;
std::string newName;
StringScratchSpace stringStorage;
std::vector<RenameLoc> locations;
};
Optional<RenameLoc>
RenameRangeCollector::indexSymbolToRenameLoc(const index::IndexSymbol &symbol,
StringRef newName) {
if (symbol.roles & (unsigned)index::SymbolRole::Implicit) {
return None;
}
NameUsage usage = NameUsage::Unknown;
if (symbol.roles & (unsigned)index::SymbolRole::Call) {
usage = NameUsage::Call;
} else if (symbol.roles & (unsigned)index::SymbolRole::Definition) {
usage = NameUsage::Definition;
} else if (symbol.roles & (unsigned)index::SymbolRole::Reference) {
usage = NameUsage::Reference;
} else {
llvm_unreachable("unexpected role");
}
bool isFunctionLike = false;
bool isNonProtocolType = false;
switch (symbol.symInfo.Kind) {
case index::SymbolKind::EnumConstant:
case index::SymbolKind::Function:
case index::SymbolKind::Constructor:
case index::SymbolKind::ConversionFunction:
case index::SymbolKind::InstanceMethod:
case index::SymbolKind::ClassMethod:
case index::SymbolKind::StaticMethod:
isFunctionLike = true;
break;
case index::SymbolKind::Class:
case index::SymbolKind::Enum:
case index::SymbolKind::Struct:
isNonProtocolType = true;
break;
default:
break;
}
StringRef oldName = stringStorage.copyString(symbol.name);
return RenameLoc{symbol.line, symbol.column, usage, oldName, newName,
isFunctionLike, isNonProtocolType};
}
ArrayRef<SourceFile*>
collectSourceFiles(ModuleDecl *MD, llvm::SmallVectorImpl<SourceFile*> &Scratch) {
for (auto Unit : MD->getFiles()) {
if (auto SF = dyn_cast<SourceFile>(Unit)) {
Scratch.push_back(SF);
}
}
return llvm::makeArrayRef(Scratch);
}
/// Get the source file that contains the given range and belongs to the module.
SourceFile *getContainingFile(ModuleDecl *M, RangeConfig Range) {
llvm::SmallVector<SourceFile*, 4> Files;
for (auto File : collectSourceFiles(M, Files)) {
if (File->getBufferID()) {
if (File->getBufferID().getValue() == Range.BufferId) {
return File;
}
}
}
return nullptr;
}
class RefactoringAction {
protected:
ModuleDecl *MD;
SourceFile *TheFile;
SourceEditConsumer &EditConsumer;
ASTContext &Ctx;
SourceManager &SM;
DiagnosticEngine DiagEngine;
SourceLoc StartLoc;
StringRef PreferredName;
public:
RefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts,
SourceEditConsumer &EditConsumer,
DiagnosticConsumer &DiagConsumer);
virtual ~RefactoringAction() = default;
virtual bool performChange() = 0;
};
RefactoringAction::
RefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts,
SourceEditConsumer &EditConsumer,
DiagnosticConsumer &DiagConsumer): MD(MD),
TheFile(getContainingFile(MD, Opts.Range)),
EditConsumer(EditConsumer), Ctx(MD->getASTContext()),
SM(MD->getASTContext().SourceMgr), DiagEngine(SM),
StartLoc(Lexer::getLocForStartOfToken(SM, Opts.Range.getStart(SM))),
PreferredName(Opts.PreferredName) {
DiagEngine.addConsumer(DiagConsumer);
}
/// Different from RangeBasedRefactoringAction, TokenBasedRefactoringAction takes
/// the input of a given token, e.g., a name or an "if" key word. Contextual
/// refactoring kinds can suggest applicable refactorings on that token, e.g.
/// rename or reverse if statement.
class TokenBasedRefactoringAction : public RefactoringAction {
protected:
ResolvedCursorInfo CursorInfo;
public:
TokenBasedRefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts,
SourceEditConsumer &EditConsumer,
DiagnosticConsumer &DiagConsumer) :
RefactoringAction(MD, Opts, EditConsumer, DiagConsumer) {
// Resolve the sema token and save it for later use.
CursorInfo = evaluateOrDefault(TheFile->getASTContext().evaluator,
CursorInfoRequest{ CursorInfoOwner(TheFile, StartLoc)},
ResolvedCursorInfo());
}
};
#define CURSOR_REFACTORING(KIND, NAME, ID) \
class RefactoringAction##KIND: public TokenBasedRefactoringAction { \
public: \
RefactoringAction##KIND(ModuleDecl *MD, RefactoringOptions &Opts, \
SourceEditConsumer &EditConsumer, \
DiagnosticConsumer &DiagConsumer) : \
TokenBasedRefactoringAction(MD, Opts, EditConsumer, DiagConsumer) {} \
bool performChange() override; \
static bool isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag); \
bool isApplicable() { \
return RefactoringAction##KIND::isApplicable(CursorInfo, DiagEngine) ; \
} \
};
#include "swift/IDE/RefactoringKinds.def"
class RangeBasedRefactoringAction : public RefactoringAction {
protected:
ResolvedRangeInfo RangeInfo;
public:
RangeBasedRefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts,
SourceEditConsumer &EditConsumer,
DiagnosticConsumer &DiagConsumer) :
RefactoringAction(MD, Opts, EditConsumer, DiagConsumer),
RangeInfo(evaluateOrDefault(MD->getASTContext().evaluator,
RangeInfoRequest(RangeInfoOwner(TheFile, Opts.Range.getStart(SM), Opts.Range.getEnd(SM))),
ResolvedRangeInfo())) {}
};
#define RANGE_REFACTORING(KIND, NAME, ID) \
class RefactoringAction##KIND: public RangeBasedRefactoringAction { \
public: \
RefactoringAction##KIND(ModuleDecl *MD, RefactoringOptions &Opts, \
SourceEditConsumer &EditConsumer, \
DiagnosticConsumer &DiagConsumer) : \
RangeBasedRefactoringAction(MD, Opts, EditConsumer, DiagConsumer) {} \
bool performChange() override; \
static bool isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag); \
bool isApplicable() { \
return RefactoringAction##KIND::isApplicable(RangeInfo, DiagEngine) ; \
} \
};
#include "swift/IDE/RefactoringKinds.def"
bool RefactoringActionLocalRename::
isApplicable(ResolvedCursorInfo CursorInfo, DiagnosticEngine &Diag) {
if (CursorInfo.Kind != CursorInfoKind::ValueRef)
return false;
Optional<RenameRefInfo> RefInfo;
if (CursorInfo.IsRef)
RefInfo = {CursorInfo.SF, CursorInfo.Loc, CursorInfo.IsKeywordArgument};
auto RenameOp = getAvailableRenameForDecl(CursorInfo.ValueD, RefInfo);
return RenameOp.hasValue() &&
RenameOp.getValue() == RefactoringKind::LocalRename;
}
static void analyzeRenameScope(ValueDecl *VD, Optional<RenameRefInfo> RefInfo,
DiagnosticEngine &Diags,
llvm::SmallVectorImpl<DeclContext *> &Scopes) {
Scopes.clear();
if (!getAvailableRenameForDecl(VD, RefInfo).hasValue()) {
Diags.diagnose(SourceLoc(), diag::value_decl_no_loc, VD->getName());
return;
}
auto *Scope = VD->getDeclContext();
// If the context is a top-level code decl, there may be other sibling
// decls that the renamed symbol is visible from
if (isa<TopLevelCodeDecl>(Scope))
Scope = Scope->getParent();
Scopes.push_back(Scope);
}
bool RefactoringActionLocalRename::performChange() {
if (StartLoc.isInvalid()) {
DiagEngine.diagnose(SourceLoc(), diag::invalid_location);
return true;
}
if (!DeclNameViewer(PreferredName).isValid()) {
DiagEngine.diagnose(SourceLoc(), diag::invalid_name, PreferredName);
return true;
}
if (!TheFile) {
DiagEngine.diagnose(StartLoc, diag::location_module_mismatch,
MD->getNameStr());
return true;
}
CursorInfo = evaluateOrDefault(TheFile->getASTContext().evaluator,
CursorInfoRequest{CursorInfoOwner(TheFile, StartLoc)},
ResolvedCursorInfo());
if (CursorInfo.isValid() && CursorInfo.ValueD) {
ValueDecl *VD = CursorInfo.CtorTyRef ? CursorInfo.CtorTyRef : CursorInfo.ValueD;
llvm::SmallVector<DeclContext *, 8> Scopes;
Optional<RenameRefInfo> RefInfo;
if (CursorInfo.IsRef)
RefInfo = {CursorInfo.SF, CursorInfo.Loc, CursorInfo.IsKeywordArgument};
analyzeRenameScope(VD, RefInfo, DiagEngine, Scopes);
if (Scopes.empty())
return true;
RenameRangeCollector rangeCollector(VD, PreferredName);
for (DeclContext *DC : Scopes)
indexDeclContext(DC, rangeCollector);
auto consumers = DiagEngine.takeConsumers();
assert(consumers.size() == 1);
return syntacticRename(TheFile, rangeCollector.results(), EditConsumer,
*consumers[0]);
} else {
DiagEngine.diagnose(StartLoc, diag::unresolved_location);
return true;
}
}
StringRef getDefaultPreferredName(RefactoringKind Kind) {
switch(Kind) {
case RefactoringKind::None:
llvm_unreachable("Should be a valid refactoring kind");
case RefactoringKind::GlobalRename:
case RefactoringKind::LocalRename:
return "newName";
case RefactoringKind::ExtractExpr:
case RefactoringKind::ExtractRepeatedExpr:
return "extractedExpr";
case RefactoringKind::ExtractFunction:
return "extractedFunc";
default:
return "";
}
}
enum class CannotExtractReason {
Literal,
VoidType,
};
class ExtractCheckResult {
bool KnownFailure;
llvm::SmallVector<CannotExtractReason, 2> AllReasons;
public:
ExtractCheckResult(): KnownFailure(true) {}
ExtractCheckResult(ArrayRef<CannotExtractReason> AllReasons):
KnownFailure(false), AllReasons(AllReasons.begin(), AllReasons.end()) {}
bool success() { return success({}); }
bool success(llvm::ArrayRef<CannotExtractReason> ExpectedReasons) {
if (KnownFailure)
return false;
bool Result = true;
// Check if any reasons aren't covered by the list of expected reasons
// provided by the client.
for (auto R: AllReasons) {
Result &= llvm::is_contained(ExpectedReasons, R);
}
return Result;
}
};
/// Check whether a given range can be extracted.
/// Return true on successful condition checking,.
/// Return false on failed conditions.
ExtractCheckResult checkExtractConditions(ResolvedRangeInfo &RangeInfo,
DiagnosticEngine &DiagEngine) {
llvm::SmallVector<CannotExtractReason, 2> AllReasons;
// If any declared declaration is refered out of the given range, return false.
auto Declared = RangeInfo.DeclaredDecls;
auto It = std::find_if(Declared.begin(), Declared.end(),
[](DeclaredDecl DD) { return DD.ReferredAfterRange; });
if (It != Declared.end()) {
DiagEngine.diagnose(It->VD->getLoc(),
diag::value_decl_referenced_out_of_range,
It->VD->getName());
return ExtractCheckResult();
}
// We cannot extract a range with multi entry points.
if (!RangeInfo.HasSingleEntry) {
DiagEngine.diagnose(SourceLoc(), diag::multi_entry_range);
return ExtractCheckResult();
}
// We cannot extract code that is not sure to exit or not.
if (RangeInfo.exit() == ExitState::Unsure) {
return ExtractCheckResult();
}
// We cannot extract expressions of l-value type.
if (auto Ty = RangeInfo.getType()) {
if (Ty->hasLValueType() || Ty->is<InOutType>())
return ExtractCheckResult();
// Disallow extracting error type expressions/statements
// FIXME: diagnose what happened?
if (Ty->hasError())
return ExtractCheckResult();
if (Ty->isVoid()) {
AllReasons.emplace_back(CannotExtractReason::VoidType);
}
}
// We cannot extract a range with orphaned loop keyword.
switch (RangeInfo.Orphan) {
case swift::ide::OrphanKind::Continue:
DiagEngine.diagnose(SourceLoc(), diag::orphan_loop_keyword, "continue");
return ExtractCheckResult();
case swift::ide::OrphanKind::Break:
DiagEngine.diagnose(SourceLoc(), diag::orphan_loop_keyword, "break");
return ExtractCheckResult();
case swift::ide::OrphanKind::None:
break;
}
// Guard statement can not be extracted.
if (llvm::any_of(RangeInfo.ContainedNodes,
[](ASTNode N) { return N.isStmt(StmtKind::Guard); })) {
return ExtractCheckResult();
}
// Disallow extracting certain kinds of statements.
if (RangeInfo.Kind == RangeKind::SingleStatement) {
Stmt *S = RangeInfo.ContainedNodes[0].get<Stmt *>();
// These aren't independent statement.
if (isa<BraceStmt>(S) || isa<CaseStmt>(S))
return ExtractCheckResult();
}
// Disallow extracting literals.
if (RangeInfo.Kind == RangeKind::SingleExpression) {
Expr *E = RangeInfo.ContainedNodes[0].get<Expr*>();
// Until implementing the performChange() part of extracting trailing
// closures, we disable them for now.
if (isa<AbstractClosureExpr>(E))
return ExtractCheckResult();
if (isa<LiteralExpr>(E))
AllReasons.emplace_back(CannotExtractReason::Literal);
}
switch (RangeInfo.RangeContext->getContextKind()) {
case swift::DeclContextKind::Initializer:
case swift::DeclContextKind::SubscriptDecl:
case swift::DeclContextKind::EnumElementDecl:
case swift::DeclContextKind::AbstractFunctionDecl:
case swift::DeclContextKind::AbstractClosureExpr:
case swift::DeclContextKind::TopLevelCodeDecl:
break;
case swift::DeclContextKind::SerializedLocal:
case swift::DeclContextKind::Module:
case swift::DeclContextKind::FileUnit:
case swift::DeclContextKind::GenericTypeDecl:
case swift::DeclContextKind::ExtensionDecl:
return ExtractCheckResult();
}
return ExtractCheckResult(AllReasons);
}
bool RefactoringActionExtractFunction::
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
switch (Info.Kind) {
case RangeKind::PartOfExpression:
case RangeKind::SingleDecl:
case RangeKind::MultiTypeMemberDecl:
case RangeKind::Invalid:
return false;
case RangeKind::SingleExpression:
case RangeKind::SingleStatement:
case RangeKind::MultiStatement: {
return checkExtractConditions(Info, Diag).
success({CannotExtractReason::VoidType});
}
}
llvm_unreachable("unhandled kind");
}
static StringRef correctNameInternal(ASTContext &Ctx, StringRef Name,
ArrayRef<ValueDecl*> AllVisibles) {
// If we find the collision.
bool FoundCollision = false;
// The suffixes we cannot use by appending to the original given name.
llvm::StringSet<> UsedSuffixes;
for (auto VD : AllVisibles) {
StringRef S = VD->getBaseName().userFacingName();
if (!S.startswith(Name))
continue;
StringRef Suffix = S.substr(Name.size());
if (Suffix.empty())
FoundCollision = true;
else
UsedSuffixes.insert(Suffix);
}
if (!FoundCollision)
return Name;
// Find the first suffix we can use.
std::string SuffixToUse;
for (unsigned I = 1; ; I ++) {
SuffixToUse = std::to_string(I);
if (UsedSuffixes.count(SuffixToUse) == 0)
break;
}
return Ctx.getIdentifier((llvm::Twine(Name) + SuffixToUse).str()).str();
}
static StringRef correctNewDeclName(DeclContext *DC, StringRef Name) {
// Collect all visible decls in the decl context.
llvm::SmallVector<ValueDecl*, 16> AllVisibles;
VectorDeclConsumer Consumer(AllVisibles);
ASTContext &Ctx = DC->getASTContext();
lookupVisibleDecls(Consumer, DC, true);
return correctNameInternal(Ctx, Name, AllVisibles);
}
static Type sanitizeType(Type Ty) {
// Transform lvalue type to inout type so that we can print it properly.
return Ty.transform([](Type Ty) {
if (Ty->is<LValueType>()) {
return Type(InOutType::get(Ty->getRValueType()->getCanonicalType()));
}
return Ty;
});
}
static SourceLoc
getNewFuncInsertLoc(DeclContext *DC, DeclContext*& InsertToContext) {
if (auto D = DC->getInnermostDeclarationDeclContext()) {
// If extracting from a getter/setter, we should skip both the immediate
// getter/setter function and the individual var decl. The pattern binding
// decl is the position before which we should insert the newly extracted
// function.
if (auto *FD = dyn_cast<AccessorDecl>(D)) {
ValueDecl *SD = FD->getStorage();
switch (SD->getKind()) {
case DeclKind::Var:
if (auto *PBD = cast<VarDecl>(SD)->getParentPatternBinding())
D = PBD;
break;
case DeclKind::Subscript:
D = SD;
break;
default:
break;
}
}
auto Result = D->getStartLoc();
assert(Result.isValid());
// The insert loc should be before every decl attributes.
for (auto Attr : D->getAttrs()) {
auto Loc = Attr->getRangeWithAt().Start;
if (Loc.isValid() &&
Loc.getOpaquePointerValue() < Result.getOpaquePointerValue())
Result = Loc;
}
// The insert loc should be before the doc comments associated with this decl.
if (!D->getRawComment().Comments.empty()) {
auto Loc = D->getRawComment().Comments.front().Range.getStart();
if (Loc.isValid() &&
Loc.getOpaquePointerValue() < Result.getOpaquePointerValue()) {
Result = Loc;
}
}
InsertToContext = D->getDeclContext();
return Result;
}
return SourceLoc();
}
static std::vector<NoteRegion>
getNotableRegions(StringRef SourceText, unsigned NameOffset, StringRef Name,
bool IsFunctionLike = false, bool IsNonProtocolType = false) {
auto InputBuffer = llvm::MemoryBuffer::getMemBufferCopy(SourceText,"<extract>");
CompilerInvocation Invocation{};
Invocation.getFrontendOptions().InputsAndOutputs.addInput(
InputFile("<extract>", true, InputBuffer.get(), file_types::TY_Swift));
Invocation.getFrontendOptions().ModuleName = "extract";
Invocation.getLangOptions().DisablePoundIfEvaluation = true;
auto Instance = std::make_unique<swift::CompilerInstance>();
if (Instance->setup(Invocation))
llvm_unreachable("Failed setup");
unsigned BufferId = Instance->getPrimarySourceFile()->getBufferID().getValue();
SourceManager &SM = Instance->getSourceMgr();
SourceLoc NameLoc = SM.getLocForOffset(BufferId, NameOffset);
auto LineAndCol = SM.getPresumedLineAndColumnForLoc(NameLoc);
UnresolvedLoc UnresoledName{NameLoc, true};
NameMatcher Matcher(*Instance->getPrimarySourceFile());
auto Resolved = Matcher.resolve(llvm::makeArrayRef(UnresoledName), None);
assert(!Resolved.empty() && "Failed to resolve generated func name loc");
RenameLoc RenameConfig = {
LineAndCol.first, LineAndCol.second,
NameUsage::Definition, /*OldName=*/Name, /*NewName=*/"",
IsFunctionLike, IsNonProtocolType
};
RenameRangeDetailCollector Renamer(SM, Name);
Renamer.addSyntacticRenameRanges(Resolved.back(), RenameConfig);
auto Ranges = Renamer.Ranges;
std::vector<NoteRegion> NoteRegions(Renamer.Ranges.size());
std::transform(
Ranges.begin(), Ranges.end(), NoteRegions.begin(),
[&SM](RenameRangeDetail &Detail) -> NoteRegion {
auto Start = SM.getPresumedLineAndColumnForLoc(Detail.Range.getStart());
auto End = SM.getPresumedLineAndColumnForLoc(Detail.Range.getEnd());
return {Detail.RangeKind, Start.first, Start.second,
End.first, End.second, Detail.Index};
});
return NoteRegions;
}
bool RefactoringActionExtractFunction::performChange() {
// Check if the new name is ok.
if (!Lexer::isIdentifier(PreferredName)) {
DiagEngine.diagnose(SourceLoc(), diag::invalid_name, PreferredName);
return true;
}
DeclContext *DC = RangeInfo.RangeContext;
DeclContext *InsertToDC = nullptr;
SourceLoc InsertLoc = getNewFuncInsertLoc(DC, InsertToDC);
// Complain about no inserting position.
if (InsertLoc.isInvalid()) {
DiagEngine.diagnose(SourceLoc(), diag::no_insert_position);
return true;
}
// Correct the given name if collision happens.
PreferredName = correctNewDeclName(InsertToDC, PreferredName);
// Collect the paramters to pass down to the new function.
std::vector<ReferencedDecl> Parameters;
for (auto &RD: RangeInfo.ReferencedDecls) {
// If the referenced decl is declared elsewhere, no need to pass as parameter
if (RD.VD->getDeclContext() != DC)
continue;
// We don't need to pass down implicitly declared variables, e.g. error in
// a catch block.
if (RD.VD->isImplicit()) {
SourceLoc Loc = RD.VD->getStartLoc();
if (Loc.isValid() &&
SM.isBeforeInBuffer(RangeInfo.ContentRange.getStart(), Loc) &&
SM.isBeforeInBuffer(Loc, RangeInfo.ContentRange.getEnd()))
continue;
}
// If the referenced decl is declared inside the range, no need to pass
// as parameter.
if (RangeInfo.DeclaredDecls.end() !=
std::find_if(RangeInfo.DeclaredDecls.begin(), RangeInfo.DeclaredDecls.end(),
[RD](DeclaredDecl DD) { return RD.VD == DD.VD; }))
continue;
// We don't need to pass down self.
if (auto PD = dyn_cast<ParamDecl>(RD.VD)) {
if (PD->isSelfParameter()) {
continue;
}
}
Parameters.emplace_back(RD.VD, sanitizeType(RD.Ty));
}
SmallString<64> Buffer;
unsigned FuncBegin = Buffer.size();
unsigned FuncNameOffset;
{
llvm::raw_svector_ostream OS(Buffer);
if (!InsertToDC->isLocalContext()) {
// Default to be file private.
OS << tok::kw_fileprivate << " ";
}
// Inherit static if the containing function is.
if (DC->getContextKind() == DeclContextKind::AbstractFunctionDecl) {
if (auto FD = dyn_cast<FuncDecl>(static_cast<AbstractFunctionDecl*>(DC))) {
if (FD->isStatic()) {
OS << tok::kw_static << " ";
}
}
}
OS << tok::kw_func << " ";
FuncNameOffset = Buffer.size() - FuncBegin;
OS << PreferredName;
OS << "(";
for (auto &RD : Parameters) {
OS << "_ " << RD.VD->getBaseName().userFacingName() << ": ";
RD.Ty->reconstituteSugar(/*Recursive*/true)->print(OS);
if (&RD != &Parameters.back())
OS << ", ";
}
OS << ")";
if (RangeInfo.ThrowingUnhandledError)
OS << " " << tok::kw_throws;
bool InsertedReturnType = false;
if (auto Ty = RangeInfo.getType()) {
// If the type of the range is not void, specify the return type.
if (!Ty->isVoid()) {
OS << " " << tok::arrow << " ";
sanitizeType(Ty)->reconstituteSugar(/*Recursive*/true)->print(OS);
InsertedReturnType = true;
}
}
OS << " {\n";
// Add "return" if the extracted entity is an expression.
if (RangeInfo.Kind == RangeKind::SingleExpression && InsertedReturnType)
OS << tok::kw_return << " ";
OS << RangeInfo.ContentRange.str() << "\n}\n\n";
}
unsigned FuncEnd = Buffer.size();
unsigned ReplaceBegin = Buffer.size();
unsigned CallNameOffset;
{
llvm::raw_svector_ostream OS(Buffer);
if (RangeInfo.exit() == ExitState::Positive)
OS << tok::kw_return <<" ";
CallNameOffset = Buffer.size() - ReplaceBegin;
OS << PreferredName << "(";
for (auto &RD : Parameters) {
// Inout argument needs "&".
if (RD.Ty->is<InOutType>())
OS << "&";
OS << RD.VD->getBaseName().userFacingName();
if (&RD != &Parameters.back())
OS << ", ";
}
OS << ")";
}
unsigned ReplaceEnd = Buffer.size();
std::string ExtractedFuncName = PreferredName.str() + "(";
for (size_t i = 0; i < Parameters.size(); ++i) {
ExtractedFuncName += "_:";
}
ExtractedFuncName += ")";
StringRef DeclStr(Buffer.begin() + FuncBegin, FuncEnd - FuncBegin);
auto NotableFuncRegions = getNotableRegions(DeclStr, FuncNameOffset,
ExtractedFuncName,
/*IsFunctionLike=*/true);
StringRef CallStr(Buffer.begin() + ReplaceBegin, ReplaceEnd - ReplaceBegin);
auto NotableCallRegions = getNotableRegions(CallStr, CallNameOffset,
ExtractedFuncName,
/*IsFunctionLike=*/true);
// Insert the new function's declaration.
EditConsumer.accept(SM, InsertLoc, DeclStr, NotableFuncRegions);
// Replace the code to extract with the function call.
EditConsumer.accept(SM, RangeInfo.ContentRange, CallStr, NotableCallRegions);
return false;
}
class RefactoringActionExtractExprBase {
SourceFile *TheFile;
ResolvedRangeInfo RangeInfo;
DiagnosticEngine &DiagEngine;
const bool ExtractRepeated;
StringRef PreferredName;
SourceEditConsumer &EditConsumer;
ASTContext &Ctx;
SourceManager &SM;
public:
RefactoringActionExtractExprBase(SourceFile *TheFile,
ResolvedRangeInfo RangeInfo,
DiagnosticEngine &DiagEngine,
bool ExtractRepeated,
StringRef PreferredName,
SourceEditConsumer &EditConsumer) :
TheFile(TheFile), RangeInfo(RangeInfo), DiagEngine(DiagEngine),
ExtractRepeated(ExtractRepeated), PreferredName(PreferredName),
EditConsumer(EditConsumer), Ctx(TheFile->getASTContext()),
SM(Ctx.SourceMgr){}
bool performChange();
};
/// This is to ensure all decl references in two expressions are identical.
struct ReferenceCollector: public SourceEntityWalker {
llvm::SmallVector<ValueDecl*, 4> References;
ReferenceCollector(Expr *E) { walk(E); }
bool visitDeclReference(ValueDecl *D, CharSourceRange Range,
TypeDecl *CtorTyRef, ExtensionDecl *ExtTyRef,
Type T, ReferenceMetaData Data) override {
References.emplace_back(D);
return true;
}
bool operator==(const ReferenceCollector &Other) const {
if (References.size() != Other.References.size())
return false;
return std::equal(References.begin(), References.end(),
Other.References.begin());
}
};
struct SimilarExprCollector: public SourceEntityWalker {
SourceManager &SM;
/// The expression under selection.
Expr *SelectedExpr;
llvm::ArrayRef<Token> AllTokens;
llvm::SetVector<Expr*> &Bucket;
/// The tokens included in the expression under selection.
llvm::ArrayRef<Token> SelectedTokens;
/// The referenced decls in the expression under selection.
ReferenceCollector SelectedReferences;
bool compareTokenContent(ArrayRef<Token> Left, ArrayRef<Token> Right) {
if (Left.size() != Right.size())
return false;
return std::equal(Left.begin(), Left.end(), Right.begin(),
[](const Token &L, const Token& R) {
return L.getText() == R.getText();
});
}
/// Find all tokens included by an expression.
llvm::ArrayRef<Token> getExprSlice(Expr *E) {
return slice_token_array(AllTokens, E->getStartLoc(), E->getEndLoc());
}
SimilarExprCollector(SourceManager &SM, Expr* SelectedExpr,
llvm::ArrayRef<Token> AllTokens,
llvm::SetVector<Expr*> &Bucket): SM(SM), SelectedExpr(SelectedExpr),
AllTokens(AllTokens), Bucket(Bucket),
SelectedTokens(getExprSlice(SelectedExpr)),
SelectedReferences(SelectedExpr){}
bool walkToExprPre(Expr *E) override {
// We don't extract implicit expressions.
if (E->isImplicit())
return true;
if (E->getKind() != SelectedExpr->getKind())
return true;
// First check the underlying token arrays have the same content.
if (compareTokenContent(getExprSlice(E), SelectedTokens)) {
ReferenceCollector CurrentReferences(E);
// Next, check the referenced decls are same.
if (CurrentReferences == SelectedReferences)
Bucket.insert(E);
}
return true;
}
};
bool RefactoringActionExtractExprBase::performChange() {
// Check if the new name is ok.
if (!Lexer::isIdentifier(PreferredName)) {
DiagEngine.diagnose(SourceLoc(), diag::invalid_name, PreferredName);
return true;
}
// Find the enclosing brace statement;
ContextFinder Finder(*TheFile, RangeInfo.ContainedNodes.front(),
[](ASTNode N) { return N.isStmt(StmtKind::Brace); });
auto *SelectedExpr = RangeInfo.ContainedNodes[0].get<Expr*>();
Finder.resolve();
SourceLoc InsertLoc;
llvm::SetVector<ValueDecl*> AllVisibleDecls;
struct DeclCollector: public SourceEntityWalker {
llvm::SetVector<ValueDecl*> &Bucket;
DeclCollector(llvm::SetVector<ValueDecl*> &Bucket): Bucket(Bucket) {}
bool walkToDeclPre(Decl *D, CharSourceRange Range) override {
if (auto *VD = dyn_cast<ValueDecl>(D))
Bucket.insert(VD);
return true;
}
} Collector(AllVisibleDecls);
llvm::SetVector<Expr*> AllExpressions;
if (!Finder.getContexts().empty()) {
// Get the innermost brace statement.
auto BS = static_cast<BraceStmt*>(Finder.getContexts().back().get<Stmt*>());
// Collect all value decls inside the brace statement.
Collector.walk(BS);
if (ExtractRepeated) {
// Collect all expressions we are going to extract.
SimilarExprCollector(SM, SelectedExpr,
slice_token_array(TheFile->getAllTokens(),
BS->getStartLoc(),
BS->getEndLoc()),
AllExpressions).walk(BS);
} else {
AllExpressions.insert(SelectedExpr);
}
assert(!AllExpressions.empty() && "at least one expression is extracted.");
for (auto Ele : BS->getElements()) {
// Find the element that encloses the first expression under extraction.
if (SM.rangeContains(Ele.getSourceRange(),
(*AllExpressions.begin())->getSourceRange())) {
// Insert before the enclosing element.
InsertLoc = Ele.getStartLoc();
}
}
}
// Complain about no inserting position.
if (InsertLoc.isInvalid()) {
DiagEngine.diagnose(SourceLoc(), diag::no_insert_position);
return true;
}
// Correct name if collision happens.
PreferredName = correctNameInternal(TheFile->getASTContext(), PreferredName,
AllVisibleDecls.getArrayRef());
// Print the type name of this expression.
llvm::SmallString<16> TyBuffer;
// We are not sure about the type of repeated expressions.
if (!ExtractRepeated) {
if (auto Ty = RangeInfo.getType()) {
llvm::raw_svector_ostream OS(TyBuffer);
OS << ": ";
Ty->getRValueType()->reconstituteSugar(true)->print(OS);
}
}
llvm::SmallString<64> DeclBuffer;
llvm::raw_svector_ostream OS(DeclBuffer);
unsigned StartOffset, EndOffset;
OS << tok::kw_let << " ";
StartOffset = DeclBuffer.size();
OS << PreferredName;
EndOffset = DeclBuffer.size();
OS << TyBuffer.str() << " = " << RangeInfo.ContentRange.str() << "\n";
NoteRegion DeclNameRegion{
RefactoringRangeKind::BaseName,
/*StartLine=*/1, /*StartColumn=*/StartOffset + 1,
/*EndLine=*/1, /*EndColumn=*/EndOffset + 1,
/*ArgIndex*/None
};
// Perform code change.
EditConsumer.accept(SM, InsertLoc, DeclBuffer.str(), {DeclNameRegion});
// Replace all occurrences of the extracted expression.
for (auto *E : AllExpressions) {
EditConsumer.accept(SM,
Lexer::getCharSourceRangeFromSourceRange(SM, E->getSourceRange()),
PreferredName,
{{
RefactoringRangeKind::BaseName,
/*StartLine=*/1, /*StartColumn-*/1, /*EndLine=*/1,
/*EndColumn=*/static_cast<unsigned int>(PreferredName.size() + 1),
/*ArgIndex*/None
}});
}
return false;
}
bool RefactoringActionExtractExpr::
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
switch (Info.Kind) {
case RangeKind::SingleExpression:
// We disallow extract literal expression for two reasons:
// (1) since we print the type for extracted expression, the type of a
// literal may print as "int2048" where it is not typically users' choice;
// (2) Extracting one literal provides little value for users.
return checkExtractConditions(Info, Diag).success();
case RangeKind::PartOfExpression:
case RangeKind::SingleDecl:
case RangeKind::MultiTypeMemberDecl:
case RangeKind::SingleStatement:
case RangeKind::MultiStatement:
case RangeKind::Invalid:
return false;
}
llvm_unreachable("unhandled kind");
}
bool RefactoringActionExtractExpr::performChange() {
return RefactoringActionExtractExprBase(TheFile, RangeInfo,
DiagEngine, false, PreferredName,
EditConsumer).performChange();
}
bool RefactoringActionExtractRepeatedExpr::
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
switch (Info.Kind) {
case RangeKind::SingleExpression:
return checkExtractConditions(Info, Diag).
success({CannotExtractReason::Literal});
case RangeKind::PartOfExpression:
case RangeKind::SingleDecl:
case RangeKind::MultiTypeMemberDecl:
case RangeKind::SingleStatement:
case RangeKind::MultiStatement:
case RangeKind::Invalid:
return false;
}
llvm_unreachable("unhandled kind");
}
bool RefactoringActionExtractRepeatedExpr::performChange() {
return RefactoringActionExtractExprBase(TheFile, RangeInfo,
DiagEngine, true, PreferredName,
EditConsumer).performChange();
}
bool RefactoringActionMoveMembersToExtension::isApplicable(
ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
switch (Info.Kind) {
case RangeKind::SingleDecl:
case RangeKind::MultiTypeMemberDecl: {
DeclContext *DC = Info.RangeContext;
// The the common decl context is not a nomial type, we cannot create an
// extension for it
if (!DC || !DC->getInnermostDeclarationDeclContext() ||
!isa<NominalTypeDecl>(DC->getInnermostDeclarationDeclContext()))
return false;
// Members of types not declared at top file level cannot be extracted
// to an extension at top file level
if (DC->getParent()->getContextKind() != DeclContextKind::FileUnit)
return false;
// Check if contained nodes are all allowed decls.
for (auto Node : Info.ContainedNodes) {
Decl *D = Node.dyn_cast<Decl*>();
if (!D)
return false;
if (isa<AccessorDecl>(D) || isa<DestructorDecl>(D) ||
isa<EnumCaseDecl>(D) || isa<EnumElementDecl>(D))
return false;
}
// We should not move instance variables with storage into the extension
// because they are not allowed to be declared there
for (auto DD : Info.DeclaredDecls) {
if (auto ASD = dyn_cast<AbstractStorageDecl>(DD.VD)) {
// Only disallow storages in the common decl context, allow them in
// any subtypes
if (ASD->hasStorage() && ASD->getDeclContext() == DC) {
return false;
}
}
}
return true;
}
case RangeKind::SingleExpression:
case RangeKind::PartOfExpression:
case RangeKind::SingleStatement:
case RangeKind::MultiStatement:
case RangeKind::Invalid:
return false;
}
llvm_unreachable("unhandled kind");
}
bool RefactoringActionMoveMembersToExtension::performChange() {
DeclContext *DC = RangeInfo.RangeContext;
auto CommonTypeDecl =
dyn_cast<NominalTypeDecl>(DC->getInnermostDeclarationDeclContext());
assert(CommonTypeDecl && "Not applicable if common parent is no nomial type");
SmallString<64> Buffer;
llvm::raw_svector_ostream OS(Buffer);
OS << "\n\n";
OS << "extension " << CommonTypeDecl->getName() << " {\n";
OS << RangeInfo.ContentRange.str().trim();
OS << "\n}";
// Insert extension after the type declaration
EditConsumer.insertAfter(SM, CommonTypeDecl->getEndLoc(), Buffer);
EditConsumer.remove(SM, RangeInfo.ContentRange);
return false;
}
namespace {
// A SingleDecl range may not include all decls actually declared in that range:
// a var decl has accessors that aren't included. This will find those missing
// decls.
class FindAllSubDecls : public SourceEntityWalker {
llvm::SmallPtrSetImpl<Decl *> &Found;
public:
FindAllSubDecls(llvm::SmallPtrSetImpl<Decl *> &found)
: Found(found) {}
bool walkToDeclPre(Decl *D, CharSourceRange range) override {
// Record this Decl, and skip its contents if we've already touched it.
if (!Found.insert(D).second)
return false;
if (auto ASD = dyn_cast<AbstractStorageDecl>(D)) {
ASD->visitParsedAccessors([&](AccessorDecl *accessor) {
Found.insert(accessor);
});
}
return true;
}
};
}
bool RefactoringActionReplaceBodiesWithFatalError::isApplicable(
ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
switch (Info.Kind) {
case RangeKind::SingleDecl:
case RangeKind::MultiTypeMemberDecl: {
llvm::SmallPtrSet<Decl *, 16> Found;
for (auto decl : Info.DeclaredDecls) {
FindAllSubDecls(Found).walk(decl.VD);
}
for (auto decl : Found) {
auto AFD = dyn_cast<AbstractFunctionDecl>(decl);
if (AFD && !AFD->isImplicit())
return true;
}
return false;
}
case RangeKind::SingleExpression:
case RangeKind::PartOfExpression:
case RangeKind::SingleStatement:
case RangeKind::MultiStatement:
case RangeKind::Invalid:
return false;
}
llvm_unreachable("unhandled kind");
}
bool RefactoringActionReplaceBodiesWithFatalError::performChange() {
const StringRef replacement = "{\nfatalError()\n}";
llvm::SmallPtrSet<Decl *, 16> Found;
for (auto decl : RangeInfo.DeclaredDecls) {
FindAllSubDecls(Found).walk(decl.VD);
}
for (auto decl : Found) {
auto AFD = dyn_cast<AbstractFunctionDecl>(decl);
if (!AFD || AFD->isImplicit())
continue;
auto range = AFD->getBodySourceRange();
// If we're in replacement mode (i.e. have an edit consumer), we can
// rewrite the function body.
auto charRange = Lexer::getCharSourceRangeFromSourceRange(SM, range);
EditConsumer.accept(SM, charRange, replacement);
}
return false;
}
static std::pair<IfStmt *, IfStmt *>
findCollapseNestedIfTarget(ResolvedCursorInfo CursorInfo) {
if (CursorInfo.Kind != CursorInfoKind::StmtStart)
return {};
// Ensure the statement is 'if' statement. It must not have 'else' clause.
IfStmt *OuterIf = dyn_cast<IfStmt>(CursorInfo.TrailingStmt);
if (!OuterIf)
return {};
if (OuterIf->getElseStmt())
return {};
// The body must contain a sole inner 'if' statement.
auto Body = dyn_cast_or_null<BraceStmt>(OuterIf->getThenStmt());
if (!Body || Body->getNumElements() != 1)
return {};
IfStmt *InnerIf =
dyn_cast_or_null<IfStmt>(Body->getFirstElement().dyn_cast<Stmt *>());
if (!InnerIf)
return {};
// Inner 'if' statement also cannot have 'else' clause.
if (InnerIf->getElseStmt())
return {};
return {OuterIf, InnerIf};
}
bool RefactoringActionCollapseNestedIfStmt::
isApplicable(ResolvedCursorInfo CursorInfo, DiagnosticEngine &Diag) {
return findCollapseNestedIfTarget(CursorInfo).first;
}
bool RefactoringActionCollapseNestedIfStmt::performChange() {
auto Target = findCollapseNestedIfTarget(CursorInfo);
if (!Target.first)
return true;
auto OuterIf = Target.first;
auto InnerIf = Target.second;
EditorConsumerInsertStream OS(
EditConsumer, SM,
Lexer::getCharSourceRangeFromSourceRange(SM, OuterIf->getSourceRange()));
OS << tok::kw_if << " ";
// Emit conditions.
bool first = true;
for (auto &C : llvm::concat<StmtConditionElement>(OuterIf->getCond(),
InnerIf->getCond())) {
if (first)
first = false;
else
OS << ", ";
OS << Lexer::getCharSourceRangeFromSourceRange(SM, C.getSourceRange())
.str();
}
// Emit body.
OS << " ";
OS << Lexer::getCharSourceRangeFromSourceRange(
SM, InnerIf->getThenStmt()->getSourceRange())
.str();
return false;
}
static std::unique_ptr<llvm::SetVector<Expr*>>
findConcatenatedExpressions(ResolvedRangeInfo Info, ASTContext &Ctx) {
Expr *E = nullptr;
switch (Info.Kind) {
case RangeKind::SingleExpression:
E = Info.ContainedNodes[0].get<Expr*>();
break;
case RangeKind::PartOfExpression:
E = Info.CommonExprParent;
break;
default:
return nullptr;
}
assert(E);
struct StringInterpolationExprFinder: public SourceEntityWalker {
std::unique_ptr<llvm::SetVector<Expr *>> Bucket =
std::make_unique<llvm::SetVector<Expr *>>();
ASTContext &Ctx;
bool IsValidInterpolation = true;
StringInterpolationExprFinder(ASTContext &Ctx): Ctx(Ctx) {}
bool isConcatenationExpr(DeclRefExpr* Expr) {
if (!Expr)
return false;
auto *FD = dyn_cast<FuncDecl>(Expr->getDecl());
if (FD == nullptr || (FD != Ctx.getPlusFunctionOnString() &&
FD != Ctx.getPlusFunctionOnRangeReplaceableCollection())) {
return false;
}
return true;
}
bool walkToExprPre(Expr *E) override {
if (E->isImplicit())
return true;
// FIXME: we should have ErrorType instead of null.
if (E->getType().isNull())
return true;
auto ExprType = E->getType()->getNominalOrBoundGenericNominal();
//Only binary concatenation operators should exist in expression
if (E->getKind() == ExprKind::Binary) {
auto *BE = dyn_cast<BinaryExpr>(E);
auto *OperatorDeclRef = BE->getSemanticFn()->getMemberOperatorRef();
if (!(isConcatenationExpr(OperatorDeclRef)
&& ExprType == Ctx.getStringDecl())) {
IsValidInterpolation = false;
return false;
}
return true;
}
// Everything that evaluates to string should be gathered.
if (ExprType == Ctx.getStringDecl()) {
Bucket->insert(E);
return false;
}
if (auto *DR = dyn_cast<DeclRefExpr>(E)) {
// Checks whether all function references in expression are concatenations.
auto *FD = dyn_cast<FuncDecl>(DR->getDecl());
auto IsConcatenation = isConcatenationExpr(DR);
if (FD && IsConcatenation) {
return false;
}
}
// There was non-expected expression, it's not valid interpolation then.
IsValidInterpolation = false;
return false;
}
} Walker(Ctx);
Walker.walk(E);
// There should be two or more expressions to convert.
if (!Walker.IsValidInterpolation || Walker.Bucket->size() < 2)
return nullptr;
return std::move(Walker.Bucket);
}
static void interpolatedExpressionForm(Expr *E, SourceManager &SM,
llvm::raw_ostream &OS) {
if (auto *Literal = dyn_cast<StringLiteralExpr>(E)) {
OS << Literal->getValue();
return;
}
auto ExpStr = Lexer::getCharSourceRangeFromSourceRange(SM,
E->getSourceRange()).str().str();
if (isa<InterpolatedStringLiteralExpr>(E)) {
ExpStr.erase(0, 1);
ExpStr.pop_back();
OS << ExpStr;
return;
}
OS << "\\(" << ExpStr << ")";
}
bool RefactoringActionConvertStringsConcatenationToInterpolation::
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
auto RangeContext = Info.RangeContext;
if (RangeContext) {
auto &Ctx = Info.RangeContext->getASTContext();
return findConcatenatedExpressions(Info, Ctx) != nullptr;
}
return false;
}
bool RefactoringActionConvertStringsConcatenationToInterpolation::performChange() {
auto Expressions = findConcatenatedExpressions(RangeInfo, Ctx);
if (!Expressions)
return true;
EditorConsumerInsertStream OS(EditConsumer, SM, RangeInfo.ContentRange);
OS << "\"";
for (auto It = Expressions->begin(); It != Expressions->end(); ++It) {
interpolatedExpressionForm(*It, SM, OS);
}
OS << "\"";
return false;
}
/// Abstract helper class containing info about an IfExpr
/// that can be expanded into an IfStmt.
class ExpandableTernaryExprInfo {
public:
virtual ~ExpandableTernaryExprInfo() {}
virtual IfExpr *getIf() = 0;
virtual SourceRange getNameRange() = 0;
virtual Type getType() = 0;
virtual bool shouldDeclareNameAndType() {
return !getType().isNull();
}
virtual bool isValid() {
//Ensure all public properties are non-nil and valid
if (!getIf() || !getNameRange().isValid())
return false;
if (shouldDeclareNameAndType() && getType().isNull())
return false;
return true; //valid
}
CharSourceRange getNameCharRange(const SourceManager &SM) {
return Lexer::getCharSourceRangeFromSourceRange(SM, getNameRange());
}
};
/// Concrete subclass containing info about an AssignExpr
/// where the source is the expandable IfExpr.
class ExpandableAssignTernaryExprInfo: public ExpandableTernaryExprInfo {
public:
ExpandableAssignTernaryExprInfo(AssignExpr *Assign): Assign(Assign) {}
IfExpr *getIf() override {
if (!Assign)
return nullptr;
return dyn_cast_or_null<IfExpr>(Assign->getSrc());
}
SourceRange getNameRange() override {
auto Invalid = SourceRange();
if (!Assign)
return Invalid;
if (auto dest = Assign->getDest())
return dest->getSourceRange();
return Invalid;
}
Type getType() override {
return nullptr;
}
private:
AssignExpr *Assign = nullptr;
};
/// Concrete subclass containing info about a PatternBindingDecl
/// where the pattern initializer is the expandable IfExpr.
class ExpandableBindingTernaryExprInfo: public ExpandableTernaryExprInfo {
public:
ExpandableBindingTernaryExprInfo(PatternBindingDecl *Binding):
Binding(Binding) {}
IfExpr *getIf() override {
if (Binding && Binding->getNumPatternEntries() == 1) {
if (auto *Init = Binding->getInit(0)) {
return dyn_cast<IfExpr>(Init);
}
}
return nullptr;
}
SourceRange getNameRange() override {
if (auto Pattern = getNamePattern())
return Pattern->getSourceRange();
return SourceRange();
}
Type getType() override {
if (auto Pattern = getNamePattern())
return Pattern->getType();
return nullptr;
}
private:
Pattern *getNamePattern() {
if (!Binding || Binding->getNumPatternEntries() != 1)
return nullptr;
auto Pattern = Binding->getPattern(0);
if (!Pattern)
return nullptr;
if (auto TyPattern = dyn_cast<TypedPattern>(Pattern))
Pattern = TyPattern->getSubPattern();
return Pattern;
}
PatternBindingDecl *Binding = nullptr;
};
std::unique_ptr<ExpandableTernaryExprInfo>
findExpandableTernaryExpression(ResolvedRangeInfo Info) {
if (Info.Kind != RangeKind::SingleDecl
&& Info.Kind != RangeKind:: SingleExpression)
return nullptr;
if (Info.ContainedNodes.size() != 1)
return nullptr;
if (auto D = Info.ContainedNodes[0].dyn_cast<Decl*>())
if (auto Binding = dyn_cast<PatternBindingDecl>(D))
return std::make_unique<ExpandableBindingTernaryExprInfo>(Binding);
if (auto E = Info.ContainedNodes[0].dyn_cast<Expr*>())
if (auto Assign = dyn_cast<AssignExpr>(E))
return std::make_unique<ExpandableAssignTernaryExprInfo>(Assign);
return nullptr;
}
bool RefactoringActionExpandTernaryExpr::
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
auto Target = findExpandableTernaryExpression(Info);
return Target && Target->isValid();
}
bool RefactoringActionExpandTernaryExpr::performChange() {
auto Target = findExpandableTernaryExpression(RangeInfo);
if (!Target || !Target->isValid())
return true; //abort
auto NameCharRange = Target->getNameCharRange(SM);
auto IfRange = Target->getIf()->getSourceRange();
auto IfCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, IfRange);
auto CondRange = Target->getIf()->getCondExpr()->getSourceRange();
auto CondCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, CondRange);
auto ThenRange = Target->getIf()->getThenExpr()->getSourceRange();
auto ThenCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ThenRange);
auto ElseRange = Target->getIf()->getElseExpr()->getSourceRange();
auto ElseCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ElseRange);
llvm::SmallString<64> DeclBuffer;
llvm::raw_svector_ostream OS(DeclBuffer);
llvm::StringRef Space = " ";
llvm::StringRef NewLine = "\n";
if (Target->shouldDeclareNameAndType()) {
//Specifier will not be replaced; append after specifier
OS << NameCharRange.str() << tok::colon << Space;
OS << Target->getType() << NewLine;
}
OS << tok::kw_if << Space;
OS << CondCharRange.str() << Space;
OS << tok::l_brace << NewLine;
OS << NameCharRange.str() << Space;
OS << tok::equal << Space;
OS << ThenCharRange.str() << NewLine;
OS << tok::r_brace << Space;
OS << tok::kw_else << Space;
OS << tok::l_brace << NewLine;
OS << NameCharRange.str() << Space;
OS << tok::equal << Space;
OS << ElseCharRange.str() << NewLine;
OS << tok::r_brace;
//Start replacement with name range, skip the specifier
auto ReplaceRange(NameCharRange);
ReplaceRange.widen(IfCharRange);
EditConsumer.accept(SM, ReplaceRange, DeclBuffer.str());
return false; //don't abort
}
bool RefactoringActionConvertIfLetExprToGuardExpr::
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
if (Info.Kind != RangeKind::SingleStatement
&& Info.Kind != RangeKind::MultiStatement)
return false;
if (Info.ContainedNodes.empty())
return false;
IfStmt *If = nullptr;
if (Info.ContainedNodes.size() == 1) {
if (auto S = Info.ContainedNodes[0].dyn_cast<Stmt*>()) {
If = dyn_cast<IfStmt>(S);
}
}
if (!If)
return false;
auto CondList = If->getCond();
if (CondList.size() == 1) {
auto E = CondList[0];
auto P = E.getKind();
if (P == swift::StmtConditionElement::CK_PatternBinding) {
auto Body = dyn_cast_or_null<BraceStmt>(If->getThenStmt());
if (Body)
return true;
}
}
return false;
}
bool RefactoringActionConvertIfLetExprToGuardExpr::performChange() {
auto S = RangeInfo.ContainedNodes[0].dyn_cast<Stmt*>();
IfStmt *If = dyn_cast<IfStmt>(S);
auto CondList = If->getCond();
// Get if-let condition
SourceRange range = CondList[0].getSourceRange();
SourceManager &SM = RangeInfo.RangeContext->getASTContext().SourceMgr;
auto CondCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, range);
auto Body = dyn_cast_or_null<BraceStmt>(If->getThenStmt());
// Get if-let then body.
auto firstElement = Body->getFirstElement();
auto lastElement = Body->getLastElement();
SourceRange bodyRange = firstElement.getSourceRange();
bodyRange.widen(lastElement.getSourceRange());
auto BodyCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, bodyRange);
llvm::SmallString<64> DeclBuffer;
llvm::raw_svector_ostream OS(DeclBuffer);
llvm::StringRef Space = " ";
llvm::StringRef NewLine = "\n";
OS << tok::kw_guard << Space;
OS << CondCharRange.str().str() << Space;
OS << tok::kw_else << Space;
OS << tok::l_brace << NewLine;
// Get if-let else body.
if (auto *ElseBody = dyn_cast_or_null<BraceStmt>(If->getElseStmt())) {
auto firstElseElement = ElseBody->getFirstElement();
auto lastElseElement = ElseBody->getLastElement();
SourceRange elseBodyRange = firstElseElement.getSourceRange();
elseBodyRange.widen(lastElseElement.getSourceRange());
auto ElseBodyCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, elseBodyRange);
OS << ElseBodyCharRange.str().str() << NewLine;
}
OS << tok::kw_return << NewLine;
OS << tok::r_brace << NewLine;
OS << BodyCharRange.str().str();
// Replace if-let to guard
auto ReplaceRange = RangeInfo.ContentRange;
EditConsumer.accept(SM, ReplaceRange, DeclBuffer.str());
return false;
}
bool RefactoringActionConvertGuardExprToIfLetExpr::
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
if (Info.Kind != RangeKind::SingleStatement
&& Info.Kind != RangeKind::MultiStatement)
return false;
if (Info.ContainedNodes.empty())
return false;
GuardStmt *guardStmt = nullptr;
if (Info.ContainedNodes.size() > 0) {
if (auto S = Info.ContainedNodes[0].dyn_cast<Stmt*>()) {
guardStmt = dyn_cast<GuardStmt>(S);
}
}
if (!guardStmt)
return false;
auto CondList = guardStmt->getCond();
if (CondList.size() == 1) {
auto E = CondList[0];
auto P = E.getPatternOrNull();
if (P && E.getKind() == swift::StmtConditionElement::CK_PatternBinding)
return true;
}
return false;
}
bool RefactoringActionConvertGuardExprToIfLetExpr::performChange() {
// Get guard stmt
auto S = RangeInfo.ContainedNodes[0].dyn_cast<Stmt*>();
GuardStmt *Guard = dyn_cast<GuardStmt>(S);
// Get guard condition
auto CondList = Guard->getCond();
// Get guard condition source
SourceRange range = CondList[0].getSourceRange();
SourceManager &SM = RangeInfo.RangeContext->getASTContext().SourceMgr;
auto CondCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, range);
llvm::SmallString<64> DeclBuffer;
llvm::raw_svector_ostream OS(DeclBuffer);
llvm::StringRef Space = " ";
llvm::StringRef NewLine = "\n";
OS << tok::kw_if << Space;
OS << CondCharRange.str().str() << Space;
OS << tok::l_brace << NewLine;
// Get nodes after guard to place them at if-let body
if (RangeInfo.ContainedNodes.size() > 1) {
auto S = RangeInfo.ContainedNodes[1].getSourceRange();
S.widen(RangeInfo.ContainedNodes.back().getSourceRange());
auto BodyCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, S);
OS << BodyCharRange.str().str() << NewLine;
}
OS << tok::r_brace;
// Get guard body
auto Body = dyn_cast_or_null<BraceStmt>(Guard->getBody());
if (Body && Body->getNumElements() > 1) {
auto firstElement = Body->getFirstElement();
auto lastElement = Body->getLastElement();
SourceRange bodyRange = firstElement.getSourceRange();
bodyRange.widen(lastElement.getSourceRange());
auto BodyCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, bodyRange);
OS << Space << tok::kw_else << Space << tok::l_brace << NewLine;
OS << BodyCharRange.str().str() << NewLine;
OS << tok::r_brace;
}
// Replace guard to if-let
auto ReplaceRange = RangeInfo.ContentRange;
EditConsumer.accept(SM, ReplaceRange, DeclBuffer.str());
return false;
}
bool RefactoringActionConvertToSwitchStmt::
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
class ConditionalChecker : public ASTWalker {
public:
bool ParamsUseSameVars = true;
bool ConditionUseOnlyAllowedFunctions = false;
StringRef ExpectName;
Expr *walkToExprPost(Expr *E) override {
if (E->getKind() != ExprKind::DeclRef)
return E;
auto D = dyn_cast<DeclRefExpr>(E)->getDecl();
if (D->getKind() == DeclKind::Var || D->getKind() == DeclKind::Param)
ParamsUseSameVars = checkName(dyn_cast<VarDecl>(D));
if (D->getKind() == DeclKind::Func)
ConditionUseOnlyAllowedFunctions = checkName(dyn_cast<FuncDecl>(D));
if (allCheckPassed())
return E;
return nullptr;
}
bool allCheckPassed() {
return ParamsUseSameVars && ConditionUseOnlyAllowedFunctions;
}
private:
bool checkName(VarDecl *VD) {
auto Name = VD->getName().str();
if (ExpectName.empty())
ExpectName = Name;
return Name == ExpectName;
}
bool checkName(FuncDecl *FD) {
const auto Name = FD->getBaseIdentifier().str();
return Name == "~="
|| Name == "=="
|| Name == "__derived_enum_equals"
|| Name == "__derived_struct_equals"
|| Name == "||"
|| Name == "...";
}
};
class SwitchConvertable {
public:
SwitchConvertable(ResolvedRangeInfo Info) {
this->Info = Info;
}
bool isApplicable() {
if (Info.Kind != RangeKind::SingleStatement)
return false;
if (!findIfStmt())
return false;
return checkEachCondition();
}
private:
ResolvedRangeInfo Info;
IfStmt *If = nullptr;
ConditionalChecker checker;
bool findIfStmt() {
if (Info.ContainedNodes.size() != 1)
return false;
if (auto S = Info.ContainedNodes.front().dyn_cast<Stmt*>())
If = dyn_cast<IfStmt>(S);
return If != nullptr;
}
bool checkEachCondition() {
checker = ConditionalChecker();
do {
if (!checkEachElement())
return false;
} while ((If = dyn_cast_or_null<IfStmt>(If->getElseStmt())));
return true;
}
bool checkEachElement() {
bool result = true;
auto ConditionalList = If->getCond();
for (auto Element : ConditionalList) {
result &= check(Element);
}
return result;
}
bool check(StmtConditionElement ConditionElement) {
if (ConditionElement.getKind() == StmtConditionElement::CK_Availability)
return false;
if (ConditionElement.getKind() == StmtConditionElement::CK_PatternBinding)
checker.ConditionUseOnlyAllowedFunctions = true;
ConditionElement.walk(checker);
return checker.allCheckPassed();
}
};
return SwitchConvertable(Info).isApplicable();
}
bool RefactoringActionConvertToSwitchStmt::performChange() {
class VarNameFinder : public ASTWalker {
public:
std::string VarName;
Expr *walkToExprPost(Expr *E) override {
if (E->getKind() != ExprKind::DeclRef)
return E;
auto D = dyn_cast<DeclRefExpr>(E)->getDecl();
if (D->getKind() != DeclKind::Var && D->getKind() != DeclKind::Param)
return E;
VarName = dyn_cast<VarDecl>(D)->getName().str().str();
return nullptr;
}
};
class ConditionalPatternFinder : public ASTWalker {
public:
ConditionalPatternFinder(SourceManager &SM) : SM(SM) {}
SmallString<64> ConditionalPattern = SmallString<64>();
Expr *walkToExprPost(Expr *E) override {
if (E->getKind() != ExprKind::Binary)
return E;
auto BE = dyn_cast<BinaryExpr>(E);
if (isFunctionNameAllowed(BE))
appendPattern(dyn_cast<BinaryExpr>(E)->getArg());
return E;
}
std::pair<bool, Pattern*> walkToPatternPre(Pattern *P) override {
ConditionalPattern.append(Lexer::getCharSourceRangeFromSourceRange(SM, P->getSourceRange()).str());
if (P->getKind() == PatternKind::OptionalSome)
ConditionalPattern.append("?");
return { true, nullptr };
}
private:
SourceManager &SM;
bool isFunctionNameAllowed(BinaryExpr *E) {
auto FunctionBody = dyn_cast<DotSyntaxCallExpr>(E->getFn())->getFn();
auto FunctionDeclaration = dyn_cast<DeclRefExpr>(FunctionBody)->getDecl();
const auto FunctionName = dyn_cast<FuncDecl>(FunctionDeclaration)
->getBaseIdentifier().str();
return FunctionName == "~="
|| FunctionName == "=="
|| FunctionName == "__derived_enum_equals"
|| FunctionName == "__derived_struct_equals";
}
void appendPattern(TupleExpr *Tuple) {
auto PatternArgument = Tuple->getElements().back();
if (PatternArgument->getKind() == ExprKind::DeclRef)
PatternArgument = Tuple->getElements().front();
if (ConditionalPattern.size() > 0)
ConditionalPattern.append(", ");
ConditionalPattern.append(Lexer::getCharSourceRangeFromSourceRange(SM, PatternArgument->getSourceRange()).str());
}
};
class ConverterToSwitch {
public:
ConverterToSwitch(ResolvedRangeInfo Info, SourceManager &SM) : SM(SM) {
this->Info = Info;
}
void performConvert(SmallString<64> &Out) {
If = findIf();
OptionalLabel = If->getLabelInfo().Name.str().str();
ControlExpression = findControlExpression();
findPatternsAndBodies(PatternsAndBodies);
DefaultStatements = findDefaultStatements();
makeSwitchStatement(Out);
}
private:
ResolvedRangeInfo Info;
SourceManager &SM;
IfStmt *If;
IfStmt *PreviousIf;
std::string OptionalLabel;
std::string ControlExpression;
SmallVector<std::pair<std::string, std::string>, 16> PatternsAndBodies;
std::string DefaultStatements;
IfStmt *findIf() {
auto S = Info.ContainedNodes[0].dyn_cast<Stmt*>();
return dyn_cast<IfStmt>(S);
}
std::string findControlExpression() {
auto ConditionElement = If->getCond().front();
auto Finder = VarNameFinder();
ConditionElement.walk(Finder);
return Finder.VarName;
}
void findPatternsAndBodies(SmallVectorImpl<std::pair<std::string, std::string>> &Out) {
do {
auto pattern = findPattern();
auto body = findBodyStatements();
Out.push_back(std::make_pair(pattern, body));
PreviousIf = If;
} while ((If = dyn_cast_or_null<IfStmt>(If->getElseStmt())));
}
std::string findPattern() {
auto ConditionElement = If->getCond().front();
auto Finder = ConditionalPatternFinder(SM);
ConditionElement.walk(Finder);
return Finder.ConditionalPattern.str().str();
}
std::string findBodyStatements() {
return findBodyWithoutBraces(If->getThenStmt());
}
std::string findDefaultStatements() {
auto ElseBody = dyn_cast_or_null<BraceStmt>(PreviousIf->getElseStmt());
if (!ElseBody)
return getTokenText(tok::kw_break).str();
return findBodyWithoutBraces(ElseBody);
}
std::string findBodyWithoutBraces(Stmt *body) {
auto BS = dyn_cast<BraceStmt>(body);
if (!BS)
return Lexer::getCharSourceRangeFromSourceRange(SM, body->getSourceRange()).str().str();
if (BS->getElements().empty())
return getTokenText(tok::kw_break).str();
SourceRange BodyRange = BS->getElements().front().getSourceRange();
BodyRange.widen(BS->getElements().back().getSourceRange());
return Lexer::getCharSourceRangeFromSourceRange(SM, BodyRange).str().str();
}
void makeSwitchStatement(SmallString<64> &Out) {
StringRef Space = " ";
StringRef NewLine = "\n";
llvm::raw_svector_ostream OS(Out);
if (OptionalLabel.size() > 0)
OS << OptionalLabel << ":" << Space;
OS << tok::kw_switch << Space << ControlExpression << Space << tok::l_brace << NewLine;
for (auto &pair : PatternsAndBodies) {
OS << tok::kw_case << Space << pair.first << tok::colon << NewLine;
OS << pair.second << NewLine;
}
OS << tok::kw_default << tok::colon << NewLine;
OS << DefaultStatements << NewLine;
OS << tok::r_brace;
}
};
SmallString<64> result;
ConverterToSwitch(RangeInfo, SM).performConvert(result);
EditConsumer.accept(SM, RangeInfo.ContentRange, result.str());
return false;
}
/// Struct containing info about an IfStmt that can be converted into an IfExpr.
struct ConvertToTernaryExprInfo {
ConvertToTernaryExprInfo() {}
Expr *AssignDest() {
if (!Then || !Then->getDest() || !Else || !Else->getDest())
return nullptr;
auto ThenDest = Then->getDest();
auto ElseDest = Else->getDest();
if (ThenDest->getKind() != ElseDest->getKind())
return nullptr;
switch (ThenDest->getKind()) {
case ExprKind::DeclRef: {
auto ThenRef = dyn_cast<DeclRefExpr>(Then->getDest());
auto ElseRef = dyn_cast<DeclRefExpr>(Else->getDest());
if (!ThenRef || !ThenRef->getDecl() || !ElseRef || !ElseRef->getDecl())
return nullptr;
const auto ThenName = ThenRef->getDecl()->getName();
const auto ElseName = ElseRef->getDecl()->getName();
if (ThenName.compare(ElseName) != 0)
return nullptr;
return Then->getDest();
}
case ExprKind::Tuple: {
auto ThenTuple = dyn_cast<TupleExpr>(Then->getDest());
auto ElseTuple = dyn_cast<TupleExpr>(Else->getDest());
if (!ThenTuple || !ElseTuple)
return nullptr;
auto ThenNames = ThenTuple->getElementNames();
auto ElseNames = ElseTuple->getElementNames();
if (!ThenNames.equals(ElseNames))
return nullptr;
return ThenTuple;
}
default:
return nullptr;
}
}
Expr *ThenSrc() {
if (!Then)
return nullptr;
return Then->getSrc();
}
Expr *ElseSrc() {
if (!Else)
return nullptr;
return Else->getSrc();
}
bool isValid() {
if (!Cond || !AssignDest() || !ThenSrc() || !ElseSrc()
|| !IfRange.isValid())
return false;
return true;
}
PatternBindingDecl *Binding = nullptr; //optional
Expr *Cond = nullptr; //required
AssignExpr *Then = nullptr; //required
AssignExpr *Else = nullptr; //required
SourceRange IfRange;
};
ConvertToTernaryExprInfo
findConvertToTernaryExpression(ResolvedRangeInfo Info) {