Skip to content

chore: Allow multiple CLI option parameter with same key - #3277

Open
filzrev wants to merge 6 commits into
dotnet:masterfrom
filzrev:chore-allow-multiple-same-cli-parameters
Open

filzrev wants to merge 6 commits into
dotnet:masterfrom
filzrev:chore-allow-multiple-same-cli-parameters

Conversation

@filzrev

@filzrev filzrev commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

This PR contains following changes.

What's changed in this PR.

1. Enable CommandLineParser's AllowMultiInstance option
This option is introduced at CommandLineParser v2.9.1.
By enable this option, it can specify same options multiple times (e.g. --filter A --filter B instead of --filter A B)

2. Modify CommandLineOptions.cs and add additional option mappings.
Add following dictionary/hashSet definitions.

  • CanonicalNames
  • MultiValueOptionNames
  • ScalarValueOptionNames

To avoid using reflection.
These mappings are manually edited (And mappings consistencies are validated by unit tests)

3. Modify ConfigParser.cs and add validation logics for duplicated scalar options.
By default, when scalar option are specified multiple times. It's overwritten by last parameter.
To keep existing behavior, add logics to validate these cases.

4. Add ConfigParser related tests.

@timcassell timcassell left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed by Claude (Opus 5.5), posted by @timcassell.

Comment thread src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs Outdated
Comment thread src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs Outdated
Comment thread src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs Outdated
Comment thread src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs Outdated
Comment thread src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs Outdated
Comment thread src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs Outdated
Comment thread src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs Outdated
Comment thread tests/BenchmarkDotNet.Tests/CommandLineOptionsTests.cs Outdated
@filzrev
filzrev force-pushed the chore-allow-multiple-same-cli-parameters branch from 728fe0f to cf92b03 Compare October 16, 2026 02:17
@filzrev
filzrev force-pushed the chore-allow-multiple-same-cli-parameters branch 2 times, most recently from 02fd109 to 38a3bdb Compare October 16, 2026 15:15
@filzrev
filzrev force-pushed the chore-allow-multiple-same-cli-parameters branch from 38a3bdb to b0abffa Compare October 16, 2026 15:21

@timcassell timcassell left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous findings are addressed, thanks. One new issue in the probe predicate below.

Reviewed by Claude (Opus 5.5), posted by @timcassell.

Comment on lines +304 to +316
private static bool HasMultiValueRepeatErrorsOnly(IEnumerable<Error> errors)
{
if (errors.IsEmpty())
return false;

return errors.All(error =>
{
if (error is not RepeatedOptionError repeated)
return false;

return CommandLineOptions.IsMultiValueOption(repeated.NameInfo.LongName);
});
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any other error alongside a valid multi-value repeat makes the probe return false, so the real parse then reports the legal repeat as an error too:

Args Errors
--filter A --filter B --bogus bogus is unknown + f, filter is defined multiple times
--filter A --filter B --iterationCount x bad format + f, filter is defined multiple times

Allow multi-instance unless a scalar option is repeated; with this, both rows report only the real error (verified against this head; the other cases in the tests are unchanged). Worth a test pairing a repeated --filter with an unknown option.

Suggested change
private static bool HasMultiValueRepeatErrorsOnly(IEnumerable<Error> errors)
{
if (errors.IsEmpty())
return false;
return errors.All(error =>
{
if (error is not RepeatedOptionError repeated)
return false;
return CommandLineOptions.IsMultiValueOption(repeated.NameInfo.LongName);
});
}
private static bool HasNoScalarRepeatErrors(IEnumerable<Error> errors)
=> !errors.Any(error => error is RepeatedOptionError repeated
&& !CommandLineOptions.IsMultiValueOption(repeated.NameInfo.LongName));

ILogger logger = NullLogger.Instance;
using (var parser = CreateParser(logger))

bool allowMultiInstance;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same probe block as in Parse; suggest extracting a helper, e.g. bool ShouldAllowMultiInstance(string[] args). Also .WithParsed(_ => allowMultiInstance = false) is a no-op since it's already initialized to false.

args = expandedArgs;
using (var parser = CreateParser(logger))

// First path: Detect repeated options with and `allowMultiInstance: false` setting.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "First path"/"Second path" → "pass"; stray "and" in "with and allowMultiInstance: false".

using CommandLine.Text;
using JetBrains.Annotations;
using Perfolizer.Mathematics.OutlierDetection;
using System.Collections.Frozen;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: unused using.


private static string Escape(string input) => UserInteractionHelper.EscapeCommandExample(input);

internal static bool IsMultiValueOption(string longName) => MultiValueOptions.Contains(longName);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contains on IReadOnlyCollection<string> is LINQ's Enumerable.Contains; it only stays case-insensitive because LINQ forwards to the HashSet. Typing the field as ISet/FrozenSet makes the lookup explicit.

private sealed record OptionProperty(PropertyInfo Property, OptionAttribute Option);

/// <summary>
/// Validates that the hardcoded set in CommandLineOptions (MultiValueOptionNames)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: MultiValueOptionNames → MultiValueOptions (also line 33).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants