@@ -60,6 +60,32 @@ package final class CheckedIndex {
6060 private var checker : IndexOutOfDateChecker
6161 private let index : IndexStoreDB
6262
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+
6389 fileprivate init ( index: IndexStoreDB , checkLevel: IndexCheckLevel ) {
6490 self . index = index
6591 self . checker = IndexOutOfDateChecker ( checkLevel: checkLevel)
@@ -183,6 +209,84 @@ package final class CheckedIndex {
183209 }
184210 return result
185211 }
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+ }
186290}
187291
188292/// A wrapper around `IndexStoreDB` that allows the retrieval of a `CheckedIndex` with a specified check level or the
0 commit comments