Repository navigation
Description
Issue #434 reported that when a label, but no values, are provided for an array @Option property, the error message incorrectly signifies a missing option instead of a missing value. PR #435 fixed this for .upToNextOption arrays, but the bug remained for .allRemainingInput arrays.
Example
@main
struct Example: ParsableCommand {
@Option(parsing: .remaining)
var test: [String]
mutating func run() throws {
print(test)
}
}Expected Behaviour
$ example --test
Error: Missing value for '--test <test>'
Actual Behaviour
$ example --test
Error: Missing expected argument '--test <test> ...'
Unexpected Side-Effect
I created a commit that fixes this issue for .allRemainingInput arrays at james-fletcher-01/swift-argument-parser@1bf93bf. However, this change breaks the following test:
func testParsing_repeatingStringRemaining_2() {
AssertParse(Baz.self, ["--names"]) { baz in
XCTAssertFalse(baz.verbose)
XCTAssertTrue(baz.names.isEmpty)
}
}This implies that for options with a default value, the expected behavior is for an option label without a value to essentially be ignored and retain the default value. For example, while the above test uses an empty array as the default value, this works as well:
struct NonEmptyDefaultValue: ParsableArguments {
@Option(parsing: .remaining)
var names = ["default"]
}
func testNonEmptyDefaultValue() {
AssertParse(NonEmptyDefaultValue.self, ["--names"]) { baz in
XCTAssertEqual(baz.names, ["default"])
}
}It's surprising to me that this would be the expected behaviour.
- No other parsing strategy accepts an option label without a value. This includes single values strategies, as well as
.upToNextOption. - In the example where the default is an empty array, it kind of makes sense. But the example I provided above seems especially counter-intuitive, since you explicitly provided an "empty option" but ended up with a non-empty value.
So if this really is expected behaviour, I can modify my changes to detect the presence of a default value and supress the error message before I submit a PR. But I would just like to get others' thoughts on this?
ArgumentParser version: main
Swift version: swift-driver version: 1.115.1 Apple Swift version 6.0.3 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)
Checklist
- If possible, I've reproduced the issue using the
mainbranch of this package - I've searched for existing GitHub issues
Activity
natecook1000 commentedon Apr 4, 2025
Thanks for filing this issue, @james-fletcher-01! So the question is what the behavior should be for
.remainingoptions that have a default value, as right now it's permitted to provide the option name (e.g.--names) without any following values, but only when there's a default value for the option.I absolutely agree that the current behavior is incorrect. It would be tenable for that input to always parse as an empty array (i.e. the "remaining" input is zero items long), but falling back to the default in this case doesn't make sense. Our options are:
What do you think? I need to consider the source-compatibility aspect for a bit.
james-fletcher-01 commentedon Apr 6, 2025
Hi @natecook1000,
I have given this significant thought over the last couple of days, and I have come to the conclusion that, if the behaviour were to be changed, turning it into an error is the better solution.
I did almost come around to the idea of normalising it as an empty array. While it’s inconsistent with all other parsing strategies, the
.remainingstrategy is already quite a special case, and having an option for “clearing the default value” did seem like a reasonable design.However, when I came back to it from the perspective of a CLI user rather than a programmer, I realised that I can’t think of any precedent for such behaviour. I think trying to convey to the user that there is a default value that can be cleared in this way could be confusing, and for applications that need such behaviour, it would be better to make it explicit (eg. with a
--no-filesoption).With that said, I understand that there are source compatibility issues regarding whether this behaviour can be changed at all, so I will leave it with you to decide what’s best!
alphasafal commentedon Oct 17, 2026
Hey @natecook1000, I looked into this one and wanted to add some detail since it's been quiet for a while.
The
.upToNextOptioncase already has the check from #435 (theguard let first = inputArguments.elements.first, first.isValueblock inArgumentSet.swift). The.allRemainingInputcase right above it doesn't. So when nothing follows--names, the consume loop never runs and the value is just whateverargument.initial(origin, &result)set, which is the default. That's where the "label with no values keeps the default" behaviour comes from. It doesn't look like it was ever intended.I'd also lean towards the error, same as @james-fletcher-01. It matches every other strategy. And if a tool really needs a way to clear the default, an explicit flag is clearer for users.
On source compat, the case I can see breaking is scripts that build args like
tool --names $FILESwhere the variable can be empty. Today that quietly falls back to the default; after the change it would error. Probably rare, but worth a line in the CHANGELOG.If you're ok with the error direction, I'm happy to put up a PR: add the same guard as
.upToNextOption, updateparsing_repeatingStringRemaining_2to expect the error, and add a test for a non-empty default. @james-fletcher-01, if you'd still like to send yours since you already have the commit, totally fine, I don't want to step on that.Edit: one data point on compat. I checked apple/container (main as of Oct 16), which depends on this package. Its only
.remaininguse is an@ArgumentinMachineSet.swift, so neither the empty-array nor the error option would affect it.james-fletcher-01 commentedon Oct 18, 2026
Hi @alphasafal,
Thanks for taking a look at this. I had completely forgotten about it.
I have rebased my branch, as it was quite out of date. I have also updated the tests I introduced, so they now use Swift Testing. The updated diff is here.
In my original commit, I had already included a test in
ErrorMessageTests.swiftto validate the error message if no value is provided. So that just leaves the question, if we were to go ahead with this, what should happen toparsing_repeatingStringRemaining_2. It really does seem superfluous, so perhaps it should just be removed, and the subsequent tests should be renumbered?alphasafal commentedon Nov 2, 2026
Sorry for the slow reply. I'd keep
parsing_repeatingStringRemaining_2and flip it to expect the error, the same way the.upToNextOptiontests checkQux.parse(["--names", "--other"]):That keeps end-to-end coverage for the bare label next to the other
.remainingcases and avoids renumbering the rest. Your branch already has the error-message test, so please go ahead and open the PR from it. Happy to review.james-fletcher-01 commentedon Nov 9, 2026
Hi @alphasafal,
Thanks for your input. I have updated the
parsing_repeatingStringRemaining_2test to expect the error as suggested, and submitted PR #1003.