Skip to content

Commit 8798c04

Browse files
authored
Add System.Text.CompositeFormat (#80753)
* Add System.Text.CompositeFormat * Conditionalize a test and remove extra const * Address PR feedback
1 parent ddb6988 commit 8798c04

File tree

27 files changed

+1540
-308
lines changed

27 files changed

+1540
-308
lines changed

src/coreclr/System.Private.CoreLib/src/System/Reflection/MdImport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public static bool IsTokenOfType(int token, params MetadataTokenType[] types)
168168
public bool IsAssembly => TokenType == MetadataTokenType.Assembly;
169169
public bool IsGenericPar => TokenType == MetadataTokenType.GenericPar;
170170

171-
public override string ToString() => string.Format(CultureInfo.InvariantCulture, "0x{0:x8}", Value);
171+
public override string ToString() => string.Create(CultureInfo.InvariantCulture, stackalloc char[64], $"0x{Value:x8}");
172172
}
173173

174174
internal unsafe struct MetadataEnumResult

src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeExceptionHandlingClause.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,18 @@ public override string ToString()
6767
{
6868
if (Flags == ExceptionHandlingClauseOptions.Clause)
6969
{
70-
return string.Format(CultureInfo.CurrentUICulture,
71-
"Flags={0}, TryOffset={1}, TryLength={2}, HandlerOffset={3}, HandlerLength={4}, CatchType={5}",
72-
Flags, TryOffset, TryLength, HandlerOffset, HandlerLength, CatchType);
70+
return string.Create(CultureInfo.CurrentUICulture,
71+
$"Flags={Flags}, TryOffset={TryOffset}, TryLength={TryLength}, HandlerOffset={HandlerOffset}, HandlerLength={HandlerLength}, CatchType={CatchType}");
7372
}
7473

7574
if (Flags == ExceptionHandlingClauseOptions.Filter)
7675
{
77-
return string.Format(CultureInfo.CurrentUICulture,
78-
"Flags={0}, TryOffset={1}, TryLength={2}, HandlerOffset={3}, HandlerLength={4}, FilterOffset={5}",
79-
Flags, TryOffset, TryLength, HandlerOffset, HandlerLength, FilterOffset);
76+
return string.Create(CultureInfo.CurrentUICulture,
77+
$"Flags={Flags}, TryOffset={TryOffset}, TryLength={TryLength}, HandlerOffset={HandlerOffset}, HandlerLength={HandlerLength}, FilterOffset={FilterOffset}");
8078
}
8179

82-
return string.Format(CultureInfo.CurrentUICulture,
83-
"Flags={0}, TryOffset={1}, TryLength={2}, HandlerOffset={3}, HandlerLength={4}",
84-
Flags, TryOffset, TryLength, HandlerOffset, HandlerLength);
80+
return string.Create(CultureInfo.CurrentUICulture,
81+
$"Flags={Flags}, TryOffset={TryOffset}, TryLength={TryLength}, HandlerOffset={HandlerOffset}, HandlerLength={HandlerLength}");
8582
}
8683
}
8784
}

src/libraries/Common/tests/Tests/System/StringTests.cs

Lines changed: 113 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2661,7 +2661,6 @@ public static IEnumerable<object[]> Format_Valid_TestData()
26612661
yield return new object[] { null, "Foo }}{0}", new object[] { 1 }, "Foo }1" }; // Escaped closed curly braces
26622662
yield return new object[] { null, "Foo {0} {{0}}", new object[] { 1 }, "Foo 1 {0}" }; // Escaped placeholder
26632663

2664-
26652664
yield return new object[] { null, "Foo {0}", new object[] { null }, "Foo " }; // Values has null only
26662665
yield return new object[] { null, "Foo {0} {1} {2}", new object[] { "Bar", null, "Baz" }, "Foo Bar Baz" }; // Values has null
26672666

@@ -2670,6 +2669,29 @@ public static IEnumerable<object[]> Format_Valid_TestData()
26702669
yield return new object[] { new CustomFormatter(), "{0}", new object[] { 1.2 }, "abc" }; // Custom format provider
26712670
yield return new object[] { new CustomFormatter(), "{0:0}", new object[] { 1.2 }, "abc" }; // Custom format provider
26722671

2672+
// More arguments than needed
2673+
yield return new object[] { null, "{0}", new object[] { 1, 2 }, "1" };
2674+
yield return new object[] { null, "{0}", new object[] { 1, 2, 3 }, "1" };
2675+
yield return new object[] { null, "{0}", new object[] { 1, 2, 3, 4 }, "1" };
2676+
yield return new object[] { null, "{0}{1}", new object[] { 1, 2, 3 }, "12" };
2677+
yield return new object[] { null, "{0}{1}", new object[] { 1, 2, 3, 4 }, "12" };
2678+
yield return new object[] { null, "{0}{1}", new object[] { 1, 2, 3, 4, 5 }, "12" };
2679+
yield return new object[] { null, "{0}{1}{2}", new object[] { 1, 2, 3, 4 }, "123" };
2680+
yield return new object[] { null, "{0}{1}{2}", new object[] { 1, 2, 3, 4, 5 }, "123" };
2681+
yield return new object[] { null, "{0}{1}{2}", new object[] { 1, 2, 3, 4, 5, 6 }, "123" };
2682+
yield return new object[] { null, "{0}{1}{2}{3}", new object[] { 1, 2, 3, 4, 5 }, "1234" };
2683+
yield return new object[] { null, "{0}{1}{2}{3}", new object[] { 1, 2, 3, 4, 5, 6 }, "1234" };
2684+
yield return new object[] { null, "{0}{1}{2}{3}", new object[] { 1, 2, 3, 4, 5, 6, 7 }, "1234" };
2685+
yield return new object[] { null, "{0}{1}{2}{3}{4}", new object[] { 1, 2, 3, 4, 5, 6 }, "12345" };
2686+
yield return new object[] { null, "{0}{1}{2}{3}{4}", new object[] { 1, 2, 3, 4, 5, 6, 7 }, "12345" };
2687+
yield return new object[] { null, "{0}{1}{2}{3}{4}", new object[] { 1, 2, 3, 4, 5, 6, 7, 8 }, "12345" };
2688+
2689+
// Out of order
2690+
yield return new object[] { null, "{1}{0}", new object[] { 1, 2 }, "21" };
2691+
yield return new object[] { null, "{2}{1}{0}", new object[] { 1, 2, 3 }, "321" };
2692+
yield return new object[] { null, "{3}{2}{1}{0}", new object[] { 1, 2, 3, 4 }, "4321" };
2693+
yield return new object[] { null, "{4}{3}{2}{1}{0}", new object[] { 1, 2, 3, 4, 5 }, "54321" };
2694+
26732695
// Longer inputs
26742696
yield return new object[] { null, "0 = {0} 1 = {1} 2 = {2} 3 = {3} 4 = {4}", new object[] { "zero", "one", "two", "three", "four" }, "0 = zero 1 = one 2 = two 3 = three 4 = four" };
26752697
yield return new object[] { new TestFormatter(), "0 = {0} 1 = {1} 2 = {2} 3 = {3} 4 = {4}", new object[] { "zero", "one", "two", "three", "four" }, "0 = Test: : zero 1 = Test: : one 2 = Test: : two 3 = Test: : three 4 = Test: : four" };
@@ -2737,7 +2759,7 @@ public static void Format_Valid(IFormatProvider provider, string format, object[
27372759
}
27382760

27392761
[Fact]
2740-
public static void Format_Invalid()
2762+
public static void Format_Invalid_ArgumentException()
27412763
{
27422764
var formatter = new TestFormatter();
27432765
var obj1 = new object();
@@ -2751,66 +2773,107 @@ public static void Format_Invalid()
27512773
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(null, obj1, obj2, obj3));
27522774
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(null, obj1, obj2, obj3, obj4));
27532775

2754-
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(formatter, null, obj1));
2755-
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(formatter, null, obj1, obj2));
2756-
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(formatter, null, obj1, obj2, obj3));
2776+
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(formatter, (string)null, obj1));
2777+
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(formatter, (string)null, obj1, obj2));
2778+
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(formatter, (string)null, obj1, obj2, obj3));
27572779

27582780
// Args is null
27592781
AssertExtensions.Throws<ArgumentNullException>("args", () => string.Format("", null));
27602782
AssertExtensions.Throws<ArgumentNullException>("args", () => string.Format(formatter, "", null));
27612783

27622784
// Args and format are null
27632785
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(null, (object[])null));
2764-
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(formatter, null, null));
2765-
2766-
Assert.Throws<FormatException>(() => string.Format("{-1}", obj1)); // Format has value < 0
2767-
Assert.Throws<FormatException>(() => string.Format("{-1}", obj1, obj2)); // Format has value < 0
2768-
Assert.Throws<FormatException>(() => string.Format("{-1}", obj1, obj2, obj3)); // Format has value < 0
2769-
Assert.Throws<FormatException>(() => string.Format("{-1}", obj1, obj2, obj3, obj4)); // Format has value < 0
2770-
Assert.Throws<FormatException>(() => string.Format(formatter, "{-1}", obj1)); // Format has value < 0
2771-
Assert.Throws<FormatException>(() => string.Format(formatter, "{-1}", obj1, obj2)); // Format has value < 0
2772-
Assert.Throws<FormatException>(() => string.Format(formatter, "{-1}", obj1, obj2, obj3)); // Format has value < 0
2773-
Assert.Throws<FormatException>(() => string.Format(formatter, "{-1}", obj1, obj2, obj3, obj4)); // Format has value < 0
2774-
2775-
Assert.Throws<FormatException>(() => string.Format("{1}", obj1)); // Format has value >= 1
2776-
Assert.Throws<FormatException>(() => string.Format("{2}", obj1, obj2)); // Format has value >= 2
2777-
Assert.Throws<FormatException>(() => string.Format("{3}", obj1, obj2, obj3)); // Format has value >= 3
2778-
Assert.Throws<FormatException>(() => string.Format("{4}", obj1, obj2, obj3, obj4)); // Format has value >= 4
2779-
Assert.Throws<FormatException>(() => string.Format(formatter, "{1}", obj1)); // Format has value >= 1
2780-
Assert.Throws<FormatException>(() => string.Format(formatter, "{2}", obj1, obj2)); // Format has value >= 2
2781-
Assert.Throws<FormatException>(() => string.Format(formatter, "{3}", obj1, obj2, obj3)); // Format has value >= 3
2782-
Assert.Throws<FormatException>(() => string.Format(formatter, "{4}", obj1, obj2, obj3, obj4)); // Format has value >= 4
2783-
2784-
Assert.Throws<FormatException>(() => string.Format("{", "")); // Format has unescaped {
2785-
Assert.Throws<FormatException>(() => string.Format("{a", "")); // Format has unescaped {
2786-
2787-
Assert.Throws<FormatException>(() => string.Format("}", "")); // Format has unescaped }
2788-
Assert.Throws<FormatException>(() => string.Format("}a", "")); // Format has unescaped }
2789-
Assert.Throws<FormatException>(() => string.Format("{0:}}", "")); // Format has unescaped }
2790-
2791-
Assert.Throws<FormatException>(() => string.Format("{\0", "")); // Format has invalid character after {
2792-
Assert.Throws<FormatException>(() => string.Format("{a", "")); // Format has invalid character after {
2793-
2794-
Assert.Throws<FormatException>(() => string.Format("{0 ", "")); // Format with index and spaces is not closed
2795-
2796-
Assert.Throws<FormatException>(() => string.Format("{1000000", new string[10])); // Format index is too long
2797-
Assert.Throws<FormatException>(() => string.Format("{10000000}", new string[10])); // Format index is too long
2786+
AssertExtensions.Throws<ArgumentNullException>("format", () => string.Format(formatter, (string)null, null));
2787+
}
27982788

2799-
Assert.Throws<FormatException>(() => string.Format("{0,", "")); // Format with comma is not closed
2800-
Assert.Throws<FormatException>(() => string.Format("{0, ", "")); // Format with comma and spaces is not closed
2801-
Assert.Throws<FormatException>(() => string.Format("{0,-", "")); // Format with comma and minus sign is not closed
2789+
public static IEnumerable<object[]> Format_Invalid_FormatExceptionFromFormat_MemberData()
2790+
{
2791+
var obj1 = new object();
2792+
var obj2 = new object();
2793+
var obj3 = new object();
2794+
var obj4 = new object();
28022795

2803-
Assert.Throws<FormatException>(() => string.Format("{0,-\0", "")); // Format has invalid character after minus sign
2804-
Assert.Throws<FormatException>(() => string.Format("{0,-a", "")); // Format has invalid character after minus sign
2796+
foreach (IFormatProvider provider in new[] { null, new TestFormatter() })
2797+
{
2798+
yield return new object[] { provider, "{-1}", new object[] { obj1 } }; // Format has value < 0
2799+
yield return new object[] { provider, "{-1}", new object[] { obj1, obj2 } }; // Format has value < 0
2800+
yield return new object[] { provider, "{-1}", new object[] { obj1, obj2, obj3 } }; // Format has value < 0
2801+
yield return new object[] { provider, "{-1}", new object[] { obj1, obj2, obj3, obj4 } }; // Format has value < 0
2802+
yield return new object[] { provider, "{", new object[] { "" } }; // Format has unescaped {
2803+
yield return new object[] { provider, "{a", new object[] { "" } }; // Format has unescaped {
2804+
yield return new object[] { provider, "}", new object[] { "" } }; // Format has unescaped }
2805+
yield return new object[] { provider, "}a", new object[] { "" } }; // Format has unescaped }
2806+
yield return new object[] { provider, "{0:}}", new object[] { "" } }; // Format has unescaped }
2807+
yield return new object[] { provider, "{\0", new object[] { "" } }; // Format has invalid character after {
2808+
yield return new object[] { provider, "{a", new object[] { "" } }; // Format has invalid character after {
2809+
yield return new object[] { provider, "{0 ", new object[] { "" } }; // Format with index and spaces is not closed
2810+
yield return new object[] { provider, "{1000000", new object[10] }; // Format has missing closing brace
2811+
yield return new object[] { provider, "{0,", new object[] { "" } }; // Format with comma is not closed
2812+
yield return new object[] { provider, "{0, ", new object[] { "" } }; // Format with comma and spaces is not closed
2813+
yield return new object[] { provider, "{0,-", new object[] { "" } }; // Format with comma and minus sign is not closed
2814+
yield return new object[] { provider, "{0,-\0", new object[] { "" } }; // Format has invalid character after minus sign
2815+
yield return new object[] { provider, "{0,-a", new object[] { "" } }; // Format has invalid character after minus sign
2816+
yield return new object[] { provider, "{0,1000000", new string[10] }; // Format length is too long
2817+
yield return new object[] { provider, "{0:", new string[10] }; // Format with colon is not closed
2818+
yield return new object[] { provider, "{0: ", new string[10] }; // Format with colon and spaces is not closed
2819+
yield return new object[] { provider, "{0:{", new string[10] }; // Format with custom format contains unescaped {
2820+
yield return new object[] { provider, "{0:{}", new string[10] }; // Format with custom format contains unescaped {
2821+
}
2822+
}
2823+
2824+
public static IEnumerable<object[]> Format_Invalid_FormatExceptionFromArgs_MemberData()
2825+
{
2826+
var obj1 = new object();
2827+
var obj2 = new object();
2828+
var obj3 = new object();
2829+
var obj4 = new object();
28052830

2806-
Assert.Throws<FormatException>(() => string.Format("{0,1000000", new string[10])); // Format length is too long
2807-
Assert.Throws<FormatException>(() => string.Format("{0,10000000}", new string[10])); // Format length is too long
2831+
foreach (IFormatProvider provider in new[] { null, new TestFormatter() })
2832+
{
2833+
yield return new object[] { provider, "{1}", new object[] { obj1 } }; // Format has value >= 1
2834+
yield return new object[] { provider, "{2}", new object[] { obj1, obj2 } }; // Format has value >= 2
2835+
yield return new object[] { provider, "{3}", new object[] { obj1, obj2, obj3 } }; // Format has value >= 3
2836+
yield return new object[] { provider, "{4}", new object[] { obj1, obj2, obj3, obj4 } }; // Format has value >= 4
2837+
}
2838+
}
28082839

2809-
Assert.Throws<FormatException>(() => string.Format("{0:", new string[10])); // Format with colon is not closed
2810-
Assert.Throws<FormatException>(() => string.Format("{0: ", new string[10])); // Format with colon and spaces is not closed
2840+
[Theory]
2841+
[MemberData(nameof(Format_Invalid_FormatExceptionFromFormat_MemberData))]
2842+
[MemberData(nameof(Format_Invalid_FormatExceptionFromArgs_MemberData))]
2843+
[InlineData(null, "{10000000}", new object[] { null })]
2844+
[InlineData(null, "{0,10000000}", new string[] { null })]
2845+
public static void Format_Invalid_FormatExceptionFromFormatOrArgs(IFormatProvider provider, string format, object[] args)
2846+
{
2847+
if (provider is null)
2848+
{
2849+
Assert.Throws<FormatException>(() => string.Format(format, args));
2850+
switch (args.Length)
2851+
{
2852+
case 1:
2853+
Assert.Throws<FormatException>(() => string.Format(format, args[0]));
2854+
break;
2855+
case 2:
2856+
Assert.Throws<FormatException>(() => string.Format(format, args[0], args[1]));
2857+
break;
2858+
case 3:
2859+
Assert.Throws<FormatException>(() => string.Format(format, args[0], args[1], args[2]));
2860+
break;
2861+
}
2862+
}
28112863

2812-
Assert.Throws<FormatException>(() => string.Format("{0:{", new string[10])); // Format with custom format contains unescaped {
2813-
Assert.Throws<FormatException>(() => string.Format("{0:{}", new string[10])); // Format with custom format contains unescaped {
2864+
Assert.Throws<FormatException>(() => string.Format(provider, format, args));
2865+
switch (args.Length)
2866+
{
2867+
case 1:
2868+
Assert.Throws<FormatException>(() => string.Format(provider, format, args[0]));
2869+
break;
2870+
case 2:
2871+
Assert.Throws<FormatException>(() => string.Format(provider, format, args[0], args[1]));
2872+
break;
2873+
case 3:
2874+
Assert.Throws<FormatException>(() => string.Format(provider, format, args[0], args[1], args[2]));
2875+
break;
2876+
}
28142877
}
28152878

28162879
[ConditionalTheory]

src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/ComInterop/IDispatchComObject.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ internal bool TryGetMemberMethodExplicit(string name, out ComMethodDesc method)
230230
return false;
231231
}
232232

233-
throw Error.CouldNotGetDispId(name, string.Format(CultureInfo.InvariantCulture, "0x{0:X})", hresult));
233+
throw Error.CouldNotGetDispId(name, $"0x{(uint)hresult:X})");
234234
}
235235

236236
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
@@ -267,7 +267,7 @@ internal bool TryGetPropertySetterExplicit(string name, out ComMethodDesc method
267267
return false;
268268
}
269269

270-
throw Error.CouldNotGetDispId(name, string.Format(CultureInfo.InvariantCulture, "0x{0:X})", hresult));
270+
throw Error.CouldNotGetDispId(name, $"0x{(uint)hresult:X})");
271271
}
272272

273273
[RequiresUnreferencedCode(Binder.TrimmerWarning)]

src/libraries/Microsoft.Extensions.DependencyInjection/src/CallSiteJsonFormatter.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ public void StartProperty(string name)
159159
{
160160
_firstItem = false;
161161
}
162-
Builder.AppendFormat("\"{0}\":", name);
162+
163+
Builder.Append('"').Append(name).Append("\":");
163164
}
164165

165166
public void StartArrayItem()
@@ -179,7 +180,7 @@ public void WriteProperty(string name, object? value)
179180
StartProperty(name);
180181
if (value != null)
181182
{
182-
Builder.AppendFormat(" \"{0}\"", value);
183+
Builder.Append(" \"").Append(value).Append('"');
183184
}
184185
else
185186
{

src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteChain.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ private void AppendResolutionPath(StringBuilder builder, Type currentlyResolving
6262
}
6363
else
6464
{
65-
builder.AppendFormat("{0}({1})",
66-
TypeNameHelper.GetTypeDisplayName(serviceType),
67-
TypeNameHelper.GetTypeDisplayName(implementationType));
65+
builder.Append(TypeNameHelper.GetTypeDisplayName(serviceType))
66+
.Append('(')
67+
.Append(TypeNameHelper.GetTypeDisplayName(implementationType))
68+
.Append(')');
6869
}
6970

7071
builder.Append(" -> ");

0 commit comments

Comments
 (0)