@@ -96,8 +96,27 @@ extension PathHierarchy {
96
96
if modules. count == 1 {
97
97
do {
98
98
return try searchForNode ( descendingFrom: modules. first!. value, pathComponents: remaining, parsedPathForError: parsedPathForError, onlyFindSymbols: onlyFindSymbols)
99
- } catch {
100
- // Ignore this error and raise an error about not finding the module instead.
99
+ } catch let error as PathHierarchy . Error {
100
+ switch error {
101
+ case . notFound:
102
+ // Ignore this error and raise an error about not finding the module instead.
103
+ break
104
+ case . unknownName( let partialResult, remaining: _, availableChildren: _) :
105
+ if partialResult. node. symbol? . kind. identifier == . module {
106
+ // Failed to find the first path component. Ignore this error and raise an error about not finding the module instead.
107
+ break
108
+ } else {
109
+ // Partially resolved the link. Raise the more specific error instead of a module-not-found error.
110
+ throw error
111
+ }
112
+
113
+ // These errors are all more specific than a module-not-found error would be.
114
+ case . unfindableMatch,
115
+ . nonSymbolMatchForSymbolLink,
116
+ . unknownDisambiguation,
117
+ . lookupCollision:
118
+ throw error
119
+ }
101
120
}
102
121
}
103
122
let topLevelNames = Set ( modules. keys + [ articlesContainer. name, tutorialContainer. name] )
@@ -193,7 +212,8 @@ extension PathHierarchy {
193
212
try handleCollision ( node: node, parsedPath: parsedPathForError, remaining: remaining, collisions: collisions, onlyFindSymbols: onlyFindSymbols)
194
213
}
195
214
196
- // See if the collision can be resolved by looking ahead on level deeper.
215
+ // When there's a collision, use the remaining path components to try and narrow down the possible collisions.
216
+
197
217
guard let nextPathComponent = remaining. dropFirst ( ) . first else {
198
218
// This was the last path component so there's nothing to look ahead.
199
219
//
@@ -219,18 +239,38 @@ extension PathHierarchy {
219
239
// A wrapped error would have been raised while iterating over the collection.
220
240
return uniqueCollisions. first!. value
221
241
}
222
- // Try resolving the rest of the path for each collision ...
223
- let possibleMatches = collisions. compactMap {
242
+
243
+ // Look ahead one path component to narrow down the list of collisions.
244
+ // For each collision where the next path component can be found unambiguously, return that matching node one level down.
245
+ let possibleMatchesOneLevelDown = collisions. compactMap {
224
246
return try ? $0. node. children [ nextPathComponent. name] ? . find ( nextPathComponent. kind, nextPathComponent. hash)
225
247
}
226
- // If only one collision matches, return that match.
227
- if possibleMatches. count == 1 {
228
- return possibleMatches. first!
248
+ let onlyPossibleMatch : Node ?
249
+
250
+ if possibleMatchesOneLevelDown. count == 1 {
251
+ // Only one of the collisions found a match for the next path component
252
+ onlyPossibleMatch = possibleMatchesOneLevelDown. first!
253
+ } else if !possibleMatchesOneLevelDown. isEmpty, possibleMatchesOneLevelDown. dropFirst ( ) . allSatisfy ( { $0. symbol? . identifier. precise == possibleMatchesOneLevelDown. first!. symbol? . identifier. precise } ) {
254
+ // It's also possible that different language representations of the same symbols appear as different collisions.
255
+ // If _all_ collisions that can find the next path component are the same symbol, then we prefer the Swift version of that symbol.
256
+ onlyPossibleMatch = possibleMatchesOneLevelDown. first ( where: { $0. symbol? . identifier. interfaceLanguage == " swift " } ) ?? possibleMatchesOneLevelDown. first!
257
+ } else {
258
+ onlyPossibleMatch = nil
229
259
}
230
- // If all matches are the same symbol, return the Swift version of that symbol
231
- if !possibleMatches. isEmpty, possibleMatches. dropFirst ( ) . allSatisfy ( { $0. symbol? . identifier. precise == possibleMatches. first!. symbol? . identifier. precise } ) {
232
- return possibleMatches. first ( where: { $0. symbol? . identifier. interfaceLanguage == " swift " } ) ?? possibleMatches. first!
260
+
261
+ if let onlyPossibleMatch = onlyPossibleMatch {
262
+ // If we found only a single match one level down then we've processed both this path component and the next.
263
+ remaining = remaining. dropFirst ( 2 )
264
+ if remaining. isEmpty {
265
+ // If that was the end of the path we can simply return the result.
266
+ return onlyPossibleMatch
267
+ } else {
268
+ // Otherwise we continue looping over the remaining path components.
269
+ node = onlyPossibleMatch
270
+ continue
271
+ }
233
272
}
273
+
234
274
// Couldn't resolve the collision by look ahead.
235
275
return try handleCollision ( node: node, parsedPath: parsedPathForError, remaining: remaining, collisions: collisions, onlyFindSymbols: onlyFindSymbols)
236
276
}
0 commit comments