Skip to content

Commit 5cae50a

Browse files
committed
[SourceKit] Refactor addSyntacticRenameRanges
Refactor `addSyntacticRenameRanges`, adding comments to make it easier to follow and remove its dependency on the `IsFunctionLike` parameter in `RenameLoc`.
1 parent db8ca19 commit 5cae50a

File tree

6 files changed

+153
-71
lines changed

6 files changed

+153
-71
lines changed

include/swift/IDE/IDEBridging.h

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,38 @@
2323
#endif
2424

2525
enum class LabelRangeType {
26+
/// The matched location did not have any arguments.
2627
None,
2728

29+
/// The argument of a function/initializer/macro/... call
30+
///
31+
/// ### Example
2832
/// `foo([a: ]2) or .foo([a: ]String)`
2933
CallArg,
3034

31-
/// `func([a b]: Int)`
35+
/// The parameter of a function/initializer/macro/... declaration
36+
///
37+
/// ### Example
38+
/// `func foo([a b]: Int)`
3239
Param,
3340

41+
/// Parameters of a function that can't be collapsed if they are the same.
42+
///
43+
/// This is the case for parameters of subscripts since subscripts have
44+
/// unnamed
45+
/// parameters by default.
46+
///
47+
/// ### Example
3448
/// `subscript([a a]: Int)`
3549
NoncollapsibleParam,
3650

37-
/// `#selector(foo.func([a]:))`
38-
Selector,
51+
/// A reference to a function that also specifies argument labels to
52+
/// disambiguate.
53+
///
54+
/// ### Examples
55+
/// - `#selector(foo.func([a]:))`
56+
/// - `foo.func([a]:)`
57+
CompoundName,
3958
};
4059

4160
enum class ResolvedLocContext { Default, Selector, Comment, StringLiteral };

lib/ASTGen/Sources/SwiftIDEUtilsBridging/NameMatcherBridging.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ fileprivate extension IDEBridging.LabelRangeType {
5151
case .call: self = .CallArg
5252
case .parameters: self = .Param
5353
case .noncollapsibleParameters: self = .NoncollapsibleParam
54-
case .selector: self = .Selector
54+
case .selector: self = .CompoundName
5555
}
5656
}
5757
}

lib/IDE/SwiftSourceDocInfo.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,8 +612,9 @@ bool NameMatcher::tryResolve(ASTWalker::ParentTy Node, DeclNameLoc NameLoc,
612612

613613
if (NameLoc.isCompound()) {
614614
auto Labels = getSelectorLabelRanges(getSourceMgr(), NameLoc);
615-
bool Resolved = tryResolve(Node, NameLoc.getBaseNameLoc(),
616-
LabelRangeType::Selector, Labels, llvm::None);
615+
bool Resolved =
616+
tryResolve(Node, NameLoc.getBaseNameLoc(), LabelRangeType::CompoundName,
617+
Labels, llvm::None);
617618
if (!isDone()) {
618619
for (auto Label: Labels) {
619620
if (tryResolve(Node, Label.getStart())) {

lib/Refactoring/SyntacticRenameRangeDetails.cpp

Lines changed: 125 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ class RenameRangeDetailCollector {
5252
llvm::Optional<unsigned> FirstTrailingLabel,
5353
LabelRangeType RangeType, bool isCallSite);
5454

55-
bool isOperator() const { return Lexer::isOperator(Old.base()); }
56-
5755
private:
5856
/// Returns the range of the (possibly escaped) identifier at the start of
5957
/// \p Range and updates \p IsEscaped to indicate whether it's escaped or not.
@@ -151,7 +149,7 @@ void RenameRangeDetailCollector::splitAndRenameLabel(CharSourceRange Range,
151149
case LabelRangeType::NoncollapsibleParam:
152150
return splitAndRenameParamLabel(Range, NameIndex,
153151
/*IsCollapsible=*/false);
154-
case LabelRangeType::Selector:
152+
case LabelRangeType::CompoundName:
155153
return addRenameRange(Range, RefactoringRangeKind::SelectorArgumentLabel,
156154
NameIndex);
157155
case LabelRangeType::None:
@@ -250,7 +248,7 @@ bool RenameRangeDetailCollector::labelRangeMatches(CharSourceRange Range,
250248
LLVM_FALLTHROUGH;
251249
case LabelRangeType::CallArg:
252250
case LabelRangeType::Param:
253-
case LabelRangeType::Selector:
251+
case LabelRangeType::CompoundName:
254252
return ExistingLabel == (Expected.empty() ? "_" : Expected);
255253
case LabelRangeType::None:
256254
llvm_unreachable("Unhandled label range type");
@@ -277,12 +275,12 @@ bool RenameRangeDetailCollector::renameLabelsLenient(
277275
if (OldNames.empty())
278276
return true;
279277

280-
while (!labelRangeMatches(Label, LabelRangeType::Selector,
278+
while (!labelRangeMatches(Label, LabelRangeType::CompoundName,
281279
OldNames.back())) {
282280
if ((OldNames = OldNames.drop_back()).empty())
283281
return true;
284282
}
285-
splitAndRenameLabel(Label, LabelRangeType::Selector,
283+
splitAndRenameLabel(Label, LabelRangeType::CompoundName,
286284
OldNames.size() - 1);
287285
OldNames = OldNames.drop_back();
288286
continue;
@@ -297,7 +295,7 @@ bool RenameRangeDetailCollector::renameLabelsLenient(
297295
if ((OldNames = OldNames.drop_back()).empty())
298296
return true;
299297
}
300-
splitAndRenameLabel(Label, LabelRangeType::Selector,
298+
splitAndRenameLabel(Label, LabelRangeType::CompoundName,
301299
OldNames.size() - 1);
302300
OldNames = OldNames.drop_back();
303301
continue;
@@ -366,89 +364,153 @@ RegionType RenameRangeDetailCollector::getSyntacticRenameRegionType(
366364
}
367365
}
368366

369-
RegionType RenameRangeDetailCollector::addSyntacticRenameRanges(
370-
const ResolvedLoc &Resolved, const RenameLoc &Config) {
367+
enum class SpecialBaseName {
368+
/// The function does not have one of the special base names.
369+
None,
370+
Subscript,
371+
Init,
372+
CallAsFunction
373+
};
371374

372-
if (!Resolved.range.isValid())
373-
return RegionType::Unmatched;
375+
static SpecialBaseName specialBaseNameFor(const DeclNameViewer &declName) {
376+
if (!declName.isFunction()) {
377+
return SpecialBaseName::None;
378+
}
379+
if (declName.base() == "subscript") {
380+
// FIXME: Don't handle as special name if it is backticked.
381+
return SpecialBaseName::Subscript;
382+
} else if (declName.base() == "init") {
383+
// FIXME: Don't handle as special name if it is backticked.
384+
return SpecialBaseName::Init;
385+
} else if (declName.base() == "callAsFunction") {
386+
// FIXME: this should only be treated specially for instance methods.
387+
return SpecialBaseName::CallAsFunction;
388+
} else {
389+
return SpecialBaseName::None;
390+
}
391+
}
374392

375-
auto RegionKind = getSyntacticRenameRegionType(Resolved);
376-
// Don't include unknown references coming from active code; if we don't
377-
// have a semantic NameUsage for them, then they're likely unrelated symbols
378-
// that happen to have the same name.
379-
if (RegionKind == RegionType::ActiveCode &&
380-
Config.Usage == NameUsage::Unknown)
381-
return RegionType::Unmatched;
393+
RegionType RenameRangeDetailCollector::addSyntacticRenameRanges(
394+
const ResolvedLoc &resolved, const RenameLoc &config) {
382395

383-
assert(Config.Usage != NameUsage::Call || Config.IsFunctionLike);
396+
if (!resolved.range.isValid())
397+
return RegionType::Unmatched;
384398

385-
// FIXME: handle escaped keyword names `init`
386-
bool IsSubscript = Old.base() == "subscript" && Config.IsFunctionLike;
387-
bool IsInit = Old.base() == "init" && Config.IsFunctionLike;
399+
NameUsage usage = config.Usage;
388400

389-
// FIXME: this should only be treated specially for instance methods.
390-
bool IsCallAsFunction =
391-
Old.base() == "callAsFunction" && Config.IsFunctionLike;
401+
auto regionKind = getSyntacticRenameRegionType(resolved);
392402

393-
bool IsSpecialBase = IsInit || IsSubscript || IsCallAsFunction;
403+
SpecialBaseName specialBaseName = specialBaseNameFor(Old);
394404

395-
// Filter out non-semantic special basename locations with no labels.
396-
// We've already filtered out those in active code, so these are
397-
// any appearance of just 'init', 'subscript', or 'callAsFunction' in
398-
// strings, comments, and inactive code.
399-
if (IsSpecialBase && (Config.Usage == NameUsage::Unknown &&
400-
Resolved.labelType == LabelRangeType::None))
401-
return RegionType::Unmatched;
405+
if (usage == NameUsage::Unknown) {
406+
// Unknown name usage occurs if we don't have an entry in the index that
407+
// tells us whether the location is a call, reference or a definition. The
408+
// most common reasons why this happens is if the editor is adding syntactic
409+
// results (eg. from comments or string literals).
410+
//
411+
// Determine whether we should include them.
412+
if (regionKind == RegionType::ActiveCode) {
413+
// If the reference is in active code, we should have had a name usage
414+
// from the index. Since we don't, they are likely unrelated symbols that
415+
// happen to have the same name. Don't return them as matching ranges.
416+
return RegionType::Unmatched;
417+
}
418+
if (specialBaseName != SpecialBaseName::None &&
419+
resolved.labelType == LabelRangeType::None) {
420+
// Filter out non-semantic special basename locations with no labels.
421+
// We've already filtered out those in active code, so these are
422+
// any appearance of just 'init', 'subscript', or 'callAsFunction' in
423+
// strings, comments, and inactive code.
424+
return RegionType::Unmatched;
425+
}
426+
}
402427

403-
if (!Config.IsFunctionLike || !IsSpecialBase) {
404-
if (renameBase(Resolved.range, RefactoringRangeKind::BaseName))
428+
switch (specialBaseName) {
429+
case SpecialBaseName::None:
430+
// If we don't have a special base name, we can just rename it.
431+
if (renameBase(resolved.range, RefactoringRangeKind::BaseName)) {
405432
return RegionType::Mismatch;
406-
407-
} else if (IsInit || IsCallAsFunction) {
408-
if (renameBase(Resolved.range, RefactoringRangeKind::KeywordBaseName)) {
409-
// The base name doesn't need to match (but may) for calls, but
410-
// it should for definitions and references.
411-
if (Config.Usage == NameUsage::Definition ||
412-
Config.Usage == NameUsage::Reference) {
433+
}
434+
break;
435+
case SpecialBaseName::Init:
436+
case SpecialBaseName::CallAsFunction:
437+
if (renameBase(resolved.range, RefactoringRangeKind::KeywordBaseName)) {
438+
// The base name doesn't need to match for calls, for example because
439+
// an initializer can be called as `MyType()` and `callAsFunction` can
440+
// be called as `myStruct()`, so even if the base fails to be renamed,
441+
// continue.
442+
// But the names do need to match for definitions and references.
443+
if (usage == NameUsage::Definition || usage == NameUsage::Reference) {
413444
return RegionType::Mismatch;
414445
}
415446
}
416-
} else if (IsSubscript && Config.Usage == NameUsage::Definition) {
417-
if (renameBase(Resolved.range, RefactoringRangeKind::KeywordBaseName))
418-
return RegionType::Mismatch;
447+
break;
448+
case SpecialBaseName::Subscript:
449+
// Only try renaming the base for definitions of the subscript.
450+
// Accesses to the subscript are modelled as references with `[` as the
451+
// base name, which does not match. Subscripts are never called in the
452+
// index.
453+
if (usage == NameUsage::Definition) {
454+
if (renameBase(resolved.range, RefactoringRangeKind::KeywordBaseName)) {
455+
return RegionType::Mismatch;
456+
}
457+
}
458+
break;
419459
}
420460

421-
bool HandleLabels = false;
422-
if (Config.IsFunctionLike) {
423-
switch (Config.Usage) {
461+
bool handleLabels = false;
462+
bool isCallSite = false;
463+
if (Old.isFunction()) {
464+
switch (usage) {
424465
case NameUsage::Call:
425-
HandleLabels = !isOperator();
466+
// All calls except for operators have argument labels that should be
467+
// renamed.
468+
handleLabels = !Lexer::isOperator(Old.base());
469+
isCallSite = true;
426470
break;
427471
case NameUsage::Definition:
428-
HandleLabels = true;
472+
// All function definitions have argument labels that should be renamed.
473+
handleLabels = true;
474+
isCallSite = false;
429475
break;
430476
case NameUsage::Reference:
431-
HandleLabels =
432-
Resolved.labelType == LabelRangeType::Selector || IsSubscript;
477+
if (resolved.labelType == LabelRangeType::CompoundName) {
478+
// If we have a compound name that specifies argument labels to
479+
// disambiguate functions with the same base name, we always need to
480+
// rename the labels.
481+
handleLabels = true;
482+
isCallSite = false;
483+
} else if (specialBaseName == SpecialBaseName::Subscript) {
484+
// Accesses to a subscript are modeled using a reference to the
485+
// subscript (with base name `[`). We always need to rename argument
486+
// labels here.
487+
handleLabels = true;
488+
isCallSite = true;
489+
} else {
490+
handleLabels = false;
491+
isCallSite = false;
492+
}
433493
break;
434494
case NameUsage::Unknown:
435-
HandleLabels = Resolved.labelType != LabelRangeType::None;
495+
// If we don't know where the function is used, fall back to trying to
496+
// rename labels if there are some.
497+
handleLabels = resolved.labelType != LabelRangeType::None;
498+
isCallSite = resolved.labelType == LabelRangeType::CallArg;
436499
break;
437500
}
438501
}
439502

440-
if (HandleLabels) {
441-
bool isCallSite = Config.Usage != NameUsage::Definition &&
442-
(Config.Usage != NameUsage::Reference || IsSubscript) &&
443-
Resolved.labelType == LabelRangeType::CallArg;
444-
445-
if (renameLabels(Resolved.labelRanges, Resolved.firstTrailingLabel,
446-
Resolved.labelType, isCallSite))
447-
return Config.Usage == NameUsage::Unknown ? RegionType::Unmatched
448-
: RegionType::Mismatch;
503+
if (handleLabels) {
504+
bool renameLabelsFailed =
505+
renameLabels(resolved.labelRanges, resolved.firstTrailingLabel,
506+
resolved.labelType, isCallSite);
507+
if (renameLabelsFailed) {
508+
return usage == NameUsage::Unknown ? RegionType::Unmatched
509+
: RegionType::Mismatch;
510+
}
449511
}
450512

451-
return RegionKind;
513+
return regionKind;
452514
}
453515

454516
} // end anonymous namespace

test/refactoring/SyntacticRename/functions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ _ = memberwise . /*memberwise-x:ref*/x
8989
// RUN: diff -u %S/Outputs/functions/import.swift.expected %t.ranges/functions_import.swift
9090
// RUN: %refactor -find-rename-ranges -source-filename %s -pos="bar" -is-function-like -old-name "bar(_:)" >> %t.ranges/functions_bar.swift
9191
// RUN: diff -u %S/Outputs/functions/bar.swift.expected %t.ranges/functions_bar.swift
92-
// RUN: %refactor -find-rename-ranges -source-filename %s -pos="no-args" -is-function-like -old-name "aFunc" >> %t.ranges/functions_no-args.swift
92+
// RUN: %refactor -find-rename-ranges -source-filename %s -pos="no-args" -is-function-like -old-name "aFunc()" >> %t.ranges/functions_no-args.swift
9393
// RUN: diff -u %S/Outputs/functions/no-args.swift.expected %t.ranges/functions_no-args.swift
9494
// RUN: %refactor -find-rename-ranges -source-filename %s -pos="param-label" -is-function-like -old-name "aFunc(a:)" >> %t.ranges/functions_param-label.swift
9595
// RUN: diff -u %S/Outputs/functions/param-label.swift.expected %t.ranges/functions_param-label.swift

test/refactoring/SyntacticRename/textual.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ _ = /*MyClass:unknown*/MyClass()
3838

3939
// REQUIRES: swift_swift_parser
4040
// RUN: %empty-directory(%t.ranges)
41-
// RUN: %refactor -find-rename-ranges -source-filename %s -pos="foo" -is-function-like -old-name "foo" >> %t.ranges/textual_foo.swift
41+
// RUN: %refactor -find-rename-ranges -source-filename %s -pos="foo" -is-function-like -old-name "foo()" >> %t.ranges/textual_foo.swift
4242
// RUN: diff -u %S/Outputs/textual/foo.swift.expected %t.ranges/textual_foo.swift
4343
// RUN: %refactor -find-rename-ranges -source-filename %s -pos="MyClass" -is-non-protocol-type -old-name "MyClass" -new-name "YourClass" >> %t.ranges/textual_MyClass.swift
4444
// RUN: diff -u %S/Outputs/textual/MyClass.swift.expected %t.ranges/textual_MyClass.swift

0 commit comments

Comments
 (0)