Skip to content

Commit

Permalink
Update interpolated string handler based on API reviews / LDM decisio…
Browse files Browse the repository at this point in the history
…ns (#53153)
  • Loading branch information
stephentoub authored May 25, 2021
1 parent eb32b38 commit cb3b035
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,9 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IsConst.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IsReadOnlyAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IsVolatile.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\InterpolatedStringBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\InterpolatedStringHandlerAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\InterpolatedStringHandlerArgumentAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\DefaultInterpolatedStringHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IteratorStateMachineAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ITuple.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\LoadHint.cs" />
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
/// <summary>Indicates which arguments to a method involving an interpolated string handler should be passed to that handler.</summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
{
/// <summary>Initializes a new instance of the <see cref="InterpolatedStringHandlerArgumentAttribute"/> class.</summary>
/// <param name="argument">The name of the argument that should be passed to the handler.</param>
/// <remarks><see langword="null"/> may be used as the name of the receiver in an instance method.</remarks>
public InterpolatedStringHandlerArgumentAttribute(string argument) => Arguments = new string[] { argument };

/// <summary>Initializes a new instance of the <see cref="InterpolatedStringHandlerArgumentAttribute"/> class.</summary>
/// <param name="arguments">The names of the arguments that should be passed to the handler.</param>
/// <remarks><see langword="null"/> may be used as the name of the receiver in an instance method.</remarks>
public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) => Arguments = arguments;

/// <summary>Gets the names of the arguments that should be passed to the handler.</summary>
/// <remarks><see langword="null"/> may be used as the name of the receiver in an instance method.</remarks>
public string[] Arguments { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
/// <summary>Indicates the attributed type is to be used as an interpolated string handler.</summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerAttribute : Attribute
{
/// <summary>Initializes the <see cref="InterpolatedStringHandlerAttribute"/>.</summary>
public InterpolatedStringHandlerAttribute() { }
}
}
22 changes: 17 additions & 5 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9651,10 +9651,26 @@ public InternalsVisibleToAttribute(string assemblyName) { }
public bool AllInternalsVisible { get { throw null; } set { } }
public string AssemblyName { get { throw null; } }
}
public ref struct InterpolatedStringBuilder
[System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerAttribute : System.Attribute
{
public InterpolatedStringHandlerAttribute() { }
}
[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerArgumentAttribute : System.Attribute
{
public InterpolatedStringHandlerArgumentAttribute(string argument) { }
public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) { }
public string[] Arguments { get { throw null; } }
}
[System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute]
public ref struct DefaultInterpolatedStringHandler
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public DefaultInterpolatedStringHandler(int literalLength, int formattedCount) { throw null; }
public DefaultInterpolatedStringHandler(int literalLength, int formattedCount, System.IFormatProvider? provider) { throw null; }
public DefaultInterpolatedStringHandler(int literalLength, int formattedCount, System.IFormatProvider? provider, System.Span<char> initialBuffer) { throw null; }
public void AppendLiteral(string value) { }
public void AppendFormatted(System.ReadOnlySpan<char> value) { }
public void AppendFormatted(System.ReadOnlySpan<char> value, int alignment = 0, string? format = null) { }
Expand All @@ -9665,10 +9681,6 @@ public void AppendFormatted<T>(T value, int alignment, string? format) { }
public void AppendFormatted(object? value, int alignment = 0, string? format = null) { }
public void AppendFormatted(string? value) { throw null; }
public void AppendFormatted(string? value, int alignment = 0, string? format = null) { }
public static System.Runtime.CompilerServices.InterpolatedStringBuilder Create(int literalLength, int formattedCount) { throw null; }
public static System.Runtime.CompilerServices.InterpolatedStringBuilder Create(int literalLength, int formattedCount, System.IFormatProvider? provider) { throw null; }
public static System.Runtime.CompilerServices.InterpolatedStringBuilder Create(int literalLength, int formattedCount, System.Span<char> scratchBuffer) { throw null; }
public static System.Runtime.CompilerServices.InterpolatedStringBuilder Create(int literalLength, int formattedCount, System.IFormatProvider? provider, System.Span<char> scratchBuffer) { throw null; }
public override string ToString() { throw null; }
public string ToStringAndClear() { throw null; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@
<Compile Include="System\Runtime\NgenServicingAttributesTests.cs" />
<Compile Include="System\Runtime\CompilerServices\AttributesTests.cs" />
<Compile Include="System\Runtime\CompilerServices\ConditionalWeakTableTests.cs" />
<Compile Include="System\Runtime\CompilerServices\DefaultInterpolatedStringHandlerTests.cs" />
<Compile Include="System\Runtime\CompilerServices\FormattableStringFactoryTests.cs" />
<Compile Include="System\Runtime\CompilerServices\InterpolatedStringBuilderTests.cs" />
<Compile Include="System\Runtime\CompilerServices\StrongBoxTests.cs" />
<Compile Include="System\Runtime\CompilerServices\RuntimeHelpersTests.cs" />
<Compile Include="System\Runtime\ConstrainedExecution\PrePrepareMethodAttributeTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,5 +295,30 @@ public static void EnumeratorCancellationAttributeTests()
{
new EnumeratorCancellationAttribute();
}

[Fact]
public static void InterpolatedStringHandlerAttributeTests()
{
new InterpolatedStringHandlerAttribute();
}

[Theory]
[InlineData("")]
[InlineData("param1")]
public static void InterpolatedStringHandlerArgumentAttributeTests(string firstParameterName)
{
var attr1 = new InterpolatedStringHandlerArgumentAttribute(firstParameterName);
Assert.NotNull(attr1.Arguments);
Assert.Same(attr1.Arguments, attr1.Arguments);
Assert.Equal(1, attr1.Arguments.Length);
Assert.Equal(firstParameterName, attr1.Arguments[0]);

string[] arguments = new[] { firstParameterName, "param2" };
var attr2 = new InterpolatedStringHandlerArgumentAttribute(arguments);
Assert.NotNull(attr2.Arguments);
Assert.Same(arguments, attr2.Arguments);
Assert.Equal(firstParameterName, attr2.Arguments[0]);
Assert.Equal("param2", attr2.Arguments[1]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace System.Runtime.CompilerServices.Tests
{
public class InterpolatedStringBuilderTests
public class DefaultInterpolatedStringHandlerTests
{
[Theory]
[InlineData(0, 0)]
Expand All @@ -18,51 +18,46 @@ public class InterpolatedStringBuilderTests
[InlineData(-16, 1)]
public void LengthAndHoleArguments_Valid(int literalLength, int formattedCount)
{
InterpolatedStringBuilder.Create(literalLength, formattedCount);
new DefaultInterpolatedStringHandler(literalLength, formattedCount);

Span<char> scratch1 = stackalloc char[1];
foreach (IFormatProvider provider in new IFormatProvider[] { null, new ConcatFormatter(), CultureInfo.InvariantCulture, CultureInfo.CurrentCulture, new CultureInfo("en-US"), new CultureInfo("fr-FR") })
{
InterpolatedStringBuilder.Create(literalLength, formattedCount, provider);
new DefaultInterpolatedStringHandler(literalLength, formattedCount, provider);

InterpolatedStringBuilder.Create(literalLength, formattedCount, provider, default);
InterpolatedStringBuilder.Create(literalLength, formattedCount, provider, scratch1);
InterpolatedStringBuilder.Create(literalLength, formattedCount, provider, Array.Empty<char>());
InterpolatedStringBuilder.Create(literalLength, formattedCount, provider, new char[256]);
new DefaultInterpolatedStringHandler(literalLength, formattedCount, provider, default);
new DefaultInterpolatedStringHandler(literalLength, formattedCount, provider, scratch1);
new DefaultInterpolatedStringHandler(literalLength, formattedCount, provider, Array.Empty<char>());
new DefaultInterpolatedStringHandler(literalLength, formattedCount, provider, new char[256]);
}

InterpolatedStringBuilder.Create(literalLength, formattedCount, Span<char>.Empty);
InterpolatedStringBuilder.Create(literalLength, formattedCount, scratch1);
InterpolatedStringBuilder.Create(literalLength, formattedCount, Array.Empty<char>());
InterpolatedStringBuilder.Create(literalLength, formattedCount, new char[256]);
}

[Fact]
public void ToString_DoesntClear()
{
InterpolatedStringBuilder builder = InterpolatedStringBuilder.Create(0, 0);
builder.AppendLiteral("hi");
DefaultInterpolatedStringHandler handler = new DefaultInterpolatedStringHandler(0, 0);
handler.AppendLiteral("hi");
for (int i = 0; i < 3; i++)
{
Assert.Equal("hi", builder.ToString());
Assert.Equal("hi", handler.ToString());
}
Assert.Equal("hi", builder.ToStringAndClear());
Assert.Equal("hi", handler.ToStringAndClear());
}

[Fact]
public void ToStringAndClear_Clears()
{
InterpolatedStringBuilder builder = InterpolatedStringBuilder.Create(0, 0);
builder.AppendLiteral("hi");
Assert.Equal("hi", builder.ToStringAndClear());
Assert.Equal(string.Empty, builder.ToStringAndClear());
DefaultInterpolatedStringHandler handler = new DefaultInterpolatedStringHandler(0, 0);
handler.AppendLiteral("hi");
Assert.Equal("hi", handler.ToStringAndClear());
Assert.Equal(string.Empty, handler.ToStringAndClear());
}

[Fact]
public void AppendLiteral()
{
var expected = new StringBuilder();
InterpolatedStringBuilder actual = InterpolatedStringBuilder.Create(0, 0);
DefaultInterpolatedStringHandler actual = new DefaultInterpolatedStringHandler(0, 0);

foreach (string s in new[] { "", "a", "bc", "def", "this is a long string", "!" })
{
Expand All @@ -77,7 +72,7 @@ public void AppendLiteral()
public void AppendFormatted_ReadOnlySpanChar()
{
var expected = new StringBuilder();
InterpolatedStringBuilder actual = InterpolatedStringBuilder.Create(0, 0);
DefaultInterpolatedStringHandler actual = new DefaultInterpolatedStringHandler(0, 0);

foreach (string s in new[] { "", "a", "bc", "def", "this is a longer string", "!" })
{
Expand Down Expand Up @@ -108,7 +103,7 @@ public void AppendFormatted_ReadOnlySpanChar()
public void AppendFormatted_String()
{
var expected = new StringBuilder();
InterpolatedStringBuilder actual = InterpolatedStringBuilder.Create(0, 0);
DefaultInterpolatedStringHandler actual = new DefaultInterpolatedStringHandler(0, 0);

foreach (string s in new[] { null, "", "a", "bc", "def", "this is a longer string", "!" })
{
Expand Down Expand Up @@ -141,7 +136,7 @@ public void AppendFormatted_String_ICustomFormatter()
var provider = new ConcatFormatter();

var expected = new StringBuilder();
InterpolatedStringBuilder actual = InterpolatedStringBuilder.Create(0, 0, provider);
DefaultInterpolatedStringHandler actual = new DefaultInterpolatedStringHandler(0, 0, provider);

foreach (string s in new[] { null, "", "a" })
{
Expand Down Expand Up @@ -169,7 +164,7 @@ public void AppendFormatted_String_ICustomFormatter()
public void AppendFormatted_ReferenceTypes()
{
var expected = new StringBuilder();
InterpolatedStringBuilder actual = InterpolatedStringBuilder.Create(0, 0);
DefaultInterpolatedStringHandler actual = new DefaultInterpolatedStringHandler(0, 0);

foreach (string rawInput in new[] { null, "", "a", "bc", "def", "this is a longer string", "!" })
{
Expand Down Expand Up @@ -231,22 +226,22 @@ public void AppendFormatted_ReferenceTypes()
public void AppendFormatted_ReferenceTypes_CreateProviderFlowed(bool useScratch)
{
var provider = new CultureInfo("en-US");
InterpolatedStringBuilder builder = useScratch ?
InterpolatedStringBuilder.Create(1, 2, provider, stackalloc char[16]) :
InterpolatedStringBuilder.Create(1, 2, provider);
DefaultInterpolatedStringHandler handler = useScratch ?
new DefaultInterpolatedStringHandler(1, 2, provider, stackalloc char[16]) :
new DefaultInterpolatedStringHandler(1, 2, provider);

foreach (IHasToStringState tss in new IHasToStringState[] { new FormattableStringWrapper("hello"), new SpanFormattableStringWrapper("hello") })
{
builder.AppendFormatted(tss);
handler.AppendFormatted(tss);
Assert.Same(provider, tss.ToStringState.LastProvider);

builder.AppendFormatted(tss, 1);
handler.AppendFormatted(tss, 1);
Assert.Same(provider, tss.ToStringState.LastProvider);

builder.AppendFormatted(tss, "X2");
handler.AppendFormatted(tss, "X2");
Assert.Same(provider, tss.ToStringState.LastProvider);

builder.AppendFormatted(tss, 1, "X2");
handler.AppendFormatted(tss, 1, "X2");
Assert.Same(provider, tss.ToStringState.LastProvider);
}
}
Expand All @@ -257,7 +252,7 @@ public void AppendFormatted_ReferenceTypes_ICustomFormatter()
var provider = new ConcatFormatter();

var expected = new StringBuilder();
InterpolatedStringBuilder actual = InterpolatedStringBuilder.Create(0, 0, provider);
DefaultInterpolatedStringHandler actual = new DefaultInterpolatedStringHandler(0, 0, provider);

foreach (string s in new[] { null, "", "a" })
{
Expand Down Expand Up @@ -301,7 +296,7 @@ public void AppendFormatted_ValueTypes()
void Test<T>(T t)
{
var expected = new StringBuilder();
InterpolatedStringBuilder actual = InterpolatedStringBuilder.Create(0, 0);
DefaultInterpolatedStringHandler actual = new DefaultInterpolatedStringHandler(0, 0);

// struct
expected.AppendFormat("{0}", t);
Expand Down Expand Up @@ -347,20 +342,20 @@ public void AppendFormatted_ValueTypes_CreateProviderFlowed(bool useScratch)
void Test<T>(T t)
{
var provider = new CultureInfo("en-US");
InterpolatedStringBuilder builder = useScratch ?
InterpolatedStringBuilder.Create(1, 2, provider, stackalloc char[16]) :
InterpolatedStringBuilder.Create(1, 2, provider);
DefaultInterpolatedStringHandler handler = useScratch ?
new DefaultInterpolatedStringHandler(1, 2, provider, stackalloc char[16]) :
new DefaultInterpolatedStringHandler(1, 2, provider);

builder.AppendFormatted(t);
handler.AppendFormatted(t);
Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider);

builder.AppendFormatted(t, 1);
handler.AppendFormatted(t, 1);
Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider);

builder.AppendFormatted(t, "X2");
handler.AppendFormatted(t, "X2");
Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider);

builder.AppendFormatted(t, 1, "X2");
handler.AppendFormatted(t, 1, "X2");
Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider);
}

Expand All @@ -385,7 +380,7 @@ void AssertTss(T tss, string format)
}

var expected = new StringBuilder();
InterpolatedStringBuilder actual = InterpolatedStringBuilder.Create(0, 0, provider);
DefaultInterpolatedStringHandler actual = new DefaultInterpolatedStringHandler(0, 0, provider);

// struct
expected.AppendFormat(provider, "{0}", t);
Expand Down Expand Up @@ -422,20 +417,20 @@ void AssertTss(T tss, string format)
public void Grow_Large(bool useScratch)
{
var expected = new StringBuilder();
InterpolatedStringBuilder builder = useScratch ?
InterpolatedStringBuilder.Create(3, 1000, null, stackalloc char[16]) :
InterpolatedStringBuilder.Create(3, 1000);
DefaultInterpolatedStringHandler handler = useScratch ?
new DefaultInterpolatedStringHandler(3, 1000, null, stackalloc char[16]) :
new DefaultInterpolatedStringHandler(3, 1000);

for (int i = 0; i < 1000; i++)
{
builder.AppendFormatted(i);
handler.AppendFormatted(i);
expected.Append(i);

builder.AppendFormatted(i, 3);
handler.AppendFormatted(i, 3);
expected.AppendFormat("{0,3}", i);
}

Assert.Equal(expected.ToString(), builder.ToStringAndClear());
Assert.Equal(expected.ToString(), handler.ToStringAndClear());
}

private static void AssertModeMatchesType<T>(T tss) where T : IHasToStringState
Expand Down

0 comments on commit cb3b035

Please sign in to comment.