Skip to content

Commit

Permalink
Ensure that INumberBase implements IUtf8SpanFormattable (#88840)
Browse files Browse the repository at this point in the history
* Ensure that INumberBase implements IUtf8SpanFormattable

* Ensure we return the rented buffers in the IUtf8SpanFormattable.TryFormat DIM

* Remember to slice the utf16Destination buffer and ensure we throw if we couldn't transcode back to valid UTF-8 in the DIM

* Update src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* Update src/libraries/System.Private.CoreLib/src/Resources/Strings.resx

Co-authored-by: Dan Moseley <danmose@microsoft.com>

---------

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
Co-authored-by: Dan Moseley <danmose@microsoft.com>
  • Loading branch information
3 people committed Jul 14, 2023
1 parent d51a8d5 commit 1177698
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2608,6 +2608,9 @@
<data name="InvalidOperation_HandleIsNotPinned" xml:space="preserve">
<value>Handle is not pinned.</value>
</data>
<data name="InvalidOperation_InvalidUtf8" xml:space="preserve">
<value>Formatted string contains characters not representable as valid UTF-8.</value>
</data>
<data name="InvalidOperation_HashInsertFailed" xml:space="preserve">
<value>Hashtable insert failed. Load factor too high. The most common cause is multiple threads writing to the Hashtable simultaneously.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public interface INumberBase<TSelf>
ISubtractionOperators<TSelf, TSelf, TSelf>,
IUnaryPlusOperators<TSelf, TSelf>,
IUnaryNegationOperators<TSelf, TSelf>,
IUtf8SpanFormattable,
IUtf8SpanParsable<TSelf>
where TSelf : INumberBase<TSelf>?
{
Expand Down Expand Up @@ -297,7 +298,7 @@ static virtual TSelf Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFor
if (textMaxCharCount < 256)
{
utf16TextArray = null;
utf16Text = stackalloc char[512];
utf16Text = stackalloc char[256];
}
else
{
Expand Down Expand Up @@ -425,7 +426,7 @@ static virtual bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IF
if (textMaxCharCount < 256)
{
utf16TextArray = null;
utf16Text = stackalloc char[512];
utf16Text = stackalloc char[256];
}
else
{
Expand Down Expand Up @@ -456,6 +457,60 @@ static virtual bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IF
return succeeded;
}

bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
{
char[]? utf16DestinationArray;
scoped Span<char> utf16Destination;
int destinationMaxCharCount = Encoding.UTF8.GetMaxCharCount(utf8Destination.Length);

if (destinationMaxCharCount < 256)
{
utf16DestinationArray = null;
utf16Destination = stackalloc char[256];
}
else
{
utf16DestinationArray = ArrayPool<char>.Shared.Rent(destinationMaxCharCount);
utf16Destination = utf16DestinationArray.AsSpan(0, destinationMaxCharCount);
}

if (!TryFormat(utf16Destination, out int charsWritten, format, provider))
{
if (utf16DestinationArray != null)
{
// Return rented buffers if necessary
ArrayPool<char>.Shared.Return(utf16DestinationArray);
}

bytesWritten = 0;
return false;
}

// Make sure we slice the buffer to just the characters written
utf16Destination = utf16Destination.Slice(0, charsWritten);

OperationStatus utf8DestinationStatus = Utf8.FromUtf16(utf16Destination, utf8Destination, out _, out bytesWritten, replaceInvalidSequences: false);

if (utf16DestinationArray != null)
{
// Return rented buffers if necessary
ArrayPool<char>.Shared.Return(utf16DestinationArray);
}

if (utf8DestinationStatus == OperationStatus.Done)
{
return true;
}

if (utf8DestinationStatus != OperationStatus.DestinationTooSmall)
{
ThrowHelper.ThrowInvalidOperationException_InvalidUtf8();
}

bytesWritten = 0;
return false;
}

static TSelf IUtf8SpanParsable<TSelf>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider)
{
// Convert text using stackalloc for <= 256 characters and ArrayPool otherwise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,12 @@ internal static void ThrowArraySegmentCtorValidationFailedExceptions(Array? arra
throw GetArraySegmentCtorValidationFailedException(array, offset, count);
}

[DoesNotReturn]
internal static void ThrowInvalidOperationException_InvalidUtf8()
{
throw new InvalidOperationException(SR.InvalidOperation_InvalidUtf8);
}

[DoesNotReturn]
internal static void ThrowFormatException_BadFormatSpecifier()
{
Expand Down
3 changes: 2 additions & 1 deletion src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10793,7 +10793,7 @@ public partial interface IMultiplyOperators<TSelf, TOther, TResult> where TSelf
static virtual TResult operator checked *(TSelf left, TOther right) { throw null; }
static abstract TResult operator *(TSelf left, TOther right);
}
public partial interface INumberBase<TSelf> : System.IEquatable<TSelf>, System.IFormattable, System.IParsable<TSelf>, System.ISpanFormattable, System.ISpanParsable<TSelf>, System.Numerics.IAdditionOperators<TSelf, TSelf, TSelf>, System.Numerics.IAdditiveIdentity<TSelf, TSelf>, System.Numerics.IDecrementOperators<TSelf>, System.Numerics.IDivisionOperators<TSelf, TSelf, TSelf>, System.Numerics.IEqualityOperators<TSelf, TSelf, bool>, System.Numerics.IIncrementOperators<TSelf>, System.Numerics.IMultiplicativeIdentity<TSelf, TSelf>, System.Numerics.IMultiplyOperators<TSelf, TSelf, TSelf>, System.Numerics.ISubtractionOperators<TSelf, TSelf, TSelf>, System.Numerics.IUnaryNegationOperators<TSelf, TSelf>, System.Numerics.IUnaryPlusOperators<TSelf, TSelf>, System.IUtf8SpanParsable<TSelf> where TSelf : System.Numerics.INumberBase<TSelf>?
public partial interface INumberBase<TSelf> : System.IEquatable<TSelf>, System.IFormattable, System.IParsable<TSelf>, System.ISpanFormattable, System.ISpanParsable<TSelf>, System.Numerics.IAdditionOperators<TSelf, TSelf, TSelf>, System.Numerics.IAdditiveIdentity<TSelf, TSelf>, System.Numerics.IDecrementOperators<TSelf>, System.Numerics.IDivisionOperators<TSelf, TSelf, TSelf>, System.Numerics.IEqualityOperators<TSelf, TSelf, bool>, System.Numerics.IIncrementOperators<TSelf>, System.Numerics.IMultiplicativeIdentity<TSelf, TSelf>, System.Numerics.IMultiplyOperators<TSelf, TSelf, TSelf>, System.Numerics.ISubtractionOperators<TSelf, TSelf, TSelf>, System.Numerics.IUnaryNegationOperators<TSelf, TSelf>, System.Numerics.IUnaryPlusOperators<TSelf, TSelf>, System.IUtf8SpanFormattable, System.IUtf8SpanParsable<TSelf> where TSelf : System.Numerics.INumberBase<TSelf>?
{
static abstract TSelf One { get; }
static abstract int Radix { get; }
Expand Down Expand Up @@ -10835,6 +10835,7 @@ static virtual TSelf CreateTruncating<TOther>(TOther value)
static virtual TSelf Parse(System.ReadOnlySpan<byte> utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; }
static abstract TSelf Parse(System.ReadOnlySpan<char> s, System.Globalization.NumberStyles style, System.IFormatProvider? provider);
static abstract TSelf Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider);
bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
static TSelf System.IUtf8SpanParsable<TSelf>.Parse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider) { throw null; }
static bool System.IUtf8SpanParsable<TSelf>.TryParse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) { throw null; }
protected static abstract bool TryConvertFromChecked<TOther>(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result)
Expand Down

0 comments on commit 1177698

Please sign in to comment.