Skip to content

Commit e4d8331

Browse files
authored
Merge pull request #1404 from ahoppen/cpp-to-swift-rename-tests
Add tests to rename C++ symbols exposed to Swift
2 parents fff9eb5 + 5b119b7 commit e4d8331

File tree

4 files changed

+175
-47
lines changed

4 files changed

+175
-47
lines changed

Sources/SKTestSupport/SwiftPMTestProject.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ public class SwiftPMTestProject: MultiFileTestProject {
177177
if !manifest.contains("swift-tools-version") {
178178
// Tests specify a shorthand package manifest that doesn't contain the tools version boilerplate.
179179
manifest = """
180-
// swift-tools-version: 5.7
180+
// swift-tools-version: 5.10
181181
182182
import PackageDescription
183183

Sources/SemanticIndex/CheckedIndex.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,6 @@ public struct UncheckedIndex: Sendable {
176176
return CheckedIndex(index: underlyingIndexStoreDB, checkLevel: checkLevel)
177177
}
178178

179-
public func symbolProvider(for sourceFilePath: String) -> SymbolProviderKind? {
180-
return underlyingIndexStoreDB.symbolProvider(for: sourceFilePath)
181-
}
182-
183179
/// Wait for IndexStoreDB to be updated based on new unit files written to disk.
184180
public func pollForUnitChangesAndWait() {
185181
self.underlyingIndexStoreDB.pollForUnitChangesAndWait()

Sources/SourceKitLSP/Rename.swift

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ extension SourceKitLSPServer {
500500
) async -> (swiftLanguageService: SwiftLanguageService, snapshot: DocumentSnapshot, location: SymbolLocation)? {
501501
var reference: SymbolOccurrence? = nil
502502
index.forEachSymbolOccurrence(byUSR: usr, roles: renameRoles) {
503-
if index.unchecked.symbolProvider(for: $0.location.path) == .swift {
503+
if $0.symbolProvider == .swift {
504504
reference = $0
505505
// We have found a reference from Swift. Stop iteration.
506506
return false
@@ -631,7 +631,7 @@ extension SourceKitLSPServer {
631631
// If we terminate early by returning `false` from the closure, `forEachSymbolOccurrence` returns `true`,
632632
// indicating that we have found a reference from clang.
633633
let hasReferenceFromClang = !index.forEachSymbolOccurrence(byUSR: usr, roles: renameRoles) {
634-
return index.unchecked.symbolProvider(for: $0.location.path) != .clang
634+
return $0.symbolProvider != .clang
635635
}
636636
let clangName: String?
637637
if hasReferenceFromClang {
@@ -730,32 +730,7 @@ extension SourceKitLSPServer {
730730

731731
// If we have a USR + old name, perform an index lookup to find workspace-wide symbols to rename.
732732
// First, group all occurrences of that USR by the files they occur in.
733-
var locationsByFile: [DocumentURI: [RenameLocation]] = [:]
734-
735-
actor LanguageServerTypesCache {
736-
let index: UncheckedIndex
737-
var languageServerTypesCache: [DocumentURI: LanguageServerType?] = [:]
738-
739-
init(index: UncheckedIndex) {
740-
self.index = index
741-
}
742-
743-
func languageServerType(for uri: DocumentURI) -> LanguageServerType? {
744-
if let cachedValue = languageServerTypesCache[uri] {
745-
return cachedValue
746-
}
747-
let serverType: LanguageServerType? =
748-
if let fileURL = uri.fileURL {
749-
LanguageServerType(symbolProvider: index.symbolProvider(for: fileURL.path))
750-
} else {
751-
nil
752-
}
753-
languageServerTypesCache[uri] = serverType
754-
return serverType
755-
}
756-
}
757-
758-
let languageServerTypesCache = LanguageServerTypesCache(index: index.unchecked)
733+
var locationsByFile: [DocumentURI: (renameLocations: [RenameLocation], symbolProvider: SymbolProviderKind)] = [:]
759734

760735
let usrsToRename = overridingAndOverriddenUsrs(of: usr, index: index)
761736
let occurrencesToRename = usrsToRename.flatMap { index.occurrences(ofUSR: $0, roles: renameRoles) }
@@ -770,19 +745,16 @@ extension SourceKitLSPServer {
770745
// perform an indexed rename for it.
771746
continue
772747
}
773-
switch await languageServerTypesCache.languageServerType(for: uri) {
748+
switch occurrence.symbolProvider {
774749
case .swift:
775750
// sourcekitd only produces AST-based results for the direct calls to this USR. This is because the Swift
776751
// AST only has upwards references to superclasses and overridden methods, not the other way round. It is
777752
// thus not possible to (easily) compute an up-down closure like described in `overridingAndOverriddenUsrs`.
778753
// We thus need to perform an indexed rename for other, related USRs.
779754
break
780-
case .clangd:
755+
case .clang:
781756
// clangd produces AST-based results for the entire class hierarchy, so nothing to do.
782757
continue
783-
case nil:
784-
// Unknown symbol provider
785-
continue
786758
}
787759
}
788760

@@ -791,25 +763,39 @@ extension SourceKitLSPServer {
791763
utf8Column: occurrence.location.utf8Column,
792764
usage: RenameLocation.Usage(roles: occurrence.roles)
793765
)
794-
locationsByFile[uri, default: []].append(renameLocation)
766+
if let existingLocations = locationsByFile[uri] {
767+
if existingLocations.symbolProvider != occurrence.symbolProvider {
768+
logger.fault(
769+
"""
770+
Found mismatching symbol providers for \(uri.forLogging): \
771+
\(String(describing: existingLocations.symbolProvider), privacy: .public) vs \
772+
\(String(describing: occurrence.symbolProvider), privacy: .public)
773+
"""
774+
)
775+
}
776+
locationsByFile[uri] = (existingLocations.renameLocations + [renameLocation], occurrence.symbolProvider)
777+
} else {
778+
locationsByFile[uri] = ([renameLocation], occurrence.symbolProvider)
779+
}
795780
}
796781

797782
// Now, call `editsToRename(locations:in:oldName:newName:)` on the language service to convert these ranges into
798783
// edits.
799784
let urisAndEdits =
800785
await locationsByFile
801-
.concurrentMap { (uri: DocumentURI, renameLocations: [RenameLocation]) -> (DocumentURI, [TextEdit])? in
786+
.concurrentMap {
787+
(
788+
uri: DocumentURI,
789+
value: (renameLocations: [RenameLocation], symbolProvider: SymbolProviderKind)
790+
) -> (DocumentURI, [TextEdit])? in
802791
let language: Language
803-
switch await languageServerTypesCache.languageServerType(for: uri) {
804-
case .clangd:
792+
switch value.symbolProvider {
793+
case .clang:
805794
// Technically, we still don't know the language of the source file but defaulting to C is sufficient to
806795
// ensure we get the clang toolchain language server, which is all we care about.
807796
language = .c
808797
case .swift:
809798
language = .swift
810-
case nil:
811-
logger.error("Failed to determine symbol provider for \(uri.forLogging)")
812-
return nil
813799
}
814800
// Create a document snapshot to operate on. If the document is open, load it from the document manager,
815801
// otherwise conjure one from the file on disk. We need the file in memory to perform UTF-8 to UTF-16 column
@@ -825,13 +811,13 @@ extension SourceKitLSPServer {
825811
var edits: [TextEdit] =
826812
await orLog("Getting edits for rename location") {
827813
return try await languageService.editsToRename(
828-
locations: renameLocations,
814+
locations: value.renameLocations,
829815
in: snapshot,
830816
oldName: oldName,
831817
newName: newName
832818
)
833819
} ?? []
834-
for location in renameLocations where location.usage == .definition {
820+
for location in value.renameLocations where location.usage == .definition {
835821
edits += await languageService.editsToRenameParametersInFunctionBody(
836822
snapshot: snapshot,
837823
renameLocation: location,

Tests/SourceKitLSPTests/CrossLanguageRenameTests.swift

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ private let libAlibBPackageManifest = """
2323
)
2424
"""
2525

26+
private let libAlibBCxxInteropPackageManifest = """
27+
let package = Package(
28+
name: "MyLibrary",
29+
targets: [
30+
.target(name: "LibA"),
31+
.target(name: "LibB", dependencies: ["LibA"], swiftSettings: [.interoperabilityMode(.Cxx)]),
32+
]
33+
)
34+
"""
35+
2636
final class CrossLanguageRenameTests: XCTestCase {
2737
func testZeroArgCFunction() async throws {
2838
try await SkipUnless.clangdSupportsIndexBasedRename()
@@ -733,4 +743,140 @@ final class CrossLanguageRenameTests: XCTestCase {
733743
manifest: libAlibBPackageManifest
734744
)
735745
}
746+
747+
func testRenameCxxClassExposedToSwift() async throws {
748+
try await SkipUnless.clangdSupportsIndexBasedRename()
749+
try await assertMultiFileRename(
750+
files: [
751+
"LibA/include/LibA.h": """
752+
struct 1️⃣Foo {};
753+
""",
754+
"LibA/LibA.cpp": "",
755+
"LibB/LibB.swift": """
756+
import LibA
757+
758+
func test(foo: 2️⃣Foo) {}
759+
""",
760+
],
761+
headerFileLanguage: .cpp,
762+
newName: "Bar",
763+
expectedPrepareRenamePlaceholder: "Foo",
764+
expected: [
765+
"LibA/include/LibA.h": """
766+
struct Bar {};
767+
""",
768+
"LibA/LibA.cpp": "",
769+
"LibB/LibB.swift": """
770+
import LibA
771+
772+
func test(foo: Bar) {}
773+
""",
774+
],
775+
manifest: libAlibBCxxInteropPackageManifest
776+
)
777+
}
778+
779+
func testRenameCxxMethodExposedToSwift() async throws {
780+
try await SkipUnless.clangdSupportsIndexBasedRename()
781+
try await assertMultiFileRename(
782+
files: [
783+
"LibA/include/LibA.h": """
784+
struct Foo {
785+
int 1️⃣doStuff() const;
786+
};
787+
""",
788+
"LibA/LibA.cpp": """
789+
#include "LibA.h"
790+
791+
int Foo::2️⃣doStuff() const {
792+
return 42;
793+
}
794+
""",
795+
"LibB/LibB.swift": """
796+
import LibA
797+
798+
func test(foo: Foo) {
799+
foo.3️⃣doStuff()
800+
}
801+
""",
802+
],
803+
headerFileLanguage: .cpp,
804+
newName: "doNewStuff",
805+
expectedPrepareRenamePlaceholder: "doStuff",
806+
expected: [
807+
"LibA/include/LibA.h": """
808+
struct Foo {
809+
int doNewStuff() const;
810+
};
811+
""",
812+
"LibA/LibA.cpp": """
813+
#include "LibA.h"
814+
815+
int Foo::doNewStuff() const {
816+
return 42;
817+
}
818+
""",
819+
"LibB/LibB.swift": """
820+
import LibA
821+
822+
func test(foo: Foo) {
823+
foo.doNewStuff()
824+
}
825+
""",
826+
],
827+
manifest: libAlibBCxxInteropPackageManifest
828+
)
829+
}
830+
831+
func testRenameSwiftMethodExposedToSwift() async throws {
832+
try await SkipUnless.clangdSupportsIndexBasedRename()
833+
try await assertMultiFileRename(
834+
files: [
835+
"LibA/include/LibA.h": """
836+
struct Foo {
837+
int 1️⃣doStuff() const;
838+
};
839+
""",
840+
"LibA/LibA.cpp": """
841+
#include "LibA.h"
842+
843+
int Foo::2️⃣doStuff() const {
844+
return 42;
845+
}
846+
""",
847+
"LibB/LibB.swift": """
848+
import LibA
849+
850+
func test(foo: Foo) {
851+
foo.3️⃣doStuff()
852+
}
853+
""",
854+
],
855+
headerFileLanguage: .cpp,
856+
newName: "doNewStuff",
857+
expectedPrepareRenamePlaceholder: "doStuff",
858+
expected: [
859+
"LibA/include/LibA.h": """
860+
struct Foo {
861+
int doNewStuff() const;
862+
};
863+
""",
864+
"LibA/LibA.cpp": """
865+
#include "LibA.h"
866+
867+
int Foo::doNewStuff() const {
868+
return 42;
869+
}
870+
""",
871+
"LibB/LibB.swift": """
872+
import LibA
873+
874+
func test(foo: Foo) {
875+
foo.doNewStuff()
876+
}
877+
""",
878+
],
879+
manifest: libAlibBCxxInteropPackageManifest
880+
)
881+
}
736882
}

0 commit comments

Comments
 (0)