apple / swift-argument-parser

Straightforward, type-safe argument parsing for Swift
Apache License 2.0
3.31k stars 311 forks source link

Optional array parameter initialised with the last argument only #562

Closed anton-plebanovich closed 1 year ago

anton-plebanovich commented 1 year ago

I have an option of an optional array type

@Option(name: [.customShort("d"), .customLong("date")], help: "help!")
private var dates: [String]?

if I call my command with arguments like -d 2020-10-02 -d 2021-05-11 it only will be initialized with the last ["2021-05-11"] argument. Non-optional array argument works properly.

Checklist

Steps to Reproduce

Something like that:

import ArgumentParser
import Foundation

struct Bug: ParsableCommand {
    static let configuration = CommandConfiguration(
        abstract: "bug!",
        subcommands: [BugSubcommand.self],
        defaultSubcommand: BugSubcommand.self)

    init() {}
}

struct BugSubcommand: ParsableCommand {
    @Option(name: [.customShort("d"), .customLong("date")], help: "help!")
    private var dates: [String]?

    func run() throws {
        print(dates!)
    }
}

Bug.main()

Execution command: bug --date 2020-10-02 --date 2021-05-11

Expected behavior

I expected to receive dates property with ["2020-10-02", "2021-05-11"] value

Actual behavior

I received dates property with ["2021-05-11"] value

natecook1000 commented 1 year ago

Hi @anton-plebanovich — I can't make your example work as is. Can you provide a working example that shows the bug that you're seeing?

anton-plebanovich commented 1 year ago

Hi @natecook1000, thanks for the response. It looks like I forgot about the extensions I added previously:

extension Optional: ExpressibleByArgument where Wrapped: LosslessStringConvertible {
    public init?(argument: String) {
        self = Wrapped(argument)
    }
}

extension Array: LosslessStringConvertible where Element == String {
    public init?(_ description: String) {
        self = description
            .components(separatedBy: ",")
            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
    }
}

Without those extensions, code does not compile at all.

Overall, it looks like I should rework my code to have an empty array as the default argument and stop using optional arrays as I can just check for array emptiness instead. Sadly, I don't remember why I needed an optional array. Probably, because nil and empty have different meanings in my logic but since it is impossible to distinguish between nil and empty array from the arguments parser perspective optional arrays do not make much sense.