Blittable strings generator implementation

Improvements to fixed array generator
This commit is contained in:
2026-01-27 14:15:41 +01:00
parent dbdf5cfbe2
commit 2c918cf4a4
3 changed files with 194 additions and 30 deletions
@@ -0,0 +1,142 @@
using Microsoft.CodeAnalysis;
using Sybil;
using System.Text;
using System.Linq;
namespace System.Extensions;
#nullable enable
[Generator(LanguageNames.CSharp)]
public class BlittableStringGenerator : IIncrementalGenerator
{
private const string UsingSystem = "System";
private const string UsingSystemRuntimeInterop = "System.Runtime.InteropServices";
private const string AttributeNamespace = "System.Extensions";
private const string AttributeName = "GenerateBlittableStringAttribute";
private const string AttributeShortName = "GenerateBlittableString";
private const string String = "String";
private const string Public = "public";
private const string Required = "required";
private const string Size = "Size";
private const string Int = "int";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(context =>
{
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
.WithNamespace(
SyntaxBuilder.CreateNamespace(AttributeNamespace)
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
.WithModifier(Public)
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
.WithModifier(Public))
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
.WithArgument(AttributeTargets.Assembly)
.WithArgument("Inherited", false)
.WithArgument("AllowMultiple", true))
.WithProperty(SyntaxBuilder.CreateProperty(Int, Size)
.WithModifier(Public)
.WithModifier(Required)
.WithAccessor(SyntaxBuilder.CreateGetter())
.WithAccessor(SyntaxBuilder.CreateSetter()))
.WithBaseClass(nameof(Attribute))));
var compilationUnitSyntax = compilationUnitBuilder.Build();
var source = compilationUnitSyntax.ToFullString();
context.AddSource($"{AttributeName}.g", source);
});
context.RegisterSourceOutput(context.CompilationProvider, (sourceProductionContext, compilation) =>
{
var assemblyAttributes =
compilation.Assembly.GetAttributes().Where(a => a.AttributeClass != null &&
(a.AttributeClass.Name == AttributeName || a.AttributeClass.Name == AttributeShortName));
foreach (var attr in assemblyAttributes)
{
// Extract the Size parameter from the named arguments.
int? fixedSize = default;
foreach (var arg in attr.NamedArguments)
{
if (arg.Key == Size && arg.Value.Value is int intValue)
{
fixedSize = intValue;
break;
}
}
if (fixedSize is null)
{
// As Size is required, skip if not provided.
continue;
}
Execute(sourceProductionContext, (int)fixedSize);
}
});
}
private static void Execute(SourceProductionContext sourceProductionContext, int size)
{
var structName = $"{String}{size}";
var builder = new StringBuilder();
builder.AppendLine($"using {UsingSystem};");
builder.AppendLine($"using {UsingSystemRuntimeInterop};");
builder.AppendLine();
builder.AppendLine($"namespace {AttributeNamespace};");
builder.AppendLine();
builder.AppendLine("[StructLayout(LayoutKind.Sequential, Pack = 1)]");
builder.AppendLine($"public unsafe partial struct {structName}");
builder.AppendLine("{");
builder.AppendLine($" public const int Size = {size};");
builder.AppendLine();
builder.AppendLine(" public fixed char Value[Size];");
builder.AppendLine();
builder.AppendLine(" public Span<char> AsSpan()");
builder.AppendLine(" {");
builder.AppendLine(" fixed (char* ptr = this.Value)");
builder.AppendLine(" {");
builder.AppendLine(" return MemoryMarshal.CreateSpan(ref *ptr, Size);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(" public readonly ReadOnlySpan<char> AsReadOnlySpan()");
builder.AppendLine(" {");
builder.AppendLine(" fixed (char* ptr = this.Value)");
builder.AppendLine(" {");
builder.AppendLine(" return new ReadOnlySpan<char>(ptr, Size);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(" public void Set(ReadOnlySpan<char> value)");
builder.AppendLine(" {");
builder.AppendLine(" fixed (char* ptr = this.Value)");
builder.AppendLine(" {");
builder.AppendLine(" var span = MemoryMarshal.CreateSpan(ref *ptr, Size);");
builder.AppendLine(" span.Clear();");
builder.AppendLine(" value[..Math.Min(value.Length, Size - 1)].CopyTo(span);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(" public override readonly string ToString()");
builder.AppendLine(" {");
builder.AppendLine(" var span = this.AsReadOnlySpan();");
builder.AppendLine(" var nullIndex = span.IndexOf('\\0');");
builder.AppendLine(" return nullIndex >= 0 ? new string(span[..nullIndex]) : new string(span);");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public static implicit operator string({structName} value) => value.ToString();");
builder.AppendLine();
builder.AppendLine($" public static implicit operator {structName}(string value)");
builder.AppendLine(" {");
builder.AppendLine($" var result = default({structName});");
builder.AppendLine(" result.Set(value);");
builder.AppendLine(" return result;");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public static implicit operator ReadOnlySpan<char>({structName} value) => value.AsReadOnlySpan();");
builder.AppendLine("}");
sourceProductionContext.AddSource($"{structName}.g.cs", builder.ToString());
}
}
#nullable disable
@@ -96,8 +96,8 @@ public class FixedArrayGenerator : IIncrementalGenerator
builder.AppendLine($"using {UsingSystem};"); builder.AppendLine($"using {UsingSystem};");
builder.AppendLine($"using {UsingSystemRuntimeInterop};"); builder.AppendLine($"using {UsingSystemRuntimeInterop};");
builder.AppendLine(); builder.AppendLine();
builder.AppendLine($"namespace {AttributeNamespace}"); builder.AppendLine($"namespace {AttributeNamespace};");
builder.AppendLine("{"); builder.AppendLine();
builder.AppendLine("[StructLayout(LayoutKind.Sequential, Pack = 1)]"); builder.AppendLine("[StructLayout(LayoutKind.Sequential, Pack = 1)]");
builder.AppendLine($"public unsafe struct {structName}"); builder.AppendLine($"public unsafe struct {structName}");
builder.AppendLine("{"); builder.AppendLine("{");
@@ -111,11 +111,8 @@ public class FixedArrayGenerator : IIncrementalGenerator
builder.AppendLine(" {"); builder.AppendLine(" {");
builder.AppendLine(" get"); builder.AppendLine(" get");
builder.AppendLine(" {"); builder.AppendLine(" {");
builder.AppendLine(" if (index >= Size) throw new IndexOutOfRangeException();"); builder.AppendLine(" if ((uint)index >= Size) throw new IndexOutOfRangeException();");
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)"); builder.AppendLine($" return ref this.elements[index];");
builder.AppendLine(" {");
builder.AppendLine(" return ref ptr[index];");
builder.AppendLine(" }");
builder.AppendLine(" }"); builder.AppendLine(" }");
builder.AppendLine(" }"); builder.AppendLine(" }");
builder.AppendLine(); builder.AppendLine();
@@ -123,18 +120,43 @@ public class FixedArrayGenerator : IIncrementalGenerator
builder.AppendLine(" {"); builder.AppendLine(" {");
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)"); builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
builder.AppendLine(" {"); builder.AppendLine(" {");
builder.AppendLine($" return new Span<{targetType}>(ptr, Size);"); builder.AppendLine($" return MemoryMarshal.CreateSpan(ref *ptr, Size);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public readonly ReadOnlySpan<{targetType}> AsReadOnlySpan()");
builder.AppendLine(" {");
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
builder.AppendLine(" {");
builder.AppendLine($" return new ReadOnlySpan<{targetType}>(ptr, Size);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public void Set(ReadOnlySpan<{targetType}> value)");
builder.AppendLine(" {");
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
builder.AppendLine(" {");
builder.AppendLine($" var span = MemoryMarshal.CreateSpan(ref *ptr, Size);");
builder.AppendLine(" span.Clear();");
builder.AppendLine(" value[..Math.Min(value.Length, Size)].CopyTo(span);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public void Clear()");
builder.AppendLine(" {");
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
builder.AppendLine(" {");
builder.AppendLine($" MemoryMarshal.CreateSpan(ref *ptr, Size).Clear();");
builder.AppendLine(" }"); builder.AppendLine(" }");
builder.AppendLine(" }"); builder.AppendLine(" }");
builder.AppendLine(); builder.AppendLine();
builder.AppendLine($" public static {structName} From{capitalizedType}Array({targetType}[] array)"); builder.AppendLine($" public static {structName} From{capitalizedType}Array({targetType}[] array)");
builder.AppendLine(" {"); builder.AppendLine(" {");
builder.AppendLine($" var arr = default({structName});"); builder.AppendLine($" var arr = default({structName});");
builder.AppendLine(" array.AsSpan().CopyTo(arr.AsSpan());"); builder.AppendLine(" arr.Set(array);");
builder.AppendLine(" return arr;"); builder.AppendLine(" return arr;");
builder.AppendLine(" }"); builder.AppendLine(" }");
builder.AppendLine("}"); builder.AppendLine("}");
builder.AppendLine("}");
sourceProductionContext.AddSource($"{structName}.g.cs", builder.ToString()); sourceProductionContext.AddSource($"{structName}.g.cs", builder.ToString());
} }
@@ -5,7 +5,7 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Alexandru Macocian</Authors> <Authors>Alexandru Macocian</Authors>
<Description>Source generators extensions for netstandard2.0.</Description> <Description>Source generators extensions for netstandard2.0.</Description>
<Version>0.1.6</Version> <Version>0.1.7</Version>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<EnableNETAnalyzers>true</EnableNETAnalyzers> <EnableNETAnalyzers>true</EnableNETAnalyzers>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>