Commit 10aeb7b
Support open generics in polymorphic serialization in System.Text.Json (#127318)
## Summary
Adds support for applying `[JsonDerivedType(typeof(Derived<>))]` to a
generic base type. The polymorphic resolver and source generator each
unify the open derived type against the closed base and, when a single
closed derived type can be constructed, register it for serialization.
This is groundwork for the upcoming C# [closed hierarchies][closed]
language feature, which allows generic closed base classes. Today,
polymorphic serialization requires every derived type to be spelled out
as a fully closed generic instantiation — workable for non-generic
hierarchies, but impractical for generic ones where each combination of
the base's type arguments yields a distinct closed instantiation that
the author would otherwise have to enumerate by hand.
Tracking: part of the work for #125449.
[closed]:
https://github.com/dotnet/csharplang/blob/main/proposals/closed-hierarchies.md
## Patterns now admitted
Each example uses regular classes; for each pattern the derived
attribute is declared once with an open generic, and the closed forms
are resolved per serialized instantiation of the base. All of these
compile (source generator) and round-trip (reflection):
1. **Matching arity / identity binding** — derived passes the base's
type parameter through unchanged.
```csharp
[JsonDerivedType(typeof(Derived<>), "d")]
public class Base<T> { ... }
public class Derived<T> : Base<T> { ... }
// Base<int> → Derived<int>
// Base<string> → Derived<string>
```
2. **Reordered parameters** — derived rebinds the base's parameters in a
different position.
```csharp
[JsonDerivedType(typeof(Derived<,>), "d")]
public class Base<T1, T2> { ... }
public class Derived<U, V> : Base<V, U> { ... }
// Base<int, string> → Derived<string, int>
```
3. **Partial concretization in the derived's base spec** — derived fixes
some of the base's parameters to concrete types and leaves others open.
```csharp
[JsonDerivedType(typeof(Derived<>), "d")]
public class Base<T1, T2> { ... }
public class Derived<T> : Base<T, int> { ... }
// Base<string, int> → Derived<string>
// Base<bool, int> → Derived<bool>
```
4. **Wrapped / nested type arguments** — derived's type parameter shows
up inside a generic construction (or array) in the base spec.
```csharp
[JsonDerivedType(typeof(Derived<>), "d")]
public class Base<T> { ... }
public class Derived<T> : Base<List<T>> { ... }
// Base<List<int>> → Derived<int>
[JsonDerivedType(typeof(ArrayDerived<>), "a")]
public class ArrayDerived<T> : Base<T[]> { ... }
// Base<int[]> → ArrayDerived<int>
```
5. **Interface bases** — same unification logic applies when the base is
a generic interface.
```csharp
[JsonDerivedType(typeof(Derived<>), "d")]
public interface IBase<T> { ... }
public class Derived<T> : IBase<T> { ... }
// IBase<int> → Derived<int>
```
6. **Type parameters from enclosing types** — derived inherits part of
its type arguments from an outer generic class.
```csharp
[JsonDerivedType(typeof(Outer<>.Leaf<>), "leaf")]
public class Base<T> { ... }
public class Outer<T>
{
public class Leaf<U> : Base<(T, U)> { ... }
}
// Base<(int, string)> → Outer<int>.Leaf<string>
```
## Patterns still not supported (loud failure)
Each of these emits **SYSLIB1229** at source-generation time and throws
`InvalidOperationException` at reflection-resolver `Configure()` time.
The restrictions mirror the C# closed-hierarchies rules ("all of the
derived's type parameters must be used in the base class
specification"), with the additional requirement that the unification be
unambiguous.
1. **Ground-position mismatch** — derived constrains a base parameter to
a concrete type that the closed base doesn't agree with.
```csharp
[JsonDerivedType(typeof(Derived<>), "d")]
public class Base<T1, T2> { ... }
public class Derived<T> : Base<T, int> { ... }
// Base<int, string> → ✗ (Derived requires T2 == int, but base has T2 ==
string)
```
2. **Wrapping not present on the closed base** — derived's base spec
wraps the parameter in a generic that the closed base doesn't carry.
```csharp
[JsonDerivedType(typeof(Derived<>), "d")]
public class Base<T> { ... }
public class Derived<T> : Base<List<T>> { ... }
// Base<int> → ✗ (closed base T is `int`, not `List<...>`)
// Base<List<int>> → ✓ Derived<int> (admitted by pattern #4)
```
3. **Unbound derived type parameters** — derived declares more type
parameters than the base substitution can pin down (the
closed-hierarchies spec rejects this form directly).
```csharp
[JsonDerivedType(typeof(Derived<,>), "d")]
public class Base<T> { ... }
public class Derived<T, U> : Base<T> { ... }
// Base<int> → ✗ (U is unbound)
```
4. **Constraint violation under the resolved substitution** —
unification succeeds structurally but the closed derived type would
violate a `where` constraint.
```csharp
[JsonDerivedType(typeof(Derived<>), "d")]
public class Base<T> { ... }
public class Derived<T> : Base<T> where T : struct { ... }
// Base<string> → ✗ (string does not satisfy `where T : struct`)
```
5. **Ambiguous match** — derived implements two distinct constructions
of the same generic base, so the closed base could correspond to either.
```csharp
[JsonDerivedType(typeof(Derived<>), "d")]
public interface IBase<T> { ... }
public class Derived<T> : IBase<T>, IBase<int> { ... }
// IBase<int> → ✗ (could be Derived<int> or Derived<T> for any T)
```
6. **Arity / shape mismatch** — derived's open form has no constructed
base in common with the closed base at all.
```csharp
[JsonDerivedType(typeof(Derived<>), "d")]
public class Base { ... } // non-generic
public class Derived<T> : Base { ... }
// Base → ✗ (closed base has no type arguments to bind T)
```
## Diagnostic
| ID | Severity | Message |
| ---------- | -------- |
---------------------------------------------------------------------------------------
|
| SYSLIB1229 | Warning | The open generic derived type `'{0}'` could not
be resolved against base `'{1}'`: `{2}` |
SYSLIB1229 is `#pragma`-suppressible, in which case the offending
derived entry is simply skipped from the generated metadata.
## Checklist
- [x] New tests in `JsonSourceGeneratorDiagnosticsTests` and
`PolymorphicTests.CustomTypeHierarchies` cover every supported and
unsupported pattern listed above.
- [x] Reflection resolver and source generator carry cross-referencing
comments on their two `TryResolveOpenGenericDerivedType` implementations
so the algorithms stay in sync.
- [x] SYSLIB1229 added to `docs/project/list-of-diagnostics.md`.
- [x] All reviewer feedback addressed.
- [x] All review threads resolved.
Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>1 parent 849bb26 commit 10aeb7b
26 files changed
Lines changed: 3337 additions & 18 deletions
File tree
- docs/project
- src/libraries/System.Text.Json
- gen
- Helpers
- Resources
- xlf
- src
- Resources
- System
- Text/Json
- Serialization/Metadata
- tests
- System.Text.Json.SourceGeneration.Tests/Serialization
- System.Text.Json.SourceGeneration.Unit.Tests
- System.Text.Json.Tests/Serialization
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
274 | 274 | | |
275 | 275 | | |
276 | 276 | | |
277 | | - | |
| 277 | + | |
278 | 278 | | |
279 | 279 | | |
280 | 280 | | |
| |||
Lines changed: 416 additions & 9 deletions
Large diffs are not rendered by default.
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
147 | 147 | | |
148 | 148 | | |
149 | 149 | | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
150 | 158 | | |
151 | 159 | | |
152 | 160 | | |
Lines changed: 220 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
938 | 939 | | |
939 | 940 | | |
940 | 941 | | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
941 | 956 | | |
942 | 957 | | |
943 | 958 | | |
| |||
1021 | 1036 | | |
1022 | 1037 | | |
1023 | 1038 | | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
| 1131 | + | |
| 1132 | + | |
| 1133 | + | |
| 1134 | + | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + | |
| 1138 | + | |
| 1139 | + | |
| 1140 | + | |
| 1141 | + | |
| 1142 | + | |
| 1143 | + | |
| 1144 | + | |
| 1145 | + | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
| 1149 | + | |
| 1150 | + | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
| 1203 | + | |
| 1204 | + | |
| 1205 | + | |
| 1206 | + | |
| 1207 | + | |
| 1208 | + | |
| 1209 | + | |
| 1210 | + | |
| 1211 | + | |
| 1212 | + | |
| 1213 | + | |
| 1214 | + | |
| 1215 | + | |
| 1216 | + | |
| 1217 | + | |
| 1218 | + | |
| 1219 | + | |
| 1220 | + | |
| 1221 | + | |
| 1222 | + | |
| 1223 | + | |
| 1224 | + | |
| 1225 | + | |
| 1226 | + | |
| 1227 | + | |
| 1228 | + | |
| 1229 | + | |
| 1230 | + | |
| 1231 | + | |
| 1232 | + | |
| 1233 | + | |
| 1234 | + | |
| 1235 | + | |
| 1236 | + | |
| 1237 | + | |
| 1238 | + | |
| 1239 | + | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
1024 | 1244 | | |
1025 | 1245 | | |
1026 | 1246 | | |
| |||
Lines changed: 21 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
219 | 219 | | |
220 | 220 | | |
221 | 221 | | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
222 | 243 | | |
Lines changed: 35 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
135 | 170 | | |
136 | 171 | | |
137 | 172 | | |
| |||
0 commit comments