Skip to content

Commit c7bd8c9

Browse files
committed
Consider all symbol replies from clangd dynamic
1 parent 84fdea9 commit c7bd8c9

File tree

2 files changed

+75
-23
lines changed

2 files changed

+75
-23
lines changed

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,9 +1682,8 @@ extension SourceKitServer {
16821682
return []
16831683
}
16841684

1685-
if let symbol = symbols.first, let bestLocalDeclaration = symbol.bestLocalDeclaration, !(symbol.isDynamic ?? false)
1686-
{
1687-
// If we have a non-dynamic symbol, we don't need to do an index lookup
1685+
if let bestLocalDeclaration = symbol.bestLocalDeclaration, !(symbol.isDynamic ?? true) {
1686+
// If we have a known non-dynamic symbol, we don't need to do an index lookup
16881687
return [bestLocalDeclaration]
16891688
}
16901689

@@ -1704,38 +1703,49 @@ extension SourceKitServer {
17041703
}
17051704
guard let usr = symbol.usr else { return [] }
17061705
logger.info("performing indexed jump-to-def with usr \(usr)")
1707-
var occurances = index.occurrences(ofUSR: usr, roles: [.definition])
1708-
if occurances.isEmpty {
1709-
occurances = index.occurrences(ofUSR: usr, roles: [.declaration])
1710-
}
1711-
if symbol.isDynamic ?? false {
1712-
lazy var transitiveReceiverUsrs: [String] = transitiveSubtypeClosure(
1713-
ofUsrs: symbol.receiverUsrs ?? [],
1714-
index: index
1715-
)
1716-
occurances += occurances.flatMap {
1706+
var occurrences = index.occurrences(ofUSR: usr, roles: [.definition])
1707+
if occurrences.isEmpty {
1708+
occurrences = index.occurrences(ofUSR: usr, roles: [.declaration])
1709+
}
1710+
if symbol.isDynamic ?? true {
1711+
lazy var transitiveReceiverUsrs: [String]? = {
1712+
if let receiverUsrs = symbol.receiverUsrs {
1713+
return transitiveSubtypeClosure(
1714+
ofUsrs: receiverUsrs,
1715+
index: index
1716+
)
1717+
} else {
1718+
return nil
1719+
}
1720+
}()
1721+
occurrences += occurrences.flatMap {
17171722
let overrides = index.occurrences(relatedToUSR: $0.symbol.usr, roles: .overrideOf)
17181723
// Only contain overrides that are children of one of the receiver types or their subtypes.
17191724
return overrides.filter { override in
17201725
override.relations.contains(where: {
1721-
$0.roles.contains(.childOf) && transitiveReceiverUsrs.contains($0.symbol.usr)
1726+
guard $0.roles.contains(.childOf) else {
1727+
return false
1728+
}
1729+
if let transitiveReceiverUsrs, !transitiveReceiverUsrs.contains($0.symbol.usr) {
1730+
return false
1731+
}
1732+
return true
17221733
})
17231734
}
17241735
}
17251736
}
17261737

1727-
return try await occurances.asyncCompactMap { occurance in
1728-
if URL(fileURLWithPath: occurance.location.path).pathExtension == "swiftinterface" {
1729-
// if first resolved location is in `.swiftinterface` file. Use moduleName to return
1730-
// textual interface
1738+
return try await occurrences.asyncCompactMap { occurrence in
1739+
if URL(fileURLWithPath: occurrence.location.path).pathExtension == "swiftinterface" {
1740+
// If the location is in `.swiftinterface` file, use moduleName to return textual interface.
17311741
return try await self.definitionInInterface(
17321742
req,
1733-
moduleName: occurance.location.moduleName,
1734-
symbolUSR: occurance.symbol.usr,
1743+
moduleName: occurrence.location.moduleName,
1744+
symbolUSR: occurrence.symbol.usr,
17351745
languageService: languageService
17361746
)
17371747
}
1738-
return indexToLSPLocation(occurance.location)
1748+
return indexToLSPLocation(occurrence.location)
17391749
}
17401750
}
17411751

@@ -1747,10 +1757,14 @@ extension SourceKitServer {
17471757
let indexBasedResponse = try await indexBasedDefinition(req, workspace: workspace, languageService: languageService)
17481758
// If we're unable to handle the definition request using our index, see if the
17491759
// language service can handle it (e.g. clangd can provide AST based definitions).
1760+
// We are on only calling the language service's `definition` function if your index-based lookup failed.
1761+
// If this fallback request fails, its error is usually not very enlightening. For example the `SwiftLanguageServer`
1762+
// will always respond with `unsupported method`. Thus, only log such a failure instead of returning it to the
1763+
// client.
17501764
if indexBasedResponse.isEmpty {
1751-
do {
1765+
return await orLog("Fallback definition request") {
17521766
return try await languageService.definition(req)
1753-
} catch {}
1767+
}
17541768
}
17551769
return .locations(indexBasedResponse)
17561770
}

Tests/SourceKitLSPTests/DefinitionTests.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,42 @@ class DefinitionTests: XCTestCase {
105105
]
106106
)
107107
}
108+
109+
func testDynamicJumpToDefinitionInClang() async throws {
110+
let ws = try await SwiftPMTestWorkspace(
111+
files: [
112+
"Sources/MyLibrary/include/dummy.h": "",
113+
"test.cpp": """
114+
struct Base {
115+
virtual void 1️⃣doStuff() {}
116+
};
117+
118+
struct Sub: Base {
119+
void 2️⃣doStuff() override {}
120+
};
121+
122+
void test(Base base) {
123+
base.3️⃣doStuff();
124+
}
125+
""",
126+
],
127+
build: true
128+
)
129+
let (uri, positions) = try ws.openDocument("test.cpp")
130+
131+
let response = try await ws.testClient.send(
132+
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["3️⃣"])
133+
)
134+
guard case .locations(let locations) = response else {
135+
XCTFail("Expected locations response")
136+
return
137+
}
138+
XCTAssertEqual(
139+
locations,
140+
[
141+
Location(uri: uri, range: Range(positions["1️⃣"])),
142+
Location(uri: uri, range: Range(positions["2️⃣"])),
143+
]
144+
)
145+
}
108146
}

0 commit comments

Comments
 (0)