@@ -1021,244 +1021,6 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
10211021
10221022 return ( ( reference, symbol. uniqueIdentifier, graphNode, documentation) , [ ] )
10231023 }
1024-
1025- /// Returns a map between symbol identifiers and topic references.
1026- ///
1027- /// - Parameters:
1028- /// - symbolGraph: The complete symbol graph to walk through.
1029- /// - bundle: The bundle to use when creating symbol references.
1030- func referencesForSymbols( in unifiedGraphs: [ String : UnifiedSymbolGraph ] , bundle: DocumentationBundle ) -> [ SymbolGraph . Symbol . Identifier : [ ResolvedTopicReference ] ] {
1031- // The implementation of this function is fairly tricky because in most cases it has to preserve past behavior.
1032- //
1033- // This is because symbol references bake the disambiguators into the path, making it the only version of that
1034- // path that resolves to that symbol. In other words, a reference with "too few" or "too many" disambiguators
1035- // will fail to resolve. Changing what's considered the "correct" disambiguators for a symbol means that links
1036- // that used to resolve will break with the new behavior.
1037- //
1038- // The tests in `SymbolDisambiguationTests` cover the known behaviors that should be preserved.
1039- //
1040- // The real solution to this problem is to allow symbol links to over-specify disambiguators and improve the
1041- // diagnostics when symbol links are ambiguous. (rdar://78518537)
1042- // That will allow for fixes to the least amount of disambiguation without breaking existing links.
1043-
1044-
1045- // The current implementation works in 3 phases:
1046- // - First, it computes the paths without disambiguators to identify colliding paths.
1047- // - Second, it computes the "correct" disambiguators for each collision.
1048- // - Lastly, it joins together the results in a stable order to avoid indeterministic behavior.
1049-
1050-
1051- let totalSymbolCount = unifiedGraphs. values. map { $0. symbols. count } . reduce ( 0 , + )
1052-
1053- /// Temporary data structure to hold input to compute paths with or without disambiguation.
1054- struct PathCollisionInfo {
1055- let symbol : UnifiedSymbolGraph . Symbol
1056- let moduleName : String
1057- var languages : Set < SourceLanguage >
1058- }
1059- var pathCollisionInfo = [ [ String] : [ PathCollisionInfo] ] ( )
1060- pathCollisionInfo. reserveCapacity ( totalSymbolCount)
1061-
1062- // Group symbols by path from all of the available symbol graphs
1063- for (moduleName, symbolGraph) in unifiedGraphs {
1064- let symbols = Array ( symbolGraph. symbols. values)
1065-
1066- let referenceMap = symbols. concurrentMap { symbol in
1067- ( symbol, referencesWithoutDisambiguationFor ( symbol, moduleName: moduleName, bundle: bundle) )
1068- } . reduce ( into: [ String: [ SourceLanguage: ResolvedTopicReference] ] ( ) , { result, next in
1069- let ( symbol, references) = next
1070- for reference in references {
1071- result [ symbol. uniqueIdentifier, default: [ : ] ] [ reference. sourceLanguage] = reference
1072- }
1073- } )
1074-
1075- let parentMap = symbolGraph. relationshipsByLanguage. reduce ( into: [ String: [ SourceLanguage: String] ] ( ) , { parentMap, next in
1076- let ( selector, relationships) = next
1077- guard let language = SourceLanguage ( knownLanguageIdentifier: selector. interfaceLanguage) else {
1078- return
1079- }
1080-
1081- for relationship in relationships {
1082- switch relationship. kind {
1083- case . memberOf, . requirementOf, . declaredIn:
1084- parentMap [ relationship. source, default: [ : ] ] [ language] = relationship. target
1085- default :
1086- break
1087- }
1088- }
1089- } )
1090-
1091- let pathsAndLanguages : [ [ ( [ String ] , SourceLanguage ) ] ] = symbols. concurrentMap { symbol in
1092- guard let references = referenceMap [ symbol. uniqueIdentifier] else {
1093- return [ ]
1094- }
1095-
1096- return references. map { language, reference in
1097- var prefixLength : Int
1098- if let parentId = parentMap [ symbol. uniqueIdentifier] ? [ language] ,
1099- let parentReference = referenceMap [ parentId] ? [ language] ?? referenceMap [ parentId] ? . values. first {
1100- // This is a child of some other symbol
1101- prefixLength = parentReference. pathComponents. count
1102- } else {
1103- // This is a top-level symbol or another symbol without parent (e.g. default implementation)
1104- prefixLength = reference. pathComponents. count- 1
1105- }
1106-
1107- // PathComponents can have prefixes which are not known locally. In that case,
1108- // the "empty" segments will be cut out later on. We follow the same logic here, as otherwise
1109- // some collisions would not be detected.
1110- // E.g. consider an extension to an external nested type `SomeModule.SomeStruct.SomeStruct`. The
1111- // parent of this extended type symbol is `SomeModule`, however, the path for the extended type symbol
1112- // is `SomeModule/SomeStruct/SomeStruct`, later on, this will change to `SomeModule/SomeStruct`. Now, if
1113- // we also extend `SomeModule.SomeStruct`, the paths for both extensions could collide. To recognize (and resolve)
1114- // the collision here, we work with the same, shortened paths.
1115- return ( ( reference. pathComponents [ 0 ..< prefixLength] + [ reference. pathComponents. last!] ) . map { $0. lowercased ( ) } , reference. sourceLanguage)
1116- }
1117- }
1118-
1119- for (symbol, symbolPathsAndLanguages) in zip ( symbols, pathsAndLanguages) {
1120- for (path, language) in symbolPathsAndLanguages {
1121- if let existingReferences = pathCollisionInfo [ path] {
1122- if existingReferences. allSatisfy ( { $0. symbol. uniqueIdentifier != symbol. uniqueIdentifier} ) {
1123- // A collision - different symbol but same paths
1124- pathCollisionInfo [ path] !. append ( PathCollisionInfo ( symbol: symbol, moduleName: moduleName, languages: [ language] ) )
1125- } else {
1126- // Same symbol but in a different source language.
1127- pathCollisionInfo [ path] ! = pathCollisionInfo [ path] !. map { PathCollisionInfo ( symbol: $0. symbol, moduleName: $0. moduleName, languages: $0. languages. union ( [ language] ) ) }
1128- }
1129- } else {
1130- // First occurrence of this path
1131- pathCollisionInfo [ path] = [ PathCollisionInfo ( symbol: symbol, moduleName: moduleName, languages: [ language] ) ]
1132- }
1133- }
1134- }
1135- }
1136-
1137- /// Temporary data structure to hold groups of disambiguated references
1138- ///
1139- /// Since the order of `pathCollisionInfo` isn't stable across program executions, simply joining the results for a given symbol that has different
1140- /// paths in different source languages would also result in an unstable order (depending on the order that the different paths were processed).
1141- /// Instead we gather all groups of results and join them in a stable order.
1142- struct IntermediateResultGroup {
1143- let conflictingSymbolLanguage : SourceLanguage
1144- let disambiguatedReferences : [ ResolvedTopicReference ]
1145- }
1146- var resultGroups = [ SymbolGraph . Symbol. Identifier: [ IntermediateResultGroup] ] ( )
1147- resultGroups. reserveCapacity ( totalSymbolCount)
1148-
1149- // Translate symbols to topic references, adjust paths where necessary.
1150- for collisions in pathCollisionInfo. values {
1151- let disambiguationSuffixes = collisions. map ( \. symbol) . requiredDisambiguationSuffixes
1152-
1153- for (collisionInfo, disambiguationSuffix) in zip ( collisions, disambiguationSuffixes) {
1154- let language = collisionInfo. languages. contains ( . swift) ? . swift : collisionInfo. languages. first!
1155-
1156- // If the symbol has externally provided disambiguated path components, trust that those are accurate.
1157- if let knownDisambiguatedComponents = knownDisambiguatedSymbolPathComponents ? [ collisionInfo. symbol. uniqueIdentifier] ,
1158- collisionInfo. symbol. defaultSymbol? . pathComponents. count == knownDisambiguatedComponents. count
1159- {
1160- let symbolReference = SymbolReference ( pathComponents: knownDisambiguatedComponents, interfaceLanguages: collisionInfo. symbol. sourceLanguages)
1161- resultGroups [ collisionInfo. symbol. defaultIdentifier, default: [ ] ] . append (
1162- IntermediateResultGroup (
1163- conflictingSymbolLanguage: language,
1164- disambiguatedReferences: [ ResolvedTopicReference ( symbolReference: symbolReference, moduleName: collisionInfo. moduleName, bundle: bundle) ]
1165- )
1166- )
1167- continue
1168- }
1169-
1170- // If this is a multi-language collision that doesn't need disambiguation, only emit that symbol once.
1171- if collisionInfo. languages. count > 1 , disambiguationSuffix == ( false , false ) {
1172- let symbolReference = SymbolReference (
1173- collisionInfo. symbol. uniqueIdentifier,
1174- interfaceLanguages: collisionInfo. symbol. sourceLanguages,
1175- defaultSymbol: collisionInfo. symbol. defaultSymbol,
1176- shouldAddHash: false ,
1177- shouldAddKind: false
1178- )
1179-
1180- resultGroups [ collisionInfo. symbol. defaultIdentifier, default: [ ] ] . append (
1181- IntermediateResultGroup (
1182- conflictingSymbolLanguage: language,
1183- disambiguatedReferences: [ ResolvedTopicReference ( symbolReference: symbolReference, moduleName: collisionInfo. moduleName, bundle: bundle) ]
1184- )
1185- )
1186- continue
1187- }
1188-
1189- // Emit the disambiguated references for all languages for this symbol's collision.
1190- var symbolSelectors = [ collisionInfo. symbol. defaultSelector!]
1191- for selector in collisionInfo. symbol. mainGraphSelectors where !symbolSelectors. contains ( selector) {
1192- symbolSelectors. append ( selector)
1193- }
1194- symbolSelectors = symbolSelectors. filter { collisionInfo. languages. contains ( SourceLanguage ( id: $0. interfaceLanguage) ) }
1195-
1196- resultGroups [ collisionInfo. symbol. defaultIdentifier, default: [ ] ] . append (
1197- IntermediateResultGroup (
1198- conflictingSymbolLanguage: language,
1199- disambiguatedReferences: symbolSelectors. map { selector in
1200- let symbolReference = SymbolReference (
1201- collisionInfo. symbol. uniqueIdentifier,
1202- interfaceLanguages: collisionInfo. symbol. sourceLanguages,
1203- defaultSymbol: collisionInfo. symbol. symbol ( forSelector: selector) ,
1204- shouldAddHash: disambiguationSuffix. shouldAddIdHash,
1205- shouldAddKind: disambiguationSuffix. shouldAddKind
1206- )
1207- return ResolvedTopicReference ( symbolReference: symbolReference, moduleName: collisionInfo. moduleName, bundle: bundle)
1208- }
1209- )
1210- )
1211- }
1212- }
1213-
1214- return resultGroups. mapValues ( {
1215- return $0. sorted ( by: { lhs, rhs in
1216- switch ( lhs. conflictingSymbolLanguage, rhs. conflictingSymbolLanguage) {
1217- // If only one result group is Swift, that comes before the other result.
1218- case ( . swift, let other) where other != . swift:
1219- return true
1220- case ( let other, . swift) where other != . swift:
1221- return false
1222-
1223- // Otherwise, compare the first path to ensure a deterministic order.
1224- default :
1225- return lhs. disambiguatedReferences [ 0 ] . path < rhs. disambiguatedReferences [ 0 ] . path
1226- }
1227- } ) . flatMap ( { $0. disambiguatedReferences } )
1228- } )
1229- }
1230-
1231- private func referencesWithoutDisambiguationFor( _ symbol: UnifiedSymbolGraph . Symbol , moduleName: String , bundle: DocumentationBundle ) -> [ ResolvedTopicReference ] {
1232- if let pathComponents = knownDisambiguatedSymbolPathComponents ? [ symbol. uniqueIdentifier] ,
1233- let componentsCount = symbol. defaultSymbol? . pathComponents. count,
1234- pathComponents. count == componentsCount
1235- {
1236- let symbolReference = SymbolReference ( pathComponents: pathComponents, interfaceLanguages: symbol. sourceLanguages)
1237- return [ ResolvedTopicReference ( symbolReference: symbolReference, moduleName: moduleName, bundle: bundle) ]
1238- }
1239-
1240- // A unified symbol that exist in multiple languages may have multiple references.
1241-
1242- // Find all of the relevant selectors, starting with the `defaultSelector`.
1243- // Any reference after the first is considered an alias/alternative to the first reference
1244- // and will resolve to the first reference.
1245- var symbolSelectors = [ symbol. defaultSelector]
1246- for selector in symbol. mainGraphSelectors where !symbolSelectors. contains ( selector) {
1247- symbolSelectors. append ( selector)
1248- }
1249-
1250- return symbolSelectors. map { selector in
1251- let defaultSymbol = symbol. symbol ( forSelector: selector) !
1252- let symbolReference = SymbolReference (
1253- symbol. uniqueIdentifier,
1254- interfaceLanguages: symbol. sourceLanguages. filter { $0 == SourceLanguage ( id: defaultSymbol. identifier. interfaceLanguage) } ,
1255- defaultSymbol: defaultSymbol,
1256- shouldAddHash: false ,
1257- shouldAddKind: false
1258- )
1259- return ResolvedTopicReference ( symbolReference: symbolReference, moduleName: moduleName, bundle: bundle)
1260- }
1261- }
12621024
12631025 private func parentChildRelationship( from edge: SymbolGraph . Relationship ) -> ( ResolvedTopicReference , ResolvedTopicReference ) ? {
12641026 // Filter only parent <-> child edges
0 commit comments