Skip to content

Commit 53f3556

Browse files
d-ronnqvistQuietMisdreavus
authored andcommitted
Support documenting parameters and return values that only appear in some alternate signatures
rdar://137964801
1 parent f7b4a06 commit 53f3556

File tree

2 files changed

+79
-28
lines changed

2 files changed

+79
-28
lines changed

Sources/SwiftDocC/Model/ParametersAndReturnValidator.swift

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -293,38 +293,26 @@ struct ParametersAndReturnValidator {
293293
private static func traitSpecificSignatures(_ symbol: UnifiedSymbolGraph.Symbol) -> Signatures? {
294294
var signatures: [DocumentationDataVariantsTrait: SymbolGraph.Symbol.FunctionSignature] = [:]
295295
for (selector, mixin) in symbol.mixins {
296-
guard let signature = mixin.getValueIfPresent(for: SymbolGraph.Symbol.FunctionSignature.self) else {
296+
guard var signature = mixin.getValueIfPresent(for: SymbolGraph.Symbol.FunctionSignature.self) else {
297297
continue
298298
}
299299

300+
if let alternateSymbols = mixin.getValueIfPresent(for: SymbolGraph.Symbol.AlternateSymbols.self) {
301+
for alternateSymbol in alternateSymbols.alternateSymbols {
302+
guard let alternateSignature = alternateSymbol.functionSignature else { continue }
303+
signature.merge(with: alternateSignature, selector: selector)
304+
}
305+
}
306+
300307
let trait = DocumentationDataVariantsTrait(for: selector)
301308
// Check if we've already encountered a different signature for another platform
302-
guard var existing = signatures.removeValue(forKey: trait) else {
309+
guard let existing = signatures.removeValue(forKey: trait) else {
303310
signatures[trait] = signature
304311
continue
305312
}
306313

307-
// An internal helper function that compares parameter names
308-
func hasSameNames(_ lhs: SymbolGraph.Symbol.FunctionSignature.FunctionParameter, _ rhs: SymbolGraph.Symbol.FunctionSignature.FunctionParameter) -> Bool {
309-
lhs.name == rhs.name && lhs.externalName == rhs.externalName
310-
}
311-
// If the two signatures have different parameters, add any missing parameters.
312-
// This allows for documenting parameters that are only available on some platforms.
313-
//
314-
// Note: Doing this redundant `elementsEqual(_:by:)` check is significantly faster in the common case when all platforms have the same signature.
315-
// In the rare case where platforms have different signatures, the overhead of checking `elementsEqual(_:by:)` first is too small to measure.
316-
if !existing.parameters.elementsEqual(signature.parameters, by: hasSameNames) {
317-
for case .insert(offset: let offset, element: let element, _) in signature.parameters.difference(from: existing.parameters, by: hasSameNames) {
318-
existing.parameters.insert(element, at: offset)
319-
}
320-
}
321-
322-
// If the already encountered signature has a void return type, replace it with the non-void return type.
323-
// This allows for documenting the return values that are only available on some platforms.
324-
if existing.returns != signature.returns, existing.returns == knownVoidReturnValuesByLanguage[.init(id: selector.interfaceLanguage)] {
325-
existing.returns = signature.returns
326-
}
327-
signatures[trait] = existing
314+
signature.merge(with: existing, selector: selector)
315+
signatures[trait] = signature
328316
}
329317

330318
guard !signatures.isEmpty else { return nil }
@@ -673,3 +661,36 @@ struct ParametersAndReturnValidator {
673661
"- \(standalone ? "Parameter " : "")\(name): <#parameter description#>"
674662
}
675663
}
664+
665+
// MARK: Helper extensions
666+
667+
private extension SymbolGraph.Symbol.FunctionSignature {
668+
mutating func merge(with signature: Self, selector: UnifiedSymbolGraph.Selector) {
669+
// An internal helper function that compares parameter names
670+
func hasSameNames(_ lhs: Self.FunctionParameter, _ rhs: Self.FunctionParameter) -> Bool {
671+
lhs.name == rhs.name && lhs.externalName == rhs.externalName
672+
}
673+
// If the two signatures have different parameters, add any missing parameters.
674+
// This allows for documenting parameters that are only available on some platforms.
675+
//
676+
// Note: Doing this redundant `elementsEqual(_:by:)` check is significantly faster in the common case when all platforms have the same signature.
677+
// In the rare case where platforms have different signatures, the overhead of checking `elementsEqual(_:by:)` first is too small to measure.
678+
if !self.parameters.elementsEqual(signature.parameters, by: hasSameNames) {
679+
for case .insert(offset: let offset, element: let element, _) in signature.parameters.difference(from: self.parameters, by: hasSameNames) {
680+
self.parameters.insert(element, at: offset)
681+
}
682+
}
683+
684+
// If the already encountered signature has a void return type, replace it with the non-void return type.
685+
// This allows for documenting the return values that are only available on some platforms.
686+
if self.returns != signature.returns,
687+
let knownVoidReturnValues = ParametersAndReturnValidator.knownVoidReturnValuesByLanguage[.init(id: selector.interfaceLanguage)]
688+
{
689+
for knownVoidReturnValue in knownVoidReturnValues where [knownVoidReturnValue] == self.returns {
690+
// The current return value was a known void return value so we replace it with the new return value.
691+
self.returns = signature.returns
692+
return
693+
}
694+
}
695+
}
696+
}

Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,36 @@ class ParametersAndReturnValidatorTests: XCTestCase {
111111
}
112112
}
113113

114+
func testParametersWithAlternateSignatures() throws {
115+
let (_, _, context) = try testBundleAndContext(copying: "AlternateDeclarations") { url in
116+
try """
117+
# ``MyClass/present(completion:)``
118+
119+
@Metadata {
120+
@DocumentationExtension(mergeBehavior: override)
121+
}
122+
123+
Override the documentation with a parameter section that raise warnings.
124+
125+
- Parameters:
126+
- completion: Description of the parameter that's available in some alternatives.
127+
- Returns: Description of the return value that's available for some other alternatives.
128+
""".write(to: url.appendingPathComponent("extension.md"), atomically: true, encoding: .utf8)
129+
}
130+
131+
let reference = try XCTUnwrap(context.soleRootModuleReference).appendingPath("MyClass/present(completion:)")
132+
let node = try context.entity(with: reference)
133+
let symbolSemantic = try XCTUnwrap(node.semantic as? Symbol)
134+
135+
let swiftParameterNames = symbolSemantic.parametersSectionVariants.firstValue?.parameters
136+
137+
XCTAssertEqual(swiftParameterNames?.map(\.name), ["completion"])
138+
XCTAssertEqual(swiftParameterNames?.map { _format($0.contents) }, ["Description of the parameter that’s available in some alternatives."])
139+
140+
let swiftReturnsContent = symbolSemantic.returnsSection.map { _format($0.content) }
141+
XCTAssertEqual(swiftReturnsContent, "Description of the return value that’s available for some other alternatives.")
142+
}
143+
114144
func testParameterDiagnosticsInDocumentationExtension() throws {
115145
let (url, _, context) = try testBundleAndContext(copying: "ErrorParameters") { url in
116146
try """
@@ -200,11 +230,6 @@ class ParametersAndReturnValidatorTests: XCTestCase {
200230
let (_, _, context) = try testBundleAndContext(named: "GeometricalShapes")
201231
XCTAssertEqual(context.problems.map(\.diagnostic.summary), [])
202232

203-
// A small test helper to format markup for test assertions in this test.
204-
func _format(_ markup: [any Markup]) -> String {
205-
markup.map { $0.format() }.joined()
206-
}
207-
208233
let reference = try XCTUnwrap(context.knownPages.first(where: { $0.lastPathComponent == "isEmpty" }))
209234
let node = try context.entity(with: reference)
210235

@@ -734,3 +759,8 @@ class ParametersAndReturnValidatorTests: XCTestCase {
734759
)
735760
}
736761
}
762+
763+
// A small test helper to format markup for test assertions in this file.
764+
private func _format(_ markup: [any Markup]) -> String {
765+
markup.map { $0.format() }.joined()
766+
}

0 commit comments

Comments
 (0)