Skip to content

Commit ff2b27a

Browse files
committed
Only report a single item in prepareCallHierarchy
Returning multiple doesn't make sense because they will all have the same USR (because we query them by USR) and will thus expand to the exact same call hierarchy. Also, VS Code doesn't seem to like multiple call hiearchy items being returned and fails to display any results if they are, failing with `Cannot read properties of undefined (reading 'map')`. rdar://118957893
1 parent 789ec94 commit ff2b27a

File tree

2 files changed

+71
-15
lines changed

2 files changed

+71
-15
lines changed

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,10 +1752,7 @@ extension SourceKitServer {
17521752
}
17531753
guard let usr = symbol.usr else { return [] }
17541754
logger.info("performing indexed jump-to-def with usr \(usr)")
1755-
var occurrences = index.occurrences(ofUSR: usr, roles: [.definition])
1756-
if occurrences.isEmpty {
1757-
occurrences = index.occurrences(ofUSR: usr, roles: [.declaration])
1758-
}
1755+
var occurrences = index.definitionOrDeclarationOccurances(ofUSR: usr)
17591756
if symbol.isDynamic ?? true {
17601757
lazy var transitiveReceiverUsrs: [String]? = {
17611758
if let receiverUsrs = symbol.receiverUsrs {
@@ -1963,17 +1960,23 @@ extension SourceKitServer {
19631960
}
19641961
// For call hierarchy preparation we only locate the definition
19651962
guard let usr = symbol.usr else { return nil }
1966-
return index.occurrences(ofUSR: usr, roles: [.definition, .declaration])
1967-
.compactMap { info -> CallHierarchyItem? in
1968-
guard let location = indexToLSPLocation(info.location) else {
1969-
return nil
1970-
}
1971-
return self.indexToLSPCallHierarchyItem(
1972-
symbol: info.symbol,
1973-
moduleName: info.location.moduleName,
1974-
location: location
1975-
)
1976-
}
1963+
1964+
// Only return a single call hierarchy item. Returning multiple doesn't make sense because they will all have the
1965+
// same USR (because we query them by USR) and will thus expand to the exact same call hierarchy.
1966+
// Also, VS Code doesn't seem to like multiple call hiearchy items being returned and fails to display any results
1967+
// if they are, failing with `Cannot read properties of undefined (reading 'map')`.
1968+
guard let definition = index.definitionOrDeclarationOccurrences(ofUSR: usr).first else {
1969+
return nil
1970+
}
1971+
guard let location = indexToLSPLocation(definition.location) else {
1972+
return nil
1973+
}
1974+
let callHierachyItem = self.indexToLSPCallHierarchyItem(
1975+
symbol: definition.symbol,
1976+
moduleName: definition.location.moduleName,
1977+
location: location
1978+
)
1979+
return [callHierachyItem]
19771980
}
19781981

19791982
/// Extracts our implementation-specific data about a call hierarchy
@@ -2255,6 +2258,18 @@ private let maxWorkspaceSymbolResults = 4096
22552258

22562259
public typealias Diagnostic = LanguageServerProtocol.Diagnostic
22572260

2261+
fileprivate extension IndexStoreDB {
2262+
/// If there are any definition occurrences of the given USR, return these.
2263+
/// Otherwise return declaration occurrences.
2264+
func definitionOrDeclarationOccurrences(ofUSR usr: String) -> [SymbolOccurrence] {
2265+
let definitions = occurrences(ofUSR: usr, roles: [.definition])
2266+
if !definitions.isEmpty {
2267+
return definitions
2268+
}
2269+
return occurrences(ofUSR: usr, roles: [.declaration])
2270+
}
2271+
}
2272+
22582273
extension IndexSymbolKind {
22592274
func asLspSymbolKind() -> SymbolKind {
22602275
switch self {

Tests/SourceKitLSPTests/CallHierarchyTests.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,45 @@ final class CallHierarchyTests: XCTestCase {
183183
]
184184
)
185185
}
186+
187+
func testReportSingleItemInPrepareCallHierarchy() async throws {
188+
let ws = try await SwiftPMTestWorkspace(
189+
files: [
190+
"MyLibrary/include/lib.h": """
191+
struct FilePathIndex {
192+
void 1️⃣foo();
193+
};
194+
""",
195+
"MyLibrary/lib.cpp": """
196+
#include "lib.h"
197+
void FilePathIndex::2️⃣foo() {}
198+
""",
199+
],
200+
build: true
201+
)
202+
let (uri, positions) = try ws.openDocument("lib.h", language: .cpp)
203+
let result = try await ws.testClient.send(
204+
CallHierarchyPrepareRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
205+
)
206+
207+
// Test that we don't provide both the definition in .cpp and the declaration on .h
208+
XCTAssertEqual(
209+
result,
210+
[
211+
CallHierarchyItem(
212+
name: "foo",
213+
kind: .method,
214+
tags: nil,
215+
detail: "",
216+
uri: try ws.uri(for: "lib.cpp"),
217+
range: try Range(ws.position(of: "2️⃣", in: "lib.cpp")),
218+
selectionRange: try Range(ws.position(of: "2️⃣", in: "lib.cpp")),
219+
data: LSPAny.dictionary([
220+
"usr": .string("c:@S@FilePathIndex@F@foo#"),
221+
"uri": .string(try ws.uri(for: "lib.cpp").stringValue),
222+
])
223+
)
224+
]
225+
)
226+
}
186227
}

0 commit comments

Comments
 (0)