using System.Collections.Generic;
using System.Text;
namespace AyCode.Core.Serializers.SourceGenerator;
///
/// Reader-side emit pass: generates the IGeneratedBinaryReader implementation for each
/// [AcBinarySerializable] type. Emits ReadProperties (inline property reads with marker
/// dispatch) and ReadObject (entry point with cache-index registration).
///
/// Sub-passes:
///
/// - EmitReadProp — per-property read emit (markerless + markered variants).
/// - EmitReadString — H2Q6 string-tier marker dispatch (FixStrAscii + tier-tables +
/// intern cases gated by EnableInternStringFeature).
/// - EmitReadComplex — Object / ObjectRef* / FixObj-slot dispatch for IId-typed children.
/// - EmitReadCollection / EmitReadCollectionInline / EmitReadCollectionElement /
/// EmitReadNonComplexCollectionElement — collection-shape inline reading.
/// - EmitReadDictionary / EmitReadDictElement — dict-shape inline reading.
/// - EmitReadMarkeredValue / EmitReadMarkeredValueForKind — primitive value-with-marker reads.
/// - EmitReadMarkerless — markerless primitive reads (FastMode + per-property markerless types).
///
///
public partial class AcBinarySourceGenerator
{
#region Reader Code Generation
///
/// Generates the IGeneratedBinaryReader implementation for a type.
/// Phase 1: handles markerless path (no UseMetadata). UseMetadata/ChainMode → runtime fallback.
/// Eliminates: GetWrapper dictionary lookup, CreateInstance delegate, property setter delegates,
/// AccessorType switch dispatch, ReadValue dispatch table.
///
private static string GenReader(SerializableClassInfo ci)
{
var sb = new StringBuilder(4096);
sb.AppendLine("// ");
sb.AppendLine("#nullable enable");
sb.AppendLine("using System.Runtime.CompilerServices;");
sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
sb.AppendLine();
if (!string.IsNullOrEmpty(ci.Namespace))
sb.AppendLine($"namespace {ci.Namespace};");
sb.AppendLine();
sb.AppendLine($"internal sealed class {ci.ClassName}_GeneratedReader : IGeneratedBinaryReader");
sb.AppendLine("{");
sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedReader Instance = new();");
sb.AppendLine();
// ReadProperties — reads all properties into an existing instance (mirrors WriteProperties)
// No depth safety net on deserialize: wire format is linear + finite, the serializer-side counter
// already prevents pathological depth in well-formed payloads.
sb.AppendLine(" public void ReadProperties(object value, AcBinaryDeserializer.BinaryDeserializationContext context)");
sb.AppendLine(" where TInput : struct, IBinaryInputBase");
sb.AppendLine(" {");
sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
// Emit property reads — markerless for primitive types, markered for the rest
foreach (var p in ci.Properties)
{
sb.AppendLine();
EmitReadProp(sb, p, " ", ci.EnableMetadata, ci.EnableInternString);
}
sb.AppendLine(" }");
sb.AppendLine();
// ReadObject — IGeneratedBinaryReader implementation (delegates to ReadProperties)
sb.AppendLine(" public object? ReadObject(AcBinaryDeserializer.BinaryDeserializationContext context, int cacheIndex)");
sb.AppendLine(" where TInput : struct, IBinaryInputBase");
sb.AppendLine(" {");
sb.AppendLine($" var obj = new {ci.FullTypeName}();");
sb.AppendLine(" if (cacheIndex >= 0)");
sb.AppendLine(" context.RegisterInternedValueAt(cacheIndex, obj);");
sb.AppendLine(" ReadProperties(obj, context);");
sb.AppendLine(" return obj;");
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}
///
/// Emits inline read code for a single property.
/// Markerless types: read raw value directly (no type code in stream).
/// Markered types: read type code byte, then dispatch.
/// Mirrors the serializer's EmitProp symmetry.
///
private static void EmitReadProp(StringBuilder sb, PropInfo p, string i, bool enableMetadata, bool enableInternString)
{
var a = $"obj.{p.Name}";
// Markerless types: read raw value directly — mirrors EmitMarkerless in writer
if (IsMarkerless(p.TypeKind))
{
if (p.TypeKind == PropertyTypeKind.Enum)
sb.AppendLine($"{i}{{ var ev = context.ReadVarInt(); {a} = Unsafe.As(ref ev); }}");
else
EmitReadMarkerless(sb, p.TypeKind, a, i);
return;
}
// ACCORE-BIN-T-K9M3 — caller-driven string marker dispatch. SGen-emit reads the marker byte
// locally + handles FastWire on a separate branch; BinaryDeserializationContext.TryReadStringProperty
// decodes every non-interning marker (FixStrAscii / StringAscii / StringSmall/Medium/Big / Null /
// StringEmpty) in one inlinable body. The 3 interning markers go through TryReadStringColdPath
// (AggressiveOptimization, Tier-1 direct). enableInternString gates the `|| TryReadStringColdPath`
// emit: interning-enabled types get the short-circuit; non-interning types omit the cold call
// entirely — the writer never produces interning markers for them, so TryReadStringProperty alone
// is total. PropertySkip / unknown → TryReadStringProperty returns false → property left at
// default (don't-touch contract preserved).
if (p.TypeKind == PropertyTypeKind.String)
{
sb.AppendLine($"{i}if (context.FastWire)");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} {a} = context.ReadStringUtf16Markerless()!;");
sb.AppendLine($"{i}}}");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var tc_{p.Name} = context.ReadByte();");
sb.AppendLine($"{i} string? v_{p.Name};");
if (enableInternString)
sb.AppendLine($"{i} if (context.TryReadStringProperty(tc_{p.Name}, out v_{p.Name}) || context.TryReadStringColdPath(tc_{p.Name}, out v_{p.Name}))");
else
sb.AppendLine($"{i} if (context.TryReadStringProperty(tc_{p.Name}, out v_{p.Name}))");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} {a} = v_{p.Name}!;");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i}}}");
return;
}
// Markered types: read type code, then dispatch
var tc = $"tc_{p.Name}";
sb.AppendLine($"{i}var {tc} = context.ReadByte();");
// PropertySkip → leave default
sb.AppendLine($"{i}if ({tc} != BinaryTypeCode.PropertySkip)");
sb.AppendLine($"{i}{{");
// Nullable value types
if (IsNullableVTKind(p.TypeKind))
{
sb.AppendLine($"{i} if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
sb.AppendLine($"{i} else");
sb.AppendLine($"{i} {{");
EmitReadMarkeredValue(sb, Underlying(p.TypeKind), a, tc, i + " ", p, nullable: true);
sb.AppendLine($"{i} }}");
}
else
{
switch (p.TypeKind)
{
case PropertyTypeKind.String:
EmitReadString(sb, a, tc, i + " ", enableInternString);
break;
case PropertyTypeKind.Complex:
EmitReadComplex(sb, p, a, tc, i + " ");
break;
case PropertyTypeKind.Collection:
EmitReadCollection(sb, p, a, tc, i + " ", enableInternString);
break;
case PropertyTypeKind.Dictionary:
EmitReadDictionary(sb, p, a, tc, i + " ", enableInternString);
break;
default:
// Unknown markered type (char, sbyte, etc.) — rewind + runtime fallback
sb.AppendLine($"{i} context._position--;");
if (p.IsNullable)
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
else
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
break;
}
}
sb.AppendLine($"{i}}}");
}
///
/// Emits raw value read — no type code in stream. Mirrors EmitMarkerless exactly.
///
private static void EmitReadMarkerless(StringBuilder sb, PropertyTypeKind k, string a, string i)
{
switch (k)
{
case PropertyTypeKind.Int32: sb.AppendLine($"{i}{a} = context.ReadVarInt();"); break;
case PropertyTypeKind.Int64: sb.AppendLine($"{i}{a} = context.ReadVarLong();"); break;
case PropertyTypeKind.Double: sb.AppendLine($"{i}{a} = context.ReadDoubleUnsafe();"); break;
case PropertyTypeKind.Single: sb.AppendLine($"{i}{a} = context.ReadSingleUnsafe();"); break;
case PropertyTypeKind.Decimal: sb.AppendLine($"{i}{a} = context.ReadDecimalUnsafe();"); break;
case PropertyTypeKind.DateTime: sb.AppendLine($"{i}{a} = context.ReadDateTimeUnsafe();"); break;
case PropertyTypeKind.Guid: sb.AppendLine($"{i}{a} = context.ReadGuidUnsafe();"); break;
case PropertyTypeKind.Byte: sb.AppendLine($"{i}{a} = context.ReadByte();"); break;
case PropertyTypeKind.Int16: sb.AppendLine($"{i}{a} = context.ReadInt16Unsafe();"); break;
case PropertyTypeKind.UInt16: sb.AppendLine($"{i}{a} = context.ReadUInt16Unsafe();"); break;
case PropertyTypeKind.UInt32: sb.AppendLine($"{i}{a} = context.ReadVarUInt();"); break;
case PropertyTypeKind.UInt64: sb.AppendLine($"{i}{a} = context.ReadVarULong();"); break;
case PropertyTypeKind.TimeSpan: sb.AppendLine($"{i}{a} = new System.TimeSpan(context.ReadRaw());"); break;
case PropertyTypeKind.DateTimeOffset: sb.AppendLine($"{i}{a} = context.ReadDateTimeOffsetUnsafe();"); break;
case PropertyTypeKind.Boolean: sb.AppendLine($"{i}{a} = context.ReadByte() != 0;"); break;
}
}
///
/// Emits inline string read from type code. Handles all H2Q6 (v3 wire format) string markers:
/// FixStr (short-form universal, 135-166), String (long-form universal, 167),
/// StringUtf16 (FastWire marker, 91),
/// StringInternFirstSmall/Medium (interning tiers, 104/105),
/// StringInterned (cache ref, 92), StringEmpty (93), Null.
///
/// FixStr is checked first as the hot path for short strings; non-ASCII
/// tier markers carry both charLen and utf8Len in fixed-width headers (1-pass decode).
///
private static void EmitReadString(StringBuilder sb, string a, string tc, string i, bool enableInternString)
{
// FixStr is the hot path — short-form universal marker with charLength in the marker.
sb.AppendLine($"{i}if (BinaryTypeCode.IsFixStr({tc}))");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} {a} = context.ReadUniversalFixStr({tc});");
sb.AppendLine($"{i}}}");
// Switch gives O(1) dispatch via JIT jump table for the remaining markers.
sb.AppendLine($"{i}else switch ({tc})");
sb.AppendLine($"{i}{{");
// Interning case (2nd+ occurrence ref) — only emit when EnableInternStringFeature is enabled
// on this type. When disabled, the writer never emits StringInterned markers for this type's
// properties, so the reader doesn't need to handle them. ACCORE-BIN-T-K9M3 Phase C.
if (enableInternString)
{
sb.AppendLine($"{i} case BinaryTypeCode.StringInterned:");
sb.AppendLine($"{i} {a} = context.GetInternedString((int)context.ReadVarUInt());");
sb.AppendLine($"{i} break;");
}
// StringUtf16 marker + String. Wire-decode body is shared with the runtime path
// (TypeReaderTable + cross-type populate) — see context.ReadStringUtf16Marker()
// and ReadUniversalLongString.
// These markers are feature-independent: writer emits them on any string property regardless of
// intern setting (intern is opt-in per-property via [AcStringIntern] + InternBit).
sb.AppendLine($"{i} case BinaryTypeCode.StringUtf16:");
sb.AppendLine($"{i} {a} = context.ReadStringUtf16Marker();");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} case BinaryTypeCode.String:");
sb.AppendLine($"{i} {a} = context.ReadUniversalLongString();");
sb.AppendLine($"{i} break;");
// Interning first-occurrence cases — see comment above.
if (enableInternString)
{
sb.AppendLine($"{i} case BinaryTypeCode.StringInternFirstSmall:");
sb.AppendLine($"{i} {a} = context.ReadAndRegisterInternedStringSmall();");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} case BinaryTypeCode.StringInternFirstMedium:");
sb.AppendLine($"{i} {a} = context.ReadAndRegisterInternedStringMedium();");
sb.AppendLine($"{i} break;");
}
sb.AppendLine($"{i} case BinaryTypeCode.Null:");
sb.AppendLine($"{i} {a} = null;");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} case BinaryTypeCode.StringEmpty:");
sb.AppendLine($"{i} {a} = string.Empty;");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i}}}");
}
///
/// Emits inline read for a Complex property.
/// SGen reader only runs in non-metadata mode → ObjectWithMetadata never appears.
/// Compile-time ChildNeedsRefScan eliminates ObjectRefFirst/ObjectRef when provably unused.
/// Non-nullable + no ref → ZERO branches (tc consumed but ignored).
/// No SGen → runtime fallback via ReadValueGenerated.
///
private static void EmitReadComplex(StringBuilder sb, PropInfo p, string a, string tc, string i)
{
if (!p.HasGeneratedWriter)
{
// No SGen reader — runtime fallback (rewind + ReadValueGenerated)
if (p.IsNullable)
{
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} context._position--;");
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
sb.AppendLine($"{i}}}");
}
else
{
sb.AppendLine($"{i}context._position--;");
sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
}
return;
}
var reader = p.WriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader");
var cast = $"({p.TypeNameForTypeof})";
// Ref-aware switch decision routed through RefAwareEmitPredicate — single source of truth shared
// with the writer-side EmitDirectCollectionWrite + the sibling EmitReadCollectionElement. The
// decision depends EXCLUSIVELY on the child compile-time fact `ChildNeedsRefScan` — the parent
// EnableRefHandlingFeature flag is NOT a factor here (it governs only the parent's SELF-tracking
// emit in the scan pass, not the marker dispatch for child property reads). Asymmetry-bug fix:
// see AcBinarySerializerIIdReferenceTests.Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction.
if (!RefAwareEmitPredicate.ChildEmitsRefMarker(p))
{
// Compile-time proven: child never tracked → only Object (+ Null for nullable) in stream
// Inline: parent creates instance, calls ReadProperties directly (mirrors EmitDirectObjectWrite)
// FixObj slot bytes (0..SlotCount-1) are also valid markers here — populate slot cache
// to keep _nextRuntimeSlot in sync with the serializer's _nextTypeSlot counter.
if (p.IsNullable)
{
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i}}}");
}
else
{
// ZERO branches — tc is always Object or FixObj
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i}}}");
}
}
else
{
// Ref tracking possible — switch on tc (Object / ObjectRefFirst / [Null] / ObjectRef /