From e404363fc15d1aed97edfcf6b42f6f12cdc90cdf Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Mon, 11 May 2020 21:48:15 -0400 Subject: [PATCH 1/2] :bug: Fix string param conversion for bigints When passing a bigint literal, e.g., `7n` as a bare string to a parameter of type [string], the parameter binder was incorrectly using the direct ToString() conversion unlike other numeric literals, which store the original TokenText in order to properly retrieve the original string that was entered at the command line. --- .../engine/LanguagePrimitives.cs | 10 ++++- .../engine/parser/Compiler.cs | 4 +- .../engine/runtime/ScriptBlockToPowerShell.cs | 4 +- .../Parser/ParameterBinding.Tests.ps1 | 41 +++++++++++++++++++ 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 54a9d568455..0d903e43bf7 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -14,6 +14,7 @@ using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; +using System.Numerics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; @@ -3307,6 +3308,11 @@ private static string ConvertNumericToString(object valueToConvert, return sgl.ToString(SinglePrecision, numberFormat); } + if (valueToConvert is BigInteger b) + { + return b.ToString(numberFormat); + } + return (string)Convert.ChangeType(valueToConvert, resultType, CultureInfo.InvariantCulture.NumberFormat); } catch (Exception e) @@ -4380,7 +4386,8 @@ internal static ConversionRank GetConversionRank(Type fromType, Type toType) typeof(Int16), typeof(Int32), typeof(Int64), typeof(UInt16), typeof(UInt32), typeof(UInt64), typeof(sbyte), typeof(byte), - typeof(Single), typeof(double), typeof(decimal) + typeof(Single), typeof(double), typeof(decimal), + typeof(System.Numerics.BigInteger) }; private static Type[] s_integerTypes = new Type[] { @@ -4415,6 +4422,7 @@ internal static void RebuildConversionCache() CacheConversion(typeofNull, type, LanguagePrimitives.ConvertNullToNumeric, ConversionRank.NullToValue); } + CacheConversion(typeof(BigInteger), typeofString, ConvertNumericToString, ConversionRank.NumericString); CacheConversion(typeof(Int16), typeofBool, ConvertInt16ToBool, ConversionRank.Language); CacheConversion(typeof(Int32), typeofBool, ConvertInt32ToBool, ConversionRank.Language); CacheConversion(typeof(Int64), typeofBool, ConvertInt64ToBool, ConversionRank.Language); diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 229aa3a5887..ac40050d4ab 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -4171,7 +4171,9 @@ public object VisitCommand(CommandAst commandAst) private Expression GetCommandArgumentExpression(CommandElementAst element) { var constElement = element as ConstantExpressionAst; - if (constElement != null && LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constElement.StaticType))) + if (constElement != null + && (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constElement.StaticType)) + || constElement.StaticType == typeof(System.Numerics.BigInteger))) { var commandArgumentText = constElement.Extent.Text; if (!commandArgumentText.Equals(constElement.Value.ToString(), StringComparison.Ordinal)) diff --git a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs index 66b5f63da82..e2409eba626 100644 --- a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs +++ b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs @@ -643,7 +643,9 @@ private void ConvertCommand(CommandAst commandAst, bool isTrustedInput) { var constantExprAst = ast as ConstantExpressionAst; object argument; - if (constantExprAst != null && LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constantExprAst.StaticType))) + if (constantExprAst != null + && (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constantExprAst.StaticType)) + || constantExprAst.StaticType == typeof(System.Numerics.BigInteger))) { var commandArgumentText = constantExprAst.Extent.Text; argument = constantExprAst.Value; diff --git a/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 b/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 index a480997271f..7fd26912ead 100644 --- a/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 +++ b/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 @@ -425,3 +425,44 @@ Describe "Custom type conversion in parameter binding" -Tags 'Feature' { } } } + +Describe 'Roundtrippable Conversions for Bare-string Numeric Literals passed to [string] Parameters' -Tags CI { + + BeforeAll { + $TestValues = @( + @{ Argument = "34uy" } + @{ Argument = "48y" } + @{ Argument = "8s" } + @{ Argument = "49us" } + @{ Argument = "26" } + @{ Argument = "28u" } + @{ Argument = "24l" } + @{ Argument = "32ul" } + @{ Argument = "20d" } + @{ Argument = "6n" } + ) + + function Test-SimpleStringValue([string] $Value) { $Value } + function Test-AdvancedStringValue { + [CmdletBinding()] + param( + [string] + $Value + ) + + $Value + } + } + + It 'should correctly convert back to string in simple functions' -TestCases $TestValues { + param($Argument) + + Invoke-Expression "Test-SimpleStringValue -Value $Argument" | Should -BeExactly $Argument + } + + It 'should correctly convert back to string in advanced functions' -TestCases $TestValues { + param($Argument) + + Invoke-Expression "Test-AdvancedStringValue -Value $Argument" | Should -BeExactly $Argument + } +} From 4004bcdc87671372ce0918abe9e17ecdf55353f1 Mon Sep 17 00:00:00 2001 From: "Joel Sallow (/u/ta11ow)" <32407840+vexx32@users.noreply.github.com> Date: Tue, 12 May 2020 13:06:57 -0400 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Dongbo Wang --- src/System.Management.Automation/engine/LanguagePrimitives.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 0d903e43bf7..50b2e7950f7 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -4387,7 +4387,7 @@ internal static ConversionRank GetConversionRank(Type fromType, Type toType) typeof(UInt16), typeof(UInt32), typeof(UInt64), typeof(sbyte), typeof(byte), typeof(Single), typeof(double), typeof(decimal), - typeof(System.Numerics.BigInteger) + typeof(BigInteger) }; private static Type[] s_integerTypes = new Type[] { @@ -4422,7 +4422,6 @@ internal static void RebuildConversionCache() CacheConversion(typeofNull, type, LanguagePrimitives.ConvertNullToNumeric, ConversionRank.NullToValue); } - CacheConversion(typeof(BigInteger), typeofString, ConvertNumericToString, ConversionRank.NumericString); CacheConversion(typeof(Int16), typeofBool, ConvertInt16ToBool, ConversionRank.Language); CacheConversion(typeof(Int32), typeofBool, ConvertInt32ToBool, ConversionRank.Language); CacheConversion(typeof(Int64), typeofBool, ConvertInt64ToBool, ConversionRank.Language);