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,
|
typeNameForTypeof,
|
||||||
kind,
|
kind,
|
||||||
p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableVT(p.Type),
|
p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableVT(p.Type),
|
||||||
|
p.Type.SpecialType == SpecialType.System_Object,
|
||||||
stringInternAttr, hasGenWriter, propTypeIsIId, writerClassName, propIdTypeName,
|
stringInternAttr, hasGenWriter, propTypeIsIId, writerClassName, propIdTypeName,
|
||||||
elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, elemIdTypeName, collKind, elemFullTypeName,
|
elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, elemIdTypeName, collKind, elemFullTypeName,
|
||||||
childTypeNameHash, childPropertyHashes,
|
childTypeNameHash, childPropertyHashes,
|
||||||
|
|
@ -536,6 +537,22 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
// when the property type has a generated writer. Falls back to WriteObjectGenerated otherwise.
|
// when the property type has a generated writer. Falls back to WriteObjectGenerated otherwise.
|
||||||
if (p.HasGeneratedWriter)
|
if (p.HasGeneratedWriter)
|
||||||
EmitDirectObjectWrite(sb, p, a, i);
|
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)
|
else if (p.IsNullable)
|
||||||
{
|
{
|
||||||
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
||||||
|
|
@ -723,13 +740,17 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Emits scan pass code for a Complex property without SGen writer (runtime fallback).
|
/// Emits scan pass code for a Complex property without SGen writer (runtime fallback).
|
||||||
|
/// System.Object properties use value.GetType() for runtime type dispatch.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void EmitScanComplexRuntime(StringBuilder sb, PropInfo p, string a, string i)
|
private static void EmitScanComplexRuntime(StringBuilder sb, PropInfo p, string a, string i)
|
||||||
{
|
{
|
||||||
var childVar = $"sc_{p.Name}";
|
var childVar = $"sc_{p.Name}";
|
||||||
sb.AppendLine($"{i}var {childVar} = {a};");
|
sb.AppendLine($"{i}var {childVar} = {a};");
|
||||||
sb.AppendLine($"{i}if ({childVar} != null)");
|
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);");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -2046,6 +2067,8 @@ internal sealed class PropInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int InterningFlags { get; }
|
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>
|
/// <summary>True if the Complex property type has [AcBinarySerializable] → has a generated writer.</summary>
|
||||||
public bool HasGeneratedWriter { get; }
|
public bool HasGeneratedWriter { get; }
|
||||||
/// <summary>True if the Complex property type implements IId<T> → needs ref tracking in write pass.</summary>
|
/// <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 bool ElementNeedsScan => ElementNeedsIdScan || ElementNeedsAllRefScan || ElementNeedsInternScan;
|
||||||
|
|
||||||
public PropInfo(string n, string tn, string tnForTypeof, PropertyTypeKind tk, bool nullable,
|
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,
|
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,
|
PropertyTypeKind elementKind = PropertyTypeKind.Unknown, bool elementHasGenWriter = false, bool elementIsIId = false,
|
||||||
string? elementWriterClassName = null, string? elementIdTypeName = null, string? collectionKind = null, string? elementFullTypeName = null,
|
string? elementWriterClassName = null, string? elementIdTypeName = null, string? collectionKind = null, string? elementFullTypeName = null,
|
||||||
|
|
@ -2120,6 +2144,7 @@ internal sealed class PropInfo
|
||||||
TypeNameForTypeof = tnForTypeof;
|
TypeNameForTypeof = tnForTypeof;
|
||||||
TypeKind = tk;
|
TypeKind = tk;
|
||||||
IsNullable = nullable;
|
IsNullable = nullable;
|
||||||
|
IsObjectDeclaredType = isObjectDeclaredType;
|
||||||
HasGeneratedWriter = hasGeneratedWriter;
|
HasGeneratedWriter = hasGeneratedWriter;
|
||||||
IsIId = isIId;
|
IsIId = isIId;
|
||||||
WriterClassName = writerClassName;
|
WriterClassName = writerClassName;
|
||||||
|
|
|
||||||
|
|
@ -114,9 +114,10 @@ public class AcBinarySerializerIIdReferenceTests
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
//order.Parent = order.Items[0];
|
if (mode != ReferenceHandlingMode.None) order.Parent = order.Items[1];
|
||||||
order.Parent = userPreferences;
|
else order.Parent = userPreferences;
|
||||||
order.Items[0].ParentOrder = order;
|
|
||||||
|
order.Items[1].ParentOrder = order;
|
||||||
|
|
||||||
var options = new AcBinarySerializerOptions { ReferenceHandling = mode };
|
var options = new AcBinarySerializerOptions { ReferenceHandling = mode };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseMetadata { get; set; } = false;
|
public bool UseMetadata { get; set; } = false;
|
||||||
|
|
||||||
public bool UseGeneratedCode { get; set; } = false;
|
public bool UseGeneratedCode { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls how string interning is applied during serialization.
|
/// Controls how string interning is applied during serialization.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue