Skip to content

Commit c5050aa

Browse files
authored
Handle unparsed non-optional complex property types (#554)
* Implement correct handling of unparsed property types which perform non-optional nested decoding.
1 parent 5649a38 commit c5050aa

File tree

2 files changed

+76
-0
lines changed

2 files changed

+76
-0
lines changed

Sources/ArgumentParser/Parsing/ArgumentDecoder.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ final class ParsedArgumentsContainer<K>: KeyedDecodingContainerProtocol where K
103103
}
104104

105105
func decode<T>(_ type: T.Type, forKey key: K) throws -> T where T : Decodable {
106+
let parsedElement = element(forKey: key)
107+
if parsedElement?.inputOrigin.isDefaultValue ?? false, let rawValue = parsedElement?.value {
108+
guard let value = rawValue as? T else {
109+
throw InternalParseError.wrongType(rawValue, forKey: parsedElement!.key)
110+
}
111+
return value
112+
}
106113
let subDecoder = SingleValueDecoder(userInfo: decoder.userInfo, underlying: decoder, codingPath: codingPath + [key], key: InputKey(codingKey: key, path: codingPath), parsedElement: element(forKey: key))
107114
return try type.init(from: subDecoder)
108115
}

Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,72 @@ extension UnparsedValuesEndToEndTests {
256256
XCTAssertThrowsError(try Bar.parse(["--bar", "--bazz", "xyz", "--age", "None"]))
257257
}
258258
}
259+
260+
// MARK: Value + unparsed dictionary
261+
262+
fileprivate struct Bamf: ParsableCommand {
263+
@Flag var bamph: Bool = false
264+
var bop: [String: String] = [:]
265+
var bopp: [String: [String]] = [:]
266+
}
267+
268+
extension UnparsedValuesEndToEndTests {
269+
func testUnparsedNestedDictionary() {
270+
AssertParse(Bamf.self, []) { bamf in
271+
XCTAssertFalse(bamf.bamph)
272+
XCTAssertEqual(bamf.bop, [:])
273+
XCTAssertEqual(bamf.bopp, [:])
274+
}
275+
}
276+
}
277+
278+
// MARK: Value + unparsed enum with associated values
279+
280+
fileprivate struct Qiqi: ParsableCommand {
281+
@Flag var qiqiqi: Bool = false
282+
var qiqii: Qiqii = .q("")
283+
}
284+
285+
fileprivate enum Qiqii: Codable, Equatable {
286+
// Enums with associated values generate a Codable conformance
287+
// which calls `KeyedDecodingContainer.nestedContainer(keyedBy:)`.
288+
//
289+
// There is no known case of anything ever actually using the
290+
// `.nestedUnkeyedContainer()` method.
291+
case q(String)
292+
case i(Int)
293+
}
294+
295+
extension UnparsedValuesEndToEndTests {
296+
func testUnparsedEnumWithAssociatedValues() {
297+
AssertParse(Qiqi.self, []) { qiqi in
298+
XCTAssertFalse(qiqi.qiqiqi)
299+
XCTAssertEqual(qiqi.qiqii, .q(""))
300+
}
301+
}
302+
}
303+
304+
// MARK: Value + nested decodable inheriting class type
305+
306+
fileprivate struct Fry: ParsableCommand {
307+
@Flag var c: Bool = false
308+
var toksVig: Vig = .init()
309+
}
310+
311+
fileprivate class Toks: Codable {
312+
var a = "hello"
313+
}
314+
315+
fileprivate final class Vig: Toks {
316+
var b = "world"
317+
}
318+
319+
extension UnparsedValuesEndToEndTests {
320+
func testUnparsedNestedInheritingClassType() {
321+
AssertParse(Fry.self, []) { fry in
322+
XCTAssertFalse(fry.c)
323+
XCTAssertEqual(fry.toksVig.a, "hello")
324+
XCTAssertEqual(fry.toksVig.b, "world")
325+
}
326+
}
327+
}

0 commit comments

Comments
 (0)