@@ -18,14 +18,7 @@ extension PathHierarchy {
18
18
/// - Returns: Returns the unique identifier for the found match or raises an error if no match can be found.
19
19
/// - Throws: Raises a ``PathHierarchy/Error`` if no match can be found.
20
20
func find( path rawPath: String , parent: ResolvedIdentifier ? = nil , onlyFindSymbols: Bool ) throws -> ResolvedIdentifier {
21
- let node = try findNode ( path: rawPath, parentID: parent, onlyFindSymbols: onlyFindSymbols)
22
- if node. identifier == nil {
23
- throw Error . unfindableMatch ( node)
24
- }
25
- if onlyFindSymbols, node. symbol == nil {
26
- throw Error . nonSymbolMatchForSymbolLink ( path: rawPath [ ... ] )
27
- }
28
- return node. identifier
21
+ return try findNode ( path: rawPath, parentID: parent, onlyFindSymbols: onlyFindSymbols) . identifier
29
22
}
30
23
31
24
private func findNode( path rawPath: String , parentID: ResolvedIdentifier ? , onlyFindSymbols: Bool ) throws -> Node {
@@ -216,98 +209,121 @@ extension PathHierarchy {
216
209
onlyFindSymbols: Bool ,
217
210
rawPathForError: String
218
211
) throws -> Node {
219
- var node = startingPoint
220
- var remaining = pathComponents [ ... ]
221
-
222
- // Third, search for the match relative to the start node.
223
- if remaining. isEmpty {
224
- // If all path components were consumed, then the start of the search is the match.
225
- return node
226
- }
212
+ // All code paths through this function wants to perform extra verification on the return value before returning it to the caller.
213
+ // To accomplish that, the core implementation happens in `_innerImplementation`, which is called once, right below its definition.
227
214
228
- // Search for the remaining components from the node
229
- while true {
230
- let ( children, pathComponent) = try findChildContainer ( node: & node, remaining: remaining, rawPathForError: rawPathForError)
215
+ func _innerImplementation(
216
+ descendingFrom startingPoint: Node ,
217
+ pathComponents: ArraySlice < PathComponent > ,
218
+ onlyFindSymbols: Bool ,
219
+ rawPathForError: String
220
+ ) throws -> Node {
221
+ var node = startingPoint
222
+ var remaining = pathComponents [ ... ]
231
223
232
- do {
233
- guard let child = try children. find ( pathComponent. disambiguation) else {
234
- // The search has ended with a node that doesn't have a child matching the next path component.
235
- throw makePartialResultError ( node: node, remaining: remaining, rawPathForError: rawPathForError)
236
- }
237
- node = child
238
- remaining = remaining. dropFirst ( )
239
- if remaining. isEmpty {
240
- // If all path components are consumed, then the match is found.
241
- return child
242
- }
243
- } catch DisambiguationContainer . Error . lookupCollision( let collisions) {
244
- func handleWrappedCollision( ) throws -> Node {
245
- try handleCollision ( node: node, remaining: remaining, collisions: collisions, onlyFindSymbols: onlyFindSymbols, rawPathForError: rawPathForError)
246
- }
247
-
248
- // When there's a collision, use the remaining path components to try and narrow down the possible collisions.
224
+ // Search for the match relative to the start node.
225
+ if remaining. isEmpty {
226
+ // If all path components were consumed, then the start of the search is the match.
227
+ return node
228
+ }
229
+
230
+ // Search for the remaining components from the node
231
+ while true {
232
+ let ( children, pathComponent) = try findChildContainer ( node: & node, remaining: remaining, rawPathForError: rawPathForError)
249
233
250
- guard let nextPathComponent = remaining. dropFirst ( ) . first else {
251
- // This was the last path component so there's nothing to look ahead.
252
- //
253
- // It's possible for a symbol that exist on multiple languages to collide with itself.
254
- // Check if the collision can be resolved by finding a unique symbol or an otherwise preferred match.
255
- var uniqueCollisions : [ String : Node ] = [ : ]
256
- for (node, _) in collisions {
257
- guard let symbol = node. symbol else {
258
- // Non-symbol collisions should have already been resolved
259
- return try handleWrappedCollision ( )
260
- }
261
-
262
- let id = symbol. identifier. precise
263
- if symbol. identifier. interfaceLanguage == " swift " || !uniqueCollisions. keys. contains ( id) {
264
- uniqueCollisions [ id] = node
265
- }
266
-
267
- guard uniqueCollisions. count < 2 else {
268
- // Encountered more than one unique symbol
269
- return try handleWrappedCollision ( )
270
- }
234
+ do {
235
+ guard let child = try children. find ( pathComponent. disambiguation) else {
236
+ // The search has ended with a node that doesn't have a child matching the next path component.
237
+ throw makePartialResultError ( node: node, remaining: remaining, rawPathForError: rawPathForError)
271
238
}
272
- // A wrapped error would have been raised while iterating over the collection.
273
- return uniqueCollisions. first!. value
274
- }
275
-
276
- // Look ahead one path component to narrow down the list of collisions.
277
- // For each collision where the next path component can be found unambiguously, return that matching node one level down.
278
- let possibleMatchesOneLevelDown = collisions. compactMap {
279
- return try ? $0. node. children [ String ( nextPathComponent. name) ] ? . find ( nextPathComponent. disambiguation)
280
- }
281
- let onlyPossibleMatch : Node ?
282
-
283
- if possibleMatchesOneLevelDown. count == 1 {
284
- // Only one of the collisions found a match for the next path component
285
- onlyPossibleMatch = possibleMatchesOneLevelDown. first!
286
- } else if !possibleMatchesOneLevelDown. isEmpty, possibleMatchesOneLevelDown. dropFirst ( ) . allSatisfy ( { $0. symbol? . identifier. precise == possibleMatchesOneLevelDown. first!. symbol? . identifier. precise } ) {
287
- // It's also possible that different language representations of the same symbols appear as different collisions.
288
- // If _all_ collisions that can find the next path component are the same symbol, then we prefer the Swift version of that symbol.
289
- onlyPossibleMatch = possibleMatchesOneLevelDown. first ( where: { $0. symbol? . identifier. interfaceLanguage == " swift " } ) ?? possibleMatchesOneLevelDown. first!
290
- } else {
291
- onlyPossibleMatch = nil
292
- }
293
-
294
- if let onlyPossibleMatch {
295
- // If we found only a single match one level down then we've processed both this path component and the next.
296
- remaining = remaining. dropFirst ( 2 )
239
+ node = child
240
+ remaining = remaining. dropFirst ( )
297
241
if remaining. isEmpty {
298
- // If that was the end of the path we can simply return the result.
299
- return onlyPossibleMatch
242
+ // If all path components are consumed, then the match is found.
243
+ return child
244
+ }
245
+ } catch DisambiguationContainer . Error . lookupCollision( let collisions) {
246
+ func handleWrappedCollision( ) throws -> Node {
247
+ let match = try handleCollision ( node: node, remaining: remaining, collisions: collisions, onlyFindSymbols: onlyFindSymbols, rawPathForError: rawPathForError)
248
+ return match
249
+ }
250
+
251
+ // When there's a collision, use the remaining path components to try and narrow down the possible collisions.
252
+
253
+ guard let nextPathComponent = remaining. dropFirst ( ) . first else {
254
+ // This was the last path component so there's nothing to look ahead.
255
+ //
256
+ // It's possible for a symbol that exist on multiple languages to collide with itself.
257
+ // Check if the collision can be resolved by finding a unique symbol or an otherwise preferred match.
258
+ var uniqueCollisions : [ String : Node ] = [ : ]
259
+ for (node, _) in collisions {
260
+ guard let symbol = node. symbol else {
261
+ // Non-symbol collisions should have already been resolved
262
+ return try handleWrappedCollision ( )
263
+ }
264
+
265
+ let id = symbol. identifier. precise
266
+ if symbol. identifier. interfaceLanguage == " swift " || !uniqueCollisions. keys. contains ( id) {
267
+ uniqueCollisions [ id] = node
268
+ }
269
+
270
+ guard uniqueCollisions. count < 2 else {
271
+ // Encountered more than one unique symbol
272
+ return try handleWrappedCollision ( )
273
+ }
274
+ }
275
+ // A wrapped error would have been raised while iterating over the collection.
276
+ return uniqueCollisions. first!. value
277
+ }
278
+
279
+ // Look ahead one path component to narrow down the list of collisions.
280
+ // For each collision where the next path component can be found unambiguously, return that matching node one level down.
281
+ let possibleMatchesOneLevelDown = collisions. compactMap {
282
+ try ? $0. node. children [ String ( nextPathComponent. name) ] ? . find ( nextPathComponent. disambiguation)
283
+ }
284
+ let onlyPossibleMatch : Node ?
285
+
286
+ if possibleMatchesOneLevelDown. count == 1 {
287
+ // Only one of the collisions found a match for the next path component
288
+ onlyPossibleMatch = possibleMatchesOneLevelDown. first!
289
+ } else if !possibleMatchesOneLevelDown. isEmpty, possibleMatchesOneLevelDown. dropFirst ( ) . allSatisfy ( { $0. symbol? . identifier. precise == possibleMatchesOneLevelDown. first!. symbol? . identifier. precise } ) {
290
+ // It's also possible that different language representations of the same symbols appear as different collisions.
291
+ // If _all_ collisions that can find the next path component are the same symbol, then we prefer the Swift version of that symbol.
292
+ onlyPossibleMatch = possibleMatchesOneLevelDown. first ( where: { $0. symbol? . identifier. interfaceLanguage == " swift " } ) ?? possibleMatchesOneLevelDown. first!
300
293
} else {
301
- // Otherwise we continue looping over the remaining path components.
302
- node = onlyPossibleMatch
303
- continue
294
+ onlyPossibleMatch = nil
295
+ }
296
+
297
+ if let onlyPossibleMatch {
298
+ // If we found only a single match one level down then we've processed both this path component and the next.
299
+ remaining = remaining. dropFirst ( 2 )
300
+ if remaining. isEmpty {
301
+ // If that was the end of the path we can simply return the result.
302
+ return onlyPossibleMatch
303
+ } else {
304
+ // Otherwise we continue looping over the remaining path components.
305
+ node = onlyPossibleMatch
306
+ continue
307
+ }
304
308
}
309
+
310
+ // Couldn't resolve the collision by look ahead.
311
+ return try handleWrappedCollision ( )
305
312
}
306
-
307
- // Couldn't resolve the collision by look ahead.
308
- return try handleCollision ( node: node, remaining: remaining, collisions: collisions, onlyFindSymbols: onlyFindSymbols, rawPathForError: rawPathForError)
309
313
}
310
314
}
315
+
316
+ // Run the core implementation, defined above.
317
+ let node = try _innerImplementation ( descendingFrom: startingPoint, pathComponents: pathComponents, onlyFindSymbols: onlyFindSymbols, rawPathForError: rawPathForError)
318
+
319
+ // Perform extra validation on the return value before returning it to the caller.
320
+ if node. identifier == nil {
321
+ throw Error . unfindableMatch ( node)
322
+ }
323
+ if onlyFindSymbols, node. symbol == nil {
324
+ throw Error . nonSymbolMatchForSymbolLink ( path: rawPathForError)
325
+ }
326
+ return node
311
327
}
312
328
313
329
private func handleCollision(
0 commit comments