Skip to content

Commit

Permalink
Fix support for generic ref struct
Browse files Browse the repository at this point in the history
  • Loading branch information
afxres committed Nov 21, 2024
1 parent 7d51e20 commit a17e8d1
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 8 deletions.
113 changes: 113 additions & 0 deletions code/Binary.Tests/Contexts/AllocatorAllowsReferenceStructureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#if NET9_0_OR_GREATER
namespace Mikodev.Binary.Tests.Contexts;

using System;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using System.Text;
using Xunit;

public class AllocatorAllowsReferenceStructureTests
{
private ref struct TestReferenceStructure<T>
{
public ref T Location;
}

[Theory(DisplayName = "Append Test (known length)")]
[InlineData(0xa)]
[InlineData(0xbeef)]
public void AppendTest(int data)
{
static void Invoke(ref Allocator allocator, int data)
{
var testStructure = new TestReferenceStructure<int> { Location = ref data };
Allocator.Append(ref allocator, sizeof(int), testStructure, static (span, status) =>
{
BinaryPrimitives.WriteInt32LittleEndian(span, status.Location);
});
var result = BinaryPrimitives.ReadInt32LittleEndian(allocator.AsSpan());
Assert.Equal(data, result);
}
var allocator = new Allocator();
Invoke(ref allocator, data);
}

[Theory(DisplayName = "Append Test (unknown length)")]
[InlineData("")]
[InlineData("babe")]
public void AppendTestUnknownLength(string data)
{
static void Invoke(ref Allocator allocator, string data)
{
var testStructure = new TestReferenceStructure<string> { Location = ref data };
Allocator.Append(ref allocator, Encoding.UTF8.GetMaxByteCount(data.Length), testStructure, static (span, status) =>
{
return Encoding.UTF8.GetBytes(status.Location, span);
});
var result = Encoding.UTF8.GetString(allocator.AsSpan());
Assert.Equal(data, result);
}
var allocator = new Allocator();
Invoke(ref allocator, data);
}

[Theory(DisplayName = "Append With Length Prefix Test (allocator writer)")]
[InlineData("")]
[InlineData("dead")]
public void AppendWithLengthPrefixTest(string data)
{
static void Invoke(ref Allocator allocator, string data)
{
var testStructure = new TestReferenceStructure<string> { Location = ref data };
Allocator.AppendWithLengthPrefix(ref allocator, Encoding.UTF8.GetMaxByteCount(data.Length), testStructure, static (span, status) =>
{
return Encoding.UTF8.GetBytes(status.Location, span);
});
var buffer = allocator.AsSpan();
var length = Converter.Decode(ref buffer);
Assert.Equal(length, buffer.Length);
var result = Encoding.UTF8.GetString(buffer);
Assert.Equal(data, result);
}
var allocator = new Allocator();
Invoke(ref allocator, data);
}

[Theory(DisplayName = "Append With Length Prefix Test (allocator action)")]
[InlineData("")]
[InlineData("cafe")]
public void AppendWithLengthPrefixAllocatorActionTest(string data)
{
static void Invoke(ref Allocator allocator, string data)
{
var testStructure = new TestReferenceStructure<string> { Location = ref data };
Allocator.AppendWithLengthPrefix(ref allocator, testStructure, static (ref Allocator allocator, scoped TestReferenceStructure<string> status) =>
{
Allocator.Append(ref allocator, MemoryMarshal.AsBytes(status.Location.AsSpan()));
});
var buffer = allocator.AsSpan();
var length = Converter.Decode(ref buffer);
Assert.Equal(length, buffer.Length);
var result = MemoryMarshal.Cast<byte, char>(buffer).ToString();
Assert.Equal(data, result);
}
var allocator = new Allocator();
Invoke(ref allocator, data);
}

[Theory(DisplayName = "Invoke Test")]
[InlineData("")]
[InlineData("cafe")]
public void InvokeTest(string data)
{
var testStructure = new TestReferenceStructure<string> { Location = ref data };
var buffer = Allocator.Invoke(testStructure, static (ref Allocator allocator, scoped TestReferenceStructure<string> status) =>
{
Allocator.Append(ref allocator, status.Location, Encoding.UTF32);
});
var result = Encoding.UTF32.GetString(buffer);
Assert.Equal(data, result);
}
}
#endif
9 changes: 7 additions & 2 deletions code/Binary.Tests/Features/AllowsReferenceStructureTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@ public class AllowsReferenceStructureTests
public void AllocatorMethodAllowsReferenceStructureTest()
{
var methods = typeof(Allocator).GetMethods(BindingFlags.Public | BindingFlags.Static).Where(x => x.IsGenericMethod).ToImmutableArray();
foreach (var i in methods)
foreach (var method in methods)
{
var genericArgument = Assert.Single(i.GetGenericArguments());
var genericArgument = Assert.Single(method.GetGenericArguments());
var genericArgumentAttributes = genericArgument.GenericParameterAttributes;
Assert.True((genericArgumentAttributes | GenericParameterAttributes.AllowByRefLike) is not 0);
var parameters = method.GetParameters();
var parameter = parameters.Single(x => x.ParameterType == genericArgument);
var attributes = parameter.GetCustomAttributes();
var attribute = Assert.Single(attributes, x => x.GetType().Name is "ScopedRefAttribute");
Assert.Equal("System.Runtime.CompilerServices", attribute.GetType().Namespace);
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions code/Binary/Allocator.Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static void Append(ref Allocator allocator, scoped ReadOnlySpan<byte> spa
}

#if NET9_0_OR_GREATER
public static void Append<T>(ref Allocator allocator, int length, T data, SpanAction<byte, T> action) where T : allows ref struct
public static void Append<T>(ref Allocator allocator, int length, scoped T data, SpanAction<byte, T> action) where T : allows ref struct
#else
public static void Append<T>(ref Allocator allocator, int length, T data, SpanAction<byte, T> action)
#endif
Expand All @@ -37,7 +37,7 @@ public static void Append<T>(ref Allocator allocator, int length, T data, SpanAc
}

#if NET9_0_OR_GREATER
public static void Append<T>(ref Allocator allocator, int maxLength, T data, AllocatorWriter<T> writer) where T : allows ref struct
public static void Append<T>(ref Allocator allocator, int maxLength, scoped T data, AllocatorWriter<T> writer) where T : allows ref struct
#else
public static void Append<T>(ref Allocator allocator, int maxLength, T data, AllocatorWriter<T> writer)
#endif
Expand All @@ -56,7 +56,7 @@ public static void Append<T>(ref Allocator allocator, int maxLength, T data, All
}

#if NET9_0_OR_GREATER
public static void AppendWithLengthPrefix<T>(ref Allocator allocator, int maxLength, T data, AllocatorWriter<T> writer) where T : allows ref struct
public static void AppendWithLengthPrefix<T>(ref Allocator allocator, int maxLength, scoped T data, AllocatorWriter<T> writer) where T : allows ref struct
#else
public static void AppendWithLengthPrefix<T>(ref Allocator allocator, int maxLength, T data, AllocatorWriter<T> writer)
#endif
Expand All @@ -76,7 +76,7 @@ public static void AppendWithLengthPrefix<T>(ref Allocator allocator, int maxLen

[MethodImpl(MethodImplOptions.AggressiveInlining)]
#if NET9_0_OR_GREATER
public static void AppendWithLengthPrefix<T>(ref Allocator allocator, T data, AllocatorAction<T> action) where T : allows ref struct
public static void AppendWithLengthPrefix<T>(ref Allocator allocator, scoped T data, AllocatorAction<T> action) where T : allows ref struct
#else
public static void AppendWithLengthPrefix<T>(ref Allocator allocator, T data, AllocatorAction<T> action)
#endif
Expand Down Expand Up @@ -104,7 +104,7 @@ public static void Expand(ref Allocator allocator, int length)
}

#if NET9_0_OR_GREATER
public static byte[] Invoke<T>(T data, AllocatorAction<T> action) where T : allows ref struct
public static byte[] Invoke<T>(scoped T data, AllocatorAction<T> action) where T : allows ref struct
#else
public static byte[] Invoke<T>(T data, AllocatorAction<T> action)
#endif
Expand Down
2 changes: 1 addition & 1 deletion code/Binary/AllocatorAction.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Mikodev.Binary;

#if NET9_0_OR_GREATER
public delegate void AllocatorAction<in T>(ref Allocator allocator, T data) where T : allows ref struct;
public delegate void AllocatorAction<in T>(ref Allocator allocator, scoped T data) where T : allows ref struct;
#else
public delegate void AllocatorAction<in T>(ref Allocator allocator, T data);
#endif

0 comments on commit a17e8d1

Please sign in to comment.