AyCode.Core/AyCode.Core.Serializers.Sou.../AcBinarySourceGenerator.Mod...

310 lines
20 KiB
C#

using System.Collections.Generic;
namespace AyCode.Core.Serializers.SourceGenerator;
// Source-generator model types — pure POCO data carriers describing a `[AcBinarySerializable]` type
// and its serializable properties. Consumed by all emit / diagnostics / analysis passes in the partial
// `AcBinarySourceGenerator` class (see siblings `*.GenWriter.cs`, `*.GenReader.cs`, etc.).
internal sealed class SerializableClassInfo
{
public string Namespace { get; }
public string ClassName { get; }
public string FullTypeName { get; }
public List<PropInfo> Properties { get; }
/// <summary>True if this type implements IId&lt;T&gt;</summary>
public bool IsIId { get; }
/// <summary>The Id type name ("int", "long", "System.Guid") if IsIId, null otherwise</summary>
public string? IdTypeName { get; }
/// <summary>True if EnableRefHandlingFeature is enabled — controls non-IId All mode tracking code emission.</summary>
public bool EnableRefHandling { get; }
/// <summary>FNV-1a hash of ClassName (matches runtime SourceType.Name hash)</summary>
public int TypeNameHash { get; }
/// <summary>FNV-1a hash of each property name, in property order</summary>
public int[] PropertyNameHashes { get; }
/// <summary>When false, skip inline metadata and use markerless property write for this type.</summary>
public bool EnableMetadata { get; }
/// <summary>True if EnablePropertyFilterFeature is enabled — controls per-property HasPropertyFilter
/// guard emission in WriteProperties / ScanObject. When false, the filter check is omitted entirely
/// → leaner generated code on the hot path (typical for high-throughput types that never use a filter).</summary>
public bool EnablePropertyFilter { get; }
/// <summary>True if EnablePolymorphDetectFeature is enabled — controls <c>ObjectWithTypeName</c> + AQN
/// prefix emit on <c>System.Object</c>-declared properties. When false, the prefix is suppressed
/// AND ACBIN002 fires at build time if such a property exists on this type (guarding against silent
/// wire corruption). Opt-out is intentional: dev guarantees no polymorphic <c>object</c> property
/// will be serialized on this type, or all such properties are excluded via <c>[AcBinaryIgnore]</c>.</summary>
public bool EnablePolymorphDetect { get; }
/// <summary>True if EnableInternStringFeature is enabled — controls whether the SGen-emitted reader
/// contains <c>StringInterned</c>, <c>StringInternFirstSmall</c>, <c>StringInternFirstMedium</c> case-ágakat.
/// When false, those cases are omitted (the writer doesn't emit those markers when intern is off,
/// so the reader doesn't need to handle them). Leaner switch dispatch (~30% fewer string cases) +
/// smaller IL → faster cold-start JIT + smaller AOT publish.</summary>
public bool EnableInternString { get; }
/// <summary>When true, type subtree has IId types needing scan (active in OnlyId + All).</summary>
public bool NeedsIdScan { get; }
/// <summary>When true, type subtree has non-IId ref tracking (active only in All mode).</summary>
public bool NeedsAllRefScan { get; }
/// <summary>When true, type subtree needs string interning scan.</summary>
public bool NeedsInternScan { get; }
/// <summary>Derived: NeedsIdScan || NeedsAllRefScan.</summary>
public bool NeedsRefScan => NeedsIdScan || NeedsAllRefScan;
/// <summary>Derived: any scan axis active.</summary>
public bool NeedsScan => NeedsIdScan || NeedsAllRefScan || NeedsInternScan;
public SerializableClassInfo(string ns, string cn, string ftn, List<PropInfo> p, bool isIId, string? idTypeName, bool enableRefHandling, int typeNameHash, int[] propertyNameHashes, bool enableMetadata, bool enablePropertyFilter, bool enablePolymorphDetect, bool enableInternString, bool needsIdScan, bool needsAllRefScan, bool needsInternScan)
{ Namespace = ns; ClassName = cn; FullTypeName = ftn; Properties = p; IsIId = isIId; IdTypeName = idTypeName; EnableRefHandling = enableRefHandling; TypeNameHash = typeNameHash; PropertyNameHashes = propertyNameHashes; EnableMetadata = enableMetadata; EnablePropertyFilter = enablePropertyFilter; EnablePolymorphDetect = enablePolymorphDetect; EnableInternString = enableInternString; NeedsIdScan = needsIdScan; NeedsAllRefScan = needsAllRefScan; NeedsInternScan = needsInternScan; }
}
internal sealed class PropInfo
{
public string Name { get; }
public string TypeName { get; }
/// <summary>
/// Type name safe for typeof() — nullable ref type annotation stripped (typeof(T?) invalid for ref types).
/// </summary>
public string TypeNameForTypeof { get; }
public PropertyTypeKind TypeKind { get; }
public bool IsNullable { get; }
/// <summary>
/// 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).
/// </summary>
public int InterningFlags { get; }
/// <summary>True when declared property type is System.Object. Runtime type dispatch needed.</summary>
public bool IsObjectDeclaredType { get; }
/// <summary>True if the Complex property type has [AcBinarySerializable] → has a generated writer.</summary>
public bool HasGeneratedWriter { get; }
/// <summary>True if the Complex property type implements IId&lt;T&gt; → needs ref tracking in write pass.</summary>
public bool IsIId { get; }
/// <summary>Generated writer class name, e.g. "SharedTag_GeneratedWriter". Only set when HasGeneratedWriter.</summary>
public string? WriterClassName { get; }
/// <summary>Id type name ("int", "long", "System.Guid") for IId child types. Null if not IId.</summary>
public string? IdTypeName { get; }
// Collection element metadata — set when TypeKind == Collection and element type is Complex with generated writer
/// <summary>Element type kind for collection properties. Only meaningful when TypeKind == Collection.</summary>
public PropertyTypeKind ElementKind { get; }
/// <summary>True if collection element type has [AcBinarySerializable].</summary>
public bool ElementHasGeneratedWriter { get; }
/// <summary>True if collection element type implements IId&lt;T&gt;.</summary>
public bool ElementIsIId { get; }
/// <summary>Generated writer class name for collection element type.</summary>
public string? ElementWriterClassName { get; }
/// <summary>Id type name for collection element IId types. Null if not IId.</summary>
public string? ElementIdTypeName { get; }
/// <summary>Collection type: "List", "Array", "IndexedCollection", "Counted", or null (unknown — fallback to runtime).</summary>
public string? CollectionKind { get; }
/// <summary>Full element type name for generated code (e.g. "SharedTag").</summary>
public string? ElementFullTypeName { get; }
/// <summary>Add method for Counted concrete collections. null → List&lt;T&gt;.Add(), "Add" → HashSet/SortedSet, "Enqueue" → Queue, "AddLast" → LinkedList.</summary>
public string? CollectionAddMethod { get; }
/// <summary>True if the concrete Counted collection has a capacity constructor (HashSet, Queue).</summary>
public bool CollectionHasCapacityCtor { get; }
// Dictionary metadata — set when TypeKind == Dictionary
/// <summary>Key type kind for dictionary properties.</summary>
public PropertyTypeKind DictKeyKind { get; }
/// <summary>Value type kind for dictionary properties.</summary>
public PropertyTypeKind DictValueKind { get; }
/// <summary>Key type name for generated code.</summary>
public string? DictKeyTypeName { get; }
/// <summary>Value type name for generated code.</summary>
public string? DictValueTypeName { get; }
/// <summary>True if dictionary value type has [AcBinarySerializable].</summary>
public bool DictValueHasGeneratedWriter { get; }
/// <summary>Generated writer class name for dictionary value type.</summary>
public string? DictValueWriterClassName { get; }
/// <summary>True if dictionary value type implements IId&lt;T&gt;.</summary>
public bool DictValueIsIId { get; }
/// <summary>When false, dict value type skips inline metadata.</summary>
public bool DictValueEnableMetadata { get; }
/// <summary>FNV-1a hash of dict value type name.</summary>
public int DictValueTypeNameHash { get; }
/// <summary>FNV-1a hashes of dict value type's properties.</summary>
public int[]? DictValuePropertyHashes { get; }
/// <summary>When true, dict value subtree has IId types needing scan.</summary>
public bool DictValueNeedsIdScan { get; }
/// <summary>When true, dict value subtree has non-IId ref tracking.</summary>
public bool DictValueNeedsAllRefScan { get; }
/// <summary>When true, dict value subtree needs string interning scan.</summary>
public bool DictValueNeedsInternScan { get; }
/// <summary>Derived: DictValueNeedsIdScan || DictValueNeedsAllRefScan.</summary>
public bool DictValueNeedsRefScan => DictValueNeedsIdScan || DictValueNeedsAllRefScan;
/// <summary>Derived: any dict value scan axis active.</summary>
public bool DictValueNeedsScan => DictValueNeedsIdScan || DictValueNeedsAllRefScan || DictValueNeedsInternScan;
// UseMetadata inline hash-ek (Complex/Collection child típushoz)
/// <summary>FNV-1a hash of child type name (Complex property). Only set when HasGeneratedWriter.</summary>
public int ChildTypeNameHash { get; }
/// <summary>FNV-1a hashes of child type's properties. Only set when HasGeneratedWriter.</summary>
public int[]? ChildPropertyHashes { get; }
/// <summary>FNV-1a hash of collection element type name. Only set when ElementHasGeneratedWriter.</summary>
public int ElementTypeNameHash { get; }
/// <summary>FNV-1a hashes of collection element type's properties. Only set when ElementHasGeneratedWriter.</summary>
public int[]? ElementPropertyHashes { get; }
/// <summary>When false, child Complex type skips inline metadata in generated code.</summary>
public bool ChildEnableMetadata { get; }
/// <summary>When false, collection element type skips inline metadata in generated code.</summary>
public bool ElementEnableMetadata { get; }
/// <summary>When true, child subtree has IId types needing scan (active in OnlyId + All).</summary>
public bool ChildNeedsIdScan { get; }
/// <summary>When true, child subtree has non-IId ref tracking (active only in All mode).</summary>
public bool ChildNeedsAllRefScan { get; }
/// <summary>When true, child subtree needs string interning scan.</summary>
public bool ChildNeedsInternScan { get; }
/// <summary>Derived: ChildNeedsIdScan || ChildNeedsAllRefScan.</summary>
public bool ChildNeedsRefScan => ChildNeedsIdScan || ChildNeedsAllRefScan;
/// <summary>Derived: any child scan axis active.</summary>
public bool ChildNeedsScan => ChildNeedsIdScan || ChildNeedsAllRefScan || ChildNeedsInternScan;
/// <summary>When true, element subtree has IId types needing scan (active in OnlyId + All).</summary>
public bool ElementNeedsIdScan { get; }
/// <summary>When true, element subtree has non-IId ref tracking (active only in All mode).</summary>
public bool ElementNeedsAllRefScan { get; }
/// <summary>When true, element subtree needs string interning scan.</summary>
public bool ElementNeedsInternScan { get; }
/// <summary>Derived: ElementNeedsIdScan || ElementNeedsAllRefScan.</summary>
public bool ElementNeedsRefScan => ElementNeedsIdScan || ElementNeedsAllRefScan;
/// <summary>Derived: any element scan axis active.</summary>
public bool ElementNeedsScan => ElementNeedsIdScan || ElementNeedsAllRefScan || ElementNeedsInternScan;
public PropInfo(string n, string tn, string tnForTypeof, PropertyTypeKind tk, bool nullable,
bool isObjectDeclaredType = false,
bool? stringInternAttr = null, bool hasGeneratedWriter = false, bool isIId = false, string? writerClassName = null, string? idTypeName = null,
PropertyTypeKind elementKind = PropertyTypeKind.Unknown, bool elementHasGenWriter = false, bool elementIsIId = false,
string? elementWriterClassName = null, string? elementIdTypeName = null, string? collectionKind = null, string? elementFullTypeName = null,
string? collectionAddMethod = null, bool collectionHasCapacityCtor = false,
PropertyTypeKind dictKeyKind = PropertyTypeKind.Unknown, PropertyTypeKind dictValueKind = PropertyTypeKind.Unknown,
string? dictKeyTypeName = null, string? dictValueTypeName = null,
bool dictValueHasGeneratedWriter = false, string? dictValueWriterClassName = null,
bool dictValueIsIId = false, bool dictValueEnableMetadata = true,
int dictValueTypeNameHash = 0, int[]? dictValuePropertyHashes = null,
bool dictValueNeedsIdScan = true, bool dictValueNeedsAllRefScan = true, bool dictValueNeedsInternScan = true,
int childTypeNameHash = 0, int[]? childPropertyHashes = null,
int elementTypeNameHash = 0, int[]? elementPropertyHashes = null,
bool childEnableMetadata = true, bool elementEnableMetadata = true,
bool childNeedsIdScan = true, bool childNeedsAllRefScan = true, bool childNeedsInternScan = true,
bool elementNeedsIdScan = true, bool elementNeedsAllRefScan = true, bool elementNeedsInternScan = true)
{
Name = n;
TypeName = tn;
TypeNameForTypeof = tnForTypeof;
TypeKind = tk;
IsNullable = nullable;
IsObjectDeclaredType = isObjectDeclaredType;
HasGeneratedWriter = hasGeneratedWriter;
IsIId = isIId;
WriterClassName = writerClassName;
IdTypeName = idTypeName;
ElementKind = elementKind;
ElementHasGeneratedWriter = elementHasGenWriter;
ElementIsIId = elementIsIId;
ElementWriterClassName = elementWriterClassName;
ElementIdTypeName = elementIdTypeName;
CollectionKind = collectionKind;
ElementFullTypeName = elementFullTypeName;
CollectionAddMethod = collectionAddMethod;
CollectionHasCapacityCtor = collectionHasCapacityCtor;
DictKeyKind = dictKeyKind;
DictValueKind = dictValueKind;
DictKeyTypeName = dictKeyTypeName;
DictValueTypeName = dictValueTypeName;
DictValueHasGeneratedWriter = dictValueHasGeneratedWriter;
DictValueWriterClassName = dictValueWriterClassName;
DictValueIsIId = dictValueIsIId;
DictValueEnableMetadata = dictValueEnableMetadata;
DictValueTypeNameHash = dictValueTypeNameHash;
DictValuePropertyHashes = dictValuePropertyHashes;
DictValueNeedsIdScan = dictValueNeedsIdScan;
DictValueNeedsAllRefScan = dictValueNeedsAllRefScan;
DictValueNeedsInternScan = dictValueNeedsInternScan;
ChildTypeNameHash = childTypeNameHash;
ChildPropertyHashes = childPropertyHashes;
ElementTypeNameHash = elementTypeNameHash;
ElementPropertyHashes = elementPropertyHashes;
ChildEnableMetadata = childEnableMetadata;
ElementEnableMetadata = elementEnableMetadata;
ChildNeedsIdScan = childNeedsIdScan;
ChildNeedsAllRefScan = childNeedsAllRefScan;
ChildNeedsInternScan = childNeedsInternScan;
ElementNeedsIdScan = elementNeedsIdScan;
ElementNeedsAllRefScan = elementNeedsAllRefScan;
ElementNeedsInternScan = elementNeedsInternScan;
// 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, Dictionary,
NullableInt32, NullableInt64, NullableInt16, NullableByte, NullableUInt16, NullableUInt32, NullableUInt64,
NullableBoolean, NullableSingle, NullableDouble, NullableDecimal, NullableDateTime,
NullableDateTimeOffset, NullableTimeSpan, NullableGuid, NullableEnum
}
/// <summary>
/// Single source of truth for the compile-time decision: does the SGen-emit need a full ref-aware
/// switch (<c>Object</c> / <c>ObjectRefFirst</c> / <c>Null</c> / <c>ObjectRef</c> / FixObj) for a
/// given Complex property or collection element, OR can it use the zero-branch path
/// (<c>Object</c> / <c>Null</c> / FixObj only)?
///
/// <para><b>Predicate semantics</b>: the decision depends EXCLUSIVELY on whether the child
/// element subtree may emit ref markers — captured by <c>PropInfo.ChildNeedsRefScan</c> /
/// <c>PropInfo.ElementNeedsRefScan</c>. The parent-level <c>EnableRefHandlingFeature</c> flag is
/// <b>NOT</b> a factor here — that flag governs only the parent's SELF-tracking emit in the scan
/// pass (<c>GenWriter.cs</c> line 140), it does NOT suppress marker dispatch for child element
/// properties of THIS type.</para>
///
/// <para><b>Writer / reader symmetry</b> — invoked from BOTH sides so the compile-time decision is
/// identical at every call site:</para>
/// <list type="bullet">
/// <item><c>GenReader.EmitReadComplex</c> — guards zero-branch vs full ref-aware switch.</item>
/// <item><c>GenReader.EmitReadCollectionElement</c> — same guard for collection-element dispatch.</item>
/// <item><c>GenReader.EmitReadDictionary</c> — same guard for dictionary-value dispatch.</item>
/// <item><c>GenWriter.EmitDirectCollectionWrite</c> — guards <c>Object</c>-only vs
/// <c>WriteObjectRefMarker*</c> (runtime decide) emit on the writer side.</item>
/// </list>
///
/// <para><b>Why a generator-only helper, not a runtime helper</b> — the result is inlined into
/// the generated code as either the zero-branch ag or the full-switch ag. The predicate runs
/// once per emit-site at generation time; the runtime code has zero overhead from this abstraction
/// (no method call, no branch on the runtime hot path).</para>
///
/// <para>Regression target: <c>AcBinarySerializerIIdReferenceTests.Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction</c>.</para>
/// </summary>
internal static class RefAwareEmitPredicate
{
/// <summary>
/// Reader-side decision for a Complex property (<c>EmitReadComplex</c>) — does the
/// emit need a full ref-aware switch on <c>p.ChildNeedsRefScan</c>?
/// </summary>
internal static bool ChildEmitsRefMarker(PropInfo p) => p.ChildNeedsRefScan;
/// <summary>
/// Reader-side decision for a collection element (<c>EmitReadCollectionElement</c>) and
/// writer-side decision for the same element (<c>EmitDirectCollectionWrite</c>) — keyed on
/// <c>p.ElementNeedsRefScan</c>.
/// </summary>
internal static bool ElementEmitsRefMarker(PropInfo p) => p.ElementNeedsRefScan;
/// <summary>
/// Reader-side overload for <c>EmitReadCollectionElement</c> when only the bool flag is in
/// scope (e.g. when <c>PropInfo</c> is unrolled at the call site). Same semantics — kept as
/// a thin overload so EVERY call site routes through this predicate, not the raw field.
/// </summary>
internal static bool ElementEmitsRefMarker(bool elementNeedsRefScan) => elementNeedsRefScan;
/// <summary>
/// Reader-side decision for a dictionary value (<c>EmitReadDictionary</c>) — keyed on
/// <c>p.DictValueNeedsRefScan</c>. Symmetric with the Complex / Collection-element overloads.
/// </summary>
internal static bool DictValueEmitsRefMarker(PropInfo p) => p.DictValueNeedsRefScan;
}