@@ -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