Skip to content

Commit 83837ce

Browse files
authored
Merge pull request #1138 from ahoppen/ahoppen/rename-with-multiple-index-entries
Don’t fail rename if there are multiple index entries for the same USR
2 parents cd07e2f + f90d3c9 commit 83837ce

File tree

2 files changed

+114
-14
lines changed

2 files changed

+114
-14
lines changed

Sources/SourceKitLSP/Rename.swift

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -554,22 +554,50 @@ extension SourceKitServer {
554554
index: IndexStoreDB
555555
) async throws -> CrossLanguageName? {
556556
let definitions = index.occurrences(ofUSR: usr, roles: [.definition])
557-
guard let definitionSymbol = definitions.only else {
558-
if definitions.isEmpty {
559-
logger.error("no definitions for \(usr) found")
560-
} else {
561-
logger.error("Multiple definitions for \(usr) found")
562-
}
557+
if definitions.isEmpty {
558+
logger.error("no definitions for \(usr) found")
563559
return nil
564560
}
561+
if definitions.count > 1 {
562+
logger.log("Multiple definitions for \(usr) found")
563+
}
564+
// There might be multiple definitions of the same symbol eg. in different `#if` branches. In this case pick any of
565+
// them because with very high likelihood they all translate to the same clang and Swift name. Sort the entries to
566+
// ensure that we deterministically pick the same entry every time.
567+
for definitionOccurrence in definitions.sorted() {
568+
do {
569+
return try await getCrossLanguageName(
570+
forDefinitionOccurrence: definitionOccurrence,
571+
overrideName: overrideName,
572+
workspace: workspace,
573+
index: index
574+
)
575+
} catch {
576+
// If getting the cross-language name fails for this occurrence, try the next definition, if there are multiple.
577+
logger.log(
578+
"Getting cross-language name for occurrence at \(definitionOccurrence.location) failed. \(error.forLogging)"
579+
)
580+
}
581+
}
582+
return nil
583+
}
584+
585+
private func getCrossLanguageName(
586+
forDefinitionOccurrence definitionOccurrence: SymbolOccurrence,
587+
overrideName: String? = nil,
588+
workspace: Workspace,
589+
index: IndexStoreDB
590+
) async throws -> CrossLanguageName {
591+
let definitionSymbol = definitionOccurrence.symbol
592+
let usr = definitionSymbol.usr
565593
let definitionLanguage: Language =
566-
switch definitionSymbol.symbol.language {
594+
switch definitionSymbol.language {
567595
case .c: .c
568596
case .cxx: .cpp
569597
case .objc: .objective_c
570598
case .swift: .swift
571599
}
572-
let definitionDocumentUri = DocumentURI(URL(fileURLWithPath: definitionSymbol.location.path))
600+
let definitionDocumentUri = DocumentURI(URL(fileURLWithPath: definitionOccurrence.location.path))
573601

574602
guard
575603
let definitionLanguageService = await self.languageService(
@@ -578,25 +606,24 @@ extension SourceKitServer {
578606
in: workspace
579607
)
580608
else {
581-
logger.fault("Failed to get language service for the document defining \(usr)")
582-
return nil
609+
throw ResponseError.unknown("Failed to get language service for the document defining \(usr)")
583610
}
584611

585-
let definitionName = overrideName ?? definitionSymbol.symbol.name
612+
let definitionName = overrideName ?? definitionSymbol.name
586613

587614
switch definitionLanguageService {
588615
case is ClangLanguageServerShim:
589616
let swiftName: String?
590617
if let swiftReference = await getReferenceFromSwift(usr: usr, index: index, workspace: workspace) {
591-
let isObjectiveCSelector = definitionLanguage == .objective_c && definitionSymbol.symbol.kind.isMethod
618+
let isObjectiveCSelector = definitionLanguage == .objective_c && definitionSymbol.kind.isMethod
592619
swiftName = try await swiftReference.languageServer.translateClangNameToSwift(
593620
at: swiftReference.location,
594621
in: swiftReference.snapshot,
595622
isObjectiveCSelector: isObjectiveCSelector,
596623
name: definitionName
597624
)
598625
} else {
599-
logger.debug("Not translating \(usr) to Swift because it is not referenced from Swift")
626+
logger.debug("Not translating \(definitionSymbol) to Swift because it is not referenced from Swift")
600627
swiftName = nil
601628
}
602629
return CrossLanguageName(clangName: definitionName, swiftName: swiftName, definitionLanguage: definitionLanguage)
@@ -610,7 +637,7 @@ extension SourceKitServer {
610637
let clangName: String?
611638
if hasReferenceFromClang {
612639
clangName = try await swiftLanguageServer.translateSwiftNameToClang(
613-
at: definitionSymbol.location,
640+
at: definitionOccurrence.location,
614641
in: definitionDocumentUri,
615642
name: CompoundDeclName(definitionName)
616643
)

Tests/SourceKitLSPTests/RenameTests.swift

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,4 +1081,77 @@ final class RenameTests: XCTestCase {
10811081
"""
10821082
)
10831083
}
1084+
1085+
func testRenameAfterFileMove() async throws {
1086+
let ws = try await SwiftPMTestWorkspace(
1087+
files: [
1088+
"definition.swift": """
1089+
func 1️⃣foo2️⃣() {}
1090+
""",
1091+
"caller.swift": """
1092+
func test() {
1093+
3️⃣foo4️⃣()
1094+
}
1095+
""",
1096+
],
1097+
build: true
1098+
)
1099+
1100+
let definitionUri = try ws.uri(for: "definition.swift")
1101+
let (callerUri, callerPositions) = try ws.openDocument("caller.swift")
1102+
1103+
// Validate that we get correct rename results before moving the definition file.
1104+
let resultBeforeFileMove = try await ws.testClient.send(
1105+
RenameRequest(textDocument: TextDocumentIdentifier(callerUri), position: callerPositions["3️⃣"], newName: "bar")
1106+
)
1107+
XCTAssertEqual(
1108+
resultBeforeFileMove,
1109+
WorkspaceEdit(
1110+
changes: [
1111+
definitionUri: [
1112+
TextEdit(
1113+
range: try ws.position(of: "1️⃣", in: "definition.swift")..<ws.position(of: "2️⃣", in: "definition.swift"),
1114+
newText: "bar"
1115+
)
1116+
],
1117+
callerUri: [TextEdit(range: callerPositions["3️⃣"]..<callerPositions["4️⃣"], newText: "bar")],
1118+
]
1119+
)
1120+
)
1121+
1122+
let movedDefinitionUri =
1123+
DocumentURI(
1124+
definitionUri.fileURL!
1125+
.deletingLastPathComponent()
1126+
.appendingPathComponent("movedDefinition.swift")
1127+
)
1128+
1129+
try FileManager.default.moveItem(at: XCTUnwrap(definitionUri.fileURL), to: XCTUnwrap(movedDefinitionUri.fileURL))
1130+
1131+
ws.testClient.send(
1132+
DidChangeWatchedFilesNotification(changes: [
1133+
FileEvent(uri: definitionUri, type: .deleted), FileEvent(uri: movedDefinitionUri, type: .created),
1134+
])
1135+
)
1136+
1137+
try await SwiftPMTestWorkspace.build(at: ws.scratchDirectory)
1138+
1139+
let resultAfterFileMove = try await ws.testClient.send(
1140+
RenameRequest(textDocument: TextDocumentIdentifier(callerUri), position: callerPositions["3️⃣"], newName: "bar")
1141+
)
1142+
XCTAssertEqual(
1143+
resultAfterFileMove,
1144+
WorkspaceEdit(
1145+
changes: [
1146+
movedDefinitionUri: [
1147+
TextEdit(
1148+
range: try ws.position(of: "1️⃣", in: "definition.swift")..<ws.position(of: "2️⃣", in: "definition.swift"),
1149+
newText: "bar"
1150+
)
1151+
],
1152+
callerUri: [TextEdit(range: callerPositions["3️⃣"]..<callerPositions["4️⃣"], newText: "bar")],
1153+
]
1154+
)
1155+
)
1156+
}
10841157
}

0 commit comments

Comments
 (0)