Skip to content

No value or parsing error if [Option] arg equals to [Value] arg #398

@zii-dmg

Description

@zii-dmg

Example:

class SameOptionAndPosValueArgs
{
	[Option('u', "unit")]
	public string OptValue { get; set; }

	[Value(0)]
	public string PosValue { get; set; }
}

[Fact]
public void SameOptionAndPosValue()
{
	var parser = Parser.Default;
	var result = parser.ParseArguments<SameOptionAndPosValueArgs>(
		new[] { "arg", "-u", "arg" }); // !!! pos and value are "arg"
	result
		.WithNotParsed(errors => { throw new InvalidOperationException("Must be parsed."); })
		.WithParsed(args =>
		{
			Assert.Equal("arg", args.OptValue);
			Assert.Equal("arg", args.PosValue); // !!! args.PosValue is null
		});
}

When user enters same values for [Option] and [Value] args, parser is losing one of it because they are equal tokens.

TokenPartitioner.cs has code:

var nonOptions = tokenList
    .Where(t => !switches.Contains(t))
    .Where(t => !scalars.Contains(t))
    .Where(t => !sequences.Contains(t)).Memorize();
var values = nonOptions.Where(v => v.IsValue()).Memorize();
var errors = nonOptions.Except(values).Memorize();

One of tokens for "-u" option is already in scalars variable so Where is filtering out both tokens with "arg" and then its not gettings into values. Filtering is happening because all tokens classes have IEqualtable<T> so "arg" == "arg" but it is two different items in memory.

One of solutions: make reference comparing, not IEqutable<T>. .Net doesn't have such comparer. Stackoverflow helps:

internal sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer<object>
{
	public static readonly ReferenceEqualityComparer Default = new ReferenceEqualityComparer();

	public new bool Equals(object x, object y)
	{
		return x == y; // reference equality because operator== is static and resolved at compile-time
	}

	public int GetHashCode(object obj)
	{
		return RuntimeHelpers.GetHashCode(obj);
	}
}

Pass this comparer to Contains and Except:

var nonOptions = tokenList
	.Where(t => !switches.Contains(t, ReferenceEqualityComparer.Default))
	.Where(t => !scalars.Contains(t, ReferenceEqualityComparer.Default))
	.Where(t => !sequences.Contains(t, ReferenceEqualityComparer.Default)).Memorize();
var values = nonOptions.Where(v => v.IsValue()).Memorize();
var errors = nonOptions.Except(values, ReferenceEqualityComparer.Default).Cast<Token>().Memorize();

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions