Parse numeric strings as numbers again during conversions (#8681)
This commit is contained in:
parent
d032cb2c87
commit
130298bfae
|
@ -2885,6 +2885,34 @@ namespace System.Management.Automation
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to use Parser.ScanNumber to get the value of a numeric string.
|
||||
/// </summary>
|
||||
/// <param name="strToConvert">The string to convert to a number.</param>
|
||||
/// <param name="resultType">The resulting value type to convert to.</param>
|
||||
/// <param name="result">The resulting numeric value.</param>
|
||||
/// <returns>
|
||||
/// True if the parse succeeds, false if a parse exception arises.
|
||||
/// In all other cases, an exception will be thrown.
|
||||
/// </returns>
|
||||
private static bool TryScanNumber(string strToConvert, Type resultType, out object result)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = Convert.ChangeType(
|
||||
Parser.ScanNumber(strToConvert, resultType, shouldTryCoercion: false),
|
||||
resultType,
|
||||
System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Parse or convert failed
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static object ConvertStringToInteger(object valueToConvert,
|
||||
Type resultType,
|
||||
bool recursion,
|
||||
|
@ -2907,7 +2935,14 @@ namespace System.Management.Automation
|
|||
TypeConverter integerConverter = LanguagePrimitives.GetIntegerSystemConverter(resultType);
|
||||
try
|
||||
{
|
||||
return integerConverter.ConvertFrom(strToConvert);
|
||||
if (TryScanNumber(strToConvert, resultType, out object result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return integerConverter.ConvertFrom(strToConvert);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -2946,8 +2981,9 @@ namespace System.Management.Automation
|
|||
TypeTable backupTable)
|
||||
{
|
||||
Diagnostics.Assert(valueToConvert is string, "Value to convert must be a string");
|
||||
var strToConvert = valueToConvert as string;
|
||||
|
||||
if (((string)valueToConvert).Length == 0)
|
||||
if (strToConvert.Length == 0)
|
||||
{
|
||||
typeConversion.WriteLine("Returning numeric zero.");
|
||||
// This is not wrapped in a try/catch because it can't fail.
|
||||
|
@ -2957,8 +2993,15 @@ namespace System.Management.Automation
|
|||
typeConversion.WriteLine("Converting to decimal.");
|
||||
try
|
||||
{
|
||||
return Convert.ChangeType(valueToConvert, resultType,
|
||||
System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
|
||||
typeConversion.WriteLine("Parsing string value to account for multipliers and type suffixes");
|
||||
if (TryScanNumber(strToConvert, resultType, out object result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Convert.ChangeType(strToConvert, resultType, CultureInfo.InvariantCulture.NumberFormat);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -2967,7 +3010,7 @@ namespace System.Management.Automation
|
|||
{
|
||||
try
|
||||
{
|
||||
return ConvertNumericThroughDouble(valueToConvert, resultType);
|
||||
return ConvertNumericThroughDouble(strToConvert, resultType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -2977,7 +3020,7 @@ namespace System.Management.Automation
|
|||
|
||||
throw new PSInvalidCastException("InvalidCastFromStringToDecimal", e,
|
||||
ExtendedTypeSystem.InvalidCastExceptionWithInnerException,
|
||||
valueToConvert.ToString(), resultType.ToString(), e.Message);
|
||||
strToConvert, resultType.ToString(), e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2989,8 +3032,9 @@ namespace System.Management.Automation
|
|||
TypeTable backupTable)
|
||||
{
|
||||
Diagnostics.Assert(valueToConvert is string, "Value to convert must be a string");
|
||||
var strToConvert = valueToConvert as string;
|
||||
|
||||
if (((string)valueToConvert).Length == 0)
|
||||
if (strToConvert.Length == 0)
|
||||
{
|
||||
typeConversion.WriteLine("Returning numeric zero.");
|
||||
// This is not wrapped in a try/catch because it can't fail.
|
||||
|
@ -3000,15 +3044,23 @@ namespace System.Management.Automation
|
|||
typeConversion.WriteLine("Converting to double or single.");
|
||||
try
|
||||
{
|
||||
return Convert.ChangeType(valueToConvert, resultType,
|
||||
System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
|
||||
typeConversion.WriteLine("Parsing string value to account for multipliers and type suffixes");
|
||||
|
||||
if (TryScanNumber(strToConvert, resultType, out object result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Convert.ChangeType(strToConvert, resultType, CultureInfo.InvariantCulture.NumberFormat);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
typeConversion.WriteLine("Exception converting to double or single: \"{0}\".", e.Message);
|
||||
throw new PSInvalidCastException("InvalidCastFromStringToDoubleOrSingle", e,
|
||||
ExtendedTypeSystem.InvalidCastExceptionWithInnerException,
|
||||
valueToConvert.ToString(), resultType.ToString(), e.Message);
|
||||
strToConvert, resultType.ToString(), e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ namespace System.Management.Automation.Language
|
|||
}
|
||||
|
||||
// This helper routine is used from the runtime to convert a string to a number.
|
||||
internal static object ScanNumber(string str, Type toType)
|
||||
internal static object ScanNumber(string str, Type toType, bool shouldTryCoercion = true)
|
||||
{
|
||||
str = str.Trim();
|
||||
if (str.Length == 0)
|
||||
|
@ -249,11 +249,17 @@ namespace System.Management.Automation.Language
|
|||
|
||||
if (token == null || !tokenizer.IsAtEndOfScript(token.Extent))
|
||||
{
|
||||
// We call ConvertTo, primarily because we expect it will throw an exception,
|
||||
// but it's possible it could succeed, e.g. if the string had commas, our lexer
|
||||
// will fail, but Convert.ChangeType could succeed.
|
||||
|
||||
return LanguagePrimitives.ConvertTo(str, toType, CultureInfo.InvariantCulture);
|
||||
if (shouldTryCoercion)
|
||||
{
|
||||
// We call ConvertTo, primarily because we expect it will throw an exception,
|
||||
// but it's possible it could succeed, e.g. if the string had commas, our lexer
|
||||
// will fail, but Convert.ChangeType could succeed.
|
||||
return LanguagePrimitives.ConvertTo(str, toType, CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ParseException();
|
||||
}
|
||||
}
|
||||
|
||||
return token.Value;
|
||||
|
|
|
@ -477,7 +477,7 @@ namespace System.Management.Automation.Language
|
|||
|
||||
return BindingRestrictions.GetExpressionRestriction(
|
||||
Expression.Block(
|
||||
new[] {tmp},
|
||||
new[] { tmp },
|
||||
Expression.Assign(tmp, ExpressionCache.GetExecutionContextFromTLS),
|
||||
test));
|
||||
}
|
||||
|
@ -2587,7 +2587,11 @@ namespace System.Management.Automation.Language
|
|||
toType = typeof(int);
|
||||
}
|
||||
|
||||
return Expression.Call(CachedReflectionInfo.Parser_ScanNumber, expr.Cast(typeof(string)), Expression.Constant(toType, typeof(Type)));
|
||||
return Expression.Call(
|
||||
CachedReflectionInfo.Parser_ScanNumber,
|
||||
expr.Cast(typeof(string)),
|
||||
Expression.Constant(toType, typeof(Type)),
|
||||
Expression.Constant(true));
|
||||
}
|
||||
|
||||
private static DynamicMetaObject GetArgAsNumericOrPrimitive(DynamicMetaObject arg, Type targetType)
|
||||
|
@ -7414,7 +7418,7 @@ namespace System.Management.Automation.Language
|
|||
// ([pscustomobject]@{ foo = 'bar' }).Where({1})
|
||||
if (string.Equals(methodName, "Where", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var enumerator = (new object[] {obj}).GetEnumerator();
|
||||
var enumerator = (new object[] { obj }).GetEnumerator();
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
|
@ -7430,7 +7434,7 @@ namespace System.Management.Automation.Language
|
|||
|
||||
if (string.Equals(methodName, "Foreach", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var enumerator = (new object[] {obj}).GetEnumerator();
|
||||
var enumerator = (new object[] { obj }).GetEnumerator();
|
||||
return EnumerableOps.ForEach(enumerator, args[0], Utils.EmptyArray<object>());
|
||||
}
|
||||
|
||||
|
|
|
@ -486,4 +486,35 @@ Describe 'method conversion' -Tags 'CI' {
|
|||
$n = [N]::new()
|
||||
{ [System.Management.Automation.LanguagePrimitives]::ConvertTo($n.GetC, [Func[[int], [object]]]) } | Should -Throw -ErrorId "PSInvalidCastException"
|
||||
}
|
||||
|
||||
$TestCases = @(
|
||||
@{ Number = "100y"; Value = "100"; Type = [int] }
|
||||
@{ Number = "100uy"; Value = "100"; Type = [double] }
|
||||
@{ Number = "1200u"; Value = "1200"; Type = [short] }
|
||||
@{ Number = "1200L"; Value = "1200"; Type = [int] }
|
||||
@{ Number = "127ul"; Value = "127"; Type = [ulong] }
|
||||
@{ Number = "127d"; Value = "127"; Type = [byte] }
|
||||
@{ Number = "127s"; Value = "127"; Type = [sbyte] }
|
||||
@{ Number = "127y"; Value = "127"; Type = [uint] }
|
||||
)
|
||||
It "Correctly casts <Number> to value <Value> as type <Type>" -TestCases $TestCases {
|
||||
param($Number, $Value, $Type)
|
||||
|
||||
$Result = $Number -as $Type
|
||||
$Result | Should -Be $Value
|
||||
$Result | Should -BeOfType $Type
|
||||
}
|
||||
|
||||
$TestCases = @(
|
||||
@{ Number = "200y" }
|
||||
@{ Number = "300uy" }
|
||||
@{ Number = "70000us" }
|
||||
@{ Number = "40000s" }
|
||||
)
|
||||
It "Fails to cast invalid PowerShell-Style suffixed numeral <Number>" -TestCases $TestCases {
|
||||
param($Number)
|
||||
|
||||
$Result = $Number -as [int]
|
||||
$Result | Should -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue