using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace AyCode.Core.Serializers.SourceGenerator; /// /// Source Generator for AcBinary serialization. /// Generates optimized serialize/deserialize methods for classes marked with [AcBinarySerializable]. /// [Generator] public class AcBinarySourceGenerator : IIncrementalGenerator { private const string AcBinarySerializableAttributeName = "AyCode.Core.Serializers.Attributes.AcBinarySerializableAttribute"; public void Initialize(IncrementalGeneratorInitializationContext context) { // Find all classes with [AcBinarySerializable] attribute var classDeclarations = context.SyntaxProvider .ForAttributeWithMetadataName( AcBinarySerializableAttributeName, predicate: static (node, _) => node is ClassDeclarationSyntax || node is StructDeclarationSyntax, transform: static (ctx, _) => GetClassInfo(ctx)) .Where(static info => info != null); // Combine with compilation var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); // Generate source context.RegisterSourceOutput(compilationAndClasses, static (spc, source) => Execute(source.Left, source.Right, spc)); } private static SerializableClassInfo GetClassInfo(GeneratorAttributeSyntaxContext context) { if (!(context.TargetSymbol is INamedTypeSymbol typeSymbol)) return null; var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace ? string.Empty : typeSymbol.ContainingNamespace.ToDisplayString(); var className = typeSymbol.Name; var fullTypeName = typeSymbol.ToDisplayString(); var isStruct = typeSymbol.IsValueType; // Check if this is a nested type var isNestedType = typeSymbol.ContainingType != null; // For nested types, we need the full containing type path for method signatures // e.g. "OuterClass.InnerClass" instead of just "InnerClass" var typeNameForSignature = isNestedType ? GetNestedTypeName(typeSymbol) : className; // Get all public properties with getter and setter // DUPLICATED LOGIC: Same filtering as TypeMetadataBase.GetSerializableProperties() var properties = new List(); foreach (var member in typeSymbol.GetMembers()) { if (member is IPropertySymbol p && p.DeclaredAccessibility == Accessibility.Public && p.GetMethod != null && p.SetMethod != null && !p.IsIndexer && !p.IsStatic) { properties.Add(new PropertyInfo( p.Name, p.Type.ToDisplayString(), GetPropertyTypeKind(p.Type), p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableValueType(p.Type))); } } // DUPLICATED LOGIC: Same ordering as TypeMetadataBase.GetSerializableProperties() properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); return new SerializableClassInfo(namespaceName, className, fullTypeName, isStruct, isNestedType, typeNameForSignature, properties); } /// /// Gets the nested type name chain (e.g., "OuterClass.MiddleClass.InnerClass") /// private static string GetNestedTypeName(INamedTypeSymbol typeSymbol) { var parts = new List(); var current = typeSymbol; while (current != null) { parts.Insert(0, current.Name); current = current.ContainingType; } return string.Join(".", parts); } private static bool IsNullableValueType(ITypeSymbol type) { return type is INamedTypeSymbol namedType && namedType.IsGenericType && namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T; } private static PropertyTypeKind GetPropertyTypeKind(ITypeSymbol type) { // Handle nullable value types if (type is INamedTypeSymbol namedType && namedType.IsGenericType && namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T) { // Get underlying type var underlyingType = namedType.TypeArguments[0]; return GetPropertyTypeKindForUnderlying(underlyingType, isNullable: true); } return GetPropertyTypeKindForUnderlying(type, isNullable: false); } private static PropertyTypeKind GetPropertyTypeKindForUnderlying(ITypeSymbol type, bool isNullable) { switch (type.SpecialType) { case SpecialType.System_String: return PropertyTypeKind.String; case SpecialType.System_Int32: return isNullable ? PropertyTypeKind.NullableInt32 : PropertyTypeKind.Int32; case SpecialType.System_Int64: return isNullable ? PropertyTypeKind.NullableInt64 : PropertyTypeKind.Int64; case SpecialType.System_Int16: return isNullable ? PropertyTypeKind.NullableInt16 : PropertyTypeKind.Int16; case SpecialType.System_Byte: return isNullable ? PropertyTypeKind.NullableByte : PropertyTypeKind.Byte; case SpecialType.System_SByte: return isNullable ? PropertyTypeKind.NullableSByte : PropertyTypeKind.SByte; case SpecialType.System_UInt16: return isNullable ? PropertyTypeKind.NullableUInt16 : PropertyTypeKind.UInt16; case SpecialType.System_UInt32: return isNullable ? PropertyTypeKind.NullableUInt32 : PropertyTypeKind.UInt32; case SpecialType.System_UInt64: return isNullable ? PropertyTypeKind.NullableUInt64 : PropertyTypeKind.UInt64; case SpecialType.System_Boolean: return isNullable ? PropertyTypeKind.NullableBoolean : PropertyTypeKind.Boolean; case SpecialType.System_Single: return isNullable ? PropertyTypeKind.NullableSingle : PropertyTypeKind.Single; case SpecialType.System_Double: return isNullable ? PropertyTypeKind.NullableDouble : PropertyTypeKind.Double; case SpecialType.System_Decimal: return isNullable ? PropertyTypeKind.NullableDecimal : PropertyTypeKind.Decimal; case SpecialType.System_DateTime: return isNullable ? PropertyTypeKind.NullableDateTime : PropertyTypeKind.DateTime; default: return GetNonSpecialTypeKind(type, isNullable); } } private static PropertyTypeKind GetNonSpecialTypeKind(ITypeSymbol type, bool isNullable) { var fullName = type.ToDisplayString(); if (fullName == "System.Guid") return isNullable ? PropertyTypeKind.NullableGuid : PropertyTypeKind.Guid; if (fullName == "System.TimeSpan") return isNullable ? PropertyTypeKind.NullableTimeSpan : PropertyTypeKind.TimeSpan; if (fullName == "System.DateTimeOffset") return isNullable ? PropertyTypeKind.NullableDateTimeOffset : PropertyTypeKind.DateTimeOffset; if (fullName == "System.DateOnly") return isNullable ? PropertyTypeKind.NullableDateOnly : PropertyTypeKind.DateOnly; if (fullName == "System.TimeOnly") return isNullable ? PropertyTypeKind.NullableTimeOnly : PropertyTypeKind.TimeOnly; if (type.TypeKind == TypeKind.Enum) return isNullable ? PropertyTypeKind.NullableEnum : PropertyTypeKind.Enum; if (IsCollectionType(type)) return PropertyTypeKind.Collection; if (type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct) return PropertyTypeKind.Complex; return PropertyTypeKind.Unknown; } private static bool IsCollectionType(ITypeSymbol type) { if (type is IArrayTypeSymbol) return true; if (type is INamedTypeSymbol namedType) { foreach (var iface in namedType.AllInterfaces) { if (iface.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T) return true; var ifaceName = iface.ToDisplayString(); if (ifaceName.StartsWith("System.Collections.Generic.IList<") || ifaceName.StartsWith("System.Collections.Generic.ICollection<")) return true; } } return false; } private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) { if (classes.IsDefaultOrEmpty) return; foreach (var classInfo in classes) { if (classInfo == null) continue; var source = GenerateSerializerClass(classInfo); context.AddSource($"{classInfo.ClassName}_AcBinarySerializer.g.cs", SourceText.From(source, Encoding.UTF8)); } } private static string GenerateSerializerClass(SerializableClassInfo classInfo) { var sb = new StringBuilder(); sb.AppendLine("// "); sb.AppendLine("#nullable enable"); sb.AppendLine(); sb.AppendLine("using System;"); sb.AppendLine("using System.Runtime.CompilerServices;"); sb.AppendLine("using AyCode.Core.Serializers.Binaries;"); sb.AppendLine(); if (!string.IsNullOrEmpty(classInfo.Namespace)) { sb.AppendLine($"namespace {classInfo.Namespace}"); sb.AppendLine("{"); } var indent = string.IsNullOrEmpty(classInfo.Namespace) ? "" : " "; sb.AppendLine($"{indent}/// "); sb.AppendLine($"{indent}/// Generated binary serializer for {classInfo.ClassName}."); sb.AppendLine($"{indent}/// "); sb.AppendLine($"{indent}internal static class {classInfo.ClassName}_AcBinarySerializer"); sb.AppendLine($"{indent}{{"); // Generate property count constant sb.AppendLine($"{indent} public const int PropertyCount = {classInfo.Properties.Count};"); sb.AppendLine(); // Generate property names array for validation sb.AppendLine($"{indent} /// "); sb.AppendLine($"{indent} /// Property names in serialization order (alphabetical)."); sb.AppendLine($"{indent} /// Used for runtime validation against TypeMetadataBase.GetSerializableProperties()."); sb.AppendLine($"{indent} /// "); sb.Append($"{indent} public static readonly string[] PropertyNames = new[] {{ "); sb.Append(string.Join(", ", classInfo.Properties.Select(p => $"\"{p.Name}\""))); sb.AppendLine(" };"); sb.AppendLine(); // Generate Serialize method GenerateSerializeMethod(sb, classInfo, indent); // Generate Deserialize method GenerateDeserializeMethod(sb, classInfo, indent); sb.AppendLine($"{indent}}}"); if (!string.IsNullOrEmpty(classInfo.Namespace)) { sb.AppendLine("}"); } return sb.ToString(); } private static void GenerateSerializeMethod(StringBuilder sb, SerializableClassInfo classInfo, string indent) { sb.AppendLine($"{indent} /// "); sb.AppendLine($"{indent} /// Serializes a {classInfo.ClassName} instance to the binary context."); sb.AppendLine($"{indent} /// Direct property access - no reflection, no boxing for primitives."); sb.AppendLine($"{indent} /// "); sb.AppendLine($"{indent} [MethodImpl(MethodImplOptions.AggressiveInlining)]"); sb.AppendLine($"{indent} public static void Serialize({classInfo.TypeNameForSignature} obj, ref AcBinarySerializer.BinarySerializationContext context)"); sb.AppendLine($"{indent} {{"); foreach (var prop in classInfo.Properties) { GenerateSerializeProperty(sb, prop, indent + " "); } sb.AppendLine($"{indent} }}"); sb.AppendLine(); } private static void GenerateSerializeProperty(StringBuilder sb, PropertyInfo prop, string indent) { var propAccess = $"obj.{prop.Name}"; // Handle nullable VALUE types (Nullable) - these use .HasValue and .Value if (IsNullableValueTypeKind(prop.TypeKind)) { sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName} (nullable value type)"); sb.AppendLine($"{indent}if ({propAccess}.HasValue)"); sb.AppendLine($"{indent}{{"); GenerateSerializeValue(sb, prop.TypeKind, $"{propAccess}.Value", indent + " "); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);"); sb.AppendLine($"{indent}}}"); return; } sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName}"); // String needs null check if (prop.TypeKind == PropertyTypeKind.String) { sb.AppendLine($"{indent}if ({propAccess} == null)"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else if ({propAccess}.Length == 0)"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.StringEmpty);"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.String);"); sb.AppendLine($"{indent} context.WriteStringUtf8({propAccess});"); sb.AppendLine($"{indent}}}"); return; } // Nullable reference types (Complex/Collection with ? annotation) - use == null if (prop.IsNullable && (prop.TypeKind == PropertyTypeKind.Complex || prop.TypeKind == PropertyTypeKind.Collection)) { sb.AppendLine($"{indent}if ({propAccess} == null)"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else"); sb.AppendLine($"{indent}{{"); GenerateSerializeValue(sb, prop.TypeKind, propAccess, indent + " "); sb.AppendLine($"{indent}}}"); return; } GenerateSerializeValue(sb, prop.TypeKind, propAccess, indent); } /// /// Checks if the type kind represents a nullable VALUE type (Nullable<T>), not a reference type /// private static bool IsNullableValueTypeKind(PropertyTypeKind kind) { return kind >= PropertyTypeKind.NullableInt32 && kind <= PropertyTypeKind.NullableEnum; } private static void GenerateSerializeValue(StringBuilder sb, PropertyTypeKind typeKind, string valueExpr, string indent) { switch (typeKind) { case PropertyTypeKind.Int32: case PropertyTypeKind.NullableInt32: sb.AppendLine($"{indent}if (BinaryTypeCode.TryEncodeTinyInt({valueExpr}, out var tiny_{valueExpr.Replace(".", "_")}))"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.WriteByte(tiny_{valueExpr.Replace(".", "_")});"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Int32);"); sb.AppendLine($"{indent} context.WriteVarInt({valueExpr});"); sb.AppendLine($"{indent}}}"); break; case PropertyTypeKind.Int64: case PropertyTypeKind.NullableInt64: sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Int64);"); sb.AppendLine($"{indent}context.WriteVarLong({valueExpr});"); break; case PropertyTypeKind.Boolean: case PropertyTypeKind.NullableBoolean: sb.AppendLine($"{indent}context.WriteByte({valueExpr} ? BinaryTypeCode.True : BinaryTypeCode.False);"); break; case PropertyTypeKind.Double: case PropertyTypeKind.NullableDouble: sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Float64, {valueExpr});"); break; case PropertyTypeKind.Single: case PropertyTypeKind.NullableSingle: sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Float32, {valueExpr});"); break; case PropertyTypeKind.Decimal: case PropertyTypeKind.NullableDecimal: sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Decimal);"); sb.AppendLine($"{indent}context.WriteDecimalBits({valueExpr});"); break; case PropertyTypeKind.DateTime: case PropertyTypeKind.NullableDateTime: sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.DateTime);"); sb.AppendLine($"{indent}context.WriteDateTimeBits({valueExpr});"); break; case PropertyTypeKind.Guid: case PropertyTypeKind.NullableGuid: sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Guid);"); sb.AppendLine($"{indent}context.WriteGuidBits({valueExpr});"); break; case PropertyTypeKind.Byte: case PropertyTypeKind.NullableByte: sb.AppendLine($"{indent}context.WriteTwoBytes(BinaryTypeCode.UInt8, {valueExpr});"); break; case PropertyTypeKind.Int16: case PropertyTypeKind.NullableInt16: sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Int16, {valueExpr});"); break; case PropertyTypeKind.UInt16: case PropertyTypeKind.NullableUInt16: sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.UInt16, {valueExpr});"); break; case PropertyTypeKind.UInt32: case PropertyTypeKind.NullableUInt32: sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt32);"); sb.AppendLine($"{indent}context.WriteVarUInt({valueExpr});"); sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt32);"); sb.AppendLine($"{indent}context.WriteVarUInt({valueExpr});"); break; case PropertyTypeKind.UInt64: case PropertyTypeKind.NullableUInt64: sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt64);"); sb.AppendLine($"{indent}context.WriteVarULong({valueExpr});"); break; case PropertyTypeKind.TimeSpan: case PropertyTypeKind.NullableTimeSpan: sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.TimeSpan, {valueExpr}.Ticks);"); break; case PropertyTypeKind.DateTimeOffset: case PropertyTypeKind.NullableDateTimeOffset: sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.DateTimeOffset);"); sb.AppendLine($"{indent}context.WriteDateTimeOffsetBits({valueExpr});"); break; case PropertyTypeKind.Enum: case PropertyTypeKind.NullableEnum: sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Enum);"); sb.AppendLine($"{indent}var enumVal = (int){valueExpr};"); sb.AppendLine($"{indent}if (BinaryTypeCode.TryEncodeTinyInt(enumVal, out var tinyEnum))"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.WriteByte(tinyEnum);"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Int32);"); sb.AppendLine($"{indent} context.WriteVarInt(enumVal);"); sb.AppendLine($"{indent}}}"); break; case PropertyTypeKind.Collection: case PropertyTypeKind.Complex: // TODO: Collections and complex types will be implemented later sb.AppendLine($"{indent}// TODO: Complex/Collection type - fallback to runtime serializer"); sb.AppendLine($"{indent}throw new NotImplementedException(\"Complex/Collection types not yet implemented in generated serializer\");"); break; default: sb.AppendLine($"{indent}// Unknown type - fallback needed"); sb.AppendLine($"{indent}throw new NotImplementedException($\"Type {typeKind} not implemented in generated serializer\");"); break; } } private static void GenerateDeserializeMethod(StringBuilder sb, SerializableClassInfo classInfo, string indent) { sb.AppendLine($"{indent} /// "); sb.AppendLine($"{indent} /// Deserializes properties into a {classInfo.ClassName} instance from the binary context."); sb.AppendLine($"{indent} /// Direct property access - no reflection, no boxing for primitives."); sb.AppendLine($"{indent} /// "); sb.AppendLine($"{indent} [MethodImpl(MethodImplOptions.AggressiveInlining)]"); sb.AppendLine($"{indent} public static void Deserialize({classInfo.TypeNameForSignature} obj, ref AcBinaryDeserializer.BinaryDeserializationContext context)"); sb.AppendLine($"{indent} {{"); foreach (var prop in classInfo.Properties) { GenerateDeserializeProperty(sb, prop, indent + " "); } sb.AppendLine($"{indent} }}"); } private static void GenerateDeserializeProperty(StringBuilder sb, PropertyInfo prop, string indent) { sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName}"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} var peekCode = context.PeekByte();"); // Handle Skip marker sb.AppendLine($"{indent} if (peekCode == BinaryTypeCode.PropertySkip)"); sb.AppendLine($"{indent} {{"); sb.AppendLine($"{indent} context.ReadByte(); // consume Skip marker"); sb.AppendLine($"{indent} // Property keeps default value"); sb.AppendLine($"{indent} }}"); // Handle Null marker sb.AppendLine($"{indent} else if (peekCode == BinaryTypeCode.Null)"); sb.AppendLine($"{indent} {{"); sb.AppendLine($"{indent} context.ReadByte(); // consume Null marker"); if (prop.IsNullable || prop.TypeKind == PropertyTypeKind.String) { sb.AppendLine($"{indent} obj.{prop.Name} = default;"); } else { sb.AppendLine($"{indent} // Non-nullable property, keep default"); } sb.AppendLine($"{indent} }}"); // Handle actual value sb.AppendLine($"{indent} else"); sb.AppendLine($"{indent} {{"); GenerateDeserializeValue(sb, prop, indent + " "); sb.AppendLine($"{indent} }}"); sb.AppendLine($"{indent}}}"); } private static void GenerateDeserializeValue(StringBuilder sb, PropertyInfo prop, string indent) { var propAccess = $"obj.{prop.Name}"; switch (prop.TypeKind) { case PropertyTypeKind.String: sb.AppendLine($"{indent}if (peekCode == BinaryTypeCode.StringEmpty)"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.ReadByte();"); sb.AppendLine($"{indent} {propAccess} = string.Empty;"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.String)"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.ReadByte();"); sb.AppendLine($"{indent} var len = (int)context.ReadVarUInt();"); sb.AppendLine($"{indent} {propAccess} = context.ReadStringUtf8(len);"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else if (BinaryTypeCode.IsFixStr(peekCode))"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.ReadByte();"); sb.AppendLine($"{indent} var len = BinaryTypeCode.DecodeFixStrLength(peekCode);"); sb.AppendLine($"{indent} {propAccess} = len == 0 ? string.Empty : context.ReadStringUtf8(len);"); sb.AppendLine($"{indent}}}"); break; case PropertyTypeKind.Int32: case PropertyTypeKind.NullableInt32: sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.ReadByte();"); sb.AppendLine($"{indent} {propAccess} = BinaryTypeCode.DecodeTinyInt(peekCode);"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int32)"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.ReadByte();"); sb.AppendLine($"{indent} {propAccess} = context.ReadVarInt();"); sb.AppendLine($"{indent}}}"); break; case PropertyTypeKind.Int64: case PropertyTypeKind.NullableInt64: sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.ReadByte();"); sb.AppendLine($"{indent} {propAccess} = BinaryTypeCode.DecodeTinyInt(peekCode);"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int32)"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.ReadByte();"); sb.AppendLine($"{indent} {propAccess} = context.ReadVarInt();"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int64)"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.ReadByte();"); sb.AppendLine($"{indent} {propAccess} = context.ReadVarLong();"); sb.AppendLine($"{indent}}}"); break; case PropertyTypeKind.Boolean: case PropertyTypeKind.NullableBoolean: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = peekCode == BinaryTypeCode.True;"); break; case PropertyTypeKind.Double: case PropertyTypeKind.NullableDouble: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadDoubleUnsafe();"); break; case PropertyTypeKind.Single: case PropertyTypeKind.NullableSingle: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadSingleUnsafe();"); break; case PropertyTypeKind.Decimal: case PropertyTypeKind.NullableDecimal: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadDecimalUnsafe();"); break; case PropertyTypeKind.DateTime: case PropertyTypeKind.NullableDateTime: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadDateTimeUnsafe();"); break; case PropertyTypeKind.Guid: case PropertyTypeKind.NullableGuid: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadGuidUnsafe();"); break; case PropertyTypeKind.Byte: case PropertyTypeKind.NullableByte: sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.ReadByte();"); sb.AppendLine($"{indent} {propAccess} = (byte)BinaryTypeCode.DecodeTinyInt(peekCode);"); sb.AppendLine($"{indent}}}"); sb.AppendLine($"{indent}else"); sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent} context.ReadByte();"); sb.AppendLine($"{indent} {propAccess} = context.ReadByte();"); sb.AppendLine($"{indent}}}"); break; case PropertyTypeKind.Int16: case PropertyTypeKind.NullableInt16: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadInt16Unsafe();"); break; case PropertyTypeKind.UInt16: case PropertyTypeKind.NullableUInt16: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadUInt16Unsafe();"); break; case PropertyTypeKind.UInt32: case PropertyTypeKind.NullableUInt32: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadVarUInt();"); break; case PropertyTypeKind.UInt64: case PropertyTypeKind.NullableUInt64: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadVarULong();"); break; case PropertyTypeKind.TimeSpan: case PropertyTypeKind.NullableTimeSpan: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadTimeSpanUnsafe();"); break; case PropertyTypeKind.DateTimeOffset: case PropertyTypeKind.NullableDateTimeOffset: sb.AppendLine($"{indent}context.ReadByte();"); sb.AppendLine($"{indent}{propAccess} = context.ReadDateTimeOffsetUnsafe();"); break; case PropertyTypeKind.Enum: case PropertyTypeKind.NullableEnum: sb.AppendLine($"{indent}// TODO: Enum deserialization needs type info"); sb.AppendLine($"{indent}throw new NotImplementedException(\"Enum deserialization not yet implemented\");"); break; case PropertyTypeKind.Collection: case PropertyTypeKind.Complex: sb.AppendLine($"{indent}// TODO: Complex/Collection types"); sb.AppendLine($"{indent}throw new NotImplementedException(\"Complex/Collection deserialization not yet implemented\");"); break; default: sb.AppendLine($"{indent}throw new NotImplementedException($\"Type deserialization not implemented\");"); break; } } } /// /// Information about a class marked with [AcBinarySerializable]. /// internal sealed class SerializableClassInfo { public string Namespace { get; } public string ClassName { get; } public string FullTypeName { get; } public bool IsStruct { get; } public bool IsNestedType { get; } /// /// The type name to use in method signatures. For nested types this includes the containing types. /// e.g., "OuterClass.InnerClass" /// public string TypeNameForSignature { get; } public List Properties { get; } public SerializableClassInfo(string ns, string className, string fullTypeName, bool isStruct, bool isNestedType, string typeNameForSignature, List properties) { Namespace = ns; ClassName = className; FullTypeName = fullTypeName; IsStruct = isStruct; IsNestedType = isNestedType; TypeNameForSignature = typeNameForSignature; Properties = properties; } } /// /// Information about a serializable property. /// internal sealed class PropertyInfo { public string Name { get; } public string TypeName { get; } public PropertyTypeKind TypeKind { get; } public bool IsNullable { get; } public PropertyInfo(string name, string typeName, PropertyTypeKind typeKind, bool isNullable) { Name = name; TypeName = typeName; TypeKind = typeKind; IsNullable = isNullable; } } /// /// Kind of property type for code generation. /// internal enum PropertyTypeKind { Unknown, String, Int32, Int64, Int16, Byte, SByte, UInt16, UInt32, UInt64, Boolean, Single, Double, Decimal, DateTime, DateTimeOffset, TimeSpan, DateOnly, TimeOnly, Guid, Enum, Collection, Complex, // Nullable variants NullableInt32, NullableInt64, NullableInt16, NullableByte, NullableSByte, NullableUInt16, NullableUInt32, NullableUInt64, NullableBoolean, NullableSingle, NullableDouble, NullableDecimal, NullableDateTime, NullableDateTimeOffset, NullableTimeSpan, NullableDateOnly, NullableTimeOnly, NullableGuid, NullableEnum }