Support nested types in source gen; improve prop filtering
- Generate writers for nested types using flat class names (Outer_Inner_Leaf) to ensure uniqueness and validity. - Apply property filters in generated code for all non-markerless properties, matching runtime behavior. - Emit skip labels for each property in generated code for correct control flow. - Remove PropertyFilter check from IsDirectObjectWrite; generated code now handles filtering. - Change default ReferenceHandlingMode to All. - Make BinaryPropertyFilterContext constructor public. - Increase release warmup iterations in Program.cs from 3000 to 5000.
This commit is contained in:
parent
418d9f839a
commit
d40e40a45a
|
|
@ -49,7 +49,7 @@ public static class Program
|
||||||
private static int WarmupIterations = 5;
|
private static int WarmupIterations = 5;
|
||||||
private static int TestIterations = 10;
|
private static int TestIterations = 10;
|
||||||
#else
|
#else
|
||||||
private static int WarmupIterations = 3000;
|
private static int WarmupIterations = 5000;
|
||||||
private static int TestIterations = 1000;
|
private static int TestIterations = 1000;
|
||||||
|
|
||||||
//private static int WarmupIterations = 5000;
|
//private static int WarmupIterations = 5000;
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
if (!(context.TargetSymbol is INamedTypeSymbol typeSymbol))
|
if (!(context.TargetSymbol is INamedTypeSymbol typeSymbol))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Skip nested types — generated writer class can't be placed inside containing type
|
|
||||||
if (typeSymbol.ContainingType != null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
|
var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
|
||||||
? string.Empty
|
? string.Empty
|
||||||
: typeSymbol.ContainingNamespace.ToDisplayString();
|
: typeSymbol.ContainingNamespace.ToDisplayString();
|
||||||
|
|
@ -94,22 +90,20 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
|
|
||||||
hasGenWriter = resolvedType.GetAttributes().Any(a =>
|
hasGenWriter = resolvedType.GetAttributes().Any(a =>
|
||||||
a.AttributeClass?.ToDisplayString() == AttributeName);
|
a.AttributeClass?.ToDisplayString() == AttributeName);
|
||||||
// Skip nested types (we don't generate writers for them)
|
|
||||||
if (hasGenWriter && resolvedType is INamedTypeSymbol nt && nt.ContainingType != null)
|
|
||||||
hasGenWriter = false;
|
|
||||||
|
|
||||||
if (hasGenWriter)
|
if (hasGenWriter)
|
||||||
{
|
{
|
||||||
propTypeIsIId = resolvedType.AllInterfaces.Any(i =>
|
propTypeIsIId = resolvedType.AllInterfaces.Any(i =>
|
||||||
i.IsGenericType &&
|
i.IsGenericType &&
|
||||||
i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
||||||
// Writer class: {Namespace}.{TypeName}_GeneratedWriter
|
// Writer class: {Namespace}.{FlatName}_GeneratedWriter
|
||||||
|
var flatName = BuildFlatName((INamedTypeSymbol)resolvedType);
|
||||||
var ns = resolvedType.ContainingNamespace.IsGlobalNamespace
|
var ns = resolvedType.ContainingNamespace.IsGlobalNamespace
|
||||||
? string.Empty
|
? string.Empty
|
||||||
: resolvedType.ContainingNamespace.ToDisplayString();
|
: resolvedType.ContainingNamespace.ToDisplayString();
|
||||||
writerClassName = string.IsNullOrEmpty(ns)
|
writerClassName = string.IsNullOrEmpty(ns)
|
||||||
? $"{resolvedType.Name}_GeneratedWriter"
|
? $"{flatName}_GeneratedWriter"
|
||||||
: $"{ns}.{resolvedType.Name}_GeneratedWriter";
|
: $"{ns}.{flatName}_GeneratedWriter";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,18 +150,17 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
? namedElem.OriginalDefinition : elemType;
|
? namedElem.OriginalDefinition : elemType;
|
||||||
elemHasGenWriter = resolvedElem.GetAttributes().Any(a =>
|
elemHasGenWriter = resolvedElem.GetAttributes().Any(a =>
|
||||||
a.AttributeClass?.ToDisplayString() == AttributeName);
|
a.AttributeClass?.ToDisplayString() == AttributeName);
|
||||||
if (elemHasGenWriter && resolvedElem is INamedTypeSymbol nte && nte.ContainingType != null)
|
|
||||||
elemHasGenWriter = false;
|
|
||||||
if (elemHasGenWriter)
|
if (elemHasGenWriter)
|
||||||
{
|
{
|
||||||
elemIsIId = resolvedElem.AllInterfaces.Any(ifc =>
|
elemIsIId = resolvedElem.AllInterfaces.Any(ifc =>
|
||||||
ifc.IsGenericType &&
|
ifc.IsGenericType &&
|
||||||
ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
||||||
|
var elemFlatName = BuildFlatName((INamedTypeSymbol)resolvedElem);
|
||||||
var ens = resolvedElem.ContainingNamespace.IsGlobalNamespace
|
var ens = resolvedElem.ContainingNamespace.IsGlobalNamespace
|
||||||
? string.Empty : resolvedElem.ContainingNamespace.ToDisplayString();
|
? string.Empty : resolvedElem.ContainingNamespace.ToDisplayString();
|
||||||
elemWriterClassName = string.IsNullOrEmpty(ens)
|
elemWriterClassName = string.IsNullOrEmpty(ens)
|
||||||
? $"{resolvedElem.Name}_GeneratedWriter"
|
? $"{elemFlatName}_GeneratedWriter"
|
||||||
: $"{ens}.{resolvedElem.Name}_GeneratedWriter";
|
: $"{ens}.{elemFlatName}_GeneratedWriter";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +193,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
else
|
else
|
||||||
properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||||
|
|
||||||
return new SerializableClassInfo(namespaceName, typeSymbol.Name, typeSymbol.ToDisplayString(), properties);
|
return new SerializableClassInfo(namespaceName, BuildFlatName(typeSymbol), typeSymbol.ToDisplayString(), properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Execute(ImmutableArray<SerializableClassInfo?> classes, SourceProductionContext context)
|
private static void Execute(ImmutableArray<SerializableClassInfo?> classes, SourceProductionContext context)
|
||||||
|
|
@ -240,7 +233,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
foreach (var p in ci.Properties)
|
foreach (var p in ci.Properties)
|
||||||
{
|
{
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
EmitProp(sb, p, " ");
|
EmitProp(sb, p, " ", ci.FullTypeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine(" }");
|
sb.AppendLine(" }");
|
||||||
|
|
@ -248,10 +241,31 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitProp(StringBuilder sb, PropInfo p, string i)
|
private static void EmitProp(StringBuilder sb, PropInfo p, string i, string fullTypeName)
|
||||||
{
|
{
|
||||||
var a = $"obj.{p.Name}";
|
var a = $"obj.{p.Name}";
|
||||||
|
|
||||||
|
// Markerless types: write raw value only, no type marker, no PropertySkip
|
||||||
|
// Matches runtime WritePropertyMarkerless — these have ExpectedTypeCode
|
||||||
|
// NEVER filtered (runtime doesn't filter markerless properties either)
|
||||||
|
if (IsMarkerless(p.TypeKind))
|
||||||
|
{
|
||||||
|
EmitMarkerless(sb, p.TypeKind, a, i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All non-markerless properties: emit PropertyFilter guard
|
||||||
|
// When filter returns false, write PropertySkip and skip the property write
|
||||||
|
sb.AppendLine($"{i}if (context.HasPropertyFilter)");
|
||||||
|
sb.AppendLine($"{i}{{");
|
||||||
|
sb.AppendLine($"{i} var fc_{p.Name} = new BinaryPropertyFilterContext(obj, typeof({fullTypeName}), \"{p.Name}\", typeof({p.TypeNameForTypeof}), static o => (({fullTypeName})o).{p.Name});");
|
||||||
|
sb.AppendLine($"{i} if (!context.PropertyFilter!(in fc_{p.Name}))");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.PropertySkip);");
|
||||||
|
sb.AppendLine($"{i} goto skip_{p.Name};");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
sb.AppendLine($"{i}}}");
|
||||||
|
|
||||||
// Nullable value types always use markered path (need Null marker)
|
// Nullable value types always use markered path (need Null marker)
|
||||||
if (IsNullableVTKind(p.TypeKind))
|
if (IsNullableVTKind(p.TypeKind))
|
||||||
{
|
{
|
||||||
|
|
@ -260,14 +274,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
EmitVal(sb, Underlying(p.TypeKind), $"{a}.Value", p.TypeNameForTypeof, i + " ");
|
EmitVal(sb, Underlying(p.TypeKind), $"{a}.Value", p.TypeNameForTypeof, i + " ");
|
||||||
sb.AppendLine($"{i}}}");
|
sb.AppendLine($"{i}}}");
|
||||||
sb.AppendLine($"{i}else context.WriteByte(BinaryTypeCode.PropertySkip);");
|
sb.AppendLine($"{i}else context.WriteByte(BinaryTypeCode.PropertySkip);");
|
||||||
return;
|
sb.AppendLine($"{i}skip_{p.Name}:;");
|
||||||
}
|
|
||||||
|
|
||||||
// Markerless types: write raw value only, no type marker, no PropertySkip
|
|
||||||
// Matches runtime WritePropertyMarkerless — these have ExpectedTypeCode
|
|
||||||
if (IsMarkerless(p.TypeKind))
|
|
||||||
{
|
|
||||||
EmitMarkerless(sb, p.TypeKind, a, i);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,6 +317,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i);
|
EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb.AppendLine($"{i}skip_{p.Name}:;");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -653,12 +662,37 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
sb.AppendLine(" internal static void Register()");
|
sb.AppendLine(" internal static void Register()");
|
||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
foreach (var ci in classes)
|
foreach (var ci in classes)
|
||||||
sb.AppendLine($" AcBinarySerializer.RegisterGeneratedWriter(typeof({ci.FullTypeName}), {ci.FullTypeName}_GeneratedWriter.Instance);");
|
{
|
||||||
|
var writerRef = string.IsNullOrEmpty(ci.Namespace)
|
||||||
|
? $"{ci.ClassName}_GeneratedWriter"
|
||||||
|
: $"{ci.Namespace}.{ci.ClassName}_GeneratedWriter";
|
||||||
|
sb.AppendLine($" AcBinarySerializer.RegisterGeneratedWriter(typeof({ci.FullTypeName}), {writerRef}.Instance);");
|
||||||
|
}
|
||||||
sb.AppendLine(" }");
|
sb.AppendLine(" }");
|
||||||
sb.AppendLine("}");
|
sb.AppendLine("}");
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a flat class name for nested types: Outer_Inner_Leaf.
|
||||||
|
/// For top-level types returns the simple name unchanged.
|
||||||
|
/// </summary>
|
||||||
|
private static string BuildFlatName(INamedTypeSymbol typeSymbol)
|
||||||
|
{
|
||||||
|
if (typeSymbol.ContainingType == null)
|
||||||
|
return typeSymbol.Name;
|
||||||
|
|
||||||
|
var parts = new List<string>();
|
||||||
|
var current = typeSymbol;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
parts.Add(current.Name);
|
||||||
|
current = current.ContainingType;
|
||||||
|
}
|
||||||
|
parts.Reverse();
|
||||||
|
return string.Join("_", parts);
|
||||||
|
}
|
||||||
|
|
||||||
#region Type analysis
|
#region Type analysis
|
||||||
|
|
||||||
private static bool IsNullableVT(ITypeSymbol t) =>
|
private static bool IsNullableVT(ITypeSymbol t) =>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ public abstract class AcSerializerOptions
|
||||||
set => _referenceHandling = value;
|
set => _referenceHandling = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReferenceHandlingMode _referenceHandling = ReferenceHandlingMode.OnlyId;
|
private ReferenceHandlingMode _referenceHandling = ReferenceHandlingMode.All;
|
||||||
|
|
||||||
private readonly byte _maxDepth = byte.MaxValue;
|
private readonly byte _maxDepth = byte.MaxValue;
|
||||||
private readonly bool _throwOnCircularReference = true;
|
private readonly bool _throwOnCircularReference = true;
|
||||||
private readonly PropertyMapperDelegate? _propertyMapper;
|
private readonly PropertyMapperDelegate? _propertyMapper;
|
||||||
|
|
|
||||||
|
|
@ -223,10 +223,11 @@ public static partial class AcBinarySerializer
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True when generated writers can bypass WriteObject entirely and write markers + properties inline.
|
/// True when generated writers can bypass WriteObject entirely and write markers + properties inline.
|
||||||
/// Requires: no UseMetadata (no inline metadata tracking), no PropertyFilter (no per-prop filtering).
|
/// Requires: no UseMetadata (no inline metadata tracking).
|
||||||
|
/// PropertyFilter is handled by generated code's per-property filter checks.
|
||||||
/// Reference handling is safe because generated code inlines TryConsumeWritePlanEntry for IId types.
|
/// Reference handling is safe because generated code inlines TryConsumeWritePlanEntry for IId types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDirectObjectWrite => !UseMetadata && !HasPropertyFilter;
|
public bool IsDirectObjectWrite => !UseMetadata;
|
||||||
//public bool FastWire { get; private set; }
|
//public bool FastWire { get; private set; }
|
||||||
public byte MinStringInternLength => Options.MinStringInternLength;
|
public byte MinStringInternLength => Options.MinStringInternLength;
|
||||||
public byte MaxStringInternLength => Options.MaxStringInternLength;
|
public byte MaxStringInternLength => Options.MaxStringInternLength;
|
||||||
|
|
@ -847,6 +848,7 @@ public static partial class AcBinarySerializer
|
||||||
property.Name,
|
property.Name,
|
||||||
property.PropertyType,
|
property.PropertyType,
|
||||||
property.DynamicGetter);
|
property.DynamicGetter);
|
||||||
|
|
||||||
return PropertyFilter(context);
|
return PropertyFilter(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1089,7 +1089,7 @@ public static partial class AcBinarySerializer
|
||||||
if (context.UseGeneratedCode)
|
if (context.UseGeneratedCode)
|
||||||
{
|
{
|
||||||
var generatedWriter = wrapper.GeneratedWriter;
|
var generatedWriter = wrapper.GeneratedWriter;
|
||||||
if (generatedWriter != null && !hasPropertyFilter && !context.UseMetadata)
|
if (generatedWriter != null && !context.UseMetadata)
|
||||||
{
|
{
|
||||||
generatedWriter.WriteProperties(value, context, nextDepth);
|
generatedWriter.WriteProperties(value, context, nextDepth);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ public readonly struct BinaryPropertyFilterContext
|
||||||
private readonly object? _instance;
|
private readonly object? _instance;
|
||||||
private readonly Func<object, object?>? _valueGetter;
|
private readonly Func<object, object?>? _valueGetter;
|
||||||
|
|
||||||
internal BinaryPropertyFilterContext(object? instance, Type declaringType, string propertyName, Type propertyType, Func<object, object?>? valueGetter)
|
public BinaryPropertyFilterContext(object? instance, Type declaringType, string propertyName, Type propertyType, Func<object, object?>? valueGetter)
|
||||||
{
|
{
|
||||||
_instance = instance;
|
_instance = instance;
|
||||||
DeclaringType = declaringType;
|
DeclaringType = declaringType;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue