Skip to content

Commit

Permalink
Handle unparsed non-optional complex property types (apple#554)
Browse files Browse the repository at this point in the history
* Implement correct handling of unparsed property types which perform non-optional nested decoding.
  • Loading branch information
gwynne authored Feb 23, 2023
1 parent 5649a38 commit c5050aa
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Sources/ArgumentParser/Parsing/ArgumentDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ final class ParsedArgumentsContainer<K>: KeyedDecodingContainerProtocol where K
}

func decode<T>(_ type: T.Type, forKey key: K) throws -> T where T : Decodable {
let parsedElement = element(forKey: key)
if parsedElement?.inputOrigin.isDefaultValue ?? false, let rawValue = parsedElement?.value {
guard let value = rawValue as? T else {
throw InternalParseError.wrongType(rawValue, forKey: parsedElement!.key)
}
return value
}
let subDecoder = SingleValueDecoder(userInfo: decoder.userInfo, underlying: decoder, codingPath: codingPath + [key], key: InputKey(codingKey: key, path: codingPath), parsedElement: element(forKey: key))
return try type.init(from: subDecoder)
}
Expand Down
69 changes: 69 additions & 0 deletions Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,72 @@ extension UnparsedValuesEndToEndTests {
XCTAssertThrowsError(try Bar.parse(["--bar", "--bazz", "xyz", "--age", "None"]))
}
}

// MARK: Value + unparsed dictionary

fileprivate struct Bamf: ParsableCommand {
@Flag var bamph: Bool = false
var bop: [String: String] = [:]
var bopp: [String: [String]] = [:]
}

extension UnparsedValuesEndToEndTests {
func testUnparsedNestedDictionary() {
AssertParse(Bamf.self, []) { bamf in
XCTAssertFalse(bamf.bamph)
XCTAssertEqual(bamf.bop, [:])
XCTAssertEqual(bamf.bopp, [:])
}
}
}

// MARK: Value + unparsed enum with associated values

fileprivate struct Qiqi: ParsableCommand {
@Flag var qiqiqi: Bool = false
var qiqii: Qiqii = .q("")
}

fileprivate enum Qiqii: Codable, Equatable {
// Enums with associated values generate a Codable conformance
// which calls `KeyedDecodingContainer.nestedContainer(keyedBy:)`.
//
// There is no known case of anything ever actually using the
// `.nestedUnkeyedContainer()` method.
case q(String)
case i(Int)
}

extension UnparsedValuesEndToEndTests {
func testUnparsedEnumWithAssociatedValues() {
AssertParse(Qiqi.self, []) { qiqi in
XCTAssertFalse(qiqi.qiqiqi)
XCTAssertEqual(qiqi.qiqii, .q(""))
}
}
}

// MARK: Value + nested decodable inheriting class type

fileprivate struct Fry: ParsableCommand {
@Flag var c: Bool = false
var toksVig: Vig = .init()
}

fileprivate class Toks: Codable {
var a = "hello"
}

fileprivate final class Vig: Toks {
var b = "world"
}

extension UnparsedValuesEndToEndTests {
func testUnparsedNestedInheritingClassType() {
AssertParse(Fry.self, []) { fry in
XCTAssertFalse(fry.c)
XCTAssertEqual(fry.toksVig.a, "hello")
XCTAssertEqual(fry.toksVig.b, "world")
}
}
}

0 comments on commit c5050aa

Please sign in to comment.