From 4fac9d3ae53cc31d98ed9ffaadb6063a79af3a57 Mon Sep 17 00:00:00 2001 From: John Wilson <44481187+JohnnyWombwell@users.noreply.github.com> Date: Fri, 18 Feb 2022 19:02:44 +0000 Subject: [PATCH 001/256] Added ZSH Completion Shim Script (#1643) * Add zsh completion shim and documentation. * ZSH shim script updated to use modern completions (thanks to @baronfel for the work on this). Co-authored-by: John Wilson --- docs/dotnet-suggest.md | 2 + .../SuggestionShellScriptHandlerTest.cs | 11 +++++- src/System.CommandLine.Suggest/ShellType.cs | 3 +- .../SuggestionShellScriptHandler.cs | 3 ++ .../dotnet-suggest-shim.zsh | 39 +++++++++++++++++++ .../dotnet-suggest.csproj | 4 ++ 6 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/System.CommandLine.Suggest/dotnet-suggest-shim.zsh diff --git a/docs/dotnet-suggest.md b/docs/dotnet-suggest.md index cc5c1103fb..5c5358793d 100644 --- a/docs/dotnet-suggest.md +++ b/docs/dotnet-suggest.md @@ -14,6 +14,8 @@ On the machine where you'd like to enable completion, you'll need to do two thin * For bash, add the contents of [dotnet-suggest-shim.bash](https://github.com/dotnet/command-line-api/blob/master/src/System.CommandLine.Suggest/dotnet-suggest-shim.bash) to `~/.bash_profile`. + * For zsh, add the contents of [dotnet-suggest-shim.zsh](https://github.com/dotnet/command-line-api/blob/master/src/System.CommandLine.Suggest/dotnet-suggest-shim.zsh) to `~/.zshrc`. + * For PowerShell, add the contents of [dotnet-suggest-shim.ps1](https://github.com/dotnet/command-line-api/blob/master/src/System.CommandLine.Suggest/dotnet-suggest-shim.ps1) to your PowerShell profile. You can find the expected path to your PowerShell profile by running the following in your console: ```console diff --git a/src/System.CommandLine.Suggest.Tests/SuggestionShellScriptHandlerTest.cs b/src/System.CommandLine.Suggest.Tests/SuggestionShellScriptHandlerTest.cs index 2dda947a7f..5a96e6171d 100644 --- a/src/System.CommandLine.Suggest.Tests/SuggestionShellScriptHandlerTest.cs +++ b/src/System.CommandLine.Suggest.Tests/SuggestionShellScriptHandlerTest.cs @@ -1,7 +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.CommandLine.Invocation; using System.CommandLine.IO; using System.CommandLine.Parsing; using System.Threading.Tasks; @@ -53,5 +52,15 @@ await _parser.InvokeAsync( _console.Out.ToString().Should().Contain("Register-ArgumentCompleter"); } + + [Fact] + public async Task It_should_print_zsh_shell_script() + { + await _parser.InvokeAsync( + "script zsh", + _console); + + _console.Out.ToString().Should().Contain("_dotnet_zsh_complete()"); + } } } diff --git a/src/System.CommandLine.Suggest/ShellType.cs b/src/System.CommandLine.Suggest/ShellType.cs index 3fda370d78..6f6fec0f20 100644 --- a/src/System.CommandLine.Suggest/ShellType.cs +++ b/src/System.CommandLine.Suggest/ShellType.cs @@ -6,6 +6,7 @@ namespace System.CommandLine.Suggest public enum ShellType { Bash, - PowerShell + PowerShell, + Zsh } } diff --git a/src/System.CommandLine.Suggest/SuggestionShellScriptHandler.cs b/src/System.CommandLine.Suggest/SuggestionShellScriptHandler.cs index b7106cbf79..5e68a051b2 100644 --- a/src/System.CommandLine.Suggest/SuggestionShellScriptHandler.cs +++ b/src/System.CommandLine.Suggest/SuggestionShellScriptHandler.cs @@ -18,6 +18,9 @@ public static void Handle(IConsole console, ShellType shellType) case ShellType.PowerShell: PrintToConsoleFrom(console, "dotnet-suggest-shim.ps1"); break; + case ShellType.Zsh: + PrintToConsoleFrom(console, "dotnet-suggest-shim.zsh"); + break; default: throw new SuggestionShellScriptException($"Shell '{shellType}' is not supported."); } diff --git a/src/System.CommandLine.Suggest/dotnet-suggest-shim.zsh b/src/System.CommandLine.Suggest/dotnet-suggest-shim.zsh new file mode 100644 index 0000000000..8aca6e16b9 --- /dev/null +++ b/src/System.CommandLine.Suggest/dotnet-suggest-shim.zsh @@ -0,0 +1,39 @@ +# dotnet suggest shell complete script start +_dotnet_zsh_complete() +{ + # debug lines, uncomment to get state variables passed to this function + # echo "\n\n\nstate:\t'$state'" + # echo "line:\t'$line'" + # echo "words:\t$words" + + # Get full path to script because dotnet-suggest needs it + # NOTE: this requires a command registered with dotnet-suggest be + # on the PATH + full_path=`which ${words[1]}` # zsh arrays are 1-indexed + # Get the full line + # $words array when quoted like this gets expanded out into the full line + full_line="$words" + + # Get the completion results, will be newline-delimited + completions=$(dotnet suggest get --executable "$full_path" -- "$full_line") + # explode the completions by linefeed instead of by spaces into the descriptions for the + # _values helper function. + + exploded=(${(f)completions}) + # for later - once we have descriptions from dotnet suggest, we can stitch them + # together like so: + # described=() + # for i in {1..$#exploded}; do + # argument="${exploded[$i]}" + # description="hello description $i" + # entry=($argument"["$description"]") + # described+=("$entry") + # done + _values 'suggestions' $exploded +} + +# apply this function to each command the dotnet-suggest knows about +compdef _dotnet_zsh_complete $(dotnet-suggest list) + +export DOTNET_SUGGEST_SCRIPT_VERSION="1.0.0" +# dotnet suggest shell complete script end diff --git a/src/System.CommandLine.Suggest/dotnet-suggest.csproj b/src/System.CommandLine.Suggest/dotnet-suggest.csproj index ee994e3f6f..fd82500868 100644 --- a/src/System.CommandLine.Suggest/dotnet-suggest.csproj +++ b/src/System.CommandLine.Suggest/dotnet-suggest.csproj @@ -37,6 +37,10 @@ PreserveNewest + + + PreserveNewest + From 6b5bba3211f15bd4599f7fd087f85b2a19bdff46 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 10:10:12 -0800 Subject: [PATCH 002/256] Update dependencies from https://github.com/dotnet/arcade build 20220217.2 (#1651) Microsoft.DotNet.Arcade.Sdk From Version 7.0.0-beta.22111.10 -> To Version 7.0.0-beta.22117.2 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 4 +- eng/common/retain-build.ps1 | 47 +++++++++++++++++++++ eng/common/templates/jobs/jobs.yml | 9 ---- eng/common/templates/steps/retain-build.yml | 28 ++++++++++++ global.json | 2 +- 5 files changed, 78 insertions(+), 12 deletions(-) create mode 100644 eng/common/retain-build.ps1 create mode 100644 eng/common/templates/steps/retain-build.yml diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 225f633989..7839ec91f4 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -3,9 +3,9 @@ - + https://github.com/dotnet/arcade - ff6cc4e9c3eef575f62a33a642ca80e79d27c9bb + 49750c02e63d0ad3a77d035bba7498a0b1acd218 diff --git a/eng/common/retain-build.ps1 b/eng/common/retain-build.ps1 new file mode 100644 index 0000000000..e08fc227b2 --- /dev/null +++ b/eng/common/retain-build.ps1 @@ -0,0 +1,47 @@ + +Param( +[Parameter(Mandatory=$true)][int] $buildId, +[Parameter(Mandatory=$true)][string] $azdoOrgUri, +[Parameter(Mandatory=$true)][string] $azdoProject, +[Parameter(Mandatory=$true)][string] $token +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +. $PSScriptRoot\tools.ps1 + + +function Get-AzDOHeaders( + [string] $token) +{ + $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":${token}")) + $headers = @{"Authorization"="Basic $base64AuthInfo"} + return $headers +} + +function Update-BuildRetention( + [string] $azdoOrgUri, + [string] $azdoProject, + [int] $buildId, + [string] $token) +{ + $headers = Get-AzDOHeaders -token $token + $requestBody = "{ + `"keepForever`": `"true`" + }" + + $requestUri = "${azdoOrgUri}/${azdoProject}/_apis/build/builds/${buildId}?api-version=6.0" + write-Host "Attempting to retain build using the following URI: ${requestUri} ..." + + try { + Invoke-RestMethod -Uri $requestUri -Method Patch -Body $requestBody -Header $headers -contentType "application/json" + Write-Host "Updated retention settings for build ${buildId}." + } + catch { + Write-PipelineTelemetryError -Category "Build" -Message "Failed to update retention settings for build: $_.Exception.Response.StatusDescription" + ExitWithExitCode 1 + } +} + +Update-BuildRetention -azdoOrgUri $azdoOrgUri -azdoProject $azdoProject -buildId $buildId -token $token +ExitWithExitCode 0 diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml index 6976330862..554e71cfc4 100644 --- a/eng/common/templates/jobs/jobs.yml +++ b/eng/common/templates/jobs/jobs.yml @@ -8,10 +8,6 @@ parameters: # Optional: Enable publishing using release pipelines enablePublishUsingPipelines: false - # Optional: Disable component governance detection. In general, component governance - # should be on for all jobs. Use only in the event of issues. - disableComponentGovernance: false - # Optional: Enable running the source-build jobs to build repo from source enableSourceBuild: false @@ -41,11 +37,6 @@ parameters: # Internal resources (telemetry, microbuild) can only be accessed from non-public projects, # and some (Microbuild) should only be applied to non-PR cases for internal builds. -# Sbom related params - enableSbom: true - PackageVersion: 7.0.0 - BuildDropPath: '$(Build.SourcesDirectory)/artifacts' - jobs: - ${{ each job in parameters.jobs }}: - template: ../job/job.yml diff --git a/eng/common/templates/steps/retain-build.yml b/eng/common/templates/steps/retain-build.yml new file mode 100644 index 0000000000..83d97a26a0 --- /dev/null +++ b/eng/common/templates/steps/retain-build.yml @@ -0,0 +1,28 @@ +parameters: + # Optional azure devops PAT with build execute permissions for the build's organization, + # only needed if the build that should be retained ran on a different organization than + # the pipeline where this template is executing from + Token: '' + # Optional BuildId to retain, defaults to the current running build + BuildId: '' + # Azure devops Organization URI for the build in the https://dev.azure.com/ format. + # Defaults to the organization the current pipeline is running on + AzdoOrgUri: '$(System.CollectionUri)' + # Azure devops project for the build. Defaults to the project the current pipeline is running on + AzdoProject: '$(System.TeamProject)' + +steps: + - task: powershell@2 + inputs: + targetType: 'filePath' + filePath: eng/common/retain-build.ps1 + pwsh: true + arguments: > + -AzdoOrgUri: ${{parameters.AzdoOrgUri}} + -AzdoProject ${{parameters.AzdoProject}} + -Token ${{coalesce(parameters.Token, '$env:SYSTEM_ACCESSTOKEN') }} + -BuildId ${{coalesce(parameters.BuildId, '$env:BUILD_ID')}} + displayName: Enable permanent build retention + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + BUILD_ID: $(Build.BuildId) \ No newline at end of file diff --git a/global.json b/global.json index db8966a7e1..cac00d9b34 100644 --- a/global.json +++ b/global.json @@ -9,6 +9,6 @@ "xcopy-msbuild": "16.10.0-preview2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22111.10" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22117.2" } } From c2a46546b69c1c988c69b5db44574e5980c415ee Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Sat, 19 Feb 2022 09:37:56 -0800 Subject: [PATCH 003/256] fix #1647 --- .../Binding/TypeConversionTests.cs | 17 ++++++++++++++++- .../Binding/ArgumentConverter.cs | 6 +++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs index d28d9c7668..3d6e406334 100644 --- a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs +++ b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// 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.Collections; @@ -221,6 +221,21 @@ public void Nullable_bool_parses_as_null_when_the_option_has_not_been_applied() .Be(null); } + [Fact] // https://github.com/dotnet/command-line-api/issues/1647 + public void Generic_option_bool_parses_when_passed_to_non_generic_GetValueForOption() + { + var option = new Option("-b"); + + var cmd = new RootCommand + { + option + }; + + var parseResult = cmd.Parse("-b"); + + parseResult.GetValueForOption((Option)option).Should().Be(true); + } + [Fact] public void By_default_an_option_with_zero_or_one_argument_parses_as_the_argument_string_value() { diff --git a/src/System.CommandLine/Binding/ArgumentConverter.cs b/src/System.CommandLine/Binding/ArgumentConverter.cs index 0cf0819c97..f7e0cba633 100644 --- a/src/System.CommandLine/Binding/ArgumentConverter.cs +++ b/src/System.CommandLine/Binding/ArgumentConverter.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// 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.Collections; @@ -204,8 +204,8 @@ internal static ArgumentConversionResult ConvertIfNeeded( toType, successful.Value, symbolResult.LocalizationResources), - - NoArgumentConversionResult _ when toType == typeof(bool) || toType == typeof(bool?) => + + NoArgumentConversionResult _ when conversionResult.Argument.ValueType == typeof(bool) || conversionResult.Argument.ValueType == typeof(bool?) => Success(conversionResult.Argument, true), NoArgumentConversionResult _ when conversionResult.Argument.Arity.MinimumNumberOfValues > 0 => From a98bee2a7cd3952ebf55ba1aeb135b4124d33a4f Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Fri, 25 Feb 2022 15:51:27 -0800 Subject: [PATCH 004/256] add support ... or, uh, remove non-support ... for OnlyTake(0) --- src/System.CommandLine.Tests/ArgumentTests.cs | 23 +++++++++++++++++++ .../Parsing/ArgumentResult.cs | 5 ---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/System.CommandLine.Tests/ArgumentTests.cs b/src/System.CommandLine.Tests/ArgumentTests.cs index 4b7a88e2d2..24a7ca1b95 100644 --- a/src/System.CommandLine.Tests/ArgumentTests.cs +++ b/src/System.CommandLine.Tests/ArgumentTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.CommandLine.Parsing; +using System.CommandLine.Tests.Utility; using System.IO; using FluentAssertions; using System.Linq; @@ -692,6 +693,28 @@ public void OnlyTake_throws_when_called_twice() .Should() .Be("OnlyTake can only be called once."); } + + [Fact] + public void OnlyTake_can_pass_on_all_tokens() + { + var argument1 = new Argument(result => + { + result.OnlyTake(0); + return null; + }); + var argument2 = new Argument(); + var command = new RootCommand + { + argument1, + argument2 + }; + + var result = command.Parse("1 2 3"); + + result.GetValueForArgument(argument1).Should().BeEmpty(); + + result.GetValueForArgument(argument2).Should().BeEquivalentSequenceTo(1, 2, 3); + } } protected override Symbol CreateSymbol(string name) diff --git a/src/System.CommandLine/Parsing/ArgumentResult.cs b/src/System.CommandLine/Parsing/ArgumentResult.cs index 8d27abb563..27ed866953 100644 --- a/src/System.CommandLine/Parsing/ArgumentResult.cs +++ b/src/System.CommandLine/Parsing/ArgumentResult.cs @@ -64,11 +64,6 @@ public void OnlyTake(int numberOfTokens) throw new InvalidOperationException($"{nameof(OnlyTake)} can only be called once."); } - if (numberOfTokens == 0) - { - return; - } - var passedOnTokensCount = _tokens.Count - numberOfTokens; PassedOnTokens = new List(_tokens.GetRange(numberOfTokens, passedOnTokensCount)); From 05ba968383fd6a1290e9ad844e110a9d68e962a3 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Mon, 28 Feb 2022 13:05:41 +0000 Subject: [PATCH 005/256] Update dependencies from https://github.com/dotnet/arcade build 20220224.4 Microsoft.DotNet.Arcade.Sdk From Version 7.0.0-beta.22117.2 -> To Version 7.0.0-beta.22124.4 --- eng/Version.Details.xml | 4 ++-- global.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 7839ec91f4..9c8592b29c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -3,9 +3,9 @@ - + https://github.com/dotnet/arcade - 49750c02e63d0ad3a77d035bba7498a0b1acd218 + f7136626d0109856df867481219eb7366951985d diff --git a/global.json b/global.json index cac00d9b34..d83c8076e4 100644 --- a/global.json +++ b/global.json @@ -9,6 +9,6 @@ "xcopy-msbuild": "16.10.0-preview2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22117.2" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22124.4" } } From 3cb430c773e946bc8500416d2cc68a745fafd7fc Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 1 Mar 2022 15:44:17 +0100 Subject: [PATCH 006/256] Perf improvements part 6: startup time (#1654) * extend ICommandHandler with synchronous Invoke method * don't build invocation chain if there is no middleware * _executablePath.Value already uses Environment.GetCommandLineArgs()[0] * Command.Validators: allocate when needed * reduce and simplify ValidateCommandResult size to JIT less code in most common scenarios * reduce ParseResultVisitor.Stop size, saves 1 ms of JIT time for simple scenarios * make Token a class again, so JIT does not need to compile specialized methods for it. -3ms for startup! * refactor ParseDirectives to JIT less code when directives are not provided (0.4 ms of JIT time) * refactor GetValueForHandlerParameter to JIT less code for most common case * remove static fields from StringExtensions (they need to be compiled and initialized) * optimize getting Middleware by using Tuple (class) instead of ValueTuple (struct). -4 JIT compilaitons * use Nullable.GetUnderlyingType instead of custom implementation (and compile less) * don't use Lazy, -4 compilations * reduce the number of types derived from ArgumentConversionResult to reduce the number of compilations and type loading * remove RootCommandNode to reduce the number of types * remove another Lazy (the code it uses needed to be compiled the first time ArgumentConverter was used, no matter if we wanted to create a list or not) * add net6.0 to benchmarks project * address code review feedback --- ...tionBinder_api_is_not_changed.approved.txt | 1 + ...ommandLine_api_is_not_changed.approved.txt | 3 +- .../System.CommandLine.Benchmarks.csproj | 4 +- .../CommandHandlerSourceGenerator.cs | 3 + .../HostingHandlerTest.cs | 8 + .../ParameterBindingTests.cs | 4 + .../ModelBindingCommandHandler.cs | 2 + src/System.CommandLine/ArgumentArity.cs | 12 +- .../Binding/ArgumentConversionResult.cs | 64 +++++- .../Binding/ArgumentConversionResultType.cs | 16 ++ .../ArgumentConverter.DefaultValues.cs | 21 +- .../Binding/ArgumentConverter.cs | 72 +++--- .../Binding/BindingContext.cs | 4 +- .../FailedArgumentConversionArityResult.cs | 12 - .../Binding/FailedArgumentConversionResult.cs | 20 -- .../FailedArgumentTypeConversionResult.cs | 42 ---- .../Binding/IValueDescriptor.cs | 2 - .../MissingArgumentConversionResult.cs | 12 - .../Binding/NoArgumentConversionResult.cs | 12 - .../SuccessfulArgumentConversionResult.cs | 16 -- .../TooManyArgumentsConversionResult.cs | 12 - .../Binding/TypeExtensions.cs | 21 +- .../Builder/CommandLineBuilder.cs | 30 ++- src/System.CommandLine/Command.cs | 12 +- .../Completions/CompletionContext.cs | 6 +- src/System.CommandLine/Handler.cs | 7 +- src/System.CommandLine/Help/VersionOption.cs | 2 +- .../Invocation/AnonymousCommandHandler.cs | 32 +-- .../Invocation/ICommandHandler.cs | 9 +- .../Invocation/InvocationPipeline.cs | 42 +++- .../Invocation/SuggestDirectiveResult.cs | 2 +- .../Parsing/ArgumentResult.cs | 20 +- .../Parsing/CommandResult.cs | 7 +- .../Parsing/DirectiveNode.cs | 2 +- .../Parsing/OptionResult.cs | 6 +- .../Parsing/ParseOperation.cs | 16 +- .../Parsing/ParseResultExtensions.cs | 15 +- .../Parsing/ParseResultVisitor.cs | 213 +++++++++--------- .../Parsing/RootCommandNode.cs | 14 -- .../Parsing/StringExtensions.cs | 20 +- .../Parsing/SymbolResultExtensions.cs | 2 +- src/System.CommandLine/Parsing/Token.cs | 6 +- src/System.CommandLine/RootCommand.cs | 38 +--- 43 files changed, 408 insertions(+), 456 deletions(-) create mode 100644 src/System.CommandLine/Binding/ArgumentConversionResultType.cs delete mode 100644 src/System.CommandLine/Binding/FailedArgumentConversionArityResult.cs delete mode 100644 src/System.CommandLine/Binding/FailedArgumentConversionResult.cs delete mode 100644 src/System.CommandLine/Binding/FailedArgumentTypeConversionResult.cs delete mode 100644 src/System.CommandLine/Binding/MissingArgumentConversionResult.cs delete mode 100644 src/System.CommandLine/Binding/NoArgumentConversionResult.cs delete mode 100644 src/System.CommandLine/Binding/SuccessfulArgumentConversionResult.cs delete mode 100644 src/System.CommandLine/Binding/TooManyArgumentsConversionResult.cs delete mode 100644 src/System.CommandLine/Parsing/RootCommandNode.cs 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