@@ -76,6 +76,8 @@ struct SymbolGraphLoader {
7676 symbolGraph = try SymbolGraphConcurrentDecoder . decode ( data)
7777 }
7878
79+ Self . applyWorkaroundFor139305015 ( to: & symbolGraph)
80+
7981 symbolGraphTransformer ? ( & symbolGraph)
8082
8183 let ( moduleName, isMainSymbolGraph) = Self . moduleNameFor ( symbolGraph, at: symbolGraphURL)
@@ -362,6 +364,62 @@ struct SymbolGraphLoader {
362364 }
363365 return ( moduleName, isMainSymbolGraph)
364366 }
367+
368+ private static func applyWorkaroundFor139305015( to symbolGraph: inout SymbolGraph ) {
369+ guard symbolGraph. symbols. values. mapFirst ( where: { SourceLanguage ( id: $0. identifier. interfaceLanguage) } ) == . objectiveC else {
370+ return
371+ }
372+
373+ // Clang emits anonymous structs and unions differently than anonymous enums (rdar://139305015).
374+ //
375+ // The anonymous structs, with empty names, causes issues in a few different places for DocC:
376+ // - The IndexingRecords (one of the `--emit-digest` files) throws an error about the empty name.
377+ // - The NavigatorIndex.Builder may throw an error about the empty name.
378+ // - Their pages can't be navigated to because their URL path end with a leading slash.
379+ // The corresponding static hosting 'index.html' copy also overrides the container's index.html file because
380+ // its file path has two slashes, for example "/documentation/ModuleName/ContainerName//index.html".
381+ //
382+ // To avoid all those issues without handling empty names throughout the code,
383+ // we fill in titles and navigator titles for these symbols using the same format as Clang uses for anonymous enums.
384+
385+ let relationshipsByTarget = [ String: [ SymbolGraph . Relationship] ] ( grouping: symbolGraph. relationships, by: \. target)
386+
387+ for (usr, symbol) in symbolGraph. symbols {
388+ guard symbol. names. title. isEmpty,
389+ symbol. names. navigator? . map ( \. spelling) . joined ( ) . isEmpty == true ,
390+ symbol. pathComponents. last? . isEmpty == true
391+ else {
392+ continue
393+ }
394+
395+ // This symbol has an empty title and an empty navigator title.
396+ var modified = symbol
397+ let fallbackTitle = " \( symbol. kind. identifier. identifier) (unnamed) "
398+ modified. names. title = fallbackTitle
399+ // Clang uses a single `identifier` fragment for anonymous enums.
400+ modified. names. navigator = [ . init( kind: . identifier, spelling: fallbackTitle, preciseIdentifier: nil ) ]
401+ // Don't update `modified.names.subHeading`. Clang _doesn't_ use "enum (unnamed)" for the `Symbol/Names/subHeading` so we don't add it here either.
402+
403+ // Clang uses the "enum (unnamed)" in the path components of anonymous enums so we follow that format for anonymous structs.
404+ modified. pathComponents [ modified. pathComponents. count - 1 ] = fallbackTitle
405+ symbolGraph. symbols [ usr] = modified
406+
407+ // Also update all the members whose path components start with the container's path components so that they're consistent.
408+ if let relationships = relationshipsByTarget [ usr] {
409+ let containerPathComponents = modified. pathComponents
410+
411+ for memberRelationship in relationships where memberRelationship. kind == . memberOf {
412+ guard var modifiedMember = symbolGraph. symbols. removeValue ( forKey: memberRelationship. source) else { continue }
413+ // Only update the member's path components if it starts with the original container's components.
414+ guard modifiedMember. pathComponents. starts ( with: symbol. pathComponents) else { continue }
415+
416+ modifiedMember. pathComponents. replaceSubrange ( containerPathComponents. indices, with: containerPathComponents)
417+
418+ symbolGraph. symbols [ memberRelationship. source] = modifiedMember
419+ }
420+ }
421+ }
422+ }
365423}
366424
367425extension SymbolGraph . SemanticVersion {
0 commit comments