Skip to content

Implement IUtf8SpanFormattable on Version #84556

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 23 additions & 6 deletions src/libraries/System.Private.CoreLib/src/System/Version.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers.Text;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System
{
Expand All @@ -16,7 +19,7 @@ namespace System

[Serializable]
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public sealed class Version : ICloneable, IComparable, IComparable<Version?>, IEquatable<Version?>, ISpanFormattable
public sealed class Version : ICloneable, IComparable, IComparable<Version?>, IEquatable<Version?>, ISpanFormattable, IUtf8SpanFormattable
{
// AssemblyName depends on the order staying the same
private readonly int _Major; // Do not rename (binary serialization)
Expand Down Expand Up @@ -177,10 +180,15 @@ string IFormattable.ToString(string? format, IFormatProvider? formatProvider) =>
ToString();

public bool TryFormat(Span<char> destination, out int charsWritten) =>
TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
TryFormatCore(destination, DefaultFormatFieldCount, out charsWritten);

public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten)
public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten) =>
TryFormatCore(destination, fieldCount, out charsWritten);

private bool TryFormatCore<TChar>(Span<TChar> destination, int fieldCount, out int charsWritten) where TChar : unmanaged, IBinaryInteger<TChar>
{
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));

switch ((uint)fieldCount)
{
case > 4:
Expand Down Expand Up @@ -211,7 +219,7 @@ static void ThrowArgumentException(string failureUpperBound) =>
return false;
}

destination[0] = '.';
destination[0] = TChar.CreateTruncating('.');
destination = destination.Slice(1);
totalCharsWritten++;
}
Expand All @@ -224,7 +232,12 @@ static void ThrowArgumentException(string failureUpperBound) =>
_ => _Revision
};

if (!((uint)value).TryFormat(destination, out int valueCharsWritten))
int valueCharsWritten;
bool formatted = typeof(TChar) == typeof(char) ?
((uint)value).TryFormat(MemoryMarshal.Cast<TChar, char>(destination), out valueCharsWritten) :
Utf8Formatter.TryFormat((uint)value, MemoryMarshal.Cast<TChar, byte>(destination), out valueCharsWritten); // TODO https://github.com/dotnet/runtime/issues/84527: Use UInt32's IUtf8SpanFormattable when available

if (!formatted)
{
charsWritten = 0;
return false;
Expand All @@ -240,7 +253,11 @@ static void ThrowArgumentException(string failureUpperBound) =>

bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
// format and provider are ignored.
TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
TryFormatCore(destination, DefaultFormatFieldCount, out charsWritten);

bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
// format and provider are ignored.
TryFormatCore(utf8Destination, DefaultFormatFieldCount, out bytesWritten);

private int DefaultFormatFieldCount =>
_Build == -1 ? 2 :
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 @@ -7058,7 +7058,7 @@ protected ValueType() { }
public override int GetHashCode() { throw null; }
public override string? ToString() { throw null; }
}
public sealed partial class Version : System.ICloneable, System.IComparable, System.IComparable<System.Version?>, System.IEquatable<System.Version?>, System.IFormattable, System.ISpanFormattable
public sealed partial class Version : System.ICloneable, System.IComparable, System.IComparable<System.Version?>, System.IEquatable<System.Version?>, System.IFormattable, System.ISpanFormattable, System.IUtf8SpanFormattable
{
public Version() { }
public Version(int major, int minor) { }
Expand Down Expand Up @@ -7087,6 +7087,7 @@ public Version(string version) { }
public static System.Version Parse(string input) { throw null; }
string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; }
bool System.ISpanFormattable.TryFormat(System.Span<char> destination, out int charsWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
public override string ToString() { throw null; }
public string ToString(int fieldCount) { throw null; }
public bool TryFormat(System.Span<char> destination, int fieldCount, out int charsWritten) { throw null; }
Expand Down
25 changes: 25 additions & 0 deletions src/libraries/System.Runtime/tests/System/VersionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Text;
using Xunit;

namespace System.Tests
Expand Down Expand Up @@ -405,5 +406,29 @@ public static void TryFormat_Invoke_WritesExpected(Version version, string[] exp
AssertExtensions.Throws<ArgumentException>("fieldCount", () => version.TryFormat(dest, -1, out charsWritten)); // Index < 0
AssertExtensions.Throws<ArgumentException>("fieldCount", () => version.TryFormat(dest, maxFieldCount + 1, out charsWritten)); // Index > version.fieldCount
}

[Theory]
[MemberData(nameof(ToString_TestData))]
public static void IUtf8SpanFormattableTryFormat_Invoke_WritesExpected(Version version, string[] expectedFieldCounts)
{
string expected = expectedFieldCounts[^1];

// Too small
byte[] dest = new byte[expected.Length - 1];
Assert.False(((IUtf8SpanFormattable)version).TryFormat(dest, out int charsWritten, default, null));
Assert.Equal(0, charsWritten);

// Just right
dest = new byte[expected.Length];
Assert.True(((IUtf8SpanFormattable)version).TryFormat(dest, out charsWritten, default, null));
Assert.Equal(expected.Length, charsWritten);
Assert.Equal(expected, Encoding.UTF8.GetString(dest.AsSpan(0, charsWritten)));

// More than needed
dest = new byte[expected.Length + 10];
Assert.True(((IUtf8SpanFormattable)version).TryFormat(dest, out charsWritten, default, null));
Assert.Equal(expected.Length, charsWritten);
Assert.Equal(expected, Encoding.UTF8.GetString(dest.AsSpan(0, charsWritten)));
}
}
}