Files
swift-mirror/lib/Refactoring/LocalRename.cpp
2023-09-11 17:14:29 -07:00

640 lines
23 KiB
C++

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 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 "LocalRename.h"
#include "RefactoringActions.h"
#include "Renamer.h"
#include "swift/AST/DiagnosticsRefactoring.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/USRGeneration.h"
#include "swift/Basic/StringExtras.h"
#include "swift/Index/Index.h"
using namespace swift::refactoring;
using namespace swift::index;
class TextReplacementsRenamer : public Renamer {
llvm::StringSet<> &ReplaceTextContext;
SmallVector<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((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((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((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({/*Path=*/{}, LabelRange, /*BufferName=*/{}, Text,
/*RegionsWorthNote=*/{}});
}
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({/*Path=*/{}, Range, /*BufferName=*/{},
registerText(New.base()),
/*RegionsWorthNote=*/{}});
}
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());
}
ArrayRef<Replacement> getReplacements() const { return Replacements; }
};
static const ValueDecl *getRelatedSystemDecl(const ValueDecl *VD) {
if (VD->getModuleContext()->isNonUserModule())
return VD;
for (auto *Req : VD->getSatisfiedProtocolRequirements()) {
if (Req->getModuleContext()->isNonUserModule())
return Req;
}
for (auto Over = VD->getOverriddenDecl(); Over;
Over = Over->getOverriddenDecl()) {
if (Over->getModuleContext()->isNonUserModule())
return Over;
}
return nullptr;
}
/// Stores information about the reference that rename availability is being
/// queried on.
struct RenameRefInfo {
SourceFile *SF; ///< The source file containing the reference.
SourceLoc Loc; ///< The reference's source location.
bool IsArgLabel; ///< Whether Loc is on an arg label, rather than base name.
};
static llvm::Optional<RefactorAvailabilityInfo>
renameAvailabilityInfo(const ValueDecl *VD,
llvm::Optional<RenameRefInfo> RefInfo) {
RefactorAvailableKind AvailKind = RefactorAvailableKind::Available;
if (getRelatedSystemDecl(VD)) {
AvailKind = RefactorAvailableKind::Unavailable_system_symbol;
} else if (VD->getClangDecl()) {
AvailKind = RefactorAvailableKind::Unavailable_decl_from_clang;
} else if (!VD->hasName()) {
AvailKind = RefactorAvailableKind::Unavailable_has_no_name;
}
auto isInMacroExpansionBuffer = [](const ValueDecl *VD) -> bool {
auto *module = VD->getModuleContext();
auto *file = module->getSourceFileContainingLocation(VD->getLoc());
if (!file)
return false;
return file->getFulfilledMacroRole() != llvm::None;
};
if (AvailKind == RefactorAvailableKind::Available) {
SourceLoc Loc = VD->getLoc();
if (!Loc.isValid()) {
AvailKind = RefactorAvailableKind::Unavailable_has_no_location;
} else if (isInMacroExpansionBuffer(VD)) {
AvailKind = RefactorAvailableKind::Unavailable_decl_in_macro;
}
}
if (isa<AbstractFunctionDecl>(VD)) {
// Disallow renaming accessors.
if (isa<AccessorDecl>(VD))
return llvm::None;
// Disallow renaming deinit.
if (isa<DestructorDecl>(VD))
return llvm::None;
// Disallow renaming init with no arguments.
if (auto CD = dyn_cast<ConstructorDecl>(VD)) {
if (!CD->getParameters()->size())
return llvm::None;
if (RefInfo && !RefInfo->IsArgLabel) {
NameMatcher Matcher(*(RefInfo->SF));
auto Resolved = Matcher.resolve({RefInfo->Loc, /*ResolveArgs*/ true});
if (Resolved.LabelRanges.empty())
return llvm::None;
}
}
// Disallow renaming 'callAsFunction' method with no arguments.
if (auto FD = dyn_cast<FuncDecl>(VD)) {
// FIXME: syntactic rename can only decide by checking the spelling, not
// whether it's an instance method, so we do the same here for now.
if (FD->getBaseIdentifier() == FD->getASTContext().Id_callAsFunction) {
if (!FD->getParameters()->size())
return llvm::None;
if (RefInfo && !RefInfo->IsArgLabel) {
NameMatcher Matcher(*(RefInfo->SF));
auto Resolved = Matcher.resolve({RefInfo->Loc, /*ResolveArgs*/ true});
if (Resolved.LabelRanges.empty())
return llvm::None;
}
}
}
}
// Always return local rename for parameters.
// FIXME: if the cursor is on the argument, we should return global rename.
if (isa<ParamDecl>(VD))
return RefactorAvailabilityInfo{RefactoringKind::LocalRename, AvailKind};
// If the indexer considers VD a global symbol, then we apply global rename.
if (index::isLocalSymbol(VD))
return RefactorAvailabilityInfo{RefactoringKind::LocalRename, AvailKind};
return RefactorAvailabilityInfo{RefactoringKind::GlobalRename, AvailKind};
}
/// Given a cursor, return the decl and its rename availability. \c None if
/// the cursor did not resolve to a decl or it resolved to a decl that we do
/// not allow renaming on.
llvm::Optional<RenameInfo>
swift::refactoring::getRenameInfo(ResolvedCursorInfoPtr cursorInfo) {
auto valueCursor = dyn_cast<ResolvedValueRefCursorInfo>(cursorInfo);
if (!valueCursor)
return llvm::None;
ValueDecl *VD = valueCursor->typeOrValue();
if (!VD)
return llvm::None;
llvm::Optional<RenameRefInfo> refInfo;
if (!valueCursor->getShorthandShadowedDecls().empty()) {
// Find the outermost decl for a shorthand if let/closure capture
VD = valueCursor->getShorthandShadowedDecls().back();
} else if (valueCursor->isRef()) {
refInfo = {valueCursor->getSourceFile(), valueCursor->getLoc(),
valueCursor->isKeywordArgument()};
}
llvm::Optional<RefactorAvailabilityInfo> info =
renameAvailabilityInfo(VD, refInfo);
if (!info)
return llvm::None;
return RenameInfo{VD, *info};
}
class RenameRangeCollector : public IndexDataConsumer {
public:
RenameRangeCollector(StringRef USR, StringRef newName)
: USR(USR), newName(newName) {}
RenameRangeCollector(const ValueDecl *D, StringRef newName)
: newName(newName) {
SmallString<64> SS;
llvm::raw_svector_ostream OS(SS);
printValueDeclUSR(D, OS);
USR = stringStorage.copyString(SS.str());
}
RenameRangeCollector(RenameRangeCollector &&collector) = default;
ArrayRef<RenameLoc> results() const { return locations; }
private:
bool indexLocals() override { return true; }
void failed(StringRef error) override {}
bool startDependency(StringRef name, StringRef path, bool isClangModule,
bool isSystem) override {
return true;
}
bool finishDependency(bool isClangModule) override { return true; }
Action startSourceEntity(const IndexSymbol &symbol) override {
if (symbol.USR == USR) {
if (auto loc = indexSymbolToRenameLoc(symbol, newName)) {
// Inside capture lists like `{ [test] in }`, 'test' refers to both the
// newly declared, captured variable and the referenced variable it is
// initialized from. Make sure to only rename it once.
auto existingLoc = llvm::find_if(locations, [&](RenameLoc searchLoc) {
return searchLoc.Line == loc->Line && searchLoc.Column == loc->Column;
});
if (existingLoc == locations.end()) {
locations.push_back(std::move(*loc));
} else {
assert(existingLoc->OldName == loc->OldName &&
existingLoc->NewName == loc->NewName &&
existingLoc->IsFunctionLike == loc->IsFunctionLike &&
existingLoc->IsNonProtocolType == loc->IsNonProtocolType &&
"Asked to do a different rename for the same location?");
}
}
}
return IndexDataConsumer::Continue;
}
bool finishSourceEntity(SymbolInfo symInfo, SymbolRoleSet roles) override {
return true;
}
llvm::Optional<RenameLoc>
indexSymbolToRenameLoc(const index::IndexSymbol &symbol, StringRef NewName);
private:
StringRef USR;
StringRef newName;
StringScratchSpace stringStorage;
std::vector<RenameLoc> locations;
};
llvm::Optional<RenameLoc>
RenameRangeCollector::indexSymbolToRenameLoc(const index::IndexSymbol &symbol,
StringRef newName) {
if (symbol.roles & (unsigned)index::SymbolRole::Implicit) {
return llvm::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};
}
bool RefactoringActionLocalRename::isApplicable(
ResolvedCursorInfoPtr CursorInfo, DiagnosticEngine &Diag) {
llvm::Optional<RenameInfo> Info = getRenameInfo(CursorInfo);
return Info &&
Info->Availability.AvailableKind == RefactorAvailableKind::Available &&
Info->Availability.Kind == RefactoringKind::LocalRename;
}
static void analyzeRenameScope(ValueDecl *VD,
SmallVectorImpl<DeclContext *> &Scopes) {
auto *Scope = VD->getDeclContext();
// There may be sibling decls that the renamed symbol is visible from.
switch (Scope->getContextKind()) {
case DeclContextKind::GenericTypeDecl:
case DeclContextKind::ExtensionDecl:
case DeclContextKind::TopLevelCodeDecl:
case DeclContextKind::SubscriptDecl:
case DeclContextKind::EnumElementDecl:
case DeclContextKind::AbstractFunctionDecl:
Scope = Scope->getParent();
break;
case DeclContextKind::AbstractClosureExpr:
case DeclContextKind::Initializer:
case DeclContextKind::SerializedLocal:
case DeclContextKind::Package:
case DeclContextKind::Module:
case DeclContextKind::FileUnit:
case DeclContextKind::MacroDecl:
break;
}
Scopes.push_back(Scope);
}
static llvm::Optional<RenameRangeCollector>
localRenames(SourceFile *SF, SourceLoc startLoc, StringRef preferredName,
DiagnosticEngine &diags) {
auto cursorInfo =
evaluateOrDefault(SF->getASTContext().evaluator,
CursorInfoRequest{CursorInfoOwner(SF, startLoc)},
new ResolvedCursorInfo());
llvm::Optional<RenameInfo> info = getRenameInfo(cursorInfo);
if (!info) {
diags.diagnose(startLoc, diag::unresolved_location);
return llvm::None;
}
switch (info->Availability.AvailableKind) {
case RefactorAvailableKind::Available:
break;
case RefactorAvailableKind::Unavailable_system_symbol:
diags.diagnose(startLoc, diag::decl_is_system_symbol, info->VD->getName());
return llvm::None;
case RefactorAvailableKind::Unavailable_has_no_location:
diags.diagnose(startLoc, diag::value_decl_no_loc, info->VD->getName());
return llvm::None;
case RefactorAvailableKind::Unavailable_has_no_name:
diags.diagnose(startLoc, diag::decl_has_no_name);
return llvm::None;
case RefactorAvailableKind::Unavailable_has_no_accessibility:
diags.diagnose(startLoc, diag::decl_no_accessibility);
return llvm::None;
case RefactorAvailableKind::Unavailable_decl_from_clang:
diags.diagnose(startLoc, diag::decl_from_clang);
return llvm::None;
case RefactorAvailableKind::Unavailable_decl_in_macro:
diags.diagnose(startLoc, diag::decl_in_macro);
return llvm::None;
}
SmallVector<DeclContext *, 8> scopes;
analyzeRenameScope(info->VD, scopes);
if (scopes.empty())
return llvm::None;
RenameRangeCollector rangeCollector(info->VD, preferredName);
for (DeclContext *DC : scopes)
indexDeclContext(DC, rangeCollector);
return rangeCollector;
}
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;
}
llvm::Optional<RenameRangeCollector> rangeCollector =
localRenames(TheFile, StartLoc, PreferredName, DiagEngine);
if (!rangeCollector)
return true;
auto consumers = DiagEngine.takeConsumers();
assert(consumers.size() == 1);
return syntacticRename(TheFile, rangeCollector->results(), EditConsumer,
*consumers[0]);
}
static std::vector<ResolvedLoc>
resolveRenameLocations(ArrayRef<RenameLoc> RenameLocs, SourceFile &SF,
DiagnosticEngine &Diags) {
SourceManager &SM = SF.getASTContext().SourceMgr;
unsigned BufferID = SF.getBufferID().value();
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, llvm::None);
} else {
EditConsumer.accept(SM, Type, Renamer.getReplacements());
}
}
return false;
}
int swift::ide::findSyntacticRenameRanges(
SourceFile *SF, 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, llvm::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));
llvm::Optional<RenameRangeCollector> RangeCollector =
localRenames(SF, StartLoc, StringRef(), Diags);
if (!RangeCollector)
return true;
return findSyntacticRenameRanges(SF, RangeCollector->results(),
RenameConsumer, DiagConsumer);
}