From 338f54c30811bc8a9f01beb88a8b79a9495e4bd6 Mon Sep 17 00:00:00 2001 From: Vera Mitchell Date: Mon, 15 Sep 2025 10:08:12 -0600 Subject: [PATCH 1/2] accept sameShape constraint kind from SymbolKit rdar://129338238 --- Package.resolved | 8 ++-- Package.swift | 3 +- .../Rendering/Symbol/ConformanceSection.swift | 1 + .../ConstraintsRenderSectionTests.swift | 37 +++++++++++++++++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/Package.resolved b/Package.resolved index ffb8d1e20d..111369de60 100644 --- a/Package.resolved +++ b/Package.resolved @@ -3,7 +3,7 @@ { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser.git", + "location" : "https://github.com/apple/swift-argument-parser", "state" : { "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", "version" : "1.4.0" @@ -66,10 +66,10 @@ { "identity" : "swift-docc-symbolkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-docc-symbolkit.git", + "location" : "https://github.com/QuietMisdreavus/swift-docc-symbolkit.git", "state" : { - "branch" : "main", - "revision" : "96bce1cfad4f4d7e265c1eb46729ebf8a7695f4b" + "branch" : "same-shape", + "revision" : "ca7571fd81e6427a8b16a50b162212aee972669a" } }, { diff --git a/Package.swift b/Package.swift index 2381b5dfeb..9681b78384 100644 --- a/Package.swift +++ b/Package.swift @@ -137,7 +137,8 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(url: "https://github.com/swiftlang/swift-markdown.git", branch: "main"), .package(url: "https://github.com/swiftlang/swift-lmdb.git", branch: "main"), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"), - .package(url: "https://github.com/swiftlang/swift-docc-symbolkit.git", branch: "main"), +// .package(url: "https://github.com/swiftlang/swift-docc-symbolkit.git", branch: "main"), + .package(url: "https://github.com/QuietMisdreavus/swift-docc-symbolkit.git", branch: "same-shape"), .package(url: "https://github.com/apple/swift-crypto.git", from: "3.0.0"), .package(url: "https://github.com/swiftlang/swift-docc-plugin.git", from: "1.2.0"), ] diff --git a/Sources/SwiftDocC/Model/Rendering/Symbol/ConformanceSection.swift b/Sources/SwiftDocC/Model/Rendering/Symbol/ConformanceSection.swift index 7048c13d3a..73ca49b210 100644 --- a/Sources/SwiftDocC/Model/Rendering/Symbol/ConformanceSection.swift +++ b/Sources/SwiftDocC/Model/Rendering/Symbol/ConformanceSection.swift @@ -20,6 +20,7 @@ extension Constraint.Kind { case .conformance: return "conforms to" case .sameType: return "is" case .superclass: return "inherits" + case .sameShape: return "is the same shape as" } } } diff --git a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift index cf83b6b49f..5be0957423 100644 --- a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift @@ -273,6 +273,43 @@ class ConstraintsRenderSectionTests: XCTestCase { // Verify we've removed the "Self." prefix in the type names XCTAssertEqual(renderReference.conformance?.constraints.map(flattenInlineElements).joined(), "Element conforms to MyProtocol and Index conforms to Equatable.") } + + func testRenderSameShape() async throws { + let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in + // Add constraints to `MyClass` + let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") + var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) + + // "Inject" generic constraints + graph.symbols = try graph.symbols.mapValues({ symbol -> SymbolGraph.Symbol in + guard symbol.identifier.precise == "s:5MyKit0A5ClassC10myFunctionyyF" else { return symbol } + var symbol = symbol + symbol.mixins[SymbolGraph.Symbol.Swift.Extension.mixinKey] = try jsonDecoder.decode(SymbolGraph.Symbol.Swift.Extension.self, from: """ + {"extendedModule": "MyKit", + "constraints": [ + { "kind" : "sameShape", "lhs" : "Element", "rhs" : "MyProtocol" } + ]} + """.data(using: .utf8)!) + return symbol + }) + try jsonEncoder.encode(graph).write(to: graphURL) + } + + // Compile docs and verify contents + let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) + let symbol = node.semantic as! Symbol + var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) + let renderNode = translator.visitSymbol(symbol) as! RenderNode + + guard let renderReference = renderNode.references.first(where: { (key, value) -> Bool in + return key.hasSuffix("myFunction()") + })?.value as? TopicRenderReference else { + XCTFail("Did not find render reference to myFunction()") + return + } + + XCTAssertEqual(renderReference.conformance?.constraints.map(flattenInlineElements).joined(), "Element is the same shape as MyProtocol.") + } } fileprivate func flattenInlineElements(el: RenderInlineContent) -> String { From 6d81acb8bb00e47d00afacda6d162448533a7b4a Mon Sep 17 00:00:00 2001 From: Vera Mitchell Date: Mon, 15 Sep 2025 15:27:13 -0600 Subject: [PATCH 2/2] review: create a test fixture for sameShape constraints --- .../ConstraintsRenderSectionTests.swift | 41 ++- .../SameShapeConstraint.symbols.json | 321 ++++++++++++++++++ 2 files changed, 340 insertions(+), 22 deletions(-) create mode 100644 Tests/SwiftDocCTests/Test Resources/SameShapeConstraint.symbols.json diff --git a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift index 5be0957423..569e0b21ef 100644 --- a/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift +++ b/Tests/SwiftDocCTests/Rendering/ConstraintsRenderSectionTests.swift @@ -11,6 +11,7 @@ import Foundation import XCTest @testable import SwiftDocC +import SwiftDocCTestUtilities import SymbolKit fileprivate let jsonDecoder = JSONDecoder() @@ -275,40 +276,36 @@ class ConstraintsRenderSectionTests: XCTestCase { } func testRenderSameShape() async throws { - let (_, bundle, context) = try await testBundleAndContext(copying: "LegacyBundle_DoNotUseInNewTests", excludingPaths: []) { bundleURL in - // Add constraints to `MyClass` - let graphURL = bundleURL.appendingPathComponent("mykit-iOS.symbols.json") - var graph = try jsonDecoder.decode(SymbolGraph.self, from: try Data(contentsOf: graphURL)) + let symbolGraphFile = Bundle.module.url( + forResource: "SameShapeConstraint", + withExtension: "symbols.json", + subdirectory: "Test Resources" + )! - // "Inject" generic constraints - graph.symbols = try graph.symbols.mapValues({ symbol -> SymbolGraph.Symbol in - guard symbol.identifier.precise == "s:5MyKit0A5ClassC10myFunctionyyF" else { return symbol } - var symbol = symbol - symbol.mixins[SymbolGraph.Symbol.Swift.Extension.mixinKey] = try jsonDecoder.decode(SymbolGraph.Symbol.Swift.Extension.self, from: """ - {"extendedModule": "MyKit", - "constraints": [ - { "kind" : "sameShape", "lhs" : "Element", "rhs" : "MyProtocol" } - ]} - """.data(using: .utf8)!) - return symbol - }) - try jsonEncoder.encode(graph).write(to: graphURL) - } + let catalog = Folder(name: "unit-test.docc", content: [ + InfoPlist(displayName: "SameShapeConstraint", identifier: "com.test.example"), + CopyOfFile(original: symbolGraphFile), + ]) + + let (bundle, context) = try await loadBundle(catalog: catalog) // Compile docs and verify contents - let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/MyKit/MyClass", sourceLanguage: .swift)) + let node = try context.entity(with: ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/SameShapeConstraint/function(_:)", sourceLanguage: .swift)) let symbol = node.semantic as! Symbol var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference) let renderNode = translator.visitSymbol(symbol) as! RenderNode guard let renderReference = renderNode.references.first(where: { (key, value) -> Bool in - return key.hasSuffix("myFunction()") + return key.hasSuffix("function(_:)") })?.value as? TopicRenderReference else { - XCTFail("Did not find render reference to myFunction()") + XCTFail("Did not find render reference to function(_:)") return } - XCTAssertEqual(renderReference.conformance?.constraints.map(flattenInlineElements).joined(), "Element is the same shape as MyProtocol.") + // The symbol graph only defines constraints on the `swiftGenerics` mixin, + // which docc doesn't load or render. + // However, this test should still run without crashing on decoding the symbol graph. + XCTAssertEqual(renderReference.conformance?.constraints.map(flattenInlineElements).joined(), nil) } } diff --git a/Tests/SwiftDocCTests/Test Resources/SameShapeConstraint.symbols.json b/Tests/SwiftDocCTests/Test Resources/SameShapeConstraint.symbols.json new file mode 100644 index 0000000000..183b29cd2e --- /dev/null +++ b/Tests/SwiftDocCTests/Test Resources/SameShapeConstraint.symbols.json @@ -0,0 +1,321 @@ +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 6, + "patch": 0 + }, + "generator": "Apple Swift version 6.2 (swiftlang-6.2.0.19.9 clang-1700.3.19.1)" + }, + "module": { + "name": "SameShapeConstraint", + "platform": { + "architecture": "arm64", + "vendor": "apple", + "operatingSystem": { + "name": "macosx", + "minimumVersion": { + "major": 12, + "minor": 4 + } + } + } + }, + "symbols": [ + { + "kind": { + "identifier": "swift.func", + "displayName": "Function" + }, + "identifier": { + "precise": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "function(_:)" + ], + "names": { + "title": "function(_:)", + "subHeading": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "function" + }, + { + "kind": "text", + "spelling": "((" + }, + { + "kind": "keyword", + "spelling": "repeat" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element0", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element0L_xmfp" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element1", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element1L_q_mfp" + }, + { + "kind": "text", + "spelling": ")))" + } + ] + }, + "functionSignature": { + "parameters": [ + { + "name": "_", + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "_" + }, + { + "kind": "text", + "spelling": ": (" + }, + { + "kind": "keyword", + "spelling": "repeat" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element0", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element0L_xmfp" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element1", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element1L_q_mfp" + }, + { + "kind": "text", + "spelling": "))" + } + ] + } + ], + "returns": [ + { + "kind": "text", + "spelling": "()" + } + ] + }, + "swiftGenerics": { + "parameters": [ + { + "name": "Element0", + "index": 0, + "depth": 0 + }, + { + "name": "Element1", + "index": 1, + "depth": 0 + } + ], + "constraints": [ + { + "kind": "sameShape", + "lhs": "each Element0", + "rhs": "each Element1" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "function" + }, + { + "kind": "text", + "spelling": "(" + }, + { + "kind": "externalParam", + "spelling": "_" + }, + { + "kind": "text", + "spelling": ": (" + }, + { + "kind": "keyword", + "spelling": "repeat" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element0", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element0L_xmfp" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "keyword", + "spelling": "each" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Element1", + "preciseIdentifier": "s:13SameShapeConstraint8functionyyx_q_txQp_t_tRvzRv_q_Rhzr0_lF8Element1L_q_mfp" + }, + { + "kind": "text", + "spelling": "))) " + }, + { + "kind": "keyword", + "spelling": "where" + }, + { + "kind": "text", + "spelling": " (repeat (each " + }, + { + "kind": "typeIdentifier", + "spelling": "Element0" + }, + { + "kind": "text", + "spelling": ", each " + }, + { + "kind": "typeIdentifier", + "spelling": "Element1" + }, + { + "kind": "text", + "spelling": ")) : Any" + } + ], + "accessLevel": "public", + "location": { + "uri": "file:///path/to/SameShapeConstraint/SwiftClass.swift", + "position": { + "line": 9, + "character": 12 + } + } + } + ], + "relationships": [] +}