Skip to content

Commit a3f7f11

Browse files
committed
Show overriden functions when performing jump-to-definition on a dynamic call
Fixes #809 rdar://114864256
1 parent e6cf723 commit a3f7f11

File tree

5 files changed

+66
-14
lines changed

5 files changed

+66
-14
lines changed

Sources/LanguageServerProtocol/Requests/SymbolInfoRequest.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,23 @@ public struct SymbolDetails: ResponseType, Hashable {
8181
/// The kind of the symbol
8282
public var kind: SymbolKind?
8383

84+
/// Whether the symbol is a dynamic call for which it isn't known which method will be invoked at runtime. This is
85+
/// the case for protocol methods and class functions.
86+
public var isDynamic: Bool
87+
8488
public init(
8589
name: String?,
86-
containerName: String? = nil,
90+
containerName: String?,
8791
usr: String?,
88-
bestLocalDeclaration: Location? = nil,
89-
kind: SymbolKind? = nil
92+
bestLocalDeclaration: Location?,
93+
kind: SymbolKind?,
94+
isDynamic: Bool
9095
) {
9196
self.name = name
9297
self.containerName = containerName
9398
self.usr = usr
9499
self.bestLocalDeclaration = bestLocalDeclaration
95100
self.kind = kind
101+
self.isDynamic = isDynamic
96102
}
97103
}

Sources/SourceKitD/sourcekitd_uids.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public struct sourcekitd_keys {
4444
public let groupname: sourcekitd_uid_t
4545
public let id: sourcekitd_uid_t
4646
public let is_system: sourcekitd_uid_t
47+
public let isDynamic: sourcekitd_uid_t
4748
public let kind: sourcekitd_uid_t
4849
public let length: sourcekitd_uid_t
4950
public let line: sourcekitd_uid_t
@@ -122,6 +123,7 @@ public struct sourcekitd_keys {
122123
groupname = api.uid_get_from_cstr("key.groupname")!
123124
id = api.uid_get_from_cstr("key.id")!
124125
is_system = api.uid_get_from_cstr("key.is_system")!
126+
isDynamic = api.uid_get_from_cstr("key.is_dynamic")!
125127
kind = api.uid_get_from_cstr("key.kind")!
126128
length = api.uid_get_from_cstr("key.length")!
127129
line = api.uid_get_from_cstr("key.line")!

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,7 +1667,7 @@ extension SourceKitServer {
16671667
symbols: [SymbolDetails],
16681668
index: IndexStoreDB?,
16691669
useLocalFallback: Bool = false,
1670-
extractOccurrences: (String, IndexStoreDB) -> [SymbolOccurrence]
1670+
extractOccurrences: (SymbolDetails, IndexStoreDB) -> [SymbolOccurrence]
16711671
) -> [(occurrence: SymbolOccurrence?, location: Location)] {
16721672
guard let symbol = symbols.first else {
16731673
return []
@@ -1680,11 +1680,11 @@ extension SourceKitServer {
16801680
fallback = []
16811681
}
16821682

1683-
guard let usr = symbol.usr, let index = index else {
1683+
guard let index = index else {
16841684
return fallback
16851685
}
16861686

1687-
let occurs = extractOccurrences(usr, index)
1687+
let occurs = extractOccurrences(symbol, index)
16881688
let resolved = occurs.compactMap { occur in
16891689
indexToLSPLocation(occur.location).map {
16901690
(occurrence: occur, location: $0)
@@ -1725,12 +1725,18 @@ extension SourceKitServer {
17251725
}
17261726

17271727
let resolved = self.extractIndexedOccurrences(symbols: symbols, index: index, useLocalFallback: true) {
1728-
(usr, index) in
1728+
(symbolDetails, index) in
1729+
guard let usr = symbolDetails.usr else { return [] }
17291730
logger.info("performing indexed jump-to-def with usr \(usr)")
17301731
var occurs = index.occurrences(ofUSR: usr, roles: [.definition])
17311732
if occurs.isEmpty {
17321733
occurs = index.occurrences(ofUSR: usr, roles: [.declaration])
17331734
}
1735+
if symbolDetails.isDynamic {
1736+
occurs += occurs.flatMap {
1737+
index.occurrences(relatedToUSR: $0.symbol.usr, roles: .overrideOf)
1738+
}
1739+
}
17341740
return occurs
17351741
}
17361742

@@ -1783,7 +1789,8 @@ extension SourceKitServer {
17831789
)
17841790
)
17851791
let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index
1786-
let extractedResult = self.extractIndexedOccurrences(symbols: symbols, index: index) { (usr, index) in
1792+
let extractedResult = self.extractIndexedOccurrences(symbols: symbols, index: index) { (symbolDetails, index) in
1793+
guard let usr = symbolDetails.usr else { return [] }
17871794
var occurs = index.occurrences(ofUSR: usr, roles: .baseOf)
17881795
if occurs.isEmpty {
17891796
occurs = index.occurrences(relatedToUSR: usr, roles: .overrideOf)
@@ -1806,7 +1813,8 @@ extension SourceKitServer {
18061813
)
18071814
)
18081815
let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index
1809-
let extractedResult = self.extractIndexedOccurrences(symbols: symbols, index: index) { (usr, index) in
1816+
let extractedResult = self.extractIndexedOccurrences(symbols: symbols, index: index) { (symbolDetails, index) in
1817+
guard let usr = symbolDetails.usr else { return [] }
18101818
logger.info("performing indexed jump-to-def with usr \(usr)")
18111819
var roles: SymbolRole = [.reference]
18121820
if req.context.includeDeclaration {
@@ -1852,8 +1860,9 @@ extension SourceKitServer {
18521860
)
18531861
let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index
18541862
// For call hierarchy preparation we only locate the definition
1855-
let extractedResult = self.extractIndexedOccurrences(symbols: symbols, index: index) { (usr, index) in
1856-
index.occurrences(ofUSR: usr, roles: [.definition, .declaration])
1863+
let extractedResult = self.extractIndexedOccurrences(symbols: symbols, index: index) { (symbolDetails, index) in
1864+
guard let usr = symbolDetails.usr else { return [] }
1865+
return index.occurrences(ofUSR: usr, roles: [.definition, .declaration])
18571866
}
18581867
return extractedResult.compactMap { info -> CallHierarchyItem? in
18591868
guard let occurrence = info.occurrence else {
@@ -2009,8 +2018,9 @@ extension SourceKitServer {
20092018
guard let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index else {
20102019
return []
20112020
}
2012-
let extractedResult = self.extractIndexedOccurrences(symbols: symbols, index: index) { (usr, index) in
2013-
index.occurrences(ofUSR: usr, roles: [.definition, .declaration])
2021+
let extractedResult = self.extractIndexedOccurrences(symbols: symbols, index: index) { (symbolDetails, index) in
2022+
guard let usr = symbolDetails.usr else { return [] }
2023+
return index.occurrences(ofUSR: usr, roles: [.definition, .declaration])
20142024
}
20152025
return extractedResult.compactMap { info -> TypeHierarchyItem? in
20162026
guard let occurrence = info.occurrence else {

Sources/SourceKitLSP/Swift/CursorInfo.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ extension SwiftLanguageServer {
131131
containerName: nil,
132132
usr: dict[keys.usr],
133133
bestLocalDeclaration: location,
134-
kind: kind.asSymbolKind(self.sourcekitd.values)
134+
kind: kind.asSymbolKind(self.sourcekitd.values),
135+
isDynamic: dict[keys.isDynamic] ?? false
135136
),
136137
annotatedDeclaration: dict[keys.annotated_decl],
137138
documentationXML: dict[keys.doc_full_as_xml],

Tests/SourceKitLSPTests/DefinitionTests.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,37 @@ class DefinitionTests: XCTestCase {
3636
}
3737
XCTAssertEqual(locations, [Location(uri: uri, range: Range(positions["1️⃣"]))])
3838
}
39+
40+
func testJumpToDefinitionIncludesOverrides() async throws {
41+
let ws = try await IndexedSingleSwiftFileWorkspace(
42+
"""
43+
protocol TestProtocol {
44+
func 1️⃣doThing()
45+
}
46+
47+
struct TestImpl: TestProtocol {
48+
func 2️⃣doThing() { }
49+
}
50+
51+
func anyTestProtocol(value: any TestProtocol) {
52+
value.3️⃣doThing()
53+
}
54+
"""
55+
)
56+
57+
let response = try await ws.testClient.send(
58+
DefinitionRequest(textDocument: TextDocumentIdentifier(ws.fileURI), position: ws.positions["3️⃣"])
59+
)
60+
guard case .locations(let locations) = response else {
61+
XCTFail("Expected locations response")
62+
return
63+
}
64+
XCTAssertEqual(
65+
locations,
66+
[
67+
Location(uri: ws.fileURI, range: Range(ws.positions["1️⃣"])),
68+
Location(uri: ws.fileURI, range: Range(ws.positions["2️⃣"])),
69+
]
70+
)
71+
}
3972
}

0 commit comments

Comments
 (0)