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

Issue:
Extra values required with `--option=value` and `.upToNextOption`
When an option is declared with the `.upToNextOption` parsing strategy, using a "tied" value isn't sufficient enough to satisfy the parser. For example, with this command declaration:

```swift
import ArgumentParser

@main
struct Example: ParsableCommand {
    @Option(name: .shortAndLong, parsing: .upToNextOption)
    var count: [Int] = []

    mutating func run() throws {
        print(count)
    }
}
```

The option requires at least one non-tied value:

```
$ example --count=1 1
[1, 1]
$ example --count=1
Error: Missing value for '--count <count>'
Help:  --count <count>
Usage: example [--count <count> ...]
  See 'example --help' for more information.
```

**ArgumentParser version:** `1.3.0`
**Swift version:** Apple Swift version 5.11-dev (LLVM fe9a485647a386d, Swift e395df3ef372fd4)

### 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)

Pull request description:
Fixes an issue where a single attached value triggered an error on `.upToNextOption` options.

Fixes #609.

### 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/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift
index c82861985..054f4527d 100644
--- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift
+++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift
@@ -327,10 +327,12 @@ struct LenientParser {
       
     case .upToNextOption:
       // Use an attached value if it exists...
+      var foundAttachedValue = false
       if let value = parsed.value {
         // This was `--foo=bar` style:
         try update(origin, parsed.name, value, &result)
         usedOrigins.formUnion(origin)
+        foundAttachedValue = true
       } else if argument.allowsJoinedValue,
           let (origin2, value) = inputArguments.extractJoinedElement(at: originElement) {
         // Found a joined argument
@@ -338,6 +340,7 @@ struct LenientParser {
         try update(origins, parsed.name, String(value), &result)
         usedOrigins.formUnion(origins)
         inputArguments.removeAll(in: usedOrigins)
+        foundAttachedValue = true
       }
       
       // Clear out the initial origin first, since it can include
@@ -350,7 +353,13 @@ struct LenientParser {
       guard let first = inputArguments.elements.first,
             first.isValue
       else {
-        throw ParserError.missingValueForOption(origin, parsed.name)
+        // No independent values to be found, which is an error if there was
+        // no `--foo=bar`-style value already found.
+        if foundAttachedValue {
+          break
+        } else {
+          throw ParserError.missingValueForOption(origin, parsed.name)
+        }
       }
       
       // ...and then consume the arguments until hitting an option


Test patch:
diff --git a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift
index 43c12c0a7..cad7a3b59 100644
--- a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift
+++ b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift
@@ -210,6 +210,24 @@ extension RepeatingEndToEndTests {
       XCTAssertEqual(qux.names, ["one", "two"])
       XCTAssertNil(qux.extra)
     }
+
+    AssertParse(Qux.self, ["--verbose", "--names=one"]) { qux in
+      XCTAssertTrue(qux.verbose)
+      XCTAssertEqual(qux.names, ["one"])
+      XCTAssertNil(qux.extra)
+    }
+
+    AssertParse(Qux.self, ["--verbose", "--names=one", "two"]) { qux in
+      XCTAssertTrue(qux.verbose)
+      XCTAssertEqual(qux.names, ["one", "two"])
+      XCTAssertNil(qux.extra)
+    }
+
+    AssertParse(Qux.self, ["--names=one", "--verbose", "two"]) { qux in
+      XCTAssertTrue(qux.verbose)
+      XCTAssertEqual(qux.names, ["one"])
+      XCTAssertEqual(qux.extra, "two")
+    }
   }
 
   func testParsing_repeatingStringUpToNext_Fails() throws {