Created
June 7, 2022 11:50
-
-
Save PJB3005/acccec8f7cc889a7c32f924340fd74d7 to your computer and use it in GitHub Desktop.
Broken string interpolation handler analysis in Rider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics.CodeAnalysis; | |
using System.Runtime.CompilerServices; | |
using System.Text; | |
namespace Robust.Shared.Utility; | |
/// <summary> | |
/// Helpers for dealing with string formatting and related things. | |
/// </summary> | |
public static class FormatHelpers | |
{ | |
/// <summary> | |
/// Format a string interpolation into a given buffer. If the buffer is not large enough, the result is truncated. | |
/// </summary> | |
/// <remarks> | |
/// Assuming everything you're formatting with implements <see cref="ISpanFormattable"/>, this should be zero-alloc. | |
/// </remarks> | |
/// <param name="buffer">The buffer to format into.</param> | |
/// <param name="handler">String interpolation handler to implement buffer formatting logic.</param> | |
/// <returns>The amount of chars written into the buffer.</returns> | |
public static int FormatInto( | |
Span<char> buffer, | |
[InterpolatedStringHandlerArgument("buffer")] ref BufferInterpolatedStringHandler handler) | |
{ | |
return handler.Length; | |
} | |
/// <summary> | |
/// Format a string interpolation into a given memory buffer. | |
/// If the buffer is not large enough, the result is truncated. | |
/// </summary> | |
/// <param name="buffer">The memory buffer to format into.</param> | |
/// <param name="handler">String interpolation handler to implement buffer formatting logic.</param> | |
/// <returns>The region of memory filled by the formatting operation.</returns> | |
public static Memory<char> FormatIntoMem( | |
Memory<char> buffer, | |
[InterpolatedStringHandlerArgument("buffer")] ref BufferInterpolatedStringHandler handler) | |
{ | |
return buffer[..handler.Length]; | |
} | |
/// <summary> | |
/// Copy the contents of a <see cref="StringBuilder"/> into a memory buffer, giving back the subregion used. | |
/// If the buffer is not enough space, the result is truncated. | |
/// </summary> | |
/// <param name="builder">The <see cref="StringBuilder"/> to copy from</param> | |
/// <param name="memory">The memory buffer to copy into.</param> | |
/// <returns>The memory region actually used by the copied data.</returns> | |
public static Memory<char> BuilderToMemory(StringBuilder builder, Memory<char> memory) | |
{ | |
var truncLength = Math.Min(builder.Length, memory.Length); | |
builder.CopyTo(0, memory.Span, truncLength); | |
return memory[..builder.Length]; | |
} | |
} | |
/// <summary> | |
/// Interpolated string handler used by <see cref="FormatHelpers.FormatInto"/>. | |
/// </summary> | |
[InterpolatedStringHandler] | |
public ref struct BufferInterpolatedStringHandler | |
{ | |
private Span<char> _buffer; | |
internal int Length; | |
[SuppressMessage("ReSharper", "UnusedParameter.Local")] | |
public BufferInterpolatedStringHandler(int literalLength, int formattedCount, Span<char> buffer) | |
{ | |
_buffer = buffer; | |
Length = 0; | |
} | |
[SuppressMessage("ReSharper", "UnusedParameter.Local")] | |
public BufferInterpolatedStringHandler(int literalLength, int formattedCount, Memory<char> buffer) | |
{ | |
_buffer = buffer.Span; | |
Length = 0; | |
} | |
public void AppendLiteral(string literal) | |
{ | |
AppendString(literal); | |
} | |
public void AppendFormatted(ReadOnlySpan<char> value) | |
{ | |
AppendString(value); | |
} | |
[SuppressMessage("ReSharper", "MergeCastWithTypeCheck")] | |
public void AppendFormatted<T>(T value) | |
{ | |
if (value is ISpanFormattable) | |
{ | |
// JIT is able to avoid boxing due to call structure. | |
((ISpanFormattable)value).TryFormat(_buffer, out var written, default, null); | |
Advance(written); | |
return; | |
} | |
var str = value?.ToString(); | |
if (str != null) | |
AppendString(str); | |
} | |
[SuppressMessage("ReSharper", "MergeCastWithTypeCheck")] | |
public void AppendFormatted<T>(T value, string format) | |
{ | |
string? str; | |
if (value is IFormattable) | |
{ | |
if (value is ISpanFormattable) | |
{ | |
// JIT is able to avoid boxing due to call structure. | |
((ISpanFormattable)value).TryFormat(_buffer, out var written, format, null); | |
Advance(written); | |
return; | |
} | |
// JIT is able to avoid boxing due to call structure. | |
str = ((IFormattable)value).ToString(format, null); | |
} | |
else | |
{ | |
str = value?.ToString(); | |
} | |
if (str != null) | |
AppendString(str); | |
} | |
private void AppendString(ReadOnlySpan<char> value) | |
{ | |
var copyLength = Math.Min(_buffer.Length, value.Length); | |
value[..copyLength].CopyTo(_buffer); | |
Advance(copyLength); | |
} | |
private void Advance(int amount) | |
{ | |
_buffer = _buffer[amount..]; | |
Length += amount; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment