Repository: apple/swift-argument-parser (swift)

Issue:
Crash when having a dictionary property (ArgumentDecoder.swift:89: Fatal error)
Swift argument parser crashes when adding a dictionary property - see attached reproducer.

**ArgumentParser version:** `1.2.2` (reproduced with `main` too)
**Swift version:**  `swift-driver version: 1.62.15 Apple Swift version 5.7.1 (swiftlang-5.7.1.135.3 clang-1400.0.29.51)
Target: arm64-apple-macosx13.0
`
### Checklist
- [x] If possible, I've reproduced the issue using the `main` branch of this package
- [x] I've searched for [existing GitHub issues](https://github.com/apple/swift-argument-parser/issues)

### Steps to Reproduce
Unpack the attached reproducible project and run from command line with:
`swift run swift-argument-parser-reproducer --command run`

The complete code is simply:
```swift
import ArgumentParser

typealias BenchmarkIdentifier=Int

struct BenchmarkResult: Codable {
    var x:Int
}

@main
struct BenchmarkTool: AsyncParsableCommand {
    @Option(name: .long, help: "The command to perform")
    var command: String

    var benchmarkResults: [BenchmarkIdentifier: [BenchmarkResult]] = [:] // comment this out to not crash

    mutating func run() async throws {
    }
}
```

### Expected behavior
```
hassila@ice ~/G/swift-argument-parser-reproducer> swift run swift-argument-parser-reproducer --command run
Building for debugging...
[3/3] Linking swift-argument-parser-reproducer
Build complete! (0.39s)
USAGE: benchmark-tool --command <command>

OPTIONS:
  --command <command>     The command to perform
  -h, --help              Show help information.

hassila@ice ~/G/swift-argument-parser-reproducer> 
```

### Actual behavior
```
hassila@ice ~/G/swift-argument-parser-reproducer> swift run swift-argument-parser-reproducer --command run
Building for debugging...
[47/47] Linking swift-argument-parser-reproducer
Build complete! (4.50s)
ArgumentParser/ArgumentDecoder.swift:89: Fatal error
fish: Job 1, 'swift run swift-argument-pars...' terminated by signal SIGTRAP (Trace or breakpoint trap)
```

[swift-argument-parser-reproducer.zip](https://github.com/apple/swift-argument-parser/files/10731814/swift-argument-parser-reproducer.zip)

Pull request:
As initially demonstrated in #553, the existing `ArgumentDecoder` logic fails when presented with a property which:

- Is unparsed
- Is not optional
- Has a default value, and
- Requests a keyed container during decoding

This occurs because while the actual property in question is properly assessed as having `.defaultValue` as an `InputOrigin`, the request for a keyed decoding container bypasses the logic which would otherwise return said default value, and since the `ArgumentDefinition` logic is (correctly) not recursive, the type fails to decode any keys it contains due to a "missing value".

The most straightforward solution I could find, one which covers all cases without negatively affecting handling of parsed properties (to the best of my ability to determine), was to apply the "default value" handling from the `decodeIfPresent<T>(_:forKey:)` method of the keyed decoding container (which ensured that _optional_ properties of this sort already worked correctly) to the more fundamental "bottleneck" `decode<T>(_:forKey:)` method as well, modified to throw a `wrongType` error instead of returning nil on type mismatch.

I additionally added a functioning `allKeys` property accessor for additional robustness against custom `Decodable.init(from:)` implementations. (The choice not to do the same for `nestedContainer(keyedBy:)`, `nestedUnkeyedContainer()`, `superDecoder()`, and `superDecoder(forKey:)` was simply pragmatic - it's not even close to worth the effort.)

Fixes #553 

### Checklist
- [x] I've added at least one test that validates that my change is working, if appropriate
- [x] I've followed the code style of the rest of the project
- [x] I've read the [Contribution Guidelines](https://github.com/apple/swift-argument-parser/blob/main/CONTRIBUTING.md)
- [x] I've updated the documentation if necessary

Patch:
diff --git a/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift b/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift
index 616785dad..81fc0932b 100644
--- a/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift
+++ b/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift
@@ -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)
   }


Test patch:
diff --git a/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift b/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift
index ccf2e6c56..e6b6a4e8a 100644
--- a/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift
+++ b/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift
@@ -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")
+    }
+  }
+}