[SourceKit] Share implemention of local rename and related idents

Local rename and related identifiers were sufficiently similar that we can implement related identifiers in terms of local rename.
This commit is contained in:
Alex Hoppen
2023-11-13 15:58:21 -08:00
parent 698397c309
commit ada3076628
12 changed files with 201 additions and 256 deletions

View File

@@ -33,15 +33,33 @@ enum class RefactoringKind : int8_t {
#include "RefactoringKinds.def"
};
struct RangeConfig {
unsigned BufferID;
unsigned Line;
unsigned Column;
unsigned Length;
SourceLoc getStart(SourceManager &SM);
SourceLoc getEnd(SourceManager &SM);
enum class RefactorAvailableKind {
Available,
Unavailable_system_symbol,
Unavailable_has_no_location,
Unavailable_has_no_name,
Unavailable_has_no_accessibility,
Unavailable_decl_from_clang,
Unavailable_decl_in_macro,
};
struct RefactorAvailabilityInfo {
RefactoringKind Kind;
RefactorAvailableKind AvailableKind;
RefactorAvailabilityInfo(RefactoringKind Kind,
RefactorAvailableKind AvailableKind)
: Kind(Kind), AvailableKind(AvailableKind) {}
RefactorAvailabilityInfo(RefactoringKind Kind)
: RefactorAvailabilityInfo(Kind, RefactorAvailableKind::Available) {}
};
struct RenameInfo {
ValueDecl *VD;
RefactorAvailabilityInfo Availability;
};
llvm::Optional<RenameInfo> getRenameInfo(ResolvedCursorInfoPtr cursorInfo);
enum class NameUsage {
Unknown,
Reference,
@@ -59,7 +77,7 @@ struct RenameLoc {
/// This may not be known if the rename locations are specified by the client
/// using the a rename locations dicationary in syntactic rename.
///
/// May be empty.
/// May be empty if no new name was specified in `localRenameLocs`.
StringRef NewName;
const bool IsFunctionLike;
const bool IsNonProtocolType;
@@ -82,14 +100,33 @@ public:
};
/// Return the location to rename when renaming the identifier at \p startLoc
/// in \p sourceFile
/// in \p sourceFile.
///
/// - Parameters:
/// - sourceFile: The source file in which to perform local rename
/// - startLoc: The location of the identifier that should be renamed
/// - preferredName: The new name that should be assigned to the identifer
/// - diags: If errors occur, a diagnostic is added to this diagnostic engine.
RenameLocs localRenameLocs(SourceFile *sourceFile, SourceLoc startLoc,
StringRef preferredName, DiagnosticEngine &diags);
/// - renameInfo: Information about the symbol to rename. See `getRenameInfo`
/// - newName: The new name that should be assigned to the identifer. Can
/// be empty, in which case the new name of all `RenameLoc`s will also be
/// empty.
RenameLocs localRenameLocs(SourceFile *sourceFile, RenameInfo renameInfo,
StringRef newName);
/// Given a list of `RenameLoc`s, get the corresponding `ResolveLoc`s.
///
/// These resolve locations contain more structured information, such as the
/// range of the base name to rename and the ranges of the argument labels.
std::vector<ResolvedLoc> resolveRenameLocations(ArrayRef<RenameLoc> renameLocs,
SourceFile &sourceFile,
DiagnosticEngine &diags);
struct RangeConfig {
unsigned BufferID;
unsigned Line;
unsigned Column;
unsigned Length;
SourceLoc getStart(SourceManager &SM);
SourceLoc getEnd(SourceManager &SM);
};
struct RefactoringOptions {
RefactoringKind Kind;
@@ -105,26 +142,6 @@ struct RenameRangeDetail {
llvm::Optional<unsigned> Index;
};
enum class RefactorAvailableKind {
Available,
Unavailable_system_symbol,
Unavailable_has_no_location,
Unavailable_has_no_name,
Unavailable_has_no_accessibility,
Unavailable_decl_from_clang,
Unavailable_decl_in_macro,
};
struct RefactorAvailabilityInfo {
RefactoringKind Kind;
RefactorAvailableKind AvailableKind;
RefactorAvailabilityInfo(RefactoringKind Kind,
RefactorAvailableKind AvailableKind)
: Kind(Kind), AvailableKind(AvailableKind) {}
RefactorAvailabilityInfo(RefactoringKind Kind)
: RefactorAvailabilityInfo(Kind, RefactorAvailableKind::Available) {}
};
class FindRenameRangesConsumer {
public:
virtual void accept(SourceManager &SM, RegionType RegionType,

View File

@@ -10,7 +10,6 @@
//
//===----------------------------------------------------------------------===//
#include "LocalRename.h"
#include "RefactoringActions.h"
#include "swift/AST/DiagnosticsRefactoring.h"
#include "swift/AST/ParameterList.h"
@@ -129,7 +128,7 @@ renameAvailabilityInfo(const ValueDecl *VD,
/// 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) {
swift::ide::getRenameInfo(ResolvedCursorInfoPtr cursorInfo) {
auto valueCursor = dyn_cast<ResolvedValueRefCursorInfo>(cursorInfo);
if (!valueCursor)
return llvm::None;
@@ -138,6 +137,23 @@ swift::refactoring::getRenameInfo(ResolvedCursorInfoPtr cursorInfo) {
if (!VD)
return llvm::None;
if (auto *V = dyn_cast<VarDecl>(VD)) {
// Always use the canonical var decl for comparison. This is so we
// pick up all occurrences of x in case statements like the below:
// case .first(let x), .second(let x)
// fallthrough
// case .third(let x)
// print(x)
VD = V->getCanonicalVarDecl();
// If we have a property wrapper backing property or projected value, use
// the wrapped property instead (i.e. if this is _foo or $foo, pretend
// it's foo).
if (auto *Wrapped = V->getOriginalWrappedProperty()) {
VD = Wrapped;
}
}
llvm::Optional<RenameRefInfo> refInfo;
if (!valueCursor->getShorthandShadowedDecls().empty()) {
// Find the outermost decl for a shorthand if let/closure capture
@@ -273,8 +289,11 @@ bool RefactoringActionLocalRename::isApplicable(
Info->Availability.Kind == RefactoringKind::LocalRename;
}
static void analyzeRenameScope(ValueDecl *VD,
SmallVectorImpl<DeclContext *> &Scopes) {
/// Get the decl context that we need to walk when renaming \p VD.
///
/// This \c DeclContext contains all possible references to \c VD within the
/// file.
DeclContext *getRenameScope(ValueDecl *VD) {
auto *Scope = VD->getDeclContext();
// There may be sibling decls that the renamed symbol is visible from.
switch (Scope->getContextKind()) {
@@ -296,13 +315,18 @@ static void analyzeRenameScope(ValueDecl *VD,
break;
}
Scopes.push_back(Scope);
return Scope;
}
RenameLocs swift::ide::localRenameLocs(SourceFile *sourceFile,
SourceLoc startLoc,
StringRef preferredName,
DiagnosticEngine &diags) {
/// Get the `RenameInfo` at `startLoc` and validate that we can perform local
/// rename on it (e.g. checking that the original definition isn't a system
/// symbol).
///
/// If the validation succeeds, return the `RenameInfo`, otherwise add an error
/// to `diags` and return `None`.
static llvm::Optional<RenameInfo>
getRenameInfoForLocalRename(SourceFile *sourceFile, SourceLoc startLoc,
DiagnosticEngine &diags) {
auto cursorInfo = evaluateOrDefault(
sourceFile->getASTContext().evaluator,
CursorInfoRequest{CursorInfoOwner(sourceFile, startLoc)},
@@ -311,7 +335,7 @@ RenameLocs swift::ide::localRenameLocs(SourceFile *sourceFile,
llvm::Optional<RenameInfo> info = getRenameInfo(cursorInfo);
if (!info) {
diags.diagnose(startLoc, diag::unresolved_location);
return RenameLocs();
return llvm::None;
}
switch (info->Availability.AvailableKind) {
@@ -319,32 +343,46 @@ RenameLocs swift::ide::localRenameLocs(SourceFile *sourceFile,
break;
case RefactorAvailableKind::Unavailable_system_symbol:
diags.diagnose(startLoc, diag::decl_is_system_symbol, info->VD->getName());
return RenameLocs();
return llvm::None;
case RefactorAvailableKind::Unavailable_has_no_location:
diags.diagnose(startLoc, diag::value_decl_no_loc, info->VD->getName());
return RenameLocs();
return llvm::None;
case RefactorAvailableKind::Unavailable_has_no_name:
diags.diagnose(startLoc, diag::decl_has_no_name);
return RenameLocs();
return llvm::None;
case RefactorAvailableKind::Unavailable_has_no_accessibility:
diags.diagnose(startLoc, diag::decl_no_accessibility);
return RenameLocs();
return llvm::None;
case RefactorAvailableKind::Unavailable_decl_from_clang:
diags.diagnose(startLoc, diag::decl_from_clang);
return RenameLocs();
return llvm::None;
case RefactorAvailableKind::Unavailable_decl_in_macro:
diags.diagnose(startLoc, diag::decl_in_macro);
return RenameLocs();
return llvm::None;
}
SmallVector<DeclContext *, 8> scopes;
analyzeRenameScope(info->VD, scopes);
if (scopes.empty())
return RenameLocs();
return info;
}
RenameRangeCollector rangeCollector(info->VD, preferredName);
for (DeclContext *DC : scopes)
indexDeclContext(DC, rangeCollector);
RenameLocs swift::ide::localRenameLocs(SourceFile *SF, RenameInfo renameInfo,
StringRef newName) {
DeclContext *RenameScope = SF;
if (!RenameScope) {
// If the value is declared in a DeclContext that's a child of the file in
// which we are performing the rename, we can limit our analysis to this
// decl context.
//
// Cases where the rename scope is not a child of the source file include
// if we are getting related identifiers of a type A that is defined in
// another file. In this case, we need to analyze the entire file.
auto DeclarationScope = getRenameScope(renameInfo.VD);
if (DeclarationScope->isChildContextOf(SF)) {
RenameScope = DeclarationScope;
}
}
RenameRangeCollector rangeCollector(renameInfo.VD, newName);
indexDeclContext(RenameScope, rangeCollector);
return rangeCollector.takeResults();
}
@@ -364,8 +402,14 @@ bool RefactoringActionLocalRename::performChange() {
return true;
}
RenameLocs renameRanges =
localRenameLocs(TheFile, StartLoc, PreferredName, DiagEngine);
llvm::Optional<RenameInfo> info =
getRenameInfoForLocalRename(TheFile, StartLoc, DiagEngine);
if (!info) {
// getRenameInfoForLocalRename has already produced an error in `DiagEngine`
return true;
}
RenameLocs renameRanges = localRenameLocs(TheFile, *info, PreferredName);
if (renameRanges.getLocations().empty())
return true;
@@ -385,7 +429,14 @@ int swift::ide::findLocalRenameRanges(SourceFile *SF, RangeConfig Range,
Diags.addConsumer(DiagConsumer);
auto StartLoc = Lexer::getLocForStartOfToken(SM, Range.getStart(SM));
RenameLocs RenameRanges = localRenameLocs(SF, StartLoc, StringRef(), Diags);
llvm::Optional<RenameInfo> info =
getRenameInfoForLocalRename(SF, StartLoc, Diags);
if (!info) {
// getRenameInfoForLocalRename has already produced an error in `Diags`.
return true;
}
RenameLocs RenameRanges = localRenameLocs(SF, *info, StringRef());
if (RenameRanges.getLocations().empty())
return true;

View File

@@ -1,31 +0,0 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_REFACTORING_LOCALRENAME_H
#define SWIFT_REFACTORING_LOCALRENAME_H
#include "swift/Refactoring/Refactoring.h"
namespace swift {
namespace refactoring {
using namespace swift::ide;
struct RenameInfo {
ValueDecl *VD;
RefactorAvailabilityInfo Availability;
};
llvm::Optional<RenameInfo> getRenameInfo(ResolvedCursorInfoPtr cursorInfo);
} // namespace refactoring
} // namespace swift
#endif

View File

@@ -11,7 +11,6 @@
//===----------------------------------------------------------------------===//
#include "swift/Refactoring/Refactoring.h"
#include "LocalRename.h"
#include "RefactoringActions.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/SourceFile.h"

View File

@@ -147,9 +147,9 @@ public:
ArrayRef<Replacement> getReplacements() const { return Replacements; }
};
static std::vector<ResolvedLoc>
resolveRenameLocations(ArrayRef<RenameLoc> RenameLocs, SourceFile &SF,
DiagnosticEngine &Diags) {
std::vector<ResolvedLoc>
swift::ide::resolveRenameLocations(ArrayRef<RenameLoc> RenameLocs,
SourceFile &SF, DiagnosticEngine &Diags) {
SourceManager &SM = SF.getASTContext().SourceMgr;
unsigned BufferID = SF.getBufferID().value();

View File

@@ -1,4 +0,0 @@
class A {
var b: B
var c: B
}

View File

@@ -1,3 +0,0 @@
class B {
var a: A
}

View File

@@ -1,7 +1,24 @@
// RUN: %sourcekitd-test -req=related-idents -pos=2:10 %S/Inputs/implicit-vis/a.swift \
// RUN: -- %S/Inputs/implicit-vis/a.swift %S/Inputs/implicit-vis/b.swift -o implicit_vis.o | %FileCheck -check-prefix=CHECK1 %s
// RUN: %empty-directory(%t)
// RUN: split-file %s %t
//--- a.swift
class A {
var b: B
var c: B
}
//--- b.swift
class B {
var a: A
}
//--- dummy.swift
// RUN: %sourcekitd-test -req=related-idents -pos=3:10 %t/a.swift -- %t/a.swift %t/b.swift -o implicit_vis.o | %FileCheck -check-prefix=CHECK1 %s
// CHECK1: START RANGES
// CHECK1: 2:10 - 1
// CHECK1: 3:10 - 1
// CHECK1: 4:10 - 1
// CHECK1: END RANGES

View File

@@ -0,0 +1,5 @@
// RUN: %sourcekitd-test -req=related-idents -pos=%(line + 1):1 %s -- %s | %FileCheck %s
// CHECK: START RANGES
// CHECK-NEXT: END RANGES

View File

@@ -0,0 +1,13 @@
// RUN: %sourcekitd-test -req=related-idents -pos=%(line + 1):10 %s -- %s | %FileCheck %s
func foo(x: Int) {
#if true
print(x)
#else {
print(x)
#endif
}
// CHECK: START RANGES
// CHECK-NEXT: 2:10 - 1
// CHECK-NEXT: 4:9 - 1
// CHECK-NEXT: END RANGES

View File

@@ -0,0 +1,9 @@
// RUN: %sourcekitd-test -req=related-idents -pos=%(line + 1):13 %s -- %s | %FileCheck %s
func foo(x: String) {
let a: String = "abc"
}
// CHECK: START RANGES
// CHECK-NEXT: 2:13 - 6
// CHECK-NEXT: 3:10 - 6
// CHECK-NEXT: END RANGES

View File

@@ -2489,129 +2489,6 @@ SwiftLangSupport::findUSRRange(StringRef DocumentName, StringRef USR) {
// SwiftLangSupport::findRelatedIdentifiersInFile
//===----------------------------------------------------------------------===//
namespace {
class RelatedIdScanner : public SourceEntityWalker {
ValueDecl *Dcl;
llvm::SmallDenseSet<std::pair<unsigned, unsigned>, 8> &Ranges;
/// Declarations that are tied to the same name as \c Dcl and should thus also
/// be renamed if \c Dcl is renamed. Most notabliy this contains closure
/// captures like `[foo]`.
llvm::SmallVectorImpl<ValueDecl *> &RelatedDecls;
SourceManager &SourceMgr;
unsigned BufferID = -1;
bool Cancelled = false;
public:
explicit RelatedIdScanner(
SourceFile &SrcFile, unsigned BufferID, ValueDecl *D,
llvm::SmallDenseSet<std::pair<unsigned, unsigned>, 8> &Ranges,
llvm::SmallVectorImpl<ValueDecl *> &RelatedDecls)
: Ranges(Ranges), RelatedDecls(RelatedDecls),
SourceMgr(SrcFile.getASTContext().SourceMgr), BufferID(BufferID) {
if (auto *V = dyn_cast<VarDecl>(D)) {
// Always use the canonical var decl for comparison. This is so we
// pick up all occurrences of x in case statements like the below:
// case .first(let x), .second(let x)
// fallthrough
// case .third(let x)
// print(x)
Dcl = V->getCanonicalVarDecl();
// If we have a property wrapper backing property or projected value, use
// the wrapped property instead (i.e. if this is _foo or $foo, pretend
// it's foo).
if (auto *Wrapped = V->getOriginalWrappedProperty()) {
Dcl = Wrapped;
}
} else {
Dcl = D;
}
}
private:
bool walkToExprPre(Expr *E) override {
if (Cancelled)
return false;
// Check if there are closure captures like `[foo]` where the caputred
// variable should also be renamed
if (auto CaptureList = dyn_cast<CaptureListExpr>(E)) {
for (auto ShorthandShadow : getShorthandShadows(CaptureList)) {
if (ShorthandShadow.first == Dcl) {
RelatedDecls.push_back(ShorthandShadow.second);
} else if (ShorthandShadow.second == Dcl) {
RelatedDecls.push_back(ShorthandShadow.first);
}
}
}
return true;
}
bool walkToStmtPre(Stmt *S) override {
if (Cancelled)
return false;
if (auto CondStmt = dyn_cast<LabeledConditionalStmt>(S)) {
for (auto ShorthandShadow : getShorthandShadows(CondStmt)) {
if (ShorthandShadow.first == Dcl) {
RelatedDecls.push_back(ShorthandShadow.second);
} else if (ShorthandShadow.second == Dcl) {
RelatedDecls.push_back(ShorthandShadow.first);
}
}
}
return true;
}
bool walkToDeclPre(Decl *D, CharSourceRange Range) override {
if (Cancelled)
return false;
if (auto *V = dyn_cast<VarDecl>(D)) {
// Handle references to the implicitly generated vars in case statements
// matching multiple patterns
D = V->getCanonicalVarDecl();
}
if (D == Dcl)
return passId(Range);
return true;
}
bool visitDeclReference(ValueDecl *D, CharSourceRange Range,
TypeDecl *CtorTyRef, ExtensionDecl *ExtTyRef, Type T,
ReferenceMetaData Data) override {
if (Cancelled)
return false;
if (auto *V = dyn_cast<VarDecl>(D)) {
D = V->getCanonicalVarDecl();
// If we have a property wrapper backing property or projected value, use
// the wrapped property for comparison instead (i.e. if this is _foo or
// $foo, pretend it's foo).
if (auto *Wrapped = V->getOriginalWrappedProperty()) {
assert(Range.getByteLength() > 1 &&
(Range.str().front() == '_' || Range.str().front() == '$'));
D = Wrapped;
Range = CharSourceRange(Range.getStart().getAdvancedLoc(1), Range.getByteLength() - 1);
}
} else if (CtorTyRef) {
D = CtorTyRef;
}
if (D == Dcl)
return passId(Range);
return true;
}
bool passId(CharSourceRange Range) {
unsigned Offset = SourceMgr.getLocOffsetInBuffer(Range.getStart(),BufferID);
Ranges.insert({Offset, Range.getByteLength()});
return !Cancelled;
}
};
} // end anonymous namespace
void SwiftLangSupport::findRelatedIdentifiersInFile(
StringRef PrimaryFilePath, StringRef InputBufferName, unsigned Offset,
bool CancelOnSubsequentRequest, ArrayRef<const char *> Args,
@@ -2661,6 +2538,8 @@ void SwiftLangSupport::findRelatedIdentifiersInFile(
if (Loc.isInvalid())
return;
SourceManager &SrcMgr = CompInst.getASTContext().SourceMgr;
ResolvedCursorInfoPtr CursorInfo =
evaluateOrDefault(CompInst.getASTContext().evaluator,
CursorInfoRequest{CursorInfoOwner(SrcFile, Loc)},
@@ -2684,40 +2563,33 @@ void SwiftLangSupport::findRelatedIdentifiersInFile(
if (VD->isOperator())
return;
llvm::Optional<RenameInfo> Info = getRenameInfo(CursorInfo);
if (!Info) {
return;
}
RenameLocs Locs =
localRenameLocs(SrcFile, *Info, /*newName=*/StringRef());
// Ignore any errors produced by `resolveRenameLocations` since, if some
// symbol failed to resolve, we still want to return all the other
// symbols. This makes related idents more fault-tolerant.
DiagnosticEngine Diags(SrcMgr);
std::vector<ResolvedLoc> ResolvedLocs =
resolveRenameLocations(Locs.getLocations(), *SrcFile, Diags);
// Record ranges in a set first so we don't record some ranges twice.
// This could happen in capture lists where e.g. `[foo]` is both the
// reference of the captured variable and the declaration of the
// variable usable in the closure.
llvm::SmallDenseSet<std::pair<unsigned, unsigned>, 8> RangesSet;
// List of decls whose ranges should be reported as related identifiers.
SmallVector<ValueDecl *, 2> Worklist;
Worklist.push_back(VD);
// Decls that we have already visited, so we don't walk circles.
SmallPtrSet<ValueDecl *, 2> VisitedDecls;
while (!Worklist.empty()) {
ValueDecl *Dcl = Worklist.back();
Worklist.pop_back();
if (!VisitedDecls.insert(Dcl).second) {
// We have already visited this decl. Don't visit it again.
continue;
}
RelatedIdScanner Scanner(*SrcFile, BufferID, Dcl, RangesSet,
Worklist);
if (auto *Case = getCaseStmtOfCanonicalVar(Dcl)) {
Scanner.walk(Case);
while ((Case = Case->getFallthroughDest().getPtrOrNull())) {
Scanner.walk(Case);
}
} else if (DeclContext *LocalDC =
Dcl->getDeclContext()->getLocalContext()) {
Scanner.walk(LocalDC);
} else {
Scanner.walk(*SrcFile);
}
for (auto ResolvedLoc : ResolvedLocs) {
unsigned Offset = SrcMgr.getLocOffsetInBuffer(
ResolvedLoc.Range.getStart(), BufferID);
RangesSet.insert({Offset, ResolvedLoc.Range.getByteLength()});
}
// Sort ranges so we get deterministic output.