From a7f43dc54ca0e9746e6a5ec32377758e4a4dbac4 Mon Sep 17 00:00:00 2001 From: Steffeeen Date: Tue, 19 May 2026 17:37:13 +0200 Subject: [PATCH] Fix rename target selection for initializer calls When rename is invoked on a type name in an initializer call, prefer renaming the nominal type symbol instead of the constructor symbol. --- Sources/SwiftLanguageService/Rename.swift | 13 +++++++++ Tests/SourceKitLSPTests/RenameTests.swift | 35 ++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) 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: [