Skip to content

Commit 6e422d6

Browse files
Have bool and string implement ISpanParsable<T> (#82836)
1 parent e0e2345 commit 6e422d6

File tree

7 files changed

+304
-9
lines changed

7 files changed

+304
-9
lines changed

src/libraries/System.Private.CoreLib/src/System/Boolean.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ namespace System
2020
{
2121
[Serializable]
2222
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
23-
public readonly struct Boolean : IComparable, IConvertible, IComparable<bool>, IEquatable<bool>
23+
public readonly struct Boolean
24+
: IComparable,
25+
IConvertible,
26+
IComparable<bool>,
27+
IEquatable<bool>,
28+
ISpanParsable<bool>
2429
{
2530
//
2631
// Member Variables
@@ -397,5 +402,21 @@ object IConvertible.ToType(Type type, IFormatProvider? provider)
397402
{
398403
return Convert.DefaultToType((IConvertible)this, type, provider);
399404
}
405+
406+
//
407+
// IParsable
408+
//
409+
410+
static bool IParsable<bool>.Parse(string s, IFormatProvider? provider) => Parse(s);
411+
412+
static bool IParsable<bool>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out bool result) => TryParse(s, out result);
413+
414+
//
415+
// ISpanParsable
416+
//
417+
418+
static bool ISpanParsable<bool>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) => Parse(s);
419+
420+
static bool ISpanParsable<bool>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out bool result) => TryParse(s, out result);
400421
}
401422
}

src/libraries/System.Private.CoreLib/src/System/String.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,15 @@ namespace System
2424
[Serializable]
2525
[NonVersionable] // This only applies to field layout
2626
[System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
27-
public sealed partial class String : IComparable, IEnumerable, IConvertible, IEnumerable<char>, IComparable<string?>, IEquatable<string?>, ICloneable
27+
public sealed partial class String
28+
: IComparable,
29+
IEnumerable,
30+
IConvertible,
31+
IEnumerable<char>,
32+
IComparable<string?>,
33+
IEquatable<string?>,
34+
ICloneable,
35+
ISpanParsable<string>
2836
{
2937
/// <summary>Maximum length allowed for a string.</summary>
3038
/// <remarks>Keep in sync with AllocateString in gchelpers.cpp.</remarks>
@@ -735,5 +743,48 @@ public int Length
735743
[Intrinsic]
736744
get => _stringLength;
737745
}
746+
747+
//
748+
// IParsable
749+
//
750+
751+
static string IParsable<string>.Parse(string s, IFormatProvider? provider)
752+
{
753+
ArgumentNullException.ThrowIfNull(s);
754+
return s;
755+
}
756+
757+
static bool IParsable<string>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(returnValue: false)] out string result)
758+
{
759+
result = s;
760+
return s is not null;
761+
}
762+
763+
//
764+
// ISpanParsable
765+
//
766+
767+
static string ISpanParsable<string>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider)
768+
{
769+
if (s.Length > MaxLength)
770+
{
771+
ThrowHelper.ThrowFormatInvalidString();
772+
}
773+
return s.ToString();
774+
}
775+
776+
static bool ISpanParsable<string>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [MaybeNullWhen(returnValue: false)] out string result)
777+
{
778+
if (s.Length <= MaxLength)
779+
{
780+
result = s.ToString();
781+
return true;
782+
}
783+
else
784+
{
785+
result = null;
786+
return false;
787+
}
788+
}
738789
}
739790
}

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ public static partial class BitConverter
675675
[System.CLSCompliantAttribute(false)]
676676
public static double UInt64BitsToDouble(ulong value) { throw null; }
677677
}
678-
public readonly partial struct Boolean : System.IComparable, System.IComparable<bool>, System.IConvertible, System.IEquatable<bool>
678+
public readonly partial struct Boolean : System.IComparable, System.IComparable<bool>, System.IConvertible, System.IEquatable<bool>, System.IParsable<bool>, System.ISpanParsable<bool>
679679
{
680680
private readonly bool _dummyPrimitive;
681681
public static readonly string FalseString;
@@ -703,6 +703,10 @@ public static partial class BitConverter
703703
ushort System.IConvertible.ToUInt16(System.IFormatProvider? provider) { throw null; }
704704
uint System.IConvertible.ToUInt32(System.IFormatProvider? provider) { throw null; }
705705
ulong System.IConvertible.ToUInt64(System.IFormatProvider? provider) { throw null; }
706+
static bool System.IParsable<bool>.Parse(string s, System.IFormatProvider? provider) { throw null; }
707+
static bool System.IParsable<bool>.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out bool result) { throw null; }
708+
static bool System.ISpanParsable<bool>.Parse(ReadOnlySpan<char> s, System.IFormatProvider? provider) { throw null; }
709+
static bool System.ISpanParsable<bool>.TryParse(ReadOnlySpan<char> s, System.IFormatProvider? provider, out bool result) { throw null; }
706710
public override string ToString() { throw null; }
707711
public string ToString(System.IFormatProvider? provider) { throw null; }
708712
public bool TryFormat(System.Span<char> destination, out int charsWritten) { throw null; }
@@ -4971,7 +4975,7 @@ public sealed partial class STAThreadAttribute : System.Attribute
49714975
{
49724976
public STAThreadAttribute() { }
49734977
}
4974-
public sealed partial class String : System.Collections.Generic.IEnumerable<char>, System.Collections.IEnumerable, System.ICloneable, System.IComparable, System.IComparable<string?>, System.IConvertible, System.IEquatable<string?>
4978+
public sealed partial class String : System.Collections.Generic.IEnumerable<char>, System.Collections.IEnumerable, System.ICloneable, System.IComparable, System.IComparable<string?>, System.IConvertible, System.IEquatable<string?>, System.IParsable<string>, System.ISpanParsable<string>
49754979
{
49764980
public static readonly string Empty;
49774981
[System.CLSCompliantAttribute(false)]
@@ -5153,6 +5157,10 @@ public void CopyTo(System.Span<char> destination) { }
51535157
ushort System.IConvertible.ToUInt16(System.IFormatProvider? provider) { throw null; }
51545158
uint System.IConvertible.ToUInt32(System.IFormatProvider? provider) { throw null; }
51555159
ulong System.IConvertible.ToUInt64(System.IFormatProvider? provider) { throw null; }
5160+
static string System.IParsable<string>.Parse(string s, System.IFormatProvider? provider) { throw null; }
5161+
static bool System.IParsable<string>.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out string result) { throw null; }
5162+
static string System.ISpanParsable<string>.Parse(ReadOnlySpan<char> s, System.IFormatProvider? provider) { throw null; }
5163+
static bool System.ISpanParsable<string>.TryParse(ReadOnlySpan<char> s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out string result) { throw null; }
51565164
public char[] ToCharArray() { throw null; }
51575165
public char[] ToCharArray(int startIndex, int length) { throw null; }
51585166
public System.String ToLower() { throw null; }

src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@
6666
<Compile Include="System\Attributes.cs" />
6767
<Compile Include="System\AttributeUsageAttributeTests.cs" />
6868
<Compile Include="System\BadImageFormatExceptionTests.cs" />
69+
<Compile Include="System\StringTests.GenericMath.cs" />
6970
<Compile Include="System\BooleanTests.cs" />
7071
<Compile Include="System\BufferTests.cs" />
72+
<Compile Include="System\BooleanTests.GenericMath.cs" />
7173
<Compile Include="System\ByteTests.cs" />
7274
<Compile Include="System\CharTests.cs" />
7375
<Compile Include="System\CLSCompliantAttributeTests.cs" />
@@ -336,10 +338,7 @@
336338
</ItemGroup>
337339

338340
<ItemGroup>
339-
<ProjectReference Include="$(LibrariesProjectRoot)System.Text.RegularExpressions\gen\System.Text.RegularExpressions.Generator.csproj"
340-
ReferenceOutputAssembly="false"
341-
SetTargetFramework="TargetFramework=netstandard2.0"
342-
OutputItemType="Analyzer" />
341+
<ProjectReference Include="$(LibrariesProjectRoot)System.Text.RegularExpressions\gen\System.Text.RegularExpressions.Generator.csproj" ReferenceOutputAssembly="false" SetTargetFramework="TargetFramework=netstandard2.0" OutputItemType="Analyzer" />
343342

344343
<PackageReference Include="Moq" Version="$(MoqVersion)" />
345344
<PackageReference Include="System.Runtime.Numerics.TestData" Version="$(SystemRuntimeNumericsTestDataVersion)" GeneratePathProperty="true" />
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Globalization;
5+
using Xunit;
6+
7+
namespace System.Tests
8+
{
9+
public class BooleanTests_GenericMath
10+
{
11+
//
12+
// IParsable and ISpanParsable
13+
//
14+
15+
[Theory]
16+
[MemberData(nameof(BooleanTests.Parse_Valid_TestData), MemberType = typeof(BooleanTests))]
17+
public static void ParseValidStringTest(string value, bool expected)
18+
{
19+
bool result;
20+
21+
// Default
22+
Assert.True(ParsableHelper<bool>.TryParse(value, provider: null, out result));
23+
Assert.Equal(expected, result);
24+
Assert.Equal(expected, ParsableHelper<bool>.Parse(value, provider: null));
25+
26+
// Current Culture
27+
Assert.True(ParsableHelper<bool>.TryParse(value, provider: CultureInfo.CurrentCulture, out result));
28+
Assert.Equal(expected, result);
29+
Assert.Equal(expected, ParsableHelper<bool>.Parse(value, provider: CultureInfo.CurrentCulture));
30+
}
31+
32+
[Theory]
33+
[MemberData(nameof(BooleanTests.Parse_Invalid_TestData), MemberType = typeof(BooleanTests))]
34+
public static void ParseInvalidStringTest(string value, Type exceptionType)
35+
{
36+
bool result;
37+
38+
// Default
39+
Assert.False(ParsableHelper<bool>.TryParse(value, provider: null, out result));
40+
Assert.Equal(default(bool), result);
41+
Assert.Throws(exceptionType, () => ParsableHelper<bool>.Parse(value, provider: null));
42+
43+
// Current Culture
44+
Assert.False(ParsableHelper<bool>.TryParse(value, provider: CultureInfo.CurrentCulture, out result));
45+
Assert.Equal(default(bool), result);
46+
Assert.Throws(exceptionType, () => ParsableHelper<bool>.Parse(value, provider: CultureInfo.CurrentCulture));
47+
}
48+
49+
[Theory]
50+
[MemberData(nameof(BooleanTests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(BooleanTests))]
51+
public static void ParseValidSpanTest(string value, int offset, int count, bool expected)
52+
{
53+
bool result;
54+
55+
// Default
56+
Assert.True(SpanParsableHelper<bool>.TryParse(value.AsSpan(offset, count), provider: null, out result));
57+
Assert.Equal(expected, result);
58+
Assert.Equal(expected, SpanParsableHelper<bool>.Parse(value.AsSpan(offset, count), provider: null));
59+
60+
// Current Culture
61+
Assert.True(SpanParsableHelper<bool>.TryParse(value.AsSpan(offset, count), provider: CultureInfo.CurrentCulture, out result));
62+
Assert.Equal(expected, result);
63+
Assert.Equal(expected, SpanParsableHelper<bool>.Parse(value.AsSpan(offset, count), provider: CultureInfo.CurrentCulture));
64+
}
65+
66+
[Theory]
67+
[MemberData(nameof(BooleanTests.Parse_Invalid_TestData), MemberType = typeof(BooleanTests))]
68+
public static void ParseInvalidSpanTest(string value, Type exceptionType)
69+
{
70+
if (value is null)
71+
{
72+
// null and empty span are treated the same
73+
return;
74+
}
75+
76+
bool result;
77+
78+
// Default
79+
Assert.False(SpanParsableHelper<bool>.TryParse(value.AsSpan(), provider: null, out result));
80+
Assert.Equal(default(bool), result);
81+
Assert.Throws(exceptionType, () => SpanParsableHelper<bool>.Parse(value.AsSpan(), provider: null));
82+
83+
// Current Culture
84+
Assert.False(SpanParsableHelper<bool>.TryParse(value.AsSpan(), provider: CultureInfo.CurrentCulture, out result));
85+
Assert.Equal(default(bool), result);
86+
Assert.Throws(exceptionType, () => SpanParsableHelper<bool>.Parse(value.AsSpan(), provider: CultureInfo.CurrentCulture));
87+
}
88+
}
89+
}

src/libraries/System.Runtime/tests/System/BooleanTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public static IEnumerable<object[]> Parse_Invalid_TestData()
6363
yield return new object[] { "T", typeof(FormatException) };
6464
yield return new object[] { "0", typeof(FormatException) };
6565
yield return new object[] { "1", typeof(FormatException) };
66-
}
66+
}
6767

6868
[Theory]
6969
[MemberData(nameof(Parse_Invalid_TestData))]
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Globalization;
6+
using Xunit;
7+
8+
namespace System.Tests
9+
{
10+
public class StringTests_GenericMath
11+
{
12+
public static IEnumerable<object[]> Parse_Valid_TestData()
13+
{
14+
yield return new object[] { "Hello" };
15+
yield return new object[] { "hello" };
16+
yield return new object[] { "HELLO" };
17+
yield return new object[] { "hElLo" };
18+
yield return new object[] { " Hello " };
19+
yield return new object[] { "Hello\0" };
20+
yield return new object[] { " \0 \0 Hello \0 " };
21+
yield return new object[] { "World" };
22+
yield return new object[] { "world" };
23+
yield return new object[] { "WORLD" };
24+
yield return new object[] { "wOrLd" };
25+
yield return new object[] { "World " };
26+
yield return new object[] { "World\0" };
27+
yield return new object[] { " World \0\0\0 " };
28+
}
29+
30+
public static IEnumerable<object[]> Parse_Invalid_TestData()
31+
{
32+
yield return new object[] { null, typeof(ArgumentNullException) };
33+
// We cannot easily test inputs that exceed `string.MaxLength` without risk of OOM
34+
}
35+
36+
public static IEnumerable<object[]> Parse_ValidWithOffsetCount_TestData()
37+
{
38+
foreach (object[] inputs in Parse_Valid_TestData())
39+
{
40+
yield return new object[] { inputs[0], 0, ((string)inputs[0]).Length, inputs[0] };
41+
}
42+
43+
yield return new object[] { " \0 \0 Hello, World! \0 ", 6, 5, "Hello" };
44+
yield return new object[] { " \0 \0 Hello, World! \0 ", 13, 5, "World" };
45+
yield return new object[] { " \0 \0 Hello, World! \0 ", 6, 13, "Hello, World!" };
46+
}
47+
48+
//
49+
// IParsable and ISpanParsable
50+
//
51+
52+
[Theory]
53+
[MemberData(nameof(StringTests_GenericMath.Parse_Valid_TestData), MemberType = typeof(StringTests_GenericMath))]
54+
public static void ParseValidStringTest(string value)
55+
{
56+
string result;
57+
string expected = value;
58+
59+
// Default
60+
Assert.True(ParsableHelper<string>.TryParse(value, provider: null, out result));
61+
Assert.Equal(expected, result);
62+
Assert.Equal(expected, ParsableHelper<string>.Parse(value, provider: null));
63+
64+
// Current Culture
65+
Assert.True(ParsableHelper<string>.TryParse(value, provider: CultureInfo.CurrentCulture, out result));
66+
Assert.Equal(expected, result);
67+
Assert.Equal(expected, ParsableHelper<string>.Parse(value, provider: CultureInfo.CurrentCulture));
68+
}
69+
70+
[Theory]
71+
[MemberData(nameof(StringTests_GenericMath.Parse_Invalid_TestData), MemberType = typeof(StringTests_GenericMath))]
72+
public static void ParseInvalidStringTest(string value, Type exceptionType)
73+
{
74+
string result;
75+
76+
// Default
77+
Assert.False(ParsableHelper<string>.TryParse(value, provider: null, out result));
78+
Assert.Equal(default(string), result);
79+
Assert.Throws(exceptionType, () => ParsableHelper<string>.Parse(value, provider: null));
80+
81+
// Current Culture
82+
Assert.False(ParsableHelper<string>.TryParse(value, provider: CultureInfo.CurrentCulture, out result));
83+
Assert.Equal(default(string), result);
84+
Assert.Throws(exceptionType, () => ParsableHelper<string>.Parse(value, provider: CultureInfo.CurrentCulture));
85+
}
86+
87+
[Theory]
88+
[MemberData(nameof(StringTests_GenericMath.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(StringTests_GenericMath))]
89+
public static void ParseValidSpanTest(string value, int offset, int count, string expected)
90+
{
91+
string result;
92+
93+
// Default
94+
Assert.True(SpanParsableHelper<string>.TryParse(value.AsSpan(offset, count), provider: null, out result));
95+
Assert.Equal(expected, result);
96+
Assert.Equal(expected, SpanParsableHelper<string>.Parse(value.AsSpan(offset, count), provider: null));
97+
98+
// Current Culture
99+
Assert.True(SpanParsableHelper<string>.TryParse(value.AsSpan(offset, count), provider: CultureInfo.CurrentCulture, out result));
100+
Assert.Equal(expected, result);
101+
Assert.Equal(expected, SpanParsableHelper<string>.Parse(value.AsSpan(offset, count), provider: CultureInfo.CurrentCulture));
102+
}
103+
104+
[Theory]
105+
[MemberData(nameof(StringTests_GenericMath.Parse_Invalid_TestData), MemberType = typeof(StringTests_GenericMath))]
106+
public static void ParseInvalidSpanTest(string value, Type exceptionType)
107+
{
108+
if (value is null)
109+
{
110+
// null and empty span are treated the same
111+
return;
112+
}
113+
114+
string result;
115+
116+
// Default
117+
Assert.False(SpanParsableHelper<string>.TryParse(value.AsSpan(), provider: null, out result));
118+
Assert.Equal(default(string), result);
119+
Assert.Throws(exceptionType, () => SpanParsableHelper<string>.Parse(value.AsSpan(), provider: null));
120+
121+
// Current Culture
122+
Assert.False(SpanParsableHelper<string>.TryParse(value.AsSpan(), provider: CultureInfo.CurrentCulture, out result));
123+
Assert.Equal(default(string), result);
124+
Assert.Throws(exceptionType, () => SpanParsableHelper<string>.Parse(value.AsSpan(), provider: CultureInfo.CurrentCulture));
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)