Skip to content

Commit db45600

Browse files
committed
fix path collision detection for extensions to nested types
1 parent 8c182d6 commit db45600

File tree

1 file changed

+52
-4
lines changed

1 file changed

+52
-4
lines changed

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,15 +1036,63 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
10361036
let moduleName: String
10371037
var languages: Set<SourceLanguage>
10381038
}
1039-
var pathCollisionInfo = [String: [PathCollisionInfo]]()
1039+
var pathCollisionInfo = [[String]: [PathCollisionInfo]]()
10401040
pathCollisionInfo.reserveCapacity(totalSymbolCount)
10411041

10421042
// Group symbols by path from all of the available symbol graphs
10431043
for (moduleName, symbolGraph) in unifiedGraphs {
10441044
let symbols = Array(symbolGraph.symbols.values)
1045-
let pathsAndLanguages: [[(String, SourceLanguage)]] = symbols.concurrentMap { referencesWithoutDisambiguationFor($0, moduleName: moduleName, bundle: bundle).map {
1046-
($0.path.lowercased(), $0.sourceLanguage)
1047-
} }
1045+
1046+
let referenceMap = symbols.concurrentMap { symbol in
1047+
(symbol, referencesWithoutDisambiguationFor(symbol, moduleName: moduleName, bundle: bundle))
1048+
}.reduce(into: [String: [SourceLanguage: ResolvedTopicReference]](), { result, next in
1049+
let (symbol, references) = next
1050+
for reference in references {
1051+
result[symbol.uniqueIdentifier, default: [:]][reference.sourceLanguage] = reference
1052+
}
1053+
})
1054+
1055+
let parentMap = symbolGraph.relationshipsByLanguage.values.reduce(into: [String: String](), { parentMap, relationships in
1056+
for relationship in relationships {
1057+
switch relationship.kind {
1058+
case .memberOf, .requirementOf, .declaredIn:
1059+
parentMap[relationship.source] = relationship.target
1060+
default:
1061+
break
1062+
}
1063+
}
1064+
})
1065+
1066+
// "/" + "documentation" + MODULE_NAME + TOP-LEVEL-SYMBOL_NAME
1067+
let topLevelSymbolPathLength = 4
1068+
1069+
let pathsAndLanguages: [[([String], SourceLanguage)]] = symbols.concurrentMap { symbol in
1070+
guard let references = referenceMap[symbol.uniqueIdentifier] else {
1071+
return []
1072+
}
1073+
1074+
return references.map { language, reference in
1075+
var prefixLength: Int
1076+
if let parentId = parentMap[symbol.uniqueIdentifier],
1077+
let parentReference = referenceMap[parentId]?[language] ?? referenceMap[parentId]?.values.first {
1078+
// This is a child of some other symbol
1079+
prefixLength = parentReference.pathComponents.count
1080+
} else {
1081+
// This is a top-level symbol
1082+
prefixLength = topLevelSymbolPathLength-1
1083+
}
1084+
1085+
// PathComponents can have prefixes which are not known locally. In that case,
1086+
// the "empty" segments will be cut out later on. We follow the same logic here, as otherwise
1087+
// some collisions would not be detected.
1088+
// E.g. consider an extension to an external nested type `SomeModule.SomeStruct.SomeStruct`. The
1089+
// parent of this extended type symbol is `SomeModule`, however, the path for the extended type symbol
1090+
// is `SomeModule/SomeStruct/SomeStruct`, later on, this will change to `SomeModule/SomeStruct`. Now, if
1091+
// we also extend `SomeModule.SomeStruct`, the paths for both extensions could collide. To recognize (and resolve)
1092+
// the collision here, we work with the same, shortened paths.
1093+
return ((reference.pathComponents[0..<prefixLength] + [reference.pathComponents.last!]).map{ $0.lowercased() }, reference.sourceLanguage)
1094+
}
1095+
}
10481096

10491097
for (symbol, symbolPathsAndLanguages) in zip(symbols, pathsAndLanguages) {
10501098
for (path, language) in symbolPathsAndLanguages {

0 commit comments

Comments
 (0)