Add new NumberStyles option to stop parsing on invalid character by tannergooding · Pull Request #130210 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
68a36cd
Updating the numeric parsing code to support AllowInvalidTrailingChar…
tannergooding Dec 18, 2025
6f0aa99
Add tests for NumberStyles.AllowTrailingInvalidCharacters (#124054)
Copilot Feb 5, 2026
8d565e8
Fix AllowTrailingInvalidCharacters parsing edge cases
tannergooding Jul 5, 2026
6a084e3
Address PR #130210 review feedback for AllowTrailingInvalidCharacters
tannergooding Jul 5, 2026
50d2219
Ensure trailing nulls are consumed and counted
tannergooding Jul 6, 2026
6581a82
Ensure we handle the case where only trailing nulls remain
tannergooding Jul 6, 2026
27d8f9c
Fix the Int32 AllowTrailingInvalidCharacter test
tannergooding Jul 6, 2026
e80f5b2
Ensure the fast path can light up for AllowTrailingInvalidCharacters
tannergooding Jul 6, 2026
5d727d9
Merge branch 'main' into fix-87171
tannergooding Jul 8, 2026
9c4585c
Potential fix for pull request finding
tannergooding Jul 8, 2026
342114b
Consume trailing whitespace in BigInteger hex/binary parsing with All…
tannergooding Jul 8, 2026
08ac386
Potential fix for pull request finding
tannergooding Jul 8, 2026
091e2af
Consume surrounding whitespace for Infinity/NaN with AllowTrailingInv…
tannergooding Jul 8, 2026
fa4f72c
Potential fix for pull request finding
tannergooding Jul 8, 2026
ff266df
Address PR feedback: hex-float trailing-invalid, binary docs, UTF-8 t…
tannergooding Jul 8, 2026
1c66a14
Address PR feedback: culture-invariant FP test data and BigInteger UT…
tannergooding Jul 8, 2026
4befc6a
Address PR feedback: remove NoInlining, enable hex-float Infinity/NaN…
tannergooding Jul 8, 2026
2f55b9d
Fix hex-float special-value matching for UTF-8 and BFloat16 tests
tannergooding Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 36 additions & 23 deletions src/libraries/Common/src/System/Number.Parsing.Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@
<value>Invalid handle.</value>
</data>
<data name="Arg_InvalidHexBinaryStyle" xml:space="preserve">
<value>With the AllowHexSpecifier or AllowBinarySpecifier bit set in the enum bit field, the only other valid bits that can be combined into the enum value must be AllowLeadingWhite and AllowTrailingWhite.</value>
<value>With the AllowHexSpecifier or AllowBinarySpecifier bit set in the enum bit field, the only other valid bits that can be combined into the enum value must be AllowLeadingWhite, AllowTrailingWhite, and AllowTrailingInvalidCharacters.</value>
</data>
<data name="Arg_InvalidNeutralResourcesLanguage_Asm_Culture" xml:space="preserve">
<value>The NeutralResourcesLanguageAttribute on the assembly "{0}" specifies an invalid culture name: "{1}".</value>
Expand Down Expand Up @@ -4501,4 +4501,4 @@
<data name="InvalidOperation_NoElements" xml:space="preserve">
<value>Sequence contains no elements.</value>
</data>
</root>
</root>
33 changes: 24 additions & 9 deletions src/libraries/System.Private.CoreLib/src/System/Byte.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,13 @@ public static byte Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out byte result)
{
NumberFormatInfo.ValidateParseStyleInteger(style);

if (s is null)
{
result = 0;
return false;
}
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out byte result)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

public override string ToString()
Expand Down Expand Up @@ -1104,6 +1098,27 @@ static bool INumberBase<byte>.TryConvertToTruncating<TOther>(byte value, [MaybeN
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(string, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out byte result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{char}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out byte result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{byte}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out byte result, out int bytesConsumed)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK;
}

//
// IParsable
//
Expand Down Expand Up @@ -1176,7 +1191,7 @@ public static byte Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style = Numbe
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out byte result)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
Expand Down
39 changes: 39 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Char.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1959,6 +1959,45 @@ static bool INumberBase<char>.TryConvertToTruncating<TOther>(char value, [MaybeN

static bool INumberBase<char>.TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out char result) => TryParse(s, out result);

static bool INumberBase<char>.TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out char result, out int charsConsumed)
{
if (TryParse(s, out result))
{
charsConsumed = 1;
return true;
}

charsConsumed = 0;
return false;
}

static bool INumberBase<char>.TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out char result, out int charsConsumed)
{
if (TryParse(s, out result))
{
charsConsumed = 1;
return true;
}

charsConsumed = 0;
return false;
}

static bool INumberBase<char>.TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out char result, out int bytesConsumed)
{
if (Rune.DecodeFromUtf8(utf8Text, out Rune rune, out bytesConsumed) != Buffers.OperationStatus.Done ||
bytesConsumed != utf8Text.Length ||
!rune.IsBmp)
{
result = '\0';
bytesConsumed = 0;
return false;
}

result = (char)rune.Value;
return true;
}

//
// IParsable
//
Expand Down
33 changes: 24 additions & 9 deletions src/libraries/System.Private.CoreLib/src/System/Decimal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -551,19 +551,13 @@ public static decimal Parse(ReadOnlySpan<char> s, NumberStyles style = NumberSty
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out decimal result)
{
NumberFormatInfo.ValidateParseStyleDecimal(style);

if (s == null)
{
result = 0;
return false;
}
return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out decimal result)
{
NumberFormatInfo.ValidateParseStyleDecimal(style);
return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

// Returns a binary representation of a Decimal. The return value is an
Expand Down Expand Up @@ -1781,6 +1775,27 @@ private static bool TryConvertTo<TOther>(decimal value, [MaybeNullWhen(false)] o
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(string, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out decimal result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleDecimal(style);
return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{char}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out decimal result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleDecimal(style);
return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{byte}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out decimal result, out int bytesConsumed)
{
NumberFormatInfo.ValidateParseStyleDecimal(style);
return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK;
}

//
// IParsable
//
Expand Down Expand Up @@ -1820,7 +1835,7 @@ public static decimal Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style = Nu
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out decimal result)
{
NumberFormatInfo.ValidateParseStyleDecimal(style);
return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
Expand Down
33 changes: 24 additions & 9 deletions src/libraries/System.Private.CoreLib/src/System/Double.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,19 +427,13 @@ public static double Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyl
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);

if (s == null)
{
result = 0;
return false;
}
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result);
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _);
}

public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out double result)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result);
return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out _);
}

//
Expand Down Expand Up @@ -1487,6 +1481,27 @@ private static bool TryConvertTo<TOther>(double value, [MaybeNullWhen(false)] ou
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(string, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed);
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{char}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out double result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed);
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{byte}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out double result, out int bytesConsumed)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed);
}

//
// IParsable
//
Expand Down Expand Up @@ -2292,7 +2307,7 @@ public static double Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style = Num
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out double result)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result);
return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _);
}

/// <inheritdoc cref="IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Private.CoreLib/src/System/Enum.cs
Loading
Loading