@@ -60,6 +60,32 @@ package final class CheckedIndex {
60
60
private var checker : IndexOutOfDateChecker
61
61
private let index : IndexStoreDB
62
62
63
+ /// Maps the USR of a symbol to its name and the name of all its containers, from outermost to innermost.
64
+ ///
65
+ /// It is important that we cache this because we might find a lot of symbols in the same container for eg. workspace
66
+ /// symbols (eg. consider many symbols in the same C++ namespace). If we didn't cache this value, then we would need
67
+ /// to perform a `primaryDefinitionOrDeclarationOccurrence` lookup for all of these containers, which is expensive.
68
+ ///
69
+ /// Since we don't expect `CheckedIndex` to be outlive a single request it is acceptable to cache these results
70
+ /// without having any invalidation logic (similar to how we don't invalide results cached in
71
+ /// `IndexOutOfDateChecker`).
72
+ ///
73
+ /// ### Examples
74
+ /// If we have
75
+ /// ```swift
76
+ /// struct Foo {}
77
+ /// ``` then
78
+ /// `containerNamesCache[<usr of Foo>]` will be `["Foo"]`.
79
+ ///
80
+ /// If we have
81
+ /// ```swift
82
+ /// struct Bar {
83
+ /// struct Foo {}
84
+ /// }
85
+ /// ```, then
86
+ /// `containerNamesCache[<usr of Foo>]` will be `["Bar", "Foo"]`.
87
+ private var containerNamesCache : [ String : [ String ] ] = [ : ]
88
+
63
89
fileprivate init ( index: IndexStoreDB , checkLevel: IndexCheckLevel ) {
64
90
self . index = index
65
91
self . checker = IndexOutOfDateChecker ( checkLevel: checkLevel)
@@ -183,6 +209,84 @@ package final class CheckedIndex {
183
209
}
184
210
return result
185
211
}
212
+
213
+ /// The names of all containers the symbol is contained in, from outermost to innermost.
214
+ ///
215
+ /// ### Examples
216
+ /// In the following, the container names of `test` are `["Foo"]`.
217
+ /// ```swift
218
+ /// struct Foo {
219
+ /// func test() {}
220
+ /// }
221
+ /// ```
222
+ ///
223
+ /// In the following, the container names of `test` are `["Bar", "Foo"]`.
224
+ /// ```swift
225
+ /// struct Bar {
226
+ /// struct Foo {
227
+ /// func test() {}
228
+ /// }
229
+ /// }
230
+ /// ```
231
+ package func containerNames( of symbol: SymbolOccurrence ) -> [ String ] {
232
+ // The container name of accessors is the container of the surrounding variable.
233
+ let accessorOf = symbol. relations. filter { $0. roles. contains ( . accessorOf) }
234
+ if let primaryVariable = accessorOf. sorted ( ) . first {
235
+ if accessorOf. count > 1 {
236
+ logger. fault ( " Expected an occurrence to an accessor of at most one symbol, not multiple " )
237
+ }
238
+ if let primaryVariable = primaryDefinitionOrDeclarationOccurrence ( ofUSR: primaryVariable. symbol. usr) {
239
+ return containerNames ( of: primaryVariable)
240
+ }
241
+ }
242
+
243
+ let containers = symbol. relations. filter { $0. roles. contains ( . childOf) }
244
+ if containers. count > 1 {
245
+ logger. fault ( " Expected an occurrence to a child of at most one symbol, not multiple " )
246
+ }
247
+ let container = containers. filter {
248
+ switch $0. symbol. kind {
249
+ case . module, . namespace, . enum, . struct, . class, . protocol, . extension, . union:
250
+ return true
251
+ case . unknown, . namespaceAlias, . macro, . typealias, . function, . variable, . field, . enumConstant,
252
+ . instanceMethod, . classMethod, . staticMethod, . instanceProperty, . classProperty, . staticProperty, . constructor,
253
+ . destructor, . conversionFunction, . parameter, . using, . concept, . commentTag:
254
+ return false
255
+ }
256
+ } . sorted ( ) . first
257
+
258
+ guard var containerSymbol = container? . symbol else {
259
+ return [ ]
260
+ }
261
+ if let cached = containerNamesCache [ containerSymbol. usr] {
262
+ return cached
263
+ }
264
+
265
+ if containerSymbol. kind == . extension,
266
+ let extendedSymbol = self . occurrences ( relatedToUSR: containerSymbol. usr, roles: . extendedBy) . first? . symbol
267
+ {
268
+ containerSymbol = extendedSymbol
269
+ }
270
+ let result : [ String ]
271
+
272
+ // Use `forEachSymbolOccurrence` instead of `primaryDefinitionOrDeclarationOccurrence` to get a symbol occurrence
273
+ // for the container because it can be significantly faster: Eg. when searching for a C++ namespace (such as `llvm`),
274
+ // it may be declared in many files. Finding the canonical definition means that we would need to scan through all
275
+ // of these files. But we expect all all of these declarations to have the same parent container names and we don't
276
+ // care about locations here.
277
+ var containerDefinition : SymbolOccurrence ?
278
+ forEachSymbolOccurrence ( byUSR: containerSymbol. usr, roles: [ . definition, . declaration] ) { occurrence in
279
+ containerDefinition = occurrence
280
+ return false // stop iteration
281
+ }
282
+ if let containerDefinition {
283
+ result = self . containerNames ( of: containerDefinition) + [ containerSymbol. name]
284
+ } else {
285
+ result = [ containerSymbol. name]
286
+ }
287
+ containerNamesCache [ containerSymbol. usr] = result
288
+ return result
289
+ }
186
290
}
187
291
188
292
/// A wrapper around `IndexStoreDB` that allows the retrieval of a `CheckedIndex` with a specified check level or the
0 commit comments