From f24c6a08dd386c8833874e8622e2ee49a39b864d Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Mon, 13 Feb 2023 23:09:50 -0800
Subject: [PATCH] fix: Fixes string interpolation in value string builder
(#1339)
---
.../Tests/Buffers/ValueStringBuilderTests.cs | 62 ++++---
Projects/Server/Buffers/ValueStringBuilder.cs | 172 ++----------------
.../Buffers/ValueStringBuilderExtensions.cs | 32 ----
Projects/Server/Client/ClientVersion.cs | 2 +-
.../Server/Localization/LocalizationEntry.cs | 15 +-
Projects/UOContent/Compression/TarArchive.cs | 15 +-
Projects/UOContent/Items/Misc/Teleporter.cs | 2 +-
Projects/UOContent/Misc/ClientVerification.cs | 26 +--
Projects/UOContent/Skills/ForensicEval.cs | 3 +-
9 files changed, 87 insertions(+), 242 deletions(-)
delete mode 100644 Projects/Server/Buffers/ValueStringBuilderExtensions.cs
diff --git a/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs b/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs
index 462b318e1..7e5c17795 100644
--- a/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs
+++ b/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs
@@ -1,34 +1,48 @@
using Server.Buffers;
using Xunit;
-namespace Server.Tests.Buffers
+namespace Server.Tests.Buffers;
+
+public class ValueStringBuilderTests
{
- public class ValueStringBuilderTests
+ [Theory]
+ [InlineData("Admin Kamron", "Kamron", 0, 6)]
+ [InlineData("Admin Kamron", "Admin ron", 6, 3)]
+ [InlineData("Admin Kamron", "Admin", 5, 7)]
+ public void TestRemove(string original, string removed, int startIndex, int length)
{
- [Theory]
- [InlineData("Admin Kamron", "Kamron", 0, 6)]
- [InlineData("Admin Kamron", "Admin ron", 6, 3)]
- [InlineData("Admin Kamron", "Admin", 5, 7)]
- public void TestRemove(string original, string removed, int startIndex, int length)
- {
- using var sb = new ValueStringBuilder(stackalloc char[64]);
- sb.Append(original);
- sb.Remove(startIndex, length);
+ using var sb = new ValueStringBuilder(stackalloc char[64]);
+ sb.Append(original);
+ sb.Remove(startIndex, length);
- Assert.Equal(removed, sb.ToString());
- }
+ Assert.Equal(removed, sb.ToString());
+ }
- [Theory]
- [InlineData(8)]
- [InlineData(9876)]
- [InlineData(-5)]
- [InlineData(-130984209)]
- public void TestAppendInt32(int value)
- {
- using var sb = new ValueStringBuilder(stackalloc char[64]);
- sb.Append(value);
+ [Theory]
+ [InlineData(8)]
+ [InlineData(9876)]
+ [InlineData(-5)]
+ [InlineData(-130984209)]
+ public void TestAppendInt32(int value)
+ {
+ using var sb = new ValueStringBuilder(stackalloc char[64]);
+ sb.Append(value);
- Assert.Equal(value.ToString(), sb.ToString());
- }
+ Assert.Equal(value.ToString(), sb.ToString());
+ }
+
+ [Theory]
+ [InlineData("Kamron")]
+ [InlineData("")]
+ [InlineData(5)]
+ [InlineData(-30.6)]
+ public void TestAppendInterpolation(object value)
+ {
+ var sb = ValueStringBuilder.Create();
+ sb.Append( $"Hi, this is {value}.");
+ sb.Append(" I am a string.");
+
+ Assert.Equal($"Hi, this is {value}. I am a string.", sb.ToString());
+ sb.Dispose();
}
}
diff --git a/Projects/Server/Buffers/ValueStringBuilder.cs b/Projects/Server/Buffers/ValueStringBuilder.cs
index b5a0e8c24..6b7746b24 100644
--- a/Projects/Server/Buffers/ValueStringBuilder.cs
+++ b/Projects/Server/Buffers/ValueStringBuilder.cs
@@ -208,6 +208,17 @@ public ref struct ValueStringBuilder
}
}
+ // Compiler generated
+ public void Append(ref RawInterpolatedStringHandler handler) => Append(handler.Text);
+
+ // Compiler generated
+ public void Append(
+ IFormatProvider? formatProvider,
+ [InterpolatedStringHandlerArgument("formatProvider")]
+ ref RawInterpolatedStringHandler handler
+ ) => Append(handler.Text);
+
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(string? s)
{
@@ -450,165 +461,4 @@ public ref struct ValueStringBuilder
_length -= length;
}
-
- /// Provides a handler used by the language compiler to append interpolated strings into instances.
- [InterpolatedStringHandler]
- public ref struct AppendInterpolatedStringHandler
- {
- // Implementation note:
- // As this type is only intended to be targeted by the compiler, public APIs eschew argument validation logic
- // in a variety of places, e.g. allowing a null input when one isn't expected to produce a NullReferenceException rather
- // than an ArgumentNullException.
-
- /// The associated StringBuilder to which to append.
- internal ValueStringBuilder _stringBuilder;
-
- /// Creates a handler used to append an interpolated string into a .
- /// The number of constant characters outside of interpolation expressions in the interpolated string.
- /// The number of interpolation expressions in the interpolated string.
- /// The associated StringBuilder to which to append.
- /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.
- public AppendInterpolatedStringHandler(int literalLength, int formattedCount, ValueStringBuilder stringBuilder)
- {
- _stringBuilder = stringBuilder;
- }
-
- /// Writes the specified string to the handler.
- /// The string to write.
- public void AppendLiteral(string value) => _stringBuilder.Append(value);
-
- // Design note:
- // This provides the same set of overloads and semantics as DefaultInterpolatedStringHandler.
-
- /// Writes the specified value to the handler.
- /// The value to write.
- public void AppendFormatted(T value) => _stringBuilder.Append(value);
-
- /// Writes the specified value to the handler.
- /// The value to write.
- /// The format string.
- public void AppendFormatted(T value, string? format) => _stringBuilder.Append(value, format);
-
- /// Writes the specified value to the handler.
- /// The value to write.
- /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.
- public void AppendFormatted(T value, int alignment) =>
- AppendFormatted(value, alignment, format: null);
-
- /// Writes the specified value to the handler.
- /// The value to write.
- /// The format string.
- /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.
- public void AppendFormatted(T value, int alignment, string? format)
- {
- if (alignment == 0)
- {
- // This overload is used as a fallback from several disambiguation overloads, so special-case 0.
- AppendFormatted(value, format);
- }
- else if (alignment < 0)
- {
- // Left aligned: format into the handler, then append any additional padding required.
- int start = _stringBuilder.Length;
- AppendFormatted(value, format);
- int paddingRequired = -alignment - (_stringBuilder.Length - start);
- if (paddingRequired > 0)
- {
- _stringBuilder.Append(' ', paddingRequired);
- }
- }
- else
- {
- var startingPos = _stringBuilder._length;
- AppendFormatted(value, format);
-
- InsertAlignment(startingPos, alignment);
- }
- }
-
- /// Writes the specified character span to the handler.
- /// The span to write.
- public void AppendFormatted(ReadOnlySpan value) => _stringBuilder.Append(value);
-
- /// Writes the specified string of chars to the handler.
- /// The span to write.
- /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.
- /// The format string.
- public void AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null)
- {
- if (alignment == 0)
- {
- _stringBuilder.Append(value);
- }
- else
- {
- bool leftAlign = false;
- if (alignment < 0)
- {
- leftAlign = true;
- alignment = -alignment;
- }
-
- int paddingRequired = alignment - value.Length;
- if (paddingRequired <= 0)
- {
- _stringBuilder.Append(value);
- }
- else if (leftAlign)
- {
- _stringBuilder.Append(value);
- _stringBuilder.Append(' ', paddingRequired);
- }
- else
- {
- _stringBuilder.Append(' ', paddingRequired);
- _stringBuilder.Append(value);
- }
- }
- }
-
- /// Writes the specified value to the handler.
- /// The value to write.
- public void AppendFormatted(string? value) => _stringBuilder.Append(value);
-
- /// Writes the specified value to the handler.
- /// The value to write.
- /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.
- /// The format string.
- public void AppendFormatted(string? value, int alignment = 0, string? format = null) =>
- // Format is meaningless for strings and doesn't make sense for someone to specify. We have the overload
- // simply to disambiguate between ROS and object, just in case someone does specify a format, as
- // string is implicitly convertible to both. Just delegate to the T-based implementation.
- AppendFormatted(value, alignment, format);
-
- /// Writes the specified value to the handler.
- /// The value to write.
- /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.
- /// The format string.
- public void AppendFormatted(object? value, int alignment = 0, string? format = null) =>
- // This overload is expected to be used rarely, only if either a) something strongly typed as object is
- // formatted with both an alignment and a format, or b) the compiler is unable to target type to T. It
- // exists purely to help make cases from (b) compile. Just delegate to the T-based implementation.
- AppendFormatted