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

Issue:
some of the help text not rendered properly when option is EnumFlag
<!--
    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
-->

If I have 

```swift
enum CoverageFormat: String, ExpressibleByArgument, CaseIterable {
    case json
    case html

    var defaultValueDescription: String {
        switch self {
            case .json: "Produces a JSON coverage report."
            case .html: "Produces an HTML report producd by llvm-cov.  The HTML report can be configured using a reponse file stored in the project repository. See 'TODO' for more."
        }
    }
}
```

And in my `AsyncParseableCommand` , I have

```swift
    @Option(
        help: "Format of the code coverage output. One of 'json' or 'html'.",
    )
    var format: [CoverageFormat] = [.json]
```

running the `--help` option produces the following

```
  --codecov-format, --code-coverage-format, --coverage-format <format>
                          (values: json, html; default: Produces a JSON coverage report.)
```

Replace this paragraph with a short description of the incorrect incorrect behavior. If this is a regression, please note the last version that the behavior was correct in addition to your current version.

**ArgumentParser version:** `main` branch at commit ID `04695ec5448962bcd5e4616ed184b8a2168751e7`
**Swift version:** 
```
❯ swift --version            
Apple Swift version 6.2 (swift-6.2-RELEASE)
Target: arm64-apple-macosx26.0
Build config: +assertions
```

### 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
A minimal reproducible case has been added here:  https://github.com/bkhouri/sap-reproducible-example.git

1. clone https://github.com/bkhouri/sap-reproducible-example.git
2. run `swift run sap-reproducible-example --help` or follow the instructions in the README.md 

### Expected behavior
The `--help` output for the options that is an array of enum display the proper `default:` value, and also lists the description of each enum item.  it addition, which may be outside the scope of this issue, would be a help string that automatically indicate the `--format-as-array` can be specified multiple times.

That is, the output looks something like 

```
  --format-as-array <format>
                          Specify the output format. Available options: json, xml, yaml, plain (default: [plain])
        json              - Output in JSON format
        xml               - Output in XML format
        yaml              - Output in YAML format
        plain             - Output in plain text format
```

### Actual behavior
```
  --format-as-array <format-as-array>
                          Specify the output format. Available options: json, xml, yaml, plain (values: json, xml, yaml, plain; default: Output in plain text
                          format)
```

Pull request description:
<!--
    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
-->

Adding an `@Option` that is an Array of Enum flag did not display the help message correctly.

Update the HelpGenerator to handle this case.

Fixes: #818

### 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/Parsable Properties/ArgumentDiscussion.swift b/Sources/ArgumentParser/Parsable Properties/ArgumentDiscussion.swift
index 00101c47..46f2ad1d 100644
--- a/Sources/ArgumentParser/Parsable Properties/ArgumentDiscussion.swift
+++ b/Sources/ArgumentParser/Parsable Properties/ArgumentDiscussion.swift
@@ -77,9 +77,29 @@
 ///    -h, --help           Show help information
 /// ```
 ///
-/// In any case where the argument type is not `EnumerableOptionValue`, the
-/// default implementation will use the `.staticText` case and will print a
-/// block of discussion text.
+/// Arrays of enumerable types are also supported and will display the same
+/// enumerated format:
+///
+/// ```swift
+/// @Option var colors: [Color] = [.red, .blue]
+/// ```
+///
+/// The printed usage would look like the following:
+///
+/// ```
+/// USAGE: example [--colors <colors> ...]
+///
+/// OPTIONS:
+///    --colors <colors>        (default: red, blue)
+///          red           - A red color.
+///          blue          - A blue color.
+///          yellow        - A yellow color.
+///    -h, --help           Show help information
+/// ```
+///
+/// In any case where the argument type is not `EnumerableOptionValue` or an
+/// array of `EnumerableOptionValue`, the default implementation will use the
+/// `.staticText` case and will print a block of discussion text.
 enum ArgumentDiscussion {
   case staticText(String)
   case enumerated(preamble: String? = nil, any ExpressibleByArgument.Type)
diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift
index 0a5537e5..024a0232 100644
--- a/Sources/ArgumentParser/Parsable Properties/Option.swift
+++ b/Sources/ArgumentParser/Parsable Properties/Option.swift
@@ -721,6 +721,11 @@ extension Option {
   /// var chars: [Character] = []
   /// ```
   ///
+  /// If the element type conforms to `ExpressibleByArgument` and has enumerable
+  /// value descriptions (via `defaultValueDescription`), the help output will
+  /// display each possible value with its description, similar to single
+  /// enumerable options.
+  ///
   /// - Parameters:
   ///   - wrappedValue: A default value to use for this property, provided
   ///     implicitly by the compiler during property wrapper initialization.
@@ -745,7 +750,13 @@ extension Option {
           container: Array<T>.self,
           key: key,
           kind: .name(key: key, specification: name),
-          help: help,
+          help: .init(
+            help?.abstract ?? "",
+            discussion: help?.discussion,
+            valueName: help?.valueName,
+            visibility: help?.visibility ?? .default,
+            argumentType: T.self
+          ),
           parsingStrategy: parsingStrategy.base,
           initial: wrappedValue,
           completion: completion)
@@ -765,6 +776,11 @@ extension Option {
   /// var chars: [Character]
   /// ```
   ///
+  /// If the element type conforms to `ExpressibleByArgument` and has enumerable
+  /// value descriptions (via `defaultValueDescription`), the help output will
+  /// display each possible value with its description, similar to single
+  /// enumerable options.
+  ///
   /// - Parameters:
   ///   - name: A specification for what names are allowed for this option.
   ///   - parsingStrategy: The behavior to use when parsing the elements for
@@ -784,7 +800,13 @@ extension Option {
           container: Array<T>.self,
           key: key,
           kind: .name(key: key, specification: name),
-          help: help,
+          help: .init(
+            help?.abstract ?? "",
+            discussion: help?.discussion,
+            valueName: help?.valueName,
+            visibility: help?.visibility ?? .default,
+            argumentType: T.self
+          ),
           parsingStrategy: parsingStrategy.base,
           initial: nil,
           completion: completion)
diff --git a/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift b/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift
index 72e3784d..b59aa80a 100644
--- a/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift
+++ b/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift
@@ -447,7 +447,12 @@ where Element: ExpressibleByArgument {
     guard !initial.isEmpty else { return nil }
     return initial
       .lazy
-      .map { $0.defaultValueDescription }
+      .map { element in
+        if let element = element as? (any CaseIterable & RawRepresentable) {
+          return String(describing: element.rawValue)
+        }
+        return element.defaultValueDescription
+      }
       .joined(separator: ", ")
   }
 }


Test patch:
diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift
index fa0a2c6b..cc3973c7 100644
--- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift
+++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift
@@ -1095,6 +1095,80 @@ extension HelpGenerationTests {
         """)
   }
 
+  struct CustomOptionAsListWithSingleDefaultValue: ParsableCommand {
+    @Option(
+      help: "An option with enumerable values.")
+    var opt: [OptionValues] = [.red]
+  }
+
+  func testEnumerableOptionAsListWithSingleDefault() {
+    AssertHelp(
+      .default,
+      for: CustomOptionAsListWithSingleDefaultValue.self,
+      columns: 100,
+      equals: """
+        USAGE: custom-option-as-list-with-single-default-value [--opt <opt> ...]
+
+        OPTIONS:
+          --opt <opt>             An option with enumerable values. (default: red)
+                blue              - The color of the sky.
+                red               - The color of a rose.
+                yellow            - The color of the sun.
+          -h, --help              Show help information.
+
+        """)
+  }
+
+  struct CustomOptionAsListWithMultipleDefaultValue: ParsableCommand {
+    @Option(
+      help: "An option with multiple enumerable values.")
+    var opt: [OptionValues] = [.red, .blue]
+  }
+
+  func testEnumerableOptionAsListWithMultipleDefault() {
+    AssertHelp(
+      .default,
+      for: CustomOptionAsListWithMultipleDefaultValue.self,
+      columns: 100,
+      equals: """
+        USAGE: custom-option-as-list-with-multiple-default-value [--opt <opt> ...]
+
+        OPTIONS:
+          --opt <opt>             An option with multiple enumerable values. (default: red, blue)
+                blue              - The color of the sky.
+                red               - The color of a rose.
+                yellow            - The color of the sun.
+          -h, --help              Show help information.
+
+        """)
+
+  }
+
+  struct CustomOptionAsListWithEmptyArrayAsDefault: ParsableCommand {
+    @Option(
+      help: "An option with default value set to empty array.")
+    var opt: [OptionValues] = []
+  }
+
+  func testEnumerableOptionAsListWithEmptyArrayAsDefault() {
+    AssertHelp(
+      .default,
+      for: CustomOptionAsListWithEmptyArrayAsDefault.self,
+      columns: 100,
+      equals: """
+        USAGE: custom-option-as-list-with-empty-array-as-default [--opt <opt> ...]
+
+        OPTIONS:
+          --opt <opt>             An option with default value set to empty array.
+                blue              - The color of the sky.
+                red               - The color of a rose.
+                yellow            - The color of the sun.
+          -h, --help              Show help information.
+
+        """)
+
+  }
+
   struct CustomOptionWithDefault: ParsableCommand {
     @Option(help: "An option with enumerable values and a custom default.")
     var opt: OptionValues = .red