Repository navigation
Conversation
The source generator now emits a per-assembly module initializer that statically registers every [YamlObject] formatter plus statically constructed formatters for the closed generic shapes harvested from the member graph (enums, collections, Nullable, closed user generics), so nothing relies on reflection-only registration and every needed generic instantiation is compiled into Native AOT binaries. - [assembly: YamlSerializable(typeof(...))] declares root-only shapes; the whole reachable closure of the declared type is registered - BuiltinResolver's MakeGenericType path no longer poisons the formatter cache on AOT: failures fall through to GeneratedResolver - The reflection-based lazy registration remains as a JIT fallback (lazy module cctors, assemblies built with an older generator) - Trim/AOT analyzers enabled (IsAotCompatible) with zero IL warnings; DynamicallyAccessedMembers annotations on the enum formatter - VYaml.Tests.NativeAot: PublishAot smoke test wired into CI Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hadashiA
marked this pull request as draft
September 2, 2026 23:32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes VYaml work on .NET Native AOT and trimmed (
PublishTrimmed) apps, with zero trim/AOT analysis warnings. Previously a Native AOT publish crashed on the very firstSerializecall: the generated__RegisterVYamlFormattermethods were only reachable via reflection (so ILC trimmed them —[Preserve]is Unity-IL2CPP-only), andBuiltinResolvercomposed enum/collection formatters withMakeGenericType, whose value-type instantiations don't exist in an AOT binary.The design follows the approach MessagePack-CSharp v4 is taking (module-initializer push registration + compile-time harvesting of closed generic shapes), translated to VYaml's resolver architecture.
What changed
Source generator
VYamlGeneratedRegistration.g.csper assembly: a[ModuleInitializer]that__RegisterVYamlFormatter()for every closed[YamlObject]type (interfaces register their nested generated formatter directly),EnumAsStringFormatter<TEnum>), collections (List<T>,Dictionary<K,V>, arrays 1–4D, sets, queues, tuples, interfaces, ...),Nullable<T>, and closed instantiations of generic[YamlObject]types (recursing into their substituted members).[assembly: YamlSerializable(typeof(...))](in VYaml.Annotations) declares root-only shapes — the ones that only appear as top-levelSerialize/Deserializearguments; the whole reachable closure of the declared type is harvested. Open generics are rejected with the newVYAML016diagnostic.ModuleInitializerAttributepolyfill is emitted when the target framework lacks it (netstandard2.0/2.1, Unity), gated on the compilation not already having one. Registration generation requires C# 9+; older LangVersions keep the previous behavior.Runtime
BuiltinResolver: theMakeGenericType/Activator.CreateInstancepath no longer throws out ofFormatterCache<T>'s static constructor on AOT — failures (NotSupportedException,TypeLoadException,MissingMethodException) return null soStandardResolverfalls through toGeneratedResolver, where the statically registered formatter lives. JIT and IL2CPP behavior is unchanged (the dynamic path still works there and wins first).GeneratedResolver: the reflection-based lazy registration is kept as a JIT fallback — the CLR runs module initializers lazily, and it also covers assemblies compiled with an older generator.EnumAsStringFormatter<T>:[DynamicallyAccessedMembers(PublicFields)]annotations so enum field metadata survives trimming.IsAotCompatible=trueon net8.0+ targets (enables the trim/AOT analyzers, published to NuGet metadata); all remaining analyzer warnings resolved or suppressed with justifications (TypeFormatter'sType.GetTypeis inherently dynamic and documented as such). PolySharp extended to netstandard2.1 for the annotation attributes.Tests / CI / docs
VYaml.Tests.NativeAot: aPublishAotsmoke test covering roundtrips with enum / alias enum / collection / array / nested / closed-generic members, interface and abstract unions,[assembly: YamlSerializable]roots, the untyped object path, and multi-document deserialization. Wired into CI: the publish fails on anywarning IL*, then the native binary runs its checks.Verification
dotnet publish -c Releaseof the AOT test on osx-arm64: 0 IL warnings, binary runs all 20 checks green.Compatibility
Known limitations
[assembly: YamlSerializable](documented).Deserialize<Dictionary<K,V>>etc. of shapes neither harvested nor declared fail on AOT with the existing "not registered" error rather than a crash.🤖 Generated with Claude Code