mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +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.
2490 lines
86 KiB
C++
2490 lines
86 KiB
C++
//===--- 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/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/Subsystems.h"
|
|
#include "clang/Rewrite/Core/RewriteBuffer.h"
|
|
#include "llvm/ADT/StringSet.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::ide;
|
|
using namespace swift::index;
|
|
|
|
namespace {
|
|
class ContextFinder : public SourceEntityWalker {
|
|
SourceFile &SF;
|
|
ASTContext &Ctx;
|
|
SourceManager &SM;
|
|
ASTNode Target;
|
|
llvm::function_ref<bool(ASTNode)> IsContext;
|
|
SmallVector<ASTNode, 4> AllContexts;
|
|
bool contains(ASTNode Enclosing) {
|
|
auto Result = SM.rangeContains(Enclosing.getSourceRange(),
|
|
Target.getSourceRange());
|
|
if (Result && IsContext(Enclosing))
|
|
AllContexts.push_back(Enclosing);
|
|
return Result;
|
|
}
|
|
public:
|
|
ContextFinder(SourceFile &SF, ASTNode Target,
|
|
llvm::function_ref<bool(ASTNode)> IsContext =
|
|
[](ASTNode N) { return true; }) : SF(SF),
|
|
Ctx(SF.getASTContext()), SM(Ctx.SourceMgr), Target(Target),
|
|
IsContext(IsContext) {}
|
|
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());
|
|
|
|
StringRef Existing = Range.str();
|
|
if (Existing != 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,
|
|
LabelRangeType RangeType, bool isCallSite) {
|
|
if (isCallSite)
|
|
return renameLabelsLenient(LabelRanges, RangeType);
|
|
|
|
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, OldLabels[Index]))
|
|
return true;
|
|
splitAndRenameLabel(LabelRange, RangeType, Index++);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool isOperator() const { return Lexer::isOperator(Old.base()); }
|
|
|
|
private:
|
|
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);
|
|
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) {
|
|
// Split parameter range foo([a b]: Int) into decl argument label [a] and
|
|
// parameter name [b]. If we have only foo([a]: Int), then we add an empty
|
|
// range for the local name.
|
|
StringRef Content = Range.str();
|
|
size_t ExternalNameEnd = Content.find_first_of(" \t\n\v\f\r/");
|
|
ExternalNameEnd =
|
|
ExternalNameEnd == StringRef::npos ? Content.size() : ExternalNameEnd;
|
|
|
|
CharSourceRange Ext{Range.getStart(), unsigned(ExternalNameEnd)};
|
|
doRenameLabel(Ext, RefactoringRangeKind::DeclArgumentLabel, NameIndex);
|
|
|
|
size_t LocalNameStart = Content.find_last_of(" \t\n\v\f\r/");
|
|
LocalNameStart =
|
|
LocalNameStart == StringRef::npos ? ExternalNameEnd : LocalNameStart;
|
|
// Note: we consider the leading whitespace part of the parameter name since
|
|
// when the parameter is removed we want to remove the whitespace too.
|
|
// FIXME: handle comments foo(a /*...*/b: Int).
|
|
auto LocalLoc = Range.getStart().getAdvancedLocOrInvalid(LocalNameStart);
|
|
assert(LocalLoc.isValid());
|
|
CharSourceRange Local{LocalLoc, unsigned(Content.size() - LocalNameStart)};
|
|
doRenameLabel(Local, RefactoringRangeKind::ParameterName, 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, StringRef Expected) {
|
|
if (Range.getByteLength()) {
|
|
StringRef ExistingLabel = Lexer::getCharSourceRangeFromSourceRange(SM,
|
|
Range.getStart()).str();
|
|
if (!Expected.empty())
|
|
return Expected == ExistingLabel;
|
|
else
|
|
return ExistingLabel == "_";
|
|
}
|
|
return Expected.empty();
|
|
}
|
|
|
|
bool renameLabelsLenient(ArrayRef<CharSourceRange> LabelRanges,
|
|
LabelRangeType RangeType) {
|
|
|
|
ArrayRef<StringRef> OldNames = Old.args();
|
|
|
|
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, 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);
|
|
|
|
bool isKeywordBase = Old.base() == "init" || Old.base() == "subscript";
|
|
|
|
if (!Config.IsFunctionLike || !isKeywordBase) {
|
|
if (renameBase(Resolved.Range, RefactoringRangeKind::BaseName))
|
|
return RegionType::Mismatch;
|
|
|
|
} else if (isKeywordBase && 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;
|
|
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 &&
|
|
Resolved.LabelType == LabelRangeType::CallArg;
|
|
|
|
if (renameLabels(Resolved.LabelRanges, Resolved.LabelType, isCallSite))
|
|
return 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::StringMap<char> &ReplaceTextContext;
|
|
std::vector<Replacement> Replacements;
|
|
|
|
public:
|
|
const DeclNameViewer New;
|
|
|
|
private:
|
|
StringRef registerText(StringRef Text) {
|
|
return ReplaceTextContext.insert({Text, char()}).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 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::DeclArgumentLabel:
|
|
case RefactoringRangeKind::SelectorArgumentLabel:
|
|
return registerText(NewLabel.empty() ? "_" : 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::StringMap<char> &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) {
|
|
std::vector<RenameAvailabiliyInfo> Scratch;
|
|
for (auto &Info : collectRenameAvailabilityInfo(VD, 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);
|
|
printDeclUSR(D, OS);
|
|
}
|
|
|
|
ArrayRef<RenameLoc> results() const { return locations; }
|
|
|
|
private:
|
|
bool indexLocals() override { return true; }
|
|
void failed(StringRef error) override {}
|
|
bool recordHash(StringRef hash, bool isKnown) override { return true; }
|
|
bool startDependency(StringRef name, StringRef path, bool isClangModule,
|
|
bool isSystem, StringRef hash) 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) {
|
|
// We can only proceed with valid location and source file.
|
|
if (StartLoc.isValid() && TheFile) {
|
|
// Resolve the sema token and save it for later use.
|
|
CursorInfoResolver Resolver(*TheFile);
|
|
CursorInfo = Resolver.resolve(StartLoc);
|
|
}
|
|
}
|
|
};
|
|
|
|
#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 {
|
|
RangeResolver Resolver;
|
|
protected:
|
|
ResolvedRangeInfo RangeInfo;
|
|
public:
|
|
RangeBasedRefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts,
|
|
SourceEditConsumer &EditConsumer,
|
|
DiagnosticConsumer &DiagConsumer) :
|
|
RefactoringAction(MD, Opts, EditConsumer, DiagConsumer),
|
|
Resolver(*TheFile, Opts.Range.getStart(SM), Opts.Range.getEnd(SM)),
|
|
RangeInfo(Resolver.resolve()) {}
|
|
};
|
|
|
|
#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;
|
|
auto RenameOp = getAvailableRenameForDecl(CursorInfo.ValueD);
|
|
return RenameOp.hasValue() &&
|
|
RenameOp.getValue() == RefactoringKind::LocalRename;
|
|
}
|
|
|
|
static void analyzeRenameScope(ValueDecl *VD, DiagnosticEngine &Diags,
|
|
llvm::SmallVectorImpl<DeclContext *> &Scopes) {
|
|
Scopes.clear();
|
|
if (!getAvailableRenameForDecl(VD).hasValue()) {
|
|
Diags.diagnose(SourceLoc(), diag::value_decl_no_loc, VD->getFullName());
|
|
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;
|
|
}
|
|
CursorInfoResolver Resolver(*TheFile);
|
|
ResolvedCursorInfo CursorInfo = Resolver.resolve(StartLoc);
|
|
if (CursorInfo.isValid() && CursorInfo.ValueD) {
|
|
ValueDecl *VD = CursorInfo.ValueD;
|
|
llvm::SmallVector<DeclContext *, 8> Scopes;
|
|
analyzeRenameScope(VD, 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->getFullName());
|
|
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->getKind() == TypeKind::InOut)
|
|
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 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::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::Invalid:
|
|
return false;
|
|
case RangeKind::SingleExpression:
|
|
case RangeKind::SingleStatement:
|
|
case RangeKind::MultiStatement: {
|
|
return checkExtractConditions(Info, Diag).
|
|
success({CannotExtractReason::VoidType});
|
|
}
|
|
}
|
|
}
|
|
|
|
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, Ctx.getLazyResolver(), 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->getKind() == TypeKind::LValue) {
|
|
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<FuncDecl>(D)) {
|
|
if (FD->isAccessor()) {
|
|
ValueDecl *SD = FD->getAccessorStorageDecl();
|
|
switch(SD->getKind()) {
|
|
case DeclKind::Var:
|
|
if (auto *PBD = static_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.addInputBuffer(InputBuffer.get());
|
|
Invocation.getFrontendOptions().Inputs.setPrimaryInput( {0, SelectedInput::InputKind::Buffer} );
|
|
Invocation.getFrontendOptions().ModuleName = "extract";
|
|
|
|
auto Instance = llvm::make_unique<swift::CompilerInstance>();
|
|
if (Instance->setup(Invocation))
|
|
llvm_unreachable("Failed setup");
|
|
|
|
Instance->performParseOnly();
|
|
|
|
unsigned BufferId = Instance->getPrimarySourceFile()->getBufferID().getValue();
|
|
SourceManager &SM = Instance->getSourceMgr();
|
|
SourceLoc NameLoc = SM.getLocForOffset(BufferId, NameOffset);
|
|
auto LineAndCol = SM.getLineAndColumn(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.getLineAndColumn(Detail.Range.getStart());
|
|
auto End = SM.getLineAndColumn(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->getKind() == TypeKind::InOut)
|
|
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::SingleStatement:
|
|
case RangeKind::MultiStatement:
|
|
case RangeKind::Invalid:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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::SingleStatement:
|
|
case RangeKind::MultiStatement:
|
|
case RangeKind::Invalid:
|
|
return false;
|
|
}
|
|
}
|
|
bool RefactoringActionExtractRepeatedExpr::performChange() {
|
|
return RefactoringActionExtractExprBase(TheFile, RangeInfo,
|
|
DiagEngine, true, PreferredName,
|
|
EditConsumer).performChange();
|
|
}
|
|
|
|
struct CollapsibleNestedIfInfo {
|
|
IfStmt *OuterIf;
|
|
IfStmt *InnerIf;
|
|
bool FinishedOuterIf;
|
|
bool FoundNonCollapsibleItem;
|
|
CollapsibleNestedIfInfo():
|
|
OuterIf(nullptr), InnerIf(nullptr),
|
|
FinishedOuterIf(false), FoundNonCollapsibleItem(false) {}
|
|
bool isValid() {
|
|
return OuterIf && InnerIf && FinishedOuterIf && !FoundNonCollapsibleItem;
|
|
}
|
|
};
|
|
|
|
static CollapsibleNestedIfInfo findCollapseNestedIfTarget(ResolvedCursorInfo CursorInfo) {
|
|
if (CursorInfo.Kind != CursorInfoKind::StmtStart)
|
|
return CollapsibleNestedIfInfo();
|
|
struct IfStmtFinder: public SourceEntityWalker {
|
|
SourceLoc StartLoc;
|
|
CollapsibleNestedIfInfo IfInfo;
|
|
IfStmtFinder(SourceLoc StartLoc): StartLoc(StartLoc), IfInfo() {}
|
|
bool finishedInnerIfButNotFinishedOuterIf() {
|
|
return IfInfo.InnerIf && !IfInfo.FinishedOuterIf;
|
|
}
|
|
bool walkToStmtPre(Stmt *S) {
|
|
if (finishedInnerIfButNotFinishedOuterIf()) {
|
|
IfInfo.FoundNonCollapsibleItem = true;
|
|
return false;
|
|
}
|
|
|
|
bool StmtIsOuterIfBrace =
|
|
IfInfo.OuterIf && !IfInfo.InnerIf && S->getKind() == StmtKind::Brace;
|
|
if (StmtIsOuterIfBrace) {
|
|
return true;
|
|
}
|
|
|
|
auto *IFS = dyn_cast<IfStmt>(S);
|
|
if (!IFS) {
|
|
return false;
|
|
}
|
|
if (!IfInfo.OuterIf) {
|
|
IfInfo.OuterIf = IFS;
|
|
return true;
|
|
} else {
|
|
IfInfo.InnerIf = IFS;
|
|
return false;
|
|
}
|
|
}
|
|
bool walkToStmtPost(Stmt *S) {
|
|
assert(S != IfInfo.InnerIf && "Should not traverse inner if statement");
|
|
if (S == IfInfo.OuterIf) {
|
|
IfInfo.FinishedOuterIf = true;
|
|
}
|
|
return true;
|
|
}
|
|
bool walkToDeclPre(Decl *D, CharSourceRange Range) {
|
|
if (finishedInnerIfButNotFinishedOuterIf()) {
|
|
IfInfo.FoundNonCollapsibleItem = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
bool walkToExprPre(Expr *E) {
|
|
if (finishedInnerIfButNotFinishedOuterIf()) {
|
|
IfInfo.FoundNonCollapsibleItem = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} Walker(CursorInfo.TrailingStmt->getStartLoc());
|
|
Walker.walk(CursorInfo.TrailingStmt);
|
|
return Walker.IfInfo;
|
|
}
|
|
|
|
bool RefactoringActionCollapseNestedIfExpr::
|
|
isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) {
|
|
return findCollapseNestedIfTarget(Tok).isValid();
|
|
}
|
|
|
|
bool RefactoringActionCollapseNestedIfExpr::performChange() {
|
|
auto Target = findCollapseNestedIfTarget(CursorInfo);
|
|
if (!Target.isValid())
|
|
return true;
|
|
auto OuterIfConds = Target.OuterIf->getCond().vec();
|
|
auto InnerIfConds = Target.InnerIf->getCond().vec();
|
|
|
|
EditorConsumerInsertStream OS(EditConsumer, SM,
|
|
Lexer::getCharSourceRangeFromSourceRange(
|
|
SM, Target.OuterIf->getSourceRange()));
|
|
|
|
OS << tok::kw_if << " ";
|
|
for (auto CI = OuterIfConds.begin(); CI != OuterIfConds.end(); ++CI) {
|
|
OS << (CI != OuterIfConds.begin() ? ", " : "");
|
|
OS << Lexer::getCharSourceRangeFromSourceRange(
|
|
SM, CI->getSourceRange()).str();
|
|
}
|
|
for (auto CI = InnerIfConds.begin(); CI != InnerIfConds.end(); ++CI) {
|
|
OS << ", " << Lexer::getCharSourceRangeFromSourceRange(
|
|
SM, CI->getSourceRange()).str();
|
|
}
|
|
auto ThenStatementText = Lexer::getCharSourceRangeFromSourceRange(
|
|
SM, Target.InnerIf->getThenStmt()->getSourceRange()).str();
|
|
OS << " " << ThenStatementText;
|
|
return false;
|
|
}
|
|
|
|
static std::unique_ptr<llvm::SetVector<Expr*>>
|
|
findConcatenatedExpressions(ResolvedRangeInfo Info, ASTContext &Ctx) {
|
|
if (Info.Kind != RangeKind::SingleExpression
|
|
&& Info.Kind != RangeKind::PartOfExpression)
|
|
return nullptr;
|
|
Expr *E = Info.ContainedNodes[0].get<Expr*>();
|
|
|
|
struct StringInterpolationExprFinder: public SourceEntityWalker {
|
|
std::unique_ptr<llvm::SetVector<Expr*>> Bucket = llvm::
|
|
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) {
|
|
if (E->isImplicit())
|
|
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;
|
|
}
|
|
|
|
/// The helper class analyzes a given nominal decl or an extension decl to
|
|
/// decide whether stubs are required to filled in and the context in which
|
|
/// these stubs should be filled.
|
|
class FillProtocolStubContext {
|
|
|
|
std::vector<ValueDecl*> getUnsatisfiedRequirements(const DeclContext *DC);
|
|
|
|
/// Context in which the content should be filled; this could be either a
|
|
/// nominal type declaraion or an extension declaration.
|
|
DeclContext *DC;
|
|
|
|
/// The type that adopts the required protocol stubs. For nominal type decl, this
|
|
/// should be the declared type itself; for extension decl, this should be the
|
|
/// extended type at hand.
|
|
Type Adopter;
|
|
|
|
/// The start location of the decl, either nominal type or extension, for the
|
|
/// printer to figure out the right indentation.
|
|
SourceLoc StartLoc;
|
|
|
|
/// The location of '{' for the decl, thus we know where to insert the filling
|
|
/// stubs.
|
|
SourceLoc BraceStartLoc;
|
|
|
|
/// The value decls that should be satisfied; this could be either function
|
|
/// decls, property decls, or required type alias.
|
|
std::vector<ValueDecl*> FillingContents;
|
|
|
|
public:
|
|
FillProtocolStubContext(ExtensionDecl *ED) : DC(ED),
|
|
Adopter(ED->getExtendedType()), StartLoc(ED->getStartLoc()),
|
|
BraceStartLoc(ED->getBraces().Start),
|
|
FillingContents(getUnsatisfiedRequirements(ED)) {};
|
|
|
|
FillProtocolStubContext(NominalTypeDecl *ND) : DC(ND),
|
|
Adopter(ND->getDeclaredType()), StartLoc(ND->getStartLoc()),
|
|
BraceStartLoc(ND->getBraces().Start),
|
|
FillingContents(getUnsatisfiedRequirements(ND)) {};
|
|
|
|
FillProtocolStubContext() : DC(nullptr), Adopter(), FillingContents({}) {};
|
|
|
|
static FillProtocolStubContext getContextFromCursorInfo(ResolvedCursorInfo Tok);
|
|
|
|
ArrayRef<ValueDecl*> getFillingContents() const {
|
|
return llvm::makeArrayRef(FillingContents);
|
|
}
|
|
|
|
DeclContext *getFillingContext() const { return DC; }
|
|
|
|
bool canProceed() const {
|
|
return StartLoc.isValid() && BraceStartLoc.isValid() &&
|
|
!getFillingContents().empty();
|
|
}
|
|
|
|
Type getAdopter() const { return Adopter; }
|
|
SourceLoc getContextStartLoc() const { return StartLoc; }
|
|
SourceLoc getBraceStartLoc() const { return BraceStartLoc; }
|
|
};
|
|
|
|
FillProtocolStubContext FillProtocolStubContext::
|
|
getContextFromCursorInfo(ResolvedCursorInfo CursorInfo) {
|
|
assert(CursorInfo.isValid());
|
|
if (!CursorInfo.IsRef) {
|
|
// If the type name is on the declared nominal, e.g. "class A {}"
|
|
if (auto ND = dyn_cast<NominalTypeDecl>(CursorInfo.ValueD)) {
|
|
return FillProtocolStubContext(ND);
|
|
}
|
|
} else if (auto *ED = CursorInfo.ExtTyRef) {
|
|
// If the type ref is on a declared extension, e.g. "extension A {}"
|
|
return FillProtocolStubContext(ED);
|
|
}
|
|
return FillProtocolStubContext();
|
|
}
|
|
|
|
std::vector<ValueDecl*> FillProtocolStubContext::
|
|
getUnsatisfiedRequirements(const DeclContext *DC) {
|
|
// The results to return.
|
|
std::vector<ValueDecl*> NonWitnessedReqs;
|
|
|
|
// For each conformance of the extended nominal.
|
|
for(ProtocolConformance *Con : DC->getLocalConformances()) {
|
|
|
|
// Collect non-witnessed requirements.
|
|
Con->forEachNonWitnessedRequirement(DC->getASTContext().getLazyResolver(),
|
|
[&](ValueDecl *VD) { NonWitnessedReqs.push_back(VD); });
|
|
}
|
|
|
|
return NonWitnessedReqs;
|
|
}
|
|
|
|
bool RefactoringActionFillProtocolStub::
|
|
isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) {
|
|
return FillProtocolStubContext::getContextFromCursorInfo(Tok).canProceed();
|
|
};
|
|
|
|
bool RefactoringActionFillProtocolStub::performChange() {
|
|
// Get the filling protocol context from the input token.
|
|
FillProtocolStubContext Context = FillProtocolStubContext::
|
|
getContextFromCursorInfo(CursorInfo);
|
|
|
|
assert(Context.canProceed());
|
|
assert(!Context.getFillingContents().empty());
|
|
assert(Context.getFillingContext());
|
|
llvm::SmallString<128> Text;
|
|
{
|
|
llvm::raw_svector_ostream SS(Text);
|
|
Type Adopter = Context.getAdopter();
|
|
SourceLoc Loc = Context.getContextStartLoc();
|
|
auto Contents = Context.getFillingContents();
|
|
|
|
// For each unsatisfied requirement, print the stub to the buffer.
|
|
std::for_each(Contents.begin(), Contents.end(), [&](ValueDecl *VD) {
|
|
printRequirementStub(VD, Context.getFillingContext(), Adopter, Loc, SS);
|
|
});
|
|
}
|
|
|
|
// Insert all stubs after '{' in the extension/nominal type decl.
|
|
EditConsumer.insertAfter(SM, Context.getBraceStartLoc(), Text);
|
|
return false;
|
|
}
|
|
|
|
ArrayRef<RefactoringKind>
|
|
collectAvailableRefactoringsAtCursor(SourceFile *SF, unsigned Line,
|
|
unsigned Column,
|
|
std::vector<RefactoringKind> &Scratch,
|
|
llvm::ArrayRef<DiagnosticConsumer*> DiagConsumers) {
|
|
// Prepare the tool box.
|
|
ASTContext &Ctx = SF->getASTContext();
|
|
SourceManager &SM = Ctx.SourceMgr;
|
|
DiagnosticEngine DiagEngine(SM);
|
|
std::for_each(DiagConsumers.begin(), DiagConsumers.end(),
|
|
[&](DiagnosticConsumer *Con) { DiagEngine.addConsumer(*Con); });
|
|
CursorInfoResolver Resolver(*SF);
|
|
SourceLoc Loc = SM.getLocForLineCol(SF->getBufferID().getValue(), Line, Column);
|
|
if (Loc.isInvalid())
|
|
return {};
|
|
ResolvedCursorInfo Tok = Resolver.resolve(Lexer::getLocForStartOfToken(SM, Loc));
|
|
return collectAvailableRefactorings(SF, Tok, Scratch, /*Exclude rename*/false);
|
|
}
|
|
|
|
bool RefactoringActionExpandDefault::
|
|
isApplicable(ResolvedCursorInfo CursorInfo, DiagnosticEngine &Diag) {
|
|
auto Exit = [&](bool Applicable) {
|
|
if (!Applicable)
|
|
Diag.diagnose(SourceLoc(), diag::invalid_default_location);
|
|
return Applicable;
|
|
};
|
|
if (CursorInfo.Kind != CursorInfoKind::StmtStart)
|
|
return Exit(false);
|
|
if (auto *CS = dyn_cast<CaseStmt>(CursorInfo.TrailingStmt)) {
|
|
return Exit(CS->isDefault());
|
|
}
|
|
return Exit(false);
|
|
}
|
|
|
|
bool RefactoringActionExpandDefault::performChange() {
|
|
// Try to find the switch statement enclosing the default statement.
|
|
auto *CS = static_cast<CaseStmt*>(CursorInfo.TrailingStmt);
|
|
auto IsSwitch = [](ASTNode Node) {
|
|
return Node.is<Stmt*>() &&
|
|
Node.get<Stmt*>()->getKind() == StmtKind::Switch;
|
|
};
|
|
ContextFinder Finder(*TheFile, CS, IsSwitch);
|
|
Finder.resolve();
|
|
|
|
// If failed to find the switch statement, issue error.
|
|
if (Finder.getContexts().empty()) {
|
|
DiagEngine.diagnose(CS->getStartLoc(), diag::no_parent_switch);
|
|
return true;
|
|
}
|
|
auto *SwitchS = static_cast<SwitchStmt*>(Finder.getContexts().back().
|
|
get<Stmt*>());
|
|
|
|
// To find the subject enum decl for this switch statement; if failing,
|
|
// issue errors.
|
|
EnumDecl *SubjectED = nullptr;
|
|
if (auto SubjectTy = SwitchS->getSubjectExpr()->getType()) {
|
|
SubjectED = SubjectTy->getAnyNominal()->getAsEnumOrEnumExtensionContext();
|
|
}
|
|
if (!SubjectED) {
|
|
DiagEngine.diagnose(CS->getStartLoc(), diag::no_subject_enum);
|
|
return true;
|
|
}
|
|
|
|
// Assume enum elements are not handled in the switch statement.
|
|
llvm::DenseSet<EnumElementDecl*> UnhandledElements;
|
|
SubjectED->getAllElements(UnhandledElements);
|
|
bool FoundDefault = false;
|
|
for (auto Current : SwitchS->getCases()) {
|
|
if (Current == CS) {
|
|
FoundDefault = true;
|
|
continue;
|
|
}
|
|
// For each handled enum element, remove it from the bucket.
|
|
for (auto Item : Current->getCaseLabelItems()) {
|
|
if (auto *EEP = dyn_cast_or_null<EnumElementPattern>(Item.getPattern())) {
|
|
UnhandledElements.erase(EEP->getElementDecl());
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we've not seen the default statement inside the switch statement, issue
|
|
// error.
|
|
if (!FoundDefault) {
|
|
DiagEngine.diagnose(CS->getStartLoc(), diag::no_parent_switch);
|
|
return true;
|
|
}
|
|
|
|
// If all enum elements are handled in the switch statement, issue error.
|
|
if (UnhandledElements.empty()) {
|
|
DiagEngine.diagnose(CS->getStartLoc(), diag::no_remaining_cases);
|
|
return true;
|
|
}
|
|
|
|
// Good to go, change the code!
|
|
EditorConsumerInsertStream OS(EditConsumer, SM,
|
|
Lexer::getCharSourceRangeFromSourceRange(SM,
|
|
CS->getLabelItemsRange()));
|
|
printEnumElementsAsCases(UnhandledElements, OS);
|
|
return false;
|
|
}
|
|
|
|
static Expr *findLocalizeTarget(ResolvedCursorInfo CursorInfo) {
|
|
if (CursorInfo.Kind != CursorInfoKind::ExprStart)
|
|
return nullptr;
|
|
struct StringLiteralFinder: public SourceEntityWalker {
|
|
SourceLoc StartLoc;
|
|
Expr *Target;
|
|
StringLiteralFinder(SourceLoc StartLoc): StartLoc(StartLoc), Target(nullptr) {}
|
|
bool walkToExprPre(Expr *E) {
|
|
if (E->getStartLoc() != StartLoc)
|
|
return false;
|
|
if (E->getKind() == ExprKind::InterpolatedStringLiteral)
|
|
return false;
|
|
if (E->getKind() == ExprKind::StringLiteral) {
|
|
Target = E;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
} Walker(CursorInfo.TrailingExpr->getStartLoc());
|
|
Walker.walk(CursorInfo.TrailingExpr);
|
|
return Walker.Target;
|
|
}
|
|
|
|
bool RefactoringActionLocalizeString::
|
|
isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) {
|
|
return findLocalizeTarget(Tok);
|
|
}
|
|
|
|
bool RefactoringActionLocalizeString::performChange() {
|
|
Expr* Target = findLocalizeTarget(CursorInfo);
|
|
if (!Target)
|
|
return true;
|
|
EditConsumer.accept(SM, Target->getStartLoc(), "NSLocalizedString(");
|
|
EditConsumer.insertAfter(SM, Target->getEndLoc(), ", comment: \"\")");
|
|
return false;
|
|
}
|
|
|
|
static CharSourceRange
|
|
findSourceRangeToWrapInCatch(ResolvedCursorInfo CursorInfo,
|
|
SourceFile *TheFile,
|
|
SourceManager &SM) {
|
|
Expr *E = CursorInfo.TrailingExpr;
|
|
if (!E)
|
|
return CharSourceRange();
|
|
auto Node = ASTNode(E);
|
|
auto NodeChecker = [](ASTNode N) { return N.isStmt(StmtKind::Brace); };
|
|
ContextFinder Finder(*TheFile, Node, NodeChecker);
|
|
Finder.resolve();
|
|
auto Contexts = Finder.getContexts();
|
|
if (Contexts.size() == 0)
|
|
return CharSourceRange();
|
|
auto TargetNode = Contexts.back();
|
|
BraceStmt *BStmt = dyn_cast<BraceStmt>(TargetNode.dyn_cast<Stmt*>());
|
|
auto ConvertToCharRange = [&SM](SourceRange SR) {
|
|
return Lexer::getCharSourceRangeFromSourceRange(SM, SR);
|
|
};
|
|
assert(BStmt);
|
|
auto ExprRange = ConvertToCharRange(E->getSourceRange());
|
|
// Check elements of the deepest BraceStmt, pick one that covers expression.
|
|
for (auto Elem: BStmt->getElements()) {
|
|
auto ElemRange = ConvertToCharRange(Elem.getSourceRange());
|
|
if (ElemRange.contains(ExprRange))
|
|
TargetNode = Elem;
|
|
}
|
|
return ConvertToCharRange(TargetNode.getSourceRange());
|
|
}
|
|
|
|
bool RefactoringActionConvertToDoCatch::
|
|
isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) {
|
|
if (!Tok.TrailingExpr)
|
|
return false;
|
|
return isa<ForceTryExpr>(Tok.TrailingExpr);
|
|
}
|
|
|
|
bool RefactoringActionConvertToDoCatch::performChange() {
|
|
auto *TryExpr = dyn_cast<ForceTryExpr>(CursorInfo.TrailingExpr);
|
|
assert(TryExpr);
|
|
auto Range = findSourceRangeToWrapInCatch(CursorInfo, TheFile, SM);
|
|
if (!Range.isValid())
|
|
return true;
|
|
// Wrap given range in do catch block.
|
|
EditConsumer.accept(SM, Range.getStart(), "do {\n");
|
|
EditorConsumerInsertStream OS(EditConsumer, SM, Range.getEnd());
|
|
OS << "\n} catch {\n" << getCodePlaceholder() << "\n}";
|
|
|
|
// Delete ! from try! expression
|
|
auto ExclaimLen = getKeywordLen(tok::exclaim_postfix);
|
|
auto ExclaimRange = CharSourceRange(TryExpr->getExclaimLoc(), ExclaimLen);
|
|
EditConsumer.remove(SM, ExclaimRange);
|
|
return false;
|
|
}
|
|
|
|
/// Given a cursor position, this function tries to collect a number literal
|
|
/// expression immediately following the cursor.
|
|
static NumberLiteralExpr *getTrailingNumberLiteral(ResolvedCursorInfo Tok) {
|
|
// This cursor must point to the start of an expression.
|
|
if (Tok.Kind != CursorInfoKind::ExprStart)
|
|
return nullptr;
|
|
Expr *Parent = Tok.TrailingExpr;
|
|
assert(Parent);
|
|
|
|
// Check if an expression is a number literal.
|
|
auto IsLiteralNumber = [&](Expr *E) -> NumberLiteralExpr* {
|
|
if (auto *NL = dyn_cast<NumberLiteralExpr>(E)) {
|
|
|
|
// The sub-expression must have the same start loc with the outermost
|
|
// expression, i.e. the cursor position.
|
|
if (Parent->getStartLoc().getOpaquePointerValue() ==
|
|
E->getStartLoc().getOpaquePointerValue()) {
|
|
return NL;
|
|
}
|
|
}
|
|
return nullptr;
|
|
};
|
|
// For every sub-expression, try to find the literal expression that matches
|
|
// our criteria.
|
|
for (auto Pair: Parent->getDepthMap()) {
|
|
if (auto Result = IsLiteralNumber(Pair.getFirst())) {
|
|
return Result;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static std::string insertUnderscore(StringRef Text) {
|
|
llvm::SmallString<64> Buffer;
|
|
llvm::raw_svector_ostream OS(Buffer);
|
|
for (auto It = Text.begin(); It != Text.end(); It++) {
|
|
unsigned Distance = It - Text.begin();
|
|
if (Distance && !(Distance % 3)) {
|
|
OS << '_';
|
|
}
|
|
OS << *It;
|
|
}
|
|
return OS.str().str();
|
|
}
|
|
|
|
static void insertUnderscoreInDigits(StringRef Digits,
|
|
llvm::raw_ostream &OS) {
|
|
std::string BeforePoint, AfterPoint;
|
|
std::tie(BeforePoint, AfterPoint) = Digits.split('.');
|
|
|
|
// Insert '_' for the part before the decimal point.
|
|
std::reverse(BeforePoint.begin(), BeforePoint.end());
|
|
BeforePoint = insertUnderscore(BeforePoint);
|
|
std::reverse(BeforePoint.begin(), BeforePoint.end());
|
|
OS << BeforePoint;
|
|
|
|
// Insert '_' for the part after the decimal point, if necessary.
|
|
if (!AfterPoint.empty()) {
|
|
OS << '.';
|
|
OS << insertUnderscore(AfterPoint);
|
|
}
|
|
}
|
|
|
|
bool RefactoringActionSimplifyNumberLiteral::
|
|
isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) {
|
|
if (auto *Literal = getTrailingNumberLiteral(Tok)) {
|
|
llvm::SmallString<64> Buffer;
|
|
llvm::raw_svector_ostream OS(Buffer);
|
|
StringRef Digits = Literal->getDigitsText();
|
|
insertUnderscoreInDigits(Digits, OS);
|
|
|
|
// If inserting '_' results in a different digit sequence, this refactoring
|
|
// is applicable.
|
|
return OS.str() != Digits;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RefactoringActionSimplifyNumberLiteral::performChange() {
|
|
if (auto *Literal = getTrailingNumberLiteral(CursorInfo)) {
|
|
|
|
EditorConsumerInsertStream OS(EditConsumer, SM,
|
|
CharSourceRange(SM, Literal->getDigitsLoc(),
|
|
Lexer::getLocForEndOfToken(SM,
|
|
Literal->getEndLoc())));
|
|
StringRef Digits = Literal->getDigitsText();
|
|
insertUnderscoreInDigits(Digits, OS);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool rangeStartMayNeedRename(ResolvedRangeInfo Info) {
|
|
switch(Info.Kind) {
|
|
case RangeKind::SingleExpression: {
|
|
Expr *E = Info.ContainedNodes[0].get<Expr*>();
|
|
// We should show rename for the selection of "foo()"
|
|
if (auto *CE = dyn_cast<CallExpr>(E)) {
|
|
if (CE->getFn()->getKind() == ExprKind::DeclRef)
|
|
return true;
|
|
|
|
// When callling an instance method inside another instance method,
|
|
// we have a dot syntax call whose dot and base are both implicit. We
|
|
// need to explicitly allow the specific case here.
|
|
if (auto *DSC = dyn_cast<DotSyntaxCallExpr>(CE->getFn())) {
|
|
if (DSC->getBase()->isImplicit() &&
|
|
DSC->getFn()->getStartLoc() == Info.TokensInRange.front().getLoc())
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
case RangeKind::PartOfExpression: {
|
|
if (auto *CE = dyn_cast<CallExpr>(Info.CommonExprParent)) {
|
|
if (auto *DSC = dyn_cast<DotSyntaxCallExpr>(CE->getFn())) {
|
|
if (DSC->getFn()->getStartLoc() == Info.TokensInRange.front().getLoc())
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
case RangeKind::SingleDecl:
|
|
case RangeKind::SingleStatement:
|
|
case RangeKind::MultiStatement:
|
|
case RangeKind::Invalid:
|
|
return false;
|
|
}
|
|
}
|
|
}// end of anonymous namespace
|
|
|
|
StringRef swift::ide::
|
|
getDescriptiveRefactoringKindName(RefactoringKind Kind) {
|
|
switch(Kind) {
|
|
case RefactoringKind::None:
|
|
llvm_unreachable("Should be a valid refactoring kind");
|
|
#define REFACTORING(KIND, NAME, ID) case RefactoringKind::KIND: return NAME;
|
|
#include "swift/IDE/RefactoringKinds.def"
|
|
}
|
|
}
|
|
|
|
StringRef swift::ide::
|
|
getDescriptiveRenameUnavailableReason(RenameAvailableKind Kind) {
|
|
switch(Kind) {
|
|
case RenameAvailableKind::Available:
|
|
return "";
|
|
case RenameAvailableKind::Unavailable_system_symbol:
|
|
return "symbol from system module cannot be renamed";
|
|
case RenameAvailableKind::Unavailable_has_no_location:
|
|
return "symbol without a declaration location cannot be renamed";
|
|
case RenameAvailableKind::Unavailable_has_no_name:
|
|
return "cannot find the name of the symbol";
|
|
case RenameAvailableKind::Unavailable_has_no_accessibility:
|
|
return "cannot decide the accessibility of the symbol";
|
|
case RenameAvailableKind::Unavailable_decl_from_clang:
|
|
return "cannot rename a Clang symbol from its Swift reference";
|
|
}
|
|
}
|
|
|
|
SourceLoc swift::ide::RangeConfig::getStart(SourceManager &SM) {
|
|
return SM.getLocForLineCol(BufferId, Line, Column);
|
|
}
|
|
|
|
SourceLoc swift::ide::RangeConfig::getEnd(SourceManager &SM) {
|
|
return getStart(SM).getAdvancedLoc(Length);
|
|
}
|
|
|
|
struct swift::ide::FindRenameRangesAnnotatingConsumer::Implementation {
|
|
std::unique_ptr<SourceEditConsumer> pRewriter;
|
|
Implementation(SourceManager &SM, unsigned BufferId, llvm::raw_ostream &OS)
|
|
: pRewriter(new SourceEditOutputConsumer(SM, BufferId, OS)) {}
|
|
static StringRef tag(RefactoringRangeKind Kind) {
|
|
switch (Kind) {
|
|
case RefactoringRangeKind::BaseName:
|
|
return "base";
|
|
case RefactoringRangeKind::KeywordBaseName:
|
|
return "keywordBase";
|
|
case RefactoringRangeKind::ParameterName:
|
|
return "param";
|
|
case RefactoringRangeKind::DeclArgumentLabel:
|
|
return "arglabel";
|
|
case RefactoringRangeKind::CallArgumentLabel:
|
|
return "callarg";
|
|
case RefactoringRangeKind::CallArgumentColon:
|
|
return "callcolon";
|
|
case RefactoringRangeKind::CallArgumentCombined:
|
|
return "callcombo";
|
|
case RefactoringRangeKind::SelectorArgumentLabel:
|
|
return "sel";
|
|
}
|
|
}
|
|
void accept(SourceManager &SM, const RenameRangeDetail &Range) {
|
|
std::string NewText;
|
|
llvm::raw_string_ostream OS(NewText);
|
|
StringRef Tag = tag(Range.RangeKind);
|
|
OS << "<" << Tag;
|
|
if (Range.Index.hasValue())
|
|
OS << " index=" << *Range.Index;
|
|
OS << ">" << Range.Range.str() << "</" << Tag << ">";
|
|
pRewriter->accept(SM, {Range.Range, OS.str(), {}});
|
|
}
|
|
};
|
|
|
|
swift::ide::FindRenameRangesAnnotatingConsumer::
|
|
FindRenameRangesAnnotatingConsumer(SourceManager &SM, unsigned BufferId,
|
|
llvm::raw_ostream &OS): Impl(*new Implementation(SM, BufferId, OS)) {}
|
|
|
|
swift::ide::FindRenameRangesAnnotatingConsumer::~FindRenameRangesAnnotatingConsumer() {
|
|
delete &Impl;
|
|
}
|
|
|
|
void swift::ide::FindRenameRangesAnnotatingConsumer::
|
|
accept(SourceManager &SM, RegionType RegionType,
|
|
ArrayRef<RenameRangeDetail> Ranges) {
|
|
for (const auto &Range : Ranges) {
|
|
Impl.accept(SM, Range);
|
|
}
|
|
}
|
|
ArrayRef<RenameAvailabiliyInfo>
|
|
swift::ide::collectRenameAvailabilityInfo(const ValueDecl *VD,
|
|
std::vector<RenameAvailabiliyInfo> &Scratch) {
|
|
RenameAvailableKind AvailKind = RenameAvailableKind::Available;
|
|
if (getRelatedSystemDecl(VD)){
|
|
AvailKind = RenameAvailableKind::Unavailable_system_symbol;
|
|
} else if (VD->getClangDecl()) {
|
|
AvailKind = RenameAvailableKind::Unavailable_decl_from_clang;
|
|
} else if (VD->getStartLoc().isInvalid()) {
|
|
AvailKind = RenameAvailableKind::Unavailable_has_no_location;
|
|
} else if (!VD->hasName()) {
|
|
AvailKind = RenameAvailableKind::Unavailable_has_no_name;
|
|
} else if (!VD->hasAccess()) {
|
|
return llvm::makeArrayRef(Scratch);
|
|
}
|
|
|
|
if (isa<AbstractFunctionDecl>(VD)) {
|
|
// Disallow renaming accessors.
|
|
if (auto FD = dyn_cast<FuncDecl>(VD)) {
|
|
if (FD->isAccessor())
|
|
return Scratch;
|
|
}
|
|
// Disallow renaming deinit.
|
|
if (isa<DestructorDecl>(VD))
|
|
return Scratch;
|
|
}
|
|
|
|
// Always return local rename for parameters.
|
|
// FIXME: if the cursor is on the argument, we should return global rename.
|
|
if (isa<ParamDecl>(VD)) {
|
|
Scratch.emplace_back(RefactoringKind::LocalRename, AvailKind);
|
|
return Scratch;
|
|
}
|
|
|
|
// If the indexer considers VD a global symbol, then we apply global rename.
|
|
if (index::isLocalSymbol(VD))
|
|
Scratch.emplace_back(RefactoringKind::LocalRename, AvailKind);
|
|
else
|
|
Scratch.emplace_back(RefactoringKind::GlobalRename, AvailKind);
|
|
|
|
return llvm::makeArrayRef(Scratch);
|
|
}
|
|
|
|
ArrayRef<RefactoringKind> swift::ide::
|
|
collectAvailableRefactorings(SourceFile *SF,
|
|
ResolvedCursorInfo CursorInfo,
|
|
std::vector<RefactoringKind> &Scratch,
|
|
bool ExcludeRename) {
|
|
llvm::SmallVector<RefactoringKind, 2> AllKinds;
|
|
switch(CursorInfo.Kind) {
|
|
case CursorInfoKind::ModuleRef:
|
|
case CursorInfoKind::Invalid:
|
|
case CursorInfoKind::StmtStart:
|
|
case CursorInfoKind::ExprStart:
|
|
break;
|
|
case CursorInfoKind::ValueRef: {
|
|
auto RenameOp = getAvailableRenameForDecl(CursorInfo.ValueD);
|
|
if (RenameOp.hasValue() &&
|
|
RenameOp.getValue() == RefactoringKind::GlobalRename)
|
|
AllKinds.push_back(RenameOp.getValue());
|
|
}
|
|
}
|
|
DiagnosticEngine DiagEngine(SF->getASTContext().SourceMgr);
|
|
#define CURSOR_REFACTORING(KIND, NAME, ID) \
|
|
if (RefactoringAction##KIND::isApplicable(CursorInfo, DiagEngine)) \
|
|
AllKinds.push_back(RefactoringKind::KIND);
|
|
#include "swift/IDE/RefactoringKinds.def"
|
|
|
|
// Exclude renames.
|
|
for(auto Kind: AllKinds) {
|
|
switch (Kind) {
|
|
case RefactoringKind::LocalRename:
|
|
case RefactoringKind::GlobalRename:
|
|
if (ExcludeRename)
|
|
break;
|
|
LLVM_FALLTHROUGH;
|
|
default:
|
|
Scratch.push_back(Kind);
|
|
}
|
|
}
|
|
|
|
return llvm::makeArrayRef(Scratch);
|
|
}
|
|
|
|
|
|
|
|
ArrayRef<RefactoringKind> swift::ide::
|
|
collectAvailableRefactorings(SourceFile *SF, RangeConfig Range,
|
|
bool &RangeStartMayNeedRename,
|
|
std::vector<RefactoringKind> &Scratch,
|
|
llvm::ArrayRef<DiagnosticConsumer*> DiagConsumers) {
|
|
|
|
if (Range.Length == 0) {
|
|
return collectAvailableRefactoringsAtCursor(SF, Range.Line, Range.Column,
|
|
Scratch, DiagConsumers);
|
|
}
|
|
// Prepare the tool box.
|
|
ASTContext &Ctx = SF->getASTContext();
|
|
SourceManager &SM = Ctx.SourceMgr;
|
|
DiagnosticEngine DiagEngine(SM);
|
|
std::for_each(DiagConsumers.begin(), DiagConsumers.end(),
|
|
[&](DiagnosticConsumer *Con) { DiagEngine.addConsumer(*Con); });
|
|
|
|
RangeResolver Resolver(*SF, Range.getStart(SF->getASTContext().SourceMgr),
|
|
Range.getEnd(SF->getASTContext().SourceMgr));
|
|
ResolvedRangeInfo Result = Resolver.resolve();
|
|
|
|
#define RANGE_REFACTORING(KIND, NAME, ID) \
|
|
if (RefactoringAction##KIND::isApplicable(Result, DiagEngine)) \
|
|
Scratch.push_back(RefactoringKind::KIND);
|
|
#include "swift/IDE/RefactoringKinds.def"
|
|
|
|
RangeStartMayNeedRename = rangeStartMayNeedRename(Result);
|
|
return Scratch;
|
|
}
|
|
|
|
bool swift::ide::
|
|
refactorSwiftModule(ModuleDecl *M, RefactoringOptions Opts,
|
|
SourceEditConsumer &EditConsumer,
|
|
DiagnosticConsumer &DiagConsumer) {
|
|
assert(Opts.Kind != RefactoringKind::None && "should have a refactoring kind.");
|
|
|
|
// Use the default name if not specified.
|
|
if (Opts.PreferredName.empty()) {
|
|
Opts.PreferredName = getDefaultPreferredName(Opts.Kind);
|
|
}
|
|
|
|
switch (Opts.Kind) {
|
|
#define SEMANTIC_REFACTORING(KIND, NAME, ID) \
|
|
case RefactoringKind::KIND: { \
|
|
RefactoringAction##KIND Action(M, Opts, EditConsumer, DiagConsumer); \
|
|
if (RefactoringKind::KIND == RefactoringKind::LocalRename || \
|
|
Action.isApplicable()) \
|
|
return Action.performChange(); \
|
|
return true; \
|
|
}
|
|
#include "swift/IDE/RefactoringKinds.def"
|
|
case RefactoringKind::GlobalRename:
|
|
case RefactoringKind::FindGlobalRenameRanges:
|
|
case RefactoringKind::FindLocalRenameRanges:
|
|
llvm_unreachable("not a valid refactoring kind");
|
|
case RefactoringKind::None:
|
|
llvm_unreachable("should not enter here.");
|
|
}
|
|
}
|
|
|
|
static std::vector<ResolvedLoc>
|
|
resolveRenameLocations(ArrayRef<RenameLoc> RenameLocs, SourceFile &SF,
|
|
DiagnosticEngine &Diags) {
|
|
SourceManager &SM = SF.getASTContext().SourceMgr;
|
|
unsigned BufferID = SF.getBufferID().getValue();
|
|
|
|
std::vector<UnresolvedLoc> UnresolvedLocs;
|
|
for (const RenameLoc &RenameLoc : RenameLocs) {
|
|
DeclNameViewer OldName(RenameLoc.OldName);
|
|
SourceLoc Location = SM.getLocForLineCol(BufferID, RenameLoc.Line,
|
|
RenameLoc.Column);
|
|
|
|
if (!OldName.isValid()) {
|
|
Diags.diagnose(Location, diag::invalid_name, RenameLoc.OldName);
|
|
return {};
|
|
}
|
|
|
|
if (!RenameLoc.NewName.empty()) {
|
|
DeclNameViewer NewName(RenameLoc.NewName);
|
|
ArrayRef<StringRef> ParamNames = NewName.args();
|
|
bool newOperator = Lexer::isOperator(NewName.base());
|
|
bool NewNameIsValid = NewName.isValid() &&
|
|
(Lexer::isIdentifier(NewName.base()) || newOperator) &&
|
|
std::all_of(ParamNames.begin(), ParamNames.end(), [](StringRef Label) {
|
|
return Label.empty() || Lexer::isIdentifier(Label);
|
|
});
|
|
|
|
if (!NewNameIsValid) {
|
|
Diags.diagnose(Location, diag::invalid_name, RenameLoc.NewName);
|
|
return {};
|
|
}
|
|
|
|
if (NewName.partsCount() != OldName.partsCount()) {
|
|
Diags.diagnose(Location, diag::arity_mismatch, RenameLoc.NewName,
|
|
RenameLoc.OldName);
|
|
return {};
|
|
}
|
|
|
|
if (RenameLoc.Usage == NameUsage::Call && !RenameLoc.IsFunctionLike) {
|
|
Diags.diagnose(Location, diag::name_not_functionlike, RenameLoc.NewName);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
bool isOperator = Lexer::isOperator(OldName.base());
|
|
UnresolvedLocs.push_back({
|
|
Location,
|
|
(RenameLoc.Usage == NameUsage::Unknown ||
|
|
(RenameLoc.Usage == NameUsage::Call && !isOperator))
|
|
});
|
|
}
|
|
|
|
NameMatcher Resolver(SF);
|
|
return Resolver.resolve(UnresolvedLocs, SF.getAllTokens());
|
|
}
|
|
|
|
int swift::ide::syntacticRename(SourceFile *SF, ArrayRef<RenameLoc> RenameLocs,
|
|
SourceEditConsumer &EditConsumer,
|
|
DiagnosticConsumer &DiagConsumer) {
|
|
|
|
assert(SF && "null source file");
|
|
|
|
SourceManager &SM = SF->getASTContext().SourceMgr;
|
|
DiagnosticEngine DiagEngine(SM);
|
|
DiagEngine.addConsumer(DiagConsumer);
|
|
|
|
auto ResolvedLocs = resolveRenameLocations(RenameLocs, *SF, DiagEngine);
|
|
if (ResolvedLocs.size() != RenameLocs.size())
|
|
return true; // Already diagnosed.
|
|
|
|
size_t index = 0;
|
|
llvm::StringMap<char> ReplaceTextContext;
|
|
for(const RenameLoc &Rename: RenameLocs) {
|
|
ResolvedLoc &Resolved = ResolvedLocs[index++];
|
|
TextReplacementsRenamer Renamer(SM, Rename.OldName, Rename.NewName,
|
|
ReplaceTextContext);
|
|
RegionType Type = Renamer.addSyntacticRenameRanges(Resolved, Rename);
|
|
if (Type == RegionType::Mismatch) {
|
|
DiagEngine.diagnose(Resolved.Range.getStart(), diag::mismatched_rename,
|
|
Rename.NewName);
|
|
EditConsumer.accept(SM, Type, None);
|
|
} else {
|
|
EditConsumer.accept(SM, Type, Renamer.getReplacements());
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int swift::ide::findSyntacticRenameRanges(
|
|
SourceFile *SF, llvm::ArrayRef<RenameLoc> RenameLocs,
|
|
FindRenameRangesConsumer &RenameConsumer,
|
|
DiagnosticConsumer &DiagConsumer) {
|
|
assert(SF && "null source file");
|
|
|
|
SourceManager &SM = SF->getASTContext().SourceMgr;
|
|
DiagnosticEngine DiagEngine(SM);
|
|
DiagEngine.addConsumer(DiagConsumer);
|
|
|
|
auto ResolvedLocs = resolveRenameLocations(RenameLocs, *SF, DiagEngine);
|
|
if (ResolvedLocs.size() != RenameLocs.size())
|
|
return true; // Already diagnosed.
|
|
|
|
size_t index = 0;
|
|
for (const RenameLoc &Rename : RenameLocs) {
|
|
ResolvedLoc &Resolved = ResolvedLocs[index++];
|
|
RenameRangeDetailCollector Renamer(SM, Rename.OldName);
|
|
RegionType Type = Renamer.addSyntacticRenameRanges(Resolved, Rename);
|
|
if (Type == RegionType::Mismatch) {
|
|
DiagEngine.diagnose(Resolved.Range.getStart(), diag::mismatched_rename,
|
|
Rename.NewName);
|
|
RenameConsumer.accept(SM, Type, None);
|
|
} else {
|
|
RenameConsumer.accept(SM, Type, Renamer.Ranges);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int swift::ide::findLocalRenameRanges(
|
|
SourceFile *SF, RangeConfig Range,
|
|
FindRenameRangesConsumer &RenameConsumer,
|
|
DiagnosticConsumer &DiagConsumer) {
|
|
assert(SF && "null source file");
|
|
|
|
SourceManager &SM = SF->getASTContext().SourceMgr;
|
|
DiagnosticEngine Diags(SM);
|
|
Diags.addConsumer(DiagConsumer);
|
|
|
|
auto StartLoc = Lexer::getLocForStartOfToken(SM, Range.getStart(SM));
|
|
|
|
CursorInfoResolver Resolver(*SF);
|
|
ResolvedCursorInfo CursorInfo = Resolver.resolve(StartLoc);
|
|
if (!CursorInfo.isValid() || !CursorInfo.ValueD) {
|
|
Diags.diagnose(StartLoc, diag::unresolved_location);
|
|
return true;
|
|
}
|
|
ValueDecl *VD = CursorInfo.ValueD;
|
|
llvm::SmallVector<DeclContext *, 8> Scopes;
|
|
analyzeRenameScope(VD, Diags, Scopes);
|
|
if (Scopes.empty())
|
|
return true;
|
|
RenameRangeCollector RangeCollector(VD, StringRef());
|
|
for (DeclContext *DC : Scopes)
|
|
indexDeclContext(DC, RangeCollector);
|
|
|
|
return findSyntacticRenameRanges(SF, RangeCollector.results(), RenameConsumer,
|
|
DiagConsumer);
|
|
}
|