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; /// /// Generates IGeneratedBinaryWriter implementations for [AcBinarySerializable] types. /// Also generates a ModuleInitializer that auto-registers all writers at startup. /// [Generator] public class AcBinarySourceGenerator : IIncrementalGenerator { private const string AttributeName = "AyCode.Core.Serializers.Attributes.AcBinarySerializableAttribute"; public void Initialize(IncrementalGeneratorInitializationContext context) { var classDeclarations = context.SyntaxProvider .ForAttributeWithMetadataName( AttributeName, predicate: static (node, _) => node is ClassDeclarationSyntax || node is StructDeclarationSyntax, transform: static (ctx, _) => GetClassInfo(ctx)) .Where(static info => info != null); context.RegisterSourceOutput(classDeclarations.Collect(), static (spc, classes) => Execute(classes!, 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 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) { var hasIgnore = p.GetAttributes().Any(a => { var name = a.AttributeClass?.Name ?? ""; return name == "JsonIgnoreAttribute" || name == "IgnoreMemberAttribute" || name == "BsonIgnoreAttribute"; }); if (hasIgnore) continue; // String interning attribútum detektálás (null = no attr, true/false = explicit) bool? stringInternAttr = null; if (GetKind(p.Type) == PropertyTypeKind.String) { var attr = p.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "AyCode.Core.Serializers.Binaries.AcStringInternAttribute"); if (attr != null && attr.ConstructorArguments.Length == 1 && attr.ConstructorArguments[0].Kind == TypedConstantKind.Primitive) { stringInternAttr = (bool)attr.ConstructorArguments[0].Value!; } } // For typeof(): strip trailing '?' from nullable reference types (typeof(T?) is invalid for ref types) // Nullable value types (int?, Guid?) keep '?' because typeof(int?) == typeof(Nullable) is valid var typeDisplayName = p.Type.ToDisplayString(); var typeNameForTypeof = (p.Type.NullableAnnotation == NullableAnnotation.Annotated && !p.Type.IsValueType) ? typeDisplayName.TrimEnd('?') : typeDisplayName; // Direct object write detection for Complex property types: // Check if the property type has [AcBinarySerializable] (→ has generated writer) // and if it implements IId (→ needs ref tracking in generated code) var kind = GetKind(p.Type); bool hasGenWriter = false; bool propTypeIsIId = false; string? writerClassName = null; int childTypeNameHash = 0; int[]? childPropertyHashes = null; if (kind == PropertyTypeKind.Complex) { // Resolve to the actual type symbol (strip nullable annotation for ref types) // For SharedTag? → SharedTag. OriginalDefinition handles generic types. var resolvedType = p.Type is INamedTypeSymbol namedPropType ? namedPropType.OriginalDefinition : p.Type; hasGenWriter = resolvedType.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == AttributeName); if (hasGenWriter) { propTypeIsIId = resolvedType.AllInterfaces.Any(i => i.IsGenericType && i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId"); // Writer class: {Namespace}.{FlatName}_GeneratedWriter var flatName = BuildFlatName((INamedTypeSymbol)resolvedType); var ns = resolvedType.ContainingNamespace.IsGlobalNamespace ? string.Empty : resolvedType.ContainingNamespace.ToDisplayString(); writerClassName = string.IsNullOrEmpty(ns) ? $"{flatName}_GeneratedWriter" : $"{ns}.{flatName}_GeneratedWriter"; // UseMetadata: compute child type hash-es for inline metadata childTypeNameHash = ComputeFnvHash(resolvedType.Name); childPropertyHashes = ComputeChildPropertyHashes(resolvedType); } } // Collection element type analysis for inline collection write PropertyTypeKind elemKind = PropertyTypeKind.Unknown; bool elemHasGenWriter = false; bool elemIsIId = false; string? elemWriterClassName = null; string? collKind = null; string? elemFullTypeName = null; int elementTypeNameHash = 0; int[]? elementPropertyHashes = null; if (kind == PropertyTypeKind.Collection) { var elemType = GetCollectionElementType(p.Type); if (elemType != null) { elemKind = GetKind(elemType); elemFullTypeName = elemType.ToDisplayString(); // Detect collection shape for inline write if (p.Type is IArrayTypeSymbol) collKind = "Array"; else if (p.Type is INamedTypeSymbol collNamedType) { var origDef = collNamedType.OriginalDefinition.ToDisplayString(); collKind = origDef switch { "System.Collections.Generic.List" => "List", "System.Collections.Generic.IList" => "List", // has Count + indexer "System.Collections.Generic.IReadOnlyList" => "List", // has Count + indexer "System.Collections.Generic.HashSet" => "Counted", // has Count, no indexer "System.Collections.Generic.Queue" => "Counted", "System.Collections.Generic.ICollection" => "Counted", "System.Collections.Generic.IReadOnlyCollection" => "Counted", "System.Collections.Generic.SortedSet" => "Counted", "System.Collections.Generic.LinkedList" => "Counted", _ => null }; } // For Complex element types, check for generated writer if (elemKind == PropertyTypeKind.Complex) { var resolvedElem = elemType is INamedTypeSymbol namedElem ? namedElem.OriginalDefinition : elemType; elemHasGenWriter = resolvedElem.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == AttributeName); if (elemHasGenWriter) { elemIsIId = resolvedElem.AllInterfaces.Any(ifc => ifc.IsGenericType && ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId"); var elemFlatName = BuildFlatName((INamedTypeSymbol)resolvedElem); var ens = resolvedElem.ContainingNamespace.IsGlobalNamespace ? string.Empty : resolvedElem.ContainingNamespace.ToDisplayString(); elemWriterClassName = string.IsNullOrEmpty(ens) ? $"{elemFlatName}_GeneratedWriter" : $"{ens}.{elemFlatName}_GeneratedWriter"; // UseMetadata: compute element type hash-es for inline metadata elementTypeNameHash = ComputeFnvHash(resolvedElem.Name); elementPropertyHashes = ComputeChildPropertyHashes(resolvedElem); } } } } properties.Add(new PropInfo( p.Name, typeDisplayName, typeNameForTypeof, kind, p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableVT(p.Type), stringInternAttr, hasGenWriter, propTypeIsIId, writerClassName, elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, collKind, elemFullTypeName, childTypeNameHash, childPropertyHashes, elementTypeNameHash, elementPropertyHashes)); } } // IId: Id first (index 0), then alphabetical — matches runtime TypeMetadataBase ordering var isIId = typeSymbol.AllInterfaces.Any(i => i.IsGenericType && i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId"); if (isIId) properties.Sort((a, b) => { var aIsId = a.Name == "Id" ? 0 : 1; var bIsId = b.Name == "Id" ? 0 : 1; if (aIsId != bIsId) return aIsId.CompareTo(bIsId); return string.Compare(a.Name, b.Name, StringComparison.Ordinal); }); else properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); var className = BuildFlatName(typeSymbol); var typeNameHash = ComputeFnvHash(typeSymbol.Name); var propertyNameHashes = properties.Select(prop => ComputeFnvHash(prop.Name)).ToArray(); return new SerializableClassInfo(namespaceName, className, typeSymbol.ToDisplayString(), properties, typeNameHash, propertyNameHashes); } private static void Execute(ImmutableArray classes, SourceProductionContext context) { if (classes.IsDefaultOrEmpty) return; var valid = classes.Where(c => c != null).Cast().ToList(); if (valid.Count == 0) return; foreach (var ci in valid) context.AddSource($"{ci.ClassName}_GeneratedWriter.g.cs", SourceText.From(GenWriter(ci), Encoding.UTF8)); context.AddSource("AcBinaryGeneratedWriters_Init.g.cs", SourceText.From(GenInit(valid), Encoding.UTF8)); } private static string GenWriter(SerializableClassInfo ci) { var sb = new StringBuilder(2048); sb.AppendLine("// "); sb.AppendLine("#nullable enable"); sb.AppendLine("using System.Runtime.CompilerServices;"); sb.AppendLine("using AyCode.Core.Serializers.Binaries;"); // ReferenceHandlingMode is needed when any Complex/Collection property has direct object write if (ci.Properties.Any(p => p.HasGeneratedWriter || p.ElementHasGeneratedWriter)) sb.AppendLine("using AyCode.Core.Serializers;"); sb.AppendLine(); if (!string.IsNullOrEmpty(ci.Namespace)) sb.AppendLine($"namespace {ci.Namespace};"); sb.AppendLine(); sb.AppendLine($"internal sealed class {ci.ClassName}_GeneratedWriter : IGeneratedBinaryWriter"); sb.AppendLine("{"); sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedWriter Instance = new();"); sb.AppendLine($" internal static readonly int s_metadataSlot = AcBinarySerializer.AllocateMetadataSlot();"); sb.AppendLine($" internal static readonly int s_typeNameHash = {ci.TypeNameHash};"); sb.Append( $" internal static readonly int[] s_propertyHashes = new int[] {{ "); sb.Append(string.Join(", ", ci.PropertyNameHashes)); sb.AppendLine(" };"); sb.AppendLine(); sb.AppendLine(" public void WriteProperties(object value, AcBinarySerializer.BinarySerializationContext context, int depth) where TOutput : struct, IBinaryOutputBase"); sb.AppendLine(" {"); sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);"); foreach (var p in ci.Properties) { sb.AppendLine(); EmitProp(sb, p, " ", ci.FullTypeName); } sb.AppendLine(" }"); sb.AppendLine("}"); return sb.ToString(); } private static void EmitProp(StringBuilder sb, PropInfo p, string i, string fullTypeName) { var a = $"obj.{p.Name}"; // Markerless types: write raw value only, no type marker, no PropertySkip // Matches runtime WritePropertyMarkerless — these have ExpectedTypeCode // NEVER filtered (runtime doesn't filter markerless properties either) // UseMetadata=true: markerless path NOT available — must use markered path (EmitSkip) // to match runtime WritePropertyOrSkip behavior (every property gets a type marker byte) if (IsMarkerless(p.TypeKind)) { sb.AppendLine($"{i}if (context.UseMetadata)"); sb.AppendLine($"{i}{{"); EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i + " "); sb.AppendLine($"{i}}}"); sb.AppendLine($"{i}else"); sb.AppendLine($"{i}{{"); EmitMarkerless(sb, p.TypeKind, a, i + " "); sb.AppendLine($"{i}}}"); return; } // All non-markerless properties: emit PropertyFilter guard // When filter returns false, write PropertySkip and skip the property write sb.AppendLine($"{i}if (context.HasPropertyFilter)"); sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} var fc_{p.Name} = new BinaryPropertyFilterContext(obj, typeof({fullTypeName}), \"{p.Name}\", typeof({p.TypeNameForTypeof}), static o => (({fullTypeName})o).{p.Name});"); sb.AppendLine($"{i} if (!context.PropertyFilter!(in fc_{p.Name}))"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i} goto skip_{p.Name};"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i}}}"); // Nullable value types always use markered path (need Null marker) if (IsNullableVTKind(p.TypeKind)) { sb.AppendLine($"{i}if ({a}.HasValue)"); sb.AppendLine($"{i}{{"); EmitVal(sb, Underlying(p.TypeKind), $"{a}.Value", p.TypeNameForTypeof, i + " "); sb.AppendLine($"{i}}}"); sb.AppendLine($"{i}else context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}skip_{p.Name}:;"); return; } // Non-markerless types: write WITH type marker byte (markered path) switch (p.TypeKind) { case PropertyTypeKind.String: if (p.InterningFlags == 0) sb.AppendLine($"{i}context.StringInternEligible = false;"); else sb.AppendLine($"{i}context.StringInternEligible = ({p.InterningFlags} & (1 << (int)context.Options.UseStringInterning)) != 0;"); sb.AppendLine($"{i}AcBinarySerializer.WriteStringGenerated({a}, context);"); break; case PropertyTypeKind.Complex: // Complex object: direct write bypasses GetWrapper + WriteObject pipeline entirely // when the property type has a generated writer. Falls back to WriteObjectGenerated otherwise. if (p.HasGeneratedWriter) EmitDirectObjectWrite(sb, p, a, i); else if (p.IsNullable) { sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); } else sb.AppendLine($"{i}AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); break; case PropertyTypeKind.Collection: // Direct collection write for List/T[] with Complex element types that have generated writers if (p.ElementHasGeneratedWriter && p.CollectionKind != null) EmitDirectCollectionWrite(sb, p, a, i); else if (p.IsNullable) { sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); } else sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); break; default: EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i); break; } sb.AppendLine($"{i}skip_{p.Name}:;"); } /// /// Returns true for property types that use markerless serialization in FastMode. /// These types have ExpectedTypeCode at runtime — no type marker byte, no PropertySkip for defaults. /// private static bool IsMarkerless(PropertyTypeKind k) => k switch { PropertyTypeKind.Int32 or PropertyTypeKind.Int64 or PropertyTypeKind.Int16 or PropertyTypeKind.Byte or PropertyTypeKind.UInt16 or PropertyTypeKind.UInt32 or PropertyTypeKind.UInt64 or PropertyTypeKind.Double or PropertyTypeKind.Single or PropertyTypeKind.Decimal or PropertyTypeKind.DateTime or PropertyTypeKind.Guid or PropertyTypeKind.TimeSpan or PropertyTypeKind.DateTimeOffset or PropertyTypeKind.Boolean or PropertyTypeKind.Enum => true, _ => false }; /// /// Emits raw value only — no type marker, no PropertySkip. /// Matches runtime WritePropertyMarkerless exactly. /// private static void EmitMarkerless(StringBuilder sb, PropertyTypeKind k, string a, string i) { switch (k) { case PropertyTypeKind.Int32: sb.AppendLine($"{i}context.WriteVarInt({a});"); break; case PropertyTypeKind.Int64: sb.AppendLine($"{i}context.WriteVarLong({a});"); break; case PropertyTypeKind.Double: sb.AppendLine($"{i}context.WriteRaw({a});"); break; case PropertyTypeKind.Single: sb.AppendLine($"{i}context.WriteRaw({a});"); break; case PropertyTypeKind.Decimal: sb.AppendLine($"{i}context.WriteDecimalBits({a});"); break; case PropertyTypeKind.DateTime: sb.AppendLine($"{i}context.WriteDateTimeBits({a});"); break; case PropertyTypeKind.Guid: sb.AppendLine($"{i}context.WriteGuidBits({a});"); break; case PropertyTypeKind.Byte: sb.AppendLine($"{i}context.WriteByte({a});"); break; case PropertyTypeKind.Int16: sb.AppendLine($"{i}context.WriteRaw({a});"); break; case PropertyTypeKind.UInt16: sb.AppendLine($"{i}context.WriteRaw({a});"); break; case PropertyTypeKind.UInt32: sb.AppendLine($"{i}context.WriteVarUInt({a});"); break; case PropertyTypeKind.UInt64: sb.AppendLine($"{i}context.WriteVarULong({a});"); break; case PropertyTypeKind.TimeSpan: sb.AppendLine($"{i}context.WriteRaw({a}.Ticks);"); break; case PropertyTypeKind.DateTimeOffset: sb.AppendLine($"{i}context.WriteDateTimeOffsetBits({a});"); break; case PropertyTypeKind.Boolean: sb.AppendLine($"{i}context.WriteByte({a} ? (byte)1 : (byte)0);"); break; case PropertyTypeKind.Enum: sb.AppendLine($"{i}context.WriteVarInt((int){a});"); break; } } /// /// Emits direct object write — bypasses GetWrapper + WriteObject entirely. /// Writes marker bytes + inline metadata (UseMetadata) + calls child GeneratedWriter.WriteProperties. /// IId types: guard ReferenceHandling != None (tracked in OnlyId + All). /// Non-IId types: guard ReferenceHandling == All (tracked only in All mode). /// No fallback to WriteObjectGenerated — handles both UseMetadata=true and false inline. /// private static void EmitDirectObjectWrite(StringBuilder sb, PropInfo p, string a, string i) { var writer = p.WriterClassName; var nextDepth = "depth + 1"; if (p.IsNullable) { sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else"); sb.AppendLine($"{i}{{"); } else { sb.AppendLine($"{i}{{"); } // MaxDepth check — matches WriteObjectGenerated sb.AppendLine($"{i} if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);"); sb.AppendLine($"{i} else"); sb.AppendLine($"{i} {{"); // UseMetadata: register type for first/repeated tracking via slot (no GetWrapper needed) sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && context.RegisterMetadataTypeBySlot({writer}.s_metadataSlot);"); // Inline ref tracking: guard depends on IId vs non-IId to match scan pass behavior. var refGuard = p.IsIId ? "context.ReferenceHandling != ReferenceHandlingMode.None" : "context.ReferenceHandling == ReferenceHandlingMode.All"; sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);"); sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} else"); sb.AppendLine($"{i} {{"); // RefFirst: UseMetadata → ObjectWithMetadataRefFirst + metadata, else → ObjectRefFirst sb.AppendLine($"{i} if (context.UseMetadata)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);"); sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);"); EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " "); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} else"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);"); sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} else"); sb.AppendLine($"{i} {{"); // No ref tracking: UseMetadata → ObjectWithMetadata + metadata, else → Object sb.AppendLine($"{i} if (context.UseMetadata)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);"); EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " "); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} else"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);"); sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i}}}"); } /// /// Emits inline metadata write: typeNameHash + (if first) propCount + property hashes. /// All values are compile-time constants. /// private static void EmitInlineMetadata(StringBuilder sb, int typeNameHash, int[] propertyHashes, string isFirstVar, string i) { sb.AppendLine($"{i}context.WriteRaw({typeNameHash});"); sb.AppendLine($"{i}if ({isFirstVar})"); sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} context.WriteVarUInt({(uint)propertyHashes.Length});"); foreach (var hash in propertyHashes) sb.AppendLine($"{i} context.WriteRaw({hash});"); sb.AppendLine($"{i}}}"); } /// /// Emits inline collection write for List<T> / T[] where T is a Complex type with generated writer. /// Bypasses GetWrapper + WriteArray + WriteValue per-element dispatch entirely. /// Wire format: [Array marker][VarUInt count][elem₁ marker+props][elem₂ marker+props]... /// Handles both UseMetadata=true and false inline — no fallback to WriteValueGenerated. /// private static void EmitDirectCollectionWrite(StringBuilder sb, PropInfo p, string a, string i) { var writer = p.ElementWriterClassName; if (p.IsNullable) { sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else"); sb.AppendLine($"{i}{{"); } else { sb.AppendLine($"{i}{{"); } sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Array);"); // Get count and iteration based on collection kind if (p.CollectionKind == "Array") { sb.AppendLine($"{i} var arr_{p.Name} = {a};"); sb.AppendLine($"{i} context.WriteVarUInt((uint)arr_{p.Name}.Length);"); sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 1;"); sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < arr_{p.Name}.Length; i_{p.Name}++)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} var elem_{p.Name} = arr_{p.Name}[i_{p.Name}];"); } else if (p.CollectionKind == "Counted") { sb.AppendLine($"{i} var col_{p.Name} = {a};"); sb.AppendLine($"{i} context.WriteVarUInt((uint)col_{p.Name}.Count);"); sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 1;"); sb.AppendLine($"{i} foreach (var elem_{p.Name} in col_{p.Name})"); sb.AppendLine($"{i} {{"); } else // List, IList, IReadOnlyList — Count + indexer { sb.AppendLine($"{i} var list_{p.Name} = {a};"); sb.AppendLine($"{i} context.WriteVarUInt((uint)list_{p.Name}.Count);"); sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 1;"); sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < list_{p.Name}.Count; i_{p.Name}++)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} var elem_{p.Name} = list_{p.Name}[i_{p.Name}];"); } // Per-element write var e = $"elem_{p.Name}"; sb.AppendLine($"{i} if ({e} == null) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}"); sb.AppendLine($"{i} if (nextDepth_{p.Name} > context.MaxDepth) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}"); // UseMetadata: register element type for first/repeated tracking sb.AppendLine($"{i} var isFirstMeta_e_{p.Name} = context.UseMetadata && context.RegisterMetadataTypeBySlot({writer}.s_metadataSlot);"); // Inline ref tracking var elemRefGuard = p.ElementIsIId ? "context.ReferenceHandling != ReferenceHandlingMode.None" : "context.ReferenceHandling == ReferenceHandlingMode.All"; sb.AppendLine($"{i} if ({elemRefGuard} && context.TryConsumeWritePlanEntry(out var epe_{p.Name}))"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} if (!epe_{p.Name}.IsFirst)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);"); sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} else"); sb.AppendLine($"{i} {{"); // RefFirst: UseMetadata → ObjectWithMetadataRefFirst + metadata, else → ObjectRefFirst sb.AppendLine($"{i} if (context.UseMetadata)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);"); sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);"); EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " "); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} else"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);"); sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} else"); sb.AppendLine($"{i} {{"); // No ref tracking: UseMetadata → ObjectWithMetadata + metadata, else → Object sb.AppendLine($"{i} if (context.UseMetadata)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);"); EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " "); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} else"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);"); sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i}}}"); } private static void EmitSkip(StringBuilder sb, PropertyTypeKind k, string a, string typeName, string i) { switch (k) { case PropertyTypeKind.Int32: { // Mirrors runtime WritePropertyOrSkip → WriteInt32 (TinyInt optimization) var s32 = a.Replace(".", "_"); sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else if (BinaryTypeCode.TryEncodeTinyInt({a}, out var ti_{s32})) context.WriteByte(ti_{s32});"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt({a}); }}"); break; } case PropertyTypeKind.Int64: { // Mirrors runtime WritePropertyOrSkip → WriteInt64 → WriteInt32 (int range + TinyInt) var s64 = a.Replace(".", "_"); sb.AppendLine($"{i}if ({a} == 0L) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else if ({a} >= int.MinValue && {a} <= int.MaxValue)"); sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} var iv_{s64} = (int){a};"); sb.AppendLine($"{i} if (BinaryTypeCode.TryEncodeTinyInt(iv_{s64}, out var ti_{s64})) context.WriteByte(ti_{s64});"); sb.AppendLine($"{i} else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt(iv_{s64}); }}"); sb.AppendLine($"{i}}}"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int64); context.WriteVarLong({a}); }}"); break; } case PropertyTypeKind.Boolean: sb.AppendLine($"{i}if (!{a}) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else context.WriteByte(BinaryTypeCode.True);"); break; case PropertyTypeKind.Double: sb.AppendLine($"{i}if ({a} == 0.0) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Float64); context.WriteRaw({a}); }}"); break; case PropertyTypeKind.Single: sb.AppendLine($"{i}if ({a} == 0f) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Float32); context.WriteRaw({a}); }}"); break; case PropertyTypeKind.Decimal: sb.AppendLine($"{i}if ({a} == 0m) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Decimal); context.WriteDecimalBits({a}); }}"); break; case PropertyTypeKind.DateTime: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTime); context.WriteDateTimeBits({a});"); break; case PropertyTypeKind.Guid: sb.AppendLine($"{i}if ({a} == System.Guid.Empty) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Guid); context.WriteGuidBits({a}); }}"); break; case PropertyTypeKind.Byte: sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt8); context.WriteByte({a}); }}"); break; case PropertyTypeKind.Int16: sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int16); context.WriteRaw({a}); }}"); break; case PropertyTypeKind.UInt16: sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt16); context.WriteRaw({a}); }}"); break; case PropertyTypeKind.UInt32: sb.AppendLine($"{i}if ({a} == 0U) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt32); context.WriteVarUInt({a}); }}"); break; case PropertyTypeKind.UInt64: sb.AppendLine($"{i}if ({a} == 0UL) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt64); context.WriteVarULong({a}); }}"); break; case PropertyTypeKind.Enum: var s = a.Replace(".", "_"); sb.AppendLine($"{i}var ev_{s} = (int){a};"); sb.AppendLine($"{i}if (ev_{s} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);"); sb.AppendLine($"{i}else if (BinaryTypeCode.TryEncodeTinyInt(ev_{s}, out var te_{s})) {{ context.WriteByte(BinaryTypeCode.Enum); context.WriteByte(te_{s}); }}"); sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Enum); context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt(ev_{s}); }}"); break; case PropertyTypeKind.TimeSpan: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.TimeSpan); context.WriteRaw({a}.Ticks);"); break; case PropertyTypeKind.DateTimeOffset: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTimeOffset); context.WriteDateTimeOffsetBits({a});"); break; default: sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({typeName}), context, depth);"); break; } } private static void EmitVal(StringBuilder sb, PropertyTypeKind k, string a, string typeName, string i) { switch (k) { case PropertyTypeKind.Int32: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt({a});"); break; case PropertyTypeKind.Int64: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Int64); context.WriteVarLong({a});"); break; case PropertyTypeKind.Boolean: sb.AppendLine($"{i}context.WriteByte({a} ? BinaryTypeCode.True : BinaryTypeCode.False);"); break; case PropertyTypeKind.Double: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Float64); context.WriteRaw({a});"); break; case PropertyTypeKind.Single: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Float32); context.WriteRaw({a});"); break; case PropertyTypeKind.Decimal: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Decimal); context.WriteDecimalBits({a});"); break; case PropertyTypeKind.DateTime: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTime); context.WriteDateTimeBits({a});"); break; case PropertyTypeKind.Guid: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Guid); context.WriteGuidBits({a});"); break; default: EmitSkip(sb, k, a, typeName, i); break; } } private static string GenInit(List classes) { var sb = new StringBuilder(512); sb.AppendLine("// "); sb.AppendLine("using System.Runtime.CompilerServices;"); sb.AppendLine("using AyCode.Core.Serializers.Binaries;"); sb.AppendLine(); sb.AppendLine("namespace AyCode.Core.Serializers.Generated;"); sb.AppendLine(); sb.AppendLine("internal static class AcBinaryGeneratedWritersInit"); sb.AppendLine("{"); sb.AppendLine(" [ModuleInitializer]"); sb.AppendLine(" internal static void Register()"); sb.AppendLine(" {"); foreach (var ci in classes) { var writerRef = string.IsNullOrEmpty(ci.Namespace) ? $"{ci.ClassName}_GeneratedWriter" : $"{ci.Namespace}.{ci.ClassName}_GeneratedWriter"; sb.AppendLine($" AcBinarySerializer.RegisterGeneratedWriter(typeof({ci.FullTypeName}), {writerRef}.Instance);"); } sb.AppendLine(" }"); sb.AppendLine("}"); return sb.ToString(); } /// /// Builds a flat class name for nested types: Outer_Inner_Leaf. /// For top-level types returns the simple name unchanged. /// private static string BuildFlatName(INamedTypeSymbol typeSymbol) { if (typeSymbol.ContainingType == null) return typeSymbol.Name; var parts = new List(); var current = typeSymbol; while (current != null) { parts.Add(current.Name); current = current.ContainingType; } parts.Reverse(); return string.Join("_", parts); } #region FNV-1a Hash (compile-time) private static int ComputeFnvHash(string value) { uint hash = 2166136261; for (int i = 0; i < value.Length; i++) { hash ^= value[i]; hash *= 16777619; } return (int)hash; } /// /// Computes FNV-1a hashes for all serializable properties of a child type. /// Property filtering and ordering matches runtime TypeMetadataBase exactly: /// public get+set, non-indexer, non-static, no ignore attributes, sorted (Id first if IId, then alphabetical). /// private static int[] ComputeChildPropertyHashes(ITypeSymbol resolvedType) { var propNames = new List(); foreach (var member in resolvedType.GetMembers()) { if (member is IPropertySymbol cp && cp.DeclaredAccessibility == Accessibility.Public && cp.GetMethod != null && cp.SetMethod != null && !cp.IsIndexer && !cp.IsStatic) { var hasIgnore = cp.GetAttributes().Any(a => { var name = a.AttributeClass?.Name ?? ""; return name == "JsonIgnoreAttribute" || name == "IgnoreMemberAttribute" || name == "BsonIgnoreAttribute"; }); if (hasIgnore) continue; propNames.Add(cp.Name); } } var childIsIId = resolvedType.AllInterfaces.Any(ifc => ifc.IsGenericType && ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId"); if (childIsIId) propNames.Sort((a, b) => { var ai = a == "Id" ? 0 : 1; var bi = b == "Id" ? 0 : 1; if (ai != bi) return ai.CompareTo(bi); return string.Compare(a, b, StringComparison.Ordinal); }); else propNames.Sort(StringComparer.Ordinal); return propNames.Select(ComputeFnvHash).ToArray(); } #endregion #region Type analysis private static bool IsNullableVT(ITypeSymbol t) => t is INamedTypeSymbol n && n.IsGenericType && n.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T; private static PropertyTypeKind GetKind(ITypeSymbol type) { if (type is INamedTypeSymbol n && n.IsGenericType && n.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T) return GetKindCore(n.TypeArguments[0], true); return GetKindCore(type, false); } private static PropertyTypeKind GetKindCore(ITypeSymbol type, bool nullable) { switch (type.SpecialType) { case SpecialType.System_String: return PropertyTypeKind.String; case SpecialType.System_Int32: return nullable ? PropertyTypeKind.NullableInt32 : PropertyTypeKind.Int32; case SpecialType.System_Int64: return nullable ? PropertyTypeKind.NullableInt64 : PropertyTypeKind.Int64; case SpecialType.System_Int16: return nullable ? PropertyTypeKind.NullableInt16 : PropertyTypeKind.Int16; case SpecialType.System_Byte: return nullable ? PropertyTypeKind.NullableByte : PropertyTypeKind.Byte; case SpecialType.System_UInt16: return nullable ? PropertyTypeKind.NullableUInt16 : PropertyTypeKind.UInt16; case SpecialType.System_UInt32: return nullable ? PropertyTypeKind.NullableUInt32 : PropertyTypeKind.UInt32; case SpecialType.System_UInt64: return nullable ? PropertyTypeKind.NullableUInt64 : PropertyTypeKind.UInt64; case SpecialType.System_Boolean: return nullable ? PropertyTypeKind.NullableBoolean : PropertyTypeKind.Boolean; case SpecialType.System_Single: return nullable ? PropertyTypeKind.NullableSingle : PropertyTypeKind.Single; case SpecialType.System_Double: return nullable ? PropertyTypeKind.NullableDouble : PropertyTypeKind.Double; case SpecialType.System_Decimal: return nullable ? PropertyTypeKind.NullableDecimal : PropertyTypeKind.Decimal; case SpecialType.System_DateTime: return nullable ? PropertyTypeKind.NullableDateTime : PropertyTypeKind.DateTime; default: break; } var fn = type.ToDisplayString(); if (fn == "System.Guid") return nullable ? PropertyTypeKind.NullableGuid : PropertyTypeKind.Guid; if (fn == "System.TimeSpan") return nullable ? PropertyTypeKind.NullableTimeSpan : PropertyTypeKind.TimeSpan; if (fn == "System.DateTimeOffset") return nullable ? PropertyTypeKind.NullableDateTimeOffset : PropertyTypeKind.DateTimeOffset; if (type.TypeKind == TypeKind.Enum) return nullable ? PropertyTypeKind.NullableEnum : PropertyTypeKind.Enum; if (type is IArrayTypeSymbol) return PropertyTypeKind.Collection; if (type is INamedTypeSymbol nt && nt.AllInterfaces.Any(iface => iface.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T)) return PropertyTypeKind.Collection; if (type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct) return PropertyTypeKind.Complex; return PropertyTypeKind.Unknown; } /// /// Extracts the element type T from List<T>, T[], IList<T>, IEnumerable<T>. /// Returns null if the element type cannot be determined. /// private static ITypeSymbol? GetCollectionElementType(ITypeSymbol type) { // T[] → element type if (type is IArrayTypeSymbol arrayType) return arrayType.ElementType; // Generic collections: List, IList, ICollection, IEnumerable if (type is INamedTypeSymbol namedType && namedType.IsGenericType) { // Direct: List, HashSet, etc. — first type argument var iface = namedType.AllInterfaces .FirstOrDefault(i => i.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T); if (iface != null) return iface.TypeArguments[0]; } return null; } private static bool IsNullableVTKind(PropertyTypeKind k) => k >= PropertyTypeKind.NullableInt32; private static PropertyTypeKind Underlying(PropertyTypeKind k) => k switch { PropertyTypeKind.NullableInt32 => PropertyTypeKind.Int32, PropertyTypeKind.NullableInt64 => PropertyTypeKind.Int64, PropertyTypeKind.NullableInt16 => PropertyTypeKind.Int16, PropertyTypeKind.NullableByte => PropertyTypeKind.Byte, PropertyTypeKind.NullableUInt16 => PropertyTypeKind.UInt16, PropertyTypeKind.NullableUInt32 => PropertyTypeKind.UInt32, PropertyTypeKind.NullableUInt64 => PropertyTypeKind.UInt64, PropertyTypeKind.NullableBoolean => PropertyTypeKind.Boolean, PropertyTypeKind.NullableSingle => PropertyTypeKind.Single, PropertyTypeKind.NullableDouble => PropertyTypeKind.Double, PropertyTypeKind.NullableDecimal => PropertyTypeKind.Decimal, PropertyTypeKind.NullableDateTime => PropertyTypeKind.DateTime, PropertyTypeKind.NullableDateTimeOffset => PropertyTypeKind.DateTimeOffset, PropertyTypeKind.NullableTimeSpan => PropertyTypeKind.TimeSpan, PropertyTypeKind.NullableGuid => PropertyTypeKind.Guid, PropertyTypeKind.NullableEnum => PropertyTypeKind.Enum, _ => PropertyTypeKind.Unknown }; #endregion } internal sealed class SerializableClassInfo { public string Namespace { get; } public string ClassName { get; } public string FullTypeName { get; } public List Properties { get; } /// FNV-1a hash of ClassName (matches runtime SourceType.Name hash) public int TypeNameHash { get; } /// FNV-1a hash of each property name, in property order public int[] PropertyNameHashes { get; } public SerializableClassInfo(string ns, string cn, string ftn, List p, int typeNameHash, int[] propertyNameHashes) { Namespace = ns; ClassName = cn; FullTypeName = ftn; Properties = p; TypeNameHash = typeNameHash; PropertyNameHashes = propertyNameHashes; } } internal sealed class PropInfo { public string Name { get; } public string TypeName { get; } /// /// Type name safe for typeof() — nullable ref type annotation stripped (typeof(T?) invalid for ref types). /// public string TypeNameForTypeof { get; } public PropertyTypeKind TypeKind { get; } public bool IsNullable { get; } /// /// Pre-computed interning flags matching runtime BinaryPropertyAccessorBase._interningFlags. /// Bit layout: bit N = eligible when StringInterningMode == N. /// None=0 → bit 0 never set. Attribute=1 → bit 1. All=2 → bit 2. /// No attr: 0b100 (4), [AcStringIntern(true)]: 0b110 (6), [AcStringIntern(false)]: 0b000 (0). /// public int InterningFlags { get; } /// True if the Complex property type has [AcBinarySerializable] → has a generated writer. public bool HasGeneratedWriter { get; } /// True if the Complex property type implements IId<T> → needs ref tracking in write pass. public bool IsIId { get; } /// Generated writer class name, e.g. "SharedTag_GeneratedWriter". Only set when HasGeneratedWriter. public string? WriterClassName { get; } // Collection element metadata — set when TypeKind == Collection and element type is Complex with generated writer /// Element type kind for collection properties. Only meaningful when TypeKind == Collection. public PropertyTypeKind ElementKind { get; } /// True if collection element type has [AcBinarySerializable]. public bool ElementHasGeneratedWriter { get; } /// True if collection element type implements IId<T>. public bool ElementIsIId { get; } /// Generated writer class name for collection element type. public string? ElementWriterClassName { get; } /// Collection type: "List", "Array", or null (unknown — fallback to runtime). public string? CollectionKind { get; } /// Full element type name for generated code (e.g. "SharedTag"). public string? ElementFullTypeName { get; } // UseMetadata inline hash-ek (Complex/Collection child típushoz) /// FNV-1a hash of child type name (Complex property). Only set when HasGeneratedWriter. public int ChildTypeNameHash { get; } /// FNV-1a hashes of child type's properties. Only set when HasGeneratedWriter. public int[]? ChildPropertyHashes { get; } /// FNV-1a hash of collection element type name. Only set when ElementHasGeneratedWriter. public int ElementTypeNameHash { get; } /// FNV-1a hashes of collection element type's properties. Only set when ElementHasGeneratedWriter. public int[]? ElementPropertyHashes { get; } public PropInfo(string n, string tn, string tnForTypeof, PropertyTypeKind tk, bool nullable, bool? stringInternAttr = null, bool hasGeneratedWriter = false, bool isIId = false, string? writerClassName = null, PropertyTypeKind elementKind = PropertyTypeKind.Unknown, bool elementHasGenWriter = false, bool elementIsIId = false, string? elementWriterClassName = null, string? collectionKind = null, string? elementFullTypeName = null, int childTypeNameHash = 0, int[]? childPropertyHashes = null, int elementTypeNameHash = 0, int[]? elementPropertyHashes = null) { Name = n; TypeName = tn; TypeNameForTypeof = tnForTypeof; TypeKind = tk; IsNullable = nullable; HasGeneratedWriter = hasGeneratedWriter; IsIId = isIId; WriterClassName = writerClassName; ElementKind = elementKind; ElementHasGeneratedWriter = elementHasGenWriter; ElementIsIId = elementIsIId; ElementWriterClassName = elementWriterClassName; CollectionKind = collectionKind; ElementFullTypeName = elementFullTypeName; ChildTypeNameHash = childTypeNameHash; ChildPropertyHashes = childPropertyHashes; ElementTypeNameHash = elementTypeNameHash; ElementPropertyHashes = elementPropertyHashes; // Mirror runtime _interningFlags computation from BinaryPropertyAccessorBase int flags = 0; if (stringInternAttr == true) flags |= (1 << 1); // Attribute bit if (stringInternAttr != false) flags |= (1 << 2); // All bit InterningFlags = flags; } } internal enum PropertyTypeKind { Unknown, String, Int32, Int64, Int16, Byte, UInt16, UInt32, UInt64, Boolean, Single, Double, Decimal, DateTime, DateTimeOffset, TimeSpan, Guid, Enum, Collection, Complex, NullableInt32, NullableInt64, NullableInt16, NullableByte, NullableUInt16, NullableUInt32, NullableUInt64, NullableBoolean, NullableSingle, NullableDouble, NullableDecimal, NullableDateTime, NullableDateTimeOffset, NullableTimeSpan, NullableGuid, NullableEnum }