Skip to content

Commit 633dfa2

Browse files
authored
Support more @DirectiveArgumentWrapped configurations (#459)
* Support more DirectiveArgumentWrapped configurations Also, generate more consistent `typeDisplayName` values * Remove commented out deprecation * Add tests for directive arguments with explicit nil default values * Fix type display name for non-directive-convertible optional values * Update generated symbol graph to reflect recent documentation changes * Fix incomplete sentence in unit test comment * Update copyright year
1 parent f0b4307 commit 633dfa2

File tree

2 files changed

+545
-54
lines changed

2 files changed

+545
-54
lines changed

Sources/SwiftDocC/Semantics/DirectiveInfrastructure/DirectiveArgumentWrapper.swift

Lines changed: 207 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2022 Apple Inc. and the Swift project authors
4+
Copyright (c) 2022-2023 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -91,6 +91,43 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
9191
fatalError()
9292
}
9393

94+
// Expected argument configurations
95+
96+
@_disfavoredOverload
97+
init(
98+
wrappedValue: Value,
99+
name: _DirectiveArgumentName = .inferredFromPropertyName,
100+
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
101+
allowedValues: [String]? = nil,
102+
hiddenFromDocumentation: Bool = false
103+
) {
104+
self.init(
105+
value: wrappedValue,
106+
name: name,
107+
transform: parseArgument,
108+
allowedValues: allowedValues,
109+
required: nil,
110+
hiddenFromDocumentation: hiddenFromDocumentation
111+
)
112+
}
113+
114+
@_disfavoredOverload
115+
init(
116+
name: _DirectiveArgumentName = .inferredFromPropertyName,
117+
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
118+
allowedValues: [String]? = nil,
119+
hiddenFromDocumentation: Bool = false
120+
) {
121+
self.init(
122+
value: nil,
123+
name: name,
124+
transform: parseArgument,
125+
allowedValues: allowedValues,
126+
required: nil,
127+
hiddenFromDocumentation: hiddenFromDocumentation
128+
)
129+
}
130+
94131
private init(
95132
value: Value?,
96133
name: _DirectiveArgumentName,
@@ -99,32 +136,37 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
99136
required: Bool?,
100137
hiddenFromDocumentation: Bool
101138
) {
139+
let required = required ?? (value == nil)
140+
102141
self.name = name
103142
self.defaultValue = value
104-
if let optionallyWrappedValue = Value.self as? OptionallyWrapped.Type {
105-
self.typeDisplayName = String(describing: optionallyWrappedValue.baseType()) + "?"
106-
} else {
107-
self.typeDisplayName = String(describing: Value.self)
108-
}
109-
110-
if let required = required {
111-
self.required = required
112-
} else {
113-
self.required = defaultValue == nil
114-
}
115-
143+
self.typeDisplayName = typeDisplayNameDescription(defaultValue: value, required: required)
116144
self.parseArgument = transform
117145
self.allowedValues = allowedValues
146+
self.required = required
118147
self.hiddenFromDocumentation = hiddenFromDocumentation
119148
}
120149

150+
func setProperty<T>(
151+
on containingDirective: T,
152+
named propertyName: String,
153+
to any: Any
154+
) where T: AutomaticDirectiveConvertible {
155+
let path = T.keyPaths[propertyName] as! ReferenceWritableKeyPath<T, DirectiveArgumentWrapped<Value>>
156+
let wrappedValuePath = path.appending(path: \Self.parsedValue)
157+
containingDirective[keyPath: wrappedValuePath] = any as! Value?
158+
}
159+
160+
// Warnings and errors for unexpected argument configurations
161+
121162
@_disfavoredOverload
163+
@available(*, deprecated, message: "Use an optional type or a default value to control whether or not a directive argument is required.")
122164
init(
123165
wrappedValue: Value,
124166
name: _DirectiveArgumentName = .inferredFromPropertyName,
125167
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
126168
allowedValues: [String]? = nil,
127-
required: Bool? = nil,
169+
required: Bool,
128170
hiddenFromDocumentation: Bool = false
129171
) {
130172
self.init(
@@ -138,11 +180,12 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
138180
}
139181

140182
@_disfavoredOverload
183+
@available(*, deprecated, message: "Use an optional type or a default value to control whether or not a directive argument is required.")
141184
init(
142185
name: _DirectiveArgumentName = .inferredFromPropertyName,
143186
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
144187
allowedValues: [String]? = nil,
145-
required: Bool? = nil,
188+
required: Bool,
146189
hiddenFromDocumentation: Bool = false
147190
) {
148191
self.init(
@@ -154,26 +197,20 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
154197
hiddenFromDocumentation: hiddenFromDocumentation
155198
)
156199
}
157-
158-
func setProperty<T>(
159-
on containingDirective: T,
160-
named propertyName: String,
161-
to any: Any
162-
) where T: AutomaticDirectiveConvertible {
163-
let path = T.keyPaths[propertyName] as! ReferenceWritableKeyPath<T, DirectiveArgumentWrapped<Value>>
164-
let wrappedValuePath = path.appending(path: \Self.parsedValue)
165-
containingDirective[keyPath: wrappedValuePath] = any as! Value?
166-
}
167200
}
168201

169202
extension DirectiveArgumentWrapped where Value: DirectiveArgumentValueConvertible {
203+
// Expected argument configurations
204+
205+
@_disfavoredOverload
170206
init(
171207
name: _DirectiveArgumentName = .inferredFromPropertyName,
172208
hiddenFromDocumentation: Bool = false
173209
) {
174210
self.init(value: nil, name: name, hiddenFromDocumentation: hiddenFromDocumentation)
175211
}
176212

213+
@_disfavoredOverload
177214
init(
178215
wrappedValue: Value,
179216
name: _DirectiveArgumentName = .inferredFromPropertyName,
@@ -182,53 +219,169 @@ extension DirectiveArgumentWrapped where Value: DirectiveArgumentValueConvertibl
182219
self.init(value: wrappedValue, name: name, hiddenFromDocumentation: hiddenFromDocumentation)
183220
}
184221

185-
private init(
186-
value: Value?,
187-
name: _DirectiveArgumentName,
188-
hiddenFromDocumentation: Bool
189-
) {
222+
private init(value: Value?, name: _DirectiveArgumentName, hiddenFromDocumentation: Bool) {
190223
self.name = name
191224
self.defaultValue = value
192225

193-
if let value = value {
194-
self.typeDisplayName = String(describing: Value.self) + " = " + String(describing: value)
195-
} else {
196-
self.typeDisplayName = String(describing: Value.self)
197-
}
198-
226+
let required = value == nil
227+
self.typeDisplayName = typeDisplayNameDescription(defaultValue: value, required: required)
199228
self.parseArgument = { _, argument in
200229
Value.init(rawDirectiveArgumentValue: argument)
201230
}
202231
self.allowedValues = Value.allowedValues()
203-
self.required = value == nil
232+
self.required = required
204233
self.hiddenFromDocumentation = hiddenFromDocumentation
205234
}
235+
236+
// Warnings and errors for unexpected argument configurations
237+
238+
@_disfavoredOverload
239+
@available(*, unavailable, message: "Directive argument of non-optional types without default value need to be required. Use an optional type or provide a default value to make this argument non-required.")
240+
init(name: _DirectiveArgumentName = .inferredFromPropertyName, required: Bool) {
241+
fatalError()
242+
}
206243
}
207244

208-
protocol OptionallyWrappedDirectiveArgumentValueConvertible: OptionallyWrapped {}
209-
extension Optional: OptionallyWrappedDirectiveArgumentValueConvertible where Wrapped: DirectiveArgumentValueConvertible {}
210-
extension DirectiveArgumentWrapped where Value: OptionallyWrappedDirectiveArgumentValueConvertible {
245+
protocol _OptionalDirectiveArgument {
246+
associatedtype WrappedArgument
247+
var wrapped: WrappedArgument? { get }
248+
init(wrapping: WrappedArgument?)
249+
}
250+
extension Optional: _OptionalDirectiveArgument {
251+
typealias WrappedArgument = Wrapped
252+
var wrapped: WrappedArgument? {
253+
switch self {
254+
case .some(let value):
255+
return value
256+
case .none: return
257+
nil
258+
}
259+
}
260+
init(wrapping: WrappedArgument?) {
261+
if let wrapped = wrapping {
262+
self = .some(wrapped)
263+
} else {
264+
self = .none
265+
}
266+
}
267+
}
268+
269+
extension DirectiveArgumentWrapped where Value: _OptionalDirectiveArgument, Value.WrappedArgument: DirectiveArgumentValueConvertible {
270+
271+
// When the wrapped value is DirectiveArgumentValueConvertible, additional arguments may be omitted
272+
273+
init(
274+
name: _DirectiveArgumentName = .inferredFromPropertyName,
275+
hiddenFromDocumentation: Bool = false
276+
) {
277+
self = .init(value: nil, name: name, hiddenFromDocumentation: hiddenFromDocumentation)
278+
}
279+
280+
@_disfavoredOverload
211281
init(
212282
wrappedValue: Value,
213283
name: _DirectiveArgumentName = .inferredFromPropertyName,
214-
required: Bool = false,
215284
hiddenFromDocumentation: Bool = false
216285
) {
217-
let argumentValueType = Value.baseType() as! DirectiveArgumentValueConvertible.Type
286+
self = .init(value: wrappedValue, name: name, hiddenFromDocumentation: hiddenFromDocumentation)
287+
}
288+
289+
private init(
290+
value: Value?,
291+
name: _DirectiveArgumentName,
292+
hiddenFromDocumentation: Bool
293+
) {
294+
let argumentValueType = Value.WrappedArgument.self
218295

296+
self = .init(
297+
value: value,
298+
name: name,
299+
parseArgument: { _, argument in
300+
Value(wrapping: argumentValueType.init(rawDirectiveArgumentValue: argument))
301+
},
302+
allowedValues: argumentValueType.allowedValues(),
303+
hiddenFromDocumentation: hiddenFromDocumentation
304+
)
305+
}
306+
}
307+
308+
extension DirectiveArgumentWrapped where Value: _OptionalDirectiveArgument {
309+
310+
// Expected argument configurations
311+
312+
@_disfavoredOverload
313+
init(
314+
name: _DirectiveArgumentName = .inferredFromPropertyName,
315+
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
316+
allowedValues: [String]? = nil,
317+
hiddenFromDocumentation: Bool = false
318+
) {
319+
self = .init(value: nil, name: name, parseArgument: parseArgument, allowedValues: allowedValues, hiddenFromDocumentation: hiddenFromDocumentation)
320+
}
321+
322+
@_disfavoredOverload
323+
init(
324+
wrappedValue: Value,
325+
name: _DirectiveArgumentName = .inferredFromPropertyName,
326+
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
327+
allowedValues: [String]? = nil,
328+
hiddenFromDocumentation: Bool = false
329+
) {
330+
self = .init(value: wrappedValue, name: name, parseArgument: parseArgument, allowedValues: allowedValues, hiddenFromDocumentation: hiddenFromDocumentation)
331+
}
332+
333+
private init(
334+
value: Value?,
335+
name: _DirectiveArgumentName,
336+
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
337+
allowedValues: [String]? = nil,
338+
hiddenFromDocumentation: Bool = false
339+
) {
219340
self.name = name
220-
self.defaultValue = wrappedValue
221-
if required {
222-
self.typeDisplayName = String(describing: argumentValueType)
223-
} else {
224-
self.typeDisplayName = String(describing: argumentValueType) + "?"
225-
}
226-
227-
self.parseArgument = { _, argument in
228-
argumentValueType.init(rawDirectiveArgumentValue: argument)
229-
}
230-
self.allowedValues = argumentValueType.allowedValues()
231-
self.required = required
341+
self.defaultValue = value
342+
self.typeDisplayName = typeDisplayNameDescription(optionalDefaultValue: value, required: false)
343+
self.parseArgument = parseArgument
344+
self.allowedValues = allowedValues
345+
self.required = false
232346
self.hiddenFromDocumentation = hiddenFromDocumentation
233347
}
348+
349+
// Warnings and errors for unexpected argument configurations
350+
351+
@_disfavoredOverload
352+
@available(*, unavailable, message: "Directive arguments with an Optional type shouldn't be required.")
353+
init(
354+
name: _DirectiveArgumentName = .inferredFromPropertyName,
355+
required: Bool,
356+
hiddenFromDocumentation: Bool = false
357+
) {
358+
fatalError()
359+
}
360+
361+
@_disfavoredOverload
362+
@available(*, unavailable, message: "Directive arguments with an Optional type shouldn't be required.")
363+
init(
364+
wrappedValue: Value,
365+
name: _DirectiveArgumentName = .inferredFromPropertyName,
366+
required: Bool,
367+
hiddenFromDocumentation: Bool = false
368+
) {
369+
fatalError()
370+
}
371+
}
372+
373+
private func typeDisplayNameDescription<Value>(defaultValue: Value?, required: Bool) -> String {
374+
var name = "\(Value.self)"
375+
376+
if let defaultValue = defaultValue {
377+
name += " = \(defaultValue)"
378+
} else if !required {
379+
name += "?"
380+
}
381+
382+
return name
383+
}
384+
385+
private func typeDisplayNameDescription<Value: _OptionalDirectiveArgument>(optionalDefaultValue: Value?, required: Bool) -> String {
386+
return typeDisplayNameDescription(defaultValue: optionalDefaultValue?.wrapped, required: required)
234387
}

0 commit comments

Comments
 (0)