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 TestIterations = 10;
|
||||
#else
|
||||
private static int WarmupIterations = 3000;
|
||||
private static int WarmupIterations = 5000;
|
||||
private static int TestIterations = 1000;
|
||||
|
||||
//private static int WarmupIterations = 5000;
|
||||
|
|
|
|||
|
|
@ -36,10 +36,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
if (!(context.TargetSymbol is INamedTypeSymbol typeSymbol))
|
||||
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
|
||||
? string.Empty
|
||||
: typeSymbol.ContainingNamespace.ToDisplayString();
|
||||
|
|
@ -94,22 +90,20 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
|
||||
hasGenWriter = resolvedType.GetAttributes().Any(a =>
|
||||
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)
|
||||
{
|
||||
propTypeIsIId = resolvedType.AllInterfaces.Any(i =>
|
||||
i.IsGenericType &&
|
||||
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
|
||||
? string.Empty
|
||||
: resolvedType.ContainingNamespace.ToDisplayString();
|
||||
writerClassName = string.IsNullOrEmpty(ns)
|
||||
? $"{resolvedType.Name}_GeneratedWriter"
|
||||
: $"{ns}.{resolvedType.Name}_GeneratedWriter";
|
||||
? $"{flatName}_GeneratedWriter"
|
||||
: $"{ns}.{flatName}_GeneratedWriter";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,18 +150,17 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
? namedElem.OriginalDefinition : elemType;
|
||||
elemHasGenWriter = resolvedElem.GetAttributes().Any(a =>
|
||||
a.AttributeClass?.ToDisplayString() == AttributeName);
|
||||
if (elemHasGenWriter && resolvedElem is INamedTypeSymbol nte && nte.ContainingType != null)
|
||||
elemHasGenWriter = false;
|
||||
if (elemHasGenWriter)
|
||||
{
|
||||
elemIsIId = resolvedElem.AllInterfaces.Any(ifc =>
|
||||
ifc.IsGenericType &&
|
||||
ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
||||
var elemFlatName = BuildFlatName((INamedTypeSymbol)resolvedElem);
|
||||
var ens = resolvedElem.ContainingNamespace.IsGlobalNamespace
|
||||
? string.Empty : resolvedElem.ContainingNamespace.ToDisplayString();
|
||||
elemWriterClassName = string.IsNullOrEmpty(ens)
|
||||
? $"{resolvedElem.Name}_GeneratedWriter"
|
||||
: $"{ens}.{resolvedElem.Name}_GeneratedWriter";
|
||||
? $"{elemFlatName}_GeneratedWriter"
|
||||
: $"{ens}.{elemFlatName}_GeneratedWriter";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -200,7 +193,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
else
|
||||
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)
|
||||
|
|
@ -240,7 +233,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
foreach (var p in ci.Properties)
|
||||
{
|
||||
sb.AppendLine();
|
||||
EmitProp(sb, p, " ");
|
||||
EmitProp(sb, p, " ", ci.FullTypeName);
|
||||
}
|
||||
|
||||
sb.AppendLine(" }");
|
||||
|
|
@ -248,10 +241,31 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
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}";
|
||||
|
||||
// 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)
|
||||
if (IsNullableVTKind(p.TypeKind))
|
||||
{
|
||||
|
|
@ -260,14 +274,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
EmitVal(sb, Underlying(p.TypeKind), $"{a}.Value", p.TypeNameForTypeof, i + " ");
|
||||
sb.AppendLine($"{i}}}");
|
||||
sb.AppendLine($"{i}else context.WriteByte(BinaryTypeCode.PropertySkip);");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
sb.AppendLine($"{i}skip_{p.Name}:;");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -310,6 +317,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i);
|
||||
break;
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i}skip_{p.Name}:;");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -653,12 +662,37 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine(" internal static void Register()");
|
||||
sb.AppendLine(" {");
|
||||
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("}");
|
||||
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
|
||||
|
||||
private static bool IsNullableVT(ITypeSymbol t) =>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ public abstract class AcSerializerOptions
|
|||
set => _referenceHandling = value;
|
||||
}
|
||||
|
||||
private ReferenceHandlingMode _referenceHandling = ReferenceHandlingMode.OnlyId;
|
||||
private ReferenceHandlingMode _referenceHandling = ReferenceHandlingMode.All;
|
||||
|
||||
private readonly byte _maxDepth = byte.MaxValue;
|
||||
private readonly bool _throwOnCircularReference = true;
|
||||
private readonly PropertyMapperDelegate? _propertyMapper;
|
||||
|
|
|
|||
|
|
@ -223,10 +223,11 @@ public static partial class AcBinarySerializer
|
|||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool IsDirectObjectWrite => !UseMetadata && !HasPropertyFilter;
|
||||
public bool IsDirectObjectWrite => !UseMetadata;
|
||||
//public bool FastWire { get; private set; }
|
||||
public byte MinStringInternLength => Options.MinStringInternLength;
|
||||
public byte MaxStringInternLength => Options.MaxStringInternLength;
|
||||
|
|
@ -847,6 +848,7 @@ public static partial class AcBinarySerializer
|
|||
property.Name,
|
||||
property.PropertyType,
|
||||
property.DynamicGetter);
|
||||
|
||||
return PropertyFilter(context);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1089,7 +1089,7 @@ public static partial class AcBinarySerializer
|
|||
if (context.UseGeneratedCode)
|
||||
{
|
||||
var generatedWriter = wrapper.GeneratedWriter;
|
||||
if (generatedWriter != null && !hasPropertyFilter && !context.UseMetadata)
|
||||
if (generatedWriter != null && !context.UseMetadata)
|
||||
{
|
||||
generatedWriter.WriteProperties(value, context, nextDepth);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public readonly struct BinaryPropertyFilterContext
|
|||
private readonly object? _instance;
|
||||
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;
|
||||
DeclaringType = declaringType;
|
||||
|
|
|
|||
Loading…
Reference in New Issue