310 lines
20 KiB
C#
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<T></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<T> → 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<T>.</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<T>.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<T>.</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;
|
|
}
|