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 |
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.