From 7214719eb5b83d782c820cedb6ad77185a31f703 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Mar 2026 01:11:28 +0000
Subject: [PATCH 01/13] Add BOIS source generator
Agent-Logs-Url: https://github.com/salarcode/Bois/sessions/f9c5b5a9-370b-4a0d-8898-6ba042ead9bf
Co-authored-by: salarcode <1272095+salarcode@users.noreply.github.com>
---
CodeGenSample/CodeGenSample.csproj | 6 +
.../Models/CompanyModel-generated.cs | 87 --
CodeGenSample/Models/CompanyModel.cs | 83 +-
Salar.Bois.Generator/BoisSourceGenerator.cs | 1235 +++++++++++++++++
.../Salar.Bois.Generator.csproj | 3 +
.../Serializers/BoisNumericSerializers.cs | 24 +
6 files changed, 1269 insertions(+), 169 deletions(-)
delete mode 100644 CodeGenSample/Models/CompanyModel-generated.cs
create mode 100644 Salar.Bois.Generator/BoisSourceGenerator.cs
diff --git a/CodeGenSample/CodeGenSample.csproj b/CodeGenSample/CodeGenSample.csproj
index fd4bd08..5d0d6f7 100644
--- a/CodeGenSample/CodeGenSample.csproj
+++ b/CodeGenSample/CodeGenSample.csproj
@@ -7,4 +7,10 @@
enable
+
+
+
+
diff --git a/CodeGenSample/Models/CompanyModel-generated.cs b/CodeGenSample/Models/CompanyModel-generated.cs
deleted file mode 100644
index ff5b2bb..0000000
--- a/CodeGenSample/Models/CompanyModel-generated.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using Salar.BinaryBuffers;
-using Salar.BinaryBuffers.Compatibility;
-using Salar.Bois.Generator.Serializers;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CodeGenSample.Models;
-
-public static partial class CompanyModelBois
-{
- public static partial CompanyModel? ReadCompanyModel(Stream source)
- {
- BufferReaderBase reader = new StreamBufferReader(source);
-
- // Member count, if null then the whole object is null, otherwise the member count is not used in source gen models
- var memCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);
- if (memCount is null)
- return null;
-
- var model = new CompanyModel();
-
- model.Id = BoisPrimitiveReaders.ReadGuid(reader); // Id
- model.Name = BoisPrimitiveReaders.ReadString(reader, System.Text.Encoding.UTF8);
- model.Address = BoisPrimitiveReaders.ReadString(reader, System.Text.Encoding.UTF8);
- model.Phone = BoisPrimitiveReaders.ReadString(reader, System.Text.Encoding.UTF8);
- model.Founded = BoisPrimitiveReaders.ReadDateTime(reader);
- model.Revenue = BoisNumericSerializers.ReadVarDecimal(reader);
- model.IsActive = BoisPrimitiveReaders.ReadBoolean(reader);
-
- // Read the list of employees
- var employeesCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);
- if (employeesCount is not null)
- {
- var employees = model.Employees;
- employees.Clear();
- for (int i = 0; i < employeesCount; i++)
- {
- var employee = BoisPrimitiveReaders.ReadString(reader, System.Text.Encoding.UTF8);
- employees.Add(employee);
- }
- }
-
- _ = BoisNumericSerializers.ReadVarInt32(reader); // EmployeesCount, not used in source gen models since we have the list
-
- return model;
- }
-
- public static partial void WriteCompanyModel(Stream output, CompanyModel? model)
- {
- var writer = new StreamBufferWriter(output);
- if (model is null)
- {
- BoisPrimitiveWriters.WriteNullValue(writer);
- return;
- }
-
- // Write `CompanyModel` member count (properties + fields)
- BoisNumericSerializers.WriteUIntNullableMemberCount(writer, 9u);
-
- // Write the members in the same order as they are defined in the class
-
- BoisPrimitiveWriters.WriteValue(writer, model.Id);
- BoisPrimitiveWriters.WriteValue(writer, model.Name, System.Text.Encoding.UTF8);
- BoisPrimitiveWriters.WriteValue(writer, model.Address, System.Text.Encoding.UTF8);
- BoisPrimitiveWriters.WriteValue(writer, model.Phone, System.Text.Encoding.UTF8);
- BoisPrimitiveWriters.WriteValue(writer, model.Founded);
- BoisNumericSerializers.WriteVarDecimal(writer, model.Revenue);
- BoisPrimitiveWriters.WriteValue(writer, model.IsActive);
-
- // Write the list of employees
- if (model.Employees is null)
- {
- BoisPrimitiveWriters.WriteNullValue(writer);
- }
- else
- {
- BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint)model.Employees.Count);
- foreach (var member in model.Employees)
- {
- BoisPrimitiveWriters.WriteValue(writer, member, System.Text.Encoding.UTF8);
- }
- }
- BoisNumericSerializers.WriteVarInt(writer, model.EmployeesCount);
- }
diff --git a/CodeGenSample/Models/CompanyModel.cs b/CodeGenSample/Models/CompanyModel.cs
index dd64efd..f13a9f5 100644
--- a/CodeGenSample/Models/CompanyModel.cs
+++ b/CodeGenSample/Models/CompanyModel.cs
@@ -1,7 +1,4 @@
-using Salar.BinaryBuffers;
-using Salar.BinaryBuffers.Compatibility;
-using Salar.Bois.Generator.Attributes;
-using Salar.Bois.Generator.Serializers;
+using Salar.Bois.Generator.Attributes;
using System;
using System.Collections.Generic;
@@ -84,81 +81,3 @@ public static partial class CompanyModelBois
[BoisWriter]
public static partial void WriteCompanyModel(Stream output, CompanyModel? model);
}
-
-public static partial class CompanyModelBois
-{
- public static partial CompanyModel? ReadCompanyModel(Stream source)
- {
- BufferReaderBase reader = new StreamBufferReader(source);
-
- // Member count, if null then the whole object is null, otherwise the member count is not used in source gen models
- var memCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);
- if (memCount is null)
- return null;
-
- var model = new CompanyModel();
-
- model.Id = BoisPrimitiveReaders.ReadGuid(reader); // Id
- model.Name = BoisPrimitiveReaders.ReadString(reader, System.Text.Encoding.UTF8);
- model.Address = BoisPrimitiveReaders.ReadString(reader, System.Text.Encoding.UTF8);
- model.Phone = BoisPrimitiveReaders.ReadString(reader, System.Text.Encoding.UTF8);
- model.Founded = BoisPrimitiveReaders.ReadDateTime(reader);
- model.Revenue = BoisNumericSerializers.ReadVarDecimal(reader);
- model.IsActive = BoisPrimitiveReaders.ReadBoolean(reader);
-
- // Read the list of employees
- var employeesCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);
- if (employeesCount is not null)
- {
- var employees = model.Employees;
- employees.Clear();
- for (int i = 0; i < employeesCount; i++)
- {
- var employee = BoisPrimitiveReaders.ReadString(reader, System.Text.Encoding.UTF8);
- employees.Add(employee);
- }
- }
-
- _ = BoisNumericSerializers.ReadVarInt32(reader); // EmployeesCount, not used in source gen models since we have the list
-
- return model;
- }
-
- public static partial void WriteCompanyModel(Stream output, CompanyModel? model)
- {
- var writer = new StreamBufferWriter(output);
- if (model is null)
- {
- BoisPrimitiveWriters.WriteNullValue(writer);
- return;
- }
-
- // Write `CompanyModel` member count (properties + fields)
- BoisNumericSerializers.WriteUIntNullableMemberCount(writer, 9u);
-
- // Write the members in the same order as they are defined in the class
-
- BoisPrimitiveWriters.WriteValue(writer, model.Id);
- BoisPrimitiveWriters.WriteValue(writer, model.Name, System.Text.Encoding.UTF8);
- BoisPrimitiveWriters.WriteValue(writer, model.Address, System.Text.Encoding.UTF8);
- BoisPrimitiveWriters.WriteValue(writer, model.Phone, System.Text.Encoding.UTF8);
- BoisPrimitiveWriters.WriteValue(writer, model.Founded);
- BoisNumericSerializers.WriteVarDecimal(writer, model.Revenue);
- BoisPrimitiveWriters.WriteValue(writer, model.IsActive);
-
- // Write the list of employees
- if (model.Employees is null)
- {
- BoisPrimitiveWriters.WriteNullValue(writer);
- }
- else
- {
- BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint)model.Employees.Count);
- foreach (var member in model.Employees)
- {
- BoisPrimitiveWriters.WriteValue(writer, member, System.Text.Encoding.UTF8);
- }
- }
- BoisNumericSerializers.WriteVarInt(writer, model.EmployeesCount);
- }
-}
\ No newline at end of file
diff --git a/Salar.Bois.Generator/BoisSourceGenerator.cs b/Salar.Bois.Generator/BoisSourceGenerator.cs
new file mode 100644
index 0000000..e814da0
--- /dev/null
+++ b/Salar.Bois.Generator/BoisSourceGenerator.cs
@@ -0,0 +1,1235 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+
+namespace Salar.Bois.Generator;
+
+[Generator]
+public sealed class BoisSourceGenerator : ISourceGenerator
+{
+ private const string ReaderAttributeName = "Salar.Bois.Generator.Attributes.BoisReaderAttribute";
+ private const string WriterAttributeName = "Salar.Bois.Generator.Attributes.BoisWriterAttribute";
+
+ private static readonly DiagnosticDescriptor InvalidMethodSignature = new(
+ "BOISGEN001",
+ "Invalid BOIS generator method signature",
+ "{0}",
+ "Salar.Bois.Generator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ private static readonly DiagnosticDescriptor UnsupportedType = new(
+ "BOISGEN002",
+ "Unsupported BOIS generator type",
+ "{0}",
+ "Salar.Bois.Generator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ context.RegisterForSyntaxNotifications(static () => new SyntaxReceiver());
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ if (context.SyntaxReceiver is not SyntaxReceiver receiver || receiver.Candidates.Count == 0)
+ return;
+
+ var readerAttribute = context.Compilation.GetTypeByMetadataName(ReaderAttributeName);
+ var writerAttribute = context.Compilation.GetTypeByMetadataName(WriterAttributeName);
+ if (readerAttribute is null || writerAttribute is null)
+ return;
+
+ var groups = new Dictionary<(INamedTypeSymbol ContainingType, string FileName), List>(new GroupComparer());
+
+ foreach (var candidate in receiver.Candidates)
+ {
+ var semanticModel = context.Compilation.GetSemanticModel(candidate.SyntaxTree);
+ if (semanticModel.GetDeclaredSymbol(candidate, context.CancellationToken) is not IMethodSymbol method)
+ continue;
+
+ var operation = GetOperation(method, readerAttribute, writerAttribute);
+ if (operation is null)
+ continue;
+
+ if (!TryCreateGenerationMethod(method, operation.Value, context, out var generationMethod))
+ continue;
+
+ var key = (generationMethod.ContainingType, GetFileName(generationMethod.RootType));
+ if (!groups.TryGetValue(key, out var methods))
+ {
+ methods = [];
+ groups.Add(key, methods);
+ }
+ methods.Add(generationMethod);
+ }
+
+ foreach (var group in groups)
+ {
+ var source = EmitContainingType(context.Compilation, group.Key.ContainingType, group.Value.ToImmutableArray(), context.ReportDiagnostic);
+ if (!string.IsNullOrWhiteSpace(source))
+ context.AddSource(group.Key.FileName + "-generated.cs", SourceText.From(source, Encoding.UTF8));
+ }
+ }
+
+ private static OperationKind? GetOperation(IMethodSymbol method, INamedTypeSymbol readerAttribute, INamedTypeSymbol writerAttribute)
+ {
+ foreach (var attribute in method.GetAttributes())
+ {
+ if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, readerAttribute))
+ return OperationKind.Reader;
+ if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, writerAttribute))
+ return OperationKind.Writer;
+ }
+ return null;
+ }
+
+ private static bool TryCreateGenerationMethod(IMethodSymbol method, OperationKind operation, GeneratorExecutionContext context, out GenerationMethod generationMethod)
+ {
+ generationMethod = default!;
+
+ if (method.ContainingType is not INamedTypeSymbol containingType || !containingType.IsStatic || !IsPartial(containingType))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(InvalidMethodSignature, method.Locations.FirstOrDefault(), $"Method '{method.Name}' must be declared inside a static partial class."));
+ return false;
+ }
+
+ if (!method.IsStatic || !method.DeclaringSyntaxReferences.Any(static x => x.GetSyntax() is MethodDeclarationSyntax m && m.Modifiers.Any(SyntaxKind.PartialKeyword)))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(InvalidMethodSignature, method.Locations.FirstOrDefault(), $"Method '{method.Name}' must be declared as a static partial method."));
+ return false;
+ }
+
+ if (operation == OperationKind.Reader)
+ {
+ if (method.ReturnsVoid || method.Parameters.Length != 1 || !IsStream(method.Parameters[0].Type))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(InvalidMethodSignature, method.Locations.FirstOrDefault(), $"Reader method '{method.Name}' must have the signature 'static partial T Method(System.IO.Stream source)'."));
+ return false;
+ }
+
+ generationMethod = new GenerationMethod(method, containingType, operation, method.ReturnType);
+ return true;
+ }
+
+ if (!method.ReturnsVoid || method.Parameters.Length != 2 || !IsStream(method.Parameters[0].Type))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(InvalidMethodSignature, method.Locations.FirstOrDefault(), $"Writer method '{method.Name}' must have the signature 'static partial void Method(System.IO.Stream output, T model)'."));
+ return false;
+ }
+
+ generationMethod = new GenerationMethod(method, containingType, operation, method.Parameters[1].Type);
+ return true;
+ }
+
+ private static string EmitContainingType(Compilation compilation, INamedTypeSymbol containingType, ImmutableArray methods, Action report)
+ {
+ var emitter = new ContainingTypeEmitter(compilation, containingType, report);
+ return emitter.Emit(methods);
+ }
+
+ private static bool IsPartial(INamedTypeSymbol type)
+ => type.DeclaringSyntaxReferences.Any(static x => x.GetSyntax() is TypeDeclarationSyntax t && t.Modifiers.Any(SyntaxKind.PartialKeyword));
+
+ private static bool IsStream(ITypeSymbol type)
+ => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::System.IO.Stream";
+
+ private static string GetFileName(ITypeSymbol type)
+ {
+ return type switch
+ {
+ IArrayTypeSymbol arrayType => arrayType.ElementType.Name + "Array",
+ INamedTypeSymbol namedType => namedType.Name,
+ _ => "BoisGenerated"
+ };
+ }
+
+ private sealed class SyntaxReceiver : ISyntaxReceiver
+ {
+ public List Candidates { get; } = [];
+
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (syntaxNode is MethodDeclarationSyntax method && method.AttributeLists.Count > 0 && method.Modifiers.Any(SyntaxKind.PartialKeyword))
+ Candidates.Add(method);
+ }
+ }
+
+ private sealed class ContainingTypeEmitter
+ {
+ private static readonly SymbolDisplayFormat QualifiedTypeFormat = new(
+ globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included,
+ typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
+ genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
+ miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
+
+ private readonly Compilation _compilation;
+ private readonly INamedTypeSymbol _containingType;
+ private readonly Action _report;
+ private readonly INamedTypeSymbol? _nullableType;
+ private readonly INamedTypeSymbol? _collectionType;
+ private readonly INamedTypeSymbol? _dictionaryType;
+ private readonly INamedTypeSymbol? _nameValueCollectionType;
+ private readonly ITypeSymbol? _guidType;
+ private readonly ITypeSymbol? _colorType;
+ private readonly ITypeSymbol? _uriType;
+ private readonly ITypeSymbol? _versionType;
+ private readonly ITypeSymbol? _dateTimeOffsetType;
+ private readonly ITypeSymbol? _timeSpanType;
+ private readonly ITypeSymbol? _dateOnlyType;
+ private readonly ITypeSymbol? _timeOnlyType;
+ private readonly ITypeSymbol? _dbNullType;
+ private readonly ITypeSymbol? _dataTableType;
+ private readonly ITypeSymbol? _dataSetType;
+
+ public ContainingTypeEmitter(Compilation compilation, INamedTypeSymbol containingType, Action report)
+ {
+ _compilation = compilation;
+ _containingType = containingType;
+ _report = report;
+ _nullableType = compilation.GetTypeByMetadataName("System.Nullable`1");
+ _collectionType = compilation.GetTypeByMetadataName("System.Collections.Generic.ICollection`1");
+ _dictionaryType = compilation.GetTypeByMetadataName("System.Collections.Generic.IDictionary`2");
+ _nameValueCollectionType = compilation.GetTypeByMetadataName("System.Collections.Specialized.NameValueCollection");
+ _guidType = compilation.GetTypeByMetadataName("System.Guid");
+ _colorType = compilation.GetTypeByMetadataName("System.Drawing.Color");
+ _uriType = compilation.GetTypeByMetadataName("System.Uri");
+ _versionType = compilation.GetTypeByMetadataName("System.Version");
+ _dateTimeOffsetType = compilation.GetTypeByMetadataName("System.DateTimeOffset");
+ _timeSpanType = compilation.GetTypeByMetadataName("System.TimeSpan");
+ _dateOnlyType = compilation.GetTypeByMetadataName("System.DateOnly");
+ _timeOnlyType = compilation.GetTypeByMetadataName("System.TimeOnly");
+ _dbNullType = compilation.GetTypeByMetadataName("System.DBNull");
+ _dataTableType = compilation.GetTypeByMetadataName("System.Data.DataTable");
+ _dataSetType = compilation.GetTypeByMetadataName("System.Data.DataSet");
+ }
+
+ public string Emit(ImmutableArray methods)
+ {
+ var builder = new CodeBuilder();
+ builder.Line("// ");
+ builder.Line("#nullable enable");
+ builder.Line();
+
+ if (!_containingType.ContainingNamespace.IsGlobalNamespace)
+ {
+ builder.Line($"namespace {_containingType.ContainingNamespace.ToDisplayString()};");
+ builder.Line();
+ }
+
+ builder.Line($"{GetContainingTypeDeclaration()} ");
+ builder.Line("{");
+ builder.Indent();
+
+ foreach (var method in methods.OrderBy(static x => x.Method.Name, StringComparer.Ordinal))
+ {
+ EmitMethod(builder, method);
+ builder.Line();
+ }
+
+ builder.Unindent();
+ builder.Line("}");
+ return builder.ToString();
+ }
+
+ private void EmitMethod(CodeBuilder builder, GenerationMethod method)
+ {
+ var emitter = new MethodEmitter(this, method);
+ emitter.Emit(builder);
+ }
+
+ private string GetContainingTypeDeclaration()
+ {
+ var access = _containingType.DeclaredAccessibility == Accessibility.Public ? "public " : "internal ";
+ return $"{access}static partial class {_containingType.Name}";
+ }
+
+ private sealed class MethodEmitter
+ {
+ private readonly ContainingTypeEmitter _owner;
+ private readonly GenerationMethod _method;
+ private readonly Dictionary _readFunctions = new(StringComparer.Ordinal);
+ private readonly Dictionary _writeFunctions = new(StringComparer.Ordinal);
+ private readonly Queue _pending = new();
+ private int _id;
+
+ public MethodEmitter(ContainingTypeEmitter owner, GenerationMethod method)
+ {
+ _owner = owner;
+ _method = method;
+ }
+
+ public void Emit(CodeBuilder builder)
+ {
+ if (!_owner.ValidateRootType(_method.RootType, out var error))
+ {
+ _owner.Report(_method.Method.Locations.FirstOrDefault(), error);
+ EmitStub(builder, error);
+ return;
+ }
+
+ var signature = BuildSignature(_method.Method);
+ builder.Line(signature);
+ builder.Line("{");
+ builder.Indent();
+
+ if (_method.Operation == OperationKind.Reader)
+ {
+ var sourceName = Escape(_method.Method.Parameters[0].Name);
+ var rootFunction = EnsureReadFunction(_method.RootType);
+ builder.Line($"var reader = new global::Salar.BinaryBuffers.Compatibility.StreamBufferReader({sourceName});");
+ builder.Line($"return {rootFunction}(reader);");
+ }
+ else
+ {
+ var outputName = Escape(_method.Method.Parameters[0].Name);
+ var valueName = Escape(_method.Method.Parameters[1].Name);
+ var rootFunction = EnsureWriteFunction(_method.RootType);
+ builder.Line($"var writer = new global::Salar.BinaryBuffers.Compatibility.StreamBufferWriter({outputName});");
+ builder.Line($"{rootFunction}(writer, {valueName});");
+ }
+
+ while (_pending.Count > 0)
+ EmitLocalFunction(builder, _pending.Dequeue());
+
+ builder.Unindent();
+ builder.Line("}");
+ }
+
+ private void EmitStub(CodeBuilder builder, string error)
+ {
+ builder.Line(BuildSignature(_method.Method));
+ builder.Line("{");
+ builder.Indent();
+ builder.Line($"throw new global::System.NotSupportedException({Literal(error)});");
+ builder.Unindent();
+ builder.Line("}");
+ }
+
+ private string EnsureReadFunction(ITypeSymbol type)
+ {
+ var key = _owner.TypeKey(type);
+ if (_readFunctions.TryGetValue(key, out var existing))
+ return existing.Name;
+
+ var function = new LocalFunctionModel(OperationKind.Reader, type, $"ReadType{_id++}");
+ _readFunctions.Add(key, function);
+ _pending.Enqueue(function);
+ return function.Name;
+ }
+
+ private string EnsureWriteFunction(ITypeSymbol type)
+ {
+ var key = _owner.TypeKey(type);
+ if (_writeFunctions.TryGetValue(key, out var existing))
+ return existing.Name;
+
+ var function = new LocalFunctionModel(OperationKind.Writer, type, $"WriteType{_id++}");
+ _writeFunctions.Add(key, function);
+ _pending.Enqueue(function);
+ return function.Name;
+ }
+
+ private void EmitLocalFunction(CodeBuilder builder, LocalFunctionModel function)
+ {
+ if (function.Operation == OperationKind.Reader)
+ EmitReadLocalFunction(builder, function);
+ else
+ EmitWriteLocalFunction(builder, function);
+ }
+
+ private void EmitReadLocalFunction(CodeBuilder builder, LocalFunctionModel function)
+ {
+ builder.Line();
+ builder.Line($"static {TypeName(function.Type)} {function.Name}(global::Salar.BinaryBuffers.BufferReaderBase reader)");
+ builder.Line("{");
+ builder.Indent();
+
+ if (!TryEmitRead(function.Type, builder, out var error))
+ {
+ _owner.Report(_method.Method.Locations.FirstOrDefault(), error);
+ builder.Line($"throw new global::System.NotSupportedException({Literal(error)});");
+ }
+
+ builder.Unindent();
+ builder.Line("}");
+ }
+
+ private void EmitWriteLocalFunction(CodeBuilder builder, LocalFunctionModel function)
+ {
+ builder.Line();
+ builder.Line($"static void {function.Name}(global::Salar.BinaryBuffers.BufferWriterBase writer, {TypeName(function.Type)} value)");
+ builder.Line("{");
+ builder.Indent();
+
+ if (!TryEmitWrite(function.Type, builder, out var error))
+ {
+ _owner.Report(_method.Method.Locations.FirstOrDefault(), error);
+ builder.Line($"throw new global::System.NotSupportedException({Literal(error)});");
+ }
+
+ builder.Unindent();
+ builder.Line("}");
+ }
+
+ private bool TryEmitWrite(ITypeSymbol type, CodeBuilder builder, out string error)
+ {
+ if (_owner.TryGetBasicType(type, out var basicType))
+ {
+ builder.Line(_owner.GetWriteStatement(type, basicType, "value"));
+ error = string.Empty;
+ return true;
+ }
+
+ if (_owner.IsEnum(type))
+ {
+ builder.Line(_owner.GetEnumWriteStatement(type, "value"));
+ error = string.Empty;
+ return true;
+ }
+
+ if (type is IArrayTypeSymbol arrayType)
+ return EmitWriteArray(arrayType, builder, out error);
+
+ if (_owner.TryGetDictionaryInfo(type, out var dict))
+ return EmitWriteDictionary(dict, builder, out error);
+
+ if (_owner.TryGetCollectionInfo(type, out var coll))
+ return EmitWriteCollection(coll, builder, out error);
+
+ if (_owner.IsNameValueCollection(type))
+ return EmitWriteNameValueCollection(builder, out error);
+
+ return EmitWriteObject(type, builder, out error);
+ }
+
+ private bool TryEmitRead(ITypeSymbol type, CodeBuilder builder, out string error)
+ {
+ if (_owner.TryGetBasicType(type, out var basicType))
+ {
+ builder.Line($"return {_owner.GetReadExpression(type, basicType)};");
+ error = string.Empty;
+ return true;
+ }
+
+ if (_owner.IsEnum(type))
+ {
+ builder.Line($"return global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadEnumGeneric<{TypeName(type)}>(reader);");
+ error = string.Empty;
+ return true;
+ }
+
+ if (type is IArrayTypeSymbol arrayType)
+ return EmitReadArray(arrayType, builder, out error);
+
+ if (_owner.TryGetDictionaryInfo(type, out var dict))
+ return EmitReadDictionary(type, dict, builder, out error);
+
+ if (_owner.TryGetCollectionInfo(type, out var coll))
+ return EmitReadCollection(type, coll, builder, out error);
+
+ if (_owner.IsNameValueCollection(type))
+ return EmitReadNameValueCollection(type, builder, out error);
+
+ return EmitReadObject(type, builder, out error);
+ }
+
+ private bool EmitWriteObject(ITypeSymbol type, CodeBuilder builder, out string error)
+ {
+ if (!_owner.TryGetMembers(type, out var members, out error))
+ return false;
+
+ if (!_owner.IsExplicitStruct(type))
+ {
+ builder.Line("if (value is null)");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteNullValue(writer);");
+ builder.Line("return;");
+ builder.Unindent();
+ builder.Line("}");
+ builder.Line($"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteUIntNullableMemberCount(writer, {members.Length}u);");
+ }
+
+ foreach (var member in members)
+ {
+ if (!EmitWriteMember(member, builder, out error))
+ return false;
+ }
+
+ error = string.Empty;
+ return true;
+ }
+
+ private bool EmitReadObject(ITypeSymbol type, CodeBuilder builder, out string error)
+ {
+ if (!_owner.TryGetMembers(type, out var members, out error))
+ return false;
+
+ if (!_owner.IsExplicitStruct(type))
+ {
+ builder.Line("var memberCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("if (memberCount is null)");
+ builder.Indent();
+ builder.Line("return null!;");
+ builder.Unindent();
+ }
+
+ if (!_owner.TryGetCreationExpression(type, out var creationExpression, out error))
+ return false;
+
+ builder.Line($"var instance = {creationExpression};");
+ foreach (var member in members)
+ {
+ if (!EmitReadMember(member, builder, out error))
+ return false;
+ }
+ builder.Line("return instance;");
+ return true;
+ }
+
+ private bool EmitWriteMember(MemberModel member, CodeBuilder builder, out string error)
+ {
+ var access = $"value.{Escape(member.Symbol.Name)}";
+ if (_owner.TryGetBasicType(member.Type, out var basicType))
+ {
+ builder.Line(_owner.GetWriteStatement(member.Type, basicType, access));
+ error = string.Empty;
+ return true;
+ }
+ if (_owner.IsEnum(member.Type))
+ {
+ builder.Line(_owner.GetEnumWriteStatement(member.Type, access));
+ error = string.Empty;
+ return true;
+ }
+ if (member.Type is IArrayTypeSymbol arrayType)
+ return EmitWriteNestedArray(arrayType, access, builder, out error);
+ if (_owner.TryGetDictionaryInfo(member.Type, out var dict))
+ return EmitWriteNestedDictionary(dict, access, builder, out error);
+ if (_owner.TryGetCollectionInfo(member.Type, out var coll))
+ return EmitWriteNestedCollection(coll, access, builder, out error);
+ if (_owner.IsNameValueCollection(member.Type))
+ return EmitWriteNestedNameValue(access, builder, out error);
+
+ builder.Line($"{EnsureWriteFunction(member.Type)}(writer, {access});");
+ error = string.Empty;
+ return true;
+ }
+
+ private bool EmitReadMember(MemberModel member, CodeBuilder builder, out string error)
+ {
+ var target = $"instance.{Escape(member.Symbol.Name)}";
+ if (_owner.TryGetBasicType(member.Type, out var basicType))
+ {
+ builder.Line($"{target} = {_owner.GetReadExpression(member.Type, basicType)};");
+ error = string.Empty;
+ return true;
+ }
+ if (_owner.IsEnum(member.Type))
+ {
+ builder.Line($"{target} = global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadEnumGeneric<{TypeName(member.Type)}>(reader);");
+ error = string.Empty;
+ return true;
+ }
+
+ if (member.IsGetterOnlyMutableCollection)
+ {
+ var localName = Escape(member.Symbol.Name) + "Target";
+ builder.Line($"var {localName} = {target} ?? throw new global::System.InvalidOperationException({Literal($"Property '{member.Symbol.Name}' returned null during deserialization.")});");
+ builder.Line($"{localName}.Clear();");
+ if (_owner.TryGetDictionaryInfo(member.Type, out var dict))
+ {
+ EmitReadIntoDictionary(dict, localName, builder);
+ error = string.Empty;
+ return true;
+ }
+ if (_owner.TryGetCollectionInfo(member.Type, out var coll))
+ {
+ EmitReadIntoCollection(coll, localName, builder);
+ error = string.Empty;
+ return true;
+ }
+ if (_owner.IsNameValueCollection(member.Type))
+ {
+ EmitReadIntoNameValue(localName, builder);
+ error = string.Empty;
+ return true;
+ }
+ }
+
+ builder.Line($"{target} = {EnsureReadFunction(member.Type)}(reader);");
+ error = string.Empty;
+ return true;
+ }
+
+ private bool EmitWriteArray(IArrayTypeSymbol arrayType, CodeBuilder builder, out string error)
+ => EmitWriteNestedArray(arrayType, "value", builder, out error, root:true);
+
+ private bool EmitWriteNestedArray(IArrayTypeSymbol arrayType, string expression, CodeBuilder builder, out string error, bool root=false)
+ {
+ builder.Line($"if ({expression} is null)");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteNullValue(writer);");
+ builder.Unindent();
+ builder.Line("}");
+ builder.Line("else");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line($"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Length);");
+ builder.Line($"foreach (var item in {expression})");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line(GetWriteElementStatement(arrayType.ElementType, "item"));
+ builder.Unindent();
+ builder.Line("}");
+ builder.Unindent();
+ builder.Line("}");
+ error = string.Empty;
+ return true;
+ }
+
+ private bool EmitReadArray(IArrayTypeSymbol arrayType, CodeBuilder builder, out string error)
+ {
+ builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("if (itemCount is null)");
+ builder.Indent();
+ builder.Line("return null!;");
+ builder.Unindent();
+ builder.Line($"var items = new {TypeName(arrayType.ElementType)}[(int)itemCount.Value];");
+ builder.Line("for (var i = 0; i < items.Length; i++)");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line($"items[i] = {GetReadValueExpression(arrayType.ElementType)};");
+ builder.Unindent();
+ builder.Line("}");
+ builder.Line("return items;");
+ error = string.Empty;
+ return true;
+ }
+
+ private bool EmitWriteCollection(CollectionInfo collectionInfo, CodeBuilder builder, out string error)
+ => EmitWriteNestedCollection(collectionInfo, "value", builder, out error, root:true);
+
+ private bool EmitWriteNestedCollection(CollectionInfo collectionInfo, string expression, CodeBuilder builder, out string error, bool root=false)
+ {
+ builder.Line($"if ({expression} is null)");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteNullValue(writer);");
+ builder.Unindent();
+ builder.Line("}");
+ builder.Line("else");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line($"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Count);");
+ builder.Line($"foreach (var item in {expression})");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line(GetWriteElementStatement(collectionInfo.ElementType, "item"));
+ builder.Unindent();
+ builder.Line("}");
+ builder.Unindent();
+ builder.Line("}");
+ error = string.Empty;
+ return true;
+ }
+
+ private bool EmitReadCollection(ITypeSymbol type, CollectionInfo collectionInfo, CodeBuilder builder, out string error)
+ {
+ if (!_owner.TryGetCreationExpression(type, out var create, out error))
+ return false;
+ builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("if (itemCount is null)");
+ builder.Indent();
+ builder.Line("return null!;");
+ builder.Unindent();
+ builder.Line($"var items = {create};");
+ EmitReadIntoCollectionBody(collectionInfo, "items", "itemCount.Value", builder);
+ builder.Line("return items;");
+ error = string.Empty;
+ return true;
+ }
+
+ private void EmitReadIntoCollection(CollectionInfo collectionInfo, string target, CodeBuilder builder)
+ {
+ builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("if (itemCount is not null)");
+ builder.Line("{");
+ builder.Indent();
+ EmitReadIntoCollectionBody(collectionInfo, target, "itemCount.Value", builder);
+ builder.Unindent();
+ builder.Line("}");
+ }
+
+ private void EmitReadIntoCollectionBody(CollectionInfo collectionInfo, string target, string countExpression, CodeBuilder builder)
+ {
+ builder.Line($"for (var i = 0; i < {countExpression}; i++)");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line($"{target}.Add({GetReadValueExpression(collectionInfo.ElementType)});");
+ builder.Unindent();
+ builder.Line("}");
+ }
+
+ private bool EmitWriteDictionary(DictionaryInfo dictionaryInfo, CodeBuilder builder, out string error)
+ => EmitWriteNestedDictionary(dictionaryInfo, "value", builder, out error, root:true);
+
+ private bool EmitWriteNestedDictionary(DictionaryInfo dictionaryInfo, string expression, CodeBuilder builder, out string error, bool root=false)
+ {
+ builder.Line($"if ({expression} is null)");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteNullValue(writer);");
+ builder.Unindent();
+ builder.Line("}");
+ builder.Line("else");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line($"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Count);");
+ builder.Line($"foreach (var item in {expression})");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line(GetWriteElementStatement(dictionaryInfo.KeyType, "item.Key"));
+ builder.Line(GetWriteElementStatement(dictionaryInfo.ValueType, "item.Value"));
+ builder.Unindent();
+ builder.Line("}");
+ builder.Unindent();
+ builder.Line("}");
+ error = string.Empty;
+ return true;
+ }
+
+ private bool EmitReadDictionary(ITypeSymbol type, DictionaryInfo dictionaryInfo, CodeBuilder builder, out string error)
+ {
+ if (!_owner.TryGetCreationExpression(type, out var create, out error))
+ return false;
+ builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("if (itemCount is null)");
+ builder.Indent();
+ builder.Line("return null!;");
+ builder.Unindent();
+ builder.Line($"var items = {create};");
+ EmitReadIntoDictionaryBody(dictionaryInfo, "items", "itemCount.Value", builder);
+ builder.Line("return items;");
+ error = string.Empty;
+ return true;
+ }
+
+ private void EmitReadIntoDictionary(DictionaryInfo dictionaryInfo, string target, CodeBuilder builder)
+ {
+ builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("if (itemCount is not null)");
+ builder.Line("{");
+ builder.Indent();
+ EmitReadIntoDictionaryBody(dictionaryInfo, target, "itemCount.Value", builder);
+ builder.Unindent();
+ builder.Line("}");
+ }
+
+ private void EmitReadIntoDictionaryBody(DictionaryInfo dictionaryInfo, string target, string countExpression, CodeBuilder builder)
+ {
+ builder.Line($"for (var i = 0; i < {countExpression}; i++)");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line($"var key = {GetReadValueExpression(dictionaryInfo.KeyType)};");
+ builder.Line($"var value = {GetReadValueExpression(dictionaryInfo.ValueType)};");
+ builder.Line($"{target}.Add(key, value);");
+ builder.Unindent();
+ builder.Line("}");
+ }
+
+ private bool EmitWriteNameValueCollection(CodeBuilder builder, out string error)
+ => EmitWriteNestedNameValue("value", builder, out error, root:true);
+
+ private bool EmitWriteNestedNameValue(string expression, CodeBuilder builder, out string error, bool root=false)
+ {
+ builder.Line($"if ({expression} is null)");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteNullValue(writer);");
+ builder.Unindent();
+ builder.Line("}");
+ builder.Line("else");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line($"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Count);");
+ builder.Line($"foreach (var key in {expression}.AllKeys)");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, key, global::System.Text.Encoding.UTF8);");
+ builder.Line($"global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, {expression}[key], global::System.Text.Encoding.UTF8);");
+ builder.Unindent();
+ builder.Line("}");
+ builder.Unindent();
+ builder.Line("}");
+ error = string.Empty;
+ return true;
+ }
+
+ private bool EmitReadNameValueCollection(ITypeSymbol type, CodeBuilder builder, out string error)
+ {
+ if (!_owner.TryGetCreationExpression(type, out var create, out error))
+ return false;
+ builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("if (itemCount is null)");
+ builder.Indent();
+ builder.Line("return null!;");
+ builder.Unindent();
+ builder.Line($"var items = {create};");
+ EmitReadIntoNameValueBody("items", "itemCount.Value", builder);
+ builder.Line("return items;");
+ error = string.Empty;
+ return true;
+ }
+
+ private void EmitReadIntoNameValue(string target, CodeBuilder builder)
+ {
+ builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("if (itemCount is not null)");
+ builder.Line("{");
+ builder.Indent();
+ EmitReadIntoNameValueBody(target, "itemCount.Value", builder);
+ builder.Unindent();
+ builder.Line("}");
+ }
+
+ private void EmitReadIntoNameValueBody(string target, string countExpression, CodeBuilder builder)
+ {
+ builder.Line($"for (var i = 0; i < {countExpression}; i++)");
+ builder.Line("{");
+ builder.Indent();
+ builder.Line("var key = global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8);");
+ builder.Line("var value = global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8);");
+ builder.Line($"{target}.Add(key, value);");
+ builder.Unindent();
+ builder.Line("}");
+ }
+
+ private string GetWriteElementStatement(ITypeSymbol type, string expression)
+ {
+ if (_owner.TryGetBasicType(type, out var basicType))
+ return _owner.GetWriteStatement(type, basicType, expression);
+ if (_owner.IsEnum(type))
+ return _owner.GetEnumWriteStatement(type, expression);
+ return $"{EnsureWriteFunction(type)}(writer, {expression});";
+ }
+
+ private string GetReadValueExpression(ITypeSymbol type)
+ {
+ if (_owner.TryGetBasicType(type, out var basicType))
+ return _owner.GetReadExpression(type, basicType);
+ if (_owner.IsEnum(type))
+ return $"global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadEnumGeneric<{TypeName(type)}>(reader)";
+ return $"{EnsureReadFunction(type)}(reader)";
+ }
+
+ private string BuildSignature(IMethodSymbol method)
+ {
+ var access = method.DeclaredAccessibility switch
+ {
+ Accessibility.Public => "public ",
+ Accessibility.Internal => "internal ",
+ _ => string.Empty
+ };
+ var returnType = method.ReturnsVoid ? "void" : TypeName(method.ReturnType);
+ var parameters = string.Join(", ", method.Parameters.Select(static p => $"{p.Type.ToDisplayString(QualifiedTypeFormat)} {Escape(p.Name)}"));
+ return $"{access}static partial {returnType} {method.Name}({parameters})";
+ }
+
+ private static string Escape(string value) => SyntaxFacts.GetKeywordKind(value) != SyntaxKind.None ? "@" + value : value;
+
+ private string TypeName(ITypeSymbol type) => type.ToDisplayString(QualifiedTypeFormat);
+ }
+
+ public void Report(Location? location, string message)
+ => _report(Diagnostic.Create(UnsupportedType, location, message));
+
+ public string TypeKey(ITypeSymbol type) => type.ToDisplayString(QualifiedTypeFormat);
+
+ public bool ValidateRootType(ITypeSymbol type, out string error)
+ {
+ if (IsNullableComplex(type))
+ {
+ error = $"Nullable complex type '{type.ToDisplayString()}' is not supported by the BOIS source generator.";
+ return false;
+ }
+ error = string.Empty;
+ return true;
+ }
+
+ public bool TryGetMembers(ITypeSymbol type, out ImmutableArray members, out string error)
+ {
+ error = string.Empty;
+ var ordered = new List();
+ var readFields = true;
+ var readProps = true;
+ foreach (var attribute in type.GetAttributes())
+ {
+ if (attribute.AttributeClass?.ToDisplayString() == "Salar.Bois.BoisContractAttribute" && attribute.ConstructorArguments.Length == 2)
+ {
+ readFields = attribute.ConstructorArguments[0].Value as bool? ?? true;
+ readProps = attribute.ConstructorArguments[1].Value as bool? ?? true;
+ break;
+ }
+ }
+
+ foreach (var symbol in EnumerateMembers(type, readFields, readProps))
+ {
+ var (included, index) = ReadMemberOptions(symbol);
+ if (!included)
+ continue;
+
+ if (symbol is IFieldSymbol field)
+ {
+ if (field.IsStatic || field.DeclaredAccessibility != Accessibility.Public || field.IsReadOnly)
+ continue;
+ Insert(ordered, new MemberModel(field, field.Type, index, false));
+ continue;
+ }
+
+ if (symbol is IPropertySymbol property)
+ {
+ if (property.IsStatic || property.DeclaredAccessibility != Accessibility.Public || property.IsIndexer || property.GetMethod is null || property.GetMethod.DeclaredAccessibility != Accessibility.Public)
+ continue;
+
+ var getterOnlyMutable = property.SetMethod is null && (TryGetCollectionInfo(property.Type, out _) || TryGetDictionaryInfo(property.Type, out _) || IsNameValueCollection(property.Type));
+ if (property.SetMethod is not null && property.SetMethod.DeclaredAccessibility == Accessibility.Public || getterOnlyMutable)
+ {
+ Insert(ordered, new MemberModel(property, property.Type, index, getterOnlyMutable));
+ }
+ }
+ }
+
+ members = ordered.ToImmutableArray();
+ return true;
+ }
+
+ private static IEnumerable EnumerateMembers(ITypeSymbol type, bool includeFields, bool includeProperties)
+ {
+ var stack = new Stack();
+ for (var current = type as INamedTypeSymbol; current is not null; current = current.BaseType)
+ stack.Push(current);
+
+ while (stack.Count > 0)
+ {
+ var current = stack.Pop();
+ if (includeFields)
+ {
+ foreach (var field in current.GetMembers().OfType())
+ yield return field;
+ }
+ if (includeProperties)
+ {
+ foreach (var property in current.GetMembers().OfType())
+ yield return property;
+ }
+ }
+ }
+
+ private static void Insert(List members, MemberModel member)
+ {
+ if (member.Index >= 0)
+ members.Insert(Math.Min(member.Index, members.Count), member);
+ else
+ members.Add(member);
+ }
+
+ private static (bool Included, int Index) ReadMemberOptions(ISymbol symbol)
+ {
+ foreach (var attribute in symbol.GetAttributes())
+ {
+ if (attribute.AttributeClass?.ToDisplayString() != "Salar.Bois.BoisMemberAttribute")
+ continue;
+
+ var included = true;
+ var index = -1;
+ if (attribute.ConstructorArguments.Length == 1)
+ {
+ var argument = attribute.ConstructorArguments[0];
+ if (argument.Type?.SpecialType == SpecialType.System_Int32)
+ index = argument.Value as int? ?? -1;
+ else if (argument.Type?.SpecialType == SpecialType.System_Boolean)
+ included = argument.Value as bool? ?? true;
+ }
+ else if (attribute.ConstructorArguments.Length == 2)
+ {
+ index = attribute.ConstructorArguments[0].Value as int? ?? -1;
+ included = attribute.ConstructorArguments[1].Value as bool? ?? true;
+ }
+ return (included, index);
+ }
+ return (true, -1);
+ }
+
+ public bool TryGetCreationExpression(ITypeSymbol type, out string expression, out string error)
+ {
+ var bare = Bare(type);
+ var constructionType = bare.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
+ if (IsExplicitStruct(type))
+ {
+ expression = "new " + constructionType.ToDisplayString(QualifiedTypeFormat) + "()";
+ error = string.Empty;
+ return true;
+ }
+
+ if (bare.TypeKind == TypeKind.Interface || bare.TypeKind == TypeKind.TypeParameter)
+ {
+ expression = string.Empty;
+ error = $"Type '{type.ToDisplayString()}' must be concrete or provide an existing initialized instance for generated BOIS code.";
+ return false;
+ }
+
+ if (bare is not INamedTypeSymbol named || !named.InstanceConstructors.Any(static c => c.Parameters.Length == 0 && c.DeclaredAccessibility == Accessibility.Public))
+ {
+ expression = string.Empty;
+ error = $"Type '{type.ToDisplayString()}' must expose a public parameterless constructor for generated BOIS code.";
+ return false;
+ }
+
+ expression = "new " + constructionType.ToDisplayString(QualifiedTypeFormat) + "()";
+ error = string.Empty;
+ return true;
+ }
+
+ public bool TryGetBasicType(ITypeSymbol type, out BasicType basicType)
+ {
+ var bare = Bare(type);
+ var nullableValue = IsNullableValueType(type);
+
+ if (bare.SpecialType == SpecialType.System_String) { basicType = BasicType.String; return true; }
+ if (bare.SpecialType == SpecialType.System_Boolean) { basicType = nullableValue ? BasicType.BoolNullable : BasicType.Bool; return true; }
+ if (bare.SpecialType == SpecialType.System_Char) { basicType = nullableValue ? BasicType.CharNullable : BasicType.Char; return true; }
+ if (bare.SpecialType == SpecialType.System_Int16) { basicType = nullableValue ? BasicType.Int16Nullable : BasicType.Int16; return true; }
+ if (bare.SpecialType == SpecialType.System_Int32) { basicType = nullableValue ? BasicType.Int32Nullable : BasicType.Int32; return true; }
+ if (bare.SpecialType == SpecialType.System_Int64) { basicType = nullableValue ? BasicType.Int64Nullable : BasicType.Int64; return true; }
+ if (bare.SpecialType == SpecialType.System_UInt16) { basicType = nullableValue ? BasicType.UInt16Nullable : BasicType.UInt16; return true; }
+ if (bare.SpecialType == SpecialType.System_UInt32) { basicType = nullableValue ? BasicType.UInt32Nullable : BasicType.UInt32; return true; }
+ if (bare.SpecialType == SpecialType.System_UInt64) { basicType = nullableValue ? BasicType.UInt64Nullable : BasicType.UInt64; return true; }
+ if (bare.SpecialType == SpecialType.System_Single) { basicType = nullableValue ? BasicType.SingleNullable : BasicType.Single; return true; }
+ if (bare.SpecialType == SpecialType.System_Double) { basicType = nullableValue ? BasicType.DoubleNullable : BasicType.Double; return true; }
+ if (bare.SpecialType == SpecialType.System_Decimal) { basicType = nullableValue ? BasicType.DecimalNullable : BasicType.Decimal; return true; }
+ if (bare.SpecialType == SpecialType.System_Byte) { basicType = nullableValue ? BasicType.ByteNullable : BasicType.Byte; return true; }
+ if (bare.SpecialType == SpecialType.System_SByte) { basicType = nullableValue ? BasicType.SByteNullable : BasicType.SByte; return true; }
+ if (bare.SpecialType == SpecialType.System_DateTime) { basicType = nullableValue ? BasicType.DateTimeNullable : BasicType.DateTime; return true; }
+ if (Equal(bare, _dateTimeOffsetType)) { basicType = nullableValue ? BasicType.DateTimeOffsetNullable : BasicType.DateTimeOffset; return true; }
+ if (Equal(bare, _dateOnlyType)) { basicType = nullableValue ? BasicType.DateOnlyNullable : BasicType.DateOnly; return true; }
+ if (Equal(bare, _timeOnlyType)) { basicType = nullableValue ? BasicType.TimeOnlyNullable : BasicType.TimeOnly; return true; }
+ if (bare is IArrayTypeSymbol arrayType && arrayType.ElementType.SpecialType == SpecialType.System_Byte) { basicType = BasicType.ByteArray; return true; }
+ if (Equal(bare, _timeSpanType)) { basicType = nullableValue ? BasicType.TimeSpanNullable : BasicType.TimeSpan; return true; }
+ if (Equal(bare, _guidType)) { basicType = nullableValue ? BasicType.GuidNullable : BasicType.Guid; return true; }
+ if (Equal(bare, _colorType)) { basicType = nullableValue ? BasicType.ColorNullable : BasicType.Color; return true; }
+ if (Equal(bare, _dbNullType)) { basicType = BasicType.DbNull; return true; }
+ if (Equal(bare, _uriType)) { basicType = BasicType.Uri; return true; }
+ if (Equal(bare, _versionType)) { basicType = BasicType.Version; return true; }
+ if (Equal(bare, _dataTableType)) { basicType = BasicType.DataTable; return true; }
+ if (Equal(bare, _dataSetType)) { basicType = BasicType.DataSet; return true; }
+
+ basicType = default;
+ return false;
+ }
+
+ public string GetWriteStatement(ITypeSymbol type, BasicType basicType, string expression) => basicType switch
+ {
+ BasicType.String => $"global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, {expression}, global::System.Text.Encoding.UTF8);",
+ BasicType.Bool or BasicType.BoolNullable or BasicType.Char or BasicType.CharNullable or BasicType.DateTime or BasicType.DateTimeNullable or BasicType.DateTimeOffset or BasicType.DateTimeOffsetNullable or BasicType.TimeSpan or BasicType.TimeSpanNullable or BasicType.Guid or BasicType.GuidNullable or BasicType.Color or BasicType.ColorNullable or BasicType.DbNull or BasicType.Uri or BasicType.Version or BasicType.DateOnly or BasicType.DateOnlyNullable or BasicType.TimeOnly or BasicType.TimeOnlyNullable or BasicType.ByteArray => $"global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, {expression});",
+ BasicType.Int16 or BasicType.Int16Nullable or BasicType.Int32 or BasicType.Int32Nullable or BasicType.Int64 or BasicType.Int64Nullable or BasicType.UInt16 or BasicType.UInt16Nullable or BasicType.UInt32 or BasicType.UInt32Nullable or BasicType.UInt64 or BasicType.UInt64Nullable or BasicType.ByteNullable or BasicType.SByteNullable => $"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteVarInt(writer, {expression});",
+ BasicType.Single or BasicType.SingleNullable or BasicType.Double or BasicType.DoubleNullable or BasicType.Decimal or BasicType.DecimalNullable => $"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteVarDecimal(writer, {expression});",
+ BasicType.Byte => $"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteByte(writer, {expression});",
+ BasicType.SByte => $"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteSByte(writer, {expression});",
+ BasicType.DataTable or BasicType.DataSet => $"global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, {expression}, global::System.Text.Encoding.UTF8);",
+ _ => throw new InvalidOperationException()
+ };
+
+ public string GetEnumWriteStatement(ITypeSymbol type, string expression)
+ {
+ var enumExpression = IsNullableValueType(type)
+ ? $"({expression}.HasValue ? (global::System.Enum)(object){expression}.Value : null)"
+ : $"({expression} is null ? null : (global::System.Enum)(object){expression})";
+ var isNullable = IsNullable(type) ? "true" : "false";
+ return $"global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, {enumExpression}, typeof({Bare(type).ToDisplayString(QualifiedTypeFormat)}), {isNullable});";
+ }
+
+ public string GetReadExpression(ITypeSymbol type, BasicType basicType) => basicType switch
+ {
+ BasicType.String => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8)",
+ BasicType.Bool => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadBoolean(reader)",
+ BasicType.BoolNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadBooleanNullable(reader)",
+ BasicType.Char => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadChar(reader)",
+ BasicType.CharNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadCharNullable(reader)",
+ BasicType.Int16 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt16(reader)",
+ BasicType.Int16Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt16Nullable(reader)",
+ BasicType.Int32 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt32(reader)",
+ BasicType.Int32Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt32Nullable(reader)",
+ BasicType.Int64 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt64(reader)",
+ BasicType.Int64Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt64Nullable(reader)",
+ BasicType.UInt16 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt16(reader)",
+ BasicType.UInt16Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt16Nullable(reader)",
+ BasicType.UInt32 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32(reader)",
+ BasicType.UInt32Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader)",
+ BasicType.UInt64 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt64(reader)",
+ BasicType.UInt64Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt64Nullable(reader)",
+ BasicType.Single => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarSingle(reader)",
+ BasicType.SingleNullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarSingleNullable(reader)",
+ BasicType.Double => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarDouble(reader)",
+ BasicType.DoubleNullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarDoubleNullable(reader)",
+ BasicType.Decimal => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarDecimal(reader)",
+ BasicType.DecimalNullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarDecimalNullable(reader)",
+ BasicType.Byte => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadByte(reader)",
+ BasicType.ByteNullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarByteNullable(reader)",
+ BasicType.SByte => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadSByte(reader)",
+ BasicType.SByteNullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarSByteNullable(reader)",
+ BasicType.DateTime => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateTime(reader)",
+ BasicType.DateTimeNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateTimeNullable(reader)",
+ BasicType.DateTimeOffset => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateTimeOffset(reader)",
+ BasicType.DateTimeOffsetNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateTimeOffsetNullable(reader)",
+ BasicType.DateOnly => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateOnly(reader)",
+ BasicType.DateOnlyNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateOnlyNullable(reader)",
+ BasicType.TimeOnly => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadTimeOnly(reader)",
+ BasicType.TimeOnlyNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadTimeOnlyNullable(reader)",
+ BasicType.ByteArray => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadByteArray(reader)",
+ BasicType.TimeSpan => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadTimeSpan(reader)",
+ BasicType.TimeSpanNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadTimeSpanNullable(reader)",
+ BasicType.Guid => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadGuid(reader)",
+ BasicType.GuidNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadGuidNullable(reader)",
+ BasicType.Color => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadColor(reader)",
+ BasicType.ColorNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadColorNullable(reader)",
+ BasicType.DbNull => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDbNull(reader)",
+ BasicType.Uri => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadUri(reader)",
+ BasicType.Version => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadVersion(reader)",
+ BasicType.DataTable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDataTable(reader, global::System.Text.Encoding.UTF8)",
+ BasicType.DataSet => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDataSet(reader, global::System.Text.Encoding.UTF8)",
+ _ => throw new InvalidOperationException()
+ };
+
+ public bool TryGetCollectionInfo(ITypeSymbol type, out CollectionInfo info)
+ {
+ var named = Bare(type) as INamedTypeSymbol;
+ var iface = named?.AllInterfaces.FirstOrDefault(i => Equal(i.OriginalDefinition, _collectionType))
+ ?? (named is not null && Equal(named.OriginalDefinition, _collectionType) ? named : null);
+ if (iface is null || iface.TypeArguments.Length != 1)
+ {
+ info = default;
+ return false;
+ }
+ info = new CollectionInfo(type, iface.TypeArguments[0]);
+ return true;
+ }
+
+ public bool TryGetDictionaryInfo(ITypeSymbol type, out DictionaryInfo info)
+ {
+ var named = Bare(type) as INamedTypeSymbol;
+ var iface = named?.AllInterfaces.FirstOrDefault(i => Equal(i.OriginalDefinition, _dictionaryType))
+ ?? (named is not null && Equal(named.OriginalDefinition, _dictionaryType) ? named : null);
+ if (iface is null || iface.TypeArguments.Length != 2)
+ {
+ info = default;
+ return false;
+ }
+ info = new DictionaryInfo(type, iface.TypeArguments[0], iface.TypeArguments[1]);
+ return true;
+ }
+
+ public bool IsNameValueCollection(ITypeSymbol type) => Equal(Bare(type), _nameValueCollectionType);
+ public bool IsEnum(ITypeSymbol type) => Bare(type).TypeKind == TypeKind.Enum;
+ public bool IsExplicitStruct(ITypeSymbol type) => Bare(type).IsValueType && Bare(type).TypeKind != TypeKind.Enum && Bare(type).SpecialType == SpecialType.None;
+ public bool IsNullable(ITypeSymbol type) => !Bare(type).IsValueType || IsNullableValueType(type);
+ private bool IsNullableValueType(ITypeSymbol type) => type is INamedTypeSymbol named && Equal(named.OriginalDefinition, _nullableType);
+ private bool IsNullableComplex(ITypeSymbol type) => IsNullableValueType(type) && !TryGetBasicType(type, out _) && !IsEnum(type);
+ private ITypeSymbol Bare(ITypeSymbol type) => type is INamedTypeSymbol named && named.IsGenericType && named.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T ? named.TypeArguments[0] : type;
+ private bool Equal(ITypeSymbol? left, ITypeSymbol? right) => left is not null && right is not null && SymbolEqualityComparer.Default.Equals(left, right);
+ private static string Literal(string value) => SymbolDisplay.FormatLiteral(value, true);
+ }
+
+ private sealed class GroupComparer : IEqualityComparer<(INamedTypeSymbol ContainingType, string FileName)>
+ {
+ public bool Equals((INamedTypeSymbol ContainingType, string FileName) x, (INamedTypeSymbol ContainingType, string FileName) y)
+ => SymbolEqualityComparer.Default.Equals(x.ContainingType, y.ContainingType) && x.FileName == y.FileName;
+
+ public int GetHashCode((INamedTypeSymbol ContainingType, string FileName) obj)
+ => HashCode.Combine(SymbolEqualityComparer.Default.GetHashCode(obj.ContainingType), obj.FileName);
+ }
+
+ private sealed record GenerationMethod(IMethodSymbol Method, INamedTypeSymbol ContainingType, OperationKind Operation, ITypeSymbol RootType);
+ private sealed record LocalFunctionModel(OperationKind Operation, ITypeSymbol Type, string Name);
+ private sealed record MemberModel(ISymbol Symbol, ITypeSymbol Type, int Index, bool IsGetterOnlyMutableCollection);
+ private sealed record CollectionInfo(ITypeSymbol Type, ITypeSymbol ElementType);
+ private sealed record DictionaryInfo(ITypeSymbol Type, ITypeSymbol KeyType, ITypeSymbol ValueType);
+
+ private enum OperationKind { Reader, Writer }
+ private enum BasicType
+ {
+ String,
+ Bool,
+ BoolNullable,
+ Char,
+ CharNullable,
+ Int16,
+ Int16Nullable,
+ Int32,
+ Int32Nullable,
+ Int64,
+ Int64Nullable,
+ UInt16,
+ UInt16Nullable,
+ UInt32,
+ UInt32Nullable,
+ UInt64,
+ UInt64Nullable,
+ Single,
+ SingleNullable,
+ Double,
+ DoubleNullable,
+ Decimal,
+ DecimalNullable,
+ Byte,
+ ByteNullable,
+ SByte,
+ SByteNullable,
+ DateTime,
+ DateTimeNullable,
+ DateTimeOffset,
+ DateTimeOffsetNullable,
+ DateOnly,
+ DateOnlyNullable,
+ TimeOnly,
+ TimeOnlyNullable,
+ ByteArray,
+ TimeSpan,
+ TimeSpanNullable,
+ Guid,
+ GuidNullable,
+ Color,
+ ColorNullable,
+ DbNull,
+ Uri,
+ Version,
+ DataTable,
+ DataSet,
+ }
+
+ private sealed class CodeBuilder
+ {
+ private readonly StringBuilder _builder = new();
+ private int _indent;
+
+ public void Line(string text = "")
+ {
+ if (text.Length > 0)
+ _builder.Append(new string(' ', _indent * 4));
+ _builder.AppendLine(text);
+ }
+
+ public void Indent() => _indent++;
+ public void Unindent() => _indent--;
+ public override string ToString() => _builder.ToString();
+ }
+}
diff --git a/Salar.Bois.Generator/Salar.Bois.Generator.csproj b/Salar.Bois.Generator/Salar.Bois.Generator.csproj
index 0f7e920..9005785 100644
--- a/Salar.Bois.Generator/Salar.Bois.Generator.csproj
+++ b/Salar.Bois.Generator/Salar.Bois.Generator.csproj
@@ -4,11 +4,14 @@
net7.0
enable
enable
+ latest
+ true
true
Salar.Bois.snk
+
diff --git a/Salar.Bois.Generator/Serializers/BoisNumericSerializers.cs b/Salar.Bois.Generator/Serializers/BoisNumericSerializers.cs
index 5c7de43..9f537c2 100644
--- a/Salar.Bois.Generator/Serializers/BoisNumericSerializers.cs
+++ b/Salar.Bois.Generator/Serializers/BoisNumericSerializers.cs
@@ -127,6 +127,18 @@ public static float ReadVarSingle(BufferReaderBase reader)
return NumericSerializers.ReadVarByteNullable(reader);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadByte(BufferReaderBase reader)
+ {
+ return reader.ReadByte();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static sbyte ReadSByte(BufferReaderBase reader)
+ {
+ return reader.ReadSByte();
+ }
+
#endregion
#region Writers
@@ -280,6 +292,18 @@ public static void WriteVarInt(BufferWriterBase writer, sbyte? num)
NumericSerializers.WriteVarInt(writer, num);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteByte(BufferWriterBase writer, byte num)
+ {
+ writer.Write(num);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteSByte(BufferWriterBase writer, sbyte num)
+ {
+ writer.Write(num);
+ }
+
///
///
///
From 002fb0efeaa3e34f9cc65f678c12088d87149c4b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Mar 2026 01:14:25 +0000
Subject: [PATCH 02/13] Polish BOIS generator review fixes
Agent-Logs-Url: https://github.com/salarcode/Bois/sessions/f9c5b5a9-370b-4a0d-8898-6ba042ead9bf
Co-authored-by: salarcode <1272095+salarcode@users.noreply.github.com>
---
Salar.Bois.Generator/BoisSourceGenerator.cs | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/Salar.Bois.Generator/BoisSourceGenerator.cs b/Salar.Bois.Generator/BoisSourceGenerator.cs
index e814da0..1ca2d18 100644
--- a/Salar.Bois.Generator/BoisSourceGenerator.cs
+++ b/Salar.Bois.Generator/BoisSourceGenerator.cs
@@ -571,9 +571,9 @@ private bool EmitReadMember(MemberModel member, CodeBuilder builder, out string
}
private bool EmitWriteArray(IArrayTypeSymbol arrayType, CodeBuilder builder, out string error)
- => EmitWriteNestedArray(arrayType, "value", builder, out error, root:true);
+ => EmitWriteNestedArray(arrayType, "value", builder, out error);
- private bool EmitWriteNestedArray(IArrayTypeSymbol arrayType, string expression, CodeBuilder builder, out string error, bool root=false)
+ private bool EmitWriteNestedArray(IArrayTypeSymbol arrayType, string expression, CodeBuilder builder, out string error)
{
builder.Line($"if ({expression} is null)");
builder.Line("{");
@@ -617,9 +617,9 @@ private bool EmitReadArray(IArrayTypeSymbol arrayType, CodeBuilder builder, out
}
private bool EmitWriteCollection(CollectionInfo collectionInfo, CodeBuilder builder, out string error)
- => EmitWriteNestedCollection(collectionInfo, "value", builder, out error, root:true);
+ => EmitWriteNestedCollection(collectionInfo, "value", builder, out error);
- private bool EmitWriteNestedCollection(CollectionInfo collectionInfo, string expression, CodeBuilder builder, out string error, bool root=false)
+ private bool EmitWriteNestedCollection(CollectionInfo collectionInfo, string expression, CodeBuilder builder, out string error)
{
builder.Line($"if ({expression} is null)");
builder.Line("{");
@@ -681,9 +681,9 @@ private void EmitReadIntoCollectionBody(CollectionInfo collectionInfo, string ta
}
private bool EmitWriteDictionary(DictionaryInfo dictionaryInfo, CodeBuilder builder, out string error)
- => EmitWriteNestedDictionary(dictionaryInfo, "value", builder, out error, root:true);
+ => EmitWriteNestedDictionary(dictionaryInfo, "value", builder, out error);
- private bool EmitWriteNestedDictionary(DictionaryInfo dictionaryInfo, string expression, CodeBuilder builder, out string error, bool root=false)
+ private bool EmitWriteNestedDictionary(DictionaryInfo dictionaryInfo, string expression, CodeBuilder builder, out string error)
{
builder.Line($"if ({expression} is null)");
builder.Line("{");
@@ -748,9 +748,9 @@ private void EmitReadIntoDictionaryBody(DictionaryInfo dictionaryInfo, string ta
}
private bool EmitWriteNameValueCollection(CodeBuilder builder, out string error)
- => EmitWriteNestedNameValue("value", builder, out error, root:true);
+ => EmitWriteNestedNameValue("value", builder, out error);
- private bool EmitWriteNestedNameValue(string expression, CodeBuilder builder, out string error, bool root=false)
+ private bool EmitWriteNestedNameValue(string expression, CodeBuilder builder, out string error)
{
builder.Line($"if ({expression} is null)");
builder.Line("{");
@@ -902,7 +902,7 @@ public bool TryGetMembers(ITypeSymbol type, out ImmutableArray memb
continue;
var getterOnlyMutable = property.SetMethod is null && (TryGetCollectionInfo(property.Type, out _) || TryGetDictionaryInfo(property.Type, out _) || IsNameValueCollection(property.Type));
- if (property.SetMethod is not null && property.SetMethod.DeclaredAccessibility == Accessibility.Public || getterOnlyMutable)
+ if ((property.SetMethod is not null && property.SetMethod.DeclaredAccessibility == Accessibility.Public) || getterOnlyMutable)
{
Insert(ordered, new MemberModel(property, property.Type, index, getterOnlyMutable));
}
From 841163c00d12d6ea3b03d18951e6125e0f51ad61 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Mar 2026 10:19:45 +0000
Subject: [PATCH 03/13] Simplify generated serializer output
Agent-Logs-Url: https://github.com/salarcode/Bois/sessions/8fa73abc-b13f-4aab-8054-57aac1e7a149
Co-authored-by: salarcode <1272095+salarcode@users.noreply.github.com>
---
Salar.Bois.Generator/BoisSourceGenerator.cs | 176 ++++++++++----------
1 file changed, 92 insertions(+), 84 deletions(-)
diff --git a/Salar.Bois.Generator/BoisSourceGenerator.cs b/Salar.Bois.Generator/BoisSourceGenerator.cs
index 1ca2d18..5e10c3f 100644
--- a/Salar.Bois.Generator/BoisSourceGenerator.cs
+++ b/Salar.Bois.Generator/BoisSourceGenerator.cs
@@ -216,6 +216,7 @@ public string Emit(ImmutableArray methods)
var builder = new CodeBuilder();
builder.Line("// ");
builder.Line("#nullable enable");
+ builder.Line("using global::Salar.Bois.Generator.Serializers;");
builder.Line();
if (!_containingType.ContainingNamespace.IsGlobalNamespace)
@@ -283,17 +284,24 @@ public void Emit(CodeBuilder builder)
if (_method.Operation == OperationKind.Reader)
{
var sourceName = Escape(_method.Method.Parameters[0].Name);
- var rootFunction = EnsureReadFunction(_method.RootType);
builder.Line($"var reader = new global::Salar.BinaryBuffers.Compatibility.StreamBufferReader({sourceName});");
- builder.Line($"return {rootFunction}(reader);");
+ if (!TryEmitRead(_method.RootType, builder, out error))
+ {
+ _owner.Report(_method.Method.Locations.FirstOrDefault(), error);
+ builder.Line($"throw new global::System.NotSupportedException({Literal(error)});");
+ }
}
else
{
var outputName = Escape(_method.Method.Parameters[0].Name);
var valueName = Escape(_method.Method.Parameters[1].Name);
- var rootFunction = EnsureWriteFunction(_method.RootType);
builder.Line($"var writer = new global::Salar.BinaryBuffers.Compatibility.StreamBufferWriter({outputName});");
- builder.Line($"{rootFunction}(writer, {valueName});");
+ builder.Line($"var value = {valueName};");
+ if (!TryEmitWrite(_method.RootType, builder, out error))
+ {
+ _owner.Report(_method.Method.Locations.FirstOrDefault(), error);
+ builder.Line($"throw new global::System.NotSupportedException({Literal(error)});");
+ }
}
while (_pending.Count > 0)
@@ -421,7 +429,7 @@ private bool TryEmitRead(ITypeSymbol type, CodeBuilder builder, out string error
if (_owner.IsEnum(type))
{
- builder.Line($"return global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadEnumGeneric<{TypeName(type)}>(reader);");
+ builder.Line($"return BoisPrimitiveReaders.ReadEnumGeneric<{TypeName(type)}>(reader);");
error = string.Empty;
return true;
}
@@ -451,11 +459,11 @@ private bool EmitWriteObject(ITypeSymbol type, CodeBuilder builder, out string e
builder.Line("if (value is null)");
builder.Line("{");
builder.Indent();
- builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteNullValue(writer);");
+ builder.Line("BoisPrimitiveWriters.WriteNullValue(writer);");
builder.Line("return;");
builder.Unindent();
builder.Line("}");
- builder.Line($"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteUIntNullableMemberCount(writer, {members.Length}u);");
+ builder.Line($"BoisNumericSerializers.WriteUIntNullableMemberCount(writer, {members.Length}u);");
}
foreach (var member in members)
@@ -475,7 +483,7 @@ private bool EmitReadObject(ITypeSymbol type, CodeBuilder builder, out string er
if (!_owner.IsExplicitStruct(type))
{
- builder.Line("var memberCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("var memberCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
builder.Line("if (memberCount is null)");
builder.Indent();
builder.Line("return null!;");
@@ -535,7 +543,7 @@ private bool EmitReadMember(MemberModel member, CodeBuilder builder, out string
}
if (_owner.IsEnum(member.Type))
{
- builder.Line($"{target} = global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadEnumGeneric<{TypeName(member.Type)}>(reader);");
+ builder.Line($"{target} = BoisPrimitiveReaders.ReadEnumGeneric<{TypeName(member.Type)}>(reader);");
error = string.Empty;
return true;
}
@@ -578,13 +586,13 @@ private bool EmitWriteNestedArray(IArrayTypeSymbol arrayType, string expression,
builder.Line($"if ({expression} is null)");
builder.Line("{");
builder.Indent();
- builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteNullValue(writer);");
+ builder.Line("BoisPrimitiveWriters.WriteNullValue(writer);");
builder.Unindent();
builder.Line("}");
builder.Line("else");
builder.Line("{");
builder.Indent();
- builder.Line($"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Length);");
+ builder.Line($"BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Length);");
builder.Line($"foreach (var item in {expression})");
builder.Line("{");
builder.Indent();
@@ -599,7 +607,7 @@ private bool EmitWriteNestedArray(IArrayTypeSymbol arrayType, string expression,
private bool EmitReadArray(IArrayTypeSymbol arrayType, CodeBuilder builder, out string error)
{
- builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("var itemCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
builder.Line("if (itemCount is null)");
builder.Indent();
builder.Line("return null!;");
@@ -624,13 +632,13 @@ private bool EmitWriteNestedCollection(CollectionInfo collectionInfo, string exp
builder.Line($"if ({expression} is null)");
builder.Line("{");
builder.Indent();
- builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteNullValue(writer);");
+ builder.Line("BoisPrimitiveWriters.WriteNullValue(writer);");
builder.Unindent();
builder.Line("}");
builder.Line("else");
builder.Line("{");
builder.Indent();
- builder.Line($"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Count);");
+ builder.Line($"BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Count);");
builder.Line($"foreach (var item in {expression})");
builder.Line("{");
builder.Indent();
@@ -647,7 +655,7 @@ private bool EmitReadCollection(ITypeSymbol type, CollectionInfo collectionInfo,
{
if (!_owner.TryGetCreationExpression(type, out var create, out error))
return false;
- builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("var itemCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
builder.Line("if (itemCount is null)");
builder.Indent();
builder.Line("return null!;");
@@ -661,7 +669,7 @@ private bool EmitReadCollection(ITypeSymbol type, CollectionInfo collectionInfo,
private void EmitReadIntoCollection(CollectionInfo collectionInfo, string target, CodeBuilder builder)
{
- builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("var itemCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
builder.Line("if (itemCount is not null)");
builder.Line("{");
builder.Indent();
@@ -688,13 +696,13 @@ private bool EmitWriteNestedDictionary(DictionaryInfo dictionaryInfo, string exp
builder.Line($"if ({expression} is null)");
builder.Line("{");
builder.Indent();
- builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteNullValue(writer);");
+ builder.Line("BoisPrimitiveWriters.WriteNullValue(writer);");
builder.Unindent();
builder.Line("}");
builder.Line("else");
builder.Line("{");
builder.Indent();
- builder.Line($"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Count);");
+ builder.Line($"BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Count);");
builder.Line($"foreach (var item in {expression})");
builder.Line("{");
builder.Indent();
@@ -712,7 +720,7 @@ private bool EmitReadDictionary(ITypeSymbol type, DictionaryInfo dictionaryInfo,
{
if (!_owner.TryGetCreationExpression(type, out var create, out error))
return false;
- builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("var itemCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
builder.Line("if (itemCount is null)");
builder.Indent();
builder.Line("return null!;");
@@ -726,7 +734,7 @@ private bool EmitReadDictionary(ITypeSymbol type, DictionaryInfo dictionaryInfo,
private void EmitReadIntoDictionary(DictionaryInfo dictionaryInfo, string target, CodeBuilder builder)
{
- builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("var itemCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
builder.Line("if (itemCount is not null)");
builder.Line("{");
builder.Indent();
@@ -755,18 +763,18 @@ private bool EmitWriteNestedNameValue(string expression, CodeBuilder builder, ou
builder.Line($"if ({expression} is null)");
builder.Line("{");
builder.Indent();
- builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteNullValue(writer);");
+ builder.Line("BoisPrimitiveWriters.WriteNullValue(writer);");
builder.Unindent();
builder.Line("}");
builder.Line("else");
builder.Line("{");
builder.Indent();
- builder.Line($"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Count);");
+ builder.Line($"BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Count);");
builder.Line($"foreach (var key in {expression}.AllKeys)");
builder.Line("{");
builder.Indent();
- builder.Line("global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, key, global::System.Text.Encoding.UTF8);");
- builder.Line($"global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, {expression}[key], global::System.Text.Encoding.UTF8);");
+ builder.Line("BoisPrimitiveWriters.WriteValue(writer, key, global::System.Text.Encoding.UTF8);");
+ builder.Line($"BoisPrimitiveWriters.WriteValue(writer, {expression}[key], global::System.Text.Encoding.UTF8);");
builder.Unindent();
builder.Line("}");
builder.Unindent();
@@ -779,7 +787,7 @@ private bool EmitReadNameValueCollection(ITypeSymbol type, CodeBuilder builder,
{
if (!_owner.TryGetCreationExpression(type, out var create, out error))
return false;
- builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("var itemCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
builder.Line("if (itemCount is null)");
builder.Indent();
builder.Line("return null!;");
@@ -793,7 +801,7 @@ private bool EmitReadNameValueCollection(ITypeSymbol type, CodeBuilder builder,
private void EmitReadIntoNameValue(string target, CodeBuilder builder)
{
- builder.Line("var itemCount = global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
+ builder.Line("var itemCount = BoisNumericSerializers.ReadVarUInt32Nullable(reader);");
builder.Line("if (itemCount is not null)");
builder.Line("{");
builder.Indent();
@@ -807,8 +815,8 @@ private void EmitReadIntoNameValueBody(string target, string countExpression, Co
builder.Line($"for (var i = 0; i < {countExpression}; i++)");
builder.Line("{");
builder.Indent();
- builder.Line("var key = global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8);");
- builder.Line("var value = global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8);");
+ builder.Line("var key = BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8);");
+ builder.Line("var value = BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8);");
builder.Line($"{target}.Add(key, value);");
builder.Unindent();
builder.Line("}");
@@ -828,7 +836,7 @@ private string GetReadValueExpression(ITypeSymbol type)
if (_owner.TryGetBasicType(type, out var basicType))
return _owner.GetReadExpression(type, basicType);
if (_owner.IsEnum(type))
- return $"global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadEnumGeneric<{TypeName(type)}>(reader)";
+ return $"BoisPrimitiveReaders.ReadEnumGeneric<{TypeName(type)}>(reader)";
return $"{EnsureReadFunction(type)}(reader)";
}
@@ -1039,13 +1047,13 @@ public bool TryGetBasicType(ITypeSymbol type, out BasicType basicType)
public string GetWriteStatement(ITypeSymbol type, BasicType basicType, string expression) => basicType switch
{
- BasicType.String => $"global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, {expression}, global::System.Text.Encoding.UTF8);",
- BasicType.Bool or BasicType.BoolNullable or BasicType.Char or BasicType.CharNullable or BasicType.DateTime or BasicType.DateTimeNullable or BasicType.DateTimeOffset or BasicType.DateTimeOffsetNullable or BasicType.TimeSpan or BasicType.TimeSpanNullable or BasicType.Guid or BasicType.GuidNullable or BasicType.Color or BasicType.ColorNullable or BasicType.DbNull or BasicType.Uri or BasicType.Version or BasicType.DateOnly or BasicType.DateOnlyNullable or BasicType.TimeOnly or BasicType.TimeOnlyNullable or BasicType.ByteArray => $"global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, {expression});",
- BasicType.Int16 or BasicType.Int16Nullable or BasicType.Int32 or BasicType.Int32Nullable or BasicType.Int64 or BasicType.Int64Nullable or BasicType.UInt16 or BasicType.UInt16Nullable or BasicType.UInt32 or BasicType.UInt32Nullable or BasicType.UInt64 or BasicType.UInt64Nullable or BasicType.ByteNullable or BasicType.SByteNullable => $"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteVarInt(writer, {expression});",
- BasicType.Single or BasicType.SingleNullable or BasicType.Double or BasicType.DoubleNullable or BasicType.Decimal or BasicType.DecimalNullable => $"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteVarDecimal(writer, {expression});",
- BasicType.Byte => $"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteByte(writer, {expression});",
- BasicType.SByte => $"global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.WriteSByte(writer, {expression});",
- BasicType.DataTable or BasicType.DataSet => $"global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, {expression}, global::System.Text.Encoding.UTF8);",
+ BasicType.String => $"BoisPrimitiveWriters.WriteValue(writer, {expression}, global::System.Text.Encoding.UTF8);",
+ BasicType.Bool or BasicType.BoolNullable or BasicType.Char or BasicType.CharNullable or BasicType.DateTime or BasicType.DateTimeNullable or BasicType.DateTimeOffset or BasicType.DateTimeOffsetNullable or BasicType.TimeSpan or BasicType.TimeSpanNullable or BasicType.Guid or BasicType.GuidNullable or BasicType.Color or BasicType.ColorNullable or BasicType.DbNull or BasicType.Uri or BasicType.Version or BasicType.DateOnly or BasicType.DateOnlyNullable or BasicType.TimeOnly or BasicType.TimeOnlyNullable or BasicType.ByteArray => $"BoisPrimitiveWriters.WriteValue(writer, {expression});",
+ BasicType.Int16 or BasicType.Int16Nullable or BasicType.Int32 or BasicType.Int32Nullable or BasicType.Int64 or BasicType.Int64Nullable or BasicType.UInt16 or BasicType.UInt16Nullable or BasicType.UInt32 or BasicType.UInt32Nullable or BasicType.UInt64 or BasicType.UInt64Nullable or BasicType.ByteNullable or BasicType.SByteNullable => $"BoisNumericSerializers.WriteVarInt(writer, {expression});",
+ BasicType.Single or BasicType.SingleNullable or BasicType.Double or BasicType.DoubleNullable or BasicType.Decimal or BasicType.DecimalNullable => $"BoisNumericSerializers.WriteVarDecimal(writer, {expression});",
+ BasicType.Byte => $"BoisNumericSerializers.WriteByte(writer, {expression});",
+ BasicType.SByte => $"BoisNumericSerializers.WriteSByte(writer, {expression});",
+ BasicType.DataTable or BasicType.DataSet => $"BoisPrimitiveWriters.WriteValue(writer, {expression}, global::System.Text.Encoding.UTF8);",
_ => throw new InvalidOperationException()
};
@@ -1055,58 +1063,58 @@ public string GetEnumWriteStatement(ITypeSymbol type, string expression)
? $"({expression}.HasValue ? (global::System.Enum)(object){expression}.Value : null)"
: $"({expression} is null ? null : (global::System.Enum)(object){expression})";
var isNullable = IsNullable(type) ? "true" : "false";
- return $"global::Salar.Bois.Generator.Serializers.BoisPrimitiveWriters.WriteValue(writer, {enumExpression}, typeof({Bare(type).ToDisplayString(QualifiedTypeFormat)}), {isNullable});";
+ return $"BoisPrimitiveWriters.WriteValue(writer, {enumExpression}, typeof({Bare(type).ToDisplayString(QualifiedTypeFormat)}), {isNullable});";
}
public string GetReadExpression(ITypeSymbol type, BasicType basicType) => basicType switch
{
- BasicType.String => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8)",
- BasicType.Bool => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadBoolean(reader)",
- BasicType.BoolNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadBooleanNullable(reader)",
- BasicType.Char => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadChar(reader)",
- BasicType.CharNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadCharNullable(reader)",
- BasicType.Int16 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt16(reader)",
- BasicType.Int16Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt16Nullable(reader)",
- BasicType.Int32 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt32(reader)",
- BasicType.Int32Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt32Nullable(reader)",
- BasicType.Int64 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt64(reader)",
- BasicType.Int64Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarInt64Nullable(reader)",
- BasicType.UInt16 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt16(reader)",
- BasicType.UInt16Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt16Nullable(reader)",
- BasicType.UInt32 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32(reader)",
- BasicType.UInt32Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt32Nullable(reader)",
- BasicType.UInt64 => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt64(reader)",
- BasicType.UInt64Nullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarUInt64Nullable(reader)",
- BasicType.Single => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarSingle(reader)",
- BasicType.SingleNullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarSingleNullable(reader)",
- BasicType.Double => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarDouble(reader)",
- BasicType.DoubleNullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarDoubleNullable(reader)",
- BasicType.Decimal => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarDecimal(reader)",
- BasicType.DecimalNullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarDecimalNullable(reader)",
- BasicType.Byte => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadByte(reader)",
- BasicType.ByteNullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarByteNullable(reader)",
- BasicType.SByte => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadSByte(reader)",
- BasicType.SByteNullable => "global::Salar.Bois.Generator.Serializers.BoisNumericSerializers.ReadVarSByteNullable(reader)",
- BasicType.DateTime => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateTime(reader)",
- BasicType.DateTimeNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateTimeNullable(reader)",
- BasicType.DateTimeOffset => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateTimeOffset(reader)",
- BasicType.DateTimeOffsetNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateTimeOffsetNullable(reader)",
- BasicType.DateOnly => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateOnly(reader)",
- BasicType.DateOnlyNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDateOnlyNullable(reader)",
- BasicType.TimeOnly => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadTimeOnly(reader)",
- BasicType.TimeOnlyNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadTimeOnlyNullable(reader)",
- BasicType.ByteArray => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadByteArray(reader)",
- BasicType.TimeSpan => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadTimeSpan(reader)",
- BasicType.TimeSpanNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadTimeSpanNullable(reader)",
- BasicType.Guid => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadGuid(reader)",
- BasicType.GuidNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadGuidNullable(reader)",
- BasicType.Color => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadColor(reader)",
- BasicType.ColorNullable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadColorNullable(reader)",
- BasicType.DbNull => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDbNull(reader)",
- BasicType.Uri => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadUri(reader)",
- BasicType.Version => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadVersion(reader)",
- BasicType.DataTable => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDataTable(reader, global::System.Text.Encoding.UTF8)",
- BasicType.DataSet => "global::Salar.Bois.Generator.Serializers.BoisPrimitiveReaders.ReadDataSet(reader, global::System.Text.Encoding.UTF8)",
+ BasicType.String => "BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8)",
+ BasicType.Bool => "BoisPrimitiveReaders.ReadBoolean(reader)",
+ BasicType.BoolNullable => "BoisPrimitiveReaders.ReadBooleanNullable(reader)",
+ BasicType.Char => "BoisPrimitiveReaders.ReadChar(reader)",
+ BasicType.CharNullable => "BoisPrimitiveReaders.ReadCharNullable(reader)",
+ BasicType.Int16 => "BoisNumericSerializers.ReadVarInt16(reader)",
+ BasicType.Int16Nullable => "BoisNumericSerializers.ReadVarInt16Nullable(reader)",
+ BasicType.Int32 => "BoisNumericSerializers.ReadVarInt32(reader)",
+ BasicType.Int32Nullable => "BoisNumericSerializers.ReadVarInt32Nullable(reader)",
+ BasicType.Int64 => "BoisNumericSerializers.ReadVarInt64(reader)",
+ BasicType.Int64Nullable => "BoisNumericSerializers.ReadVarInt64Nullable(reader)",
+ BasicType.UInt16 => "BoisNumericSerializers.ReadVarUInt16(reader)",
+ BasicType.UInt16Nullable => "BoisNumericSerializers.ReadVarUInt16Nullable(reader)",
+ BasicType.UInt32 => "BoisNumericSerializers.ReadVarUInt32(reader)",
+ BasicType.UInt32Nullable => "BoisNumericSerializers.ReadVarUInt32Nullable(reader)",
+ BasicType.UInt64 => "BoisNumericSerializers.ReadVarUInt64(reader)",
+ BasicType.UInt64Nullable => "BoisNumericSerializers.ReadVarUInt64Nullable(reader)",
+ BasicType.Single => "BoisNumericSerializers.ReadVarSingle(reader)",
+ BasicType.SingleNullable => "BoisNumericSerializers.ReadVarSingleNullable(reader)",
+ BasicType.Double => "BoisNumericSerializers.ReadVarDouble(reader)",
+ BasicType.DoubleNullable => "BoisNumericSerializers.ReadVarDoubleNullable(reader)",
+ BasicType.Decimal => "BoisNumericSerializers.ReadVarDecimal(reader)",
+ BasicType.DecimalNullable => "BoisNumericSerializers.ReadVarDecimalNullable(reader)",
+ BasicType.Byte => "BoisNumericSerializers.ReadByte(reader)",
+ BasicType.ByteNullable => "BoisNumericSerializers.ReadVarByteNullable(reader)",
+ BasicType.SByte => "BoisNumericSerializers.ReadSByte(reader)",
+ BasicType.SByteNullable => "BoisNumericSerializers.ReadVarSByteNullable(reader)",
+ BasicType.DateTime => "BoisPrimitiveReaders.ReadDateTime(reader)",
+ BasicType.DateTimeNullable => "BoisPrimitiveReaders.ReadDateTimeNullable(reader)",
+ BasicType.DateTimeOffset => "BoisPrimitiveReaders.ReadDateTimeOffset(reader)",
+ BasicType.DateTimeOffsetNullable => "BoisPrimitiveReaders.ReadDateTimeOffsetNullable(reader)",
+ BasicType.DateOnly => "BoisPrimitiveReaders.ReadDateOnly(reader)",
+ BasicType.DateOnlyNullable => "BoisPrimitiveReaders.ReadDateOnlyNullable(reader)",
+ BasicType.TimeOnly => "BoisPrimitiveReaders.ReadTimeOnly(reader)",
+ BasicType.TimeOnlyNullable => "BoisPrimitiveReaders.ReadTimeOnlyNullable(reader)",
+ BasicType.ByteArray => "BoisPrimitiveReaders.ReadByteArray(reader)",
+ BasicType.TimeSpan => "BoisPrimitiveReaders.ReadTimeSpan(reader)",
+ BasicType.TimeSpanNullable => "BoisPrimitiveReaders.ReadTimeSpanNullable(reader)",
+ BasicType.Guid => "BoisPrimitiveReaders.ReadGuid(reader)",
+ BasicType.GuidNullable => "BoisPrimitiveReaders.ReadGuidNullable(reader)",
+ BasicType.Color => "BoisPrimitiveReaders.ReadColor(reader)",
+ BasicType.ColorNullable => "BoisPrimitiveReaders.ReadColorNullable(reader)",
+ BasicType.DbNull => "BoisPrimitiveReaders.ReadDbNull(reader)",
+ BasicType.Uri => "BoisPrimitiveReaders.ReadUri(reader)",
+ BasicType.Version => "BoisPrimitiveReaders.ReadVersion(reader)",
+ BasicType.DataTable => "BoisPrimitiveReaders.ReadDataTable(reader, global::System.Text.Encoding.UTF8)",
+ BasicType.DataSet => "BoisPrimitiveReaders.ReadDataSet(reader, global::System.Text.Encoding.UTF8)",
_ => throw new InvalidOperationException()
};
From aa0b5944c11438ea4370549afbd1b9a446e02244 Mon Sep 17 00:00:00 2001
From: salarcode
Date: Sun, 29 Mar 2026 22:08:54 +1100
Subject: [PATCH 04/13] Using using
---
Salar.Bois.Generator/BoisSourceGenerator.cs | 25 ++++++++++-----------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/Salar.Bois.Generator/BoisSourceGenerator.cs b/Salar.Bois.Generator/BoisSourceGenerator.cs
index 5e10c3f..19cfc81 100644
--- a/Salar.Bois.Generator/BoisSourceGenerator.cs
+++ b/Salar.Bois.Generator/BoisSourceGenerator.cs
@@ -2,10 +2,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
-using System;
-using System.Collections.Generic;
using System.Collections.Immutable;
-using System.Linq;
using System.Text;
namespace Salar.Bois.Generator;
@@ -216,7 +213,9 @@ public string Emit(ImmutableArray methods)
var builder = new CodeBuilder();
builder.Line("// ");
builder.Line("#nullable enable");
+ builder.Line("using global::System;");
builder.Line("using global::Salar.Bois.Generator.Serializers;");
+ builder.Line("using global::Salar.BinaryBuffers.Compatibility;");
builder.Line();
if (!_containingType.ContainingNamespace.IsGlobalNamespace)
@@ -284,23 +283,23 @@ public void Emit(CodeBuilder builder)
if (_method.Operation == OperationKind.Reader)
{
var sourceName = Escape(_method.Method.Parameters[0].Name);
- builder.Line($"var reader = new global::Salar.BinaryBuffers.Compatibility.StreamBufferReader({sourceName});");
+ builder.Line($"var reader = new StreamBufferReader({sourceName});");
if (!TryEmitRead(_method.RootType, builder, out error))
{
_owner.Report(_method.Method.Locations.FirstOrDefault(), error);
- builder.Line($"throw new global::System.NotSupportedException({Literal(error)});");
+ builder.Line($"throw new global::NotSupportedException({Literal(error)});");
}
}
else
{
var outputName = Escape(_method.Method.Parameters[0].Name);
var valueName = Escape(_method.Method.Parameters[1].Name);
- builder.Line($"var writer = new global::Salar.BinaryBuffers.Compatibility.StreamBufferWriter({outputName});");
+ builder.Line($"var writer = new StreamBufferWriter({outputName});");
builder.Line($"var value = {valueName};");
if (!TryEmitWrite(_method.RootType, builder, out error))
{
_owner.Report(_method.Method.Locations.FirstOrDefault(), error);
- builder.Line($"throw new global::System.NotSupportedException({Literal(error)});");
+ builder.Line($"throw new global::NotSupportedException({Literal(error)});");
}
}
@@ -316,7 +315,7 @@ private void EmitStub(CodeBuilder builder, string error)
builder.Line(BuildSignature(_method.Method));
builder.Line("{");
builder.Indent();
- builder.Line($"throw new global::System.NotSupportedException({Literal(error)});");
+ builder.Line($"throw new global::NotSupportedException({Literal(error)});");
builder.Unindent();
builder.Line("}");
}
@@ -363,7 +362,7 @@ private void EmitReadLocalFunction(CodeBuilder builder, LocalFunctionModel funct
if (!TryEmitRead(function.Type, builder, out var error))
{
_owner.Report(_method.Method.Locations.FirstOrDefault(), error);
- builder.Line($"throw new global::System.NotSupportedException({Literal(error)});");
+ builder.Line($"throw new global::NotSupportedException({Literal(error)});");
}
builder.Unindent();
@@ -380,7 +379,7 @@ private void EmitWriteLocalFunction(CodeBuilder builder, LocalFunctionModel func
if (!TryEmitWrite(function.Type, builder, out var error))
{
_owner.Report(_method.Method.Locations.FirstOrDefault(), error);
- builder.Line($"throw new global::System.NotSupportedException({Literal(error)});");
+ builder.Line($"throw new global::NotSupportedException({Literal(error)});");
}
builder.Unindent();
@@ -551,7 +550,7 @@ private bool EmitReadMember(MemberModel member, CodeBuilder builder, out string
if (member.IsGetterOnlyMutableCollection)
{
var localName = Escape(member.Symbol.Name) + "Target";
- builder.Line($"var {localName} = {target} ?? throw new global::System.InvalidOperationException({Literal($"Property '{member.Symbol.Name}' returned null during deserialization.")});");
+ builder.Line($"var {localName} = {target} ?? throw new global::InvalidOperationException({Literal($"Property '{member.Symbol.Name}' returned null during deserialization.")});");
builder.Line($"{localName}.Clear();");
if (_owner.TryGetDictionaryInfo(member.Type, out var dict))
{
@@ -1060,8 +1059,8 @@ public bool TryGetBasicType(ITypeSymbol type, out BasicType basicType)
public string GetEnumWriteStatement(ITypeSymbol type, string expression)
{
var enumExpression = IsNullableValueType(type)
- ? $"({expression}.HasValue ? (global::System.Enum)(object){expression}.Value : null)"
- : $"({expression} is null ? null : (global::System.Enum)(object){expression})";
+ ? $"({expression}.HasValue ? (global::Enum)(object){expression}.Value : null)"
+ : $"({expression} is null ? null : (global::Enum)(object){expression})";
var isNullable = IsNullable(type) ? "true" : "false";
return $"BoisPrimitiveWriters.WriteValue(writer, {enumExpression}, typeof({Bare(type).ToDisplayString(QualifiedTypeFormat)}), {isNullable});";
}
From 9569bd6f9cd70955138081e03458694bbbc578cb Mon Sep 17 00:00:00 2001
From: salarcode
Date: Sun, 29 Mar 2026 22:21:12 +1100
Subject: [PATCH 05/13] Multi line
---
Salar.Bois.Generator/BoisSourceGenerator.cs | 72 +++++++++++++++++++--
1 file changed, 66 insertions(+), 6 deletions(-)
diff --git a/Salar.Bois.Generator/BoisSourceGenerator.cs b/Salar.Bois.Generator/BoisSourceGenerator.cs
index 19cfc81..c8e6c6d 100644
--- a/Salar.Bois.Generator/BoisSourceGenerator.cs
+++ b/Salar.Bois.Generator/BoisSourceGenerator.cs
@@ -905,10 +905,17 @@ public bool TryGetMembers(ITypeSymbol type, out ImmutableArray memb
if (symbol is IPropertySymbol property)
{
- if (property.IsStatic || property.DeclaredAccessibility != Accessibility.Public || property.IsIndexer || property.GetMethod is null || property.GetMethod.DeclaredAccessibility != Accessibility.Public)
+ if (property.IsStatic ||
+ property.DeclaredAccessibility != Accessibility.Public ||
+ property.IsIndexer ||
+ property.GetMethod is null ||
+ property.GetMethod.DeclaredAccessibility != Accessibility.Public)
continue;
- var getterOnlyMutable = property.SetMethod is null && (TryGetCollectionInfo(property.Type, out _) || TryGetDictionaryInfo(property.Type, out _) || IsNameValueCollection(property.Type));
+ var getterOnlyMutable = property.SetMethod is null &&
+ (TryGetCollectionInfo(property.Type, out _) ||
+ TryGetDictionaryInfo(property.Type, out _) ||
+ IsNameValueCollection(property.Type));
if ((property.SetMethod is not null && property.SetMethod.DeclaredAccessibility == Accessibility.Public) || getterOnlyMutable)
{
Insert(ordered, new MemberModel(property, property.Type, index, getterOnlyMutable));
@@ -1012,8 +1019,16 @@ public bool TryGetBasicType(ITypeSymbol type, out BasicType basicType)
var bare = Bare(type);
var nullableValue = IsNullableValueType(type);
- if (bare.SpecialType == SpecialType.System_String) { basicType = BasicType.String; return true; }
- if (bare.SpecialType == SpecialType.System_Boolean) { basicType = nullableValue ? BasicType.BoolNullable : BasicType.Bool; return true; }
+ if (bare.SpecialType == SpecialType.System_String)
+ {
+ basicType = BasicType.String;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_Boolean)
+ {
+ basicType = nullableValue ? BasicType.BoolNullable : BasicType.Bool;
+ return true;
+ }
if (bare.SpecialType == SpecialType.System_Char) { basicType = nullableValue ? BasicType.CharNullable : BasicType.Char; return true; }
if (bare.SpecialType == SpecialType.System_Int16) { basicType = nullableValue ? BasicType.Int16Nullable : BasicType.Int16; return true; }
if (bare.SpecialType == SpecialType.System_Int32) { basicType = nullableValue ? BasicType.Int32Nullable : BasicType.Int32; return true; }
@@ -1047,8 +1062,35 @@ public bool TryGetBasicType(ITypeSymbol type, out BasicType basicType)
public string GetWriteStatement(ITypeSymbol type, BasicType basicType, string expression) => basicType switch
{
BasicType.String => $"BoisPrimitiveWriters.WriteValue(writer, {expression}, global::System.Text.Encoding.UTF8);",
- BasicType.Bool or BasicType.BoolNullable or BasicType.Char or BasicType.CharNullable or BasicType.DateTime or BasicType.DateTimeNullable or BasicType.DateTimeOffset or BasicType.DateTimeOffsetNullable or BasicType.TimeSpan or BasicType.TimeSpanNullable or BasicType.Guid or BasicType.GuidNullable or BasicType.Color or BasicType.ColorNullable or BasicType.DbNull or BasicType.Uri or BasicType.Version or BasicType.DateOnly or BasicType.DateOnlyNullable or BasicType.TimeOnly or BasicType.TimeOnlyNullable or BasicType.ByteArray => $"BoisPrimitiveWriters.WriteValue(writer, {expression});",
- BasicType.Int16 or BasicType.Int16Nullable or BasicType.Int32 or BasicType.Int32Nullable or BasicType.Int64 or BasicType.Int64Nullable or BasicType.UInt16 or BasicType.UInt16Nullable or BasicType.UInt32 or BasicType.UInt32Nullable or BasicType.UInt64 or BasicType.UInt64Nullable or BasicType.ByteNullable or BasicType.SByteNullable => $"BoisNumericSerializers.WriteVarInt(writer, {expression});",
+ BasicType.Bool or
+ BasicType.BoolNullable or
+ BasicType.Char or
+ BasicType.CharNullable or
+ BasicType.DateTime or
+ BasicType.DateTimeNullable or
+ BasicType.DateTimeOffset or
+ BasicType.DateTimeOffsetNullable or
+ BasicType.TimeSpan or
+ BasicType.TimeSpanNullable or
+ BasicType.Guid or
+ BasicType.GuidNullable or
+ BasicType.Color or
+ BasicType.ColorNullable or
+ BasicType.DbNull or
+ BasicType.Uri or
+ BasicType.Version or
+ BasicType.DateOnly or
+ BasicType.DateOnlyNullable or
+ BasicType.TimeOnly or
+ BasicType.TimeOnlyNullable or
+ BasicType.ByteArray => $"BoisPrimitiveWriters.WriteValue(writer, {expression});",
+ BasicType.Int16 or
+ BasicType.Int16Nullable or
+ BasicType.Int32 or
+ BasicType.Int32Nullable or
+ BasicType.Int64 or
+ BasicType.Int64Nullable or BasicType.UInt16 or BasicType.UInt16Nullable or BasicType.UInt32 or BasicType.UInt32Nullable or BasicType.UInt64 or BasicType.UInt64Nullable or BasicType.ByteNullable or
+ BasicType.SByteNullable => $"BoisNumericSerializers.WriteVarInt(writer, {expression});",
BasicType.Single or BasicType.SingleNullable or BasicType.Double or BasicType.DoubleNullable or BasicType.Decimal or BasicType.DecimalNullable => $"BoisNumericSerializers.WriteVarDecimal(writer, {expression});",
BasicType.Byte => $"BoisNumericSerializers.WriteByte(writer, {expression});",
BasicType.SByte => $"BoisNumericSerializers.WriteSByte(writer, {expression});",
@@ -1172,6 +1214,7 @@ private sealed record CollectionInfo(ITypeSymbol Type, ITypeSymbol ElementType);
private sealed record DictionaryInfo(ITypeSymbol Type, ITypeSymbol KeyType, ITypeSymbol ValueType);
private enum OperationKind { Reader, Writer }
+
private enum BasicType
{
String,
@@ -1235,8 +1278,25 @@ public void Line(string text = "")
_builder.AppendLine(text);
}
+ public void MultiLine(string text)
+ {
+ if (text.Length == 0)
+ return;
+
+ var indentText = new string(' ', _indent * 4);
+ var lines = text.ReplaceLineEndings("\n").Split('\n');
+
+ foreach (var line in lines)
+ {
+ _builder.Append(indentText);
+ _builder.AppendLine(line);
+ }
+ }
+
public void Indent() => _indent++;
+
public void Unindent() => _indent--;
+
public override string ToString() => _builder.ToString();
}
}
From cb8660ad008e94f4af93d7d4f862fa1da4b75a43 Mon Sep 17 00:00:00 2001
From: salarcode
Date: Sun, 29 Mar 2026 22:31:44 +1100
Subject: [PATCH 06/13] DefaultEncoding
---
CodeGenSample/Models/CompanyModel.cs | 26 ++++++++++++++++++++++++--
Salar.Bois/BoisSerializer.cs | 12 +++++++-----
2 files changed, 31 insertions(+), 7 deletions(-)
diff --git a/CodeGenSample/Models/CompanyModel.cs b/CodeGenSample/Models/CompanyModel.cs
index f13a9f5..852be5a 100644
--- a/CodeGenSample/Models/CompanyModel.cs
+++ b/CodeGenSample/Models/CompanyModel.cs
@@ -1,6 +1,8 @@
-using Salar.Bois.Generator.Attributes;
+using Salar.BinaryBuffers;
+using Salar.Bois.Generator.Attributes;
using System;
using System.Collections.Generic;
+using System.Text;
namespace CodeGenSample.Models;
@@ -78,6 +80,26 @@ public static partial class CompanyModelBois
[BoisReader]
public static partial CompanyModel? ReadCompanyModel(Stream source);
+ [BoisReader]
+ public static partial CompanyModel? ReadCompanyModel(BufferReaderBase reader);
+
+ [BoisReader]
+ public static partial CompanyModel? ReadCompanyModel(BufferReaderBase reader, Encoding encoding);
+
+ [BoisReader]
+ public static partial CompanyModel? ReadCompanyModel(byte[] buffer, int position, int length);
+
+ [BoisWriter]
+ public static partial void WriteCompanyModel(CompanyModel? model, Stream output);
+
+ [BoisWriter]
+ public static partial void WriteCompanyModel(CompanyModel? model, BufferWriterBase writer);
+
[BoisWriter]
- public static partial void WriteCompanyModel(Stream output, CompanyModel? model);
+ public static partial void WriteCompanyModel(CompanyModel? model, BufferWriterBase writer, Encoding encoding);
+
+ [BoisWriter]
+ public static partial void WriteCompanyModel(CompanyModel? model, byte[] output, int position, int length);
+
+
}
diff --git a/Salar.Bois/BoisSerializer.cs b/Salar.Bois/BoisSerializer.cs
index 0c022ca..bcc9a63 100644
--- a/Salar.Bois/BoisSerializer.cs
+++ b/Salar.Bois/BoisSerializer.cs
@@ -33,12 +33,14 @@ public class BoisSerializer
///
public Encoding Encoding { get; set; }
- ///
- /// Initializing a new instance of Bois serializer.
- ///
- public BoisSerializer()
+ public static Encoding DefaultEncoding { get; set; } = Encoding.UTF8;
+
+ ///
+ /// Initializing a new instance of Bois serializer.
+ ///
+ public BoisSerializer()
{
- Encoding = Encoding.UTF8;
+ Encoding = DefaultEncoding;
}
static BoisSerializer()
From abe4e91ea80eddb2f28570ca7c4575cf434d2979 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Mar 2026 11:39:37 +0000
Subject: [PATCH 07/13] Add generator support for buffer and encoding overloads
Agent-Logs-Url: https://github.com/salarcode/Bois/sessions/101fc880-4b63-4967-8b45-a606fa4dea3f
Co-authored-by: salarcode <1272095+salarcode@users.noreply.github.com>
---
Salar.Bois.Generator/BoisSourceGenerator.cs | 529 ++++++++++++++++----
1 file changed, 432 insertions(+), 97 deletions(-)
diff --git a/Salar.Bois.Generator/BoisSourceGenerator.cs b/Salar.Bois.Generator/BoisSourceGenerator.cs
index c8e6c6d..8c2ca7c 100644
--- a/Salar.Bois.Generator/BoisSourceGenerator.cs
+++ b/Salar.Bois.Generator/BoisSourceGenerator.cs
@@ -106,23 +106,21 @@ private static bool TryCreateGenerationMethod(IMethodSymbol method, OperationKin
if (operation == OperationKind.Reader)
{
- if (method.ReturnsVoid || method.Parameters.Length != 1 || !IsStream(method.Parameters[0].Type))
+ if (!TryCreateReaderGenerationMethod(method, containingType, out generationMethod))
{
- context.ReportDiagnostic(Diagnostic.Create(InvalidMethodSignature, method.Locations.FirstOrDefault(), $"Reader method '{method.Name}' must have the signature 'static partial T Method(System.IO.Stream source)'."));
+ context.ReportDiagnostic(Diagnostic.Create(InvalidMethodSignature, method.Locations.FirstOrDefault(), $"Reader method '{method.Name}' must have one of the supported signatures: {GetSupportedReaderSignatures()}."));
return false;
}
- generationMethod = new GenerationMethod(method, containingType, operation, method.ReturnType);
return true;
}
- if (!method.ReturnsVoid || method.Parameters.Length != 2 || !IsStream(method.Parameters[0].Type))
+ if (!TryCreateWriterGenerationMethod(method, containingType, out generationMethod))
{
- context.ReportDiagnostic(Diagnostic.Create(InvalidMethodSignature, method.Locations.FirstOrDefault(), $"Writer method '{method.Name}' must have the signature 'static partial void Method(System.IO.Stream output, T model)'."));
+ context.ReportDiagnostic(Diagnostic.Create(InvalidMethodSignature, method.Locations.FirstOrDefault(), $"Writer method '{method.Name}' must have one of the supported signatures: {GetSupportedWriterSignatures()}."));
return false;
}
- generationMethod = new GenerationMethod(method, containingType, operation, method.Parameters[1].Type);
return true;
}
@@ -138,6 +136,134 @@ private static bool IsPartial(INamedTypeSymbol type)
private static bool IsStream(ITypeSymbol type)
=> type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::System.IO.Stream";
+ private static bool IsBufferReader(ITypeSymbol type)
+ => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::Salar.BinaryBuffers.BufferReaderBase";
+
+ private static bool IsBufferWriter(ITypeSymbol type)
+ => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::Salar.BinaryBuffers.BufferWriterBase";
+
+ private static bool IsEncoding(ITypeSymbol type)
+ => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::System.Text.Encoding";
+
+ private static bool IsByteArray(ITypeSymbol type)
+ => type is IArrayTypeSymbol arrayType && arrayType.ElementType.SpecialType == SpecialType.System_Byte;
+
+ private static bool IsInt32(ITypeSymbol type)
+ => type.SpecialType == SpecialType.System_Int32;
+
+ private static bool TryCreateReaderGenerationMethod(IMethodSymbol method, INamedTypeSymbol containingType, out GenerationMethod generationMethod)
+ {
+ generationMethod = default!;
+
+ if (method.ReturnsVoid)
+ return false;
+
+ var parameters = method.Parameters;
+ var encodingParameterIndex = TryGetTrailingEncodingParameterIndex(parameters);
+ var parameterCount = encodingParameterIndex is null ? parameters.Length : parameters.Length - 1;
+
+ ReaderSignature? signature = null;
+
+ if (parameterCount == 1)
+ {
+ if (IsStream(parameters[0].Type))
+ {
+ signature = new ReaderSignature(ReaderInputKind.Stream, 0, -1, -1, encodingParameterIndex);
+ }
+ else if (IsBufferReader(parameters[0].Type))
+ {
+ signature = new ReaderSignature(ReaderInputKind.BufferReader, 0, -1, -1, encodingParameterIndex);
+ }
+ }
+ else if (parameterCount == 3 &&
+ IsByteArray(parameters[0].Type) &&
+ IsInt32(parameters[1].Type) &&
+ IsInt32(parameters[2].Type))
+ {
+ signature = new ReaderSignature(ReaderInputKind.ByteArray, 0, 1, 2, encodingParameterIndex);
+ }
+
+ if (signature is null)
+ return false;
+
+ generationMethod = new GenerationMethod(method, containingType, OperationKind.Reader, method.ReturnType, signature);
+ return true;
+ }
+
+ private static bool TryCreateWriterGenerationMethod(IMethodSymbol method, INamedTypeSymbol containingType, out GenerationMethod generationMethod)
+ {
+ generationMethod = default!;
+
+ if (!method.ReturnsVoid)
+ return false;
+
+ var parameters = method.Parameters;
+ var encodingParameterIndex = TryGetTrailingEncodingParameterIndex(parameters);
+ var parameterCount = encodingParameterIndex is null ? parameters.Length : parameters.Length - 1;
+
+ WriterSignature? signature = null;
+
+ if (parameterCount == 2)
+ {
+ if (IsStream(parameters[0].Type) || IsBufferWriter(parameters[0].Type))
+ {
+ signature = new WriterSignature(
+ IsStream(parameters[0].Type) ? WriterOutputKind.Stream : WriterOutputKind.BufferWriter,
+ 1,
+ 0,
+ -1,
+ -1,
+ encodingParameterIndex);
+ }
+ else if (IsStream(parameters[1].Type) || IsBufferWriter(parameters[1].Type))
+ {
+ signature = new WriterSignature(
+ IsStream(parameters[1].Type) ? WriterOutputKind.Stream : WriterOutputKind.BufferWriter,
+ 0,
+ 1,
+ -1,
+ -1,
+ encodingParameterIndex);
+ }
+ }
+ else if (parameterCount == 4)
+ {
+ if (IsByteArray(parameters[0].Type) &&
+ IsInt32(parameters[1].Type) &&
+ IsInt32(parameters[2].Type))
+ {
+ signature = new WriterSignature(WriterOutputKind.ByteArray, 3, 0, 1, 2, encodingParameterIndex);
+ }
+ else if (IsByteArray(parameters[1].Type) &&
+ IsInt32(parameters[2].Type) &&
+ IsInt32(parameters[3].Type))
+ {
+ signature = new WriterSignature(WriterOutputKind.ByteArray, 0, 1, 2, 3, encodingParameterIndex);
+ }
+ }
+
+ if (signature is null)
+ return false;
+
+ generationMethod = new GenerationMethod(method, containingType, OperationKind.Writer, parameters[signature.ModelParameterIndex].Type, signature);
+ return true;
+ }
+
+ private static int? TryGetTrailingEncodingParameterIndex(ImmutableArray parameters)
+ {
+ if (parameters.Length == 0)
+ return null;
+
+ var lastIndex = parameters.Length - 1;
+ return IsEncoding(parameters[lastIndex].Type) ? lastIndex : null;
+ }
+
+ private static string GetSupportedReaderSignatures()
+ => "'static partial T Method(System.IO.Stream source)', 'static partial T Method(System.IO.Stream source, System.Text.Encoding encoding)', 'static partial T Method(Salar.BinaryBuffers.BufferReaderBase reader)', 'static partial T Method(Salar.BinaryBuffers.BufferReaderBase reader, System.Text.Encoding encoding)', 'static partial T Method(byte[] buffer, int position, int length)', or 'static partial T Method(byte[] buffer, int position, int length, System.Text.Encoding encoding)'";
+
+ private static string GetSupportedWriterSignatures()
+ => "'static partial void Method(T model, System.IO.Stream output)', 'static partial void Method(System.IO.Stream output, T model)', 'static partial void Method(T model, Salar.BinaryBuffers.BufferWriterBase writer)', 'static partial void Method(Salar.BinaryBuffers.BufferWriterBase writer, T model)', 'static partial void Method(T model, byte[] output, int position, int length)', 'static partial void Method(byte[] output, int position, int length, T model)', and those same signatures with an optional trailing System.Text.Encoding parameter";
+
private static string GetFileName(ITypeSymbol type)
{
return type switch
@@ -154,9 +280,25 @@ private sealed class SyntaxReceiver : ISyntaxReceiver
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
- if (syntaxNode is MethodDeclarationSyntax method && method.AttributeLists.Count > 0 && method.Modifiers.Any(SyntaxKind.PartialKeyword))
+ if (syntaxNode is MethodDeclarationSyntax method &&
+ method.Modifiers.Any(SyntaxKind.PartialKeyword) &&
+ HasBoisAttribute(method))
+ {
Candidates.Add(method);
+ }
}
+
+ private static bool HasBoisAttribute(MethodDeclarationSyntax method)
+ => method.AttributeLists.SelectMany(static list => list.Attributes).Any(static attribute => GetAttributeName(attribute.Name) is "BoisReader" or "BoisReaderAttribute" or "BoisWriter" or "BoisWriterAttribute");
+
+ private static string GetAttributeName(NameSyntax name)
+ => name switch
+ {
+ IdentifierNameSyntax identifier => identifier.Identifier.ValueText,
+ QualifiedNameSyntax qualified => GetAttributeName(qualified.Right),
+ AliasQualifiedNameSyntax aliasQualified => aliasQualified.Name.Identifier.ValueText,
+ _ => name.ToString()
+ };
}
private sealed class ContainingTypeEmitter
@@ -214,6 +356,7 @@ public string Emit(ImmutableArray methods)
builder.Line("// ");
builder.Line("#nullable enable");
builder.Line("using global::System;");
+ builder.Line("using global::Salar.BinaryBuffers;");
builder.Line("using global::Salar.Bois.Generator.Serializers;");
builder.Line("using global::Salar.BinaryBuffers.Compatibility;");
builder.Line();
@@ -282,8 +425,7 @@ public void Emit(CodeBuilder builder)
if (_method.Operation == OperationKind.Reader)
{
- var sourceName = Escape(_method.Method.Parameters[0].Name);
- builder.Line($"var reader = new StreamBufferReader({sourceName});");
+ EmitReaderSetup(builder);
if (!TryEmitRead(_method.RootType, builder, out error))
{
_owner.Report(_method.Method.Locations.FirstOrDefault(), error);
@@ -292,10 +434,7 @@ public void Emit(CodeBuilder builder)
}
else
{
- var outputName = Escape(_method.Method.Parameters[0].Name);
- var valueName = Escape(_method.Method.Parameters[1].Name);
- builder.Line($"var writer = new StreamBufferWriter({outputName});");
- builder.Line($"var value = {valueName};");
+ EmitWriterSetup(builder);
if (!TryEmitWrite(_method.RootType, builder, out error))
{
_owner.Report(_method.Method.Locations.FirstOrDefault(), error);
@@ -355,7 +494,7 @@ private void EmitLocalFunction(CodeBuilder builder, LocalFunctionModel function)
private void EmitReadLocalFunction(CodeBuilder builder, LocalFunctionModel function)
{
builder.Line();
- builder.Line($"static {TypeName(function.Type)} {function.Name}(global::Salar.BinaryBuffers.BufferReaderBase reader)");
+ builder.Line($"static {TypeName(function.Type)} {function.Name}(global::Salar.BinaryBuffers.BufferReaderBase reader, global::System.Text.Encoding encoding)");
builder.Line("{");
builder.Indent();
@@ -372,7 +511,7 @@ private void EmitReadLocalFunction(CodeBuilder builder, LocalFunctionModel funct
private void EmitWriteLocalFunction(CodeBuilder builder, LocalFunctionModel function)
{
builder.Line();
- builder.Line($"static void {function.Name}(global::Salar.BinaryBuffers.BufferWriterBase writer, {TypeName(function.Type)} value)");
+ builder.Line($"static void {function.Name}(global::Salar.BinaryBuffers.BufferWriterBase writer, {TypeName(function.Type)} value, global::System.Text.Encoding encoding)");
builder.Line("{");
builder.Indent();
@@ -386,6 +525,66 @@ private void EmitWriteLocalFunction(CodeBuilder builder, LocalFunctionModel func
builder.Line("}");
}
+ private void EmitReaderSetup(CodeBuilder builder)
+ {
+ var signature = (ReaderSignature)_method.Signature;
+ builder.Line($"var encoding = {GetEncodingExpression(signature.EncodingParameterIndex)};");
+
+ var sourceName = Escape(_method.Method.Parameters[signature.SourceParameterIndex].Name);
+ switch (signature.InputKind)
+ {
+ case ReaderInputKind.Stream:
+ builder.Line($"var reader = new StreamBufferReader({sourceName});");
+ break;
+ case ReaderInputKind.BufferReader:
+ builder.Line($"var reader = {sourceName};");
+ break;
+ case ReaderInputKind.ByteArray:
+ var positionName = Escape(_method.Method.Parameters[signature.PositionParameterIndex].Name);
+ var lengthName = Escape(_method.Method.Parameters[signature.LengthParameterIndex].Name);
+ builder.Line($"var reader = new BinaryBufferReader({sourceName}, {positionName}, {lengthName});");
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ private void EmitWriterSetup(CodeBuilder builder)
+ {
+ var signature = (WriterSignature)_method.Signature;
+ builder.Line($"var encoding = {GetEncodingExpression(signature.EncodingParameterIndex)};");
+
+ var valueName = Escape(_method.Method.Parameters[signature.ModelParameterIndex].Name);
+ builder.Line($"var value = {valueName};");
+
+ var outputName = Escape(_method.Method.Parameters[signature.OutputParameterIndex].Name);
+ switch (signature.OutputKind)
+ {
+ case WriterOutputKind.Stream:
+ builder.Line($"var writer = new StreamBufferWriter({outputName});");
+ break;
+ case WriterOutputKind.BufferWriter:
+ builder.Line($"var writer = {outputName};");
+ break;
+ case WriterOutputKind.ByteArray:
+ var positionName = Escape(_method.Method.Parameters[signature.PositionParameterIndex].Name);
+ var lengthName = Escape(_method.Method.Parameters[signature.LengthParameterIndex].Name);
+ builder.Line($"var writer = new BinaryBufferWriter({outputName}, {positionName}, {lengthName});");
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ private string GetEncodingExpression(int? encodingParameterIndex)
+ {
+ if (encodingParameterIndex is null)
+ return "global::Salar.Bois.BoisSerializer.DefaultEncoding";
+
+ var encodingName = Escape(_method.Method.Parameters[encodingParameterIndex.Value].Name);
+ return $"{encodingName} ?? global::Salar.Bois.BoisSerializer.DefaultEncoding";
+ }
+
private bool TryEmitWrite(ITypeSymbol type, CodeBuilder builder, out string error)
{
if (_owner.TryGetBasicType(type, out var basicType))
@@ -526,7 +725,7 @@ private bool EmitWriteMember(MemberModel member, CodeBuilder builder, out string
if (_owner.IsNameValueCollection(member.Type))
return EmitWriteNestedNameValue(access, builder, out error);
- builder.Line($"{EnsureWriteFunction(member.Type)}(writer, {access});");
+ builder.Line($"{EnsureWriteFunction(member.Type)}(writer, {access}, encoding);");
error = string.Empty;
return true;
}
@@ -572,7 +771,7 @@ private bool EmitReadMember(MemberModel member, CodeBuilder builder, out string
}
}
- builder.Line($"{target} = {EnsureReadFunction(member.Type)}(reader);");
+ builder.Line($"{target} = {EnsureReadFunction(member.Type)}(reader, encoding);");
error = string.Empty;
return true;
}
@@ -759,21 +958,21 @@ private bool EmitWriteNameValueCollection(CodeBuilder builder, out string error)
private bool EmitWriteNestedNameValue(string expression, CodeBuilder builder, out string error)
{
- builder.Line($"if ({expression} is null)");
- builder.Line("{");
- builder.Indent();
- builder.Line("BoisPrimitiveWriters.WriteNullValue(writer);");
- builder.Unindent();
- builder.Line("}");
- builder.Line("else");
- builder.Line("{");
+ builder.MultiLine($$"""
+ if ({{expression}} is null)
+ {
+ BoisPrimitiveWriters.WriteNullValue(writer);
+ }
+ else
+ {
+ """);
builder.Indent();
builder.Line($"BoisNumericSerializers.WriteUIntNullableMemberCount(writer, (uint){expression}.Count);");
builder.Line($"foreach (var key in {expression}.AllKeys)");
builder.Line("{");
builder.Indent();
- builder.Line("BoisPrimitiveWriters.WriteValue(writer, key, global::System.Text.Encoding.UTF8);");
- builder.Line($"BoisPrimitiveWriters.WriteValue(writer, {expression}[key], global::System.Text.Encoding.UTF8);");
+ builder.Line("BoisPrimitiveWriters.WriteValue(writer, key, encoding);");
+ builder.Line($"BoisPrimitiveWriters.WriteValue(writer, {expression}[key], encoding);");
builder.Unindent();
builder.Line("}");
builder.Unindent();
@@ -814,8 +1013,8 @@ private void EmitReadIntoNameValueBody(string target, string countExpression, Co
builder.Line($"for (var i = 0; i < {countExpression}; i++)");
builder.Line("{");
builder.Indent();
- builder.Line("var key = BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8);");
- builder.Line("var value = BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8);");
+ builder.Line("var key = BoisPrimitiveReaders.ReadString(reader, encoding);");
+ builder.Line("var value = BoisPrimitiveReaders.ReadString(reader, encoding);");
builder.Line($"{target}.Add(key, value);");
builder.Unindent();
builder.Line("}");
@@ -827,7 +1026,7 @@ private string GetWriteElementStatement(ITypeSymbol type, string expression)
return _owner.GetWriteStatement(type, basicType, expression);
if (_owner.IsEnum(type))
return _owner.GetEnumWriteStatement(type, expression);
- return $"{EnsureWriteFunction(type)}(writer, {expression});";
+ return $"{EnsureWriteFunction(type)}(writer, {expression}, encoding);";
}
private string GetReadValueExpression(ITypeSymbol type)
@@ -836,7 +1035,7 @@ private string GetReadValueExpression(ITypeSymbol type)
return _owner.GetReadExpression(type, basicType);
if (_owner.IsEnum(type))
return $"BoisPrimitiveReaders.ReadEnumGeneric<{TypeName(type)}>(reader)";
- return $"{EnsureReadFunction(type)}(reader)";
+ return $"{EnsureReadFunction(type)}(reader, encoding)";
}
private string BuildSignature(IMethodSymbol method)
@@ -1029,74 +1228,205 @@ public bool TryGetBasicType(ITypeSymbol type, out BasicType basicType)
basicType = nullableValue ? BasicType.BoolNullable : BasicType.Bool;
return true;
}
- if (bare.SpecialType == SpecialType.System_Char) { basicType = nullableValue ? BasicType.CharNullable : BasicType.Char; return true; }
- if (bare.SpecialType == SpecialType.System_Int16) { basicType = nullableValue ? BasicType.Int16Nullable : BasicType.Int16; return true; }
- if (bare.SpecialType == SpecialType.System_Int32) { basicType = nullableValue ? BasicType.Int32Nullable : BasicType.Int32; return true; }
- if (bare.SpecialType == SpecialType.System_Int64) { basicType = nullableValue ? BasicType.Int64Nullable : BasicType.Int64; return true; }
- if (bare.SpecialType == SpecialType.System_UInt16) { basicType = nullableValue ? BasicType.UInt16Nullable : BasicType.UInt16; return true; }
- if (bare.SpecialType == SpecialType.System_UInt32) { basicType = nullableValue ? BasicType.UInt32Nullable : BasicType.UInt32; return true; }
- if (bare.SpecialType == SpecialType.System_UInt64) { basicType = nullableValue ? BasicType.UInt64Nullable : BasicType.UInt64; return true; }
- if (bare.SpecialType == SpecialType.System_Single) { basicType = nullableValue ? BasicType.SingleNullable : BasicType.Single; return true; }
- if (bare.SpecialType == SpecialType.System_Double) { basicType = nullableValue ? BasicType.DoubleNullable : BasicType.Double; return true; }
- if (bare.SpecialType == SpecialType.System_Decimal) { basicType = nullableValue ? BasicType.DecimalNullable : BasicType.Decimal; return true; }
- if (bare.SpecialType == SpecialType.System_Byte) { basicType = nullableValue ? BasicType.ByteNullable : BasicType.Byte; return true; }
- if (bare.SpecialType == SpecialType.System_SByte) { basicType = nullableValue ? BasicType.SByteNullable : BasicType.SByte; return true; }
- if (bare.SpecialType == SpecialType.System_DateTime) { basicType = nullableValue ? BasicType.DateTimeNullable : BasicType.DateTime; return true; }
- if (Equal(bare, _dateTimeOffsetType)) { basicType = nullableValue ? BasicType.DateTimeOffsetNullable : BasicType.DateTimeOffset; return true; }
- if (Equal(bare, _dateOnlyType)) { basicType = nullableValue ? BasicType.DateOnlyNullable : BasicType.DateOnly; return true; }
- if (Equal(bare, _timeOnlyType)) { basicType = nullableValue ? BasicType.TimeOnlyNullable : BasicType.TimeOnly; return true; }
- if (bare is IArrayTypeSymbol arrayType && arrayType.ElementType.SpecialType == SpecialType.System_Byte) { basicType = BasicType.ByteArray; return true; }
- if (Equal(bare, _timeSpanType)) { basicType = nullableValue ? BasicType.TimeSpanNullable : BasicType.TimeSpan; return true; }
- if (Equal(bare, _guidType)) { basicType = nullableValue ? BasicType.GuidNullable : BasicType.Guid; return true; }
- if (Equal(bare, _colorType)) { basicType = nullableValue ? BasicType.ColorNullable : BasicType.Color; return true; }
- if (Equal(bare, _dbNullType)) { basicType = BasicType.DbNull; return true; }
- if (Equal(bare, _uriType)) { basicType = BasicType.Uri; return true; }
- if (Equal(bare, _versionType)) { basicType = BasicType.Version; return true; }
- if (Equal(bare, _dataTableType)) { basicType = BasicType.DataTable; return true; }
- if (Equal(bare, _dataSetType)) { basicType = BasicType.DataSet; return true; }
+ if (bare.SpecialType == SpecialType.System_Char)
+ {
+ basicType = nullableValue ? BasicType.CharNullable : BasicType.Char;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_Int16)
+ {
+ basicType = nullableValue ? BasicType.Int16Nullable : BasicType.Int16;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_Int32)
+ {
+ basicType = nullableValue ? BasicType.Int32Nullable : BasicType.Int32;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_Int64)
+ {
+ basicType = nullableValue ? BasicType.Int64Nullable : BasicType.Int64;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_UInt16)
+ {
+ basicType = nullableValue ? BasicType.UInt16Nullable : BasicType.UInt16;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_UInt32)
+ {
+ basicType = nullableValue ? BasicType.UInt32Nullable : BasicType.UInt32;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_UInt64)
+ {
+ basicType = nullableValue ? BasicType.UInt64Nullable : BasicType.UInt64;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_Single)
+ {
+ basicType = nullableValue ? BasicType.SingleNullable : BasicType.Single;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_Double)
+ {
+ basicType = nullableValue ? BasicType.DoubleNullable : BasicType.Double;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_Decimal)
+ {
+ basicType = nullableValue ? BasicType.DecimalNullable : BasicType.Decimal;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_Byte)
+ {
+ basicType = nullableValue ? BasicType.ByteNullable : BasicType.Byte;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_SByte)
+ {
+ basicType = nullableValue ? BasicType.SByteNullable : BasicType.SByte;
+ return true;
+ }
+ if (bare.SpecialType == SpecialType.System_DateTime)
+ {
+ basicType = nullableValue ? BasicType.DateTimeNullable : BasicType.DateTime;
+ return true;
+ }
+ if (Equal(bare, _dateTimeOffsetType))
+ {
+ basicType = nullableValue ? BasicType.DateTimeOffsetNullable : BasicType.DateTimeOffset;
+ return true;
+ }
+ if (Equal(bare, _dateOnlyType))
+ {
+ basicType = nullableValue ? BasicType.DateOnlyNullable : BasicType.DateOnly;
+ return true;
+ }
+ if (Equal(bare, _timeOnlyType))
+ {
+ basicType = nullableValue ? BasicType.TimeOnlyNullable : BasicType.TimeOnly;
+ return true;
+ }
+ if (bare is IArrayTypeSymbol arrayType && arrayType.ElementType.SpecialType == SpecialType.System_Byte)
+ {
+ basicType = BasicType.ByteArray;
+ return true;
+ }
+ if (Equal(bare, _timeSpanType))
+ {
+ basicType = nullableValue ? BasicType.TimeSpanNullable : BasicType.TimeSpan;
+ return true;
+ }
+ if (Equal(bare, _guidType))
+ {
+ basicType = nullableValue ? BasicType.GuidNullable : BasicType.Guid;
+ return true;
+ }
+ if (Equal(bare, _colorType))
+ {
+ basicType = nullableValue ? BasicType.ColorNullable : BasicType.Color;
+ return true;
+ }
+ if (Equal(bare, _dbNullType))
+ {
+ basicType = BasicType.DbNull;
+ return true;
+ }
+ if (Equal(bare, _uriType))
+ {
+ basicType = BasicType.Uri;
+ return true;
+ }
+ if (Equal(bare, _versionType))
+ {
+ basicType = BasicType.Version;
+ return true;
+ }
+ if (Equal(bare, _dataTableType))
+ {
+ basicType = BasicType.DataTable;
+ return true;
+ }
+ if (Equal(bare, _dataSetType))
+ {
+ basicType = BasicType.DataSet;
+ return true;
+ }
basicType = default;
return false;
}
- public string GetWriteStatement(ITypeSymbol type, BasicType basicType, string expression) => basicType switch
+ public string GetWriteStatement(ITypeSymbol type, BasicType basicType, string expression)
{
- BasicType.String => $"BoisPrimitiveWriters.WriteValue(writer, {expression}, global::System.Text.Encoding.UTF8);",
- BasicType.Bool or
- BasicType.BoolNullable or
- BasicType.Char or
- BasicType.CharNullable or
- BasicType.DateTime or
- BasicType.DateTimeNullable or
- BasicType.DateTimeOffset or
- BasicType.DateTimeOffsetNullable or
- BasicType.TimeSpan or
- BasicType.TimeSpanNullable or
- BasicType.Guid or
- BasicType.GuidNullable or
- BasicType.Color or
- BasicType.ColorNullable or
- BasicType.DbNull or
- BasicType.Uri or
- BasicType.Version or
- BasicType.DateOnly or
- BasicType.DateOnlyNullable or
- BasicType.TimeOnly or
- BasicType.TimeOnlyNullable or
- BasicType.ByteArray => $"BoisPrimitiveWriters.WriteValue(writer, {expression});",
- BasicType.Int16 or
- BasicType.Int16Nullable or
- BasicType.Int32 or
- BasicType.Int32Nullable or
- BasicType.Int64 or
- BasicType.Int64Nullable or BasicType.UInt16 or BasicType.UInt16Nullable or BasicType.UInt32 or BasicType.UInt32Nullable or BasicType.UInt64 or BasicType.UInt64Nullable or BasicType.ByteNullable or
- BasicType.SByteNullable => $"BoisNumericSerializers.WriteVarInt(writer, {expression});",
- BasicType.Single or BasicType.SingleNullable or BasicType.Double or BasicType.DoubleNullable or BasicType.Decimal or BasicType.DecimalNullable => $"BoisNumericSerializers.WriteVarDecimal(writer, {expression});",
- BasicType.Byte => $"BoisNumericSerializers.WriteByte(writer, {expression});",
- BasicType.SByte => $"BoisNumericSerializers.WriteSByte(writer, {expression});",
- BasicType.DataTable or BasicType.DataSet => $"BoisPrimitiveWriters.WriteValue(writer, {expression}, global::System.Text.Encoding.UTF8);",
- _ => throw new InvalidOperationException()
- };
+ switch (basicType)
+ {
+ case BasicType.String:
+ return $"BoisPrimitiveWriters.WriteValue(writer, {expression}, encoding);";
+
+ case BasicType.Bool:
+ case BasicType.BoolNullable:
+ case BasicType.Char:
+ case BasicType.CharNullable:
+ case BasicType.DateTime:
+ case BasicType.DateTimeNullable:
+ case BasicType.DateTimeOffset:
+ case BasicType.DateTimeOffsetNullable:
+ case BasicType.TimeSpan:
+ case BasicType.TimeSpanNullable:
+ case BasicType.Guid:
+ case BasicType.GuidNullable:
+ case BasicType.Color:
+ case BasicType.ColorNullable:
+ case BasicType.DbNull:
+ case BasicType.Uri:
+ case BasicType.Version:
+ case BasicType.DateOnly:
+ case BasicType.DateOnlyNullable:
+ case BasicType.TimeOnly:
+ case BasicType.TimeOnlyNullable:
+ case BasicType.ByteArray:
+ return $"BoisPrimitiveWriters.WriteValue(writer, {expression});";
+
+ case BasicType.Int16:
+ case BasicType.Int16Nullable:
+ case BasicType.Int32:
+ case BasicType.Int32Nullable:
+ case BasicType.Int64:
+ case BasicType.Int64Nullable:
+ case BasicType.UInt16:
+ case BasicType.UInt16Nullable:
+ case BasicType.UInt32:
+ case BasicType.UInt32Nullable:
+ case BasicType.UInt64:
+ case BasicType.UInt64Nullable:
+ case BasicType.ByteNullable:
+ case BasicType.SByteNullable:
+ return $"BoisNumericSerializers.WriteVarInt(writer, {expression});";
+
+ case BasicType.Single:
+ case BasicType.SingleNullable:
+ case BasicType.Double:
+ case BasicType.DoubleNullable:
+ case BasicType.Decimal:
+ case BasicType.DecimalNullable:
+ return $"BoisNumericSerializers.WriteVarDecimal(writer, {expression});";
+
+ case BasicType.Byte:
+ return $"BoisNumericSerializers.WriteByte(writer, {expression});";
+
+ case BasicType.SByte:
+ return $"BoisNumericSerializers.WriteSByte(writer, {expression});";
+
+ case BasicType.DataTable:
+ case BasicType.DataSet:
+ return $"BoisPrimitiveWriters.WriteValue(writer, {expression}, encoding);";
+
+ default:
+ throw new InvalidOperationException();
+ }
+ }
public string GetEnumWriteStatement(ITypeSymbol type, string expression)
{
@@ -1109,7 +1439,7 @@ public string GetEnumWriteStatement(ITypeSymbol type, string expression)
public string GetReadExpression(ITypeSymbol type, BasicType basicType) => basicType switch
{
- BasicType.String => "BoisPrimitiveReaders.ReadString(reader, global::System.Text.Encoding.UTF8)",
+ BasicType.String => "BoisPrimitiveReaders.ReadString(reader, encoding)",
BasicType.Bool => "BoisPrimitiveReaders.ReadBoolean(reader)",
BasicType.BoolNullable => "BoisPrimitiveReaders.ReadBooleanNullable(reader)",
BasicType.Char => "BoisPrimitiveReaders.ReadChar(reader)",
@@ -1154,8 +1484,8 @@ public string GetEnumWriteStatement(ITypeSymbol type, string expression)
BasicType.DbNull => "BoisPrimitiveReaders.ReadDbNull(reader)",
BasicType.Uri => "BoisPrimitiveReaders.ReadUri(reader)",
BasicType.Version => "BoisPrimitiveReaders.ReadVersion(reader)",
- BasicType.DataTable => "BoisPrimitiveReaders.ReadDataTable(reader, global::System.Text.Encoding.UTF8)",
- BasicType.DataSet => "BoisPrimitiveReaders.ReadDataSet(reader, global::System.Text.Encoding.UTF8)",
+ BasicType.DataTable => "BoisPrimitiveReaders.ReadDataTable(reader, encoding)",
+ BasicType.DataSet => "BoisPrimitiveReaders.ReadDataSet(reader, encoding)",
_ => throw new InvalidOperationException()
};
@@ -1207,13 +1537,18 @@ public int GetHashCode((INamedTypeSymbol ContainingType, string FileName) obj)
=> HashCode.Combine(SymbolEqualityComparer.Default.GetHashCode(obj.ContainingType), obj.FileName);
}
- private sealed record GenerationMethod(IMethodSymbol Method, INamedTypeSymbol ContainingType, OperationKind Operation, ITypeSymbol RootType);
+ private sealed record GenerationMethod(IMethodSymbol Method, INamedTypeSymbol ContainingType, OperationKind Operation, ITypeSymbol RootType, MethodSignature Signature);
private sealed record LocalFunctionModel(OperationKind Operation, ITypeSymbol Type, string Name);
private sealed record MemberModel(ISymbol Symbol, ITypeSymbol Type, int Index, bool IsGetterOnlyMutableCollection);
private sealed record CollectionInfo(ITypeSymbol Type, ITypeSymbol ElementType);
private sealed record DictionaryInfo(ITypeSymbol Type, ITypeSymbol KeyType, ITypeSymbol ValueType);
+ private abstract record MethodSignature(int? EncodingParameterIndex);
+ private sealed record ReaderSignature(ReaderInputKind InputKind, int SourceParameterIndex, int PositionParameterIndex, int LengthParameterIndex, int? EncodingParameterIndex) : MethodSignature(EncodingParameterIndex);
+ private sealed record WriterSignature(WriterOutputKind OutputKind, int ModelParameterIndex, int OutputParameterIndex, int PositionParameterIndex, int LengthParameterIndex, int? EncodingParameterIndex) : MethodSignature(EncodingParameterIndex);
private enum OperationKind { Reader, Writer }
+ private enum ReaderInputKind { Stream, BufferReader, ByteArray }
+ private enum WriterOutputKind { Stream, BufferWriter, ByteArray }
private enum BasicType
{
From a82d66513fb29df032a051f7591dc776c7fb4e3c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Mar 2026 11:42:43 +0000
Subject: [PATCH 08/13] Polish generator setup for overload support
Agent-Logs-Url: https://github.com/salarcode/Bois/sessions/101fc880-4b63-4967-8b45-a606fa4dea3f
Co-authored-by: salarcode <1272095+salarcode@users.noreply.github.com>
---
Salar.Bois.Generator/BoisSourceGenerator.cs | 30 ++++++++++++++-------
1 file changed, 21 insertions(+), 9 deletions(-)
diff --git a/Salar.Bois.Generator/BoisSourceGenerator.cs b/Salar.Bois.Generator/BoisSourceGenerator.cs
index 8c2ca7c..66280f8 100644
--- a/Salar.Bois.Generator/BoisSourceGenerator.cs
+++ b/Salar.Bois.Generator/BoisSourceGenerator.cs
@@ -528,7 +528,7 @@ private void EmitWriteLocalFunction(CodeBuilder builder, LocalFunctionModel func
private void EmitReaderSetup(CodeBuilder builder)
{
var signature = (ReaderSignature)_method.Signature;
- builder.Line($"var encoding = {GetEncodingExpression(signature.EncodingParameterIndex)};");
+ EmitEncodingSetup(builder, signature.EncodingParameterIndex);
var sourceName = Escape(_method.Method.Parameters[signature.SourceParameterIndex].Name);
switch (signature.InputKind)
@@ -537,7 +537,8 @@ private void EmitReaderSetup(CodeBuilder builder)
builder.Line($"var reader = new StreamBufferReader({sourceName});");
break;
case ReaderInputKind.BufferReader:
- builder.Line($"var reader = {sourceName};");
+ if (_method.Method.Parameters[signature.SourceParameterIndex].Name != "reader")
+ builder.Line($"var reader = {sourceName};");
break;
case ReaderInputKind.ByteArray:
var positionName = Escape(_method.Method.Parameters[signature.PositionParameterIndex].Name);
@@ -552,10 +553,11 @@ private void EmitReaderSetup(CodeBuilder builder)
private void EmitWriterSetup(CodeBuilder builder)
{
var signature = (WriterSignature)_method.Signature;
- builder.Line($"var encoding = {GetEncodingExpression(signature.EncodingParameterIndex)};");
+ EmitEncodingSetup(builder, signature.EncodingParameterIndex);
var valueName = Escape(_method.Method.Parameters[signature.ModelParameterIndex].Name);
- builder.Line($"var value = {valueName};");
+ if (_method.Method.Parameters[signature.ModelParameterIndex].Name != "value")
+ builder.Line($"var value = {valueName};");
var outputName = Escape(_method.Method.Parameters[signature.OutputParameterIndex].Name);
switch (signature.OutputKind)
@@ -564,7 +566,8 @@ private void EmitWriterSetup(CodeBuilder builder)
builder.Line($"var writer = new StreamBufferWriter({outputName});");
break;
case WriterOutputKind.BufferWriter:
- builder.Line($"var writer = {outputName};");
+ if (_method.Method.Parameters[signature.OutputParameterIndex].Name != "writer")
+ builder.Line($"var writer = {outputName};");
break;
case WriterOutputKind.ByteArray:
var positionName = Escape(_method.Method.Parameters[signature.PositionParameterIndex].Name);
@@ -576,13 +579,22 @@ private void EmitWriterSetup(CodeBuilder builder)
}
}
- private string GetEncodingExpression(int? encodingParameterIndex)
+ private void EmitEncodingSetup(CodeBuilder builder, int? encodingParameterIndex)
{
if (encodingParameterIndex is null)
- return "global::Salar.Bois.BoisSerializer.DefaultEncoding";
+ {
+ builder.Line("var encoding = global::Salar.Bois.BoisSerializer.DefaultEncoding;");
+ return;
+ }
+
+ if (_method.Method.Parameters[encodingParameterIndex.Value].Name == "encoding")
+ {
+ builder.Line("encoding ??= global::Salar.Bois.BoisSerializer.DefaultEncoding;");
+ return;
+ }
var encodingName = Escape(_method.Method.Parameters[encodingParameterIndex.Value].Name);
- return $"{encodingName} ?? global::Salar.Bois.BoisSerializer.DefaultEncoding";
+ builder.Line($"var encoding = {encodingName} ?? global::Salar.Bois.BoisSerializer.DefaultEncoding;");
}
private bool TryEmitWrite(ITypeSymbol type, CodeBuilder builder, out string error)
@@ -749,7 +761,7 @@ private bool EmitReadMember(MemberModel member, CodeBuilder builder, out string
if (member.IsGetterOnlyMutableCollection)
{
var localName = Escape(member.Symbol.Name) + "Target";
- builder.Line($"var {localName} = {target} ?? throw new global::InvalidOperationException({Literal($"Property '{member.Symbol.Name}' returned null during deserialization.")});");
+ builder.Line($"var {localName} = {target} ?? throw new global::System.InvalidOperationException({Literal($"Property '{member.Symbol.Name}' returned null during deserialization.")});");
builder.Line($"{localName}.Clear();");
if (_owner.TryGetDictionaryInfo(member.Type, out var dict))
{
From 8ef8705bc9e2984e174823dce3c03a8bf0e1e232 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Mar 2026 11:43:53 +0000
Subject: [PATCH 09/13] Refine generator signature diagnostics formatting
Agent-Logs-Url: https://github.com/salarcode/Bois/sessions/101fc880-4b63-4967-8b45-a606fa4dea3f
Co-authored-by: salarcode <1272095+salarcode@users.noreply.github.com>
---
Salar.Bois.Generator/BoisSourceGenerator.cs | 30 ++++++++++++++++++---
1 file changed, 27 insertions(+), 3 deletions(-)
diff --git a/Salar.Bois.Generator/BoisSourceGenerator.cs b/Salar.Bois.Generator/BoisSourceGenerator.cs
index 66280f8..8af17bf 100644
--- a/Salar.Bois.Generator/BoisSourceGenerator.cs
+++ b/Salar.Bois.Generator/BoisSourceGenerator.cs
@@ -259,10 +259,29 @@ private static bool TryCreateWriterGenerationMethod(IMethodSymbol method, INamed
}
private static string GetSupportedReaderSignatures()
- => "'static partial T Method(System.IO.Stream source)', 'static partial T Method(System.IO.Stream source, System.Text.Encoding encoding)', 'static partial T Method(Salar.BinaryBuffers.BufferReaderBase reader)', 'static partial T Method(Salar.BinaryBuffers.BufferReaderBase reader, System.Text.Encoding encoding)', 'static partial T Method(byte[] buffer, int position, int length)', or 'static partial T Method(byte[] buffer, int position, int length, System.Text.Encoding encoding)'";
+ => string.Join(
+ ", ",
+ [
+ "'static partial T Method(System.IO.Stream source)'",
+ "'static partial T Method(System.IO.Stream source, System.Text.Encoding encoding)'",
+ "'static partial T Method(Salar.BinaryBuffers.BufferReaderBase reader)'",
+ "'static partial T Method(Salar.BinaryBuffers.BufferReaderBase reader, System.Text.Encoding encoding)'",
+ "'static partial T Method(byte[] buffer, int position, int length)'",
+ "or 'static partial T Method(byte[] buffer, int position, int length, System.Text.Encoding encoding)'"
+ ]);
private static string GetSupportedWriterSignatures()
- => "'static partial void Method(T model, System.IO.Stream output)', 'static partial void Method(System.IO.Stream output, T model)', 'static partial void Method(T model, Salar.BinaryBuffers.BufferWriterBase writer)', 'static partial void Method(Salar.BinaryBuffers.BufferWriterBase writer, T model)', 'static partial void Method(T model, byte[] output, int position, int length)', 'static partial void Method(byte[] output, int position, int length, T model)', and those same signatures with an optional trailing System.Text.Encoding parameter";
+ => string.Join(
+ ", ",
+ [
+ "'static partial void Method(T model, System.IO.Stream output)'",
+ "'static partial void Method(System.IO.Stream output, T model)'",
+ "'static partial void Method(T model, Salar.BinaryBuffers.BufferWriterBase writer)'",
+ "'static partial void Method(Salar.BinaryBuffers.BufferWriterBase writer, T model)'",
+ "'static partial void Method(T model, byte[] output, int position, int length)'",
+ "'static partial void Method(byte[] output, int position, int length, T model)'",
+ "and those same signatures with an optional trailing System.Text.Encoding parameter"
+ ]);
private static string GetFileName(ITypeSymbol type)
{
@@ -289,7 +308,12 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
}
private static bool HasBoisAttribute(MethodDeclarationSyntax method)
- => method.AttributeLists.SelectMany(static list => list.Attributes).Any(static attribute => GetAttributeName(attribute.Name) is "BoisReader" or "BoisReaderAttribute" or "BoisWriter" or "BoisWriterAttribute");
+ => method.AttributeLists
+ .SelectMany(static list => list.Attributes)
+ .Any(static attribute => IsBoisAttributeName(GetAttributeName(attribute.Name)));
+
+ private static bool IsBoisAttributeName(string attributeName)
+ => attributeName is "BoisReader" or "BoisReaderAttribute" or "BoisWriter" or "BoisWriterAttribute";
private static string GetAttributeName(NameSyntax name)
=> name switch
From a61ae551146c986d024adf6d4ac48d6d0e23a559 Mon Sep 17 00:00:00 2001
From: salarcode
Date: Sun, 29 Mar 2026 23:33:45 +1100
Subject: [PATCH 10/13] Move classes
---
.../Attributes => Salar.Bois}/BoisReaderAttribute.cs | 4 +++-
.../Attributes => Salar.Bois}/BoisWriterAttribute.cs | 4 +++-
.../CodeGen}/BoisNumericSerializers.cs | 2 +-
.../CodeGen}/BoisPrimitiveReaders.cs | 2 +-
.../CodeGen}/BoisPrimitiveWriters.cs | 2 +-
5 files changed, 9 insertions(+), 5 deletions(-)
rename {Salar.Bois.Generator/Attributes => Salar.Bois}/BoisReaderAttribute.cs (90%)
rename {Salar.Bois.Generator/Attributes => Salar.Bois}/BoisWriterAttribute.cs (90%)
rename {Salar.Bois.Generator/Serializers => Salar.Bois/CodeGen}/BoisNumericSerializers.cs (99%)
rename {Salar.Bois.Generator/Serializers => Salar.Bois/CodeGen}/BoisPrimitiveReaders.cs (99%)
rename {Salar.Bois.Generator/Serializers => Salar.Bois/CodeGen}/BoisPrimitiveWriters.cs (99%)
diff --git a/Salar.Bois.Generator/Attributes/BoisReaderAttribute.cs b/Salar.Bois/BoisReaderAttribute.cs
similarity index 90%
rename from Salar.Bois.Generator/Attributes/BoisReaderAttribute.cs
rename to Salar.Bois/BoisReaderAttribute.cs
index d095321..9db9c0c 100644
--- a/Salar.Bois.Generator/Attributes/BoisReaderAttribute.cs
+++ b/Salar.Bois/BoisReaderAttribute.cs
@@ -1,4 +1,6 @@
-namespace Salar.Bois.Generator.Attributes;
+using System;
+
+namespace Salar.Bois;
///
/// Marks a partial method as the target for generated BOIS reader implementation.
diff --git a/Salar.Bois.Generator/Attributes/BoisWriterAttribute.cs b/Salar.Bois/BoisWriterAttribute.cs
similarity index 90%
rename from Salar.Bois.Generator/Attributes/BoisWriterAttribute.cs
rename to Salar.Bois/BoisWriterAttribute.cs
index 6cac105..1319997 100644
--- a/Salar.Bois.Generator/Attributes/BoisWriterAttribute.cs
+++ b/Salar.Bois/BoisWriterAttribute.cs
@@ -1,4 +1,6 @@
-namespace Salar.Bois.Generator.Attributes;
+using System;
+
+namespace Salar.Bois;
///
/// Marks a partial method as the target for generated BOIS writer implementation.
diff --git a/Salar.Bois.Generator/Serializers/BoisNumericSerializers.cs b/Salar.Bois/CodeGen/BoisNumericSerializers.cs
similarity index 99%
rename from Salar.Bois.Generator/Serializers/BoisNumericSerializers.cs
rename to Salar.Bois/CodeGen/BoisNumericSerializers.cs
index 9f537c2..ba37ad5 100644
--- a/Salar.Bois.Generator/Serializers/BoisNumericSerializers.cs
+++ b/Salar.Bois/CodeGen/BoisNumericSerializers.cs
@@ -2,7 +2,7 @@
using Salar.Bois.Serializers;
using System.Runtime.CompilerServices;
-namespace Salar.Bois.Generator.Serializers;
+namespace Salar.Bois.CodeGen;
public static class BoisNumericSerializers
{
diff --git a/Salar.Bois.Generator/Serializers/BoisPrimitiveReaders.cs b/Salar.Bois/CodeGen/BoisPrimitiveReaders.cs
similarity index 99%
rename from Salar.Bois.Generator/Serializers/BoisPrimitiveReaders.cs
rename to Salar.Bois/CodeGen/BoisPrimitiveReaders.cs
index 5834802..25db4e7 100644
--- a/Salar.Bois.Generator/Serializers/BoisPrimitiveReaders.cs
+++ b/Salar.Bois/CodeGen/BoisPrimitiveReaders.cs
@@ -6,7 +6,7 @@
using System.Runtime.CompilerServices;
using System.Text;
-namespace Salar.Bois.Generator.Serializers;
+namespace Salar.Bois.CodeGen;
public static class BoisPrimitiveReaders
{
diff --git a/Salar.Bois.Generator/Serializers/BoisPrimitiveWriters.cs b/Salar.Bois/CodeGen/BoisPrimitiveWriters.cs
similarity index 99%
rename from Salar.Bois.Generator/Serializers/BoisPrimitiveWriters.cs
rename to Salar.Bois/CodeGen/BoisPrimitiveWriters.cs
index 6231d62..0184cec 100644
--- a/Salar.Bois.Generator/Serializers/BoisPrimitiveWriters.cs
+++ b/Salar.Bois/CodeGen/BoisPrimitiveWriters.cs
@@ -6,7 +6,7 @@
using System.Runtime.CompilerServices;
using System.Text;
-namespace Salar.Bois.Generator.Serializers;
+namespace Salar.Bois.CodeGen;
public static class BoisPrimitiveWriters
{
From bce66dbc6a116d005e11ff000ddc985a725dfbd0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Mar 2026 12:41:42 +0000
Subject: [PATCH 11/13] Update generator namespaces and analyzer references
Agent-Logs-Url: https://github.com/salarcode/Bois/sessions/273f1e86-34c2-425b-8847-f6616feb2e9d
Co-authored-by: salarcode <1272095+salarcode@users.noreply.github.com>
---
CodeGenSample/CodeGenSample.csproj | 3 ++-
CodeGenSample/Models/CompanyModel.cs | 4 ++--
Salar.Bois.Generator/BoisSourceGenerator.cs | 18 +++++++++++++-----
Salar.Bois.Generator/IsExternalInit.cs | 5 +++++
.../Salar.Bois.Generator.csproj | 7 +------
5 files changed, 23 insertions(+), 14 deletions(-)
create mode 100644 Salar.Bois.Generator/IsExternalInit.cs
diff --git a/CodeGenSample/CodeGenSample.csproj b/CodeGenSample/CodeGenSample.csproj
index 5d0d6f7..1b27f3e 100644
--- a/CodeGenSample/CodeGenSample.csproj
+++ b/CodeGenSample/CodeGenSample.csproj
@@ -10,7 +10,8 @@
+ ReferenceOutputAssembly="false" />
+
diff --git a/CodeGenSample/Models/CompanyModel.cs b/CodeGenSample/Models/CompanyModel.cs
index 852be5a..d8c207b 100644
--- a/CodeGenSample/Models/CompanyModel.cs
+++ b/CodeGenSample/Models/CompanyModel.cs
@@ -1,5 +1,5 @@
-using Salar.BinaryBuffers;
-using Salar.Bois.Generator.Attributes;
+using Salar.BinaryBuffers;
+using Salar.Bois;
using System;
using System.Collections.Generic;
using System.Text;
diff --git a/Salar.Bois.Generator/BoisSourceGenerator.cs b/Salar.Bois.Generator/BoisSourceGenerator.cs
index 8af17bf..224df60 100644
--- a/Salar.Bois.Generator/BoisSourceGenerator.cs
+++ b/Salar.Bois.Generator/BoisSourceGenerator.cs
@@ -10,8 +10,8 @@ namespace Salar.Bois.Generator;
[Generator]
public sealed class BoisSourceGenerator : ISourceGenerator
{
- private const string ReaderAttributeName = "Salar.Bois.Generator.Attributes.BoisReaderAttribute";
- private const string WriterAttributeName = "Salar.Bois.Generator.Attributes.BoisWriterAttribute";
+ private const string ReaderAttributeName = "Salar.Bois.BoisReaderAttribute";
+ private const string WriterAttributeName = "Salar.Bois.BoisWriterAttribute";
private static readonly DiagnosticDescriptor InvalidMethodSignature = new(
"BOISGEN001",
@@ -381,7 +381,7 @@ public string Emit(ImmutableArray methods)
builder.Line("#nullable enable");
builder.Line("using global::System;");
builder.Line("using global::Salar.BinaryBuffers;");
- builder.Line("using global::Salar.Bois.Generator.Serializers;");
+ builder.Line("using global::Salar.Bois.CodeGen;");
builder.Line("using global::Salar.BinaryBuffers.Compatibility;");
builder.Line();
@@ -1570,7 +1570,14 @@ public bool Equals((INamedTypeSymbol ContainingType, string FileName) x, (INamed
=> SymbolEqualityComparer.Default.Equals(x.ContainingType, y.ContainingType) && x.FileName == y.FileName;
public int GetHashCode((INamedTypeSymbol ContainingType, string FileName) obj)
- => HashCode.Combine(SymbolEqualityComparer.Default.GetHashCode(obj.ContainingType), obj.FileName);
+ {
+ unchecked
+ {
+ var hash = SymbolEqualityComparer.Default.GetHashCode(obj.ContainingType);
+ hash = (hash * 397) ^ StringComparer.Ordinal.GetHashCode(obj.FileName);
+ return hash;
+ }
+ }
}
private sealed record GenerationMethod(IMethodSymbol Method, INamedTypeSymbol ContainingType, OperationKind Operation, ITypeSymbol RootType, MethodSignature Signature);
@@ -1655,7 +1662,8 @@ public void MultiLine(string text)
return;
var indentText = new string(' ', _indent * 4);
- var lines = text.ReplaceLineEndings("\n").Split('\n');
+ var normalizedText = text.Replace("\r\n", "\n").Replace('\r', '\n');
+ var lines = normalizedText.Split('\n');
foreach (var line in lines)
{
diff --git a/Salar.Bois.Generator/IsExternalInit.cs b/Salar.Bois.Generator/IsExternalInit.cs
new file mode 100644
index 0000000..b05eb4a
--- /dev/null
+++ b/Salar.Bois.Generator/IsExternalInit.cs
@@ -0,0 +1,5 @@
+namespace System.Runtime.CompilerServices;
+
+internal static class IsExternalInit
+{
+}
diff --git a/Salar.Bois.Generator/Salar.Bois.Generator.csproj b/Salar.Bois.Generator/Salar.Bois.Generator.csproj
index 9005785..ab8943f 100644
--- a/Salar.Bois.Generator/Salar.Bois.Generator.csproj
+++ b/Salar.Bois.Generator/Salar.Bois.Generator.csproj
@@ -1,7 +1,7 @@
- net7.0
+ netstandard2.0
enable
enable
latest
@@ -12,11 +12,6 @@
-
-
-
-
-
From 4d83b585f45a1c661b4827689df73b1f27b5d799 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Mar 2026 12:43:04 +0000
Subject: [PATCH 12/13] Fix netstandard IsExternalInit compatibility shim
Agent-Logs-Url: https://github.com/salarcode/Bois/sessions/273f1e86-34c2-425b-8847-f6616feb2e9d
Co-authored-by: salarcode <1272095+salarcode@users.noreply.github.com>
---
Salar.Bois.Generator/IsExternalInit.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Salar.Bois.Generator/IsExternalInit.cs b/Salar.Bois.Generator/IsExternalInit.cs
index b05eb4a..f30ac99 100644
--- a/Salar.Bois.Generator/IsExternalInit.cs
+++ b/Salar.Bois.Generator/IsExternalInit.cs
@@ -1,5 +1,5 @@
namespace System.Runtime.CompilerServices;
-internal static class IsExternalInit
+internal sealed class IsExternalInit
{
}
From 2ab31a1e14a4630fcd84d2548b7886da80d038b5 Mon Sep 17 00:00:00 2001
From: salarcode
Date: Mon, 30 Mar 2026 07:00:29 +1100
Subject: [PATCH 13/13] Added benchmarks
---
.../BoisBenchmark_Test1_Arrays_Big.cs | 82 +++++++++++++++++++
.../Salar.Bois.BenchBois.csproj | 3 +
.../Salar.Bois.BenchmarksRun/BenchEngine.cs | 5 +-
.../Salar.Bois.BenchmarksRun/BenchRunner.cs | 2 +
4 files changed, 91 insertions(+), 1 deletion(-)
create mode 100644 Benchmarks/Salar.Bois.BenchBois/BoisBenchmark_Test1_Arrays_Big.cs
diff --git a/Benchmarks/Salar.Bois.BenchBois/BoisBenchmark_Test1_Arrays_Big.cs b/Benchmarks/Salar.Bois.BenchBois/BoisBenchmark_Test1_Arrays_Big.cs
new file mode 100644
index 0000000..c30ae21
--- /dev/null
+++ b/Benchmarks/Salar.Bois.BenchBois/BoisBenchmark_Test1_Arrays_Big.cs
@@ -0,0 +1,82 @@
+using BenchmarkDotNet.Attributes;
+using Salar.Bois.BenchmarksBase;
+using Salar.Bois.BenchmarksObjects.TestObjects;
+using System.IO;
+
+namespace Salar.Bois.BenchBois;
+
+public class BoisBenchmark_Test1_Arrays_Big : BenchmarkBase
+{
+ [Params("Bois")]
+ public override string TestName { get; set; }
+
+
+ [Benchmark(Description = "Serialize")]
+ [BenchmarkCategory("Bois")]
+ public override void Serialize()
+ {
+ for (int i = 0; i < IterationCount; i++)
+ {
+ Reset();
+ Bois_Test1_Arrays_Big.WriteCompanyModel(TestObject, TestStream);
+ }
+ }
+
+ [Benchmark(Description = "Deserialize")]
+ [BenchmarkCategory("Bois")]
+ public override void Deserialize()
+ {
+ for (int i = 0; i < IterationCount; i++)
+ {
+ Reset();
+ Bois_Test1_Arrays_Big.ReadCompanyModel(TestStream);
+ }
+ }
+}
+
+public class BoisBenchmark_Test1_Arrays_Small : BenchmarkBase
+{
+ [Params("Bois")]
+ public override string TestName { get; set; }
+
+
+ [Benchmark(Description = "Serialize")]
+ [BenchmarkCategory("Bois")]
+ public override void Serialize()
+ {
+ for (int i = 0; i < IterationCount; i++)
+ {
+ Reset();
+ Bois_Test1_Arrays_Small.WriteCompanyModel(TestObject, TestStream);
+ }
+ }
+
+ [Benchmark(Description = "Deserialize")]
+ [BenchmarkCategory("Bois")]
+ public override void Deserialize()
+ {
+ for (int i = 0; i < IterationCount; i++)
+ {
+ Reset();
+ Bois_Test1_Arrays_Small.ReadCompanyModel(TestStream);
+ }
+ }
+}
+
+public static partial class Bois_Test1_Arrays_Big
+{
+ [BoisReader]
+ public static partial Test1_Arrays_Big? ReadCompanyModel(Stream source);
+
+ [BoisWriter]
+ public static partial void WriteCompanyModel(Test1_Arrays_Big? model, Stream output);
+}
+
+public static partial class Bois_Test1_Arrays_Small
+{
+ [BoisReader]
+ public static partial Test1_Arrays_Small? ReadCompanyModel(Stream source);
+
+ [BoisWriter]
+ public static partial void WriteCompanyModel(Test1_Arrays_Small? model, Stream output);
+}
\ No newline at end of file
diff --git a/Benchmarks/Salar.Bois.BenchBois/Salar.Bois.BenchBois.csproj b/Benchmarks/Salar.Bois.BenchBois/Salar.Bois.BenchBois.csproj
index 692bf22..69aa7bf 100644
--- a/Benchmarks/Salar.Bois.BenchBois/Salar.Bois.BenchBois.csproj
+++ b/Benchmarks/Salar.Bois.BenchBois/Salar.Bois.BenchBois.csproj
@@ -13,6 +13,9 @@
+
diff --git a/Benchmarks/Salar.Bois.BenchmarksRun/BenchEngine.cs b/Benchmarks/Salar.Bois.BenchmarksRun/BenchEngine.cs
index 09722b2..c375861 100644
--- a/Benchmarks/Salar.Bois.BenchmarksRun/BenchEngine.cs
+++ b/Benchmarks/Salar.Bois.BenchmarksRun/BenchEngine.cs
@@ -43,7 +43,10 @@ public IEnumerable GetBenchmarkable()
{
foreach (var benchmarkType in _benchmarks)
{
- yield return benchmarkType.MakeGenericType(testObjectType);
+ if (benchmarkType.IsGenericType)
+ yield return benchmarkType.MakeGenericType(testObjectType);
+ else
+ yield return benchmarkType;
}
}
}
diff --git a/Benchmarks/Salar.Bois.BenchmarksRun/BenchRunner.cs b/Benchmarks/Salar.Bois.BenchmarksRun/BenchRunner.cs
index 752a1b0..f93d173 100644
--- a/Benchmarks/Salar.Bois.BenchmarksRun/BenchRunner.cs
+++ b/Benchmarks/Salar.Bois.BenchmarksRun/BenchRunner.cs
@@ -24,6 +24,8 @@ private void SetupBenchmarks()
_engine.AddBenchmark(typeof(BoisBenchmark<>));
_engine.AddBenchmark(typeof(BoisBufferBenchmark<>));
_engine.AddBenchmark(typeof(BoisLz4Benchmark<>));
+ _engine.AddBenchmark(typeof(BoisBenchmark_Test1_Arrays_Big));
+ _engine.AddBenchmark(typeof(BoisBenchmark_Test1_Arrays_Small));
_engine.AddBenchmark(typeof(MessagePackBenchmark<>));
_engine.AddBenchmark(typeof(MessagePackLz4Benchmark<>));
_engine.AddBenchmark(typeof(ProtobufNetBenchmark<>));