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

Issue:
Repeated flags with FlagExclusivity.chooseFirst error instead of choosing first.
<!--
    Thanks for contributing to the Swift Argument Parser!

    Before you submit your issue, please replace each paragraph
    below with the relevant details for your bug, and complete
    the steps in the checklist by placing an 'x' in each box:
    
    - [x] I've completed this task
    - [ ] This task isn't completed
-->

Repeated flags defined with `FlagExclusivity.chooseFirst` error instead of choosing first.

**ArgumentParser version:** `master` at https://github.com/apple/swift-argument-parser/commit/58660b05c444b6916df980e7ad1e8f047cf14977
**Swift version:** Apple Swift version 5.2 (swiftlang-1103.0.30 clang-1103.0.30)

### Checklist
- [x] If possible, I've reproduced the issue using the `master` branch of this package
- [x] I've searched for [existing GitHub issues](https://github.com/apple/swift-argument-parser/issues)

### Steps to Reproduce
Use this simple program:
```
enum Enum: String, CaseIterable { case s, t, u }

struct Options: ParsableCommand {
  @Flag(name: .short, exclusivity: .chooseFirst)
  var flag1: Enum
}

let options = Options.parseOrExit()
print(options.flag1)
```
```
$ options -s -t
```
### Expected behavior
It should print "s" by itself, on a line

### Actual behavior
```
$ options -s -t
Error: Unknown option '-t'
Usage: options -s -t -u
```

Pull request:
<!--
    Thanks for contributing to the Swift Argument Parser!

    If this pull request adds new API, please add '?template=new.md'
    to the URL to switch to the appropriate template.

    Before you submit your request, please replace the paragraph
    below with the relevant details, and complete the steps in the
    checklist by placing an 'x' in each box:
    
    - [x] I've completed this task
    - [ ] This task isn't completed
-->

Previously, using a set of flags declared with FlagExclusivity.chooseFirst would result in an error instead of choosing the first from the command line. (See https://github.com/apple/swift-argument-parser/issues/66).

### Detailed Design
Added a case handling repeating arguments in the `ArgumentSet.updateFlag` function.
The previous behaviour had the repeating-arguments-with-.chooseFirst situation going through the `default` case, which didn't add the current argument to the list of used arguments, which later caused an error due to the argument being incorrectly deemed to have not been used.

### Documentation Plan
No change to documentation is needed.

### Test Plan
Tests added to `FlagsEndToEndTests.testParsing_invert` and `FlagsEndToEndTests.testParsingCaseIterable_RepeatableFlags`.

### Source Impact
None.

### 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](CONTRIBUTING.md)
- [x] I've updated the documentation if necessary

This PR fixes https://github.com/apple/swift-argument-parser/issues/66

Patch:
diff --git a/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift
index 976f8223d..06edd0526 100644
--- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift
+++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift
@@ -176,12 +176,14 @@ extension ArgumentSet {
     case(true, let previous?, .exclusive):
       // This value has already been set.
       throw ParserError.duplicateExclusiveValues(previous: previous.inputOrigin, duplicate: origin, originalInput: values.originalInput)
+    case (true, _, .chooseFirst):
+      values.update(forKey: key, inputOrigin: origin, initial: value, closure: { _ in })
     case (false, _, _), (_, _, .chooseLast):
       values.set(value, forKey: key, inputOrigin: origin)
-      return true
     default:
-      return hasUpdated
+      break
     }
+    return true
   }
   
   /// Creates an argument set for a pair of inverted Boolean flags.


Test patch:
diff --git a/Tests/EndToEndTests/FlagsEndToEndTests.swift b/Tests/EndToEndTests/FlagsEndToEndTests.swift
index 86cb9bf84..198f95fe2 100644
--- a/Tests/EndToEndTests/FlagsEndToEndTests.swift
+++ b/Tests/EndToEndTests/FlagsEndToEndTests.swift
@@ -80,10 +80,9 @@ extension FlagsEndToEndTests {
     AssertParse(Bar.self, ["--enable-logging"]) { options in
       XCTAssertEqual(options.logging, true)
     }
-// Can't test this yet, because .chooseFirst flags don't work
-//    AssertParse(Bar.self, ["--enable-logging", "--disable-logging"]) { options in
-//      XCTAssertEqual(options.logging, false)
-//    }
+    AssertParse(Bar.self, ["--disable-logging", "--enable-logging"]) { options in
+      XCTAssertEqual(options.logging, false)
+    }
   }
 }
 
@@ -263,3 +262,25 @@ extension FlagsEndToEndTests {
     XCTAssertThrowsError(try Qux.parse(["--pink", "--small", "--bloop"]))
   }
 }
+
+fileprivate struct RepeatOK: ParsableArguments {
+  @Flag(exclusivity: .chooseFirst)
+  var color: Color
+
+  @Flag(exclusivity: .chooseLast)
+  var shape: Shape
+}
+
+extension FlagsEndToEndTests {
+  func testParsingCaseIterable_RepeatableFlags() throws {
+    AssertParse(RepeatOK.self, ["--pink", "--purple", "--square"]) { options in
+      XCTAssertEqual(options.color, .pink)
+      XCTAssertEqual(options.shape, .square)
+    }
+
+    AssertParse(RepeatOK.self, ["--round", "--oblong", "--silver"]) { options in
+      XCTAssertEqual(options.color, .silver)
+      XCTAssertEqual(options.shape, .oblong)
+    }
+  }
+}