diff --git a/Sources/SwiftLanguageService/Rename.swift b/Sources/SwiftLanguageService/Rename.swift index dcba0af3..019b48fb 100644 --- a/Sources/SwiftLanguageService/Rename.swift +++ b/Sources/SwiftLanguageService/Rename.swift @@ -607,6 +607,19 @@ extension SwiftLanguageService { ) guard let functionLikeRange = await findFunctionLikeRange(of: startOfIdentifierPosition, in: snapshot) else { + if let symbolInfo, symbolInfo.count == 2, + symbolInfo.first(where: { $0.kind == .constructor }) != nil, + let structClassEnumActorSymbolInfo = symbolInfo.first(where: { + // There is no SymbolKind for actors, they just use `.class`. + $0.kind == .struct || $0.kind == .class || $0.kind == .enum + }) + { + // We have multiple symbols at the position but none of them have a function-like range. This can happen when + // invoking a rename from the name of a initializer call like `MyStruct(x: 1)` with the cursor inside `MyStruct`. + // In this case, we want to rename the struct/class/actor instead of the initializer. + return (startOfIdentifierPosition, structClassEnumActorSymbolInfo.usr, nil) + } + return (startOfIdentifierPosition, symbolInfo?.only?.usr, nil) } if let onlySymbol = symbolInfo?.only, onlySymbol.kind == .constructor { diff --git a/Tests/SourceKitLSPTests/RenameTests.swift b/Tests/SourceKitLSPTests/RenameTests.swift index 49b951bd..f940b213 100644 --- a/Tests/SourceKitLSPTests/RenameTests.swift +++ b/Tests/SourceKitLSPTests/RenameTests.swift @@ -258,7 +258,7 @@ final class RenameTests: SourceKitLSPTestCase { struct Foo { 1️⃣init(4️⃣x: Int) {} } - Foo(x: 1) + Foo(7️⃣x: 1) Foo.2️⃣init(5️⃣x: 1) _ = Foo.3️⃣init(6️⃣x:) """, @@ -513,6 +513,39 @@ final class RenameTests: SourceKitLSPTestCase { ) } + func testCrossFileSwiftRenameInInitializer() async throws { + for type in ["struct", "class", "actor", "enum"] { + try await assertMultiFileRename( + files: [ + "a.swift": """ + func main() { + let foo: Foo = 1️⃣Foo() + } + """, + "b.swift": """ + \(type) Foo { + init() {} // explicit initializer is needed for enums + } + """, + ], + newName: "Bar", + expectedPrepareRenamePlaceholder: "Foo", + expected: [ + "a.swift": """ + func main() { + let foo: Bar = Bar() + } + """, + "b.swift": """ + \(type) Bar { + init() {} // explicit initializer is needed for enums + } + """, + ] + ) + } + } + func testSwiftCrossModuleRename() async throws { try await assertMultiFileRename( files: [