Skip to content

Commit 78e6cf9

Browse files
authored
Allow property wrappers to be used without parentheses (#207)
Fixes #206[1], where unless the property wrapper types were used with empty parentheses, `ParsableArguments`-conforming types would not have a no-argument initializer synthesized and thus would unexpectedbly fail to conform to `ParsableArguments`.[1] This is caused by a limitation of the compiler's property wrapper support where it fails to see initializers in extensions as candidates for property wrapper requirements.[2] By declaring a no-argument intializer in each type, but then marking it as unavailable, the no-parenthesis syntax now works, but selects the existing initializers in extensions anyway. [1]: #206 [2]: https://bugs.swift.org/browse/SR-13295
1 parent e77b025 commit 78e6cf9

File tree

5 files changed

+78
-37
lines changed

5 files changed

+78
-37
lines changed

Sources/ArgumentParser/Parsable Properties/Argument.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@ public struct Argument<Value>:
3535
public init(from decoder: Decoder) throws {
3636
try self.init(_decoder: decoder)
3737
}
38+
39+
/// This initializer works around a quirk of property wrappers, where the
40+
/// compiler will not see no-argument initializers in extensions. Explicitly
41+
/// marking this initializer unavailable means that when `Value` conforms to
42+
/// `ExpressibleByArgument`, that overload will be selected instead.
43+
///
44+
/// ```swift
45+
/// @Argument() var foo: String // Syntax without this initializer
46+
/// @Argument var foo: String // Syntax with this initializer
47+
/// ```
48+
@available(*, unavailable, message: "A default value must be provided unless the value type conforms to ExpressibleByArgument.")
49+
public init() {
50+
fatalError("unavailable")
51+
}
3852

3953
/// The value presented by this property wrapper.
4054
public var wrappedValue: Value {
@@ -113,8 +127,7 @@ extension Argument where Value: ExpressibleByArgument {
113127
///
114128
/// This method is called to initialize an `Argument` with a default value such as:
115129
/// ```swift
116-
/// @Argument()
117-
/// var foo: String = "bar"
130+
/// @Argument var foo: String = "bar"
118131
/// ```
119132
///
120133
/// - Parameters:
@@ -134,8 +147,7 @@ extension Argument where Value: ExpressibleByArgument {
134147
///
135148
/// This method is called to initialize an `Argument` without a default value such as:
136149
/// ```swift
137-
/// @Argument()
138-
/// var foo: String
150+
/// @Argument var foo: String
139151
/// ```
140152
///
141153
/// - Parameters:

Sources/ArgumentParser/Parsable Properties/Flag.swift

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,22 @@ public struct Flag<Value>: Decodable, ParsedWrapper {
4747
public init(from decoder: Decoder) throws {
4848
try self.init(_decoder: decoder)
4949
}
50-
50+
51+
/// This initializer works around a quirk of property wrappers, where the
52+
/// compiler will not see no-argument initializers in extensions. Explicitly
53+
/// marking this initializer unavailable means that when `Value` is a type
54+
/// supported by `Flag` like `Bool` or `EnumerableFlag`, the appropriate
55+
/// overload will be selected instead.
56+
///
57+
/// ```swift
58+
/// @Flag() var flag: Bool // Syntax without this initializer
59+
/// @Flag var flag: Bool // Syntax with this initializer
60+
/// ```
61+
@available(*, unavailable, message: "A default value must be provided unless the value type is supported by Flag.")
62+
public init() {
63+
fatalError("unavailable")
64+
}
65+
5166
/// The value presented by this property wrapper.
5267
public var wrappedValue: Value {
5368
get {
@@ -222,8 +237,7 @@ extension Flag where Value == Bool {
222237
/// ```diff
223238
/// -@Flag(default: true)
224239
/// -var foo: Bool
225-
/// +@Flag()
226-
/// +var foo: Bool = true
240+
/// +@Flag var foo: Bool = true
227241
/// ```
228242
///
229243
/// Use this initializer to create a Boolean flag with an on/off pair. With
@@ -245,7 +259,7 @@ extension Flag where Value == Bool {
245259
/// case useDevelopmentServer
246260
/// }
247261
///
248-
/// @Flag() var serverChoice: ServerChoice
262+
/// @Flag var serverChoice: ServerChoice
249263
///
250264
/// - Parameters:
251265
/// - name: A specification for what names are allowed for this flag.
@@ -401,10 +415,9 @@ extension Flag where Value: EnumerableFlag {
401415
///
402416
/// Existing usage of the `default` parameter should be replaced such as follows:
403417
/// ```diff
404-
/// -@Argument(default: .baz)
418+
/// -@Flag(default: .baz)
405419
/// -var foo: Bar
406-
/// +@Argument()
407-
/// +var foo: Bar = baz
420+
/// +@Flag var foo: Bar = baz
408421
/// ```
409422
///
410423
/// - Parameters:
@@ -438,7 +451,7 @@ extension Flag where Value: EnumerableFlag {
438451
/// case useDevelopmentServer
439452
/// }
440453
///
441-
/// @Flag() var serverChoice: ServerChoice = .useProductionServer
454+
/// @Flag var serverChoice: ServerChoice = .useProductionServer
442455
/// ```
443456
///
444457
/// - Parameters:
@@ -469,7 +482,7 @@ extension Flag where Value: EnumerableFlag {
469482
/// case useDevelopmentServer
470483
/// }
471484
///
472-
/// @Flag() var serverChoice: ServerChoice
485+
/// @Flag var serverChoice: ServerChoice
473486
/// ```
474487
///
475488
/// - Parameters:

Sources/ArgumentParser/Parsable Properties/Option.swift

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,21 @@ public struct Option<Value>: Decodable, ParsedWrapper {
3737
public init(from decoder: Decoder) throws {
3838
try self.init(_decoder: decoder)
3939
}
40-
40+
41+
/// This initializer works around a quirk of property wrappers, where the
42+
/// compiler will not see no-argument initializers in extensions. Explicitly
43+
/// marking this initializer unavailable means that when `Value` conforms to
44+
/// `ExpressibleByArgument`, that overload will be selected instead.
45+
///
46+
/// ```swift
47+
/// @Option() var foo: String // Syntax without this initializer
48+
/// @Option var foo: String // Syntax with this initializer
49+
/// ```
50+
@available(*, unavailable, message: "A default value must be provided unless the value type conforms to ExpressibleByArgument.")
51+
public init() {
52+
fatalError("unavailable")
53+
}
54+
4155
/// The value presented by this property wrapper.
4256
public var wrappedValue: Value {
4357
get {
@@ -100,8 +114,7 @@ extension Option where Value: ExpressibleByArgument {
100114
/// ```diff
101115
/// -@Option(default: "bar")
102116
/// -var foo: String
103-
/// +@Option()
104-
/// +var foo: String = "bar"
117+
/// +@Option var foo: String = "bar"
105118
/// ```
106119
///
107120
/// - Parameters:
@@ -130,8 +143,7 @@ extension Option where Value: ExpressibleByArgument {
130143
///
131144
/// This method is called to initialize an `Option` with a default value such as:
132145
/// ```swift
133-
/// @Option()
134-
/// var foo: String = "bar"
146+
/// @Option var foo: String = "bar"
135147
/// ```
136148
///
137149
/// - Parameters:
@@ -157,8 +169,7 @@ extension Option where Value: ExpressibleByArgument {
157169
///
158170
/// This method is called to initialize an `Option` without a default value such as:
159171
/// ```swift
160-
/// @Option()
161-
/// var foo: String
172+
/// @Option var foo: String
162173
/// ```
163174
///
164175
/// - Parameters:

Sources/ArgumentParser/Parsable Properties/OptionGroup.swift

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,12 @@
1818
/// @Flag(name: .shortAndLong)
1919
/// var verbose: Bool
2020
///
21-
/// @Argument()
22-
/// var values: [Int]
21+
/// @Argument var values: [Int]
2322
/// }
2423
///
2524
/// struct Options: ParsableArguments {
26-
/// @Option()
27-
/// var name: String
28-
///
29-
/// @OptionGroup()
30-
/// var globals: GlobalOptions
25+
/// @Option var name: String
26+
/// @OptionGroup var globals: GlobalOptions
3127
/// }
3228
///
3329
/// The flag and positional arguments declared as part of `GlobalOptions` are
@@ -58,7 +54,14 @@ public struct OptionGroup<Value: ParsableArguments>: Decodable, ParsedWrapper {
5854
throw ParserError.userValidationError(error)
5955
}
6056
}
61-
57+
58+
/// Creates a property that represents another parsable type.
59+
public init() {
60+
self.init(_parsedValue: .init { _ in
61+
ArgumentSet(Value.self)
62+
})
63+
}
64+
6265
/// The value presented by this property wrapper.
6366
public var wrappedValue: Value {
6467
get {
@@ -85,12 +88,3 @@ extension OptionGroup: CustomStringConvertible {
8588
}
8689
}
8790
}
88-
89-
extension OptionGroup {
90-
/// Creates a property that represents another parsable type.
91-
public init() {
92-
self.init(_parsedValue: .init { _ in
93-
ArgumentSet(Value.self)
94-
})
95-
}
96-
}

Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,17 @@ final class ParsableArgumentsValidationTests: XCTestCase {
167167
var options: Options
168168
}
169169

170+
private struct L: ParsableArguments {
171+
struct Options: ParsableArguments {
172+
@Argument var items: [Int] = []
173+
}
174+
175+
@Argument var foo: String
176+
@Option var bar: String
177+
@OptionGroup var options: Options
178+
@Flag var flag: Bool
179+
}
180+
170181
func testPositionalArgumentsValidation() throws {
171182
XCTAssertNil(PositionalArgumentsValidator.validate(A.self))
172183
XCTAssertNil(PositionalArgumentsValidator.validate(F.self))

0 commit comments

Comments
 (0)