Improve Native AOT Support (Closes #1085)#1092
Conversation
Add net9.0 and net10.0 to both the library and test project TargetFrameworks. Fix OrderedDictionary ambiguity for net9.0+: .NET 9 introduced System.Collections.Generic.OrderedDictionary<TKey, TValue> which conflicts with YamlDotNet.Helpers.OrderedDictionary<TKey, TValue>. Disambiguate by using fully qualified type names in YamlMappingNode.cs and OrderedDictionaryTests.cs. Update appveyor.yml to install the .NET 10 SDK and add artifact paths for the new target frameworks.
Added: Nullable type support for all built-in type convertersThe latest commit ( ProblemThe converters introduced in this PR ( ChangesAll value-type converters (
Reference-type converter (
TestsAdded
All existing tests continue to pass. |
Updated PR — New features addedThis PR is now rebased on top of Summary of changes1. Improve error messages for unregistered types in static contextClear, actionable error messages when a type is missing from the static context, instead of cryptic 2. Support
|
Replace ArgumentOutOfRangeException with InvalidOperationException in the generated StaticObjectFactory code. The new error message clearly indicates that the type is not registered and suggests adding [YamlSerializable] to the static context class. Addresses suggestion aaubry#5 from YAML_DOTNET_SUGGESTIONS.md.
The source generator now detects C# 'required' properties and fields and
emits object initializer syntax with 'default!' values instead of bare
'new T()'. This prevents CS9035 compile errors when types have required
members.
Generated code example:
new MyType() { RequiredProp = default!, RequiredField = default! }
Addresses suggestion aaubry#2 from YAML_DOTNET_SUGGESTIONS.md.
Add recognition of System.Collections.Generic.OrderedDictionary<TKey, TValue> (.NET 9+) in the source generator's CheckForSupportedGeneric method. The type is now correctly identified as a dictionary, enabling proper code generation for IsDictionary, GetKeyType, GetValueType, and Create methods. Addresses suggestion aaubry#3 from YAML_DOTNET_SUGGESTIONS.md.
Add TimeSpanConverter and UriConverter as built-in IYamlTypeConverter implementations, registered by default in both the regular and static builder skeletons. This means TimeSpan and Uri values now round-trip correctly without requiring users to write custom converters. Both converters support JSON-compatible mode (double-quoted output) and are automatically swapped in when JsonCompatible() is called on the serializer builder. Addresses suggestion aaubry#4 from YAML_DOTNET_SUGGESTIONS.md.
All value-type converters (Guid, TimeSpan, DateTime, DateTimeOffset, DateOnly, TimeOnly) now accept their Nullable<T> counterpart. All converters (including reference-type Uri) now handle null values in ReadYaml (return null for empty/null scalars) and WriteYaml (emit an empty scalar instead of throwing NullReferenceException). SystemTypeConverter is intentionally excluded — it is already fixed by PR aaubry#1091.
Types with a static Parse(string, IFormatProvider) method — such as those implementing IParsable<T> (.NET 7+) — are now automatically deserialized from YAML scalars without needing a custom IYamlTypeConverter. This is especially important for the static/AOT deserialization path where the NullTypeConverter cannot perform type conversions. Types like TimeSpan, DateTimeOffset, DateOnly, TimeOnly, Guid, and IPAddress now work out of the box in both the regular and static deserializer paths.
The source generator now emits a compile-time warning (YDNG001) when a property or field on a [YamlSerializable] type references a type that is not itself registered in the YamlDotNet static context. This catches missing [YamlSerializable(typeof(T))] registrations at compile time instead of at runtime, preventing ArgumentOutOfRangeException errors during deserialization. The diagnostic checks property/field types recursively, handles nullable types, and skips well-known BCL types (primitives, Guid, TimeSpan, etc.) and generic collections (verifying their element types instead).
|
I’m keeping all the changes in this PR for now, but feel free to suggest any modifications or ask me to split them into smaller PRs. |
|
I merged in your other branches and now this one has conflicts. If you can get to them quickly I can merge them in in a few hours. Otherwise I'll probably create a branch from this PR and fix them in my repository and close this PR |
|
Oh yeah, if you can remove net9.0 and net6.0 since those frameworks are EOL now that would be great. |
Working on it... 👍🏻 |
# Conflicts: # YamlDotNet/Serialization/Converters/DateOnlyConverter.cs # YamlDotNet/Serialization/Converters/DateTime8601Converter.cs # YamlDotNet/Serialization/Converters/DateTimeConverter.cs # YamlDotNet/Serialization/Converters/DateTimeOffsetConverter.cs # YamlDotNet/Serialization/Converters/TimeOnlyConverter.cs
|
Merged latest Conflict Summary5 files had conflicts, all in Conflicting files:
ResolutionInstead of keeping per-converter public bool Accepts(Type type)
{
return type == typeof(T) || Nullable.GetUnderlyingType(type) == typeof(T);
}This gives all converters inheriting from For
Verification
|
Doing... |
|
Done — removed Files updated:
All 1972 tests pass on |
Updated [Discord.Net](https://github.com/discord-net/Discord.Net) from 3.19.1 to 3.20.1. <details> <summary>Release notes</summary> _Sourced from [Discord.Net's releases](https://github.com/discord-net/Discord.Net/releases)._ ## 3.20.1 ## [3.20.1] - 2026-06-07 This release fixes a regression introduced in 3.20.0 ### Fixed - #3276 Handle null VoiceChannel in SocketVoiceState constructor (61ed916) **Full Changelog**: discord-net/Discord.Net@3.20.0...3.20.1 ## 3.20.0 ## [3.20.0] - 2026-06-06 This release brings support for checkboxes and checkbox/radio groups in modals, and also covers the "new" message search endpoint. ### Breaking changes - `SelectMenuOptionAttribute` from the Interaction Framework was renamed to `EnumOptionAttribute`. ### Added - #3232 IF modal radio buttons, and checkboxes (c95fbf6) - #3268 add support for getting messages from a guild (with filters) (31fed25) - #3255 add missing audit log action types (4476eea) - #3265 Add GET voice-state REST wrappers (13d83da) ### Fixed - #3258 propagate parent module attributes to child commands (cbc61d9) - #3263 strip RTP padding before DAVE decrypt (RFC 3550 В§5.1) (1a843fb) - #3256 Add empty payload check (6527e71) - #3264 Fix reference to PreCompiledLambdas/UseCompiledLambda (763aa79) - #3271 fix for #3269 (9abfbfd) - #3272 Fix default array converter in modals & add docs for checkboxes/radio groups (527764c) ### Misc - #3254 user `global_name` description (05af64b) - #3257 feat(Core): add missing JSON error codes (4272ae1) - #3259 refactor(Core): rename JSON error code (504e1db) - #3261 Message call data timestamp nullability (5a328a0) - #3266 Add play audio sample (4d8b0bc) ## New Contributors * @Archivelit made their first contribution in discord-net/Discord.Net#3256 * @Sim-hu made their first contribution in discord-net/Discord.Net#3255 * @yury-opolev made their first contribution in discord-net/Discord.Net#3263 * @apartje made their first contribution in discord-net/Discord.Net#3271 **Full Changelog**: discord-net/Discord.Net@3.19.1...3.20.0 Commits viewable in [compare view](discord-net/Discord.Net@3.19.1...3.20.1). </details> Updated [DotNetEnv](https://github.com/tonerdo/dotnet-env) from 3.1.1 to 3.2.0. <details> <summary>Release notes</summary> _Sourced from [DotNetEnv's releases](https://github.com/tonerdo/dotnet-env/releases)._ ## 3.2.0 - Switch parsing to Superpower (from Sprache) - Fix utf8 parsing - Interpolated variables parsing Commits viewable in [compare view](tonerdo/dotnet-env@v3.1.1...v3.2.0). </details> Updated [YamlDotNet](https://github.com/aaubry/YamlDotNet) from 16.3.0 to 18.1.0. <details> <summary>Release notes</summary> _Sourced from [YamlDotNet's releases](https://github.com/aaubry/YamlDotNet/releases)._ ## 18.1.0 ## What's Changed * Use NET 10 with benchmarks by @mcraiha in aaubry/YamlDotNet#1099 * Revert package upgrades by @EdwardCooke in aaubry/YamlDotNet#1104 * Added default maximum recursion level of 130 (max when using defaults on Windows/.net8) by @EdwardCooke in aaubry/YamlDotNet#1110 * Static deserializer builder needed the default maximum recursion by @EdwardCooke in aaubry/YamlDotNet#1111 ## New Contributors * @mcraiha made their first contribution in aaubry/YamlDotNet#1099 **Full Changelog**: aaubry/YamlDotNet@v18.0.0...v18.1.0 ## Breaking * Maximum depth of yaml files is now 130 by default. If you need higher you will need to adjust the maximum yaml depth. Going above 130 runs the risk of stack overflow exceptions when any exception happens inside of the deserialization ## 18.0.0 ## What's Changed * Add a parse method wrapper and caching to fix AoT compilation by @EdwardCooke in aaubry/YamlDotNet#1103 **BREAKING CHANGE** This is a breaking change in the `TypeInspectorSkeleton` class and the `ITypeInspector` interface by adding 2 methods . Quick fix to resolve those breaking changes in your own custom TypeInspector is to return false on the HasParseMethod method and return null or throw an exception on the Parse method. **Full Changelog**: aaubry/YamlDotNet@v17.1.0...v18.0.0 ## 17.1.0 ## What's Changed * Security improvements by @EdwardCooke in aaubry/YamlDotNet#1102 There was a potential breaking change for large yaml files in the MergingParser. You may need to specify the optional parameter for maximum events to be processed. It default to 100k events which is a very large yaml file. **Full Changelog**: aaubry/YamlDotNet@v17.0.0...v17.1.0 ## 17.0.0 ## What's Changed * Clean-up the "IsKey" logic by @aaubry in aaubry/YamlDotNet#1073 * Fix for gitversion and pinning it so it doesnt break...again. by @EdwardCooke in aaubry/YamlDotNet#1074 * Add max depth handling to StaticDeserializerBuilder (builds on #1072) by @skdishansachin in aaubry/YamlDotNet#1082 * Allow specifying a maximum recursion for the deserializer by @aaubry in aaubry/YamlDotNet#1072 * Fix NullReferenceException when serializing null System.Type properties by @fdcastel in aaubry/YamlDotNet#1091 * Reduce code duplication in converters and event emitters by @fdcastel in aaubry/YamlDotNet#1090 * Use pre-compiled static Regex instances in ScalarNodeDeserializer by @fdcastel in aaubry/YamlDotNet#1088 * Fix infinite loop in source generator exception handler by @fdcastel in aaubry/YamlDotNet#1087 * Fix TODOs, typos, and add missing tests by @fdcastel in aaubry/YamlDotNet#1086 * Fix YamlException.ToString() to include stack trace by @skdishansachin in aaubry/YamlDotNet#1084 * Fix remaining spec cases during parsing: L383, C2SP by @am11 in aaubry/YamlDotNet#1081 * Improve type fidelity in UnquotedStringTypeDeserialization test by @jhgbrt in aaubry/YamlDotNet#1076 * CodeQL Advanced Workflow by @aluty in aaubry/YamlDotNet#1067 * Nullable fixes in non-public code by @Kielek in aaubry/YamlDotNet#1064 * Use string interning by @simonthum in aaubry/YamlDotNet#1055 * Fix grammar in comments in DefaultValuesHandling.cs by @209jkjkjk in aaubry/YamlDotNet#1041 * fix #1031 by @dogdie233 in aaubry/YamlDotNet#1033 * Improve Native AOT Support (Closes #1085) by @fdcastel in aaubry/YamlDotNet#1092 ## New Contributors * @skdishansachin made their first contribution in aaubry/YamlDotNet#1082 * @fdcastel made their first contribution in aaubry/YamlDotNet#1091 * @jhgbrt made their first contribution in aaubry/YamlDotNet#1076 * @aluty made their first contribution in aaubry/YamlDotNet#1067 * @Kielek made their first contribution in aaubry/YamlDotNet#1064 * @simonthum made their first contribution in aaubry/YamlDotNet#1055 * @209jkjkjk made their first contribution in aaubry/YamlDotNet#1041 * @dogdie233 made their first contribution in aaubry/YamlDotNet#1033 **Full Changelog**: aaubry/YamlDotNet@v16.3.0...v17.0.0 Commits viewable in [compare view](aaubry/YamlDotNet@v16.3.0...v18.1.0). </details> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Based on #1089. Closes #1085.
This PR implements four of the seven suggestions from #1085 to improve YamlDotNet's Native AOT support via
StaticDeserializerBuilderand theYamlDotNet.Analyzers.StaticGeneratorsource generator.Changes
1. Better Error Messages for Unregistered Types (Suggestion #5)
Files changed:
StaticObjectFactoryFile.cs,ObjectTests.csReplaced
ArgumentOutOfRangeExceptionwithInvalidOperationExceptionin the generatedStaticObjectFactorycode. The new error message clearly says:This replaces the ambiguous
"Unknown type: ..."message thrown asArgumentOutOfRangeException, which was misleading and hard to diagnose.2. Support
requiredMembers in Generated Object Factories (Suggestion #2)Files changed:
StaticObjectFactoryFile.cs,ObjectTests.csThe source generator now detects C#
requiredproperties and fields (C# 11+) and emits object initializer syntax withdefault!values instead of barenew T(). This preventsCS9035compile errors when types have required members.Before (fails):
After (works):
3. Support
OrderedDictionary<TKey, TValue>in the Static Context (Suggestion #3)Files changed:
SerializableSyntaxReceiver.csAdded recognition of
System.Collections.Generic.OrderedDictionary<TKey, TValue>(.NET 9+) in the source generator'sCheckForSupportedGenericmethod. The type is now correctly identified as a dictionary, enabling proper code generation forIsDictionary,GetKeyType,GetValueType, andCreatemethods.4. Built-in Type Converters for TimeSpan and Uri (Suggestion #4)
Files changed:
TimeSpanConverter.cs(new),UriConverter.cs(new),BuilderSkeleton.cs,StaticBuilderSkeleton.cs,SerializerBuilder.cs,StaticSerializerBuilder.csAdded
TimeSpanConverterandUriConverteras built-inIYamlTypeConverterimplementations, registered by default in both the regular and static builder skeletons. Both converters support JSON-compatible mode (double-quoted output).Skipped Suggestions
The following suggestions were intentionally deferred:
Testing
UnregisteredTypeThrowsDescriptiveException— verifies the improved error messageRequiredMembersWork— verifiesrequiredproperty round-trip (net8.0 only)TimeSpanConverterTests(9 tests) — unit + round-trip testsUriConverterTests(8 tests) — unit + round-trip testsPre-existing Test Failures
4 tests in
UnquotedStringTypeDeserialization_RegularNumbersfail due to floating-point precision/locale issues unrelated to this PR. These failures also occur onmaster.