You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The serializer manifest registers implementation types and rediscovers their target contracts by enumerating interfaces at startup. Under NativeAOT, preserving an implementation's constructors and interfaces does not reliably preserve the generic target definitions needed by that reflection path. The default manifest consequently fails resolving System.ValueTuple`8 during serializer initialization.
The generator now emits explicit implementation/target registrations for field and base codecs, value serializers, deep and base copiers, activators, and converters. Target-taking TypeManifestOptions.Add* APIs preserve implementation constructors and target interface metadata. CodecProvider and TypeConverter consume these mappings directly. Converter activation uses the registered surrogate. Existing single-type registrations and collection membership/order/removal retain their compatibility behavior.
Declarative SerializationType descriptions preserve fixed and reordered generic arguments and structural array shapes. Lookup selects exact closed entries first, then matching named generic entries, then array and bare-parameter patterns in reverse registration order. Implementation closure uses the winning registration's parameter bindings. Accessible targets use typeof; inaccessible targets resolve a generated constant assembly-qualified name as part of their registration.
SerializationType.Array is a structural target-matching pattern. Executable array types require source-known closed descriptors such as SerializationType.Create(typeof(MyValue[])) or explicit closed converter/surrogate registrations. The generator emits concrete descriptors for fully known array nodes, including those nested inside otherwise generic contracts. Resolving an array pattern as an executable type produces NotSupportedException with registration guidance on both JIT and NativeAOT. This intentionally narrows the new, unreleased recipe API: metadata Type construction alone can succeed while its native representation is unavailable. Runtime array construction and its scoped AOT suppression are removed. Typed closed code supplies the native representations used by supported registrations.
The Metadata scenario uses the shared harness and discovery matrix merged in #11367. It initializes the default metadata consumers, resolves tuple definitions of arity 1–8, verifies inaccessible and inherited target metadata, and round-trips primitives, tuples, and a custom array codec. It also exercises registration priority, reordered parameter binding, generated and explicit concrete-array surrogate descriptors, immutable-array implementation closure, and uniform rejection of unresolved executable array recipes. Its descriptor retains legacy diagnostics while rejecting IL diagnostics from generated manifest initialization and explicit registration APIs.
The generated public API surface is verified with GenAPI. Coverage includes interface-inspection traps, legacy/explicit precedence and removal, generic shape binding, selected-registration closure, source-known array emission, closed converter witnesses, validation, and registration annotations. Normal Release package validation remains enabled.
The independent nested-generic NativeAOT Type.Namespace inconsistency is tracked in dotnet/runtime#135106. The private-target probe here verifies registration and full-name resolution.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🟡 Changes recommended
The NativeAOT smoke program does not compile due to parameter shadowing, and its CI warning configuration does not enforce the stated metadata-root diagnostics.
The current-main baseline is commit d114d7522a and uses the same reviewed coverage matrix.
Coverage combines every CI test matrix job, including providers, CodeGen, .NET 8/10, Linux, Windows, and macOS, using canonical physical source and branch identities.
The comparison remains report-only while normal line and branch variance is calibrated.
Field-codec and serializer registrations share SerializerContracts even though their membership sets are independently mutable. If the same implementation is also present in Serializers (for example via AddBaseCodec), removing it from FieldCodecs does not remove its field-codec mapping: the field contract is consumed again while iterating SerializerTypes. Preserve the originating collection for each contract (or use separate contract maps) so existing collection removal semantics remain effective.
Removing an explicitly registered implementation from the public HashSet only makes these contracts temporarily inactive: the dictionary entries remain. If the same type is later re-added (for example, register A for T1, remove A from Copiers, then register A for T2), both T1 and T2 are consumed, so the removed registration is resurrected. Track contract lifetime with collection removal/re-addition (or clear stale contracts on a fresh membership generation) so mutable-collection removal remains effective.
Storing contracts in a list per implementation loses global registration order because consumption iterates each implementation once in the implementation HashSet. For example, registering pattern A, then overlapping pattern B, then another overlapping pattern for A causes all of A's entries to be consumed before B, so B wins even though A was registered last. This contradicts the documented reverse-registration-order selection. Preserve a category-wide ordered registration sequence rather than grouping order solely by implementation.
Generic constraint violations cause candidate selection exceptions
Pattern matching does not account for generic constraints before selecting a candidate. A valid registration such as ReferenceCopier<T> : IDeepCopier<T> where T : class produces a bare-parameter pattern which also matches int; CloseImplementation then calls MakeGenericType(typeof(int)) and throws instead of trying the next candidate or the normal shallow-copy fallback. Reject candidates whose bound arguments do not satisfy the implementation constraints during selection.
A structurally matching candidate is selected without checking whether its bound arguments satisfy the implementation's generic constraints. For example, [RegisterCopier] StructCopier<T> : IDeepCopier<T> where T : struct emits a bare Parameter(0) pattern; a request for string selects it here, then MakeGenericType(typeof(string)) throws instead of considering an earlier applicable candidate. Validate closure while scanning candidates (and continue when constraints reject the binding) so constrained generated registrations do not hijack unsupported targets.
This branch has not been deployed
No deployments
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
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.
Fixes #11368.
The serializer manifest registers implementation types and rediscovers their target contracts by enumerating interfaces at startup. Under NativeAOT, preserving an implementation's constructors and interfaces does not reliably preserve the generic target definitions needed by that reflection path. The default manifest consequently fails resolving
System.ValueTuple`8during serializer initialization.The generator now emits explicit implementation/target registrations for field and base codecs, value serializers, deep and base copiers, activators, and converters. Target-taking
TypeManifestOptions.Add*APIs preserve implementation constructors and target interface metadata.CodecProviderandTypeConverterconsume these mappings directly. Converter activation uses the registered surrogate. Existing single-type registrations and collection membership/order/removal retain their compatibility behavior.Declarative
SerializationTypedescriptions preserve fixed and reordered generic arguments and structural array shapes. Lookup selects exact closed entries first, then matching named generic entries, then array and bare-parameter patterns in reverse registration order. Implementation closure uses the winning registration's parameter bindings. Accessible targets usetypeof; inaccessible targets resolve a generated constant assembly-qualified name as part of their registration.SerializationType.Arrayis a structural target-matching pattern. Executable array types require source-known closed descriptors such asSerializationType.Create(typeof(MyValue[]))or explicit closed converter/surrogate registrations. The generator emits concrete descriptors for fully known array nodes, including those nested inside otherwise generic contracts. Resolving an array pattern as an executable type producesNotSupportedExceptionwith registration guidance on both JIT and NativeAOT. This intentionally narrows the new, unreleased recipe API: metadataTypeconstruction alone can succeed while its native representation is unavailable. Runtime array construction and its scoped AOT suppression are removed. Typed closed code supplies the native representations used by supported registrations.The Metadata scenario uses the shared harness and discovery matrix merged in #11367. It initializes the default metadata consumers, resolves tuple definitions of arity 1–8, verifies inaccessible and inherited target metadata, and round-trips primitives, tuples, and a custom array codec. It also exercises registration priority, reordered parameter binding, generated and explicit concrete-array surrogate descriptors, immutable-array implementation closure, and uniform rejection of unresolved executable array recipes. Its descriptor retains legacy diagnostics while rejecting IL diagnostics from generated manifest initialization and explicit registration APIs.
The generated public API surface is verified with GenAPI. Coverage includes interface-inspection traps, legacy/explicit precedence and removal, generic shape binding, selected-registration closure, source-known array emission, closed converter witnesses, validation, and registration annotations. Normal Release package validation remains enabled.
The independent nested-generic NativeAOT
Type.Namespaceinconsistency is tracked in dotnet/runtime#135106. The private-target probe here verifies registration and full-name resolution.Microsoft Reviewers: Open in CodeFlow