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.
This commit is contained in:
parent
8eeaa6725e
commit
03e5cd9f29
|
|
@ -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,12 +740,16 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
|
||||
/// <summary>
|
||||
/// Emits scan pass code for a Complex property without SGen writer (runtime fallback).
|
||||
/// System.Object properties use value.GetType() for runtime type dispatch.
|
||||
/// </summary>
|
||||
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)");
|
||||
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
|
|||
/// </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>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
|||
/// </summary>
|
||||
public bool UseMetadata { get; set; } = false;
|
||||
|
||||
public bool UseGeneratedCode { get; set; } = false;
|
||||
public bool UseGeneratedCode { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Controls how string interning is applied during serialization.
|
||||
|
|
|
|||
Loading…
Reference in New Issue