Skip to content

Commit 587d26a

Browse files
authored
Fixes incorrect value copying when ParseArguments has the same field name+type as the ParseCommand (Issue #322) (#495)
* Contains fixes and test cases for #322
1 parent 774de9c commit 587d26a

23 files changed

+301
-137
lines changed

Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ struct BashCompletionsGenerator {
132132
///
133133
/// These consist of completions that are defined as `.list` or `.custom`.
134134
fileprivate static func generateArgumentCompletions(_ commands: [ParsableCommand.Type]) -> [String] {
135-
ArgumentSet(commands.last!, visibility: .default)
135+
ArgumentSet(commands.last!, visibility: .default, parent: .root)
136136
.compactMap { arg -> String? in
137137
guard arg.isPositional else { return nil }
138138

@@ -148,7 +148,7 @@ struct BashCompletionsGenerator {
148148
let subcommandNames = commands.dropFirst().map { $0._commandName }.joined(separator: " ")
149149
// TODO: Make this work for @Arguments
150150
let argumentName = arg.names.preferredName?.synopsisString
151-
?? arg.help.keys.first?.rawValue ?? "---"
151+
?? arg.help.keys.first?.name ?? "---"
152152

153153
return """
154154
$("${COMP_WORDS[0]}" ---completion \(subcommandNames) -- \(argumentName) "${COMP_WORDS[@]}")
@@ -159,7 +159,7 @@ struct BashCompletionsGenerator {
159159

160160
/// Returns the case-matching statements for supplying completions after an option or flag.
161161
fileprivate static func generateOptionHandlers(_ commands: [ParsableCommand.Type]) -> String {
162-
ArgumentSet(commands.last!, visibility: .default)
162+
ArgumentSet(commands.last!, visibility: .default, parent: .root)
163163
.compactMap { arg -> String? in
164164
let words = arg.bashCompletionWords()
165165
if words.isEmpty { return nil }

Sources/ArgumentParser/Completions/CompletionsGenerator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ extension ArgumentDefinition {
105105
func customCompletionCall(_ commands: [ParsableCommand.Type]) -> String {
106106
let subcommandNames = commands.dropFirst().map { $0._commandName }.joined(separator: " ")
107107
let argumentName = names.preferredName?.synopsisString
108-
?? self.help.keys.first?.rawValue ?? "---"
108+
?? self.help.keys.first?.name ?? "---"
109109
return "---completion \(subcommandNames) -- \(argumentName)"
110110
}
111111
}

Sources/ArgumentParser/Parsable Properties/Flag.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ extension Flag where Value: EnumerableFlag {
396396
// flag, the default value to show to the user is the `--value-name`
397397
// flag that a user would provide on the command line, not a Swift value.
398398
let defaultValueFlag = initial.flatMap { value -> String? in
399-
let defaultKey = InputKey(rawValue: String(describing: value))
399+
let defaultKey = InputKey(name: String(describing: value), parent: .key(key))
400400
let defaultNames = Value.name(for: value).makeNames(defaultKey)
401401
return defaultNames.first?.synopsisString
402402
}
@@ -405,7 +405,7 @@ extension Flag where Value: EnumerableFlag {
405405
let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil })
406406

407407
let args = Value.allCases.enumerated().map { (i, value) -> ArgumentDefinition in
408-
let caseKey = InputKey(rawValue: String(describing: value))
408+
let caseKey = InputKey(name: String(describing: value), parent: .key(key))
409409
let name = Value.name(for: value)
410410

411411
let helpForCase = caseHelps[i] ?? help
@@ -510,7 +510,7 @@ extension Flag {
510510
exclusivity: FlagExclusivity = .exclusive,
511511
help: ArgumentHelp? = nil
512512
) where Value == Element?, Element: EnumerableFlag {
513-
self.init(_parsedValue: .init { key in
513+
self.init(_parsedValue: .init { parentKey in
514514
// This gets flipped to `true` the first time one of these flags is
515515
// encountered.
516516
var hasUpdated = false
@@ -519,7 +519,7 @@ extension Flag {
519519
let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil })
520520

521521
let args = Element.allCases.enumerated().map { (i, value) -> ArgumentDefinition in
522-
let caseKey = InputKey(rawValue: String(describing: value))
522+
let caseKey = InputKey(name: String(describing: value), parent: .key(parentKey))
523523
let name = Element.name(for: value)
524524
let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help
525525

@@ -528,11 +528,11 @@ extension Flag {
528528
options: [.isOptional],
529529
help: helpForCase,
530530
defaultValue: nil,
531-
key: key,
531+
key: parentKey,
532532
isComposite: !hasCustomCaseHelp)
533533

534-
return ArgumentDefinition.flag(name: name, key: key, caseKey: caseKey, help: help, parsingStrategy: .default, initialValue: nil as Element?, update: .nullary({ (origin, name, values) in
535-
hasUpdated = try ArgumentSet.updateFlag(key: key, value: value, origin: origin, values: &values, hasUpdated: hasUpdated, exclusivity: exclusivity)
534+
return ArgumentDefinition.flag(name: name, key: parentKey, caseKey: caseKey, help: help, parsingStrategy: .default, initialValue: nil as Element?, update: .nullary({ (origin, name, values) in
535+
hasUpdated = try ArgumentSet.updateFlag(key: parentKey, value: value, origin: origin, values: &values, hasUpdated: hasUpdated, exclusivity: exclusivity)
536536
}))
537537

538538
}
@@ -547,24 +547,24 @@ extension Flag {
547547
initial: [Element]?,
548548
help: ArgumentHelp? = nil
549549
) where Value == Array<Element>, Element: EnumerableFlag {
550-
self.init(_parsedValue: .init { key in
550+
self.init(_parsedValue: .init { parentKey in
551551
let caseHelps = Element.allCases.map { Element.help(for: $0) }
552552
let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil })
553553

554554
let args = Element.allCases.enumerated().map { (i, value) -> ArgumentDefinition in
555-
let caseKey = InputKey(rawValue: String(describing: value))
555+
let caseKey = InputKey(name: String(describing: value), parent: .key(parentKey))
556556
let name = Element.name(for: value)
557557
let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help
558558
let help = ArgumentDefinition.Help(
559559
allValues: [],
560560
options: [.isOptional],
561561
help: helpForCase,
562562
defaultValue: nil,
563-
key: key,
563+
key: parentKey,
564564
isComposite: !hasCustomCaseHelp)
565565

566-
return ArgumentDefinition.flag(name: name, key: key, caseKey: caseKey, help: help, parsingStrategy: .default, initialValue: initial, update: .nullary({ (origin, name, values) in
567-
values.update(forKey: key, inputOrigin: origin, initial: [Element](), closure: {
566+
return ArgumentDefinition.flag(name: name, key: parentKey, caseKey: caseKey, help: help, parsingStrategy: .default, initialValue: initial, update: .nullary({ (origin, name, values) in
567+
values.update(forKey: parentKey, inputOrigin: origin, initial: [Element](), closure: {
568568
$0.append(value)
569569
})
570570
}))

Sources/ArgumentParser/Parsable Properties/NameSpecification.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@ extension NameSpecification.Element {
136136
internal func name(for key: InputKey) -> Name? {
137137
switch self.base {
138138
case .long:
139-
return .long(key.rawValue.convertedToSnakeCase(separator: "-"))
139+
return .long(key.name.convertedToSnakeCase(separator: "-"))
140140
case .short:
141-
guard let c = key.rawValue.first else { fatalError("Key '\(key.rawValue)' has not characters to form short option name.") }
141+
guard let c = key.name.first else { fatalError("Key '\(key.name)' has not characters to form short option name.") }
142142
return .short(c)
143143
case .customLong(let name, let withSingleDash):
144144
return withSingleDash
@@ -167,7 +167,7 @@ extension FlagInversion {
167167
case .short, .customShort:
168168
return includingShort ? element.name(for: key) : nil
169169
case .long:
170-
let modifiedKey = InputKey(rawValue: key.rawValue.addingIntercappedPrefix(prefix))
170+
let modifiedKey = key.with(newName: key.name.addingIntercappedPrefix(prefix))
171171
return element.name(for: modifiedKey)
172172
case .customLong(let name, let withSingleDash):
173173
let modifiedName = name.addingPrefixWithAutodetectedStyle(prefix)

Sources/ArgumentParser/Parsable Properties/OptionGroup.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ public struct OptionGroup<Value: ParsableArguments>: Decodable, ParsedWrapper {
6464
/// Creates a property that represents another parsable type, using the
6565
/// specified visibility.
6666
public init(visibility: ArgumentVisibility = .default) {
67-
self.init(_parsedValue: .init { _ in
68-
ArgumentSet(Value.self, visibility: .private)
67+
self.init(_parsedValue: .init { parentKey in
68+
return ArgumentSet(Value.self, visibility: .private, parent: .key(parentKey))
6969
})
7070
self._visibility = visibility
7171
}

Sources/ArgumentParser/Parsable Types/ParsableArguments.swift

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,10 @@ extension ArgumentSetProvider {
269269
}
270270

271271
extension ArgumentSet {
272-
init(_ type: ParsableArguments.Type, visibility: ArgumentVisibility) {
272+
init(_ type: ParsableArguments.Type, visibility: ArgumentVisibility, parent: InputKey.Parent) {
273273
#if DEBUG
274274
do {
275-
try type._validate()
275+
try type._validate(parent: parent)
276276
} catch {
277277
assertionFailure("\(error)")
278278
}
@@ -281,22 +281,18 @@ extension ArgumentSet {
281281
let a: [ArgumentSet] = Mirror(reflecting: type.init())
282282
.children
283283
.compactMap { child -> ArgumentSet? in
284-
guard var codingKey = child.label else { return nil }
284+
guard let codingKey = child.label else { return nil }
285285

286286
if let parsed = child.value as? ArgumentSetProvider {
287287
guard parsed._visibility.isAtLeastAsVisible(as: visibility)
288288
else { return nil }
289289

290-
// Property wrappers have underscore-prefixed names
291-
codingKey = String(codingKey.first == "_"
292-
? codingKey.dropFirst(1)
293-
: codingKey.dropFirst(0))
294-
let key = InputKey(rawValue: codingKey)
290+
let key = InputKey(name: codingKey, parent: parent)
295291
return parsed.argumentSet(for: key)
296292
} else {
297293
let arg = ArgumentDefinition(
298294
unparsedKey: codingKey,
299-
default: nilOrValue(child.value))
295+
default: nilOrValue(child.value), parent: parent)
300296

301297
// Save a non-wrapped property as is
302298
return ArgumentSet(arg)

Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
fileprivate protocol ParsableArgumentsValidator {
13-
static func validate(_ type: ParsableArguments.Type) -> ParsableArgumentsValidatorError?
13+
static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError?
1414
}
1515

1616
enum ValidatorErrorKind {
@@ -37,15 +37,15 @@ struct ParsableArgumentsValidationError: Error, CustomStringConvertible {
3737
}
3838

3939
extension ParsableArguments {
40-
static func _validate() throws {
40+
static func _validate(parent: InputKey.Parent) throws {
4141
let validators: [ParsableArgumentsValidator.Type] = [
4242
PositionalArgumentsValidator.self,
4343
ParsableArgumentsCodingKeyValidator.self,
4444
ParsableArgumentsUniqueNamesValidator.self,
4545
NonsenseFlagsValidator.self,
4646
]
4747
let errors = validators.compactMap { validator in
48-
validator.validate(self)
48+
validator.validate(self, parent: parent)
4949
}
5050
if errors.count > 0 {
5151
throw ParsableArgumentsValidationError(parsableArgumentsType: self, underlayingErrors: errors)
@@ -68,7 +68,6 @@ fileprivate extension ArgumentSet {
6868
/// in the argument list. Any other configuration leads to ambiguity in
6969
/// parsing the arguments.
7070
struct PositionalArgumentsValidator: ParsableArgumentsValidator {
71-
7271
struct Error: ParsableArgumentsValidatorError, CustomStringConvertible {
7372
let repeatedPositionalArgument: String
7473

@@ -81,19 +80,16 @@ struct PositionalArgumentsValidator: ParsableArgumentsValidator {
8180
var kind: ValidatorErrorKind { .failure }
8281
}
8382

84-
static func validate(_ type: ParsableArguments.Type) -> ParsableArgumentsValidatorError? {
83+
static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? {
8584
let sets: [ArgumentSet] = Mirror(reflecting: type.init())
8685
.children
8786
.compactMap { child in
8887
guard
89-
var codingKey = child.label,
88+
let codingKey = child.label,
9089
let parsed = child.value as? ArgumentSetProvider
9190
else { return nil }
9291

93-
// Property wrappers have underscore-prefixed names
94-
codingKey = String(codingKey.first == "_" ? codingKey.dropFirst(1) : codingKey.dropFirst(0))
95-
96-
let key = InputKey(rawValue: codingKey)
92+
let key = InputKey(name: codingKey, parent: parent)
9793
return parsed.argumentSet(for: key)
9894
}
9995

@@ -107,20 +103,20 @@ struct PositionalArgumentsValidator: ParsableArgumentsValidator {
107103
let firstRepeatedPositionalArgument: ArgumentDefinition = sets[repeatedPositional].firstRepeatedPositionalArgument!
108104
let positionalFollowingRepeatedArgument: ArgumentDefinition = positionalFollowingRepeated.firstPositionalArgument!
109105
return Error(
110-
repeatedPositionalArgument: firstRepeatedPositionalArgument.help.keys.first!.rawValue,
111-
positionalArgumentFollowingRepeated: positionalFollowingRepeatedArgument.help.keys.first!.rawValue)
106+
repeatedPositionalArgument: firstRepeatedPositionalArgument.help.keys.first!.name,
107+
positionalArgumentFollowingRepeated: positionalFollowingRepeatedArgument.help.keys.first!.name)
112108
}
113109
}
114110

115111
/// Ensure that all arguments have corresponding coding keys
116112
struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator {
117113

118114
private struct Validator: Decoder {
119-
let argumentKeys: [String]
115+
let argumentKeys: [InputKey]
120116

121117
enum ValidationResult: Swift.Error {
122118
case success
123-
case missingCodingKeys([String])
119+
case missingCodingKeys([InputKey])
124120
}
125121

126122
let codingPath: [CodingKey] = []
@@ -135,7 +131,7 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator {
135131
}
136132

137133
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
138-
let missingKeys = argumentKeys.filter { Key(stringValue: $0) == nil }
134+
let missingKeys = argumentKeys.filter { Key(stringValue: $0.name) == nil }
139135
if missingKeys.isEmpty {
140136
throw ValidationResult.success
141137
} else {
@@ -147,7 +143,7 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator {
147143
/// This error indicates that an option, a flag, or an argument of
148144
/// a `ParsableArguments` is defined without a corresponding `CodingKey`.
149145
struct MissingKeysError: ParsableArgumentsValidatorError, CustomStringConvertible {
150-
let missingCodingKeys: [String]
146+
let missingCodingKeys: [InputKey]
151147

152148
var description: String {
153149
let resolution = """
@@ -194,8 +190,8 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator {
194190
}
195191
}
196192

197-
static func validate(_ type: ParsableArguments.Type) -> ParsableArgumentsValidatorError? {
198-
let argumentKeys: [String] = Mirror(reflecting: type.init())
193+
static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? {
194+
let argumentKeys: [InputKey] = Mirror(reflecting: type.init())
199195
.children
200196
.compactMap { child in
201197
guard
@@ -204,7 +200,7 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator {
204200
else { return nil }
205201

206202
// Property wrappers have underscore-prefixed names
207-
return String(codingKey.first == "_" ? codingKey.dropFirst(1) : codingKey.dropFirst(0))
203+
return InputKey(name: codingKey, parent: parent)
208204
}
209205
guard argumentKeys.count > 0 else {
210206
return nil
@@ -239,19 +235,16 @@ struct ParsableArgumentsUniqueNamesValidator: ParsableArgumentsValidator {
239235
var kind: ValidatorErrorKind { .failure }
240236
}
241237

242-
static func validate(_ type: ParsableArguments.Type) -> ParsableArgumentsValidatorError? {
238+
static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? {
243239
let argSets: [ArgumentSet] = Mirror(reflecting: type.init())
244240
.children
245241
.compactMap { child in
246242
guard
247-
var codingKey = child.label,
243+
let codingKey = child.label,
248244
let parsed = child.value as? ArgumentSetProvider
249245
else { return nil }
250246

251-
// Property wrappers have underscore-prefixed names
252-
codingKey = String(codingKey.first == "_" ? codingKey.dropFirst(1) : codingKey.dropFirst(0))
253-
254-
let key = InputKey(rawValue: codingKey)
247+
let key = InputKey(name: codingKey, parent: parent)
255248
return parsed.argumentSet(for: key)
256249
}
257250

@@ -290,19 +283,16 @@ struct NonsenseFlagsValidator: ParsableArgumentsValidator {
290283
var kind: ValidatorErrorKind { .warning }
291284
}
292285

293-
static func validate(_ type: ParsableArguments.Type) -> ParsableArgumentsValidatorError? {
286+
static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? {
294287
let argSets: [ArgumentSet] = Mirror(reflecting: type.init())
295288
.children
296289
.compactMap { child in
297290
guard
298-
var codingKey = child.label,
291+
let codingKey = child.label,
299292
let parsed = child.value as? ArgumentSetProvider
300293
else { return nil }
301294

302-
// Property wrappers have underscore-prefixed names
303-
codingKey = String(codingKey.first == "_" ? codingKey.dropFirst(1) : codingKey.dropFirst(0))
304-
305-
let key = InputKey(rawValue: codingKey)
295+
let key = InputKey(name: codingKey, parent: parent)
306296
return parsed.argumentSet(for: key)
307297
}
308298

Sources/ArgumentParser/Parsable Types/ParsableCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ extension ParsableCommand {
160160
/// `true` if this command contains any array arguments that are declared
161161
/// with `.unconditionalRemaining`.
162162
internal static var includesUnconditionalArguments: Bool {
163-
ArgumentSet(self, visibility: .private).contains(where: {
163+
ArgumentSet(self, visibility: .private, parent: .root).contains(where: {
164164
$0.isRepeatingPositional && $0.parsingStrategy == .allRemainingInput
165165
})
166166
}

0 commit comments

Comments
 (0)