Skip to content

Commit 405c9b0

Browse files
committed
Make resolver GetTypeInfo methods return new instances on every call
1 parent a889912 commit 405c9b0

File tree

2 files changed

+62
-38
lines changed

2 files changed

+62
-38
lines changed

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ private sealed partial class Emitter
2828
private const string DefaultOptionsStaticVarName = "s_defaultOptions";
2929
private const string DefaultContextBackingStaticVarName = "s_defaultContext";
3030
internal const string GetConverterFromFactoryMethodName = "GetConverterFromFactory";
31+
private const string GetTypeInfoCoreMethodName = "GetTypeInfoCore";
32+
private const string GetTypeInfoMethodName = "GetTypeInfo";
3133
private const string InfoVarName = "info";
3234
private const string PropertyInfoVarName = "propertyInfo";
3335
internal const string JsonContextVarName = "jsonContext";
@@ -37,6 +39,7 @@ private sealed partial class Emitter
3739
private const string JsonTypeInfoReturnValueLocalVariableName = "jsonTypeInfo";
3840
private const string PropInitMethodNameSuffix = "PropInit";
3941
private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter";
42+
private const string TypeLocalVariableName = "type";
4043
private const string SerializeHandlerPropName = "SerializeHandler";
4144
private const string ValueVarName = "value";
4245
private const string WriterVarName = "writer";
@@ -1227,9 +1230,9 @@ private static string GetFetchLogicForGetCustomConverter_TypesWithFactories()
12271230
{
12281231
return @$"
12291232

1230-
private static {JsonConverterTypeRef} {GetConverterFromFactoryMethodName}({JsonSerializerOptionsTypeRef} {OptionsLocalVariableName}, {TypeTypeRef} type, {JsonConverterFactoryTypeRef} factory)
1233+
private static {JsonConverterTypeRef} {GetConverterFromFactoryMethodName}({JsonSerializerOptionsTypeRef} {OptionsLocalVariableName}, {TypeTypeRef} {TypeLocalVariableName}, {JsonConverterFactoryTypeRef} factory)
12311234
{{
1232-
{JsonConverterTypeRef}? converter = factory.CreateConverter(type, {OptionsLocalVariableName});
1235+
{JsonConverterTypeRef}? converter = factory.CreateConverter({TypeLocalVariableName}, {OptionsLocalVariableName});
12331236
if (converter == null || converter is {JsonConverterFactoryTypeRef})
12341237
{{
12351238
throw new {InvalidOperationExceptionTypeRef}(string.Format(""{ExceptionMessages.InvalidJsonConverterFactoryOutput}"", factory.GetType()));
@@ -1245,56 +1248,33 @@ private string GetGetTypeInfoImplementation()
12451248

12461249
sb.Append(
12471250
@$"/// <inheritdoc/>
1248-
public override {JsonTypeInfoTypeRef} GetTypeInfo({TypeTypeRef} type)
1249-
{{");
1250-
1251-
HashSet<TypeGenerationSpec> types = new(_currentContext.TypesWithMetadataGenerated);
1251+
public override {JsonTypeInfoTypeRef} {GetTypeInfoMethodName}({TypeTypeRef} {TypeLocalVariableName})
1252+
=> {GetTypeInfoCoreMethodName}({TypeLocalVariableName}, {OptionsInstanceVariableName});
12521253

1253-
// TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64.
1254-
foreach (TypeGenerationSpec metadata in types)
1255-
{
1256-
if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen)
1257-
{
1258-
sb.Append($@"
1259-
if (type == typeof({metadata.TypeRef}))
1260-
{{
1261-
return this.{metadata.TypeInfoPropertyName};
1262-
}}
1254+
{JsonTypeInfoTypeRef}? {JsonTypeInfoResolverTypeRef}.{GetTypeInfoMethodName}({TypeTypeRef} {TypeLocalVariableName}, {JsonSerializerOptionsTypeRef} {OptionsLocalVariableName})
1255+
=> {GetTypeInfoCoreMethodName}({TypeLocalVariableName}, {OptionsLocalVariableName});
12631256
");
1264-
}
1265-
}
1266-
1267-
sb.AppendLine(@"
1268-
return null!;
1269-
}");
12701257

1271-
// Explicit IJsonTypeInfoResolver implementation
12721258
sb.AppendLine();
1273-
sb.Append(@$"{JsonTypeInfoTypeRef}? {JsonTypeInfoResolverTypeRef}.GetTypeInfo({TypeTypeRef} type, {JsonSerializerOptionsTypeRef} {OptionsLocalVariableName})
1274-
{{
1275-
if ({OptionsInstanceVariableName} == {OptionsLocalVariableName})
1276-
{{
1277-
return this.GetTypeInfo(type);
1278-
}}
1279-
else
1280-
{{");
1259+
sb.Append(@$"private {JsonTypeInfoTypeRef}? {GetTypeInfoCoreMethodName}({TypeTypeRef} {TypeLocalVariableName}, {JsonSerializerOptionsTypeRef} {OptionsLocalVariableName})
1260+
{{");
1261+
12811262
// TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64.
1282-
foreach (TypeGenerationSpec metadata in types)
1263+
foreach (TypeGenerationSpec metadata in _currentContext.TypesWithMetadataGenerated)
12831264
{
12841265
if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen)
12851266
{
12861267
sb.Append($@"
1287-
if (type == typeof({metadata.TypeRef}))
1288-
{{
1289-
return {metadata.CreateTypeInfoMethodName}({OptionsLocalVariableName});
1290-
}}
1268+
if ({TypeLocalVariableName} == typeof({metadata.TypeRef}))
1269+
{{
1270+
return {metadata.CreateTypeInfoMethodName}({OptionsLocalVariableName});
1271+
}}
12911272
");
12921273
}
12931274
}
12941275

12951276
sb.Append($@"
1296-
return null;
1297-
}}
1277+
return null;
12981278
}}
12991279
");
13001280

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,5 +463,49 @@ public TestResolver(Func<Type, JsonSerializerOptions, JsonTypeInfo?> getTypeInfo
463463

464464
public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) => _getTypeInfo(type, options);
465465
}
466+
467+
[Fact]
468+
public static void OptionsGetTypeInfoDoesNotReturnContextInstance()
469+
{
470+
PersonJsonContext context = PersonJsonContext.Default;
471+
472+
// Context GetTypeInfo(Type) method returns new instance on each call
473+
Assert.NotSame(context.GetTypeInfo(typeof(Person)), context.GetTypeInfo(typeof(Person)));
474+
475+
JsonTypeInfo context_StaticProp_TypeInfo = context.Person;
476+
JsonTypeInfo context_Fetched_TypeInfo = context.GetTypeInfo(typeof(Person));
477+
478+
// Context fetched instance is different from context static instance
479+
Assert.NotSame(context_StaticProp_TypeInfo, context_Fetched_TypeInfo);
480+
context_Fetched_TypeInfo.Properties.Clear(); // Context fetched instance is mutable
481+
Assert.Equal(2, context_StaticProp_TypeInfo.Properties.Count);
482+
Assert.Equal(0, context_Fetched_TypeInfo.Properties.Count);
483+
484+
// Context static instance is not mutable
485+
Assert.Throws<InvalidOperationException>(() => context_StaticProp_TypeInfo.CreateObject = null);
486+
Assert.Throws<InvalidOperationException>(() => context_StaticProp_TypeInfo.Properties.Clear());
487+
488+
// Context-owned options is read-only so fetched instance is cached
489+
JsonTypeInfo options_Cached_TypeInfo = context.Options.GetTypeInfo(typeof(Person));
490+
Assert.Same(options_Cached_TypeInfo, context.Options.GetTypeInfo(typeof(Person)));
491+
492+
// Context-owned type info is read-only
493+
Assert.Throws<InvalidOperationException>(() => options_Cached_TypeInfo.Properties.Clear());
494+
495+
// Options fetched instance is different from context instances
496+
Assert.NotSame(context_StaticProp_TypeInfo, options_Cached_TypeInfo);
497+
Assert.NotSame(context_Fetched_TypeInfo, options_Cached_TypeInfo);
498+
499+
// Context GetTypeInfo(Type, Options) method returns new instance on each call
500+
IJsonTypeInfoResolver resolver = context;
501+
JsonSerializerOptions options = new() { TypeInfoResolver = context };
502+
503+
Assert.NotSame(
504+
resolver.GetTypeInfo(typeof(Person), options),
505+
resolver.GetTypeInfo(typeof(Person), options));
506+
507+
// Options are still mutable, so fetched type-info's are not cached.
508+
Assert.NotSame(options.GetTypeInfo(typeof(Person)), options.GetTypeInfo(typeof(Person)));
509+
}
466510
}
467511
}

0 commit comments

Comments
 (0)