blob: 242e277a142a275f3af4efaa5210ad073f2a3c57 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
using System.Collections.Immutable;
using System.Globalization;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace Apache.Fory.Generator;
[Generator(LanguageNames.CSharp)]
public sealed class ForyObjectGenerator : IIncrementalGenerator
{
private static readonly SymbolDisplayFormat FullNameFormat =
SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(
SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
private static readonly DiagnosticDescriptor GenericTypeNotSupported = new(
id: "FORY001",
title: "Generic types are not supported by ForyObject generator",
messageFormat: "Type '{0}' is generic and is not supported by [ForyObject]",
category: "Fory",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
private static readonly DiagnosticDescriptor MissingCtor = new(
id: "FORY002",
title: "Missing parameterless constructor",
messageFormat: "Class '{0}' must declare an accessible parameterless constructor for [ForyObject]",
category: "Fory",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
private static readonly DiagnosticDescriptor UnsupportedSchemaType = new(
id: "FORY003",
title: "Unsupported Fory field schema type",
messageFormat: "Member '{0}' uses unsupported [ForyField] schema descriptor for type '{1}'",
category: "Fory",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValuesProvider<TypeModel?> typeModels = context.SyntaxProvider
.ForAttributeWithMetadataName(
"Apache.Fory.ForyObjectAttribute",
static (node, _) => node is TypeDeclarationSyntax || node is EnumDeclarationSyntax,
static (syntaxContext, ct) => BuildTypeModel(syntaxContext, ct))
.Where(static m => m is not null);
context.RegisterSourceOutput(
typeModels.Collect(),
static (spc, models) => Emit(spc, models));
}
private static void Emit(SourceProductionContext context, ImmutableArray<TypeModel?> maybeModels)
{
if (maybeModels.IsDefaultOrEmpty)
{
return;
}
Dictionary<string, TypeModel> models = new(StringComparer.Ordinal);
foreach (TypeModel? maybeModel in maybeModels)
{
if (maybeModel is null)
{
continue;
}
models[maybeModel.TypeName] = maybeModel;
}
if (models.Count == 0)
{
return;
}
StringBuilder sb = new();
sb.AppendLine("// <auto-generated/>");
sb.AppendLine("#nullable enable");
sb.AppendLine("namespace Apache.Fory.Generated;");
sb.AppendLine();
foreach (KeyValuePair<string, TypeModel> entry in models.OrderBy(kv => kv.Key, StringComparer.Ordinal))
{
TypeModel model = entry.Value;
if (model.Kind == DeclKind.Struct || model.Kind == DeclKind.Class)
{
EmitObjectSerializer(sb, model);
sb.AppendLine();
}
}
sb.AppendLine("internal static class __ForyGeneratedModuleInitializer");
sb.AppendLine("{");
sb.AppendLine(" [global::System.Runtime.CompilerServices.ModuleInitializer]");
sb.AppendLine(" internal static void Register()");
sb.AppendLine(" {");
foreach (KeyValuePair<string, TypeModel> entry in models.OrderBy(kv => kv.Key, StringComparer.Ordinal))
{
TypeModel model = entry.Value;
if (model.Kind == DeclKind.Enum)
{
sb.AppendLine(
$" global::Apache.Fory.TypeResolver.RegisterGenerated<{model.TypeName}, global::Apache.Fory.EnumSerializer<{model.TypeName}>>();");
}
else
{
sb.AppendLine(
$" global::Apache.Fory.TypeResolver.RegisterGenerated<{model.TypeName}, {model.SerializerName}>();");
}
}
sb.AppendLine(" }");
sb.AppendLine("}");
context.AddSource("Fory.GeneratedSerializers.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
}
private static void EmitObjectSerializer(StringBuilder sb, TypeModel model)
{
sb.AppendLine(
$"file sealed class {model.SerializerName} : global::Apache.Fory.Serializer<{model.TypeName}>");
sb.AppendLine("{");
sb.AppendLine(" private static readonly object __ForyTypeMetaCacheLock = new();");
sb.AppendLine(" private static ulong __ForyTypeMetaResolverVersion;");
sb.AppendLine(" private static ulong __ForyTypeMetaHeaderHashNoTrackRef;");
sb.AppendLine(" private static ulong __ForyTypeMetaHeaderHashTrackRef;");
sb.AppendLine(" private static global::Apache.Fory.TypeMeta? __ForyLastTypeMetaNoTrackRef;");
sb.AppendLine(" private static bool __ForyLastTypeMetaMatchedNoTrackRef;");
sb.AppendLine(" private static global::Apache.Fory.TypeMeta? __ForyLastTypeMetaTrackRef;");
sb.AppendLine(" private static bool __ForyLastTypeMetaMatchedTrackRef;");
sb.AppendLine(
$" private const bool __ForyAllFieldsBuiltIn = {BoolLiteral(model.SortedMembers.All(m => m.DynamicAnyKind == DynamicAnyKind.None && m.Classification.IsBuiltIn))};");
sb.AppendLine(
" private static global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo>? __ForyTypeMetaFieldsNoTrackRef;");
sb.AppendLine(
" private static global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo>? __ForyTypeMetaFieldsTrackRef;");
if (model.SortedMembers.Length > 0)
{
sb.AppendLine();
}
sb.AppendLine(" private static global::Apache.Fory.RefMode __ForyRefMode(bool nullable, bool trackRef)");
sb.AppendLine(" {");
sb.AppendLine(" if (trackRef)");
sb.AppendLine(" {");
sb.AppendLine(" return global::Apache.Fory.RefMode.Tracking;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" return nullable ? global::Apache.Fory.RefMode.NullOnly : global::Apache.Fory.RefMode.None;");
sb.AppendLine(" }");
sb.AppendLine();
foreach (MemberModel member in model.SortedMembers)
{
if (member.FieldCodec is not null)
{
EmitFieldCodecMethods(sb, member);
}
}
EmitCompatibleFieldCodecMethods(sb, model);
sb.AppendLine(" private static bool __ForyCanReadCompatiblePrimitive(global::Apache.Fory.TypeId typeId)");
sb.AppendLine(" {");
sb.AppendLine(" return typeId switch");
sb.AppendLine(" {");
sb.AppendLine(" global::Apache.Fory.TypeId.Bool or");
sb.AppendLine(" global::Apache.Fory.TypeId.Int8 or");
sb.AppendLine(" global::Apache.Fory.TypeId.Int16 or");
sb.AppendLine(" global::Apache.Fory.TypeId.Int32 or");
sb.AppendLine(" global::Apache.Fory.TypeId.VarInt32 or");
sb.AppendLine(" global::Apache.Fory.TypeId.Int64 or");
sb.AppendLine(" global::Apache.Fory.TypeId.VarInt64 or");
sb.AppendLine(" global::Apache.Fory.TypeId.TaggedInt64 or");
sb.AppendLine(" global::Apache.Fory.TypeId.UInt8 or");
sb.AppendLine(" global::Apache.Fory.TypeId.UInt16 or");
sb.AppendLine(" global::Apache.Fory.TypeId.UInt32 or");
sb.AppendLine(" global::Apache.Fory.TypeId.VarUInt32 or");
sb.AppendLine(" global::Apache.Fory.TypeId.UInt64 or");
sb.AppendLine(" global::Apache.Fory.TypeId.VarUInt64 or");
sb.AppendLine(" global::Apache.Fory.TypeId.TaggedUInt64 or");
sb.AppendLine(" global::Apache.Fory.TypeId.Float16 or");
sb.AppendLine(" global::Apache.Fory.TypeId.BFloat16 or");
sb.AppendLine(" global::Apache.Fory.TypeId.Float32 or");
sb.AppendLine(" global::Apache.Fory.TypeId.Float64 or");
sb.AppendLine(" global::Apache.Fory.TypeId.String => true,");
sb.AppendLine(" _ => false,");
sb.AppendLine(" };");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" private static object __ForyReadCompatiblePrimitivePayload(global::Apache.Fory.TypeId typeId, global::Apache.Fory.ReadContext context)");
sb.AppendLine(" {");
sb.AppendLine(" return typeId switch");
sb.AppendLine(" {");
sb.AppendLine(" global::Apache.Fory.TypeId.Bool => context.Reader.ReadUInt8() != 0,");
sb.AppendLine(" global::Apache.Fory.TypeId.Int8 => context.Reader.ReadInt8(),");
sb.AppendLine(" global::Apache.Fory.TypeId.Int16 => context.Reader.ReadInt16(),");
sb.AppendLine(" global::Apache.Fory.TypeId.Int32 => context.Reader.ReadInt32(),");
sb.AppendLine(" global::Apache.Fory.TypeId.VarInt32 => context.Reader.ReadVarInt32(),");
sb.AppendLine(" global::Apache.Fory.TypeId.Int64 => context.Reader.ReadInt64(),");
sb.AppendLine(" global::Apache.Fory.TypeId.VarInt64 => context.Reader.ReadVarInt64(),");
sb.AppendLine(" global::Apache.Fory.TypeId.TaggedInt64 => context.Reader.ReadTaggedInt64(),");
sb.AppendLine(" global::Apache.Fory.TypeId.UInt8 => context.Reader.ReadUInt8(),");
sb.AppendLine(" global::Apache.Fory.TypeId.UInt16 => context.Reader.ReadUInt16(),");
sb.AppendLine(" global::Apache.Fory.TypeId.UInt32 => context.Reader.ReadUInt32(),");
sb.AppendLine(" global::Apache.Fory.TypeId.VarUInt32 => context.Reader.ReadVarUInt32(),");
sb.AppendLine(" global::Apache.Fory.TypeId.UInt64 => context.Reader.ReadUInt64(),");
sb.AppendLine(" global::Apache.Fory.TypeId.VarUInt64 => context.Reader.ReadVarUInt64(),");
sb.AppendLine(" global::Apache.Fory.TypeId.TaggedUInt64 => context.Reader.ReadTaggedUInt64(),");
sb.AppendLine(" global::Apache.Fory.TypeId.Float16 => global::System.BitConverter.UInt16BitsToHalf(context.Reader.ReadUInt16()),");
sb.AppendLine(" global::Apache.Fory.TypeId.BFloat16 => global::Apache.Fory.BFloat16.FromBits(context.Reader.ReadUInt16()),");
sb.AppendLine(" global::Apache.Fory.TypeId.Float32 => context.Reader.ReadFloat32(),");
sb.AppendLine(" global::Apache.Fory.TypeId.Float64 => context.Reader.ReadFloat64(),");
sb.AppendLine(" global::Apache.Fory.TypeId.String => global::Apache.Fory.StringSerializer.ReadString(context),");
sb.AppendLine(" _ => throw new global::Apache.Fory.InvalidDataException($\"unsupported compatible primitive type id {typeId}\"),");
sb.AppendLine(" };");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" private static T __ForyReadCompatibleField<T>(");
sb.AppendLine(" global::Apache.Fory.ReadContext context,");
sb.AppendLine(" global::Apache.Fory.TypeMetaFieldType fieldType,");
sb.AppendLine(" global::Apache.Fory.RefMode refMode,");
sb.AppendLine(" bool readTypeInfo)");
sb.AppendLine(" {");
sb.AppendLine(" global::Apache.Fory.TypeId typeId = (global::Apache.Fory.TypeId)fieldType.TypeId;");
sb.AppendLine(" if (!readTypeInfo && __ForyCanReadCompatiblePrimitive(typeId))");
sb.AppendLine(" {");
sb.AppendLine(" object? value;");
sb.AppendLine(" switch (refMode)");
sb.AppendLine(" {");
sb.AppendLine(" case global::Apache.Fory.RefMode.None:");
sb.AppendLine(" value = __ForyReadCompatiblePrimitivePayload(typeId, context);");
sb.AppendLine(" break;");
sb.AppendLine(" case global::Apache.Fory.RefMode.NullOnly:");
sb.AppendLine(" {");
sb.AppendLine(" sbyte refFlag = context.Reader.ReadInt8();");
sb.AppendLine(" if (refFlag == (sbyte)global::Apache.Fory.RefFlag.Null)");
sb.AppendLine(" {");
sb.AppendLine(" value = null;");
sb.AppendLine(" }");
sb.AppendLine(" else if (refFlag == (sbyte)global::Apache.Fory.RefFlag.NotNullValue)");
sb.AppendLine(" {");
sb.AppendLine(" value = __ForyReadCompatiblePrimitivePayload(typeId, context);");
sb.AppendLine(" }");
sb.AppendLine(" else");
sb.AppendLine(" {");
sb.AppendLine(" throw new global::Apache.Fory.InvalidDataException($\"invalid compatible nullOnly ref flag {refFlag}\");");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" break;");
sb.AppendLine(" }");
sb.AppendLine(" default:");
sb.AppendLine(" return context.TypeResolver.GetSerializer<T>().Read(context, refMode, readTypeInfo);");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" if (value is null)");
sb.AppendLine(" {");
sb.AppendLine(" return default!;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" if (value is T typedValue)");
sb.AppendLine(" {");
sb.AppendLine(" return typedValue;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" try");
sb.AppendLine(" {");
sb.AppendLine(" return (T)value;");
sb.AppendLine(" }");
sb.AppendLine(" catch (global::System.InvalidCastException ex)");
sb.AppendLine(" {");
sb.AppendLine(" throw new global::Apache.Fory.InvalidDataException($\"cannot cast compatible field value of type {value.GetType()} to {typeof(T)}: {ex.Message}\");");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" return context.TypeResolver.GetSerializer<T>().Read(context, refMode, readTypeInfo);");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
" private static global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo> __ForyBuildTypeMetaFields(bool trackRef)");
sb.AppendLine(" {");
if (model.SortedMembers.Length == 0)
{
sb.AppendLine(" return global::System.Array.Empty<global::Apache.Fory.TypeMetaFieldInfo>();");
}
else
{
sb.AppendLine(" return new global::Apache.Fory.TypeMetaFieldInfo[]");
sb.AppendLine(" {");
foreach (MemberModel member in model.SortedMembers)
{
sb.AppendLine(
$" new global::Apache.Fory.TypeMetaFieldInfo({BuildTypeMetaFieldIdExpression(member.FieldId)}, \"{EscapeString(member.FieldIdentifier)}\", {BuildTypeMetaExpression(member.TypeMeta, "trackRef")}),");
}
sb.AppendLine(" };");
}
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
" private bool __ForyMatchesTypeMeta(global::Apache.Fory.TypeMeta typeMeta, bool trackRef)");
sb.AppendLine(" {");
sb.AppendLine(
" global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo> expectedFields = TypeMetaFields(trackRef);");
sb.AppendLine(" if (typeMeta.Fields.Count != expectedFields.Count)");
sb.AppendLine(" {");
sb.AppendLine(" return false;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" for (int i = 0; i < expectedFields.Count; i++)");
sb.AppendLine(" {");
sb.AppendLine(
" global::Apache.Fory.TypeMetaFieldInfo remoteField = typeMeta.Fields[i];");
sb.AppendLine(
" global::Apache.Fory.TypeMetaFieldInfo localField = expectedFields[i];");
sb.AppendLine(" if (remoteField.FieldId.HasValue && localField.FieldId.HasValue)");
sb.AppendLine(" {");
sb.AppendLine(
" if (remoteField.FieldId.Value != localField.FieldId.Value || !remoteField.FieldType.Equals(localField.FieldType))");
sb.AppendLine(" {");
sb.AppendLine(" return false;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" continue;");
sb.AppendLine(" }");
sb.AppendLine(
" if (remoteField.FieldName != localField.FieldName || !remoteField.FieldType.Equals(localField.FieldType))");
sb.AppendLine(" {");
sb.AppendLine(" return false;");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" return true;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
" private static void __ForyEnsureTypeMetaCache(global::Apache.Fory.TypeResolver typeResolver)");
sb.AppendLine(" {");
sb.AppendLine(" ulong resolverVersion = typeResolver.VersionHash();");
sb.AppendLine(" if (__ForyTypeMetaResolverVersion == resolverVersion)");
sb.AppendLine(" {");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" lock (__ForyTypeMetaCacheLock)");
sb.AppendLine(" {");
sb.AppendLine(" if (__ForyTypeMetaResolverVersion == resolverVersion)");
sb.AppendLine(" {");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
$" global::Apache.Fory.TypeInfo typeInfo = typeResolver.GetTypeInfo<{model.TypeName}>();");
sb.AppendLine(
" __ForyTypeMetaHeaderHashNoTrackRef = typeInfo.GetTypeMetaHeaderHash(false);");
sb.AppendLine(
" __ForyTypeMetaHeaderHashTrackRef = typeInfo.GetTypeMetaHeaderHash(true);");
sb.AppendLine(" __ForyLastTypeMetaNoTrackRef = null;");
sb.AppendLine(" __ForyLastTypeMetaMatchedNoTrackRef = false;");
sb.AppendLine(" __ForyLastTypeMetaTrackRef = null;");
sb.AppendLine(" __ForyLastTypeMetaMatchedTrackRef = false;");
sb.AppendLine(" __ForyTypeMetaResolverVersion = resolverVersion;");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
" private bool __ForyMatchesCachedTypeMeta(global::Apache.Fory.TypeMeta typeMeta, bool trackRef, global::Apache.Fory.TypeResolver typeResolver)");
sb.AppendLine(" {");
sb.AppendLine(" if (trackRef)");
sb.AppendLine(" {");
sb.AppendLine(
" if (global::System.Object.ReferenceEquals(__ForyLastTypeMetaTrackRef, typeMeta))");
sb.AppendLine(" {");
sb.AppendLine(" return __ForyLastTypeMetaMatchedTrackRef;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" __ForyEnsureTypeMetaCache(typeResolver);");
sb.AppendLine();
sb.AppendLine(" bool matched = false;");
sb.AppendLine(" if (typeMeta.HeaderHash == __ForyTypeMetaHeaderHashTrackRef)");
sb.AppendLine(" {");
sb.AppendLine(" matched = __ForyMatchesTypeMeta(typeMeta, true);");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" __ForyLastTypeMetaTrackRef = typeMeta;");
sb.AppendLine(" __ForyLastTypeMetaMatchedTrackRef = matched;");
sb.AppendLine(" return matched;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
" if (global::System.Object.ReferenceEquals(__ForyLastTypeMetaNoTrackRef, typeMeta))");
sb.AppendLine(" {");
sb.AppendLine(" return __ForyLastTypeMetaMatchedNoTrackRef;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" __ForyEnsureTypeMetaCache(typeResolver);");
sb.AppendLine();
sb.AppendLine(" bool noTrackMatched = false;");
sb.AppendLine(" if (typeMeta.HeaderHash == __ForyTypeMetaHeaderHashNoTrackRef)");
sb.AppendLine(" {");
sb.AppendLine(" noTrackMatched = __ForyMatchesTypeMeta(typeMeta, false);");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" __ForyLastTypeMetaNoTrackRef = typeMeta;");
sb.AppendLine(" __ForyLastTypeMetaMatchedNoTrackRef = noTrackMatched;");
sb.AppendLine(" return noTrackMatched;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" private static uint? __ForySchemaHashNoTrackRef;");
sb.AppendLine();
sb.AppendLine(" private static uint __ForySchemaHash(bool trackRef, global::Apache.Fory.TypeResolver typeResolver)");
sb.AppendLine(" {");
sb.AppendLine(" if (!trackRef && __ForySchemaHashNoTrackRef.HasValue)");
sb.AppendLine(" {");
sb.AppendLine(" return __ForySchemaHashNoTrackRef.Value;");
sb.AppendLine(" }");
sb.AppendLine();
sb.Append(" uint value = global::Apache.Fory.SchemaHash.StructHash32(");
sb.Append(BuildSchemaFingerprintExpression(model.Members));
sb.AppendLine(");");
sb.AppendLine(" if (!trackRef)");
sb.AppendLine(" {");
sb.AppendLine(" __ForySchemaHashNoTrackRef = value;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" return value;");
sb.AppendLine(" }");
sb.AppendLine();
if (model.Kind == DeclKind.Class)
{
sb.AppendLine($" public override {model.TypeName} DefaultValue => null!;");
}
else
{
sb.AppendLine($" public override {model.TypeName} DefaultValue => new {model.TypeName}();");
}
sb.AppendLine();
sb.AppendLine(" private global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo> TypeMetaFields(bool trackRef)");
sb.AppendLine(" {");
sb.AppendLine(" if (trackRef)");
sb.AppendLine(" {");
sb.AppendLine(
" return __ForyTypeMetaFieldsTrackRef ??= __ForyBuildTypeMetaFields(true);");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
" return __ForyTypeMetaFieldsNoTrackRef ??= __ForyBuildTypeMetaFields(false);");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
$" public override void WriteData(global::Apache.Fory.WriteContext context, in {model.TypeName} value, bool hasGenerics)");
sb.AppendLine(" {");
sb.AppendLine(" _ = hasGenerics;");
sb.AppendLine(" if (context.Compatible)");
sb.AppendLine(" {");
if (model.SortedMembers.Length == 0)
{
sb.AppendLine(" return;");
}
else
{
foreach (MemberModel member in model.SortedMembers)
{
EmitWriteMember(sb, member, true);
}
sb.AppendLine(" return;");
}
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" uint schemaHash = __ForySchemaHash(context.TrackRef, context.TypeResolver);");
sb.AppendLine(" context.Writer.WriteInt32(unchecked((int)schemaHash));");
foreach (MemberModel member in model.SortedMembers)
{
EmitWriteMember(sb, member, false);
}
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine($" private {model.TypeName} ReadDataWithoutTypeMeta(global::Apache.Fory.ReadContext context)");
sb.AppendLine(" {");
sb.AppendLine($" {model.TypeName} valueNoTypeMeta = new {model.TypeName}();");
if (model.Kind == DeclKind.Class)
{
sb.AppendLine(" context.StoreRef(valueNoTypeMeta);");
}
foreach (MemberModel member in model.SortedMembers)
{
EmitReadMemberAssignment(
sb,
member,
BuildWriteRefModeExpression(member),
BuildFieldTypeInfoLiteral(member),
"valueNoTypeMeta",
"CompatNoTypeMeta",
4,
true);
}
sb.AppendLine(" return valueNoTypeMeta;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine($" public override {model.TypeName} ReadData(global::Apache.Fory.ReadContext context)");
sb.AppendLine(" {");
sb.AppendLine(" if (context.Compatible)");
sb.AppendLine(" {");
sb.AppendLine(
$" global::Apache.Fory.TypeMeta? maybeTypeMeta = context.GetTypeMeta<{model.TypeName}>();");
sb.AppendLine(" if (maybeTypeMeta is null)");
sb.AppendLine(" {");
sb.AppendLine(" return ReadDataWithoutTypeMeta(context);");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" global::Apache.Fory.TypeMeta typeMeta = maybeTypeMeta;");
sb.AppendLine($" {model.TypeName} value = new {model.TypeName}();");
if (model.Kind == DeclKind.Class)
{
sb.AppendLine(" context.StoreRef(value);");
}
sb.AppendLine(" bool __ForyExactTypeMeta = __ForyMatchesCachedTypeMeta(typeMeta, context.TrackRef, context.TypeResolver);");
sb.AppendLine(" if (__ForyAllFieldsBuiltIn && __ForyExactTypeMeta)");
sb.AppendLine(" {");
foreach (MemberModel member in model.SortedMembers)
{
EmitReadMemberAssignment(
sb,
member,
BuildWriteRefModeExpression(member),
"false",
"value",
"CompatExact",
6,
true);
}
sb.AppendLine(" return value;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" if (__ForyExactTypeMeta)");
sb.AppendLine(" {");
foreach (MemberModel member in model.SortedMembers)
{
EmitReadMemberAssignment(
sb,
member,
BuildWriteRefModeExpression(member),
BuildFieldTypeInfoLiteral(member),
"value",
"CompatExactTyped",
6,
true);
}
sb.AppendLine(" return value;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" for (int i = 0; i < typeMeta.Fields.Count; i++)");
sb.AppendLine(" {");
sb.AppendLine(" global::Apache.Fory.TypeMetaFieldInfo remoteField = typeMeta.Fields[i];");
sb.AppendLine(" global::Apache.Fory.RefMode remoteRefMode = __ForyRefMode(remoteField.FieldType.Nullable, remoteField.FieldType.TrackRef);");
sb.AppendLine(" switch (remoteField.AssignedFieldId)");
sb.AppendLine(" {");
for (int idx = 0; idx < model.SortedMembers.Length; idx++)
{
MemberModel member = model.SortedMembers[idx];
sb.AppendLine($" case {idx}:");
sb.AppendLine(" {");
EmitReadMemberAssignment(
sb,
member,
"remoteRefMode",
BuildFieldTypeInfoLiteral(member),
"value",
"Compat",
7,
false);
sb.AppendLine(" break;");
sb.AppendLine(" }");
}
sb.AppendLine(" default:");
sb.AppendLine(" global::Apache.Fory.FieldSkipper.SkipFieldValue(context, remoteField.FieldType);");
sb.AppendLine(" break;");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine(" return value;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" uint schemaHash = unchecked((uint)context.Reader.ReadInt32());");
sb.AppendLine(" if (context.CheckStructVersion)");
sb.AppendLine(" {");
sb.AppendLine(" uint expectedHash = __ForySchemaHash(context.TrackRef, context.TypeResolver);");
sb.AppendLine(" if (schemaHash != expectedHash)");
sb.AppendLine(" {");
sb.AppendLine(" throw new global::Apache.Fory.InvalidDataException($\"class version hash mismatch: expected {expectedHash}, got {schemaHash}\");");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine($" {model.TypeName} valueSchema = new {model.TypeName}();");
if (model.Kind == DeclKind.Class)
{
sb.AppendLine(" context.StoreRef(valueSchema);");
}
foreach (MemberModel member in model.SortedMembers)
{
EmitReadMemberAssignment(sb, member, BuildWriteRefModeExpression(member), "false", "valueSchema", "Schema", 2, true);
}
sb.AppendLine(" return valueSchema;");
sb.AppendLine(" }");
sb.AppendLine("}");
}
private static void EmitFieldCodecMethods(StringBuilder sb, MemberModel member)
{
FieldCodecModel codec = member.FieldCodec!;
string memberId = Sanitize(member.Name);
sb.AppendLine(
$" private static void __ForyWrite{memberId}Field(global::Apache.Fory.WriteContext context, {member.TypeName} value, global::Apache.Fory.RefMode refMode)");
sb.AppendLine(" {");
sb.AppendLine(" if (refMode == global::Apache.Fory.RefMode.NullOnly)");
sb.AppendLine(" {");
if (member.IsNullableValueType)
{
sb.AppendLine(" if (!value.HasValue)");
}
else
{
sb.AppendLine(" if (value is null)");
}
sb.AppendLine(" {");
sb.AppendLine(" context.Writer.WriteInt8((sbyte)global::Apache.Fory.RefFlag.Null);");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" context.Writer.WriteInt8((sbyte)global::Apache.Fory.RefFlag.NotNullValue);");
sb.AppendLine(" }");
string writeValueExpr = member.IsNullableValueType ? "value.Value" : member.IsNullable ? "value!" : "value";
int id = 0;
EmitWritePayload(sb, codec, writeValueExpr, 2, ref id);
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
$" private static {member.TypeName} __ForyRead{memberId}Field(global::Apache.Fory.ReadContext context, global::Apache.Fory.RefMode refMode)");
sb.AppendLine(" {");
sb.AppendLine(" if (refMode == global::Apache.Fory.RefMode.NullOnly)");
sb.AppendLine(" {");
sb.AppendLine(" sbyte refFlag = context.Reader.ReadInt8();");
sb.AppendLine(" if (refFlag == (sbyte)global::Apache.Fory.RefFlag.Null)");
sb.AppendLine(" {");
sb.AppendLine($" return ({member.TypeName})default!;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" if (refFlag != (sbyte)global::Apache.Fory.RefFlag.NotNullValue)");
sb.AppendLine(" {");
sb.AppendLine(" throw new global::Apache.Fory.InvalidDataException($\"invalid nullOnly ref flag {refFlag}\");");
sb.AppendLine(" }");
sb.AppendLine(" }");
string resultVar = $"__{memberId}Value";
id = 0;
EmitReadPayload(sb, codec, resultVar, 2, ref id);
sb.AppendLine($" return {resultVar};");
sb.AppendLine(" }");
sb.AppendLine();
}
private static void EmitCompatibleFieldCodecMethods(StringBuilder sb, TypeModel model)
{
bool hasCompatibleField = false;
foreach (MemberModel member in model.SortedMembers)
{
if (member.FieldCodec is not null &&
TryBuildCompatibleListArrayReadCodec(member.FieldCodec, out _))
{
hasCompatibleField = true;
break;
}
}
if (!hasCompatibleField)
{
return;
}
sb.AppendLine(" private static class __ForyCompatibleFieldReaders");
sb.AppendLine(" {");
foreach (MemberModel member in model.SortedMembers)
{
if (member.FieldCodec is not null &&
TryBuildCompatibleListArrayReadCodec(member.FieldCodec, out FieldCodecModel? alternateCodec))
{
EmitCompatibleFieldCodecMethod(sb, member, member.FieldCodec, alternateCodec);
}
}
sb.AppendLine(" }");
sb.AppendLine();
}
private static void EmitCompatibleFieldCodecMethod(
StringBuilder sb,
MemberModel member,
FieldCodecModel codec,
FieldCodecModel alternateCodec)
{
string memberId = Sanitize(member.Name);
sb.AppendLine(" [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]");
sb.AppendLine(
$" internal static {member.TypeName} Read{memberId}ListArrayBridge(global::Apache.Fory.ReadContext context, global::Apache.Fory.TypeMetaFieldType remoteFieldType, global::Apache.Fory.RefMode refMode)");
sb.AppendLine(" {");
sb.AppendLine(" if (remoteFieldType.TypeId == " + alternateCodec.TypeId + ")");
sb.AppendLine(" {");
if (codec.Kind == FieldCodecKind.PackedArray)
{
sb.AppendLine(" if (remoteFieldType.Generics.Count != 1)");
sb.AppendLine(" {");
sb.AppendLine(" throw new global::Apache.Fory.InvalidDataException(\"compatible list to array field requires one element schema\");");
sb.AppendLine(" }");
}
sb.AppendLine(" if (refMode == global::Apache.Fory.RefMode.NullOnly)");
sb.AppendLine(" {");
sb.AppendLine(" sbyte refFlag = context.Reader.ReadInt8();");
sb.AppendLine(" if (refFlag == (sbyte)global::Apache.Fory.RefFlag.Null)");
sb.AppendLine(" {");
sb.AppendLine($" return ({member.TypeName})default!;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" if (refFlag != (sbyte)global::Apache.Fory.RefFlag.NotNullValue)");
sb.AppendLine(" {");
sb.AppendLine(" throw new global::Apache.Fory.InvalidDataException($\"invalid nullOnly ref flag {refFlag}\");");
sb.AppendLine(" }");
sb.AppendLine(" }");
int id = 0;
string compatibleResultVar = $"__{memberId}CompatibleValue";
if (codec.Kind == FieldCodecKind.PackedArray && alternateCodec.Kind == FieldCodecKind.List)
{
EmitReadCompatibleListArrayPayload(sb, codec, compatibleResultVar, 4, ref id);
}
else
{
EmitReadPayload(sb, alternateCodec, compatibleResultVar, 4, ref id);
}
sb.AppendLine($" return {compatibleResultVar};");
sb.AppendLine(" }");
sb.AppendLine(" throw new global::Apache.Fory.InvalidDataException($\"unsupported compatible field schema pair: local " + codec.TypeId + ", remote {remoteFieldType.TypeId}\");");
sb.AppendLine(" }");
}
private static bool TryBuildCompatibleListArrayReadCodec(FieldCodecModel codec, out FieldCodecModel compatibleCodec)
{
if (codec.Kind == FieldCodecKind.PackedArray)
{
uint elementTypeId = PackedArrayElementTypeId(codec.TypeId);
compatibleCodec = new FieldCodecModel(
FieldCodecKind.List,
22,
codec.TypeName,
codec.Nullable,
codec.NullableValueType,
codec.CarrierKind,
ImmutableArray.Create(new FieldCodecModel(
FieldCodecKind.Scalar,
elementTypeId,
PackedArrayElementTypeName(codec.TypeId),
false,
false,
CarrierKind.Value,
ImmutableArray<FieldCodecModel>.Empty)));
return true;
}
if (codec.Kind == FieldCodecKind.List &&
codec.Generics.Length == 1 &&
TryResolveArrayTypeIdForElement(codec.Generics[0].TypeId) is uint arrayTypeId)
{
compatibleCodec = new FieldCodecModel(
FieldCodecKind.PackedArray,
arrayTypeId,
codec.TypeName,
codec.Nullable,
codec.NullableValueType,
codec.CarrierKind,
ImmutableArray<FieldCodecModel>.Empty);
return true;
}
compatibleCodec = codec;
return false;
}
private static void EmitReadCompatibleListArrayPayload(
StringBuilder sb,
FieldCodecModel codec,
string targetVar,
int indentLevel,
ref int id)
{
string indent = new(' ', indentLevel * 4);
string lengthVar = $"__foryLength{id++}";
string headerVar = $"__foryHeader{id++}";
string declaredVar = $"__foryDeclared{id++}";
string sameTypeVar = $"__forySameType{id++}";
sb.AppendLine($"{indent}int {lengthVar} = checked((int)context.Reader.ReadVarUInt32());");
sb.AppendLine($"{indent}if ({lengthVar} != 0)");
sb.AppendLine($"{indent}{{");
string innerIndent = indent + " ";
sb.AppendLine($"{innerIndent}byte {headerVar} = context.Reader.ReadUInt8();");
sb.AppendLine($"{innerIndent}if (({headerVar} & 0b0000_0011) != 0)");
sb.AppendLine($"{innerIndent}{{");
sb.AppendLine($"{innerIndent} throw new global::Apache.Fory.InvalidDataException(\"compatible list to array field requires non-null elements\");");
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{innerIndent}bool {declaredVar} = ({headerVar} & 0b0000_0100) != 0;");
sb.AppendLine($"{innerIndent}bool {sameTypeVar} = ({headerVar} & 0b0000_1000) != 0;");
sb.AppendLine($"{innerIndent}if (!{sameTypeVar})");
sb.AppendLine($"{innerIndent}{{");
sb.AppendLine($"{innerIndent} throw new global::Apache.Fory.InvalidDataException(\"compatible list to array field requires same-type elements\");");
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{innerIndent}if (!{declaredVar})");
sb.AppendLine($"{innerIndent}{{");
sb.AppendLine($"{innerIndent} uint __foryWireTypeId = context.Reader.ReadUInt8();");
sb.AppendLine($"{innerIndent} if (__foryWireTypeId != remoteFieldType.Generics[0].TypeId)");
sb.AppendLine($"{innerIndent} {{");
sb.AppendLine($"{innerIndent} throw new global::Apache.Fory.TypeMismatchException(remoteFieldType.Generics[0].TypeId, __foryWireTypeId);");
sb.AppendLine($"{innerIndent} }}");
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{indent}}}");
string indexVar = $"__foryIndex{id++}";
string elementTypeName = codec.CarrierKind == CarrierKind.Array ? ElementTypeName(codec.TypeName) : PackedArrayElementTypeName(codec.TypeId);
if (codec.CarrierKind == CarrierKind.Array)
{
sb.AppendLine($"{indent}{codec.TypeName} {targetVar} = new {ElementTypeName(codec.TypeName)}[{lengthVar}];");
}
else
{
sb.AppendLine($"{indent}{codec.TypeName} {targetVar} = new({lengthVar});");
}
sb.AppendLine($"{indent}for (int {indexVar} = 0; {indexVar} < {lengthVar}; {indexVar}++)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{innerIndent}object __foryItem = __ForyReadCompatiblePrimitivePayload((global::Apache.Fory.TypeId)remoteFieldType.Generics[0].TypeId, context);");
if (codec.CarrierKind == CarrierKind.Array)
{
sb.AppendLine($"{innerIndent}{targetVar}[{indexVar}] = ({elementTypeName})__foryItem;");
}
else
{
sb.AppendLine($"{innerIndent}{targetVar}.Add(({elementTypeName})__foryItem);");
}
sb.AppendLine($"{indent}}}");
}
private static void EmitWritePayload(
StringBuilder sb,
FieldCodecModel codec,
string valueExpr,
int indentLevel,
ref int id)
{
string indent = new(' ', indentLevel * 4);
switch (codec.Kind)
{
case FieldCodecKind.Scalar:
if (!TryBuildDirectPayloadWrite(codec.TypeId, valueExpr, out string? writeCode))
{
sb.AppendLine($"{indent}context.TypeResolver.GetSerializer<{codec.TypeName}>().WriteData(context, {valueExpr}, false);");
return;
}
sb.AppendLine($"{indent}{writeCode}");
return;
case FieldCodecKind.PackedArray:
EmitWritePackedArrayPayload(sb, codec, valueExpr, indentLevel, ref id);
return;
case FieldCodecKind.List:
EmitWriteCollectionPayload(sb, codec, valueExpr, indentLevel, ref id, isSet: false);
return;
case FieldCodecKind.Set:
EmitWriteCollectionPayload(sb, codec, valueExpr, indentLevel, ref id, isSet: true);
return;
case FieldCodecKind.Map:
EmitWriteMapPayload(sb, codec, valueExpr, indentLevel, ref id);
return;
}
}
private static void EmitWritePackedArrayPayload(
StringBuilder sb,
FieldCodecModel codec,
string valueExpr,
int indentLevel,
ref int id)
{
string indent = new(' ', indentLevel * 4);
string valuesVar = $"__foryPacked{id++}";
sb.AppendLine($"{indent}{codec.TypeName} {valuesVar} = {valueExpr} ?? [];");
string countExpr = codec.CarrierKind == CarrierKind.Array ? $"{valuesVar}.Length" : $"{valuesVar}.Count";
int width = PackedArrayElementWidth(codec.TypeId);
string lengthExpr = width == 1 ? countExpr : $"checked({countExpr} * {width})";
sb.AppendLine($"{indent}context.Writer.WriteVarUInt32((uint){lengthExpr});");
string packedIndexVar = $"__foryIndex{id++}";
sb.AppendLine($"{indent}for (int {packedIndexVar} = 0; {packedIndexVar} < {countExpr}; {packedIndexVar}++)");
sb.AppendLine($"{indent}{{");
string itemExpr = $"{valuesVar}[{packedIndexVar}]";
uint elementTypeId = PackedArrayElementTypeId(codec.TypeId);
if (!TryBuildDirectPayloadWrite(elementTypeId, itemExpr, out string? writeCode))
{
throw new InvalidOperationException($"unsupported packed array type id {codec.TypeId}");
}
sb.AppendLine($"{indent} {writeCode}");
sb.AppendLine($"{indent}}}");
}
private static void EmitWriteCollectionPayload(
StringBuilder sb,
FieldCodecModel codec,
string valueExpr,
int indentLevel,
ref int id,
bool isSet)
{
string indent = new(' ', indentLevel * 4);
FieldCodecModel element = codec.Generics[0];
string valuesVar = $"__foryCollection{id++}";
sb.AppendLine($"{indent}{codec.TypeName} {valuesVar} = {valueExpr} ?? [];");
string countExpr = codec.CarrierKind == CarrierKind.Array ? $"{valuesVar}.Length" : $"{valuesVar}.Count";
sb.AppendLine($"{indent}int __foryCount{id} = {countExpr};");
string countVar = $"__foryCount{id++}";
sb.AppendLine($"{indent}context.Writer.WriteVarUInt32((uint){countVar});");
sb.AppendLine($"{indent}if ({countVar} != 0)");
sb.AppendLine($"{indent}{{");
string innerIndent = indent + " ";
string hasNullVar = $"__foryHasNull{id++}";
if (element.Nullable)
{
sb.AppendLine($"{innerIndent}bool {hasNullVar} = false;");
if (isSet)
{
sb.AppendLine($"{innerIndent}foreach ({element.TypeName} __foryItem in {valuesVar})");
sb.AppendLine($"{innerIndent}{{");
sb.AppendLine($"{innerIndent} if (__foryItem is null)");
sb.AppendLine($"{innerIndent} {{");
sb.AppendLine($"{innerIndent} {hasNullVar} = true;");
sb.AppendLine($"{innerIndent} break;");
sb.AppendLine($"{innerIndent} }}");
sb.AppendLine($"{innerIndent}}}");
}
else
{
string scanIndexVar = $"__foryIndex{id++}";
sb.AppendLine($"{innerIndent}for (int {scanIndexVar} = 0; {scanIndexVar} < {countVar}; {scanIndexVar}++)");
sb.AppendLine($"{innerIndent}{{");
string itemExpr = $"{valuesVar}[{scanIndexVar}]";
sb.AppendLine($"{innerIndent} if ({itemExpr} is null)");
sb.AppendLine($"{innerIndent} {{");
sb.AppendLine($"{innerIndent} {hasNullVar} = true;");
sb.AppendLine($"{innerIndent} break;");
sb.AppendLine($"{innerIndent} }}");
sb.AppendLine($"{innerIndent}}}");
}
}
else
{
sb.AppendLine($"{innerIndent}bool {hasNullVar} = false;");
}
string collectionHeaderVar = $"__foryHeader{id++}";
sb.AppendLine($"{innerIndent}byte {collectionHeaderVar} = 0b0000_1000 | 0b0000_0100;");
sb.AppendLine($"{innerIndent}if ({hasNullVar})");
sb.AppendLine($"{innerIndent}{{");
sb.AppendLine($"{innerIndent} {collectionHeaderVar} |= 0b0000_0010;");
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{innerIndent}context.Writer.WriteUInt8({collectionHeaderVar});");
if (isSet)
{
sb.AppendLine($"{innerIndent}foreach ({element.TypeName} __foryItem in {valuesVar})");
sb.AppendLine($"{innerIndent}{{");
EmitWriteNullableElementPayload(sb, element, "__foryItem", indentLevel + 2, ref id, hasNullVar);
sb.AppendLine($"{innerIndent}}}");
}
else
{
string writeIndexVar = $"__foryIndex{id++}";
sb.AppendLine($"{innerIndent}for (int {writeIndexVar} = 0; {writeIndexVar} < {countVar}; {writeIndexVar}++)");
sb.AppendLine($"{innerIndent}{{");
sb.AppendLine($"{innerIndent} {element.TypeName} __foryItem = {valuesVar}[{writeIndexVar}];");
EmitWriteNullableElementPayload(sb, element, "__foryItem", indentLevel + 2, ref id, hasNullVar);
sb.AppendLine($"{innerIndent}}}");
}
sb.AppendLine($"{indent}}}");
}
private static void EmitWriteNullableElementPayload(
StringBuilder sb,
FieldCodecModel element,
string itemExpr,
int indentLevel,
ref int id,
string hasNullVar)
{
string indent = new(' ', indentLevel * 4);
if (!element.Nullable)
{
EmitWritePayload(sb, element, itemExpr, indentLevel, ref id);
return;
}
sb.AppendLine($"{indent}if ({hasNullVar})");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} if ({itemExpr} is null)");
sb.AppendLine($"{indent} {{");
sb.AppendLine($"{indent} context.Writer.WriteInt8((sbyte)global::Apache.Fory.RefFlag.Null);");
sb.AppendLine($"{indent} continue;");
sb.AppendLine($"{indent} }}");
sb.AppendLine();
sb.AppendLine($"{indent} context.Writer.WriteInt8((sbyte)global::Apache.Fory.RefFlag.NotNullValue);");
string nonNullExpr = element.NullableValueType ? $"{itemExpr}.GetValueOrDefault()" : $"{itemExpr}!";
EmitWritePayload(sb, element, nonNullExpr, indentLevel + 1, ref id);
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
EmitWritePayload(sb, element, element.NullableValueType ? $"{itemExpr}.GetValueOrDefault()" : $"{itemExpr}!", indentLevel + 1, ref id);
sb.AppendLine($"{indent}}}");
}
private static void EmitWriteMapPayload(
StringBuilder sb,
FieldCodecModel codec,
string valueExpr,
int indentLevel,
ref int id)
{
string indent = new(' ', indentLevel * 4);
FieldCodecModel key = codec.Generics[0];
FieldCodecModel value = codec.Generics[1];
string mapVar = $"__foryMap{id++}";
sb.AppendLine($"{indent}{codec.TypeName} {mapVar} = {valueExpr} ?? [];");
sb.AppendLine($"{indent}context.Writer.WriteVarUInt32((uint){mapVar}.Count);");
sb.AppendLine($"{indent}foreach (global::System.Collections.Generic.KeyValuePair<{key.TypeName}, {value.TypeName}> __foryEntry in {mapVar})");
sb.AppendLine($"{indent}{{");
string innerIndent = indent + " ";
string keyNullVar = $"__foryKeyNull{id++}";
string valueNullVar = $"__foryValueNull{id++}";
if (key.Nullable)
{
sb.AppendLine($"{innerIndent}bool {keyNullVar} = __foryEntry.Key is null;");
}
else
{
sb.AppendLine($"{innerIndent}bool {keyNullVar} = false;");
}
if (value.Nullable)
{
sb.AppendLine($"{innerIndent}bool {valueNullVar} = __foryEntry.Value is null;");
}
else
{
sb.AppendLine($"{innerIndent}bool {valueNullVar} = false;");
}
string mapHeaderVar = $"__foryHeader{id++}";
sb.AppendLine($"{innerIndent}byte {mapHeaderVar} = 0;");
sb.AppendLine($"{innerIndent}if ({keyNullVar}) {mapHeaderVar} |= 0b0000_0010; else {mapHeaderVar} |= 0b0000_0100;");
sb.AppendLine($"{innerIndent}if ({valueNullVar}) {mapHeaderVar} |= 0b0001_0000; else {mapHeaderVar} |= 0b0010_0000;");
sb.AppendLine($"{innerIndent}context.Writer.WriteUInt8({mapHeaderVar});");
sb.AppendLine($"{innerIndent}if (!{keyNullVar} && !{valueNullVar})");
sb.AppendLine($"{innerIndent}{{");
sb.AppendLine($"{innerIndent} context.Writer.WriteUInt8(1);");
EmitWritePayload(sb, key, key.NullableValueType ? "__foryEntry.Key.GetValueOrDefault()" : "__foryEntry.Key!", indentLevel + 2, ref id);
EmitWritePayload(sb, value, value.NullableValueType ? "__foryEntry.Value.GetValueOrDefault()" : "__foryEntry.Value!", indentLevel + 2, ref id);
sb.AppendLine($"{innerIndent} continue;");
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{innerIndent}if (!{keyNullVar})");
sb.AppendLine($"{innerIndent}{{");
EmitWritePayload(sb, key, key.NullableValueType ? "__foryEntry.Key.GetValueOrDefault()" : "__foryEntry.Key!", indentLevel + 2, ref id);
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{innerIndent}if (!{valueNullVar})");
sb.AppendLine($"{innerIndent}{{");
EmitWritePayload(sb, value, value.NullableValueType ? "__foryEntry.Value.GetValueOrDefault()" : "__foryEntry.Value!", indentLevel + 2, ref id);
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{indent}}}");
}
private static void EmitReadPayload(
StringBuilder sb,
FieldCodecModel codec,
string targetVar,
int indentLevel,
ref int id)
{
string indent = new(' ', indentLevel * 4);
switch (codec.Kind)
{
case FieldCodecKind.Scalar:
if (TryBuildDirectPayloadRead(codec.TypeId, out string? readExpr))
{
sb.AppendLine($"{indent}{codec.TypeName} {targetVar} = {readExpr};");
}
else
{
sb.AppendLine($"{indent}{codec.TypeName} {targetVar} = context.TypeResolver.GetSerializer<{codec.TypeName}>().ReadData(context);");
}
return;
case FieldCodecKind.PackedArray:
EmitReadPackedArrayPayload(sb, codec, targetVar, indentLevel, ref id);
return;
case FieldCodecKind.List:
EmitReadCollectionPayload(sb, codec, targetVar, indentLevel, ref id, isSet: false);
return;
case FieldCodecKind.Set:
EmitReadCollectionPayload(sb, codec, targetVar, indentLevel, ref id, isSet: true);
return;
case FieldCodecKind.Map:
EmitReadMapPayload(sb, codec, targetVar, indentLevel, ref id);
return;
}
}
private static void EmitReadPackedArrayPayload(
StringBuilder sb,
FieldCodecModel codec,
string targetVar,
int indentLevel,
ref int id)
{
string indent = new(' ', indentLevel * 4);
int width = PackedArrayElementWidth(codec.TypeId);
uint elementTypeId = PackedArrayElementTypeId(codec.TypeId);
string payloadSizeVar = $"__foryPayloadSize{id++}";
string countVar = $"__foryPackedCount{id++}";
sb.AppendLine($"{indent}int {payloadSizeVar} = checked((int)context.Reader.ReadVarUInt32());");
if (width > 1)
{
int mask = width - 1;
sb.AppendLine($"{indent}if (({payloadSizeVar} & {mask}) != 0)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} throw new global::Apache.Fory.InvalidDataException(\"packed array payload size mismatch\");");
sb.AppendLine($"{indent}}}");
}
sb.AppendLine($"{indent}int {countVar} = {payloadSizeVar}{(width == 1 ? string.Empty : $" / {width}")};");
if (codec.CarrierKind == CarrierKind.Array)
{
sb.AppendLine($"{indent}{codec.TypeName} {targetVar} = new {ElementTypeName(codec.TypeName)}[{countVar}];");
}
else
{
sb.AppendLine($"{indent}{codec.TypeName} {targetVar} = new({countVar});");
}
string packedIndexVar = $"__foryIndex{id++}";
sb.AppendLine($"{indent}for (int {packedIndexVar} = 0; {packedIndexVar} < {countVar}; {packedIndexVar}++)");
sb.AppendLine($"{indent}{{");
if (!TryBuildDirectPayloadRead(elementTypeId, out string? readExpr))
{
throw new InvalidOperationException($"unsupported packed array type id {codec.TypeId}");
}
if (codec.CarrierKind == CarrierKind.Array)
{
sb.AppendLine($"{indent} {targetVar}[{packedIndexVar}] = {readExpr};");
}
else
{
sb.AppendLine($"{indent} {targetVar}.Add({readExpr});");
}
sb.AppendLine($"{indent}}}");
}
private static void EmitReadCollectionPayload(
StringBuilder sb,
FieldCodecModel codec,
string targetVar,
int indentLevel,
ref int id,
bool isSet)
{
string indent = new(' ', indentLevel * 4);
FieldCodecModel element = codec.Generics[0];
string lengthVar = $"__foryLength{id++}";
string headerVar = $"__foryHeader{id++}";
string hasNullVar = $"__foryHasNull{id++}";
string sameTypeVar = $"__forySameType{id++}";
string declaredVar = $"__foryDeclared{id++}";
sb.AppendLine($"{indent}int {lengthVar} = checked((int)context.Reader.ReadVarUInt32());");
if (isSet)
{
sb.AppendLine($"{indent}{codec.TypeName} {targetVar} = new();");
}
else if (codec.CarrierKind == CarrierKind.Array)
{
sb.AppendLine($"{indent}{codec.TypeName} {targetVar} = new {ElementTypeName(codec.TypeName)}[{lengthVar}];");
}
else
{
sb.AppendLine($"{indent}{codec.TypeName} {targetVar} = new({lengthVar});");
}
sb.AppendLine($"{indent}if ({lengthVar} != 0)");
sb.AppendLine($"{indent}{{");
string innerIndent = indent + " ";
sb.AppendLine($"{innerIndent}byte {headerVar} = context.Reader.ReadUInt8();");
sb.AppendLine($"{innerIndent}bool {hasNullVar} = ({headerVar} & 0b0000_0010) != 0;");
sb.AppendLine($"{innerIndent}bool {declaredVar} = ({headerVar} & 0b0000_0100) != 0;");
sb.AppendLine($"{innerIndent}bool {sameTypeVar} = ({headerVar} & 0b0000_1000) != 0;");
sb.AppendLine($"{innerIndent}if (!{sameTypeVar})");
sb.AppendLine($"{innerIndent}{{");
sb.AppendLine($"{innerIndent} throw new global::Apache.Fory.InvalidDataException(\"generated collection fields require same-type element payloads\");");
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{innerIndent}if (!{declaredVar})");
sb.AppendLine($"{innerIndent}{{");
EmitReadInlineTypeInfo(sb, NonNullableCodec(element), indentLevel + 2, ref id);
sb.AppendLine($"{innerIndent}}}");
string collectionIndexVar = $"__foryIndex{id++}";
sb.AppendLine($"{innerIndent}for (int {collectionIndexVar} = 0; {collectionIndexVar} < {lengthVar}; {collectionIndexVar}++)");
sb.AppendLine($"{innerIndent}{{");
EmitReadNullableElementPayload(sb, element, "__foryItem", indentLevel + 2, ref id, hasNullVar);
if (codec.CarrierKind == CarrierKind.Array)
{
sb.AppendLine($"{innerIndent} {targetVar}[{collectionIndexVar}] = __foryItem;");
}
else
{
sb.AppendLine($"{innerIndent} {targetVar}.Add(__foryItem);");
}
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{indent}}}");
}
private static void EmitReadNullableElementPayload(
StringBuilder sb,
FieldCodecModel element,
string targetVar,
int indentLevel,
ref int id,
string hasNullVar)
{
string indent = new(' ', indentLevel * 4);
sb.AppendLine($"{indent}{element.TypeName} {targetVar};");
if (element.Nullable)
{
sb.AppendLine($"{indent}if ({hasNullVar})");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} sbyte __foryRefFlag = context.Reader.ReadInt8();");
sb.AppendLine($"{indent} if (__foryRefFlag == (sbyte)global::Apache.Fory.RefFlag.Null)");
sb.AppendLine($"{indent} {{");
sb.AppendLine($"{indent} {targetVar} = ({element.TypeName})default!;");
sb.AppendLine($"{indent} }}");
sb.AppendLine($"{indent} else if (__foryRefFlag == (sbyte)global::Apache.Fory.RefFlag.NotNullValue)");
sb.AppendLine($"{indent} {{");
string nullableNonNullVar = $"__foryNonNull{id++}";
EmitReadPayload(sb, NonNullableCodec(element), nullableNonNullVar, indentLevel + 2, ref id);
sb.AppendLine($"{indent} {targetVar} = {nullableNonNullVar};");
sb.AppendLine($"{indent} }}");
sb.AppendLine($"{indent} else");
sb.AppendLine($"{indent} {{");
sb.AppendLine($"{indent} throw new global::Apache.Fory.InvalidDataException($\"invalid collection null flag {{__foryRefFlag}}\");");
sb.AppendLine($"{indent} }}");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
string nonNullVar = $"__foryNonNull{id++}";
EmitReadPayload(sb, NonNullableCodec(element), nonNullVar, indentLevel + 1, ref id);
sb.AppendLine($"{indent} {targetVar} = {nonNullVar};");
sb.AppendLine($"{indent}}}");
return;
}
string directNonNullVar = $"__foryNonNull{id++}";
EmitReadPayload(sb, element, directNonNullVar, indentLevel, ref id);
sb.AppendLine($"{indent}{targetVar} = {directNonNullVar};");
}
private static void EmitReadMapPayload(
StringBuilder sb,
FieldCodecModel codec,
string targetVar,
int indentLevel,
ref int id)
{
string indent = new(' ', indentLevel * 4);
FieldCodecModel key = codec.Generics[0];
FieldCodecModel value = codec.Generics[1];
string totalVar = $"__foryTotal{id++}";
sb.AppendLine($"{indent}int {totalVar} = checked((int)context.Reader.ReadVarUInt32());");
sb.AppendLine($"{indent}{codec.TypeName} {targetVar} = new({totalVar});");
sb.AppendLine($"{indent}int __foryRead = 0;");
sb.AppendLine($"{indent}while (__foryRead < {totalVar})");
sb.AppendLine($"{indent}{{");
string innerIndent = indent + " ";
sb.AppendLine($"{innerIndent}byte __foryHeader = context.Reader.ReadUInt8();");
sb.AppendLine($"{innerIndent}bool __foryKeyNull = (__foryHeader & 0b0000_0010) != 0;");
sb.AppendLine($"{innerIndent}bool __foryKeyDeclared = (__foryHeader & 0b0000_0100) != 0;");
sb.AppendLine($"{innerIndent}bool __foryValueNull = (__foryHeader & 0b0001_0000) != 0;");
sb.AppendLine($"{innerIndent}bool __foryValueDeclared = (__foryHeader & 0b0010_0000) != 0;");
sb.AppendLine($"{innerIndent}if (__foryKeyNull || __foryValueNull)");
sb.AppendLine($"{innerIndent}{{");
sb.AppendLine($"{innerIndent} {key.TypeName} __foryKey = ({key.TypeName})default!;");
sb.AppendLine($"{innerIndent} {value.TypeName} __foryValue = ({value.TypeName})default!;");
sb.AppendLine($"{innerIndent} if (!__foryKeyNull)");
sb.AppendLine($"{innerIndent} {{");
sb.AppendLine($"{innerIndent} if (!__foryKeyDeclared)");
sb.AppendLine($"{innerIndent} {{");
EmitReadInlineTypeInfo(sb, NonNullableCodec(key), indentLevel + 3, ref id);
sb.AppendLine($"{innerIndent} }}");
EmitReadPayload(sb, NonNullableCodec(key), "__foryReadKey", indentLevel + 2, ref id);
sb.AppendLine($"{innerIndent} __foryKey = __foryReadKey;");
sb.AppendLine($"{innerIndent} }}");
sb.AppendLine($"{innerIndent} if (!__foryValueNull)");
sb.AppendLine($"{innerIndent} {{");
sb.AppendLine($"{innerIndent} if (!__foryValueDeclared)");
sb.AppendLine($"{innerIndent} {{");
EmitReadInlineTypeInfo(sb, NonNullableCodec(value), indentLevel + 3, ref id);
sb.AppendLine($"{innerIndent} }}");
EmitReadPayload(sb, NonNullableCodec(value), "__foryReadValue", indentLevel + 2, ref id);
sb.AppendLine($"{innerIndent} __foryValue = __foryReadValue;");
sb.AppendLine($"{innerIndent} }}");
if (codec.CarrierKind == CarrierKind.NullableKeyDictionary)
{
sb.AppendLine($"{innerIndent} {targetVar}[__foryKey] = __foryValue;");
}
else
{
sb.AppendLine($"{innerIndent} if (!__foryKeyNull)");
sb.AppendLine($"{innerIndent} {{");
sb.AppendLine($"{innerIndent} {targetVar}[__foryKey] = __foryValue;");
sb.AppendLine($"{innerIndent} }}");
}
sb.AppendLine($"{innerIndent} __foryRead++;");
sb.AppendLine($"{innerIndent} continue;");
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{innerIndent}int __foryChunkSize = context.Reader.ReadUInt8();");
sb.AppendLine($"{innerIndent}if (!__foryKeyDeclared)");
sb.AppendLine($"{innerIndent}{{");
EmitReadInlineTypeInfo(sb, NonNullableCodec(key), indentLevel + 2, ref id);
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{innerIndent}if (!__foryValueDeclared)");
sb.AppendLine($"{innerIndent}{{");
EmitReadInlineTypeInfo(sb, NonNullableCodec(value), indentLevel + 2, ref id);
sb.AppendLine($"{innerIndent}}}");
string mapIndexVar = $"__foryIndex{id++}";
sb.AppendLine($"{innerIndent}for (int {mapIndexVar} = 0; {mapIndexVar} < __foryChunkSize; {mapIndexVar}++)");
sb.AppendLine($"{innerIndent}{{");
EmitReadPayload(sb, NonNullableCodec(key), "__foryKey", indentLevel + 2, ref id);
EmitReadPayload(sb, NonNullableCodec(value), "__foryValue", indentLevel + 2, ref id);
sb.AppendLine($"{innerIndent} {targetVar}[__foryKey] = __foryValue;");
sb.AppendLine($"{innerIndent}}}");
sb.AppendLine($"{innerIndent}__foryRead += __foryChunkSize;");
sb.AppendLine($"{indent}}}");
}
private static void EmitReadInlineTypeInfo(
StringBuilder sb,
FieldCodecModel codec,
int indentLevel,
ref int id)
{
string indent = new(' ', indentLevel * 4);
if (!CanValidateInlineTypeInfo(codec.TypeId))
{
sb.AppendLine(
$"{indent}throw new global::Apache.Fory.InvalidDataException(\"generated field payload requires declared nested user type metadata\");");
return;
}
string typeIdVar = $"__foryWireTypeId{id++}";
sb.AppendLine($"{indent}uint {typeIdVar} = context.Reader.ReadUInt8();");
sb.AppendLine($"{indent}if ({typeIdVar} != {codec.TypeId}u)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} throw new global::Apache.Fory.TypeMismatchException({codec.TypeId}u, {typeIdVar});");
sb.AppendLine($"{indent}}}");
}
private static bool CanValidateInlineTypeInfo(uint typeId)
{
return typeId is > 0 and <= 24 or >= 36 and <= 56;
}
private static FieldCodecModel NonNullableCodec(FieldCodecModel codec)
{
if (!codec.Nullable)
{
return codec;
}
return new FieldCodecModel(
codec.Kind,
codec.TypeId,
codec.NullableValueType && codec.TypeName.EndsWith("?", StringComparison.Ordinal)
? codec.TypeName.Substring(0, codec.TypeName.Length - 1)
: codec.TypeName,
false,
false,
codec.CarrierKind,
codec.Generics);
}
private static string ElementTypeName(string arrayTypeName)
{
return arrayTypeName.EndsWith("[]", StringComparison.Ordinal)
? arrayTypeName.Substring(0, arrayTypeName.Length - 2)
: "object";
}
private static string PackedArrayElementTypeName(uint typeId)
{
return typeId switch
{
41 => "byte",
43 => "bool",
44 => "sbyte",
45 => "short",
46 => "int",
47 => "long",
48 => "byte",
49 => "ushort",
50 => "uint",
51 => "ulong",
53 => "global::System.Half",
54 => "global::Apache.Fory.BFloat16",
55 => "float",
56 => "double",
_ => throw new InvalidOperationException($"unsupported packed array type id {typeId}"),
};
}
private static int PackedArrayElementWidth(uint typeId)
{
return typeId switch
{
41 or 43 or 44 or 48 => 1,
45 or 49 or 53 or 54 => 2,
46 or 50 or 55 => 4,
47 or 51 or 56 => 8,
_ => throw new InvalidOperationException($"unsupported packed array type id {typeId}"),
};
}
private static uint PackedArrayElementTypeId(uint typeId)
{
return typeId switch
{
41 => 9,
43 => 1,
44 => 2,
45 => 3,
46 => 4,
47 => 6,
48 => 9,
49 => 10,
50 => 11,
51 => 13,
53 => 17,
54 => 18,
55 => 19,
56 => 20,
_ => throw new InvalidOperationException($"unsupported packed array type id {typeId}"),
};
}
private static void EmitWriteMember(StringBuilder sb, MemberModel member, bool compatibleMode)
{
string refModeExpr = BuildWriteRefModeExpression(member);
string memberAccess = $"value.{member.Name}";
string hasGenerics = member.IsCollection ? "true" : "false";
string writeTypeInfo = compatibleMode
? BuildFieldTypeInfoLiteral(member)
: "false";
switch (member.DynamicAnyKind)
{
case DynamicAnyKind.AnyValue:
sb.AppendLine(
$" global::Apache.Fory.DynamicAnyCodec.WriteAny(context, {memberAccess}, {refModeExpr}, true, false);");
return;
case DynamicAnyKind.None:
break;
default:
throw new InvalidOperationException($"unsupported dynamic any kind {member.DynamicAnyKind}");
}
if (member.FieldCodec is not null)
{
sb.AppendLine(
$" __ForyWrite{Sanitize(member.Name)}Field(context, {memberAccess}, {refModeExpr});");
return;
}
if (member.UseDictionaryTypeInfoCache)
{
EmitWriteDictionaryWithTypeInfoCache(
sb,
member,
memberAccess,
refModeExpr,
writeTypeInfo,
hasGenerics,
compatibleMode);
return;
}
if (!member.IsNullable && TryBuildDirectFieldWrite(member, memberAccess, out string? directWriteCode))
{
sb.AppendLine($" {directWriteCode}");
return;
}
if (TryBuildNullableFixedTaggedFieldWrite(member, memberAccess, out string? nullableWriteCode))
{
sb.AppendLine($" {nullableWriteCode}");
return;
}
if (writeTypeInfo == "false")
{
if (CanUseDirectWriteDataInvocation(member))
{
sb.AppendLine(
$" context.TypeResolver.GetSerializer<{member.TypeName}>().WriteData(context, {memberAccess}, {hasGenerics});");
return;
}
if (CanUseTrackRefBranchWriteDataInvocation(member))
{
sb.AppendLine(" if (context.TrackRef)");
sb.AppendLine(" {");
sb.AppendLine(
$" context.TypeResolver.GetSerializer<{member.TypeName}>().Write(context, {memberAccess}, global::Apache.Fory.RefMode.Tracking, false, {hasGenerics});");
sb.AppendLine(" }");
sb.AppendLine(" else");
sb.AppendLine(" {");
sb.AppendLine(
$" context.TypeResolver.GetSerializer<{member.TypeName}>().WriteData(context, {memberAccess}, {hasGenerics});");
sb.AppendLine(" }");
return;
}
}
sb.AppendLine(
$" context.TypeResolver.GetSerializer<{member.TypeName}>().Write(context, {memberAccess}, {refModeExpr}, {writeTypeInfo}, {hasGenerics});");
}
private static void EmitWriteDictionaryWithTypeInfoCache(
StringBuilder sb,
MemberModel member,
string memberAccess,
string refModeExpr,
string writeTypeInfo,
string hasGenerics,
bool compatibleMode)
{
string memberId = Sanitize(member.Name);
string modeSuffix = compatibleMode ? "Compat" : "Schema";
string fieldValueVar = $"__{memberId}DictValue{modeSuffix}";
string runtimeTypeVar = $"__{memberId}DictRuntimeType{modeSuffix}";
string typeInfoVar = $"__{memberId}DictTypeInfo{modeSuffix}";
sb.AppendLine($" {member.TypeName} {fieldValueVar} = {memberAccess};");
sb.AppendLine($" if ({fieldValueVar} is null)");
sb.AppendLine(" {");
sb.AppendLine(
$" context.TypeResolver.GetSerializer<{member.TypeName}>().Write(context, ({member.TypeName})null!, {refModeExpr}, {writeTypeInfo}, {hasGenerics});");
sb.AppendLine(" }");
sb.AppendLine(" else");
sb.AppendLine(" {");
sb.AppendLine($" global::System.Type {runtimeTypeVar} = {fieldValueVar}.GetType();");
sb.AppendLine($" global::Apache.Fory.TypeInfo {typeInfoVar} = context.TypeResolver.GetTypeInfo({runtimeTypeVar});");
sb.AppendLine(
$" context.TypeResolver.WriteObject({typeInfoVar}, context, {fieldValueVar}, {refModeExpr}, {writeTypeInfo}, {hasGenerics});");
sb.AppendLine(" }");
}
private static void EmitReadMemberAssignment(
StringBuilder sb,
MemberModel member,
string refModeExpr,
string readTypeInfoExpr,
string valueVar,
string variableSuffix,
int indentLevel,
bool allowDirectRead)
{
string indent = new(' ', indentLevel * 2);
string assignmentTarget = $"{valueVar}.{member.Name}";
string typeOfTypeName = StripNullableForTypeOf(member.TypeName);
switch (member.DynamicAnyKind)
{
case DynamicAnyKind.AnyValue:
sb.AppendLine(
$"{indent}{assignmentTarget} = ({member.TypeName})global::Apache.Fory.DynamicAnyCodec.CastAnyDynamicValue(global::Apache.Fory.DynamicAnyCodec.ReadAny(context, {refModeExpr}, true), typeof({typeOfTypeName}))!;");
return;
case DynamicAnyKind.None:
break;
default:
throw new InvalidOperationException($"unsupported dynamic any kind {member.DynamicAnyKind}");
}
if (member.FieldCodec is not null)
{
if (variableSuffix == "Compat" &&
TryBuildCompatibleListArrayReadCodec(member.FieldCodec, out _))
{
sb.AppendLine($"{indent}if (remoteField.FieldType.TypeId == {member.FieldCodec.TypeId})");
sb.AppendLine($"{indent}{{");
sb.AppendLine(
$"{indent} {assignmentTarget} = __ForyRead{Sanitize(member.Name)}Field(context, {refModeExpr});");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
sb.AppendLine(
$"{indent} {assignmentTarget} = __ForyCompatibleFieldReaders.Read{Sanitize(member.Name)}ListArrayBridge(context, remoteField.FieldType, {refModeExpr});");
sb.AppendLine($"{indent}}}");
}
else
{
sb.AppendLine(
$"{indent}{assignmentTarget} = __ForyRead{Sanitize(member.Name)}Field(context, {refModeExpr});");
}
return;
}
if (allowDirectRead && !member.IsNullable && TryBuildDirectFieldRead(member, out string? directReadExpr))
{
sb.AppendLine($"{indent}{assignmentTarget} = {directReadExpr};");
return;
}
if (allowDirectRead && TryBuildNullableFixedTaggedFieldRead(member, assignmentTarget, variableSuffix, indent, out string? nullableReadCode))
{
sb.AppendLine(nullableReadCode);
return;
}
if (variableSuffix == "Compat")
{
sb.AppendLine(
$"{indent}{assignmentTarget} = __ForyReadCompatibleField<{member.TypeName}>(context, remoteField.FieldType, {refModeExpr}, {readTypeInfoExpr});");
return;
}
sb.AppendLine(
$"{indent}{assignmentTarget} = context.TypeResolver.GetSerializer<{member.TypeName}>().Read(context, {refModeExpr}, {readTypeInfoExpr});");
}
private static string StripNullableForTypeOf(string typeName)
{
return typeName.Replace("?", string.Empty);
}
private static bool TryBuildDirectFieldWrite(MemberModel member, string memberAccess, out string? writeCode)
{
writeCode = null;
if (!CanUseDirectBuiltInFieldAccess(member))
{
return false;
}
return TryBuildDirectPayloadWrite(member.Classification.TypeId, memberAccess, out writeCode);
}
private static bool TryBuildDirectFieldRead(MemberModel member, out string? readExpr)
{
readExpr = null;
if (!CanUseDirectBuiltInFieldAccess(member))
{
return false;
}
return TryBuildDirectPayloadRead(member.Classification.TypeId, out readExpr);
}
private static bool TryBuildNullableFixedTaggedFieldWrite(MemberModel member, string memberAccess, out string? writeCode)
{
writeCode = null;
if (!member.IsNullableValueType || !IsFixedTaggedTypeId(member.Classification.TypeId))
{
return false;
}
if (!TryBuildDirectPayloadWrite(member.Classification.TypeId, $"{memberAccess}.Value", out string? payloadWriteCode))
{
return false;
}
writeCode = $"if (!{memberAccess}.HasValue) {{ context.Writer.WriteInt8((sbyte)global::Apache.Fory.RefFlag.Null); }} else {{ context.Writer.WriteInt8((sbyte)global::Apache.Fory.RefFlag.NotNullValue); {payloadWriteCode} }}";
return true;
}
private static bool TryBuildNullableFixedTaggedFieldRead(
MemberModel member,
string assignmentTarget,
string variableSuffix,
string indent,
out string code)
{
code = string.Empty;
if (!member.IsNullableValueType || !IsFixedTaggedTypeId(member.Classification.TypeId))
{
return false;
}
if (!TryBuildDirectPayloadRead(member.Classification.TypeId, out string? payloadReadExpr))
{
return false;
}
string refFlagVar = $"__{member.Name}RefFlag{variableSuffix}";
string nestedIndent = indent + " ";
StringBuilder sb = new();
sb.AppendLine($"{indent}sbyte {refFlagVar} = context.Reader.ReadInt8();");
sb.AppendLine($"{indent}if ({refFlagVar} == (sbyte)global::Apache.Fory.RefFlag.Null)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{nestedIndent}{assignmentTarget} = ({member.TypeName})null!;");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{nestedIndent}{assignmentTarget} = {payloadReadExpr};");
sb.Append($"{indent}}}");
code = sb.ToString();
return true;
}
private static bool IsFixedTaggedTypeId(uint typeId)
{
return typeId is 4 or 6 or 8 or 11 or 13 or 15;
}
private static bool TryBuildDirectPayloadWrite(uint typeId, string valueExpr, out string? writeCode)
{
writeCode = null;
switch (typeId)
{
case 1:
writeCode = $"context.Writer.WriteUInt8({valueExpr} ? (byte)1 : (byte)0);";
return true;
case 2:
writeCode = $"context.Writer.WriteInt8({valueExpr});";
return true;
case 3:
writeCode = $"context.Writer.WriteInt16({valueExpr});";
return true;
case 4:
writeCode = $"context.Writer.WriteInt32({valueExpr});";
return true;
case 5:
writeCode = $"context.Writer.WriteVarInt32({valueExpr});";
return true;
case 6:
writeCode = $"context.Writer.WriteInt64({valueExpr});";
return true;
case 7:
writeCode = $"context.Writer.WriteVarInt64({valueExpr});";
return true;
case 8:
writeCode = $"context.Writer.WriteTaggedInt64({valueExpr});";
return true;
case 9:
writeCode = $"context.Writer.WriteUInt8({valueExpr});";
return true;
case 10:
writeCode = $"context.Writer.WriteUInt16({valueExpr});";
return true;
case 11:
writeCode = $"context.Writer.WriteUInt32({valueExpr});";
return true;
case 12:
writeCode = $"context.Writer.WriteVarUInt32({valueExpr});";
return true;
case 13:
writeCode = $"context.Writer.WriteUInt64({valueExpr});";
return true;
case 14:
writeCode = $"context.Writer.WriteVarUInt64({valueExpr});";
return true;
case 15:
writeCode = $"context.Writer.WriteTaggedUInt64({valueExpr});";
return true;
case 17:
writeCode = $"context.Writer.WriteUInt16(global::System.BitConverter.HalfToUInt16Bits({valueExpr}));";
return true;
case 18:
writeCode = $"context.Writer.WriteUInt16({valueExpr}.ToBits());";
return true;
case 19:
writeCode = $"context.Writer.WriteFloat32({valueExpr});";
return true;
case 20:
writeCode = $"context.Writer.WriteFloat64({valueExpr});";
return true;
case 21:
writeCode = $"global::Apache.Fory.StringSerializer.WriteString(context, {valueExpr});";
return true;
default:
return false;
}
}
private static bool TryBuildDirectPayloadRead(uint typeId, out string? readExpr)
{
readExpr = null;
switch (typeId)
{
case 1:
readExpr = "context.Reader.ReadUInt8() != 0";
return true;
case 2:
readExpr = "context.Reader.ReadInt8()";
return true;
case 3:
readExpr = "context.Reader.ReadInt16()";
return true;
case 4:
readExpr = "context.Reader.ReadInt32()";
return true;
case 5:
readExpr = "context.Reader.ReadVarInt32()";
return true;
case 6:
readExpr = "context.Reader.ReadInt64()";
return true;
case 7:
readExpr = "context.Reader.ReadVarInt64()";
return true;
case 8:
readExpr = "context.Reader.ReadTaggedInt64()";
return true;
case 9:
readExpr = "context.Reader.ReadUInt8()";
return true;
case 10:
readExpr = "context.Reader.ReadUInt16()";
return true;
case 11:
readExpr = "context.Reader.ReadUInt32()";
return true;
case 12:
readExpr = "context.Reader.ReadVarUInt32()";
return true;
case 13:
readExpr = "context.Reader.ReadUInt64()";
return true;
case 14:
readExpr = "context.Reader.ReadVarUInt64()";
return true;
case 15:
readExpr = "context.Reader.ReadTaggedUInt64()";
return true;
case 17:
readExpr = "global::System.BitConverter.UInt16BitsToHalf(context.Reader.ReadUInt16())";
return true;
case 18:
readExpr = "global::Apache.Fory.BFloat16.FromBits(context.Reader.ReadUInt16())";
return true;
case 19:
readExpr = "context.Reader.ReadFloat32()";
return true;
case 20:
readExpr = "context.Reader.ReadFloat64()";
return true;
case 21:
readExpr = "global::Apache.Fory.StringSerializer.ReadString(context)";
return true;
default:
return false;
}
}
private static bool CanUseDirectBuiltInFieldAccess(MemberModel member)
{
if (member.IsNullable ||
member.DynamicAnyKind != DynamicAnyKind.None ||
member.IsCollection ||
member.Classification.IsMap)
{
return false;
}
return member.Classification.IsPrimitive || member.Classification.TypeId == 21;
}
private static bool CanUseDirectWriteDataInvocation(MemberModel member)
{
if (member.IsNullable || member.DynamicAnyKind != DynamicAnyKind.None)
{
return false;
}
return member.Classification.IsBuiltIn || !member.IsRefType;
}
private static bool CanUseTrackRefBranchWriteDataInvocation(MemberModel member)
{
if (member.IsNullable || member.DynamicAnyKind != DynamicAnyKind.None)
{
return false;
}
return !member.Classification.IsBuiltIn && member.IsRefType;
}
private static string BuildSchemaFingerprintExpression(ImmutableArray<MemberModel> members)
{
if (members.IsDefaultOrEmpty)
{
return "\"\"";
}
IEnumerable<MemberModel> ordered = members
.OrderBy(m => m.FieldId.HasValue ? 0 : 1)
.ThenBy(m => m.FieldId.GetValueOrDefault())
.ThenBy(m => m.FieldIdentifier, StringComparer.Ordinal)
.ThenBy(m => m.OriginalIndex);
StringBuilder sb = new();
bool first = true;
foreach (MemberModel member in ordered)
{
string piece =
$"\"{EscapeString(BuildSchemaFieldIdentifier(member))},\" + {BuildSchemaFieldTypeFingerprintExpression(member.TypeMeta, "trackRef", includeNullable: true)} + \";\"";
if (!first)
{
sb.Append(" + ");
}
first = false;
sb.Append(piece);
}
return sb.ToString().Replace("b_float16", "bfloat16");
}
private static string BuildSchemaFieldIdentifier(MemberModel member)
{
return member.FieldId.HasValue
? member.FieldId.Value.ToString(CultureInfo.InvariantCulture)
: member.FieldIdentifier;
}
private static string BuildSchemaFieldTypeFingerprintExpression(
TypeMetaFieldTypeModel model,
string trackRefExpr,
bool includeNullable)
{
string localTrackRefExpr = model.TrackRefByContext
? $"({trackRefExpr} ? 1 : 0)"
: "0";
string prefix =
$"\"{NormalizeSchemaFingerprintTypeId(model.TypeIdExpr).ToString(CultureInfo.InvariantCulture)},\" + {localTrackRefExpr} + \","
+ (includeNullable && model.Nullable ? "1" : "0")
+ "\"";
if (model.Generics.Length == 0)
{
return prefix;
}
if (model.Generics.Length == 1)
{
string child = BuildSchemaFieldTypeFingerprintExpression(model.Generics[0], "false", includeNullable: false);
return $"{prefix} + \"[\" + {child} + \"]\"";
}
if (model.Generics.Length == 2)
{
string key = BuildSchemaFieldTypeFingerprintExpression(model.Generics[0], "false", includeNullable: false);
string value = BuildSchemaFieldTypeFingerprintExpression(model.Generics[1], "false", includeNullable: false);
return $"{prefix} + \"[\" + {key} + \"|\" + {value} + \"]\"";
}
throw new InvalidOperationException("schema fingerprint supports only list/set/map generic arity");
}
private static uint NormalizeSchemaFingerprintTypeId(string typeIdExpr)
{
if (!TryParseSchemaFingerprintTypeId(typeIdExpr, out uint typeId))
{
throw new InvalidOperationException($"unsupported schema fingerprint type id expression {typeIdExpr}");
}
return typeId switch
{
0 or 25 or 26 or 27 or 28 or 29 or 30 or 31 or 32 or 33 or 34 or 35 => 0,
_ => typeId,
};
}
private static bool TryParseSchemaFingerprintTypeId(string typeIdExpr, out uint typeId)
{
string normalized = typeIdExpr.Replace(" ", string.Empty);
if (normalized.StartsWith("(uint)", StringComparison.Ordinal))
{
normalized = normalized.Substring(6);
}
if (uint.TryParse(normalized, NumberStyles.None, CultureInfo.InvariantCulture, out typeId))
{
return true;
}
switch (normalized)
{
case "global::Apache.Fory.TypeId.Unknown":
typeId = 0;
return true;
case "global::Apache.Fory.TypeId.List":
typeId = 22;
return true;
case "global::Apache.Fory.TypeId.Set":
typeId = 23;
return true;
case "global::Apache.Fory.TypeId.Map":
typeId = 24;
return true;
case "global::Apache.Fory.TypeId.Enum":
typeId = 25;
return true;
case "global::Apache.Fory.TypeId.Union":
typeId = 33;
return true;
default:
typeId = 0;
return false;
}
}
private static string BuildTypeMetaExpression(TypeMetaFieldTypeModel model, string trackRefExpr)
{
string localTrackRefExpr = model.TrackRefByContext ? trackRefExpr : "false";
if (model.Generics.Length > 0)
{
string generics = string.Join(
", ",
model.Generics.Select(g => BuildTypeMetaExpression(g, trackRefExpr)));
return
$"new global::Apache.Fory.TypeMetaFieldType({model.TypeIdExpr}, {BoolLiteral(model.Nullable)}, {localTrackRefExpr}, new global::Apache.Fory.TypeMetaFieldType[] {{ {generics} }})";
}
return $"new global::Apache.Fory.TypeMetaFieldType({model.TypeIdExpr}, {BoolLiteral(model.Nullable)}, {localTrackRefExpr})";
}
private static string BuildTypeMetaFieldIdExpression(short? fieldId)
{
return fieldId.HasValue ? $"(short){fieldId.Value}" : "null";
}
private static string BuildWriteRefModeExpression(MemberModel member)
{
return member.DynamicAnyKind switch
{
DynamicAnyKind.AnyValue => $"__ForyRefMode({BoolLiteral(member.IsNullable)}, context.TrackRef)",
_ => member.Classification.IsBuiltIn || !member.IsRefType
? $"__ForyRefMode({BoolLiteral(member.IsNullable)}, false)"
: $"__ForyRefMode({BoolLiteral(member.IsNullable)}, context.TrackRef)",
};
}
private static string BuildFieldTypeInfoLiteral(MemberModel member)
{
return BoolLiteral(member.NeedsFieldTypeInfo);
}
private static TypeModel? BuildTypeModel(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
{
_ = cancellationToken;
if (context.TargetSymbol is not INamedTypeSymbol typeSymbol)
{
return null;
}
if (typeSymbol.TypeParameters.Length > 0)
{
return null;
}
string typeName = typeSymbol.ToDisplayString(FullNameFormat);
string serializerName = "__ForySerializer_" + Sanitize(typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
if (typeSymbol.TypeKind == TypeKind.Enum)
{
return new TypeModel(
typeName,
serializerName,
DeclKind.Enum,
ImmutableArray<MemberModel>.Empty,
ImmutableArray<MemberModel>.Empty);
}
DeclKind kind = typeSymbol.TypeKind switch
{
TypeKind.Struct => DeclKind.Struct,
TypeKind.Class => DeclKind.Class,
_ => DeclKind.Unknown,
};
if (kind == DeclKind.Unknown)
{
return null;
}
if (kind == DeclKind.Class && !HasAccessibleParameterlessCtor(typeSymbol))
{
return null;
}
List<MemberModel> members = [];
foreach (ISymbol member in typeSymbol.GetMembers())
{
if (member.IsStatic)
{
continue;
}
if (member is IFieldSymbol field)
{
if (field.IsConst || field.IsReadOnly || !IsReadableWritableAccessibility(field.DeclaredAccessibility))
{
continue;
}
MemberModel? parsedField = BuildMemberModel(field.Name, field.Type, field, MemberDeclKind.Field);
if (parsedField is not null)
{
members.Add(parsedField);
}
continue;
}
if (member is IPropertySymbol property)
{
if (property.IsIndexer || property.GetMethod is null || property.SetMethod is null)
{
continue;
}
if (property.SetMethod.IsInitOnly)
{
continue;
}
if (!IsReadableWritableAccessibility(property.GetMethod.DeclaredAccessibility) ||
!IsReadableWritableAccessibility(property.SetMethod.DeclaredAccessibility))
{
continue;
}
MemberModel? parsedProperty = BuildMemberModel(
property.Name,
property.Type,
property,
MemberDeclKind.Property);
if (parsedProperty is not null)
{
members.Add(parsedProperty);
}
}
}
ImmutableArray<MemberModel> ordered = members
.OrderBy(m => m.OriginalIndex)
.ToImmutableArray();
ImmutableArray<MemberModel> sorted = SortMembers(ordered);
return new TypeModel(typeName, serializerName, kind, ordered, sorted);
}
private static MemberModel? BuildMemberModel(
string name,
ITypeSymbol memberType,
ISymbol memberSymbol,
MemberDeclKind memberDeclKind)
{
(bool isOptional, ITypeSymbol unwrappedType) = UnwrapNullable(memberType);
short? fieldId = null;
SchemaTypeModel? schemaType = null;
foreach (AttributeData attribute in memberSymbol.GetAttributes())
{
string? attrName = attribute.AttributeClass?.ToDisplayString();
if (!string.Equals(attrName, "Apache.Fory.ForyFieldAttribute", StringComparison.Ordinal))
{
continue;
}
if (attribute.ConstructorArguments.Length == 1 &&
TryGetNonNegativeShort(attribute.ConstructorArguments[0], out short ctorFieldId))
{
fieldId = ctorFieldId;
}
foreach (KeyValuePair<string, TypedConstant> namedArg in attribute.NamedArguments)
{
if (string.Equals(namedArg.Key, "Id", StringComparison.Ordinal))
{
if (TryGetNonNegativeShort(namedArg.Value, out short parsedFieldId))
{
fieldId = parsedFieldId;
}
continue;
}
if (!string.Equals(namedArg.Key, "Type", StringComparison.Ordinal))
{
continue;
}
if (namedArg.Value.Value is ITypeSymbol schemaSymbol)
{
schemaType = TryParseSchemaType(schemaSymbol);
}
}
}
DynamicAnyKind dynamicAnyKind = ResolveDynamicAnyKind(unwrappedType);
TypeResolution resolution = ResolveTypeResolution(unwrappedType, schemaType);
if (!resolution.Supported)
{
return null;
}
TypeClassification classification = resolution.Classification;
int group = classification.IsPrimitive
? (isOptional ? 2 : 1)
: classification.IsMap
? 5
: classification.IsCollection
? 4
: classification.IsBuiltIn
? 3
: 6;
int index = int.MaxValue;
Location? sourceLocation = memberSymbol.Locations.FirstOrDefault(loc => loc.IsInSource);
if (sourceLocation is not null)
{
index = sourceLocation.SourceSpan.Start;
}
string typeName = memberType.ToDisplayString(FullNameFormat);
TypeMetaFieldTypeModel typeMeta = BuildTypeMetaFieldTypeModel(
memberType,
isOptional,
dynamicAnyKind,
resolution.Classification.TypeId,
schemaType);
FieldCodecModel? fieldCodec = BuildFieldCodecModel(memberType, typeMeta, schemaType, classification);
return new MemberModel(
name,
ToSnakeCase(name),
index,
memberDeclKind,
typeName,
isOptional,
memberType is INamedTypeSymbol nts &&
nts.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T,
fieldId,
classification,
group,
classification.IsCollection || classification.IsMap,
classification.IsMap && !IsTypeSealed(unwrappedType),
!unwrappedType.IsValueType && classification.TypeId != 21,
FieldNeedsTypeInfo(classification, dynamicAnyKind, unwrappedType),
dynamicAnyKind == DynamicAnyKind.None ? DynamicAnyKind.None : dynamicAnyKind,
typeMeta,
fieldCodec);
}
private static TypeMetaFieldTypeModel BuildTypeMetaFieldTypeModel(
ITypeSymbol memberType,
bool nullable,
DynamicAnyKind dynamicAnyKind,
uint explicitTypeId,
SchemaTypeModel? schemaType = null)
{
(bool _, ITypeSymbol unwrapped) = UnwrapNullable(memberType);
if (schemaType is not null)
{
return BuildSchemaTypeMetaFieldTypeModel(memberType, nullable, schemaType);
}
if (unwrapped is IArrayTypeSymbol &&
ClassifyType(unwrapped) is { TypeId: not 22 } arrayClassification &&
IsPackedArrayTypeId(arrayClassification.TypeId))
{
return new TypeMetaFieldTypeModel(
$"(uint){arrayClassification.TypeId}",
nullable,
false,
ImmutableArray<TypeMetaFieldTypeModel>.Empty);
}
if (TryGetListElementType(unwrapped, out ITypeSymbol? listElementType))
{
bool elementNullable = GenericNullable(listElementType!);
TypeMetaFieldTypeModel element = BuildTypeMetaFieldTypeModel(
listElementType!,
elementNullable,
ResolveDynamicAnyKind(UnwrapNullable(listElementType!).Item2),
0);
return new TypeMetaFieldTypeModel(
"(uint)global::Apache.Fory.TypeId.List",
nullable,
false,
ImmutableArray.Create(element));
}
if (TryGetSetElementType(unwrapped, out ITypeSymbol? setElementType))
{
bool elementNullable = GenericNullable(setElementType!);
TypeMetaFieldTypeModel element = BuildTypeMetaFieldTypeModel(
setElementType!,
elementNullable,
ResolveDynamicAnyKind(UnwrapNullable(setElementType!).Item2),
0);
return new TypeMetaFieldTypeModel(
"(uint)global::Apache.Fory.TypeId.Set",
nullable,
false,
ImmutableArray.Create(element));
}
if (TryGetMapTypeArguments(unwrapped, out ITypeSymbol? keyType, out ITypeSymbol? valueType))
{
bool keyNullable = GenericNullable(keyType!);
bool valueNullable = GenericNullable(valueType!);
TypeMetaFieldTypeModel key = BuildTypeMetaFieldTypeModel(
keyType!,
keyNullable,
ResolveDynamicAnyKind(UnwrapNullable(keyType!).Item2),
0);
TypeMetaFieldTypeModel value = BuildTypeMetaFieldTypeModel(
valueType!,
valueNullable,
ResolveDynamicAnyKind(UnwrapNullable(valueType!).Item2),
0);
return new TypeMetaFieldTypeModel(
"(uint)global::Apache.Fory.TypeId.Map",
nullable,
false,
ImmutableArray.Create(key, value));
}
TypeClassification classification = ClassifyType(unwrapped);
if (explicitTypeId != 0 && classification.IsPrimitive && classification.TypeId != explicitTypeId)
{
return new TypeMetaFieldTypeModel(
explicitTypeId.ToString(),
nullable,
false,
ImmutableArray<TypeMetaFieldTypeModel>.Empty);
}
if (IsUnionType(unwrapped))
{
return new TypeMetaFieldTypeModel(
"(uint)global::Apache.Fory.TypeId.Union",
nullable,
true,
ImmutableArray<TypeMetaFieldTypeModel>.Empty);
}
if (dynamicAnyKind == DynamicAnyKind.AnyValue)
{
return new TypeMetaFieldTypeModel(
"(uint)global::Apache.Fory.TypeId.Unknown",
nullable,
true,
ImmutableArray<TypeMetaFieldTypeModel>.Empty);
}
if (unwrapped.TypeKind == TypeKind.Enum)
{
return new TypeMetaFieldTypeModel(
"(uint)global::Apache.Fory.TypeId.Enum",
nullable,
false,
ImmutableArray<TypeMetaFieldTypeModel>.Empty);
}
return new TypeMetaFieldTypeModel(
$"(uint){classification.TypeId}",
nullable,
!classification.IsBuiltIn && unwrapped.TypeKind != TypeKind.Enum,
ImmutableArray<TypeMetaFieldTypeModel>.Empty);
}
private static TypeMetaFieldTypeModel BuildSchemaTypeMetaFieldTypeModel(
ITypeSymbol carrierType,
bool nullable,
SchemaTypeModel schemaType)
{
(bool _, ITypeSymbol unwrapped) = UnwrapNullable(carrierType);
switch (schemaType.Kind)
{
case SchemaTypeKind.List:
if (!TryGetListElementType(unwrapped, out ITypeSymbol? listElementType))
{
return new TypeMetaFieldTypeModel(
schemaType.TypeId.ToString(),
nullable,
false,
ImmutableArray<TypeMetaFieldTypeModel>.Empty);
}
bool elementNullable = GenericNullable(listElementType!);
return new TypeMetaFieldTypeModel(
"(uint)global::Apache.Fory.TypeId.List",
nullable,
false,
ImmutableArray.Create(
BuildSchemaTypeMetaFieldTypeModel(
listElementType!,
elementNullable,
schemaType.Generics[0])));
case SchemaTypeKind.Set:
if (!TryGetSetElementType(unwrapped, out ITypeSymbol? setElementType))
{
return new TypeMetaFieldTypeModel(
schemaType.TypeId.ToString(),
nullable,
false,
ImmutableArray<TypeMetaFieldTypeModel>.Empty);
}
bool setElementNullable = GenericNullable(setElementType!);
return new TypeMetaFieldTypeModel(
"(uint)global::Apache.Fory.TypeId.Set",
nullable,
false,
ImmutableArray.Create(
BuildSchemaTypeMetaFieldTypeModel(
setElementType!,
setElementNullable,
schemaType.Generics[0])));
case SchemaTypeKind.Map:
if (!TryGetMapTypeArguments(unwrapped, out ITypeSymbol? keyType, out ITypeSymbol? valueType))
{
return new TypeMetaFieldTypeModel(
schemaType.TypeId.ToString(),
nullable,
false,
ImmutableArray<TypeMetaFieldTypeModel>.Empty);
}
bool keyNullable = GenericNullable(keyType!);
bool valueNullable = GenericNullable(valueType!);
return new TypeMetaFieldTypeModel(
"(uint)global::Apache.Fory.TypeId.Map",
nullable,
false,
ImmutableArray.Create(
BuildSchemaTypeMetaFieldTypeModel(keyType!, keyNullable, schemaType.Generics[0]),
BuildSchemaTypeMetaFieldTypeModel(valueType!, valueNullable, schemaType.Generics[1])));
default:
return new TypeMetaFieldTypeModel(
schemaType.TypeId.ToString(),
nullable,
false,
ImmutableArray<TypeMetaFieldTypeModel>.Empty);
}
}
private static FieldCodecModel? BuildFieldCodecModel(
ITypeSymbol carrierType,
TypeMetaFieldTypeModel typeMeta,
SchemaTypeModel? schemaType,
TypeClassification classification)
{
(bool nullable, ITypeSymbol unwrapped) = UnwrapNullable(carrierType);
bool nullableValueType = carrierType is INamedTypeSymbol nts &&
nts.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T;
if (schemaType is not null)
{
FieldCodecModel codec = BuildFieldCodecFromSchema(carrierType, nullable, nullableValueType, schemaType);
return codec.Kind == FieldCodecKind.Scalar ? null : codec;
}
_ = typeMeta;
_ = classification;
return null;
}
private static FieldCodecModel BuildFieldCodecFromSchema(
ITypeSymbol carrierType,
bool nullable,
bool nullableValueType,
SchemaTypeModel schemaType)
{
(bool _, ITypeSymbol unwrapped) = UnwrapNullable(carrierType);
switch (schemaType.Kind)
{
case SchemaTypeKind.List:
{
ITypeSymbol elementType = TryGetListElementType(unwrapped, out ITypeSymbol? listElementType)
? listElementType!
: carrierType;
FieldCodecModel element = BuildFieldCodecFromSchema(
elementType,
GenericNullable(elementType),
elementType is INamedTypeSymbol elementNamed &&
elementNamed.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T,
schemaType.Generics[0]);
return new FieldCodecModel(
FieldCodecKind.List,
schemaType.TypeId,
carrierType.ToDisplayString(FullNameFormat),
nullable,
nullableValueType,
GetCarrierKind(unwrapped),
ImmutableArray.Create(element));
}
case SchemaTypeKind.Set:
{
ITypeSymbol elementType = TryGetSetElementType(unwrapped, out ITypeSymbol? setElementType)
? setElementType!
: carrierType;
FieldCodecModel element = BuildFieldCodecFromSchema(
elementType,
GenericNullable(elementType),
elementType is INamedTypeSymbol elementNamed &&
elementNamed.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T,
schemaType.Generics[0]);
return new FieldCodecModel(
FieldCodecKind.Set,
schemaType.TypeId,
carrierType.ToDisplayString(FullNameFormat),
nullable,
nullableValueType,
GetCarrierKind(unwrapped),
ImmutableArray.Create(element));
}
case SchemaTypeKind.Map:
{
ITypeSymbol keyType = carrierType;
ITypeSymbol valueType = carrierType;
if (TryGetMapTypeArguments(unwrapped, out ITypeSymbol? parsedKeyType, out ITypeSymbol? parsedValueType))
{
keyType = parsedKeyType!;
valueType = parsedValueType!;
}
FieldCodecModel key = BuildFieldCodecFromSchema(
keyType,
GenericNullable(keyType),
keyType is INamedTypeSymbol keyNamed &&
keyNamed.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T,
schemaType.Generics[0]);
FieldCodecModel value = BuildFieldCodecFromSchema(
valueType,
GenericNullable(valueType),
valueType is INamedTypeSymbol valueNamed &&
valueNamed.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T,
schemaType.Generics[1]);
return new FieldCodecModel(
FieldCodecKind.Map,
schemaType.TypeId,
carrierType.ToDisplayString(FullNameFormat),
nullable,
nullableValueType,
GetCarrierKind(unwrapped),
ImmutableArray.Create(key, value));
}
case SchemaTypeKind.PackedArray:
return new FieldCodecModel(
FieldCodecKind.PackedArray,
schemaType.TypeId,
carrierType.ToDisplayString(FullNameFormat),
nullable,
nullableValueType,
GetCarrierKind(unwrapped),
ImmutableArray<FieldCodecModel>.Empty);
default:
return new FieldCodecModel(
FieldCodecKind.Scalar,
schemaType.TypeId,
carrierType.ToDisplayString(FullNameFormat),
nullable,
nullableValueType,
GetCarrierKind(unwrapped),
ImmutableArray<FieldCodecModel>.Empty);
}
}
private static CarrierKind GetCarrierKind(ITypeSymbol unwrappedType)
{
if (unwrappedType is IArrayTypeSymbol)
{
return CarrierKind.Array;
}
if (unwrappedType is not INamedTypeSymbol named)
{
return CarrierKind.Value;
}
string genericName = named.ConstructedFrom.ToDisplayString();
return genericName switch
{
"System.Collections.Generic.List<T>" => CarrierKind.List,
"System.Collections.Generic.HashSet<T>" => CarrierKind.HashSet,
"System.Collections.Generic.Dictionary<TKey, TValue>" => CarrierKind.Dictionary,
"Apache.Fory.NullableKeyDictionary<TKey, TValue>" => CarrierKind.NullableKeyDictionary,
_ => CarrierKind.Value,
};
}
private static bool TryGetNonNegativeShort(TypedConstant value, out short fieldId)
{
fieldId = default;
object? raw = value.Value;
if (raw is null)
{
return false;
}
long numeric;
switch (raw)
{
case byte v:
numeric = v;
break;
case sbyte v:
numeric = v;
break;
case short v:
numeric = v;
break;
case ushort v:
numeric = v;
break;
case int v:
numeric = v;
break;
case uint v:
numeric = v;
break;
case long v:
numeric = v;
break;
case ulong v:
if (v > (ulong)short.MaxValue)
{
return false;
}
numeric = (long)v;
break;
default:
return false;
}
if (numeric < 0 || numeric > short.MaxValue)
{
return false;
}
fieldId = (short)numeric;
return true;
}
private static ImmutableArray<MemberModel> SortMembers(ImmutableArray<MemberModel> members)
{
return members
.OrderBy(m => m.Group)
.ThenBy(m =>
{
if (m.Group is 1 or 2)
{
return m.Classification.IsCompressedNumeric ? 1 : 0;
}
return 0;
})
.ThenByDescending(m => m.Group is 1 or 2 ? m.Classification.PrimitiveSize : 0)
.ThenBy(m =>
{
if (m.Group is 1 or 2)
{
return (int)m.Classification.TypeId;
}
if (m.Group is 3 or 5)
{
return (int)m.Classification.TypeId;
}
return 0;
})
.ThenBy(m => m.FieldId.HasValue ? 0 : 1)
.ThenBy(m => m.FieldId.GetValueOrDefault())
.ThenBy(m => m.FieldIdentifier, StringComparer.Ordinal)
.ThenBy(m => m.Name, StringComparer.Ordinal)
.ThenBy(m => m.OriginalIndex)
.ToImmutableArray();
}
private static bool GenericNullable(ITypeSymbol type)
{
(bool optional, ITypeSymbol unwrapped) = UnwrapNullable(type);
if (optional)
{
return true;
}
if (unwrapped.IsValueType)
{
return false;
}
TypeClassification c = ClassifyType(unwrapped);
return !c.IsPrimitive;
}
private static bool FieldNeedsTypeInfo(
TypeClassification classification,
DynamicAnyKind dynamicAnyKind,
ITypeSymbol unwrappedType)
{
if (dynamicAnyKind == DynamicAnyKind.AnyValue)
{
return true;
}
if (classification.IsBuiltIn || IsUnionType(unwrappedType) || unwrappedType.TypeKind == TypeKind.Enum)
{
return false;
}
return true;
}
private static bool IsReadableWritableAccessibility(Accessibility accessibility)
{
return accessibility is Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrInternal;
}
private static bool HasAccessibleParameterlessCtor(INamedTypeSymbol type)
{
foreach (IMethodSymbol ctor in type.InstanceConstructors)
{
if (ctor.Parameters.Length != 0)
{
continue;
}
if (ctor.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrInternal)
{
return true;
}
}
return false;
}
private static SchemaTypeModel? TryParseSchemaType(ITypeSymbol symbol)
{
if (symbol is not INamedTypeSymbol named)
{
return null;
}
string fullName = named.ConstructedFrom.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
fullName = fullName.StartsWith("global::", StringComparison.Ordinal)
? fullName.Substring("global::".Length)
: fullName;
if (fullName == "Apache.Fory.Schema.Types.List<TElement>")
{
if (named.TypeArguments.Length != 1 ||
TryParseSchemaType(named.TypeArguments[0]) is not SchemaTypeModel element)
{
return null;
}
return new SchemaTypeModel(22, SchemaTypeKind.List, ImmutableArray.Create(element));
}
if (fullName == "Apache.Fory.Schema.Types.Array<TElement>")
{
if (named.TypeArguments.Length != 1 ||
TryParseSchemaType(named.TypeArguments[0]) is not SchemaTypeModel element ||
element.HasExplicitScalarEncoding ||
TryResolveArrayTypeIdForElement(element.TypeId) is not uint arrayTypeId)
{
return null;
}
return new SchemaTypeModel(arrayTypeId, SchemaTypeKind.PackedArray, ImmutableArray.Create(element));
}
if (fullName == "Apache.Fory.Schema.Types.Fixed<TScalar>")
{
if (named.TypeArguments.Length != 1 ||
TryParseSchemaType(named.TypeArguments[0]) is not SchemaTypeModel scalar ||
TryResolveFixedTypeId(scalar.TypeId) is not uint fixedTypeId)
{
return null;
}
return new SchemaTypeModel(
fixedTypeId,
SchemaTypeKind.Scalar,
ImmutableArray<SchemaTypeModel>.Empty,
hasExplicitScalarEncoding: true);
}
if (fullName == "Apache.Fory.Schema.Types.Tagged<TScalar>")
{
if (named.TypeArguments.Length != 1 ||
TryParseSchemaType(named.TypeArguments[0]) is not SchemaTypeModel scalar ||
TryResolveTaggedTypeId(scalar.TypeId) is not uint taggedTypeId)
{
return null;
}
return new SchemaTypeModel(
taggedTypeId,
SchemaTypeKind.Scalar,
ImmutableArray<SchemaTypeModel>.Empty,
hasExplicitScalarEncoding: true);
}
if (fullName == "Apache.Fory.Schema.Types.Set<TElement>")
{
if (named.TypeArguments.Length != 1 ||
TryParseSchemaType(named.TypeArguments[0]) is not SchemaTypeModel element)
{
return null;
}
return new SchemaTypeModel(23, SchemaTypeKind.Set, ImmutableArray.Create(element));
}
if (fullName == "Apache.Fory.Schema.Types.Map<TKey, TValue>")
{
if (named.TypeArguments.Length != 2 ||
TryParseSchemaType(named.TypeArguments[0]) is not SchemaTypeModel key ||
TryParseSchemaType(named.TypeArguments[1]) is not SchemaTypeModel value)
{
return null;
}
return new SchemaTypeModel(24, SchemaTypeKind.Map, ImmutableArray.Create(key, value));
}
return TryResolveSchemaTypeId(fullName, out uint typeId, out SchemaTypeKind kind)
? new SchemaTypeModel(typeId, kind, ImmutableArray<SchemaTypeModel>.Empty)
: null;
}
private static bool TryResolveSchemaTypeId(string fullName, out uint typeId, out SchemaTypeKind kind)
{
kind = SchemaTypeKind.Scalar;
switch (fullName)
{
case "Apache.Fory.Schema.Types.Bool":
typeId = 1;
return true;
case "Apache.Fory.Schema.Types.Int8":
typeId = 2;
return true;
case "Apache.Fory.Schema.Types.Int16":
typeId = 3;
return true;
case "Apache.Fory.Schema.Types.Int32":
typeId = 5;
return true;
case "Apache.Fory.Schema.Types.Int64":
typeId = 7;
return true;
case "Apache.Fory.Schema.Types.UInt8":
typeId = 9;
return true;
case "Apache.Fory.Schema.Types.UInt16":
typeId = 10;
return true;
case "Apache.Fory.Schema.Types.UInt32":
typeId = 12;
return true;
case "Apache.Fory.Schema.Types.UInt64":
typeId = 14;
return true;
case "Apache.Fory.Schema.Types.Float16":
typeId = 17;
return true;
case "Apache.Fory.Schema.Types.BFloat16":
typeId = 18;
return true;
case "Apache.Fory.Schema.Types.Float32":
typeId = 19;
return true;
case "Apache.Fory.Schema.Types.Float64":
typeId = 20;
return true;
case "Apache.Fory.Schema.Types.String":
typeId = 21;
return true;
case "Apache.Fory.Schema.Types.Binary":
typeId = 41;
return true;
case "Apache.Fory.Schema.Types.Duration":
typeId = 37;
return true;
case "Apache.Fory.Schema.Types.Timestamp":
typeId = 38;
return true;
case "Apache.Fory.Schema.Types.Date":
typeId = 39;
return true;
case "Apache.Fory.Schema.Types.Decimal":
typeId = 40;
return true;
default:
typeId = 0;
return false;
}
}
private static uint? TryResolveFixedTypeId(uint scalarTypeId)
{
return scalarTypeId switch
{
5 => 4,
7 => 6,
12 => 11,
14 => 13,
4 or 6 or 11 or 13 => scalarTypeId,
_ => null,
};
}
private static uint? TryResolveTaggedTypeId(uint scalarTypeId)
{
return scalarTypeId switch
{
7 or 6 => 8,
14 or 13 => 15,
_ => null,
};
}
private static uint? TryResolveArrayTypeIdForElement(uint elementTypeId)
{
return elementTypeId switch
{
1 => 43,
2 => 44,
3 => 45,
4 or 5 => 46,
6 or 7 or 8 => 47,
9 => 48,
10 => 49,
11 or 12 => 50,
13 or 14 or 15 => 51,
17 => 53,
18 => 54,
19 => 55,
20 => 56,
_ => null,
};
}
private static bool IsPackedArrayTypeId(uint typeId)
{
return typeId is 41 or 43 or 44 or 45 or 46 or 47 or 48 or 49 or 50 or 51 or 53 or 54 or 55 or 56;
}
private static TypeResolution ResolveTypeResolution(ITypeSymbol type, SchemaTypeModel? schemaType)
{
TypeClassification baseType = ClassifyType(type);
if (schemaType is null)
{
return new TypeResolution(true, baseType);
}
bool isPrimitive = schemaType.Kind == SchemaTypeKind.Scalar;
bool isCollection = schemaType.Kind == SchemaTypeKind.List ||
schemaType.Kind == SchemaTypeKind.Set;
bool isMap = schemaType.Kind == SchemaTypeKind.Map;
bool isCompressedNumeric = schemaType.TypeId is 5 or 7 or 8 or 12 or 14 or 15;
int primitiveSize = schemaType.TypeId switch
{
1 or 2 or 9 => 1,
3 or 10 or 17 or 18 => 2,
4 or 5 or 11 or 12 or 19 => 4,
6 or 7 or 8 or 13 or 14 or 15 or 20 => 8,
_ => 0,
};
return new TypeResolution(
true,
new TypeClassification(
schemaType.TypeId,
isPrimitive,
true,
isCollection,
isMap,
isCompressedNumeric,
primitiveSize));
}
private static TypeClassification ClassifyType(ITypeSymbol type)
{
if (ResolveDynamicAnyKind(type) == DynamicAnyKind.AnyValue)
{
return new TypeClassification(0, false, true, false, false, false, 0);
}
if (type.SpecialType == SpecialType.System_Boolean)
{
return new TypeClassification(1, true, true, false, false, false, 1);
}
if (type.SpecialType == SpecialType.System_SByte)
{
return new TypeClassification(2, true, true, false, false, false, 1);
}
if (type.SpecialType == SpecialType.System_Int16)
{
return new TypeClassification(3, true, true, false, false, false, 2);
}
if (type.SpecialType == SpecialType.System_Int32)
{
return new TypeClassification(5, true, true, false, false, true, 4);
}
if (type.SpecialType == SpecialType.System_Int64)
{
return new TypeClassification(7, true, true, false, false, true, 8);
}
if (type.SpecialType == SpecialType.System_Byte)
{
return new TypeClassification(9, true, true, false, false, false, 1);
}
if (type.SpecialType == SpecialType.System_UInt16)
{
return new TypeClassification(10, true, true, false, false, false, 2);
}
if (type.SpecialType == SpecialType.System_UInt32)
{
return new TypeClassification(12, true, true, false, false, true, 4);
}
if (type.SpecialType == SpecialType.System_UInt64)
{
return new TypeClassification(14, true, true, false, false, true, 8);
}
if (type.SpecialType == SpecialType.System_Single)
{
return new TypeClassification(19, true, true, false, false, false, 4);
}
if (string.Equals(type.ToDisplayString(), "System.Half", StringComparison.Ordinal))
{
return new TypeClassification(17, true, true, false, false, false, 2);
}
if (string.Equals(type.ToDisplayString(), "Apache.Fory.BFloat16", StringComparison.Ordinal))
{
return new TypeClassification(18, true, true, false, false, false, 2);
}
if (type.SpecialType == SpecialType.System_Double)
{
return new TypeClassification(20, true, true, false, false, false, 8);
}
if (type.SpecialType == SpecialType.System_String)
{
return new TypeClassification(21, false, true, false, false, false, 0);
}
if (IsDateType(type))
{
return new TypeClassification(39, false, true, false, false, false, 0);
}
if (IsTimestampType(type))
{
return new TypeClassification(38, false, true, false, false, false, 0);
}
if (IsDurationType(type))
{
return new TypeClassification(37, false, true, false, false, false, 0);
}
if (type.SpecialType == SpecialType.System_Decimal ||
string.Equals(type.ToDisplayString(), "Apache.Fory.ForyDecimal", StringComparison.Ordinal))
{
return new TypeClassification(40, false, true, false, false, false, 0);
}
if (type is IArrayTypeSymbol arrayType)
{
if (TryResolvePackedArrayTypeIdForElement(arrayType.ElementType) is uint packedArrayTypeId)
{
return new TypeClassification(packedArrayTypeId, false, true, false, false, false, 0);
}
return new TypeClassification(22, false, true, true, false, false, 0);
}
if (TryGetListElementType(type, out _))
{
return new TypeClassification(22, false, true, true, false, false, 0);
}
if (TryGetSetElementType(type, out _))
{
return new TypeClassification(23, false, true, true, false, false, 0);
}
if (TryGetMapTypeArguments(type, out _, out _))
{
return new TypeClassification(24, false, true, false, true, false, 0);
}
if (IsUnionType(type))
{
return new TypeClassification(33, false, false, false, false, false, 0);
}
return new TypeClassification(27, false, false, false, false, false, 0);
}
private static DynamicAnyKind ResolveDynamicAnyKind(ITypeSymbol type)
{
if (type.SpecialType == SpecialType.System_Object)
{
return DynamicAnyKind.AnyValue;
}
return DynamicAnyKind.None;
}
private static bool IsDateType(ITypeSymbol symbol)
{
return string.Equals(symbol.ToDisplayString(), "System.DateOnly", StringComparison.Ordinal);
}
private static bool IsTimestampType(ITypeSymbol symbol)
{
string name = symbol.ToDisplayString();
return string.Equals(name, "System.DateTime", StringComparison.Ordinal) ||
string.Equals(name, "System.DateTimeOffset", StringComparison.Ordinal);
}
private static bool IsDurationType(ITypeSymbol symbol)
{
return string.Equals(symbol.ToDisplayString(), "System.TimeSpan", StringComparison.Ordinal);
}
private static bool IsUnionType(ITypeSymbol symbol)
{
INamedTypeSymbol? current = symbol as INamedTypeSymbol;
while (current is not null)
{
if (string.Equals(current.ToDisplayString(), "Apache.Fory.Union", StringComparison.Ordinal))
{
return true;
}
current = current.BaseType;
}
return false;
}
private static bool IsTypeSealed(ITypeSymbol symbol)
{
if (symbol.TypeKind == TypeKind.TypeParameter)
{
return false;
}
return symbol.IsSealed;
}
private static bool TryGetListElementType(ITypeSymbol type, out ITypeSymbol? elementType)
{
elementType = null;
if (type is IArrayTypeSymbol arrayType)
{
elementType = arrayType.ElementType;
return true;
}
if (type is not INamedTypeSymbol named)
{
return false;
}
string genericName = named.ConstructedFrom.ToDisplayString();
if (genericName is
"System.Collections.Generic.List<T>" or
"System.Collections.Generic.LinkedList<T>" or
"System.Collections.Generic.Queue<T>" or
"System.Collections.Generic.Stack<T>" or
"System.Collections.Generic.IList<T>" or
"System.Collections.Generic.IReadOnlyList<T>")
{
elementType = named.TypeArguments[0];
return true;
}
return false;
}
private static bool TryGetSetElementType(ITypeSymbol type, out ITypeSymbol? elementType)
{
elementType = null;
if (type is not INamedTypeSymbol named)
{
return false;
}
string genericName = named.ConstructedFrom.ToDisplayString();
if (genericName is
"System.Collections.Generic.HashSet<T>" or
"System.Collections.Generic.SortedSet<T>" or
"System.Collections.Immutable.ImmutableHashSet<T>" or
"System.Collections.Generic.ISet<T>" or
"System.Collections.Generic.IReadOnlySet<T>" or
"System.Collections.Immutable.IImmutableSet<T>")
{
elementType = named.TypeArguments[0];
return true;
}
return false;
}
private static bool TryGetMapTypeArguments(ITypeSymbol type, out ITypeSymbol? keyType, out ITypeSymbol? valueType)
{
keyType = null;
valueType = null;
if (type is not INamedTypeSymbol named)
{
return false;
}
string genericName = named.ConstructedFrom.ToDisplayString();
if (genericName is
"System.Collections.Generic.Dictionary<TKey, TValue>" or
"System.Collections.Generic.SortedDictionary<TKey, TValue>" or
"System.Collections.Generic.SortedList<TKey, TValue>" or
"System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>" or
"System.Collections.Generic.IDictionary<TKey, TValue>" or
"System.Collections.Generic.IReadOnlyDictionary<TKey, TValue>" or
"Apache.Fory.NullableKeyDictionary<TKey, TValue>")
{
keyType = named.TypeArguments[0];
valueType = named.TypeArguments[1];
return true;
}
return false;
}
private static uint? TryResolvePackedArrayTypeIdForElement(ITypeSymbol elementType)
{
(bool isNullable, ITypeSymbol unwrapped) = UnwrapNullable(elementType);
if (isNullable)
{
return null;
}
uint elementTypeId = ClassifyType(unwrapped).TypeId;
return elementTypeId switch
{
9 => 41, // byte -> binary
1 => 43, // bool -> bool array
2 => 44, // sbyte -> int8 array
3 => 45, // short -> int16 array
5 => 46, // int -> int32 array
7 => 47, // long -> int64 array
10 => 49, // ushort -> uint16 array
12 => 50, // uint -> uint32 array
14 => 51, // ulong -> uint64 array
17 => 53, // Half -> float16 array
18 => 54, // BFloat16 -> bfloat16 array
19 => 55, // float -> float32 array
20 => 56, // double -> float64 array
_ => null,
};
}
private static (bool, ITypeSymbol) UnwrapNullable(ITypeSymbol type)
{
if (type is INamedTypeSymbol named &&
named.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
{
return (true, named.TypeArguments[0]);
}
if (type.IsReferenceType && type.NullableAnnotation == NullableAnnotation.Annotated)
{
return (true, type.WithNullableAnnotation(NullableAnnotation.NotAnnotated));
}
return (false, type);
}
private static string BoolLiteral(bool value) => value ? "true" : "false";
private static string EscapeString(string value) => value.Replace("\\", "\\\\").Replace("\"", "\\\"");
private static string ToSnakeCase(string name)
{
if (string.IsNullOrEmpty(name))
{
return name;
}
StringBuilder sb = new(name.Length + 4);
for (int i = 0; i < name.Length; i++)
{
char c = name[i];
if (char.IsUpper(c))
{
if (i > 0)
{
bool prevUpper = char.IsUpper(name[i - 1]);
bool nextUpperOrEnd = i + 1 >= name.Length || char.IsUpper(name[i + 1]);
bool leadingPascalBoundary = i == 1 && prevUpper && !nextUpperOrEnd;
if ((!prevUpper || !nextUpperOrEnd) && !leadingPascalBoundary)
{
sb.Append('_');
}
}
sb.Append(char.ToLowerInvariant(c));
}
else
{
sb.Append(c);
}
}
return sb.ToString();
}
private static string Sanitize(string name)
{
StringBuilder sb = new(name.Length + 8);
foreach (char c in name)
{
sb.Append(char.IsLetterOrDigit(c) ? c : '_');
}
return sb.ToString();
}
private sealed class TypeResolution
{
public TypeResolution(bool supported, TypeClassification classification)
{
Supported = supported;
Classification = classification;
}
public bool Supported { get; }
public TypeClassification Classification { get; }
}
private sealed class TypeClassification
{
public TypeClassification(
uint typeId,
bool isPrimitive,
bool isBuiltIn,
bool isCollection,
bool isMap,
bool isCompressedNumeric,
int primitiveSize)
{
TypeId = typeId;
IsPrimitive = isPrimitive;
IsBuiltIn = isBuiltIn;
IsCollection = isCollection;
IsMap = isMap;
IsCompressedNumeric = isCompressedNumeric;
PrimitiveSize = primitiveSize;
}
public uint TypeId { get; }
public bool IsPrimitive { get; }
public bool IsBuiltIn { get; }
public bool IsCollection { get; }
public bool IsMap { get; }
public bool IsCompressedNumeric { get; }
public int PrimitiveSize { get; }
}
private sealed class TypeMetaFieldTypeModel
{
public TypeMetaFieldTypeModel(
string typeIdExpr,
bool nullable,
bool trackRefByContext,
ImmutableArray<TypeMetaFieldTypeModel> generics)
{
TypeIdExpr = typeIdExpr;
Nullable = nullable;
TrackRefByContext = trackRefByContext;
Generics = generics;
}
public string TypeIdExpr { get; }
public bool Nullable { get; }
public bool TrackRefByContext { get; }
public ImmutableArray<TypeMetaFieldTypeModel> Generics { get; }
}
private sealed class SchemaTypeModel
{
public SchemaTypeModel(
uint typeId,
SchemaTypeKind kind,
ImmutableArray<SchemaTypeModel> generics,
bool hasExplicitScalarEncoding = false)
{
TypeId = typeId;
Kind = kind;
Generics = generics;
HasExplicitScalarEncoding = hasExplicitScalarEncoding;
}
public uint TypeId { get; }
public SchemaTypeKind Kind { get; }
public ImmutableArray<SchemaTypeModel> Generics { get; }
public bool HasExplicitScalarEncoding { get; }
}
private sealed class FieldCodecModel
{
public FieldCodecModel(
FieldCodecKind kind,
uint typeId,
string typeName,
bool nullable,
bool nullableValueType,
CarrierKind carrierKind,
ImmutableArray<FieldCodecModel> generics)
{
Kind = kind;
TypeId = typeId;
TypeName = typeName;
Nullable = nullable;
NullableValueType = nullableValueType;
CarrierKind = carrierKind;
Generics = generics;
}
public FieldCodecKind Kind { get; }
public uint TypeId { get; }
public string TypeName { get; }
public bool Nullable { get; }
public bool NullableValueType { get; }
public CarrierKind CarrierKind { get; }
public ImmutableArray<FieldCodecModel> Generics { get; }
}
private sealed class TypeModel
{
public TypeModel(
string typeName,
string serializerName,
DeclKind kind,
ImmutableArray<MemberModel> members,
ImmutableArray<MemberModel> sortedMembers)
{
TypeName = typeName;
SerializerName = serializerName;
Kind = kind;
Members = members;
SortedMembers = sortedMembers;
}
public string TypeName { get; }
public string SerializerName { get; }
public DeclKind Kind { get; }
public ImmutableArray<MemberModel> Members { get; }
public ImmutableArray<MemberModel> SortedMembers { get; }
}
private sealed class MemberModel
{
public MemberModel(
string name,
string fieldIdentifier,
int originalIndex,
MemberDeclKind declKind,
string typeName,
bool isNullable,
bool isNullableValueType,
short? fieldId,
TypeClassification classification,
int group,
bool isCollection,
bool useDictionaryTypeInfoCache,
bool isRefType,
bool needsFieldTypeInfo,
DynamicAnyKind dynamicAnyKind,
TypeMetaFieldTypeModel typeMeta,
FieldCodecModel? fieldCodec)
{
Name = name;
FieldIdentifier = fieldIdentifier;
OriginalIndex = originalIndex;
DeclKind = declKind;
TypeName = typeName;
IsNullable = isNullable;
IsNullableValueType = isNullableValueType;
FieldId = fieldId;
Classification = classification;
Group = group;
IsCollection = isCollection;
UseDictionaryTypeInfoCache = useDictionaryTypeInfoCache;
IsRefType = isRefType;
NeedsFieldTypeInfo = needsFieldTypeInfo;
DynamicAnyKind = dynamicAnyKind;
TypeMeta = typeMeta;
FieldCodec = fieldCodec;
}
public string Name { get; }
public string FieldIdentifier { get; }
public int OriginalIndex { get; }
public MemberDeclKind DeclKind { get; }
public string TypeName { get; }
public bool IsNullable { get; }
public bool IsNullableValueType { get; }
public short? FieldId { get; }
public TypeClassification Classification { get; }
public int Group { get; }
public bool IsCollection { get; }
public bool UseDictionaryTypeInfoCache { get; }
public bool IsRefType { get; }
public bool NeedsFieldTypeInfo { get; }
public DynamicAnyKind DynamicAnyKind { get; }
public TypeMetaFieldTypeModel TypeMeta { get; }
public FieldCodecModel? FieldCodec { get; }
}
private enum MemberDeclKind
{
Field,
Property,
}
private enum DeclKind
{
Unknown,
Class,
Struct,
Enum,
}
private enum DynamicAnyKind
{
None,
AnyValue,
}
private enum SchemaTypeKind
{
Scalar,
PackedArray,
List,
Set,
Map,
}
private enum FieldCodecKind
{
Scalar,
PackedArray,
List,
Set,
Map,
}
private enum CarrierKind
{
Value,
Array,
List,
HashSet,
Dictionary,
NullableKeyDictionary,
}
}