@@ -1607,7 +1607,7 @@ extension SourceKitServer {
1607
1607
1608
1608
/// Find all symbols in the workspace that include a string in their name.
1609
1609
/// - returns: An array of SymbolOccurrences that match the string.
1610
- func findWorkspaceSymbols( matching: String ) -> [ SymbolOccurrence ] {
1610
+ func findWorkspaceSymbols( matching: String ) throws -> [ SymbolOccurrence ] {
1611
1611
// Ignore short queries since they are:
1612
1612
// - noisy and slow, since they can match many symbols
1613
1613
// - normally unintentional, triggered when the user types slowly or if the editor doesn't
@@ -1624,25 +1624,24 @@ extension SourceKitServer {
1624
1624
subsequence: true ,
1625
1625
ignoreCase: true
1626
1626
) { symbol in
1627
+ if Task . isCancelled {
1628
+ return false
1629
+ }
1627
1630
guard !symbol. location. isSystem && !symbol. roles. contains ( . accessorOf) else {
1628
1631
return true
1629
1632
}
1630
1633
symbolOccurrenceResults. append ( symbol)
1631
- // FIXME: Once we have cancellation support, we should fetch all results and take the top
1632
- // `maxWorkspaceSymbolResults` symbols but bail if cancelled.
1633
- //
1634
- // Until then, take the first `maxWorkspaceSymbolResults` symbols to limit the impact of
1635
- // queries which match many symbols.
1636
- return symbolOccurrenceResults. count < maxWorkspaceSymbolResults
1634
+ return true
1637
1635
}
1636
+ try Task . checkCancellation ( )
1638
1637
}
1639
- return symbolOccurrenceResults
1638
+ return symbolOccurrenceResults. sorted ( )
1640
1639
}
1641
1640
1642
1641
/// Handle a workspace/symbol request, returning the SymbolInformation.
1643
1642
/// - returns: An array with SymbolInformation for each matching symbol in the workspace.
1644
1643
func workspaceSymbols( _ req: WorkspaceSymbolsRequest ) async throws -> [ WorkspaceSymbolItem ] ? {
1645
- let symbols = findWorkspaceSymbols ( matching: req. query) . map ( WorkspaceSymbolItem . init)
1644
+ let symbols = try findWorkspaceSymbols ( matching: req. query) . map ( WorkspaceSymbolItem . init)
1646
1645
return symbols
1647
1646
}
1648
1647
@@ -1870,6 +1869,7 @@ extension SourceKitServer {
1870
1869
}
1871
1870
return indexToLSPLocation ( occurrence. location)
1872
1871
}
1872
+ . sorted ( )
1873
1873
}
1874
1874
1875
1875
/// Returns the result of a `DefinitionRequest` by running a `SymbolInfoRequest`, inspecting
@@ -1962,18 +1962,19 @@ extension SourceKitServer {
1962
1962
position: req. position
1963
1963
)
1964
1964
)
1965
- guard let symbol = symbols. first,
1966
- let index = await self . workspaceForDocument ( uri: req. textDocument. uri) ? . index
1967
- else {
1965
+ guard let index = await self . workspaceForDocument ( uri: req. textDocument. uri) ? . index else {
1968
1966
return nil
1969
1967
}
1970
- guard let usr = symbol. usr else { return nil }
1971
- var occurrences = index. occurrences ( ofUSR: usr, roles: . baseOf)
1972
- if occurrences. isEmpty {
1973
- occurrences = index. occurrences ( relatedToUSR: usr, roles: . overrideOf)
1974
- }
1968
+ let locations = symbols. flatMap { ( symbol) -> [ Location ] in
1969
+ guard let usr = symbol. usr else { return [ ] }
1970
+ var occurrences = index. occurrences ( ofUSR: usr, roles: . baseOf)
1971
+ if occurrences. isEmpty {
1972
+ occurrences = index. occurrences ( relatedToUSR: usr, roles: . overrideOf)
1973
+ }
1975
1974
1976
- return . locations( occurrences. compactMap { indexToLSPLocation ( $0. location) } )
1975
+ return occurrences. compactMap { indexToLSPLocation ( $0. location) }
1976
+ }
1977
+ return . locations( locations. sorted ( ) )
1977
1978
}
1978
1979
1979
1980
func references(
@@ -1987,17 +1988,19 @@ extension SourceKitServer {
1987
1988
position: req. position
1988
1989
)
1989
1990
)
1990
- guard let symbol = symbols. first, let index = await self . workspaceForDocument ( uri: req. textDocument. uri) ? . index
1991
- else {
1991
+ guard let index = await self . workspaceForDocument ( uri: req. textDocument. uri) ? . index else {
1992
1992
return [ ]
1993
1993
}
1994
- guard let usr = symbol. usr else { return [ ] }
1995
- logger. info ( " performing indexed jump-to-def with usr \( usr) " )
1996
- var roles : SymbolRole = [ . reference]
1997
- if req. context. includeDeclaration {
1998
- roles. formUnion ( [ . declaration, . definition] )
1994
+ let locations = symbols. flatMap { ( symbol) -> [ Location ] in
1995
+ guard let usr = symbol. usr else { return [ ] }
1996
+ logger. info ( " Finding references for USR \( usr) " )
1997
+ var roles : SymbolRole = [ . reference]
1998
+ if req. context. includeDeclaration {
1999
+ roles. formUnion ( [ . declaration, . definition] )
2000
+ }
2001
+ return index. occurrences ( ofUSR: usr, roles: roles) . compactMap { indexToLSPLocation ( $0. location) }
1999
2002
}
2000
- return index . occurrences ( ofUSR : usr , roles : roles ) . compactMap { indexToLSPLocation ( $0 . location ) }
2003
+ return locations . sorted ( )
2001
2004
}
2002
2005
2003
2006
private func indexToLSPCallHierarchyItem(
@@ -2032,29 +2035,31 @@ extension SourceKitServer {
2032
2035
position: req. position
2033
2036
)
2034
2037
)
2035
- guard let symbol = symbols. first, let index = await self . workspaceForDocument ( uri: req. textDocument. uri) ? . index
2036
- else {
2038
+ guard let index = await self . workspaceForDocument ( uri: req. textDocument. uri) ? . index else {
2037
2039
return nil
2038
2040
}
2039
2041
// For call hierarchy preparation we only locate the definition
2040
- guard let usr = symbol . usr else { return nil }
2042
+ let usrs = symbols . compactMap ( \ . usr)
2041
2043
2042
2044
// Only return a single call hierarchy item. Returning multiple doesn't make sense because they will all have the
2043
2045
// same USR (because we query them by USR) and will thus expand to the exact same call hierarchy.
2044
- // Also, VS Code doesn't seem to like multiple call hierarchy items being returned and fails to display any results
2045
- // if they are, failing with `Cannot read properties of undefined (reading 'map')`.
2046
- guard let definition = index. definitionOrDeclarationOccurrences ( ofUSR: usr) . first else {
2047
- return nil
2048
- }
2049
- guard let location = indexToLSPLocation ( definition. location) else {
2050
- return nil
2051
- }
2052
- let callHierarchyItem = self . indexToLSPCallHierarchyItem (
2053
- symbol: definition. symbol,
2054
- moduleName: definition. location. moduleName,
2055
- location: location
2056
- )
2057
- return [ callHierarchyItem]
2046
+ var callHierarchyItems = usrs. compactMap { ( usr) -> CallHierarchyItem ? in
2047
+ guard let definition = index. primaryDefinitionOrDeclarationOccurrence ( ofUSR: usr) else {
2048
+ return nil
2049
+ }
2050
+ guard let location = indexToLSPLocation ( definition. location) else {
2051
+ return nil
2052
+ }
2053
+ return self . indexToLSPCallHierarchyItem (
2054
+ symbol: definition. symbol,
2055
+ moduleName: definition. location. moduleName,
2056
+ location: location
2057
+ )
2058
+ } . sorted ( by: { Location ( uri: $0. uri, range: $0. range) < Location ( uri: $1. uri, range: $1. range) } )
2059
+
2060
+ // Ideally, we should show multiple symbols. But VS Code fails to display call hierarchies with multiple root items,
2061
+ // failing with `Cannot read properties of undefined (reading 'map')`. Pick the first one.
2062
+ return Array ( callHierarchyItems. prefix ( 1 ) )
2058
2063
}
2059
2064
2060
2065
/// Extracts our implementation-specific data about a call hierarchy
@@ -2090,7 +2095,7 @@ extension SourceKitServer {
2090
2095
return occurrence. relations. filter { $0. symbol. kind. isCallable }
2091
2096
. map { related in
2092
2097
// Resolve the caller's definition to find its location
2093
- let definition = index. occurrences ( ofUSR: related. symbol. usr, roles : [ . definition , . declaration ] ) . first
2098
+ let definition = index. primaryDefinitionOrDeclarationOccurrence ( ofUSR: related. symbol. usr)
2094
2099
let definitionSymbolLocation = definition? . location
2095
2100
let definitionLocation = definitionSymbolLocation. flatMap ( indexToLSPLocation)
2096
2101
@@ -2104,7 +2109,7 @@ extension SourceKitServer {
2104
2109
)
2105
2110
}
2106
2111
}
2107
- return calls
2112
+ return calls. sorted ( by : { $0 . from . name < $1 . from . name } )
2108
2113
}
2109
2114
2110
2115
func outgoingCalls( _ req: CallHierarchyOutgoingCallsRequest ) async throws -> [ CallHierarchyOutgoingCall ] ? {
@@ -2121,7 +2126,7 @@ extension SourceKitServer {
2121
2126
}
2122
2127
2123
2128
// Resolve the callee's definition to find its location
2124
- let definition = index. occurrences ( ofUSR: occurrence. symbol. usr, roles : [ . definition , . declaration ] ) . first
2129
+ let definition = index. primaryDefinitionOrDeclarationOccurrence ( ofUSR: occurrence. symbol. usr)
2125
2130
let definitionSymbolLocation = definition? . location
2126
2131
let definitionLocation = definitionSymbolLocation. flatMap ( indexToLSPLocation)
2127
2132
@@ -2134,7 +2139,7 @@ extension SourceKitServer {
2134
2139
fromRanges: [ location. range]
2135
2140
)
2136
2141
}
2137
- return calls
2142
+ return calls. sorted ( by : { $0 . to . name < $1 . to . name } )
2138
2143
}
2139
2144
2140
2145
private func indexToLSPTypeHierarchyItem(
@@ -2153,7 +2158,7 @@ extension SourceKitServer {
2153
2158
if conformances. isEmpty {
2154
2159
name = symbol. name
2155
2160
} else {
2156
- name = " \( symbol. name) : \( conformances. map ( \. symbol. name) . joined ( separator: " , " ) ) "
2161
+ name = " \( symbol. name) : \( conformances. map ( \. symbol. name) . sorted ( ) . joined ( separator: " , " ) ) "
2157
2162
}
2158
2163
// Add the file name and line to the detail string
2159
2164
if let url = location. uri. fileURL,
@@ -2197,25 +2202,42 @@ extension SourceKitServer {
2197
2202
position: req. position
2198
2203
)
2199
2204
)
2200
- guard let symbol = symbols. first else {
2205
+ guard ! symbols. isEmpty else {
2201
2206
return nil
2202
2207
}
2203
2208
guard let index = await self . workspaceForDocument ( uri: req. textDocument. uri) ? . index else {
2204
2209
return nil
2205
2210
}
2206
- guard let usr = symbol. usr else { return nil }
2207
- return index. occurrences ( ofUSR: usr, roles: [ . definition, . declaration] )
2208
- . compactMap { info -> TypeHierarchyItem ? in
2209
- guard let location = indexToLSPLocation ( info. location) else {
2210
- return nil
2211
+ let usrs =
2212
+ symbols
2213
+ . filter {
2214
+ // Only include references to type. For example, we don't want to find the type hierarchy of a constructor when
2215
+ // starting the type hierarchy on `Foo()``.
2216
+ switch $0. kind {
2217
+ case . class, . enum, . interface, . struct: return true
2218
+ default : return false
2211
2219
}
2212
- return self . indexToLSPTypeHierarchyItem (
2213
- symbol: info. symbol,
2214
- moduleName: info. location. moduleName,
2215
- location: location,
2216
- index: index
2217
- )
2218
2220
}
2221
+ . compactMap ( \. usr)
2222
+ let typeHierarchyItems = usrs. compactMap { ( usr) -> TypeHierarchyItem ? in
2223
+ guard
2224
+ let info = index. primaryDefinitionOrDeclarationOccurrence ( ofUSR: usr) ,
2225
+ let location = indexToLSPLocation ( info. location)
2226
+ else {
2227
+ return nil
2228
+ }
2229
+ return self . indexToLSPTypeHierarchyItem (
2230
+ symbol: info. symbol,
2231
+ moduleName: info. location. moduleName,
2232
+ location: location,
2233
+ index: index
2234
+ )
2235
+ }
2236
+ . sorted ( by: { $0. name < $1. name } )
2237
+
2238
+ // Ideally, we should show multiple symbols. But VS Code fails to display type hierarchies with multiple root items,
2239
+ // failing with `Cannot read properties of undefined (reading 'map')`. Pick the first one.
2240
+ return Array ( typeHierarchyItems. prefix ( 1 ) )
2219
2241
}
2220
2242
2221
2243
/// Extracts our implementation-specific data about a type hierarchy
@@ -2249,7 +2271,12 @@ extension SourceKitServer {
2249
2271
// Resolve retroactive conformances via the extensions
2250
2272
let extensions = index. occurrences ( ofUSR: data. usr, roles: . extendedBy)
2251
2273
let retroactiveConformanceOccurs = extensions. flatMap { occurrence -> [ SymbolOccurrence ] in
2252
- guard let related = occurrence. relations. first else {
2274
+ if occurrence. relations. count > 1 {
2275
+ // When the occurrence has an `extendedBy` relation, it's an extension declaration. An extension can only extend
2276
+ // a single type, so there can only be a single relation here.
2277
+ logger. fault ( " Expected at most extendedBy relation but got \( occurrence. relations. count) " )
2278
+ }
2279
+ guard let related = occurrence. relations. sorted ( ) . first else {
2253
2280
return [ ]
2254
2281
}
2255
2282
return index. occurrences ( relatedToUSR: related. symbol. usr, roles: . baseOf)
@@ -2263,7 +2290,7 @@ extension SourceKitServer {
2263
2290
}
2264
2291
2265
2292
// Resolve the supertype's definition to find its location
2266
- let definition = index. occurrences ( ofUSR: occurrence. symbol. usr, roles : [ . definition , . declaration ] ) . first
2293
+ let definition = index. primaryDefinitionOrDeclarationOccurrence ( ofUSR: occurrence. symbol. usr)
2267
2294
let definitionSymbolLocation = definition? . location
2268
2295
let definitionLocation = definitionSymbolLocation. flatMap ( indexToLSPLocation)
2269
2296
@@ -2274,7 +2301,7 @@ extension SourceKitServer {
2274
2301
index: index
2275
2302
)
2276
2303
}
2277
- return types
2304
+ return types. sorted ( by : { $0 . name < $1 . name } )
2278
2305
}
2279
2306
2280
2307
func subtypes( _ req: TypeHierarchySubtypesRequest ) async throws -> [ TypeHierarchyItem ] ? {
@@ -2289,14 +2316,19 @@ extension SourceKitServer {
2289
2316
2290
2317
// Convert occurrences to type hierarchy items
2291
2318
let types = occurs. compactMap { occurrence -> TypeHierarchyItem ? in
2292
- guard let location = indexToLSPLocation ( occurrence. location) ,
2293
- let related = occurrence. relations. first
2319
+ if occurrence. relations. count > 1 {
2320
+ // An occurrence with a `baseOf` or `extendedBy` relation is an occurrence inside an inheritance clause.
2321
+ // Such an occurrence can only be the source of a single type, namely the one that the inheritance clause belongs
2322
+ // to.
2323
+ logger. fault ( " Expected at most extendedBy or baseOf relation but got \( occurrence. relations. count) " )
2324
+ }
2325
+ guard let related = occurrence. relations. sorted ( ) . first, let location = indexToLSPLocation ( occurrence. location)
2294
2326
else {
2295
2327
return nil
2296
2328
}
2297
2329
2298
2330
// Resolve the subtype's definition to find its location
2299
- let definition = index. occurrences ( ofUSR: related. symbol. usr, roles : [ . definition , . declaration ] ) . first
2331
+ let definition = index. primaryDefinitionOrDeclarationOccurrence ( ofUSR: related. symbol. usr)
2300
2332
let definitionSymbolLocation = definition. map ( \. location)
2301
2333
let definitionLocation = definitionSymbolLocation. flatMap ( indexToLSPLocation)
2302
2334
@@ -2307,7 +2339,7 @@ extension SourceKitServer {
2307
2339
index: index
2308
2340
)
2309
2341
}
2310
- return types
2342
+ return types. sorted { $0 . name < $1 . name }
2311
2343
}
2312
2344
2313
2345
func pollIndex( _ req: PollIndexRequest ) async throws -> VoidResponse {
@@ -2348,6 +2380,14 @@ fileprivate extension IndexStoreDB {
2348
2380
}
2349
2381
return occurrences ( ofUSR: usr, roles: [ . declaration] )
2350
2382
}
2383
+
2384
+ /// Find a `SymbolOccurrence` that is considered the primary definition of the symbol with the given USR.
2385
+ ///
2386
+ /// If the USR has an ambiguous definition, the most important role of this function is to deterministically return
2387
+ /// the same result every time.
2388
+ func primaryDefinitionOrDeclarationOccurrence( ofUSR usr: String ) -> SymbolOccurrence ? {
2389
+ return definitionOrDeclarationOccurrences ( ofUSR: usr) . sorted ( ) . first
2390
+ }
2351
2391
}
2352
2392
2353
2393
extension IndexSymbolKind {
@@ -2394,7 +2434,11 @@ extension IndexSymbolKind {
2394
2434
extension SymbolOccurrence {
2395
2435
/// Get the name of the symbol that is a parent of this symbol, if one exists
2396
2436
func getContainerName( ) -> String ? {
2397
- return relations. first ( where: { $0. roles. contains ( . childOf) } ) ? . symbol. name
2437
+ let containers = relations. filter { $0. roles. contains ( . childOf) }
2438
+ if containers. count > 1 {
2439
+ logger. fault ( " Expected an occurrence to a child of at most one symbol, not multiple " )
2440
+ }
2441
+ return containers. sorted ( ) . first? . symbol. name
2398
2442
}
2399
2443
}
2400
2444
0 commit comments