From fc2d297e7ebd58f38d2290094646ded94d96f8f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:43:03 +0000 Subject: [PATCH 01/12] Initial plan From 574687672f6fb174f14ac04d2eccc252f9eed069 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:23:17 +0000 Subject: [PATCH 02/12] Add open generic derived type support in polymorphic serialization - Add TryResolveOpenGenericDerivedType with type unification to PolymorphicTypeResolver - Resolve open generic derived types in PolymorphicTypeResolver constructor - Add new error message for unresolvable open generic derived types - Add SYSLIB1227 source generator diagnostic for invalid open generics - Update existing test to expect success for now-valid generic scenario Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f39c123c-a576-4d8d-9da4-b02cd81b6577 Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com> --- docs/project/list-of-diagnostics.md | 2 +- ...onSourceGenerator.DiagnosticDescriptors.cs | 8 + .../gen/JsonSourceGenerator.Parser.cs | 16 +- .../gen/Resources/Strings.resx | 6 + .../gen/Resources/xlf/Strings.cs.xlf | 10 + .../gen/Resources/xlf/Strings.de.xlf | 10 + .../gen/Resources/xlf/Strings.es.xlf | 10 + .../gen/Resources/xlf/Strings.fr.xlf | 10 + .../gen/Resources/xlf/Strings.it.xlf | 10 + .../gen/Resources/xlf/Strings.ja.xlf | 10 + .../gen/Resources/xlf/Strings.ko.xlf | 10 + .../gen/Resources/xlf/Strings.pl.xlf | 10 + .../gen/Resources/xlf/Strings.pt-BR.xlf | 10 + .../gen/Resources/xlf/Strings.ru.xlf | 10 + .../gen/Resources/xlf/Strings.tr.xlf | 10 + .../gen/Resources/xlf/Strings.zh-Hans.xlf | 10 + .../gen/Resources/xlf/Strings.zh-Hant.xlf | 10 + .../src/Resources/Strings.resx | 5 +- .../Metadata/PolymorphicTypeResolver.cs | 171 +++++++++++++++++- .../Text/Json/ThrowHelper.Serialization.cs | 6 + .../PolymorphicTests.CustomTypeHierarchies.cs | 5 +- 21 files changed, 338 insertions(+), 11 deletions(-) diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index 306e9f9d8f2e83..561f0710e4bcc2 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -272,7 +272,7 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL | __`SYSLIB1224`__ | Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. | | __`SYSLIB1225`__ | Type includes ref like property, field or constructor parameter. | | __`SYSLIB1226`__ | 'JsonIgnoreCondition.Always' is not valid on type-level 'JsonIgnoreAttribute' annotations. | -| __`SYSLIB1227`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ | +| __`SYSLIB1227`__ | Open generic derived type is not supported on a non-generic base type. | | __`SYSLIB1228`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ | | __`SYSLIB1229`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ | | __`SYSLIB1230`__ | Deriving from a `GeneratedComInterface`-attributed interface defined in another assembly is not supported. | diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs index a926b75a5c1631..168c5d311019cf 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs @@ -131,6 +131,14 @@ internal static class DiagnosticDescriptors category: JsonConstants.SystemTextJsonSourceGenerationName, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); + + public static DiagnosticDescriptor OpenGenericDerivedTypeNotSupported { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1227", + title: new LocalizableResourceString(nameof(SR.OpenGenericDerivedTypeNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.OpenGenericDerivedTypeNotSupportedMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 5428245c7ce1b1..0a736abe948b77 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -847,7 +847,21 @@ private void ProcessTypeCustomAttributes( { Debug.Assert(attributeData.ConstructorArguments.Length > 0); var derivedType = (ITypeSymbol)attributeData.ConstructorArguments[0].Value!; - EnqueueType(derivedType, typeToGenerate.Mode); + + // Open generic derived types (e.g. typeof(Derived<>)) are resolved at runtime + // based on the constructed base type's type arguments. + // Skip enqueueing them for source generation. + if (derivedType is INamedTypeSymbol { IsUnboundGenericType: true }) + { + if (typeToGenerate.Type is not INamedTypeSymbol { IsGenericType: true }) + { + ReportDiagnostic(DiagnosticDescriptors.OpenGenericDerivedTypeNotSupported, typeToGenerate.Location, derivedType.ToDisplayString(), typeToGenerate.Type.ToDisplayString()); + } + } + else + { + EnqueueType(derivedType, typeToGenerate.Mode); + } if (!isPolymorphic && typeToGenerate.Mode == JsonSourceGenerationMode.Serialization) { diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx index 71ed2ca15e21bf..e06e5becec0600 100644 --- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -207,4 +207,10 @@ The type '{0}' has been annotated with 'JsonIgnoreAttribute' using 'JsonIgnoreCondition.Always' which is not valid on type declarations. The attribute will be ignored. + + Open generic derived type is not supported on a non-generic base type. + + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf index 79b8cd0d6dd8ba..93d723f8ecc4ac 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf @@ -132,6 +132,16 @@ Typ obsahuje více členů s komentářem JsonExtensionDataAttribute + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. Typ {0} zahrnuje ref-like parametr vlastnosti, pole nebo konstruktoru {1}. Pro vlastnost, pole nebo konstruktor se nevygeneruje žádný zdrojový kód. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf index 52b5e98696508a..a12b4e8ddfa9ff 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf @@ -132,6 +132,16 @@ Der Typ enthält mehrere Elemente, die mit dem JsonExtensionDataAttribute versehen sind. + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. Der Typ „{0}“ enthält die Verweise wie Eigenschaft, Feld oder Konstruktorparameter „{1}“. Für die Eigenschaft, das Feld oder den Konstruktor wird kein Quellcode generiert. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf index 0c75870edb1d11..b89f3d83c7b697 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf @@ -132,6 +132,16 @@ El tipo tiene varios miembros anotados con JsonExtensionDataAttribute. + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. El tipo "{0}" incluye una referencia como propiedad, campo o parámetro de constructor "{1}". No se generará código fuente para la propiedad, campo o constructor. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf index 6ece010bcfd65a..0680c89e68fd8f 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf @@ -132,6 +132,16 @@ Le type comporte plusieurs membres annotés avec JsonExtensionDataAttribute. + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. Le type « {0} » inclut le ref comme propriété, champ ou paramètre de constructeur « {1} ». Aucun code source ne sera généré pour la propriété, le champ ou le constructeur. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf index a542b8017265e5..d862bde27fc305 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf @@ -132,6 +132,16 @@ Nel tipo sono presenti più membri annotati con JsonExtensionDataAttribute. + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. Il tipo '{0}' include un riferimento come la proprietà, il campo o il parametro del costruttore '{1}'. Non verrà generato codice sorgente per la proprietà, il campo o il costruttore. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf index 27f29423ff6a5f..f7b1085e35da91 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf @@ -132,6 +132,16 @@ 型には、'JsonExtensionDataAttribute' に注釈が付けられた複数のメンバーが含まれます。 + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. 型 '{0}' には、プロパティ、フィールド、コンストラクター パラメーター '{1}' などの ref が含まれます。プロパティ、フィールド、またはコンストラクターのソース コードは生成されません。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf index 8832eb4d85a761..cc7bd794882ff1 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf @@ -132,6 +132,16 @@ 형식에 JsonExtensionDataAttribute로 주석이 추가 된 멤버가 여러 개 있습니다. + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. '{0}' 형식에는 속성, 필드 또는 생성자 매개 변수 '{1}'와 같은 ref가 포함됩니다. 속성, 필드 또는 생성자에 대한 소스 코드가 생성되지 않습니다. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf index 9cdc8b227b3532..9ffaaa18703481 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf @@ -132,6 +132,16 @@ Typ ma wiele składowych opatrzonych adnotacjami za pomocą atrybutu JsonExtensionDataAttribute. + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. Typ „{0}” zawiera parametr ref, taki jak właściwość, pole lub konstruktor „{1}”. Nie zostanie wygenerowany kod źródłowy dla właściwości, pola lub konstruktora. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf index 3b74ad1d07dfde..1e855f5ce87139 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf @@ -132,6 +132,16 @@ Tipo tem vários membros anotados com JsonExtensionDataAttribute. + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. O tipo "{0}" inclui a propriedade ref like, campo ou parâmetro de construtor "{1}". Nenhum código-fonte será gerado para a propriedade, campo ou construtor. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf index bd9e003fac6361..97b701de79573e 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf @@ -132,6 +132,16 @@ Тип содержит несколько элементов, помеченных с помощью JsonExtensionDataAttribute. + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. Тип "{0}" содержит ссылку, например свойство, поле или параметр конструктора "{1}". Для свойства, поля или конструктора не будет создан исходный код. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf index 0757cda7d71717..332fb8fcdb8c3a 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf @@ -132,6 +132,16 @@ Tür, JsonExtensionDataAttribute ile açıklanan birden çok üyeye sahip. + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. '{0}' türü; özellik, alan veya oluşturucu parametresi '{1}' gibi başvuru içeriyor. Özellik, alan veya oluşturucu için kaynak kodu üretilmeyecek. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf index 63e0251fec99f5..e395658677e1b5 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -132,6 +132,16 @@ 类型具有多个带有 JsonExtensionDataAttribute 注释的成员。 + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. 类型“{0}”包括属性、字段或构造函数参数“{1}”等引用。不会为属性、字段或构造函数生成源代码。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf index 95061df16ecbb5..cee7d66617083d 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -132,6 +132,16 @@ 類型具有使用 JsonExtensionDataAttribute 標註的多個成員。 + + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + + + + Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not supported on a non-generic base type. + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. 類型 '{0}' 包含 ref,例如屬性、欄位或建構函式參數 '{1}'。不會針對屬性、欄位或建構函式產生原始程式碼。 diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index b67fc02b7a5a86..4a777657e021e4 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -663,7 +663,10 @@ Specified type '{0}' does not support polymorphism. Polymorphic types cannot be structs, sealed types, generic types or System.Object. - Specified type '{0}' is not a supported derived type for the polymorphic type '{1}'. Derived types must be assignable to the base type, must not be generic and cannot be abstract classes or interfaces unless 'JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor' is specified. + Specified type '{0}' is not a supported derived type for the polymorphic type '{1}'. Derived types must be assignable to the base type, must not be open generic type definitions and cannot be abstract classes or interfaces unless 'JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor' is specified. + + + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. The polymorphic type '{0}' has already specified derived type '{1}'. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs index 075ea31a65a380..7565f47361508e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs @@ -36,18 +36,30 @@ public PolymorphicTypeResolver(JsonSerializerOptions options, JsonPolymorphismOp { Debug.Assert(typeDiscriminator is null or int or string); - if (!IsSupportedDerivedType(BaseType, derivedType) || - (derivedType.IsAbstract && UnknownDerivedTypeHandling != JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)) + Type resolvedDerivedType = derivedType; + + if (derivedType is not null && derivedType.IsGenericTypeDefinition) { - ThrowHelper.ThrowInvalidOperationException_DerivedTypeNotSupported(BaseType, derivedType); + if (!TryResolveOpenGenericDerivedType(derivedType, BaseType, out Type? resolved)) + { + ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(BaseType, derivedType); + } + + resolvedDerivedType = resolved; } - JsonTypeInfo derivedTypeInfo = options.GetTypeInfoInternal(derivedType); + if (!IsSupportedDerivedType(BaseType, resolvedDerivedType) || + (resolvedDerivedType.IsAbstract && UnknownDerivedTypeHandling != JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)) + { + ThrowHelper.ThrowInvalidOperationException_DerivedTypeNotSupported(BaseType, resolvedDerivedType); + } + + JsonTypeInfo derivedTypeInfo = options.GetTypeInfoInternal(resolvedDerivedType); DerivedJsonTypeInfo derivedTypeInfoHolder = new(typeDiscriminator, derivedTypeInfo); - if (!_typeToDiscriminatorId.TryAdd(derivedType, derivedTypeInfoHolder)) + if (!_typeToDiscriminatorId.TryAdd(resolvedDerivedType, derivedTypeInfoHolder)) { - ThrowHelper.ThrowInvalidOperationException_DerivedTypeIsAlreadySpecified(BaseType, derivedType); + ThrowHelper.ThrowInvalidOperationException_DerivedTypeIsAlreadySpecified(BaseType, resolvedDerivedType); } if (typeDiscriminator is not null) @@ -193,6 +205,153 @@ public static bool IsSupportedPolymorphicBaseType(Type? type) => public static bool IsSupportedDerivedType(Type baseType, Type? derivedType) => baseType.IsAssignableFrom(derivedType) && !derivedType.IsGenericTypeDefinition; + /// + /// Attempts to resolve an open generic derived type to a closed generic type + /// using the type arguments of the constructed base type. + /// + /// + /// For example, given Base<int> and Derived<> where + /// Derived<T> : Base<T>, this resolves to Derived<int>. + /// + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:UnrecognizedReflectionPattern", + Justification = "The types being constructed are derived types explicitly declared as polymorphic by the user.")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", + Justification = "The call to GetInterfaces is used to find the inheritance relationship between the derived type and the base type definition.")] + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "The open generic derived types are explicitly declared by the user and MakeGenericType is used to construct them with resolved type arguments.")] + internal static bool TryResolveOpenGenericDerivedType(Type openDerivedType, Type constructedBaseType, [NotNullWhen(true)] out Type? closedDerivedType) + { + Debug.Assert(openDerivedType.IsGenericTypeDefinition); + closedDerivedType = null; + + if (!constructedBaseType.IsGenericType) + { + // Base type is not generic; cannot resolve type parameters of an open generic derived type. + return false; + } + + Type baseTypeDefinition = constructedBaseType.GetGenericTypeDefinition(); + Type[] baseTypeArgs = constructedBaseType.GetGenericArguments(); + + // Find the ancestor of the open derived type that matches the base type definition. + Type? matchingBase = FindMatchingBaseType(openDerivedType, baseTypeDefinition); + if (matchingBase is null) + { + return false; + } + + Type[] matchingBaseArgs = matchingBase.GetGenericArguments(); + Debug.Assert(matchingBaseArgs.Length == baseTypeArgs.Length); + + // Build a mapping from the derived type's generic parameters to concrete types. + Type[] derivedTypeParams = openDerivedType.GetGenericArguments(); + Type?[] resolvedArgs = new Type?[derivedTypeParams.Length]; + + for (int i = 0; i < matchingBaseArgs.Length; i++) + { + if (!TryUnifyTypes(matchingBaseArgs[i], baseTypeArgs[i], derivedTypeParams, resolvedArgs)) + { + return false; + } + } + + // Verify all type parameters were resolved. + for (int i = 0; i < resolvedArgs.Length; i++) + { + if (resolvedArgs[i] is null) + { + return false; + } + } + + try + { + closedDerivedType = openDerivedType.MakeGenericType(resolvedArgs!); + return true; + } + catch (ArgumentException) + { + // Type constraints were violated. + return false; + } + + static Type? FindMatchingBaseType(Type derivedType, Type baseTypeDefinition) + { + if (baseTypeDefinition.IsInterface) + { + foreach (Type iface in derivedType.GetInterfaces()) + { + if (iface.IsGenericType && iface.GetGenericTypeDefinition() == baseTypeDefinition) + { + return iface; + } + } + } + else + { + for (Type? current = derivedType.BaseType; current is not null; current = current.BaseType) + { + if (current.IsGenericType && current.GetGenericTypeDefinition() == baseTypeDefinition) + { + return current; + } + } + } + + return null; + } + + static bool TryUnifyTypes(Type pattern, Type concrete, Type[] typeParams, Type?[] resolvedArgs) + { + if (pattern.IsGenericParameter) + { + int index = Array.IndexOf(typeParams, pattern); + if (index < 0) + { + // Not one of the derived type's type parameters. + return false; + } + + if (resolvedArgs[index] is null) + { + resolvedArgs[index] = concrete; + return true; + } + + return resolvedArgs[index] == concrete; + } + + if (pattern.IsGenericType) + { + if (!concrete.IsGenericType) + { + return false; + } + + if (pattern.GetGenericTypeDefinition() != concrete.GetGenericTypeDefinition()) + { + return false; + } + + Type[] patternArgs = pattern.GetGenericArguments(); + Type[] concreteArgs = concrete.GetGenericArguments(); + + for (int i = 0; i < patternArgs.Length; i++) + { + if (!TryUnifyTypes(patternArgs[i], concreteArgs[i], typeParams, resolvedArgs)) + { + return false; + } + } + + return true; + } + + // Non-generic, non-parameter type: must be an exact match. + return pattern == concrete; + } + } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", Justification = "The call to GetInterfaces will cross-reference results with interface types " + "already declared as derived types of the polymorphic base type.")] diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index afccfa57946fe1..33035deda8a124 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -949,6 +949,12 @@ public static void ThrowInvalidOperationException_DerivedTypeNotSupported(Type b throw new InvalidOperationException(SR.Format(SR.Polymorphism_DerivedTypeIsNotSupported, derivedType, baseType)); } + [DoesNotReturn] + public static void ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(Type baseType, Type derivedType) + { + throw new InvalidOperationException(SR.Format(SR.Polymorphism_OpenGenericDerivedTypeNotSupported, derivedType, baseType)); + } + [DoesNotReturn] public static void ThrowInvalidOperationException_DerivedTypeIsAlreadySpecified(Type baseType, Type derivedType) { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs index 3951eb4adac365..07197bf5e22d94 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs @@ -2638,10 +2638,11 @@ public class B : PolymorphicClasWithDuplicateTypeDiscriminators { } } [Fact] - public async Task PolymorphicGenericClass_ThrowsInvalidOperationException() + public async Task PolymorphicGenericClass_SupportsOpenGenericDerivedType() { PolymorphicGenericClass value = new PolymorphicGenericClass.DerivedClass(); - await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("{}", json); } [JsonDerivedType(typeof(PolymorphicGenericClass<>.DerivedClass))] From c9e89e746d8a3c4f9e98a2953dae827c091ea270 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:26:16 +0000 Subject: [PATCH 03/12] Add comprehensive tests for open generic polymorphic serialization Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f39c123c-a576-4d8d-9da4-b02cd81b6577 Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com> --- .../PolymorphicTests.CustomTypeHierarchies.cs | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs index 07197bf5e22d94..9e481edc69ab93 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs @@ -2668,6 +2668,245 @@ public class DerivedClass : PolymorphicDerivedGenericClass } } + #region Open Generic Polymorphism Tests + + [Fact] + public async Task OpenGenericDerivedType_WithStringDiscriminator_SerializationWorks() + { + OpenGenericBase_StringDisc value = new OpenGenericDerived_StringDisc { Value = 42 }; + string json = await Serializer.SerializeWrapper(value); + JsonTestHelper.AssertJsonEqual("""{"$type":"derived","Value":42}""", json); + } + + [Fact] + public async Task OpenGenericDerivedType_WithStringDiscriminator_DeserializationWorks() + { + string json = """{"$type":"derived","Value":42}"""; + var result = await Serializer.DeserializeWrapper>(json); + Assert.IsType>(result); + Assert.Equal(42, ((OpenGenericDerived_StringDisc)result).Value); + } + + [JsonDerivedType(typeof(OpenGenericDerived_StringDisc<>), "derived")] + public class OpenGenericBase_StringDisc + { + public T? Value { get; set; } + } + + public class OpenGenericDerived_StringDisc : OpenGenericBase_StringDisc; + + [Fact] + public async Task OpenGenericDerivedType_WithIntDiscriminator_SerializationWorks() + { + OpenGenericBase_IntDisc value = new OpenGenericDerived_IntDisc { Value = "hello" }; + string json = await Serializer.SerializeWrapper(value); + JsonTestHelper.AssertJsonEqual("""{"$type":1,"Value":"hello"}""", json); + } + + [Fact] + public async Task OpenGenericDerivedType_WithIntDiscriminator_DeserializationWorks() + { + string json = """{"$type":1,"Value":"hello"}"""; + var result = await Serializer.DeserializeWrapper>(json); + Assert.IsType>(result); + Assert.Equal("hello", ((OpenGenericDerived_IntDisc)result).Value); + } + + [JsonDerivedType(typeof(OpenGenericDerived_IntDisc<>), 1)] + public class OpenGenericBase_IntDisc + { + public T? Value { get; set; } + } + + public class OpenGenericDerived_IntDisc : OpenGenericBase_IntDisc; + + [Fact] + public async Task OpenGenericDerivedType_MultipleDerivedTypes_Work() + { + OpenGenericBase_Multi valueA = new OpenGenericDerivedA_Multi { ValueA = 1 }; + OpenGenericBase_Multi valueB = new OpenGenericDerivedB_Multi { ValueB = 2 }; + + string jsonA = await Serializer.SerializeWrapper(valueA); + string jsonB = await Serializer.SerializeWrapper(valueB); + + JsonTestHelper.AssertJsonEqual("""{"$type":"a","ValueA":1}""", jsonA); + JsonTestHelper.AssertJsonEqual("""{"$type":"b","ValueB":2}""", jsonB); + + var resultA = await Serializer.DeserializeWrapper>(jsonA); + var resultB = await Serializer.DeserializeWrapper>(jsonB); + + Assert.IsType>(resultA); + Assert.IsType>(resultB); + } + + [JsonDerivedType(typeof(OpenGenericDerivedA_Multi<>), "a")] + [JsonDerivedType(typeof(OpenGenericDerivedB_Multi<>), "b")] + public class OpenGenericBase_Multi; + + public class OpenGenericDerivedA_Multi : OpenGenericBase_Multi + { + public int ValueA { get; set; } + } + + public class OpenGenericDerivedB_Multi : OpenGenericBase_Multi + { + public int ValueB { get; set; } + } + + [Fact] + public async Task OpenGenericDerivedType_NestedClass_Works() + { + OpenGenericBase_Nested value = new OpenGenericBase_Nested.Derived(); + string json = await Serializer.SerializeWrapper(value); + JsonTestHelper.AssertJsonEqual("""{"$type":"nested"}""", json); + } + + [JsonDerivedType(typeof(OpenGenericBase_Nested<>.Derived), "nested")] + public class OpenGenericBase_Nested + { + public class Derived : OpenGenericBase_Nested; + } + + [Fact] + public async Task OpenGenericDerivedType_ComplexTypeArg_Works() + { + OpenGenericBase_ComplexArg> value = new OpenGenericDerived_ComplexArg> { Data = [1, 2, 3] }; + string json = await Serializer.SerializeWrapper(value); + JsonTestHelper.AssertJsonEqual("""{"$type":"derived","Data":[1,2,3]}""", json); + } + + [JsonDerivedType(typeof(OpenGenericDerived_ComplexArg<>), "derived")] + public class OpenGenericBase_ComplexArg + { + public T? Data { get; set; } + } + + public class OpenGenericDerived_ComplexArg : OpenGenericBase_ComplexArg; + + [Fact] + public async Task OpenGenericDerivedType_WrappedTypeArg_Works() + { + // Derived : Base> - the type args are related but not identical + OpenGenericBase_Wrapped> value = new OpenGenericDerived_Wrapped { Data = ["a", "b"] }; + string json = await Serializer.SerializeWrapper(value); + JsonTestHelper.AssertJsonEqual("""{"$type":"derived","Data":["a","b"]}""", json); + + var result = await Serializer.DeserializeWrapper>>(json); + Assert.IsType>(result); + } + + [JsonDerivedType(typeof(OpenGenericDerived_Wrapped<>), "derived")] + public class OpenGenericBase_Wrapped + { + public T? Data { get; set; } + } + + public class OpenGenericDerived_Wrapped : OpenGenericBase_Wrapped>; + + [Fact] + public async Task OpenGenericDerivedType_Interface_Works() + { + IOpenGenericBase value = new OpenGenericInterfaceImpl { Value = 42 }; + string json = await Serializer.SerializeWrapper(value); + JsonTestHelper.AssertJsonEqual("""{"$type":"impl","Value":42}""", json); + + var result = await Serializer.DeserializeWrapper>(json); + Assert.IsType>(result); + } + + [JsonDerivedType(typeof(OpenGenericInterfaceImpl<>), "impl")] + public interface IOpenGenericBase + { + T? Value { get; set; } + } + + public class OpenGenericInterfaceImpl : IOpenGenericBase + { + public T? Value { get; set; } + } + + [Fact] + public async Task OpenGenericDerivedType_DifferentTypeArguments_ProduceDifferentResults() + { + OpenGenericBase_StringDisc intValue = new OpenGenericDerived_StringDisc { Value = 42 }; + OpenGenericBase_StringDisc strValue = new OpenGenericDerived_StringDisc { Value = "hello" }; + + string intJson = await Serializer.SerializeWrapper(intValue); + string strJson = await Serializer.SerializeWrapper(strValue); + + JsonTestHelper.AssertJsonEqual("""{"$type":"derived","Value":42}""", intJson); + JsonTestHelper.AssertJsonEqual("""{"$type":"derived","Value":"hello"}""", strJson); + } + + [Fact] + public async Task OpenGenericDerivedType_NonGenericBase_ThrowsInvalidOperationException() + { + var value = new NonGenericBaseWithOpenGenericDerived(); + await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); + } + + [JsonDerivedType(typeof(NonGenericBaseWithOpenGenericDerived.OpenDerived<>), "derived")] + public class NonGenericBaseWithOpenGenericDerived + { + public class OpenDerived : NonGenericBaseWithOpenGenericDerived; + } + + [Fact] + public async Task OpenGenericDerivedType_TypeArgsNotResolvable_ThrowsInvalidOperationException() + { + // Derived : Base - T cannot be determined from Base + var value = new OpenGenericBase_Unresolvable(); + await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); + } + + [JsonDerivedType(typeof(OpenGenericDerived_Unresolvable<>), "derived")] + public class OpenGenericBase_Unresolvable + { + public T? Value { get; set; } + } + + public class OpenGenericDerived_Unresolvable : OpenGenericBase_Unresolvable; + + [Fact] + public async Task OpenGenericDerivedType_ProgrammaticApi_Works() + { + var options = new JsonSerializerOptions + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + static typeInfo => + { + if (typeInfo.Type == typeof(OpenGenericBase_Programmatic)) + { + typeInfo.PolymorphismOptions = new JsonPolymorphismOptions + { + DerivedTypes = + { + new JsonDerivedType(typeof(OpenGenericDerived_Programmatic<>), "derived"), + } + }; + } + } + } + } + }; + + OpenGenericBase_Programmatic value = new OpenGenericDerived_Programmatic { Value = 99 }; + string json = JsonSerializer.Serialize(value, options); + JsonTestHelper.AssertJsonEqual("""{"$type":"derived","Value":99}""", json); + } + + public class OpenGenericBase_Programmatic + { + public T? Value { get; set; } + } + + public class OpenGenericDerived_Programmatic : OpenGenericBase_Programmatic; + + #endregion + [Fact] public async Task PolymorphicClass_CustomConverter_TypeDiscriminator_Serialization_ThrowsNotSupportedException() { From 68aa74efde2b41bfbc4176cd46bdfe412688025d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:32:39 +0000 Subject: [PATCH 04/12] Update Polymorphism_TypeDoesNotSupportPolymorphism error message to say "open generic type definitions" instead of "generic types" Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f39c123c-a576-4d8d-9da4-b02cd81b6577 Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com> --- src/libraries/System.Text.Json/src/Resources/Strings.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 4a777657e021e4..f84919fa616173 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -660,7 +660,7 @@ The converter for derived type '{0}' does not support metadata writes or reads. - Specified type '{0}' does not support polymorphism. Polymorphic types cannot be structs, sealed types, generic types or System.Object. + Specified type '{0}' does not support polymorphism. Polymorphic types cannot be structs, sealed types, open generic type definitions or System.Object. Specified type '{0}' is not a supported derived type for the polymorphic type '{1}'. Derived types must be assignable to the base type, must not be open generic type definitions and cannot be abstract classes or interfaces unless 'JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor' is specified. From 3c4d937298112af8fdb4c050b738858cb711ac05 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:14:32 +0000 Subject: [PATCH 05/12] Architectural rework: move MakeGenericType to reflection-only path, add source generator compile-time resolution - Revert PolymorphicTypeResolver to original (no open generic handling) - Move open generic resolution to DefaultJsonTypeInfoResolver.Helpers (reflection-only) - Add TryResolveOpenGenericDerivedType with Roslyn type unification in source generator Parser - Source generator emits resolved derived types directly into PolymorphismOptions - Add PolymorphicDerivedTypeSpec model record for source generator - CreateFromAttributeDeclarations skips open generic derived types (null-safe) - Update diagnostic description to cover broader unresolvable cases - Fix tests: remove deserialization from no-discriminator test, use closed types in programmatic API test Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ad0de94b-b94b-48dd-8df5-4159bc77a3c0 Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com> --- docs/project/list-of-diagnostics.md | 2 +- .../gen/JsonSourceGenerator.Emitter.cs | 16 ++ .../gen/JsonSourceGenerator.Parser.cs | 160 ++++++++++++++- .../gen/Model/PolymorphicDerivedTypeSpec.cs | 20 ++ .../gen/Model/TypeGenerationSpec.cs | 7 + .../gen/Resources/Strings.resx | 4 +- .../gen/Resources/xlf/Strings.cs.xlf | 8 +- .../gen/Resources/xlf/Strings.de.xlf | 8 +- .../gen/Resources/xlf/Strings.es.xlf | 8 +- .../gen/Resources/xlf/Strings.fr.xlf | 8 +- .../gen/Resources/xlf/Strings.it.xlf | 8 +- .../gen/Resources/xlf/Strings.ja.xlf | 8 +- .../gen/Resources/xlf/Strings.ko.xlf | 8 +- .../gen/Resources/xlf/Strings.pl.xlf | 8 +- .../gen/Resources/xlf/Strings.pt-BR.xlf | 8 +- .../gen/Resources/xlf/Strings.ru.xlf | 8 +- .../gen/Resources/xlf/Strings.tr.xlf | 8 +- .../gen/Resources/xlf/Strings.zh-Hans.xlf | 8 +- .../gen/Resources/xlf/Strings.zh-Hant.xlf | 8 +- .../System.Text.Json.SourceGeneration.targets | 1 + .../DefaultJsonTypeInfoResolver.Helpers.cs | 192 ++++++++++++++++++ .../Metadata/JsonPolymorphismOptions.cs | 7 + .../Metadata/PolymorphicTypeResolver.cs | 171 +--------------- .../PolymorphicTests.CustomTypeHierarchies.cs | 13 +- 24 files changed, 468 insertions(+), 229 deletions(-) create mode 100644 src/libraries/System.Text.Json/gen/Model/PolymorphicDerivedTypeSpec.cs diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index 561f0710e4bcc2..6db6e267c4d8cf 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -272,7 +272,7 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL | __`SYSLIB1224`__ | Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. | | __`SYSLIB1225`__ | Type includes ref like property, field or constructor parameter. | | __`SYSLIB1226`__ | 'JsonIgnoreCondition.Always' is not valid on type-level 'JsonIgnoreAttribute' annotations. | -| __`SYSLIB1227`__ | Open generic derived type is not supported on a non-generic base type. | +| __`SYSLIB1227`__ | Open generic derived type is not resolvable for the polymorphic base type. | | __`SYSLIB1228`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ | | __`SYSLIB1229`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ | | __`SYSLIB1230`__ | Deriving from a `GeneratedComInterface`-attributed interface defined in another assembly is not supported. | diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index d30851a469dc1b..df4a7845b9bafd 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -28,6 +28,7 @@ private sealed partial class Emitter private const string NumberHandlingPropName = "NumberHandling"; private const string UnmappedMemberHandlingPropName = "UnmappedMemberHandling"; private const string PreferredPropertyObjectCreationHandlingPropName = "PreferredPropertyObjectCreationHandling"; + private const string PolymorphismOptionsPropName = "PolymorphismOptions"; private const string ObjectCreatorPropName = "ObjectCreator"; private const string OptionsInstanceVariableName = "Options"; private const string JsonTypeInfoLocalVariableName = "jsonTypeInfo"; @@ -582,6 +583,21 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene } } + if (typeMetadata.OpenGenericDerivedTypes is { Count: > 0 } openGenericDerivedTypes) + { + writer.WriteLine(); + foreach (PolymorphicDerivedTypeSpec derivedTypeSpec in openGenericDerivedTypes) + { + string discriminatorArg = derivedTypeSpec.TypeDiscriminator switch + { + string s => $", \"{s}\"", + int i => $", {i}", + _ => "", + }; + writer.WriteLine($"({JsonTypeInfoLocalVariableName}.{PolymorphismOptionsPropName} ??= new()).DerivedTypes.Add(new global::System.Text.Json.Serialization.Metadata.JsonDerivedType(typeof({derivedTypeSpec.DerivedType.FullyQualifiedName}){discriminatorArg}));"); + } + } + GenerateTypeInfoFactoryFooter(writer); if (propInitMethodName != null) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 0a736abe948b77..a0606e91d09e94 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -601,7 +601,8 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener out JsonIgnoreCondition? typeIgnoreCondition, out bool foundJsonConverterAttribute, out TypeRef? customConverterType, - out bool isPolymorphic); + out bool isPolymorphic, + out List? openGenericDerivedTypes); if (type is { IsRefLikeType: true } or INamedTypeSymbol { IsUnboundGenericType: true } or IErrorTypeSymbol) { @@ -732,6 +733,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener ClassType = classType, PrimitiveTypeKind = primitiveTypeKind, IsPolymorphic = isPolymorphic, + OpenGenericDerivedTypes = openGenericDerivedTypes?.ToImmutableEquatableArray(), NumberHandling = numberHandling, UnmappedMemberHandling = unmappedMemberHandling, PreferredPropertyObjectCreationHandling = preferredPropertyObjectCreationHandling, @@ -770,7 +772,8 @@ private void ProcessTypeCustomAttributes( out JsonIgnoreCondition? typeIgnoreCondition, out bool foundJsonConverterAttribute, out TypeRef? customConverterType, - out bool isPolymorphic) + out bool isPolymorphic, + out List? openGenericDerivedTypes) { numberHandling = null; unmappedMemberHandling = null; @@ -780,6 +783,7 @@ private void ProcessTypeCustomAttributes( customConverterType = null; foundJsonConverterAttribute = false; isPolymorphic = false; + openGenericDerivedTypes = null; foreach (AttributeData attributeData in typeToGenerate.Type.GetAttributes()) { @@ -848,12 +852,26 @@ private void ProcessTypeCustomAttributes( Debug.Assert(attributeData.ConstructorArguments.Length > 0); var derivedType = (ITypeSymbol)attributeData.ConstructorArguments[0].Value!; - // Open generic derived types (e.g. typeof(Derived<>)) are resolved at runtime + // Open generic derived types (e.g. typeof(Derived<>)) are resolved at compile time // based on the constructed base type's type arguments. - // Skip enqueueing them for source generation. - if (derivedType is INamedTypeSymbol { IsUnboundGenericType: true }) + if (derivedType is INamedTypeSymbol { IsUnboundGenericType: true } unboundDerived) { - if (typeToGenerate.Type is not INamedTypeSymbol { IsGenericType: true }) + if (typeToGenerate.Type is INamedTypeSymbol { IsGenericType: true } constructedBase + && TryResolveOpenGenericDerivedType(unboundDerived, constructedBase, out INamedTypeSymbol? resolvedType)) + { + EnqueueType(resolvedType, typeToGenerate.Mode); + + object? discriminator = attributeData.ConstructorArguments.Length > 1 + ? attributeData.ConstructorArguments[1].Value + : null; + + (openGenericDerivedTypes ??= new()).Add(new PolymorphicDerivedTypeSpec + { + DerivedType = new TypeRef(resolvedType), + TypeDiscriminator = discriminator, + }); + } + else { ReportDiagnostic(DiagnosticDescriptors.OpenGenericDerivedTypeNotSupported, typeToGenerate.Location, derivedType.ToDisplayString(), typeToGenerate.Type.ToDisplayString()); } @@ -873,6 +891,136 @@ private void ProcessTypeCustomAttributes( } } + /// + /// Resolves an unbound generic derived type to a closed type using the + /// type arguments from the constructed base type at compile time. + /// + private static bool TryResolveOpenGenericDerivedType( + INamedTypeSymbol unboundDerived, + INamedTypeSymbol constructedBase, + [NotNullWhen(true)] out INamedTypeSymbol? resolvedType) + { + resolvedType = null; + + if (!constructedBase.IsGenericType) + { + return false; + } + + INamedTypeSymbol derivedDefinition = unboundDerived.OriginalDefinition; + INamedTypeSymbol baseDefinition = constructedBase.OriginalDefinition; + + // Find the ancestor of the derived type definition that matches the base type definition. + INamedTypeSymbol? matchingBase = FindMatchingBaseType(derivedDefinition, baseDefinition); + if (matchingBase is null) + { + return false; + } + + var baseTypeArgs = constructedBase.TypeArguments; + var matchingBaseArgs = matchingBase.TypeArguments; + var derivedTypeParams = derivedDefinition.TypeParameters; + var resolved = new ITypeSymbol?[derivedTypeParams.Length]; + + // Unify the type arguments to build a mapping. + for (int i = 0; i < matchingBaseArgs.Length; i++) + { + if (!TryUnifyTypes(matchingBaseArgs[i], baseTypeArgs[i], derivedTypeParams, resolved)) + { + return false; + } + } + + // Verify all type parameters were resolved. + for (int i = 0; i < resolved.Length; i++) + { + if (resolved[i] is null) + { + return false; + } + } + + resolvedType = derivedDefinition.Construct(resolved!); + return true; + + static INamedTypeSymbol? FindMatchingBaseType(INamedTypeSymbol derivedDef, INamedTypeSymbol baseDef) + { + if (baseDef.TypeKind == TypeKind.Interface) + { + foreach (INamedTypeSymbol iface in derivedDef.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(iface.OriginalDefinition, baseDef)) + { + return iface; + } + } + } + else + { + for (INamedTypeSymbol? current = derivedDef.BaseType; current is not null; current = current.BaseType) + { + if (SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, baseDef)) + { + return current; + } + } + } + + return null; + } + + static bool TryUnifyTypes( + ITypeSymbol pattern, ITypeSymbol concrete, + System.Collections.Immutable.ImmutableArray typeParams, + ITypeSymbol?[] resolvedArgs) + { + if (pattern is ITypeParameterSymbol typeParam) + { + int index = typeParams.IndexOf(typeParam, SymbolEqualityComparer.Default); + if (index < 0) + { + return false; + } + + if (resolvedArgs[index] is null) + { + resolvedArgs[index] = concrete; + return true; + } + + return SymbolEqualityComparer.Default.Equals(resolvedArgs[index], concrete); + } + + if (pattern is INamedTypeSymbol { IsGenericType: true } namedPattern) + { + if (concrete is not INamedTypeSymbol { IsGenericType: true } namedConcrete) + { + return false; + } + + if (!SymbolEqualityComparer.Default.Equals(namedPattern.OriginalDefinition, namedConcrete.OriginalDefinition)) + { + return false; + } + + var patternArgs = namedPattern.TypeArguments; + var concreteArgs = namedConcrete.TypeArguments; + + for (int i = 0; i < patternArgs.Length; i++) + { + if (!TryUnifyTypes(patternArgs[i], concreteArgs[i], typeParams, resolvedArgs)) + { + return false; + } + } + + return true; + } + + return SymbolEqualityComparer.Default.Equals(pattern, concrete); + } + } + private bool TryResolveCollectionType( ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? valueType, diff --git a/src/libraries/System.Text.Json/gen/Model/PolymorphicDerivedTypeSpec.cs b/src/libraries/System.Text.Json/gen/Model/PolymorphicDerivedTypeSpec.cs new file mode 100644 index 00000000000000..72994c5168a6eb --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Model/PolymorphicDerivedTypeSpec.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using SourceGenerators; + +namespace System.Text.Json.SourceGeneration +{ + /// + /// Models a resolved open generic derived type for polymorphic serialization. + /// + public sealed record PolymorphicDerivedTypeSpec + { + public required TypeRef DerivedType { get; init; } + + /// + /// The type discriminator, either a or value, or null. + /// + public required object? TypeDiscriminator { get; init; } + } +} diff --git a/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs index 92bdbac52066c2..47df7ac4c9fc83 100644 --- a/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs @@ -51,6 +51,13 @@ public sealed record TypeGenerationSpec public required bool IsPolymorphic { get; init; } + /// + /// Resolved open generic derived types for polymorphic serialization. + /// These are open generic [JsonDerivedType] declarations that have been + /// resolved to closed types at compile time using the base type's type arguments. + /// + public required ImmutableEquatableArray? OpenGenericDerivedTypes { get; init; } + public required bool IsValueTuple { get; init; } public required JsonNumberHandling? NumberHandling { get; init; } diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx index e06e5becec0600..781d4424e8c036 100644 --- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -208,9 +208,9 @@ The type '{0}' has been annotated with 'JsonIgnoreAttribute' using 'JsonIgnoreCondition.Always' which is not valid on type declarations. The attribute will be ignored. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf index 93d723f8ecc4ac..129908b23377cf 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf index a12b4e8ddfa9ff..d1499a25d2a0f1 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf index b89f3d83c7b697..23d1f2d3e4267b 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf index 0680c89e68fd8f..3b88eb10c5e591 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf index d862bde27fc305..cde61c230f5fd0 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf index f7b1085e35da91..f3947bce538fad 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf index cc7bd794882ff1..99bf103ab3bca3 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf index 9ffaaa18703481..a22b9d908d42e9 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf index 1e855f5ce87139..3f182fd646720a 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf index 97b701de79573e..20012f312f45b3 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf index 332fb8fcdb8c3a..3d24d32066d0d0 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf index e395658677e1b5..1607a8da9c63fa 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf index cee7d66617083d..c65fcbcc6ee622 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -133,13 +133,13 @@ - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. - The open generic type '{0}' cannot be used as a derived type for the non-generic polymorphic type '{1}'. The base type must be generic to resolve open generic derived types. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. + The open generic type '{0}' cannot be used as a derived type for the polymorphic type '{1}'. The type arguments of the open generic derived type must be resolvable from the base type's generic arguments. - Open generic derived type is not supported on a non-generic base type. - Open generic derived type is not supported on a non-generic base type. + Open generic derived type is not resolvable for the polymorphic base type. + Open generic derived type is not resolvable for the polymorphic base type. diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets index 0c7c4cead803df..abdaa73bfb0823 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets @@ -73,6 +73,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs index 697bdc2b7b7300..b16cda2abc6207 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs @@ -64,6 +64,7 @@ private static JsonTypeInfo CreateTypeInfoCore(Type type, JsonConverter converte } typeInfo.PopulatePolymorphismMetadata(); + ResolveOpenGenericDerivedTypes(typeInfo); typeInfo.MapInterfaceTypesToCallbacks(); Func? createObject = DetermineCreateObjectDelegate(type, converter); @@ -640,5 +641,196 @@ static NullabilityState TranslateByte(byte b) => NullabilityInfo nullability = nullabilityCtx.Create(parameterInfo); return nullability.WriteState; } + + /// + /// Resolves any open generic derived types declared via + /// on the given type info. This uses and is only safe + /// for the reflection-based resolver path (not source generator / AOT). + /// + private static void ResolveOpenGenericDerivedTypes(JsonTypeInfo typeInfo) + { + Type baseType = typeInfo.Type; + + bool hasOpenGenericDerivedTypes = false; + foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) + { + if (attr.DerivedType.IsGenericTypeDefinition) + { + hasOpenGenericDerivedTypes = true; + break; + } + } + + if (!hasOpenGenericDerivedTypes) + { + return; + } + + if (!baseType.IsGenericType) + { + // Non-generic base with open generic derived types — always an error. + foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) + { + if (attr.DerivedType.IsGenericTypeDefinition) + { + ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(baseType, attr.DerivedType); + } + } + + return; + } + + Type baseTypeDefinition = baseType.GetGenericTypeDefinition(); + Type[] baseTypeArgs = baseType.GetGenericArguments(); + + foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) + { + Type derivedType = attr.DerivedType; + if (!derivedType.IsGenericTypeDefinition) + { + continue; + } + + if (!TryResolveOpenGenericDerivedType(derivedType, baseTypeDefinition, baseTypeArgs, out Type? resolvedType)) + { + ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(baseType, derivedType); + } + + JsonPolymorphismOptions options = typeInfo.PolymorphismOptions ??= new(); + options.DerivedTypes.Add(new JsonDerivedType(resolvedType, attr.TypeDiscriminator)); + } + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:UnrecognizedReflectionPattern", + Justification = "The types being constructed are derived types explicitly declared as polymorphic by the user.")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", + Justification = "The call to GetInterfaces is used to find the inheritance relationship between the derived type and the base type definition.")] + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "Open generic derived type resolution is only performed by the reflection-based DefaultJsonTypeInfoResolver. The source generator resolves these at compile time.")] + private static bool TryResolveOpenGenericDerivedType( + Type openDerivedType, + Type baseTypeDefinition, + Type[] baseTypeArgs, + [NotNullWhen(true)] out Type? closedDerivedType) + { + closedDerivedType = null; + + // Find the ancestor of the open derived type that matches the base type definition. + Type? matchingBase = FindMatchingBaseType(openDerivedType, baseTypeDefinition); + if (matchingBase is null) + { + return false; + } + + Type[] matchingBaseArgs = matchingBase.GetGenericArguments(); + Debug.Assert(matchingBaseArgs.Length == baseTypeArgs.Length); + + // Build a mapping from the derived type's generic parameters to concrete types. + Type[] derivedTypeParams = openDerivedType.GetGenericArguments(); + Type?[] resolvedArgs = new Type?[derivedTypeParams.Length]; + + for (int i = 0; i < matchingBaseArgs.Length; i++) + { + if (!TryUnifyTypes(matchingBaseArgs[i], baseTypeArgs[i], derivedTypeParams, resolvedArgs)) + { + return false; + } + } + + // Verify all type parameters were resolved. + for (int i = 0; i < resolvedArgs.Length; i++) + { + if (resolvedArgs[i] is null) + { + return false; + } + } + + try + { + closedDerivedType = openDerivedType.MakeGenericType(resolvedArgs!); + return true; + } + catch (ArgumentException) + { + // Type constraints were violated. + return false; + } + + static Type? FindMatchingBaseType(Type derivedType, Type baseTypeDefinition) + { + if (baseTypeDefinition.IsInterface) + { + foreach (Type iface in derivedType.GetInterfaces()) + { + if (iface.IsGenericType && iface.GetGenericTypeDefinition() == baseTypeDefinition) + { + return iface; + } + } + } + else + { + for (Type? current = derivedType.BaseType; current is not null; current = current.BaseType) + { + if (current.IsGenericType && current.GetGenericTypeDefinition() == baseTypeDefinition) + { + return current; + } + } + } + + return null; + } + + static bool TryUnifyTypes(Type pattern, Type concrete, Type[] typeParams, Type?[] resolvedArgs) + { + if (pattern.IsGenericParameter) + { + int index = Array.IndexOf(typeParams, pattern); + if (index < 0) + { + return false; + } + + if (resolvedArgs[index] is null) + { + resolvedArgs[index] = concrete; + return true; + } + + return resolvedArgs[index] == concrete; + } + + if (pattern.IsGenericType) + { + if (!concrete.IsGenericType) + { + return false; + } + + if (pattern.GetGenericTypeDefinition() != concrete.GetGenericTypeDefinition()) + { + return false; + } + + Type[] patternArgs = pattern.GetGenericArguments(); + Type[] concreteArgs = concrete.GetGenericArguments(); + + for (int i = 0; i < patternArgs.Length; i++) + { + if (!TryUnifyTypes(patternArgs[i], concreteArgs[i], typeParams, resolvedArgs)) + { + return false; + } + } + + return true; + } + + // Non-generic, non-parameter type: must be an exact match. + return pattern == concrete; + } + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs index de6f6993edd835..172df7397ddd45 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs @@ -114,6 +114,13 @@ public DerivedTypeList(JsonPolymorphismOptions parent) foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) { + // Skip open generic derived types; they are resolved separately + // by the DefaultJsonTypeInfoResolver (reflection) or source generator (AOT). + if (attr.DerivedType is { IsGenericTypeDefinition: true }) + { + continue; + } + (options ??= new()).DerivedTypes.Add(new JsonDerivedType(attr.DerivedType, attr.TypeDiscriminator)); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs index 7565f47361508e..075ea31a65a380 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs @@ -36,30 +36,18 @@ public PolymorphicTypeResolver(JsonSerializerOptions options, JsonPolymorphismOp { Debug.Assert(typeDiscriminator is null or int or string); - Type resolvedDerivedType = derivedType; - - if (derivedType is not null && derivedType.IsGenericTypeDefinition) + if (!IsSupportedDerivedType(BaseType, derivedType) || + (derivedType.IsAbstract && UnknownDerivedTypeHandling != JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)) { - if (!TryResolveOpenGenericDerivedType(derivedType, BaseType, out Type? resolved)) - { - ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(BaseType, derivedType); - } - - resolvedDerivedType = resolved; + ThrowHelper.ThrowInvalidOperationException_DerivedTypeNotSupported(BaseType, derivedType); } - if (!IsSupportedDerivedType(BaseType, resolvedDerivedType) || - (resolvedDerivedType.IsAbstract && UnknownDerivedTypeHandling != JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)) - { - ThrowHelper.ThrowInvalidOperationException_DerivedTypeNotSupported(BaseType, resolvedDerivedType); - } - - JsonTypeInfo derivedTypeInfo = options.GetTypeInfoInternal(resolvedDerivedType); + JsonTypeInfo derivedTypeInfo = options.GetTypeInfoInternal(derivedType); DerivedJsonTypeInfo derivedTypeInfoHolder = new(typeDiscriminator, derivedTypeInfo); - if (!_typeToDiscriminatorId.TryAdd(resolvedDerivedType, derivedTypeInfoHolder)) + if (!_typeToDiscriminatorId.TryAdd(derivedType, derivedTypeInfoHolder)) { - ThrowHelper.ThrowInvalidOperationException_DerivedTypeIsAlreadySpecified(BaseType, resolvedDerivedType); + ThrowHelper.ThrowInvalidOperationException_DerivedTypeIsAlreadySpecified(BaseType, derivedType); } if (typeDiscriminator is not null) @@ -205,153 +193,6 @@ public static bool IsSupportedPolymorphicBaseType(Type? type) => public static bool IsSupportedDerivedType(Type baseType, Type? derivedType) => baseType.IsAssignableFrom(derivedType) && !derivedType.IsGenericTypeDefinition; - /// - /// Attempts to resolve an open generic derived type to a closed generic type - /// using the type arguments of the constructed base type. - /// - /// - /// For example, given Base<int> and Derived<> where - /// Derived<T> : Base<T>, this resolves to Derived<int>. - /// - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:UnrecognizedReflectionPattern", - Justification = "The types being constructed are derived types explicitly declared as polymorphic by the user.")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", - Justification = "The call to GetInterfaces is used to find the inheritance relationship between the derived type and the base type definition.")] - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "The open generic derived types are explicitly declared by the user and MakeGenericType is used to construct them with resolved type arguments.")] - internal static bool TryResolveOpenGenericDerivedType(Type openDerivedType, Type constructedBaseType, [NotNullWhen(true)] out Type? closedDerivedType) - { - Debug.Assert(openDerivedType.IsGenericTypeDefinition); - closedDerivedType = null; - - if (!constructedBaseType.IsGenericType) - { - // Base type is not generic; cannot resolve type parameters of an open generic derived type. - return false; - } - - Type baseTypeDefinition = constructedBaseType.GetGenericTypeDefinition(); - Type[] baseTypeArgs = constructedBaseType.GetGenericArguments(); - - // Find the ancestor of the open derived type that matches the base type definition. - Type? matchingBase = FindMatchingBaseType(openDerivedType, baseTypeDefinition); - if (matchingBase is null) - { - return false; - } - - Type[] matchingBaseArgs = matchingBase.GetGenericArguments(); - Debug.Assert(matchingBaseArgs.Length == baseTypeArgs.Length); - - // Build a mapping from the derived type's generic parameters to concrete types. - Type[] derivedTypeParams = openDerivedType.GetGenericArguments(); - Type?[] resolvedArgs = new Type?[derivedTypeParams.Length]; - - for (int i = 0; i < matchingBaseArgs.Length; i++) - { - if (!TryUnifyTypes(matchingBaseArgs[i], baseTypeArgs[i], derivedTypeParams, resolvedArgs)) - { - return false; - } - } - - // Verify all type parameters were resolved. - for (int i = 0; i < resolvedArgs.Length; i++) - { - if (resolvedArgs[i] is null) - { - return false; - } - } - - try - { - closedDerivedType = openDerivedType.MakeGenericType(resolvedArgs!); - return true; - } - catch (ArgumentException) - { - // Type constraints were violated. - return false; - } - - static Type? FindMatchingBaseType(Type derivedType, Type baseTypeDefinition) - { - if (baseTypeDefinition.IsInterface) - { - foreach (Type iface in derivedType.GetInterfaces()) - { - if (iface.IsGenericType && iface.GetGenericTypeDefinition() == baseTypeDefinition) - { - return iface; - } - } - } - else - { - for (Type? current = derivedType.BaseType; current is not null; current = current.BaseType) - { - if (current.IsGenericType && current.GetGenericTypeDefinition() == baseTypeDefinition) - { - return current; - } - } - } - - return null; - } - - static bool TryUnifyTypes(Type pattern, Type concrete, Type[] typeParams, Type?[] resolvedArgs) - { - if (pattern.IsGenericParameter) - { - int index = Array.IndexOf(typeParams, pattern); - if (index < 0) - { - // Not one of the derived type's type parameters. - return false; - } - - if (resolvedArgs[index] is null) - { - resolvedArgs[index] = concrete; - return true; - } - - return resolvedArgs[index] == concrete; - } - - if (pattern.IsGenericType) - { - if (!concrete.IsGenericType) - { - return false; - } - - if (pattern.GetGenericTypeDefinition() != concrete.GetGenericTypeDefinition()) - { - return false; - } - - Type[] patternArgs = pattern.GetGenericArguments(); - Type[] concreteArgs = concrete.GetGenericArguments(); - - for (int i = 0; i < patternArgs.Length; i++) - { - if (!TryUnifyTypes(patternArgs[i], concreteArgs[i], typeParams, resolvedArgs)) - { - return false; - } - } - - return true; - } - - // Non-generic, non-parameter type: must be an exact match. - return pattern == concrete; - } - } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", Justification = "The call to GetInterfaces will cross-reference results with interface types " + "already declared as derived types of the polymorphic base type.")] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs index 9e481edc69ab93..be3caa0a59ca57 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs @@ -2640,16 +2640,19 @@ public class B : PolymorphicClasWithDuplicateTypeDiscriminators { } [Fact] public async Task PolymorphicGenericClass_SupportsOpenGenericDerivedType() { - PolymorphicGenericClass value = new PolymorphicGenericClass.DerivedClass(); + PolymorphicGenericClass value = new PolymorphicGenericClass.DerivedClass { BaseValue = 1, DerivedValue = 2 }; string json = await Serializer.SerializeWrapper(value); - Assert.Equal("{}", json); + JsonTestHelper.AssertJsonEqual("""{"BaseValue":1,"DerivedValue":2}""", json); } [JsonDerivedType(typeof(PolymorphicGenericClass<>.DerivedClass))] public class PolymorphicGenericClass { + public T? BaseValue { get; set; } + public class DerivedClass : PolymorphicGenericClass { + public T? DerivedValue { get; set; } } } @@ -2884,7 +2887,7 @@ public async Task OpenGenericDerivedType_ProgrammaticApi_Works() { DerivedTypes = { - new JsonDerivedType(typeof(OpenGenericDerived_Programmatic<>), "derived"), + new JsonDerivedType(typeof(OpenGenericDerived_Programmatic), "derived"), } }; } @@ -2896,6 +2899,10 @@ public async Task OpenGenericDerivedType_ProgrammaticApi_Works() OpenGenericBase_Programmatic value = new OpenGenericDerived_Programmatic { Value = 99 }; string json = JsonSerializer.Serialize(value, options); JsonTestHelper.AssertJsonEqual("""{"$type":"derived","Value":99}""", json); + + var result = JsonSerializer.Deserialize>(json, options); + Assert.IsType>(result); + Assert.Equal(99, ((OpenGenericDerived_Programmatic)result).Value); } public class OpenGenericBase_Programmatic From ce67a9236c3ebdc6ea886e3d2a6c27b84b71b284 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:32:49 +0000 Subject: [PATCH 06/12] Replace UnconditionalSuppressMessage with RequiresUnreferencedCode/RequiresDynamicCode propagation Remove invalid suppressions on TryResolveOpenGenericDerivedType and propagate the requirements using RequiresUnreferencedCode and RequiresDynamicCode on both ResolveOpenGenericDerivedTypes and TryResolveOpenGenericDerivedType. The caller CreateTypeInfoCore already has both attributes. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d2df80f8-8ef2-4e50-8949-b80c4ab36d89 Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com> --- .../Metadata/DefaultJsonTypeInfoResolver.Helpers.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs index b16cda2abc6207..e5c11e42ad1d9e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs @@ -647,6 +647,8 @@ static NullabilityState TranslateByte(byte b) => /// on the given type info. This uses and is only safe /// for the reflection-based resolver path (not source generator / AOT). /// + [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] private static void ResolveOpenGenericDerivedTypes(JsonTypeInfo typeInfo) { Type baseType = typeInfo.Type; @@ -701,12 +703,8 @@ private static void ResolveOpenGenericDerivedTypes(JsonTypeInfo typeInfo) } } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:UnrecognizedReflectionPattern", - Justification = "The types being constructed are derived types explicitly declared as polymorphic by the user.")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", - Justification = "The call to GetInterfaces is used to find the inheritance relationship between the derived type and the base type definition.")] - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "Open generic derived type resolution is only performed by the reflection-based DefaultJsonTypeInfoResolver. The source generator resolves these at compile time.")] + [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] private static bool TryResolveOpenGenericDerivedType( Type openDerivedType, Type baseTypeDefinition, From 4712c266318c8cb95a0c480cbb715d79792678b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 19:16:55 +0000 Subject: [PATCH 07/12] Address review feedback: fix emitter pattern, sort derived types, refactor parser, remove doc comment, fix null guard, conditional PopulatePolymorphismMetadata Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e620b332-1edf-45ad-9d85-bc907429e008 Co-authored-by: eiriktsarpalis <6668460+eiriktsarpalis@users.noreply.github.com> --- .../gen/JsonSourceGenerator.Emitter.cs | 9 +++++++- .../gen/JsonSourceGenerator.Parser.cs | 12 ++++------ .../DefaultJsonTypeInfoResolver.Helpers.cs | 13 ++++------- .../Serialization/Metadata/JsonTypeInfo.cs | 8 +++++++ .../JsonPolymorphismOptionsTests.cs | 23 ++++++++++++++++--- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index df4a7845b9bafd..3acf96393ce400 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -76,6 +76,8 @@ private sealed partial class Emitter private const string JsonParameterInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues"; private const string JsonPropertyInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo"; private const string JsonPropertyInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues"; + private const string JsonPolymorphismOptionsTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPolymorphismOptions"; + private const string JsonDerivedTypeTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonDerivedType"; private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo"; private const string JsonTypeInfoResolverTypeRef = "global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver"; private const string ReferenceHandlerTypeRef = "global::System.Text.Json.Serialization.ReferenceHandler"; @@ -585,7 +587,10 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene if (typeMetadata.OpenGenericDerivedTypes is { Count: > 0 } openGenericDerivedTypes) { + const string OptionsVarName = "polymorphismOptions"; writer.WriteLine(); + writer.WriteLine($"var {OptionsVarName} = new {JsonPolymorphismOptionsTypeRef}();"); + foreach (PolymorphicDerivedTypeSpec derivedTypeSpec in openGenericDerivedTypes) { string discriminatorArg = derivedTypeSpec.TypeDiscriminator switch @@ -594,8 +599,10 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene int i => $", {i}", _ => "", }; - writer.WriteLine($"({JsonTypeInfoLocalVariableName}.{PolymorphismOptionsPropName} ??= new()).DerivedTypes.Add(new global::System.Text.Json.Serialization.Metadata.JsonDerivedType(typeof({derivedTypeSpec.DerivedType.FullyQualifiedName}){discriminatorArg}));"); + writer.WriteLine($"{OptionsVarName}.DerivedTypes.Add(new {JsonDerivedTypeTypeRef}(typeof({derivedTypeSpec.DerivedType.FullyQualifiedName}){discriminatorArg}));"); } + + writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{PolymorphismOptionsPropName} = {OptionsVarName};"); } GenerateTypeInfoFactoryFooter(writer); diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index a0606e91d09e94..7efebea9490f5b 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -733,7 +733,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener ClassType = classType, PrimitiveTypeKind = primitiveTypeKind, IsPolymorphic = isPolymorphic, - OpenGenericDerivedTypes = openGenericDerivedTypes?.ToImmutableEquatableArray(), + OpenGenericDerivedTypes = openGenericDerivedTypes?.OrderBy(d => d.DerivedType.FullyQualifiedName).ToImmutableEquatableArray(), NumberHandling = numberHandling, UnmappedMemberHandling = unmappedMemberHandling, PreferredPropertyObjectCreationHandling = preferredPropertyObjectCreationHandling, @@ -852,12 +852,9 @@ private void ProcessTypeCustomAttributes( Debug.Assert(attributeData.ConstructorArguments.Length > 0); var derivedType = (ITypeSymbol)attributeData.ConstructorArguments[0].Value!; - // Open generic derived types (e.g. typeof(Derived<>)) are resolved at compile time - // based on the constructed base type's type arguments. if (derivedType is INamedTypeSymbol { IsUnboundGenericType: true } unboundDerived) { - if (typeToGenerate.Type is INamedTypeSymbol { IsGenericType: true } constructedBase - && TryResolveOpenGenericDerivedType(unboundDerived, constructedBase, out INamedTypeSymbol? resolvedType)) + if (TryResolveOpenGenericDerivedType(unboundDerived, typeToGenerate.Type, out INamedTypeSymbol? resolvedType)) { EnqueueType(resolvedType, typeToGenerate.Mode); @@ -894,15 +891,16 @@ private void ProcessTypeCustomAttributes( /// /// Resolves an unbound generic derived type to a closed type using the /// type arguments from the constructed base type at compile time. + /// Returns null if the type arguments cannot be resolved. /// private static bool TryResolveOpenGenericDerivedType( INamedTypeSymbol unboundDerived, - INamedTypeSymbol constructedBase, + ITypeSymbol baseType, [NotNullWhen(true)] out INamedTypeSymbol? resolvedType) { resolvedType = null; - if (!constructedBase.IsGenericType) + if (baseType is not INamedTypeSymbol { IsGenericType: true } constructedBase) { return false; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs index e5c11e42ad1d9e..cb75333dfb0771 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs @@ -642,11 +642,6 @@ static NullabilityState TranslateByte(byte b) => return nullability.WriteState; } - /// - /// Resolves any open generic derived types declared via - /// on the given type info. This uses and is only safe - /// for the reflection-based resolver path (not source generator / AOT). - /// [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] private static void ResolveOpenGenericDerivedTypes(JsonTypeInfo typeInfo) @@ -656,7 +651,7 @@ private static void ResolveOpenGenericDerivedTypes(JsonTypeInfo typeInfo) bool hasOpenGenericDerivedTypes = false; foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) { - if (attr.DerivedType.IsGenericTypeDefinition) + if (attr.DerivedType is { IsGenericTypeDefinition: true }) { hasOpenGenericDerivedTypes = true; break; @@ -673,7 +668,7 @@ private static void ResolveOpenGenericDerivedTypes(JsonTypeInfo typeInfo) // Non-generic base with open generic derived types — always an error. foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) { - if (attr.DerivedType.IsGenericTypeDefinition) + if (attr.DerivedType is { IsGenericTypeDefinition: true }) { ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(baseType, attr.DerivedType); } @@ -687,12 +682,12 @@ private static void ResolveOpenGenericDerivedTypes(JsonTypeInfo typeInfo) foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) { - Type derivedType = attr.DerivedType; - if (!derivedType.IsGenericTypeDefinition) + if (attr.DerivedType is not { IsGenericTypeDefinition: true }) { continue; } + Type derivedType = attr.DerivedType; if (!TryResolveOpenGenericDerivedType(derivedType, baseTypeDefinition, baseTypeArgs, out Type? resolvedType)) { ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(baseType, derivedType); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index 400817503b9bdc..3baca8482b39ac 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -1235,6 +1235,14 @@ internal void PopulatePolymorphismMetadata() { Debug.Assert(!IsReadOnly); + // If polymorphism options have already been set by the source generator, + // skip attribute-based resolution. Preserve the attribute logic for + // binary compatibility with source generated code from earlier versions. + if (_polymorphismOptions is not null) + { + return; + } + JsonPolymorphismOptions? options = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type); if (options != null) { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonPolymorphismOptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonPolymorphismOptionsTests.cs index d0d6bd48862071..9e2613e78db63b 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonPolymorphismOptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonPolymorphismOptionsTests.cs @@ -136,9 +136,26 @@ public static void DefaultResolver_ReportsCorrectPolymorphismMetadata(Type polym Assert.Equal(polymorphicAttribute?.IgnoreUnrecognizedTypeDiscriminators ?? false, polyOptions.IgnoreUnrecognizedTypeDiscriminators); Assert.Equal(polymorphicAttribute?.UnknownDerivedTypeHandling ?? default, polyOptions.UnknownDerivedTypeHandling); Assert.Equal(polymorphicAttribute?.TypeDiscriminatorPropertyName ?? "$type", polyOptions.TypeDiscriminatorPropertyName); - Assert.Equal( - expected: derivedTypeAttributes.Select(attr => (attr.DerivedType, attr.TypeDiscriminator)), - actual: polyOptions.DerivedTypes.Select(attr => (attr.DerivedType, attr.TypeDiscriminator))); + Assert.Equal(derivedTypeAttributes.Length, polyOptions.DerivedTypes.Count); + + for (int i = 0; i < derivedTypeAttributes.Length; i++) + { + JsonDerivedTypeAttribute attr = derivedTypeAttributes[i]; + JsonDerivedType derivedType = polyOptions.DerivedTypes[i]; + Assert.Equal(attr.TypeDiscriminator, derivedType.TypeDiscriminator); + + if (attr.DerivedType is { IsGenericTypeDefinition: true } || + (attr.DerivedType is { DeclaringType: { IsGenericTypeDefinition: true } })) + { + // Open generic derived types are resolved by the reflection resolver + // to closed types, so we can't compare them directly. + Assert.False(derivedType.DerivedType.IsGenericTypeDefinition); + } + else + { + Assert.Equal(attr.DerivedType, derivedType.DerivedType); + } + } } } } From 05f70b2ca23ba5eb6a112e7a3f981110839f4cb1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 19:18:43 +0000 Subject: [PATCH 08/12] Fix alphabetical ordering of type ref constants in Emitter Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e620b332-1edf-45ad-9d85-bc907429e008 Co-authored-by: eiriktsarpalis <6668460+eiriktsarpalis@users.noreply.github.com> --- .../System.Text.Json/gen/JsonSourceGenerator.Emitter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 3acf96393ce400..a09267839f25f4 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -76,8 +76,8 @@ private sealed partial class Emitter private const string JsonParameterInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues"; private const string JsonPropertyInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo"; private const string JsonPropertyInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues"; - private const string JsonPolymorphismOptionsTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPolymorphismOptions"; private const string JsonDerivedTypeTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonDerivedType"; + private const string JsonPolymorphismOptionsTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPolymorphismOptions"; private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo"; private const string JsonTypeInfoResolverTypeRef = "global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver"; private const string ReferenceHandlerTypeRef = "global::System.Text.Json.Serialization.ReferenceHandler"; From 87340d5c39cdb3f8e531fc5058dcdb1e4e66df03 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 24 Apr 2026 13:49:10 +0300 Subject: [PATCH 09/12] Fix emitter to merge with existing PolymorphismOptions instead of overwriting The source generator emitter was creating a new JsonPolymorphismOptions instance and assigning it to jsonTypeInfo.PolymorphismOptions, which overwrites any options already set by PopulatePolymorphismMetadata() (called inside CreateObjectInfo). This caused regular (non-open-generic) derived types and [JsonPolymorphic] attribute settings to be lost when a type had both regular and open generic derived types. Fix: use ?? to get existing options or create new ones, preserving regular derived types and attribute settings while adding the resolved open generic derived types. Also adds a test for the mixed regular + open generic derived type scenario and updates the PopulatePolymorphismMetadata() comment to be more accurate about when the early return is triggered. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../gen/JsonSourceGenerator.Emitter.cs | 2 +- .../Serialization/Metadata/JsonTypeInfo.cs | 5 ++- .../PolymorphicTests.CustomTypeHierarchies.cs | 36 +++++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index a09267839f25f4..48f66d4baec235 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -589,7 +589,7 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene { const string OptionsVarName = "polymorphismOptions"; writer.WriteLine(); - writer.WriteLine($"var {OptionsVarName} = new {JsonPolymorphismOptionsTypeRef}();"); + writer.WriteLine($"var {OptionsVarName} = {JsonTypeInfoLocalVariableName}.{PolymorphismOptionsPropName} ?? new {JsonPolymorphismOptionsTypeRef}();"); foreach (PolymorphicDerivedTypeSpec derivedTypeSpec in openGenericDerivedTypes) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index 3baca8482b39ac..4d98115acf9103 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -1235,9 +1235,8 @@ internal void PopulatePolymorphismMetadata() { Debug.Assert(!IsReadOnly); - // If polymorphism options have already been set by the source generator, - // skip attribute-based resolution. Preserve the attribute logic for - // binary compatibility with source generated code from earlier versions. + // If polymorphism options have already been set (e.g., programmatically + // or by a future source generator pattern), skip attribute-based resolution. if (_polymorphismOptions is not null) { return; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs index be3caa0a59ca57..3f5745c81a8e3a 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs @@ -2912,6 +2912,42 @@ public class OpenGenericBase_Programmatic public class OpenGenericDerived_Programmatic : OpenGenericBase_Programmatic; + [Fact] + public async Task OpenGenericDerivedType_MixedWithRegularDerivedType_Works() + { + // Validates that both regular and open generic derived types coexist. + OpenGenericBase_Mixed openValue = new OpenGenericDerived_Mixed { Value = 1 }; + OpenGenericBase_Mixed regularValue = new RegularDerived_Mixed { Value = 2, Extra = "extra" }; + + string openJson = await Serializer.SerializeWrapper(openValue); + string regularJson = await Serializer.SerializeWrapper(regularValue); + + JsonTestHelper.AssertJsonEqual("""{"$type":"open","Value":1}""", openJson); + JsonTestHelper.AssertJsonEqual("""{"$type":"regular","Value":2,"Extra":"extra"}""", regularJson); + + var openResult = await Serializer.DeserializeWrapper>(openJson); + var regularResult = await Serializer.DeserializeWrapper>(regularJson); + + Assert.IsType>(openResult); + Assert.IsType(regularResult); + Assert.Equal(1, openResult.Value); + Assert.Equal("extra", ((RegularDerived_Mixed)regularResult).Extra); + } + + [JsonDerivedType(typeof(OpenGenericDerived_Mixed<>), "open")] + [JsonDerivedType(typeof(RegularDerived_Mixed), "regular")] + public class OpenGenericBase_Mixed + { + public T? Value { get; set; } + } + + public class OpenGenericDerived_Mixed : OpenGenericBase_Mixed; + + public class RegularDerived_Mixed : OpenGenericBase_Mixed + { + public string? Extra { get; set; } + } + #endregion [Fact] From 888ad1f4d7c1d41ddcdf824e0a03fb0af38fd636 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 11:35:13 +0000 Subject: [PATCH 10/12] Address review: consolidate attribute resolution, track all derived types in source gen, add interface hierarchy tests Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/a0d521b2-5b4b-4f05-bb14-6fe9d541bf0d Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com> --- .../gen/JsonSourceGenerator.Emitter.cs | 5 +- .../gen/JsonSourceGenerator.Parser.cs | 36 +++++----- .../gen/Model/PolymorphicDerivedTypeSpec.cs | 2 +- .../gen/Model/TypeGenerationSpec.cs | 13 ++-- .../DefaultJsonTypeInfoResolver.Helpers.cs | 65 +++++++++---------- .../Metadata/JsonPolymorphismOptions.cs | 7 -- .../Serialization/Metadata/JsonTypeInfo.cs | 11 ++++ .../PolymorphicTests.CustomTypeHierarchies.cs | 52 +++++++++++++++ 8 files changed, 118 insertions(+), 73 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 48f66d4baec235..19df5aac6c560e 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -585,13 +585,14 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene } } - if (typeMetadata.OpenGenericDerivedTypes is { Count: > 0 } openGenericDerivedTypes) + if (typeMetadata.ResolvedDerivedTypes is { Count: > 0 } resolvedDerivedTypes) { const string OptionsVarName = "polymorphismOptions"; writer.WriteLine(); writer.WriteLine($"var {OptionsVarName} = {JsonTypeInfoLocalVariableName}.{PolymorphismOptionsPropName} ?? new {JsonPolymorphismOptionsTypeRef}();"); + writer.WriteLine($"{OptionsVarName}.DerivedTypes.Clear();"); - foreach (PolymorphicDerivedTypeSpec derivedTypeSpec in openGenericDerivedTypes) + foreach (PolymorphicDerivedTypeSpec derivedTypeSpec in resolvedDerivedTypes) { string discriminatorArg = derivedTypeSpec.TypeDiscriminator switch { diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 7efebea9490f5b..d8a31a4af081a9 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -601,8 +601,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener out JsonIgnoreCondition? typeIgnoreCondition, out bool foundJsonConverterAttribute, out TypeRef? customConverterType, - out bool isPolymorphic, - out List? openGenericDerivedTypes); + out List? resolvedDerivedTypes); if (type is { IsRefLikeType: true } or INamedTypeSymbol { IsUnboundGenericType: true } or IErrorTypeSymbol) { @@ -732,8 +731,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener GenerationMode = typeToGenerate.Mode ?? options?.GenerationMode ?? JsonSourceGenerationMode.Default, ClassType = classType, PrimitiveTypeKind = primitiveTypeKind, - IsPolymorphic = isPolymorphic, - OpenGenericDerivedTypes = openGenericDerivedTypes?.OrderBy(d => d.DerivedType.FullyQualifiedName).ToImmutableEquatableArray(), + ResolvedDerivedTypes = resolvedDerivedTypes?.OrderBy(d => d.DerivedType.FullyQualifiedName).ToImmutableEquatableArray(), NumberHandling = numberHandling, UnmappedMemberHandling = unmappedMemberHandling, PreferredPropertyObjectCreationHandling = preferredPropertyObjectCreationHandling, @@ -772,8 +770,7 @@ private void ProcessTypeCustomAttributes( out JsonIgnoreCondition? typeIgnoreCondition, out bool foundJsonConverterAttribute, out TypeRef? customConverterType, - out bool isPolymorphic, - out List? openGenericDerivedTypes) + out List? resolvedDerivedTypes) { numberHandling = null; unmappedMemberHandling = null; @@ -782,8 +779,7 @@ private void ProcessTypeCustomAttributes( typeIgnoreCondition = null; customConverterType = null; foundJsonConverterAttribute = false; - isPolymorphic = false; - openGenericDerivedTypes = null; + resolvedDerivedTypes = null; foreach (AttributeData attributeData in typeToGenerate.Type.GetAttributes()) { @@ -852,25 +848,21 @@ private void ProcessTypeCustomAttributes( Debug.Assert(attributeData.ConstructorArguments.Length > 0); var derivedType = (ITypeSymbol)attributeData.ConstructorArguments[0].Value!; + object? discriminator = attributeData.ConstructorArguments.Length > 1 + ? attributeData.ConstructorArguments[1].Value + : null; + if (derivedType is INamedTypeSymbol { IsUnboundGenericType: true } unboundDerived) { if (TryResolveOpenGenericDerivedType(unboundDerived, typeToGenerate.Type, out INamedTypeSymbol? resolvedType)) { EnqueueType(resolvedType, typeToGenerate.Mode); - - object? discriminator = attributeData.ConstructorArguments.Length > 1 - ? attributeData.ConstructorArguments[1].Value - : null; - - (openGenericDerivedTypes ??= new()).Add(new PolymorphicDerivedTypeSpec - { - DerivedType = new TypeRef(resolvedType), - TypeDiscriminator = discriminator, - }); + derivedType = resolvedType; } else { ReportDiagnostic(DiagnosticDescriptors.OpenGenericDerivedTypeNotSupported, typeToGenerate.Location, derivedType.ToDisplayString(), typeToGenerate.Type.ToDisplayString()); + continue; } } else @@ -878,12 +870,16 @@ private void ProcessTypeCustomAttributes( EnqueueType(derivedType, typeToGenerate.Mode); } - if (!isPolymorphic && typeToGenerate.Mode == JsonSourceGenerationMode.Serialization) + if (resolvedDerivedTypes is null && typeToGenerate.Mode == JsonSourceGenerationMode.Serialization) { ReportDiagnostic(DiagnosticDescriptors.PolymorphismNotSupported, typeToGenerate.Location, typeToGenerate.Type.ToDisplayString()); } - isPolymorphic = true; + (resolvedDerivedTypes ??= new()).Add(new PolymorphicDerivedTypeSpec + { + DerivedType = new TypeRef(derivedType), + TypeDiscriminator = discriminator, + }); } } } diff --git a/src/libraries/System.Text.Json/gen/Model/PolymorphicDerivedTypeSpec.cs b/src/libraries/System.Text.Json/gen/Model/PolymorphicDerivedTypeSpec.cs index 72994c5168a6eb..5cec9367baf3c1 100644 --- a/src/libraries/System.Text.Json/gen/Model/PolymorphicDerivedTypeSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/PolymorphicDerivedTypeSpec.cs @@ -6,7 +6,7 @@ namespace System.Text.Json.SourceGeneration { /// - /// Models a resolved open generic derived type for polymorphic serialization. + /// Models a resolved derived type for polymorphic serialization. /// public sealed record PolymorphicDerivedTypeSpec { diff --git a/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs index 47df7ac4c9fc83..c076aeaf3207f8 100644 --- a/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs @@ -49,14 +49,13 @@ public sealed record TypeGenerationSpec public required bool ImplementsIJsonOnSerialized { get; init; } public required bool ImplementsIJsonOnSerializing { get; init; } - public required bool IsPolymorphic { get; init; } - /// - /// Resolved open generic derived types for polymorphic serialization. - /// These are open generic [JsonDerivedType] declarations that have been - /// resolved to closed types at compile time using the base type's type arguments. + /// Resolved derived types for polymorphic serialization. + /// These are [JsonDerivedType] declarations resolved at compile time. + /// Open generic derived types are resolved to closed types using the base type's type arguments. + /// A non-null value indicates the type is polymorphic. /// - public required ImmutableEquatableArray? OpenGenericDerivedTypes { get; init; } + public required ImmutableEquatableArray? ResolvedDerivedTypes { get; init; } public required bool IsValueTuple { get; init; } @@ -119,7 +118,7 @@ public sealed record TypeGenerationSpec public bool IsFastPathSupported() { - if (IsPolymorphic) + if (ResolvedDerivedTypes is not null) { return false; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs index cb75333dfb0771..c7f8d6135d2cb1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs @@ -63,8 +63,7 @@ private static JsonTypeInfo CreateTypeInfoCore(Type type, JsonConverter converte typeInfo.UnmappedMemberHandling = unmappedMemberHandling; } - typeInfo.PopulatePolymorphismMetadata(); - ResolveOpenGenericDerivedTypes(typeInfo); + PopulatePolymorphismFromAttributes(typeInfo); typeInfo.MapInterfaceTypesToCallbacks(); Func? createObject = DetermineCreateObjectDelegate(type, converter); @@ -644,57 +643,51 @@ static NullabilityState TranslateByte(byte b) => [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] - private static void ResolveOpenGenericDerivedTypes(JsonTypeInfo typeInfo) + private static void PopulatePolymorphismFromAttributes(JsonTypeInfo typeInfo) { + Debug.Assert(!typeInfo.IsReadOnly); + Type baseType = typeInfo.Type; + JsonPolymorphismOptions? options = null; - bool hasOpenGenericDerivedTypes = false; - foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) + if (baseType.GetCustomAttribute(inherit: false) is JsonPolymorphicAttribute polymorphicAttribute) { - if (attr.DerivedType is { IsGenericTypeDefinition: true }) + options = new() { - hasOpenGenericDerivedTypes = true; - break; - } + IgnoreUnrecognizedTypeDiscriminators = polymorphicAttribute.IgnoreUnrecognizedTypeDiscriminators, + UnknownDerivedTypeHandling = polymorphicAttribute.UnknownDerivedTypeHandling, + TypeDiscriminatorPropertyName = polymorphicAttribute.TypeDiscriminatorPropertyName, + }; } - if (!hasOpenGenericDerivedTypes) - { - return; - } + Type? baseTypeDefinition = baseType.IsGenericType ? baseType.GetGenericTypeDefinition() : null; + Type[]? baseTypeArgs = baseType.IsGenericType ? baseType.GetGenericArguments() : null; - if (!baseType.IsGenericType) + foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) { - // Non-generic base with open generic derived types — always an error. - foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) + Type derivedType = attr.DerivedType; + + if (derivedType is { IsGenericTypeDefinition: true }) { - if (attr.DerivedType is { IsGenericTypeDefinition: true }) + if (baseTypeDefinition is null || baseTypeArgs is null) { - ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(baseType, attr.DerivedType); + ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(baseType, derivedType); } - } - return; - } - - Type baseTypeDefinition = baseType.GetGenericTypeDefinition(); - Type[] baseTypeArgs = baseType.GetGenericArguments(); + if (!TryResolveOpenGenericDerivedType(derivedType, baseTypeDefinition, baseTypeArgs, out Type? resolvedType)) + { + ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(baseType, derivedType); + } - foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) - { - if (attr.DerivedType is not { IsGenericTypeDefinition: true }) - { - continue; + derivedType = resolvedType; } - Type derivedType = attr.DerivedType; - if (!TryResolveOpenGenericDerivedType(derivedType, baseTypeDefinition, baseTypeArgs, out Type? resolvedType)) - { - ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(baseType, derivedType); - } + (options ??= new()).DerivedTypes.Add(new JsonDerivedType(derivedType, attr.TypeDiscriminator)); + } - JsonPolymorphismOptions options = typeInfo.PolymorphismOptions ??= new(); - options.DerivedTypes.Add(new JsonDerivedType(resolvedType, attr.TypeDiscriminator)); + if (options != null) + { + typeInfo.SetPolymorphismOptions(options); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs index 172df7397ddd45..de6f6993edd835 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs @@ -114,13 +114,6 @@ public DerivedTypeList(JsonPolymorphismOptions parent) foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) { - // Skip open generic derived types; they are resolved separately - // by the DefaultJsonTypeInfoResolver (reflection) or source generator (AOT). - if (attr.DerivedType is { IsGenericTypeDefinition: true }) - { - continue; - } - (options ??= new()).DerivedTypes.Add(new JsonDerivedType(attr.DerivedType, attr.TypeDiscriminator)); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index 4d98115acf9103..271455b60aa6d7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -1250,6 +1250,17 @@ internal void PopulatePolymorphismMetadata() } } + /// + /// Sets polymorphism options from the internal resolver, bypassing + /// the public setter's Kind validation. + /// + internal void SetPolymorphismOptions(JsonPolymorphismOptions options) + { + Debug.Assert(!IsReadOnly); + options.DeclaringTypeInfo = this; + _polymorphismOptions = options; + } + internal void MapInterfaceTypesToCallbacks() { Debug.Assert(!IsReadOnly); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs index 3f5745c81a8e3a..3dd94a5c7e220f 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs @@ -2948,6 +2948,58 @@ public class RegularDerived_Mixed : OpenGenericBase_Mixed public string? Extra { get; set; } } + [Fact] + public async Task OpenGenericDerivedType_InterfaceHierarchy_Works() + { + // Tests unification through a chain of generic interfaces: + // IDerived extends IBase, and we serialize through IBase. + IOpenGenericBase_InterfaceHierarchy value = new OpenGenericImpl_InterfaceHierarchy { Value = 42, Extra = "extra" }; + string json = await Serializer.SerializeWrapper(value); + JsonTestHelper.AssertJsonEqual("""{"$type":"impl","Value":42,"Extra":"extra"}""", json); + + var result = await Serializer.DeserializeWrapper>(json); + Assert.IsType>(result); + Assert.Equal(42, result.Value); + } + + [JsonDerivedType(typeof(OpenGenericImpl_InterfaceHierarchy<>), "impl")] + public interface IOpenGenericBase_InterfaceHierarchy + { + T? Value { get; set; } + } + + public interface IOpenGenericDerived_InterfaceHierarchy : IOpenGenericBase_InterfaceHierarchy; + + public class OpenGenericImpl_InterfaceHierarchy : IOpenGenericDerived_InterfaceHierarchy + { + public T? Value { get; set; } + public string? Extra { get; set; } + } + + [Fact] + public async Task OpenGenericDerivedType_InterfaceBaseWithWrappedTypeArg_Works() + { + // Tests unification through an interface with wrapped type args: + // Impl implements IBase>. + IOpenGenericBase_InterfaceWrapped> value = new OpenGenericImpl_InterfaceWrapped { Data = ["a", "b"] }; + string json = await Serializer.SerializeWrapper(value); + JsonTestHelper.AssertJsonEqual("""{"$type":"impl","Data":["a","b"]}""", json); + + var result = await Serializer.DeserializeWrapper>>(json); + Assert.IsType>(result); + } + + [JsonDerivedType(typeof(OpenGenericImpl_InterfaceWrapped<>), "impl")] + public interface IOpenGenericBase_InterfaceWrapped + { + T? Data { get; set; } + } + + public class OpenGenericImpl_InterfaceWrapped : IOpenGenericBase_InterfaceWrapped> + { + public List? Data { get; set; } + } + #endregion [Fact] From 151f0b0d54dfa5e5be8a527a208ce12a37900cc2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 11:41:09 +0000 Subject: [PATCH 11/12] Lazily compute generic type info in PopulatePolymorphismFromAttributes Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/a0d521b2-5b4b-4f05-bb14-6fe9d541bf0d Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com> --- .../Metadata/DefaultJsonTypeInfoResolver.Helpers.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs index c7f8d6135d2cb1..9962144820e3e2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs @@ -660,8 +660,8 @@ private static void PopulatePolymorphismFromAttributes(JsonTypeInfo typeInfo) }; } - Type? baseTypeDefinition = baseType.IsGenericType ? baseType.GetGenericTypeDefinition() : null; - Type[]? baseTypeArgs = baseType.IsGenericType ? baseType.GetGenericArguments() : null; + Type? baseTypeDefinition = null; + Type[]? baseTypeArgs = null; foreach (JsonDerivedTypeAttribute attr in baseType.GetCustomAttributes(inherit: false)) { @@ -669,11 +669,14 @@ private static void PopulatePolymorphismFromAttributes(JsonTypeInfo typeInfo) if (derivedType is { IsGenericTypeDefinition: true }) { - if (baseTypeDefinition is null || baseTypeArgs is null) + if (!baseType.IsGenericType) { ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(baseType, derivedType); } + baseTypeDefinition ??= baseType.GetGenericTypeDefinition(); + baseTypeArgs ??= baseType.GetGenericArguments(); + if (!TryResolveOpenGenericDerivedType(derivedType, baseTypeDefinition, baseTypeArgs, out Type? resolvedType)) { ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeNotSupported(baseType, derivedType); From 22a4b47ca4d6932732d0a23c6c1d9be51fef08f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:12:15 +0000 Subject: [PATCH 12/12] Use FormatStringLiteral for emitter string discriminator and add constraint validation in TryResolveOpenGenericDerivedType Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/cf3798fe-f3d6-47a1-9174-621b8812f5a4 Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com> --- .../gen/JsonSourceGenerator.Emitter.cs | 2 +- .../gen/JsonSourceGenerator.Parser.cs | 36 ++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 19df5aac6c560e..924bc848d01e7e 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -596,7 +596,7 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene { string discriminatorArg = derivedTypeSpec.TypeDiscriminator switch { - string s => $", \"{s}\"", + string s => $", {FormatStringLiteral(s)}", int i => $", {i}", _ => "", }; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index d8a31a4af081a9..c8d8b884bbc74b 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -889,7 +889,7 @@ private void ProcessTypeCustomAttributes( /// type arguments from the constructed base type at compile time. /// Returns null if the type arguments cannot be resolved. /// - private static bool TryResolveOpenGenericDerivedType( + private bool TryResolveOpenGenericDerivedType( INamedTypeSymbol unboundDerived, ITypeSymbol baseType, [NotNullWhen(true)] out INamedTypeSymbol? resolvedType) @@ -925,13 +925,41 @@ private static bool TryResolveOpenGenericDerivedType( } } - // Verify all type parameters were resolved. - for (int i = 0; i < resolved.Length; i++) + // Verify all type parameters were resolved and satisfy constraints. + Compilation compilation = _knownSymbols.Compilation; + for (int i = 0; i < derivedTypeParams.Length; i++) { - if (resolved[i] is null) + ITypeSymbol? resolvedArg = resolved[i]; + if (resolvedArg is null) { return false; } + + ITypeParameterSymbol typeParam = derivedTypeParams[i]; + + if (typeParam.HasReferenceTypeConstraint && !resolvedArg.IsReferenceType) + { + return false; + } + + if (typeParam.HasValueTypeConstraint && !resolvedArg.IsValueType) + { + return false; + } + + if (typeParam.HasUnmanagedTypeConstraint && !resolvedArg.IsUnmanagedType) + { + return false; + } + + foreach (ITypeSymbol constraintType in typeParam.ConstraintTypes) + { + Conversion conversion = compilation.ClassifyConversion(resolvedArg, constraintType); + if (!(conversion.IsImplicit || conversion.IsIdentity)) + { + return false; + } + } } resolvedType = derivedDefinition.Construct(resolved!);