From 03e5cd9f29fc60236ea37fdf8798c18ac9a02373 Mon Sep 17 00:00:00 2001 From: Loretta Date: Wed, 25 Feb 2026 09:28:44 +0100 Subject: [PATCH] Handle System.Object properties with runtime type dispatch - Emit special serialization logic for properties declared as System.Object, using value.GetType() and writing type name metadata for correct polymorphic deserialization. - Add IsObjectDeclaredType to PropInfo to support this logic. - Update scan pass to use runtime type for object properties. - Adjust IId reference test to ensure circular reference coverage. - Default UseGeneratedCode to true in serializer options. --- .../AcBinarySourceGenerator.cs | 27 ++++++++++++++++++- .../AcBinarySerializerIIdReferenceTests.cs | 7 ++--- .../Binaries/AcBinarySerializerOptions.cs | 2 +- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs index 407bd1d..0b5add8 100644 --- a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs +++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs @@ -248,6 +248,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator 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, childTypeNameHash, childPropertyHashes, @@ -536,6 +537,22 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // when the property type has a generated writer. Falls back to WriteObjectGenerated otherwise. if (p.HasGeneratedWriter) EmitDirectObjectWrite(sb, p, a, i); + else if (p.IsObjectDeclaredType) + { + // System.Object property: runtime type unknown at compile time. + // Write ObjectWithTypeName prefix so deserializer can resolve the concrete type. + // Use value.GetType() for runtime type dispatch (not typeof(object)). + sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); + sb.AppendLine($"{i}else"); + sb.AppendLine($"{i}{{"); + sb.AppendLine($"{i} if (!context.UseMetadata)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithTypeName);"); + sb.AppendLine($"{i} context.WriteStringUtf8({a}.GetType().AssemblyQualifiedName!);"); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} AcBinarySerializer.WriteValueGenerated({a}, {a}.GetType(), context, depth);"); + sb.AppendLine($"{i}}}"); + } else if (p.IsNullable) { sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); @@ -723,13 +740,17 @@ public class AcBinarySourceGenerator : IIncrementalGenerator /// /// Emits scan pass code for a Complex property without SGen writer (runtime fallback). + /// System.Object properties use value.GetType() for runtime type dispatch. /// private static void EmitScanComplexRuntime(StringBuilder sb, PropInfo p, string a, string i) { var childVar = $"sc_{p.Name}"; sb.AppendLine($"{i}var {childVar} = {a};"); sb.AppendLine($"{i}if ({childVar} != null)"); - sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, typeof({p.TypeNameForTypeof}), context, depth + 1);"); + if (p.IsObjectDeclaredType) + sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, {childVar}.GetType(), context, depth + 1);"); + else + sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, typeof({p.TypeNameForTypeof}), context, depth + 1);"); } /// @@ -2046,6 +2067,8 @@ internal sealed class PropInfo /// public int InterningFlags { get; } + /// True when declared property type is System.Object. Runtime type dispatch needed. + public bool IsObjectDeclaredType { 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. @@ -2106,6 +2129,7 @@ internal sealed class PropInfo 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, @@ -2120,6 +2144,7 @@ internal sealed class PropInfo TypeNameForTypeof = tnForTypeof; TypeKind = tk; IsNullable = nullable; + IsObjectDeclaredType = isObjectDeclaredType; HasGeneratedWriter = hasGeneratedWriter; IsIId = isIId; WriterClassName = writerClassName; diff --git a/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs b/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs index 0253c96..d3926d4 100644 --- a/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs +++ b/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs @@ -114,9 +114,10 @@ public class AcBinarySerializerIIdReferenceTests ] }; - //order.Parent = order.Items[0]; - order.Parent = userPreferences; - order.Items[0].ParentOrder = order; + if (mode != ReferenceHandlingMode.None) order.Parent = order.Items[1]; + else order.Parent = userPreferences; + + order.Items[1].ParentOrder = order; var options = new AcBinarySerializerOptions { ReferenceHandling = mode }; diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs index 4f4d363..6b01f75 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs @@ -84,7 +84,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions /// public bool UseMetadata { get; set; } = false; - public bool UseGeneratedCode { get; set; } = false; + public bool UseGeneratedCode { get; set; } = true; /// /// Controls how string interning is applied during serialization.