diff --git a/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_NamingConventionBinder_api_is_not_changed.approved.txt b/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_NamingConventionBinder_api_is_not_changed.approved.txt index 28565c243f..d01b397a21 100644 --- a/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_NamingConventionBinder_api_is_not_changed.approved.txt +++ b/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_NamingConventionBinder_api_is_not_changed.approved.txt @@ -98,6 +98,7 @@ public class ModelBindingCommandHandler, System.CommandLine.Invocation.ICommandHandler public System.Void BindParameter(System.Reflection.ParameterInfo param, System.CommandLine.Argument argument) public System.Void BindParameter(System.Reflection.ParameterInfo param, System.CommandLine.Option option) + public System.Int32 Invoke(System.CommandLine.Invocation.InvocationContext context) public System.Threading.Tasks.Task InvokeAsync(System.CommandLine.Invocation.InvocationContext context) public class ModelDescriptor public static ModelDescriptor FromType() diff --git a/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt b/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt index f39318f893..7cd496d848 100644 --- a/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt +++ b/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt @@ -356,6 +356,7 @@ System.CommandLine.Help public System.Int32 GetHashCode() System.CommandLine.Invocation public interface ICommandHandler + public System.Int32 Invoke(InvocationContext context) public System.Threading.Tasks.Task InvokeAsync(InvocationContext context) public interface IInvocationResult public System.Void Apply(InvocationContext context) @@ -498,7 +499,7 @@ System.CommandLine.Parsing public T GetValueForOption(Option option) public System.Object GetValueForOption(System.CommandLine.Option option) public System.String ToString() - public struct Token : System.ValueType, System.IEquatable + public class Token, System.IEquatable public static System.Boolean op_Equality(Token left, Token right) public static System.Boolean op_Inequality(Token left, Token right) .ctor(System.String value, TokenType type, System.CommandLine.Symbol symbol) diff --git a/src/System.CommandLine.Benchmarks/System.CommandLine.Benchmarks.csproj b/src/System.CommandLine.Benchmarks/System.CommandLine.Benchmarks.csproj index 4da123c513..e54aabc422 100644 --- a/src/System.CommandLine.Benchmarks/System.CommandLine.Benchmarks.csproj +++ b/src/System.CommandLine.Benchmarks/System.CommandLine.Benchmarks.csproj @@ -9,8 +9,8 @@ false - net461;net5.0; - net5.0; + net461;net5.0;net6.0; + net5.0;net6.0; False diff --git a/src/System.CommandLine.Generator/CommandHandlerSourceGenerator.cs b/src/System.CommandLine.Generator/CommandHandlerSourceGenerator.cs index cbae2f8a83..8d4166eb20 100644 --- a/src/System.CommandLine.Generator/CommandHandlerSourceGenerator.cs +++ b/src/System.CommandLine.Generator/CommandHandlerSourceGenerator.cs @@ -114,6 +114,9 @@ private class GeneratedHandler_{handlerCount} : {ICommandHandlerType} {propertyDeclaration}"); } + builder.Append($@" + public int Invoke(global::System.CommandLine.Invocation.InvocationContext context) => InvokeAsync(context).GetAwaiter().GetResult();"); + builder.Append($@" public async global::System.Threading.Tasks.Task InvokeAsync(global::System.CommandLine.Invocation.InvocationContext context) {{"); diff --git a/src/System.CommandLine.Hosting.Tests/HostingHandlerTest.cs b/src/System.CommandLine.Hosting.Tests/HostingHandlerTest.cs index 6191715e30..ab446b2d9c 100644 --- a/src/System.CommandLine.Hosting.Tests/HostingHandlerTest.cs +++ b/src/System.CommandLine.Hosting.Tests/HostingHandlerTest.cs @@ -132,6 +132,12 @@ public MyHandler(MyService service) public int IntOption { get; set; } // bound from option public IConsole Console { get; set; } // bound from DI + public int Invoke(InvocationContext context) + { + service.Value = IntOption; + return IntOption; + } + public Task InvokeAsync(InvocationContext context) { service.Value = IntOption; @@ -162,6 +168,8 @@ public MyHandler(MyService service) public string One { get; set; } + public int Invoke(InvocationContext context) => InvokeAsync(context).GetAwaiter().GetResult(); + public Task InvokeAsync(InvocationContext context) { service.Value = IntOption; diff --git a/src/System.CommandLine.NamingConventionBinder.Tests/ParameterBindingTests.cs b/src/System.CommandLine.NamingConventionBinder.Tests/ParameterBindingTests.cs index 23ad1e474b..2f0530845b 100644 --- a/src/System.CommandLine.NamingConventionBinder.Tests/ParameterBindingTests.cs +++ b/src/System.CommandLine.NamingConventionBinder.Tests/ParameterBindingTests.cs @@ -446,6 +446,8 @@ public abstract class AbstractTestCommandHandler : ICommandHandler { public abstract Task DoJobAsync(); + public int Invoke(InvocationContext context) => InvokeAsync(context).GetAwaiter().GetResult(); + public Task InvokeAsync(InvocationContext context) => DoJobAsync(); } @@ -458,6 +460,8 @@ public override Task DoJobAsync() public class VirtualTestCommandHandler : ICommandHandler { + public int Invoke(InvocationContext context) => 42; + public virtual Task InvokeAsync(InvocationContext context) => Task.FromResult(42); } diff --git a/src/System.CommandLine.NamingConventionBinder/ModelBindingCommandHandler.cs b/src/System.CommandLine.NamingConventionBinder/ModelBindingCommandHandler.cs index aca39c524a..292e545de9 100644 --- a/src/System.CommandLine.NamingConventionBinder/ModelBindingCommandHandler.cs +++ b/src/System.CommandLine.NamingConventionBinder/ModelBindingCommandHandler.cs @@ -128,4 +128,6 @@ private void BindValueSource(ParameterInfo param, IValueSource valueSource) : _methodDescriptor.ParameterDescriptors .FirstOrDefault(x => x.ValueName == param.Name && x.ValueType == param.ParameterType); + + public int Invoke(InvocationContext context) => InvokeAsync(context).GetAwaiter().GetResult(); } \ No newline at end of file diff --git a/src/System.CommandLine/ArgumentArity.cs b/src/System.CommandLine/ArgumentArity.cs index f887c474c1..c0153fc86e 100644 --- a/src/System.CommandLine/ArgumentArity.cs +++ b/src/System.CommandLine/ArgumentArity.cs @@ -72,7 +72,7 @@ public bool Equals(ArgumentArity other) => public override int GetHashCode() => MaximumNumberOfValues ^ MinimumNumberOfValues ^ IsNonDefault.GetHashCode(); - internal static FailedArgumentConversionArityResult? Validate( + internal static ArgumentConversionResult? Validate( SymbolResult symbolResult, Argument argument, int minimumNumberOfValues, @@ -93,9 +93,10 @@ public override int GetHashCode() return null; } - return new MissingArgumentConversionResult( + return ArgumentConversionResult.Failure( argument, - symbolResult.LocalizationResources.RequiredArgumentMissing(symbolResult)); + symbolResult.LocalizationResources.RequiredArgumentMissing(symbolResult), + ArgumentConversionResultType.FailedMissingArgument); } if (tokenCount > maximumNumberOfValues) @@ -104,9 +105,10 @@ public override int GetHashCode() { if (!optionResult.Option.AllowMultipleArgumentsPerToken) { - return new TooManyArgumentsConversionResult( + return ArgumentConversionResult.Failure( argument, - symbolResult!.LocalizationResources.ExpectsOneArgument(symbolResult)); + symbolResult!.LocalizationResources.ExpectsOneArgument(symbolResult), + ArgumentConversionResultType.FailedTooManyArguments); } } } diff --git a/src/System.CommandLine/Binding/ArgumentConversionResult.cs b/src/System.CommandLine/Binding/ArgumentConversionResult.cs index 2da4231ad9..9cc56e88f2 100644 --- a/src/System.CommandLine/Binding/ArgumentConversionResult.cs +++ b/src/System.CommandLine/Binding/ArgumentConversionResult.cs @@ -1,23 +1,73 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Linq; + namespace System.CommandLine.Binding { - internal abstract class ArgumentConversionResult + internal sealed class ArgumentConversionResult { - private protected ArgumentConversionResult(Argument argument) + internal readonly Argument Argument; + internal readonly object? Value; + internal readonly string? ErrorMessage; + internal ArgumentConversionResultType Result; + + private ArgumentConversionResult(Argument argument, string error, ArgumentConversionResultType failure) + { + Argument = argument ?? throw new ArgumentNullException(nameof(argument)); + ErrorMessage = error ?? throw new ArgumentNullException(nameof(error)); + Result = failure; + } + + private ArgumentConversionResult(Argument argument, object? value) + { + Argument = argument ?? throw new ArgumentNullException(nameof(argument)); + Value = value; + Result = ArgumentConversionResultType.Successful; + } + + private ArgumentConversionResult(Argument argument) { Argument = argument ?? throw new ArgumentNullException(nameof(argument)); + Result = ArgumentConversionResultType.NoArgument; + } + + internal ArgumentConversionResult( + Argument argument, + Type expectedType, + string value, + LocalizationResources localizationResources) : + this(argument, FormatErrorMessage(argument, expectedType, value, localizationResources), ArgumentConversionResultType.FailedType) + { } - public Argument Argument { get; } + internal static ArgumentConversionResult Failure(Argument argument, string error, ArgumentConversionResultType reason) => new(argument, error, reason); + + public static ArgumentConversionResult Success(Argument argument, object? value) => new(argument, value); - internal string? ErrorMessage { get; set; } + internal static ArgumentConversionResult None(Argument argument) => new(argument); - internal static FailedArgumentConversionResult Failure(Argument argument, string error) => new(argument, error); + private static string FormatErrorMessage( + Argument argument, + Type expectedType, + string value, + LocalizationResources localizationResources) + { + if (argument.FirstParent?.Symbol is IdentifierSymbol identifierSymbol && + argument.FirstParent.Next is null) + { + var alias = identifierSymbol.Aliases.First(); - public static SuccessfulArgumentConversionResult Success(Argument argument, object? value) => new(argument, value); + switch (identifierSymbol) + { + case Command _: + return localizationResources.ArgumentConversionCannotParseForCommand(value, alias, expectedType); + case Option _: + return localizationResources.ArgumentConversionCannotParseForOption(value, alias, expectedType); + } + } - internal static NoArgumentConversionResult None(Argument argument) => new(argument); + return localizationResources.ArgumentConversionCannotParse(value, expectedType); + } } } \ No newline at end of file diff --git a/src/System.CommandLine/Binding/ArgumentConversionResultType.cs b/src/System.CommandLine/Binding/ArgumentConversionResultType.cs new file mode 100644 index 0000000000..8915793850 --- /dev/null +++ b/src/System.CommandLine/Binding/ArgumentConversionResultType.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.CommandLine.Binding +{ + internal enum ArgumentConversionResultType + { + NoArgument, // NoArgumentConversionResult + Successful, // SuccessfulArgumentConversionResult + Failed, // FailedArgumentConversionResult + FailedArity, // FailedArgumentConversionArityResult + FailedType, // FailedArgumentTypeConversionResult + FailedTooManyArguments, // TooManyArgumentsConversionResult + FailedMissingArgument, // MissingArgumentConversionResult + } +} \ No newline at end of file diff --git a/src/System.CommandLine/Binding/ArgumentConverter.DefaultValues.cs b/src/System.CommandLine/Binding/ArgumentConverter.DefaultValues.cs index 2538a16a3d..a4e971961b 100644 --- a/src/System.CommandLine/Binding/ArgumentConverter.DefaultValues.cs +++ b/src/System.CommandLine/Binding/ArgumentConverter.DefaultValues.cs @@ -4,7 +4,6 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -13,10 +12,7 @@ namespace System.CommandLine.Binding; internal static partial class ArgumentConverter { #if NET6_0_OR_GREATER - private static readonly Lazy _listCtor = - new(() => typeof(List<>) - .GetConstructors() - .SingleOrDefault(c => c.GetParameters().Length == 0)!); + private static ConstructorInfo? _listCtor; #endif private static Array CreateEmptyArray(Type itemType, int capacity = 0) @@ -25,14 +21,19 @@ private static Array CreateEmptyArray(Type itemType, int capacity = 0) private static IList CreateEmptyList(Type listType) { #if NET6_0_OR_GREATER - var ctor = (ConstructorInfo)listType.GetMemberWithSameMetadataDefinitionAs(_listCtor.Value); + ConstructorInfo? listCtor = _listCtor; + + if (listCtor is null) + { + _listCtor = listCtor = typeof(List<>).GetConstructor(Type.EmptyTypes)!; + } + + var ctor = (ConstructorInfo)listType.GetMemberWithSameMetadataDefinitionAs(listCtor); #else - var ctor = listType - .GetConstructors() - .SingleOrDefault(c => c.GetParameters().Length == 0); + var ctor = listType.GetConstructor(Type.EmptyTypes); #endif - return (IList)ctor.Invoke(new object[] { }); + return (IList)ctor.Invoke(null); } private static IList CreateEnumerable(Type type, Type itemType, int capacity = 0) diff --git a/src/System.CommandLine/Binding/ArgumentConverter.cs b/src/System.CommandLine/Binding/ArgumentConverter.cs index f7e0cba633..a2a0fbb26c 100644 --- a/src/System.CommandLine/Binding/ArgumentConverter.cs +++ b/src/System.CommandLine/Binding/ArgumentConverter.cs @@ -97,31 +97,33 @@ private static ArgumentConversionResult ConvertTokens( var result = ConvertToken(argument, itemType, token, localizationResources); - switch (result) + switch (result.Result) { - case FailedArgumentTypeConversionResult _: - case FailedArgumentConversionResult _: - if (argumentResult is { Parent: CommandResult }) - { - argumentResult.OnlyTake(i); - - i = tokens.Count; - break; - } - - return result; + case ArgumentConversionResultType.NoArgument: + break; - case SuccessfulArgumentConversionResult success: + case ArgumentConversionResultType.Successful: if (isArray) { - values[i] = success.Value; + values[i] = result.Value; } else { - values.Add(success.Value); + values.Add(result.Value); } break; + + default: // failures + if (argumentResult is { Parent: CommandResult }) + { + argumentResult.OnlyTake(i); + + i = tokens.Count; + break; + } + + return result; } } @@ -183,13 +185,13 @@ private static bool CanBeBoundFromScalarValue(this Type type) } } - private static FailedArgumentConversionResult Failure( + private static ArgumentConversionResult Failure( Argument argument, Type expectedType, string value, LocalizationResources localizationResources) { - return new FailedArgumentTypeConversionResult(argument, expectedType, value, localizationResources); + return new ArgumentConversionResult(argument, expectedType, value, localizationResources); } internal static ArgumentConversionResult ConvertIfNeeded( @@ -197,22 +199,24 @@ internal static ArgumentConversionResult ConvertIfNeeded( SymbolResult symbolResult, Type toType) { - return conversionResult switch + return conversionResult.Result switch { - SuccessfulArgumentConversionResult successful when !toType.IsInstanceOfType(successful.Value) => + ArgumentConversionResultType.Successful when !toType.IsInstanceOfType(conversionResult.Value) => ConvertObject(conversionResult.Argument, toType, - successful.Value, + conversionResult.Value, symbolResult.LocalizationResources), - - NoArgumentConversionResult _ when conversionResult.Argument.ValueType == typeof(bool) || conversionResult.Argument.ValueType == typeof(bool?) => + + ArgumentConversionResultType.NoArgument when conversionResult.Argument.ValueType == typeof(bool) || conversionResult.Argument.ValueType == typeof(bool?) => Success(conversionResult.Argument, true), - - NoArgumentConversionResult _ when conversionResult.Argument.Arity.MinimumNumberOfValues > 0 => - new MissingArgumentConversionResult(conversionResult.Argument, - symbolResult.LocalizationResources.RequiredArgumentMissing(symbolResult)), - NoArgumentConversionResult _ when conversionResult.Argument.Arity.MaximumNumberOfValues > 1 => + ArgumentConversionResultType.NoArgument when conversionResult.Argument.Arity.MinimumNumberOfValues > 0 => + ArgumentConversionResult.Failure( + conversionResult.Argument, + symbolResult.LocalizationResources.RequiredArgumentMissing(symbolResult), + ArgumentConversionResultType.FailedMissingArgument), + + ArgumentConversionResultType.NoArgument when conversionResult.Argument.Arity.MaximumNumberOfValues > 1 => Success(conversionResult.Argument, Array.Empty()), _ => conversionResult @@ -221,12 +225,11 @@ internal static ArgumentConversionResult ConvertIfNeeded( internal static T GetValueOrDefault(this ArgumentConversionResult result) { - return result switch + return result.Result switch { - SuccessfulArgumentConversionResult successful => (T)successful.Value!, - FailedArgumentConversionResult failed => throw new InvalidOperationException(failed.ErrorMessage), - NoArgumentConversionResult _ => default!, - _ => default!, + ArgumentConversionResultType.Successful => (T)result.Value!, + ArgumentConversionResultType.NoArgument => default!, + _ => throw new InvalidOperationException(result.ErrorMessage), }; } @@ -234,7 +237,7 @@ public static bool TryConvertArgument(ArgumentResult argumentResult, out object? { var argument = argumentResult.Argument; - value = argument.Arity.MaximumNumberOfValues switch + ArgumentConversionResult result = argument.Arity.MaximumNumberOfValues switch { // 0 is an implicit bool, i.e. a "flag" 0 => Success(argumentResult.Argument, true), @@ -251,7 +254,8 @@ public static bool TryConvertArgument(ArgumentResult argumentResult, out object? argumentResult) }; - return value is SuccessfulArgumentConversionResult; + value = result; + return result.Result == ArgumentConversionResultType.Successful; } internal static object? GetDefaultValue(Type type) diff --git a/src/System.CommandLine/Binding/BindingContext.cs b/src/System.CommandLine/Binding/BindingContext.cs index 67892e84de..0bfa6500c3 100644 --- a/src/System.CommandLine/Binding/BindingContext.cs +++ b/src/System.CommandLine/Binding/BindingContext.cs @@ -105,9 +105,9 @@ internal bool TryBindToScalarValue( value, localizationResources); - if (parsed is SuccessfulArgumentConversionResult successful) + if (parsed.Result == ArgumentConversionResultType.Successful) { - boundValue = new BoundValue(successful.Value, valueDescriptor, valueSource); + boundValue = new BoundValue(parsed.Value, valueDescriptor, valueSource); return true; } } diff --git a/src/System.CommandLine/Binding/FailedArgumentConversionArityResult.cs b/src/System.CommandLine/Binding/FailedArgumentConversionArityResult.cs deleted file mode 100644 index 5efcc2dd87..0000000000 --- a/src/System.CommandLine/Binding/FailedArgumentConversionArityResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Binding -{ - internal abstract class FailedArgumentConversionArityResult : FailedArgumentConversionResult - { - internal FailedArgumentConversionArityResult(Argument argument, string errorMessage) : base(argument, errorMessage) - { - } - } -} diff --git a/src/System.CommandLine/Binding/FailedArgumentConversionResult.cs b/src/System.CommandLine/Binding/FailedArgumentConversionResult.cs deleted file mode 100644 index ed0946253d..0000000000 --- a/src/System.CommandLine/Binding/FailedArgumentConversionResult.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Binding -{ - internal class FailedArgumentConversionResult : ArgumentConversionResult - { - internal FailedArgumentConversionResult( - Argument argument, - string errorMessage) : base(argument) - { - if (string.IsNullOrWhiteSpace(errorMessage)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(errorMessage)); - } - - ErrorMessage = errorMessage; - } - } -} diff --git a/src/System.CommandLine/Binding/FailedArgumentTypeConversionResult.cs b/src/System.CommandLine/Binding/FailedArgumentTypeConversionResult.cs deleted file mode 100644 index 2dcec61a68..0000000000 --- a/src/System.CommandLine/Binding/FailedArgumentTypeConversionResult.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Linq; - -namespace System.CommandLine.Binding -{ - internal class FailedArgumentTypeConversionResult : FailedArgumentConversionResult - { - internal FailedArgumentTypeConversionResult( - Argument argument, - Type expectedType, - string value, - LocalizationResources localizationResources) : - base(argument, FormatErrorMessage(argument, expectedType, value, localizationResources)) - { - } - - private static string FormatErrorMessage( - Argument argument, - Type expectedType, - string value, - LocalizationResources localizationResources) - { - if (argument.FirstParent?.Symbol is IdentifierSymbol identifierSymbol && - argument.FirstParent.Next is null) - { - var alias = identifierSymbol.Aliases.First(); - - switch (identifierSymbol) - { - case Command _: - return localizationResources.ArgumentConversionCannotParseForCommand(value, alias, expectedType); - case Option _: - return localizationResources.ArgumentConversionCannotParseForOption(value, alias, expectedType); - } - } - - return localizationResources.ArgumentConversionCannotParse(value, expectedType); - } - } -} diff --git a/src/System.CommandLine/Binding/IValueDescriptor.cs b/src/System.CommandLine/Binding/IValueDescriptor.cs index 94d8964c90..34b9e0052d 100644 --- a/src/System.CommandLine/Binding/IValueDescriptor.cs +++ b/src/System.CommandLine/Binding/IValueDescriptor.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Diagnostics.CodeAnalysis; - namespace System.CommandLine.Binding { /// diff --git a/src/System.CommandLine/Binding/MissingArgumentConversionResult.cs b/src/System.CommandLine/Binding/MissingArgumentConversionResult.cs deleted file mode 100644 index 133b8af8b3..0000000000 --- a/src/System.CommandLine/Binding/MissingArgumentConversionResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Binding -{ - internal class MissingArgumentConversionResult : FailedArgumentConversionArityResult - { - internal MissingArgumentConversionResult(Argument argument, string errorMessage) : base(argument, errorMessage) - { - } - } -} diff --git a/src/System.CommandLine/Binding/NoArgumentConversionResult.cs b/src/System.CommandLine/Binding/NoArgumentConversionResult.cs deleted file mode 100644 index 7dd718a261..0000000000 --- a/src/System.CommandLine/Binding/NoArgumentConversionResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Binding -{ - internal class NoArgumentConversionResult : ArgumentConversionResult - { - internal NoArgumentConversionResult(Argument argument) : base(argument) - { - } - } -} diff --git a/src/System.CommandLine/Binding/SuccessfulArgumentConversionResult.cs b/src/System.CommandLine/Binding/SuccessfulArgumentConversionResult.cs deleted file mode 100644 index 72c9b5a991..0000000000 --- a/src/System.CommandLine/Binding/SuccessfulArgumentConversionResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Binding; - -internal class SuccessfulArgumentConversionResult : ArgumentConversionResult -{ - internal SuccessfulArgumentConversionResult( - Argument argument, - object? value) : base(argument) - { - Value = value; - } - - public object? Value { get; } -} \ No newline at end of file diff --git a/src/System.CommandLine/Binding/TooManyArgumentsConversionResult.cs b/src/System.CommandLine/Binding/TooManyArgumentsConversionResult.cs deleted file mode 100644 index c35c6f8bd9..0000000000 --- a/src/System.CommandLine/Binding/TooManyArgumentsConversionResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Binding -{ - internal class TooManyArgumentsConversionResult : FailedArgumentConversionArityResult - { - internal TooManyArgumentsConversionResult(Argument argument, string errorMessage) : base(argument, errorMessage) - { - } - } -} diff --git a/src/System.CommandLine/Binding/TypeExtensions.cs b/src/System.CommandLine/Binding/TypeExtensions.cs index 6dd83a7751..b0b680a698 100644 --- a/src/System.CommandLine/Binding/TypeExtensions.cs +++ b/src/System.CommandLine/Binding/TypeExtensions.cs @@ -47,29 +47,14 @@ internal static bool IsEnumerable(this Type type) typeof(IEnumerable).IsAssignableFrom(type); } - internal static bool IsNullable(this Type t) - { - return t.IsGenericType && - t.GetGenericTypeDefinition() == typeof(Nullable<>); - } + internal static bool IsNullable(this Type t) => Nullable.GetUnderlyingType(t) is not null; internal static bool TryGetNullableType( this Type type, [NotNullWhen(true)] out Type? nullableType) { - if (type.IsGenericType) - { - var genericTypeDefinition = type.GetGenericTypeDefinition(); - - if (genericTypeDefinition == typeof(Nullable<>)) - { - nullableType = type.GetGenericArguments()[0]; - return true; - } - } - - nullableType = null; - return false; + nullableType = Nullable.GetUnderlyingType(type); + return nullableType is not null; } } } \ No newline at end of file diff --git a/src/System.CommandLine/Builder/CommandLineBuilder.cs b/src/System.CommandLine/Builder/CommandLineBuilder.cs index 9bd9dfed6d..959fcecdca 100644 --- a/src/System.CommandLine/Builder/CommandLineBuilder.cs +++ b/src/System.CommandLine/Builder/CommandLineBuilder.cs @@ -14,7 +14,10 @@ namespace System.CommandLine.Builder /// public class CommandLineBuilder { - private readonly List<(InvocationMiddleware middleware, int order)> _middlewareList = new(); + // for every generic type with type argument being struct JIT needs to compile a dedicated version + // (because each struct is of a different size) + // that is why we don't use List for middleware + private List>? _middlewareList; private LocalizationResources? _localizationResources; private Action? _customizeHelpBuilder; private Func? _helpBuilderFactory; @@ -99,7 +102,7 @@ public Parser Build() enableLegacyDoubleDashBehavior: EnableLegacyDoubleDashBehavior, resources: LocalizationResources, responseFileHandling: ResponseFileHandling, - middlewarePipeline: GetMiddleware(), + middlewarePipeline: _middlewareList is null ? Array.Empty() : GetMiddleware(), helpBuilderFactory: GetHelpBuilderFactory())); return parser; @@ -107,27 +110,22 @@ public Parser Build() private IReadOnlyList GetMiddleware() { - _middlewareList.Sort(static (m1, m2) => m1.order.CompareTo(m2.order)); + _middlewareList!.Sort(static (m1, m2) => m1.Item2.CompareTo(m2.Item2)); InvocationMiddleware[] result = new InvocationMiddleware[_middlewareList.Count]; for (int i = 0; i < result.Length; i++) { - result[i] = _middlewareList[i].middleware; + result[i] = _middlewareList[i].Item1; } return result; } - internal void AddMiddleware( - InvocationMiddleware middleware, - MiddlewareOrder order) - { - _middlewareList.Add((middleware, (int) order)); - } + internal void AddMiddleware(InvocationMiddleware middleware, MiddlewareOrder order) + => AddMiddleware(middleware, (int)order); - internal void AddMiddleware( - InvocationMiddleware middleware, - MiddlewareOrderInternal order) - { - _middlewareList.Add((middleware, (int) order)); - } + internal void AddMiddleware(InvocationMiddleware middleware, MiddlewareOrderInternal order) + => AddMiddleware(middleware, (int)order); + + private void AddMiddleware(InvocationMiddleware middleware, int order) + => (_middlewareList ??= new()).Add(new Tuple(middleware, order)); } } diff --git a/src/System.CommandLine/Command.cs b/src/System.CommandLine/Command.cs index 48382b05b4..4f9cfc7cd8 100644 --- a/src/System.CommandLine/Command.cs +++ b/src/System.CommandLine/Command.cs @@ -23,6 +23,7 @@ public class Command : IdentifierSymbol, IEnumerable private List? _arguments; private List