Skip to content

Commit 0b2ca89

Browse files
committed
Ensure we handle multiple results returned by the index correctly
This replaces all uses of `.first` on index results in `SourceKitServer` by code that explicitly handles multiple results.
1 parent fef3cb8 commit 0b2ca89

File tree

2 files changed

+115
-73
lines changed

2 files changed

+115
-73
lines changed

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 98 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,18 +1961,19 @@ extension SourceKitServer {
19611961
position: req.position
19621962
)
19631963
)
1964-
guard let symbol = symbols.first,
1965-
let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index
1966-
else {
1964+
guard let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index else {
19671965
return nil
19681966
}
1969-
guard let usr = symbol.usr else { return nil }
1970-
var occurrences = index.occurrences(ofUSR: usr, roles: .baseOf)
1971-
if occurrences.isEmpty {
1972-
occurrences = index.occurrences(relatedToUSR: usr, roles: .overrideOf)
1973-
}
1967+
let locations = symbols.flatMap { (symbol) -> [Location] in
1968+
guard let usr = symbol.usr else { return [] }
1969+
var occurrences = index.occurrences(ofUSR: usr, roles: .baseOf)
1970+
if occurrences.isEmpty {
1971+
occurrences = index.occurrences(relatedToUSR: usr, roles: .overrideOf)
1972+
}
19741973

1975-
return .locations(occurrences.compactMap { indexToLSPLocation($0.location) }.sorted())
1974+
return occurrences.compactMap { indexToLSPLocation($0.location) }
1975+
}
1976+
return .locations(locations.sorted())
19761977
}
19771978

19781979
func references(
@@ -1986,17 +1987,19 @@ extension SourceKitServer {
19861987
position: req.position
19871988
)
19881989
)
1989-
guard let symbol = symbols.first, let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index
1990-
else {
1990+
guard let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index else {
19911991
return []
19921992
}
1993-
guard let usr = symbol.usr else { return [] }
1994-
logger.info("performing indexed jump-to-def with usr \(usr)")
1995-
var roles: SymbolRole = [.reference]
1996-
if req.context.includeDeclaration {
1997-
roles.formUnion([.declaration, .definition])
1993+
let locations = symbols.flatMap { (symbol) -> [Location] in
1994+
guard let usr = symbol.usr else { return [] }
1995+
logger.info("Finding references for USR \(usr)")
1996+
var roles: SymbolRole = [.reference]
1997+
if req.context.includeDeclaration {
1998+
roles.formUnion([.declaration, .definition])
1999+
}
2000+
return index.occurrences(ofUSR: usr, roles: roles).compactMap { indexToLSPLocation($0.location) }
19982001
}
1999-
return index.occurrences(ofUSR: usr, roles: roles).compactMap { indexToLSPLocation($0.location) }.sorted()
2002+
return locations.sorted()
20002003
}
20012004

20022005
private func indexToLSPCallHierarchyItem(
@@ -2031,29 +2034,31 @@ extension SourceKitServer {
20312034
position: req.position
20322035
)
20332036
)
2034-
guard let symbol = symbols.first, let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index
2035-
else {
2037+
guard let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index else {
20362038
return nil
20372039
}
20382040
// For call hierarchy preparation we only locate the definition
2039-
guard let usr = symbol.usr else { return nil }
2041+
let usrs = symbols.compactMap(\.usr)
20402042

20412043
// Only return a single call hierarchy item. Returning multiple doesn't make sense because they will all have the
20422044
// same USR (because we query them by USR) and will thus expand to the exact same call hierarchy.
2043-
// Also, VS Code doesn't seem to like multiple call hierarchy items being returned and fails to display any results
2044-
// if they are, failing with `Cannot read properties of undefined (reading 'map')`.
2045-
guard let definition = index.definitionOrDeclarationOccurrences(ofUSR: usr).first else {
2046-
return nil
2047-
}
2048-
guard let location = indexToLSPLocation(definition.location) else {
2049-
return nil
2050-
}
2051-
let callHierarchyItem = self.indexToLSPCallHierarchyItem(
2052-
symbol: definition.symbol,
2053-
moduleName: definition.location.moduleName,
2054-
location: location
2055-
)
2056-
return [callHierarchyItem]
2045+
var callHierarchyItems = usrs.compactMap { (usr) -> CallHierarchyItem? in
2046+
guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
2047+
return nil
2048+
}
2049+
guard let location = indexToLSPLocation(definition.location) else {
2050+
return nil
2051+
}
2052+
return self.indexToLSPCallHierarchyItem(
2053+
symbol: definition.symbol,
2054+
moduleName: definition.location.moduleName,
2055+
location: location
2056+
)
2057+
}.sorted(by: { Location(uri: $0.uri, range: $0.range) < Location(uri: $1.uri, range: $1.range) })
2058+
2059+
// Ideally, we should show multiple symbols. But VS Code fails to display call hierarchies with multiple root items,
2060+
// failing with `Cannot read properties of undefined (reading 'map')`. Pick the first one.
2061+
return Array(callHierarchyItems.prefix(1))
20572062
}
20582063

20592064
/// Extracts our implementation-specific data about a call hierarchy
@@ -2089,7 +2094,7 @@ extension SourceKitServer {
20892094
return occurrence.relations.filter { $0.symbol.kind.isCallable }
20902095
.map { related in
20912096
// Resolve the caller's definition to find its location
2092-
let definition = index.occurrences(ofUSR: related.symbol.usr, roles: [.definition, .declaration]).first
2097+
let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: related.symbol.usr)
20932098
let definitionSymbolLocation = definition?.location
20942099
let definitionLocation = definitionSymbolLocation.flatMap(indexToLSPLocation)
20952100

@@ -2120,7 +2125,7 @@ extension SourceKitServer {
21202125
}
21212126

21222127
// Resolve the callee's definition to find its location
2123-
let definition = index.occurrences(ofUSR: occurrence.symbol.usr, roles: [.definition, .declaration]).first
2128+
let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: occurrence.symbol.usr)
21242129
let definitionSymbolLocation = definition?.location
21252130
let definitionLocation = definitionSymbolLocation.flatMap(indexToLSPLocation)
21262131

@@ -2196,26 +2201,42 @@ extension SourceKitServer {
21962201
position: req.position
21972202
)
21982203
)
2199-
guard let symbol = symbols.first else {
2204+
guard !symbols.isEmpty else {
22002205
return nil
22012206
}
22022207
guard let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index else {
22032208
return nil
22042209
}
2205-
guard let usr = symbol.usr else { return nil }
2206-
return index.occurrences(ofUSR: usr, roles: [.definition, .declaration])
2207-
.compactMap { info -> TypeHierarchyItem? in
2208-
guard let location = indexToLSPLocation(info.location) else {
2209-
return nil
2210+
let usrs =
2211+
symbols
2212+
.filter {
2213+
// Only include references to type. For example, we don't want to find the type hierarchy of a constructor when
2214+
// starting the type hierarchy on `Foo()``.
2215+
switch $0.kind {
2216+
case .class, .enum, .interface, .struct: return true
2217+
default: return false
22102218
}
2211-
return self.indexToLSPTypeHierarchyItem(
2212-
symbol: info.symbol,
2213-
moduleName: info.location.moduleName,
2214-
location: location,
2215-
index: index
2216-
)
22172219
}
2218-
.sorted(by: { $0.name < $1.name })
2220+
.compactMap(\.usr)
2221+
let typeHierarchyItems = usrs.compactMap { (usr) -> TypeHierarchyItem? in
2222+
guard
2223+
let info = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr),
2224+
let location = indexToLSPLocation(info.location)
2225+
else {
2226+
return nil
2227+
}
2228+
return self.indexToLSPTypeHierarchyItem(
2229+
symbol: info.symbol,
2230+
moduleName: info.location.moduleName,
2231+
location: location,
2232+
index: index
2233+
)
2234+
}
2235+
.sorted(by: { $0.name < $1.name })
2236+
2237+
// Ideally, we should show multiple symbols. But VS Code fails to display type hierarchies with multiple root items,
2238+
// failing with `Cannot read properties of undefined (reading 'map')`. Pick the first one.
2239+
return Array(typeHierarchyItems.prefix(1))
22192240
}
22202241

22212242
/// Extracts our implementation-specific data about a type hierarchy
@@ -2249,7 +2270,12 @@ extension SourceKitServer {
22492270
// Resolve retroactive conformances via the extensions
22502271
let extensions = index.occurrences(ofUSR: data.usr, roles: .extendedBy)
22512272
let retroactiveConformanceOccurs = extensions.flatMap { occurrence -> [SymbolOccurrence] in
2252-
guard let related = occurrence.relations.first else {
2273+
if occurrence.relations.count > 1 {
2274+
// When the occurrence has an `extendedBy` relation, it's an extension declaration. An extension can only extend
2275+
// a single type, so there can only be a single relation here.
2276+
logger.fault("Expected at most extendedBy relation but got \(occurrence.relations.count)")
2277+
}
2278+
guard let related = occurrence.relations.sorted().first else {
22532279
return []
22542280
}
22552281
return index.occurrences(relatedToUSR: related.symbol.usr, roles: .baseOf)
@@ -2263,7 +2289,7 @@ extension SourceKitServer {
22632289
}
22642290

22652291
// Resolve the supertype's definition to find its location
2266-
let definition = index.occurrences(ofUSR: occurrence.symbol.usr, roles: [.definition, .declaration]).first
2292+
let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: occurrence.symbol.usr)
22672293
let definitionSymbolLocation = definition?.location
22682294
let definitionLocation = definitionSymbolLocation.flatMap(indexToLSPLocation)
22692295

@@ -2289,14 +2315,19 @@ extension SourceKitServer {
22892315

22902316
// Convert occurrences to type hierarchy items
22912317
let types = occurs.compactMap { occurrence -> TypeHierarchyItem? in
2292-
guard let location = indexToLSPLocation(occurrence.location),
2293-
let related = occurrence.relations.first
2318+
if occurrence.relations.count > 1 {
2319+
// An occurrence with a `baseOf` or `extendedBy` relation is an occurrence inside an inheritance clause.
2320+
// Such an occurrence can only be the source of a single type, namely the one that the inheritance clause belongs
2321+
// to.
2322+
logger.fault("Expected at most extendedBy or baseOf relation but got \(occurrence.relations.count)")
2323+
}
2324+
guard let related = occurrence.relations.sorted().first, let location = indexToLSPLocation(occurrence.location)
22942325
else {
22952326
return nil
22962327
}
22972328

22982329
// Resolve the subtype's definition to find its location
2299-
let definition = index.occurrences(ofUSR: related.symbol.usr, roles: [.definition, .declaration]).first
2330+
let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: related.symbol.usr)
23002331
let definitionSymbolLocation = definition.map(\.location)
23012332
let definitionLocation = definitionSymbolLocation.flatMap(indexToLSPLocation)
23022333

@@ -2348,6 +2379,14 @@ fileprivate extension IndexStoreDB {
23482379
}
23492380
return occurrences(ofUSR: usr, roles: [.declaration])
23502381
}
2382+
2383+
/// Find a `SymbolOccurrence` that is considered the primary definition of the symbol with the given USR.
2384+
///
2385+
/// If the USR has an ambiguous definition, the most important role of this function is to deterministically return
2386+
/// the same result every time.
2387+
func primaryDefinitionOrDeclarationOccurrence(ofUSR usr: String) -> SymbolOccurrence? {
2388+
return definitionOrDeclarationOccurrences(ofUSR: usr).sorted().first
2389+
}
23512390
}
23522391

23532392
extension IndexSymbolKind {
@@ -2394,7 +2433,11 @@ extension IndexSymbolKind {
23942433
extension SymbolOccurrence {
23952434
/// Get the name of the symbol that is a parent of this symbol, if one exists
23962435
func getContainerName() -> String? {
2397-
return relations.first(where: { $0.roles.contains(.childOf) })?.symbol.name
2436+
let containers = relations.filter { $0.roles.contains(.childOf) }
2437+
if containers.count > 1 {
2438+
logger.fault("Expected an occurrence to a child of at most one symbol, not multiple")
2439+
}
2440+
return containers.sorted().first?.symbol.name
23982441
}
23992442
}
24002443

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,38 +1091,37 @@ extension sourcekitd_api_uid_t {
10911091

10921092
func asSymbolKind(_ vals: sourcekitd_api_values) -> SymbolKind? {
10931093
switch self {
1094-
case vals.declClass:
1094+
case vals.declClass, vals.refClass, vals.declActor, vals.refActor:
10951095
return .class
1096-
case vals.declMethodInstance,
1097-
vals.declMethodStatic,
1098-
vals.declMethodClass:
1096+
case vals.declMethodInstance, vals.refMethodInstance,
1097+
vals.declMethodStatic, vals.refMethodStatic,
1098+
vals.declMethodClass, vals.refMethodClass:
10991099
return .method
1100-
case vals.declVarInstance,
1101-
vals.declVarStatic,
1102-
vals.declVarClass:
1100+
case vals.declVarInstance, vals.refVarInstance,
1101+
vals.declVarStatic, vals.refVarStatic,
1102+
vals.declVarClass, vals.refVarClass:
11031103
return .property
1104-
case vals.declEnum:
1104+
case vals.declEnum, vals.refEnum:
11051105
return .enum
1106-
case vals.declEnumElement:
1106+
case vals.declEnumElement, vals.refEnumElement:
11071107
return .enumMember
1108-
case vals.declProtocol:
1108+
case vals.declProtocol, vals.refProtocol:
11091109
return .interface
1110-
case vals.declFunctionFree:
1110+
case vals.declFunctionFree, vals.refFunctionFree:
11111111
return .function
1112-
case vals.declVarGlobal,
1113-
vals.declVarLocal:
1112+
case vals.declVarGlobal, vals.refVarGlobal,
1113+
vals.declVarLocal, vals.refVarLocal:
11141114
return .variable
1115-
case vals.declStruct:
1115+
case vals.declStruct, vals.refStruct:
11161116
return .struct
1117-
case vals.declGenericTypeParam:
1117+
case vals.declGenericTypeParam, vals.refGenericTypeParam:
11181118
return .typeParameter
11191119
case vals.declExtension:
1120-
// There are no extensions in LSP, so I return something vaguely similar
1120+
// There are no extensions in LSP, so we return something vaguely similar
11211121
return .namespace
11221122
case vals.refModule:
11231123
return .module
1124-
case vals.refConstructor,
1125-
vals.declConstructor:
1124+
case vals.declConstructor, vals.refConstructor:
11261125
return .constructor
11271126
default:
11281127
return nil

0 commit comments

Comments
 (0)