using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; namespace AyCode.Core.Serializers.SourceGenerator; /// /// Class-info extraction pass — transforms a Roslyn /// (a class/struct annotated with [AcBinarySerializable]) into the /// model consumed by the emit passes (writer / reader / scan / init). /// /// Reads the attribute's feature flags (1-, 4-, 5-, 6-bool ctor variants), walks the inheritance /// hierarchy via GetAllSerializablePropertySymbols, and computes per-property metadata: kind, /// nullability, intern eligibility, complex / collection / dictionary element types, generated-writer /// pointers, FNV hashes for inline-metadata, and recursive scan-need flags. /// public partial class AcBinarySourceGenerator { 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(); // Read feature flags from [AcBinarySerializable] — disabled features eliminate // corresponding code blocks from generated ScanObject/WriteProperties. var enableIdTracking = true; var enableRefHandling = true; var enableInternString = true; var enableMetadata = true; var enablePropertyFilter = true; var enablePolymorphDetect = true; var binarySerializableAttr = typeSymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == AttributeName); if (binarySerializableAttr != null) { if (binarySerializableAttr.ConstructorArguments.Length == 1) { // Single bool ctor: AcBinarySerializable(enableAllFeatures) var all = (bool)binarySerializableAttr.ConstructorArguments[0].Value!; enableIdTracking = all; enableRefHandling = all; enableInternString = all; enableMetadata = all; enablePropertyFilter = all; enablePolymorphDetect = all; } else if (binarySerializableAttr.ConstructorArguments.Length == 4) { // Four bool ctor: (metadata, idTracking, refHandling, internString) — filter + polymorph default to true enableMetadata = (bool)binarySerializableAttr.ConstructorArguments[0].Value!; enableIdTracking = (bool)binarySerializableAttr.ConstructorArguments[1].Value!; enableRefHandling = (bool)binarySerializableAttr.ConstructorArguments[2].Value!; enableInternString = (bool)binarySerializableAttr.ConstructorArguments[3].Value!; } else if (binarySerializableAttr.ConstructorArguments.Length == 5) { // Five bool ctor: (metadata, idTracking, refHandling, internString, propertyFilter) — polymorph defaults to true enableMetadata = (bool)binarySerializableAttr.ConstructorArguments[0].Value!; enableIdTracking = (bool)binarySerializableAttr.ConstructorArguments[1].Value!; enableRefHandling = (bool)binarySerializableAttr.ConstructorArguments[2].Value!; enableInternString = (bool)binarySerializableAttr.ConstructorArguments[3].Value!; enablePropertyFilter = (bool)binarySerializableAttr.ConstructorArguments[4].Value!; } else if (binarySerializableAttr.ConstructorArguments.Length == 6) { // Six bool ctor: (metadata, idTracking, refHandling, internString, propertyFilter, polymorphDetect) enableMetadata = (bool)binarySerializableAttr.ConstructorArguments[0].Value!; enableIdTracking = (bool)binarySerializableAttr.ConstructorArguments[1].Value!; enableRefHandling = (bool)binarySerializableAttr.ConstructorArguments[2].Value!; enableInternString = (bool)binarySerializableAttr.ConstructorArguments[3].Value!; enablePropertyFilter = (bool)binarySerializableAttr.ConstructorArguments[4].Value!; enablePolymorphDetect = (bool)binarySerializableAttr.ConstructorArguments[5].Value!; } } foreach (var p in GetAllSerializablePropertySymbols(typeSymbol)) { // String interning attribútum detektálás (null = no attr, true/false = explicit) bool? stringInternAttr = null; if (!enableInternString) { stringInternAttr = false; } else 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; bool propEnableMetadata = true; bool childNeedsIdScan = true; bool childNeedsAllRefScan = true; bool childNeedsInternScan = true; string? writerClassName = null; string? propIdTypeName = 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.Locations.Any(l => l.IsInSource) && resolvedType.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == AttributeName); if (hasGenWriter) { // Read child type's EnableMetadataFeature propEnableMetadata = ReadEnableMetadata(resolvedType); var childScanFlags = ComputeNeedsScan(resolvedType); childNeedsIdScan = childScanFlags.needsIdScan; childNeedsAllRefScan = childScanFlags.needsAllRefScan; childNeedsInternScan = childScanFlags.needsInternScan; var iidIface = resolvedType.AllInterfaces.FirstOrDefault(i => i.IsGenericType && i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId"); propTypeIsIId = iidIface != null; if (iidIface != null) propIdTypeName = iidIface.TypeArguments[0].ToDisplayString(); // 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; bool elemEnableMetadata = true; bool elemNeedsIdScan = true; bool elemNeedsAllRefScan = true; bool elemNeedsInternScan = true; string? elemWriterClassName = null; string? elemIdTypeName = null; string? collKind = null; string? collAddMethod = null; bool collHasCapacityCtor = false; 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" => "IndexedCollection", "System.Collections.Generic.IReadOnlyList" => "IndexedCollection", "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 }; // Determine add method + capacity ctor for Counted concrete types if (collKind == "Counted") { collAddMethod = origDef switch { "System.Collections.Generic.HashSet" => "Add", "System.Collections.Generic.SortedSet" => "Add", "System.Collections.Generic.Queue" => "Enqueue", "System.Collections.Generic.LinkedList" => "AddLast", _ => null // ICollection, IReadOnlyCollection → backed by List }; collHasCapacityCtor = origDef is "System.Collections.Generic.HashSet" or "System.Collections.Generic.Queue"; } } // For Complex element types, check for generated writer if (elemKind == PropertyTypeKind.Complex) { var resolvedElem = elemType is INamedTypeSymbol namedElem ? namedElem.OriginalDefinition : elemType; elemHasGenWriter = resolvedElem.Locations.Any(l => l.IsInSource) && resolvedElem.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == AttributeName); if (elemHasGenWriter) { // Read element type's EnableMetadataFeature elemEnableMetadata = ReadEnableMetadata(resolvedElem); var elemScanFlags = ComputeNeedsScan(resolvedElem); elemNeedsIdScan = elemScanFlags.needsIdScan; elemNeedsAllRefScan = elemScanFlags.needsAllRefScan; elemNeedsInternScan = elemScanFlags.needsInternScan; var elemIidIface = resolvedElem.AllInterfaces.FirstOrDefault(ifc => ifc.IsGenericType && ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId"); elemIsIId = elemIidIface != null; if (elemIidIface != null) elemIdTypeName = elemIidIface.TypeArguments[0].ToDisplayString(); 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); } } } } // Dictionary key/value type analysis for inline dictionary read PropertyTypeKind dictKeyKind = PropertyTypeKind.Unknown; PropertyTypeKind dictValueKind = PropertyTypeKind.Unknown; string? dictKeyTypeName = null; string? dictValueTypeName = null; bool dictValueHasGenWriter = false; string? dictValueWriterClassName = null; bool dictValueIsIId = false; bool dictValueEnableMetadata = true; bool dictValueNeedsIdScan = true; bool dictValueNeedsAllRefScan = true; bool dictValueNeedsInternScan = true; int dictValueTypeNameHash = 0; int[]? dictValuePropertyHashes = null; if (kind == PropertyTypeKind.Dictionary) { var (keyType, valueType) = GetDictionaryKeyValueTypes(p.Type); if (keyType != null) { dictKeyKind = GetKind(keyType); dictKeyTypeName = keyType.ToDisplayString(); } if (valueType != null) { dictValueKind = GetKind(valueType); dictValueTypeName = valueType.ToDisplayString(); if (dictValueKind == PropertyTypeKind.Complex) { var resolvedValue = valueType is INamedTypeSymbol nvt ? nvt.OriginalDefinition : valueType; dictValueHasGenWriter = resolvedValue.Locations.Any(l => l.IsInSource) && resolvedValue.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == AttributeName); if (dictValueHasGenWriter) { var vfn = BuildFlatName((INamedTypeSymbol)resolvedValue); var vns = resolvedValue.ContainingNamespace.IsGlobalNamespace ? string.Empty : resolvedValue.ContainingNamespace.ToDisplayString(); dictValueWriterClassName = string.IsNullOrEmpty(vns) ? $"{vfn}_GeneratedWriter" : $"{vns}.{vfn}_GeneratedWriter"; dictValueEnableMetadata = ReadEnableMetadata(resolvedValue); var dvScanFlags = ComputeNeedsScan(resolvedValue); dictValueNeedsIdScan = dvScanFlags.needsIdScan; dictValueNeedsAllRefScan = dvScanFlags.needsAllRefScan; dictValueNeedsInternScan = dvScanFlags.needsInternScan; var dvIidIface = resolvedValue.AllInterfaces.FirstOrDefault(ifc => ifc.IsGenericType && ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId"); dictValueIsIId = dvIidIface != null; dictValueTypeNameHash = ComputeFnvHash(resolvedValue.Name); dictValuePropertyHashes = ComputeChildPropertyHashes(resolvedValue); } } } } properties.Add(new PropInfo( p.Name, typeDisplayName, typeNameForTypeof, kind, p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableVT(p.Type), p.Type.SpecialType == SpecialType.System_Object, stringInternAttr, hasGenWriter, propTypeIsIId, writerClassName, propIdTypeName, elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, elemIdTypeName, collKind, elemFullTypeName, collAddMethod, collHasCapacityCtor, dictKeyKind, dictValueKind, dictKeyTypeName, dictValueTypeName, dictValueHasGenWriter, dictValueWriterClassName, dictValueIsIId, dictValueEnableMetadata, dictValueTypeNameHash, dictValuePropertyHashes, dictValueNeedsIdScan, dictValueNeedsAllRefScan, dictValueNeedsInternScan, childTypeNameHash, childPropertyHashes, elementTypeNameHash, elementPropertyHashes, propEnableMetadata, elemEnableMetadata, childNeedsIdScan, childNeedsAllRefScan, childNeedsInternScan, elemNeedsIdScan, elemNeedsAllRefScan, elemNeedsInternScan)); } // IId: Id first (index 0), then alphabetical — matches runtime TypeMetadataBase ordering // If EnableIdTrackingFeature == false, skip IId detection entirely → isIId = false var isIId = false; string? idTypeName = null; if (enableIdTracking) { var iidInterface = typeSymbol.AllInterfaces.FirstOrDefault(i => i.IsGenericType && i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId"); if (iidInterface != null) { isIId = true; idTypeName = iidInterface.TypeArguments[0].ToDisplayString(); } } // Properties are already in runtime-matching order from GetAllSerializablePropertySymbols: // derived → base, each level sorted alphabetically (matches TypeMetadataBase.GetUnfilteredProperties). var className = BuildFlatName(typeSymbol); var typeNameHash = ComputeFnvHash(typeSymbol.Name); var propertyNameHashes = properties.Select(prop => ComputeFnvHash(prop.Name)).ToArray(); var selfScanFlags = ComputeNeedsScan(typeSymbol); return new SerializableClassInfo(namespaceName, className, typeSymbol.ToDisplayString(), properties, isIId, idTypeName, enableRefHandling, typeNameHash, propertyNameHashes, enableMetadata, enablePropertyFilter, enablePolymorphDetect, enableInternString, selfScanFlags.needsIdScan, selfScanFlags.needsAllRefScan, selfScanFlags.needsInternScan); } }