mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Instead of building ArgumentShuffleExprs, lets just build a TupleExpr, with explicit representation of collected varargs and default arguments. This isn't quite as elegant as it should be, because when re-typechecking, SanitizeExpr needs to restore the 'old' parameter list by stripping out the nodes inserted by type checking. However that hackery is all isolated in one place and will go away soon. Note that there's a minor change the generated SIL. Caller default arguments (#file, #line, etc) are no longer delayed and are instead evaluated in their usual argument position. I don't believe this actually results in an observable change in behavior, but if it turns out to be a problem, we can pretty easily change it back to the old behavior with a bit of extra work.
3371 lines
112 KiB
C++
3371 lines
112 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/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());
|
|
|
|
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, RangeType, 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, /*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()) {
|
|
CharSourceRange ExistingLabelRange =
|
|
Lexer::getCharSourceRangeFromSourceRange(SM, Range.getStart());
|
|
StringRef ExistingLabel = ExistingLabelRange.str();
|
|
|
|
switch (RangeType) {
|
|
case LabelRangeType::NoncollapsibleParam:
|
|
if (ExistingLabelRange == Range && 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,
|
|
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, 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;
|
|
bool IsKeywordBase = IsInit || IsSubscript;
|
|
|
|
// Filter out non-semantic keyword basename locations with no labels.
|
|
// We've already filtered out those in active code, so these are
|
|
// any appearance of just 'init' or 'subscript' in strings, comments, and
|
|
// inactive code.
|
|
if (IsKeywordBase && (Config.Usage == NameUsage::Unknown &&
|
|
Resolved.LabelType == LabelRangeType::None))
|
|
return RegionType::Unmatched;
|
|
|
|
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 || 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.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) {
|
|
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->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<CatchStmt>(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, 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->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()));
|
|
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->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) {
|
|
// 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)) {
|
|
auto accessors = ASD->getAllAccessors();
|
|
Found.insert(accessors.begin(), accessors.end());
|
|
}
|
|
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->getElement(0).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:
|
|
// FIXME: the range info kind should imply non-empty list.
|
|
if (!Info.ContainedNodes.empty())
|
|
E = Info.ContainedNodes[0].get<Expr*>();
|
|
else
|
|
return nullptr;
|
|
break;
|
|
case RangeKind::PartOfExpression:
|
|
E = Info.CommonExprParent;
|
|
break;
|
|
default:
|
|
return nullptr;
|
|
}
|
|
|
|
assert(E);
|
|
|
|
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;
|
|
// 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() {
|
|
if (!Assign)
|
|
return nullptr;
|
|
return dyn_cast_or_null<IfExpr>(Assign->getSrc());
|
|
}
|
|
|
|
SourceRange getNameRange() {
|
|
auto Invalid = SourceRange();
|
|
|
|
if (!Assign)
|
|
return Invalid;
|
|
|
|
if (auto dest = Assign->getDest())
|
|
return dest->getSourceRange();
|
|
|
|
return Invalid;
|
|
}
|
|
|
|
Type getType() {
|
|
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() {
|
|
if (Binding && Binding->getNumPatternEntries() == 1) {
|
|
if (auto *Init = Binding->getInit(0)) {
|
|
return dyn_cast<IfExpr>(Init);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SourceRange getNameRange() {
|
|
if (auto Pattern = getNamePattern())
|
|
return Pattern->getSourceRange();
|
|
|
|
return SourceRange();
|
|
}
|
|
|
|
Type getType() {
|
|
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 llvm::make_unique<ExpandableBindingTernaryExprInfo>(Binding);
|
|
|
|
if (auto E = Info.ContainedNodes[0].dyn_cast<Expr*>())
|
|
if (auto Assign = dyn_cast<AssignExpr>(E))
|
|
return llvm::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
|
|
}
|
|
|
|
/// 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;
|
|
|
|
auto ThenName = ThenRef->getDecl()->getFullName();
|
|
auto ElseName = ElseRef->getDecl()->getFullName();
|
|
|
|
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) {
|
|
|
|
auto notFound = ConvertToTernaryExprInfo();
|
|
|
|
if (Info.Kind != RangeKind::SingleStatement
|
|
&& Info.Kind != RangeKind::MultiStatement)
|
|
return notFound;
|
|
|
|
if (Info.ContainedNodes.empty())
|
|
return notFound;
|
|
|
|
struct AssignExprFinder: public SourceEntityWalker {
|
|
|
|
AssignExpr *Assign = nullptr;
|
|
|
|
AssignExprFinder(Stmt* S) {
|
|
if (S)
|
|
walk(S);
|
|
}
|
|
|
|
virtual bool walkToExprPre(Expr *E) {
|
|
Assign = dyn_cast<AssignExpr>(E);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
ConvertToTernaryExprInfo Target;
|
|
|
|
IfStmt *If = nullptr;
|
|
|
|
if (Info.ContainedNodes.size() == 1) {
|
|
if (auto S = Info.ContainedNodes[0].dyn_cast<Stmt*>())
|
|
If = dyn_cast<IfStmt>(S);
|
|
}
|
|
|
|
if (Info.ContainedNodes.size() == 2) {
|
|
if (auto D = Info.ContainedNodes[0].dyn_cast<Decl*>())
|
|
Target.Binding = dyn_cast<PatternBindingDecl>(D);
|
|
if (auto S = Info.ContainedNodes[1].dyn_cast<Stmt*>())
|
|
If = dyn_cast<IfStmt>(S);
|
|
}
|
|
|
|
if (!If)
|
|
return notFound;
|
|
|
|
auto CondList = If->getCond();
|
|
|
|
if (CondList.size() != 1)
|
|
return notFound;
|
|
|
|
Target.Cond = CondList[0].getBooleanOrNull();
|
|
Target.IfRange = If->getSourceRange();
|
|
|
|
Target.Then = AssignExprFinder(If->getThenStmt()).Assign;
|
|
Target.Else = AssignExprFinder(If->getElseStmt()).Assign;
|
|
|
|
return Target;
|
|
}
|
|
|
|
bool RefactoringActionConvertToTernaryExpr::
|
|
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
|
|
return findConvertToTernaryExpression(Info).isValid();
|
|
}
|
|
|
|
bool RefactoringActionConvertToTernaryExpr::performChange() {
|
|
auto Target = findConvertToTernaryExpression(RangeInfo);
|
|
|
|
if (!Target.isValid())
|
|
return true; //abort
|
|
|
|
llvm::SmallString<64> DeclBuffer;
|
|
llvm::raw_svector_ostream OS(DeclBuffer);
|
|
|
|
llvm::StringRef Space = " ";
|
|
|
|
auto IfRange = Target.IfRange;
|
|
auto ReplaceRange = Lexer::getCharSourceRangeFromSourceRange(SM, IfRange);
|
|
|
|
auto CondRange = Target.Cond->getSourceRange();
|
|
auto CondCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, CondRange);
|
|
|
|
auto ThenRange = Target.ThenSrc()->getSourceRange();
|
|
auto ThenCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ThenRange);
|
|
|
|
auto ElseRange = Target.ElseSrc()->getSourceRange();
|
|
auto ElseCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ElseRange);
|
|
|
|
CharSourceRange DestCharRange;
|
|
|
|
if (Target.Binding) {
|
|
auto DestRange = Target.Binding->getSourceRange();
|
|
DestCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, DestRange);
|
|
ReplaceRange.widen(DestCharRange);
|
|
} else {
|
|
auto DestRange = Target.AssignDest()->getSourceRange();
|
|
DestCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, DestRange);
|
|
}
|
|
|
|
OS << DestCharRange.str() << Space << tok::equal << Space;
|
|
OS << CondCharRange.str() << Space << tok::question_postfix << Space;
|
|
OS << ThenCharRange.str() << Space << tok::colon << Space;
|
|
OS << ElseCharRange.str();
|
|
|
|
EditConsumer.accept(SM, ReplaceRange, DeclBuffer.str());
|
|
|
|
return false; //don't abort
|
|
}
|
|
|
|
/// 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) {
|
|
if(!CursorInfo.isValid())
|
|
return FillProtocolStubContext();
|
|
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);
|
|
}
|
|
|
|
static EnumDecl* getEnumDeclFromSwitchStmt(SwitchStmt *SwitchS) {
|
|
if (auto SubjectTy = SwitchS->getSubjectExpr()->getType()) {
|
|
// FIXME: Support more complex subject like '(Enum1, Enum2)'.
|
|
return dyn_cast_or_null<EnumDecl>(SubjectTy->getAnyNominal());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static bool performCasesExpansionInSwitchStmt(SwitchStmt *SwitchS,
|
|
DiagnosticEngine &DiagEngine,
|
|
SourceLoc ExpandedStmtLoc,
|
|
EditorConsumerInsertStream &OS
|
|
) {
|
|
// Assume enum elements are not handled in the switch statement.
|
|
auto EnumDecl = getEnumDeclFromSwitchStmt(SwitchS);
|
|
assert(EnumDecl);
|
|
llvm::DenseSet<EnumElementDecl*> UnhandledElements;
|
|
EnumDecl->getAllElements(UnhandledElements);
|
|
for (auto Current : SwitchS->getCases()) {
|
|
if (Current->isDefault()) {
|
|
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 all enum elements are handled in the switch statement, issue error.
|
|
if (UnhandledElements.empty()) {
|
|
DiagEngine.diagnose(ExpandedStmtLoc, diag::no_remaining_cases);
|
|
return true;
|
|
}
|
|
|
|
printEnumElementsAsCases(UnhandledElements, OS);
|
|
return false;
|
|
}
|
|
|
|
// Finds SwitchStmt that contains given CaseStmt.
|
|
static SwitchStmt* findEnclosingSwitchStmt(CaseStmt *CS,
|
|
SourceFile *SF,
|
|
DiagnosticEngine &DiagEngine) {
|
|
auto IsSwitch = [](ASTNode Node) {
|
|
return Node.is<Stmt*>() &&
|
|
Node.get<Stmt*>()->getKind() == StmtKind::Switch;
|
|
};
|
|
ContextFinder Finder(*SF, 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 nullptr;
|
|
}
|
|
auto *SwitchS = static_cast<SwitchStmt*>(Finder.getContexts().back().
|
|
get<Stmt*>());
|
|
// Make sure that CaseStmt is included in switch that was found.
|
|
auto Cases = SwitchS->getCases();
|
|
auto Default = std::find(Cases.begin(), Cases.end(), CS);
|
|
if (Default == Cases.end()) {
|
|
DiagEngine.diagnose(CS->getStartLoc(), diag::no_parent_switch);
|
|
return nullptr;
|
|
}
|
|
return SwitchS;
|
|
}
|
|
|
|
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)) {
|
|
auto EnclosingSwitchStmt = findEnclosingSwitchStmt(CS,
|
|
CursorInfo.SF,
|
|
Diag);
|
|
if (!EnclosingSwitchStmt)
|
|
return false;
|
|
auto EnumD = getEnumDeclFromSwitchStmt(EnclosingSwitchStmt);
|
|
auto IsApplicable = CS->isDefault() && EnumD != nullptr;
|
|
return IsApplicable;
|
|
}
|
|
return Exit(false);
|
|
}
|
|
|
|
bool RefactoringActionExpandDefault::performChange() {
|
|
// If we've not seen the default statement inside the switch statement, issue
|
|
// error.
|
|
auto *CS = static_cast<CaseStmt*>(CursorInfo.TrailingStmt);
|
|
auto *SwitchS = findEnclosingSwitchStmt(CS, TheFile, DiagEngine);
|
|
assert(SwitchS);
|
|
EditorConsumerInsertStream OS(EditConsumer, SM,
|
|
Lexer::getCharSourceRangeFromSourceRange(SM,
|
|
CS->getLabelItemsRange()));
|
|
return performCasesExpansionInSwitchStmt(SwitchS,
|
|
DiagEngine,
|
|
CS->getStartLoc(),
|
|
OS);
|
|
}
|
|
|
|
bool RefactoringActionExpandSwitchCases::
|
|
isApplicable(ResolvedCursorInfo CursorInfo, DiagnosticEngine &DiagEngine) {
|
|
if (!CursorInfo.TrailingStmt)
|
|
return false;
|
|
if (auto *Switch = dyn_cast<SwitchStmt>(CursorInfo.TrailingStmt)) {
|
|
return getEnumDeclFromSwitchStmt(Switch);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RefactoringActionExpandSwitchCases::performChange() {
|
|
auto *SwitchS = dyn_cast<SwitchStmt>(CursorInfo.TrailingStmt);
|
|
assert(SwitchS);
|
|
|
|
auto InsertRange = CharSourceRange();
|
|
auto Cases = SwitchS->getCases();
|
|
auto Default = std::find_if(Cases.begin(), Cases.end(), [](CaseStmt *Stmt) {
|
|
return Stmt->isDefault();
|
|
});
|
|
if (Default != Cases.end()) {
|
|
auto DefaultRange = (*Default)->getLabelItemsRange();
|
|
InsertRange = Lexer::getCharSourceRangeFromSourceRange(SM, DefaultRange);
|
|
} else {
|
|
auto RBraceLoc = SwitchS->getRBraceLoc();
|
|
InsertRange = CharSourceRange(SM, RBraceLoc, RBraceLoc);
|
|
}
|
|
EditorConsumerInsertStream OS(EditConsumer, SM, InsertRange);
|
|
if (SM.getLineNumber(SwitchS->getLBraceLoc()) ==
|
|
SM.getLineNumber(SwitchS->getRBraceLoc())) {
|
|
OS << "\n";
|
|
}
|
|
auto Result = performCasesExpansionInSwitchStmt(SwitchS,
|
|
DiagEngine,
|
|
SwitchS->getStartLoc(),
|
|
OS);
|
|
return Result;
|
|
}
|
|
|
|
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 void generateMemberwiseInit(SourceEditConsumer &EditConsumer,
|
|
SourceManager &SM,
|
|
SmallVectorImpl<std::string>& memberNameVector,
|
|
SmallVectorImpl<std::string>& memberTypeVector,
|
|
SourceLoc targetLocation) {
|
|
|
|
assert(!memberTypeVector.empty());
|
|
assert(memberTypeVector.size() == memberNameVector.size());
|
|
|
|
EditConsumer.accept(SM, targetLocation, "\ninternal init(");
|
|
|
|
for (size_t i = 0, n = memberTypeVector.size(); i < n ; i++) {
|
|
EditConsumer.accept(SM, targetLocation, memberNameVector[i] + ": " +
|
|
memberTypeVector[i]);
|
|
|
|
if (i != memberTypeVector.size() - 1) {
|
|
EditConsumer.accept(SM, targetLocation, ", ");
|
|
}
|
|
}
|
|
|
|
EditConsumer.accept(SM, targetLocation, ") {\n");
|
|
|
|
for (auto varName: memberNameVector) {
|
|
EditConsumer.accept(SM, targetLocation,
|
|
"self." + varName + " = " + varName + "\n");
|
|
}
|
|
|
|
EditConsumer.accept(SM, targetLocation, "}\n");
|
|
}
|
|
|
|
static SourceLoc collectMembersForInit(ResolvedCursorInfo CursorInfo,
|
|
SmallVectorImpl<std::string>& memberNameVector,
|
|
SmallVectorImpl<std::string>& memberTypeVector) {
|
|
|
|
if (!CursorInfo.ValueD)
|
|
return SourceLoc();
|
|
|
|
ClassDecl *classDecl = dyn_cast<ClassDecl>(CursorInfo.ValueD);
|
|
if (!classDecl || classDecl->getStoredProperties().empty() ||
|
|
CursorInfo.IsRef) {
|
|
return SourceLoc();
|
|
}
|
|
|
|
SourceLoc bracesStart = classDecl->getBraces().Start;
|
|
if (!bracesStart.isValid())
|
|
return SourceLoc();
|
|
|
|
SourceLoc targetLocation = bracesStart.getAdvancedLoc(1);
|
|
if (!targetLocation.isValid())
|
|
return SourceLoc();
|
|
|
|
for (auto varDecl : classDecl->getStoredProperties()) {
|
|
auto parentPatternBinding = varDecl->getParentPatternBinding();
|
|
if (!parentPatternBinding)
|
|
continue;
|
|
|
|
auto varDeclIndex =
|
|
parentPatternBinding->getPatternEntryIndexForVarDecl(varDecl);
|
|
|
|
if (auto init = varDecl->getParentPatternBinding()->getInit(varDeclIndex)) {
|
|
if (init->getStartLoc().isValid())
|
|
continue;
|
|
}
|
|
|
|
StringRef memberName = varDecl->getName().str();
|
|
memberNameVector.push_back(memberName.str());
|
|
|
|
std::string memberType = varDecl->getType().getString();
|
|
memberTypeVector.push_back(memberType);
|
|
}
|
|
|
|
if (memberNameVector.empty() || memberTypeVector.empty()) {
|
|
return SourceLoc();
|
|
}
|
|
|
|
return targetLocation;
|
|
}
|
|
|
|
bool RefactoringActionMemberwiseInitLocalRefactoring::
|
|
isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) {
|
|
|
|
SmallVector<std::string, 8> memberNameVector;
|
|
SmallVector<std::string, 8> memberTypeVector;
|
|
|
|
return collectMembersForInit(Tok, memberNameVector,
|
|
memberTypeVector).isValid();
|
|
}
|
|
|
|
bool RefactoringActionMemberwiseInitLocalRefactoring::performChange() {
|
|
|
|
SmallVector<std::string, 8> memberNameVector;
|
|
SmallVector<std::string, 8> memberTypeVector;
|
|
|
|
SourceLoc targetLocation = collectMembersForInit(CursorInfo, memberNameVector,
|
|
memberTypeVector);
|
|
if (targetLocation.isInvalid())
|
|
return true;
|
|
|
|
generateMemberwiseInit(EditConsumer, SM, memberNameVector,
|
|
memberTypeVector, targetLocation);
|
|
|
|
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.empty())
|
|
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 CallExpr *findTrailingClosureTarget(SourceManager &SM,
|
|
ResolvedCursorInfo CursorInfo) {
|
|
if (CursorInfo.Kind == CursorInfoKind::StmtStart)
|
|
// StmtStart postion can't be a part of CallExpr.
|
|
return nullptr;
|
|
|
|
// Find inner most CallExpr
|
|
ContextFinder
|
|
Finder(*CursorInfo.SF, CursorInfo.Loc,
|
|
[](ASTNode N) {
|
|
return N.isStmt(StmtKind::Brace) || N.isExpr(ExprKind::Call);
|
|
});
|
|
Finder.resolve();
|
|
if (Finder.getContexts().empty()
|
|
|| !Finder.getContexts().back().is<Expr*>())
|
|
return nullptr;
|
|
CallExpr *CE = cast<CallExpr>(Finder.getContexts().back().get<Expr*>());
|
|
|
|
if (CE->hasTrailingClosure())
|
|
// Call expression already has a trailing closure.
|
|
return nullptr;
|
|
|
|
// The last arugment is a closure?
|
|
Expr *Args = CE->getArg();
|
|
if (!Args)
|
|
return nullptr;
|
|
Expr *LastArg;
|
|
if (auto *ASE = dyn_cast<ArgumentShuffleExpr>(Args))
|
|
Args = ASE->getSubExpr();
|
|
if (auto *PE = dyn_cast<ParenExpr>(Args)) {
|
|
LastArg = PE->getSubExpr();
|
|
} else {
|
|
auto *TE = cast<TupleExpr>(Args);
|
|
if (TE->getNumElements() == 0)
|
|
return nullptr;
|
|
LastArg = TE->getElements().back();
|
|
}
|
|
|
|
if (auto *ICE = dyn_cast<ImplicitConversionExpr>(LastArg))
|
|
LastArg = ICE->getSyntacticSubExpr();
|
|
|
|
if (isa<ClosureExpr>(LastArg) || isa<CaptureListExpr>(LastArg))
|
|
return CE;
|
|
return nullptr;
|
|
}
|
|
|
|
bool RefactoringActionTrailingClosure::
|
|
isApplicable(ResolvedCursorInfo CursorInfo, DiagnosticEngine &Diag) {
|
|
SourceManager &SM = CursorInfo.SF->getASTContext().SourceMgr;
|
|
return findTrailingClosureTarget(SM, CursorInfo);
|
|
}
|
|
|
|
bool RefactoringActionTrailingClosure::performChange() {
|
|
auto *CE = findTrailingClosureTarget(SM, CursorInfo);
|
|
if (!CE)
|
|
return true;
|
|
Expr *Arg = CE->getArg();
|
|
|
|
Expr *ClosureArg = nullptr;
|
|
Expr *PrevArg = nullptr;
|
|
|
|
OriginalArgumentList ArgList = getOriginalArgumentList(Arg);
|
|
|
|
auto NumArgs = ArgList.args.size();
|
|
if (NumArgs == 0)
|
|
return true;
|
|
ClosureArg = ArgList.args[NumArgs - 1];
|
|
if (NumArgs > 1)
|
|
PrevArg = ArgList.args[NumArgs - 2];
|
|
|
|
if (auto *ICE = dyn_cast<ImplicitConversionExpr>(ClosureArg))
|
|
ClosureArg = ICE->getSyntacticSubExpr();
|
|
|
|
if (ArgList.lParenLoc.isInvalid() || ArgList.rParenLoc.isInvalid())
|
|
return true;
|
|
|
|
// Replace:
|
|
// * Open paren with ' ' if the closure is sole argument.
|
|
// * Comma with ') ' otherwise.
|
|
if (PrevArg) {
|
|
CharSourceRange PreRange(
|
|
SM,
|
|
Lexer::getLocForEndOfToken(SM, PrevArg->getEndLoc()),
|
|
ClosureArg->getStartLoc());
|
|
EditConsumer.accept(SM, PreRange, ") ");
|
|
} else {
|
|
CharSourceRange PreRange(
|
|
SM, ArgList.lParenLoc, ClosureArg->getStartLoc());
|
|
EditConsumer.accept(SM, PreRange, " ");
|
|
}
|
|
// Remove original closing paren.
|
|
CharSourceRange PostRange(
|
|
SM,
|
|
Lexer::getLocForEndOfToken(SM, ClosureArg->getEndLoc()),
|
|
Lexer::getLocForEndOfToken(SM, ArgList.rParenLoc));
|
|
EditConsumer.remove(SM, PostRange);
|
|
return false;
|
|
}
|
|
|
|
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::MultiTypeMemberDecl:
|
|
case RangeKind::SingleStatement:
|
|
case RangeKind::MultiStatement:
|
|
case RangeKind::Invalid:
|
|
return false;
|
|
}
|
|
llvm_unreachable("unhandled kind");
|
|
}
|
|
}// 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"
|
|
}
|
|
llvm_unreachable("unhandled kind");
|
|
}
|
|
|
|
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";
|
|
}
|
|
llvm_unreachable("unhandled kind");
|
|
}
|
|
|
|
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::NoncollapsibleParameterName:
|
|
return "noncollapsibleparam";
|
|
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";
|
|
}
|
|
llvm_unreachable("unhandled kind");
|
|
}
|
|
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) {
|
|
if (RegionType == RegionType::Mismatch || RegionType == RegionType::Unmatched)
|
|
return;
|
|
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;
|
|
}
|
|
|
|
if (isa<AbstractFunctionDecl>(VD)) {
|
|
// Disallow renaming accessors.
|
|
if (isa<AccessorDecl>(VD))
|
|
return Scratch;
|
|
|
|
// Disallow renaming deinit.
|
|
if (isa<DestructorDecl>(VD))
|
|
return Scratch;
|
|
|
|
// Disallow renaming init with no arguments.
|
|
if (auto CD = dyn_cast<ConstructorDecl>(VD)) {
|
|
if (!CD->getParameters()->size())
|
|
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();
|
|
|
|
bool enableInternalRefactoring = getenv("SWIFT_ENABLE_INTERNAL_REFACTORING_ACTIONS");
|
|
|
|
#define RANGE_REFACTORING(KIND, NAME, ID) \
|
|
if (RefactoringAction##KIND::isApplicable(Result, DiagEngine)) \
|
|
Scratch.push_back(RefactoringKind::KIND);
|
|
#define INTERNAL_RANGE_REFACTORING(KIND, NAME, ID) \
|
|
if (enableInternalRefactoring) \
|
|
RANGE_REFACTORING(KIND, NAME, ID)
|
|
#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.");
|
|
}
|
|
llvm_unreachable("unhandled kind");
|
|
}
|
|
|
|
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::StringSet<> 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);
|
|
}
|