Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose additional metadata on the STJ contract model. #102902

Merged
merged 6 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ private sealed partial class Emitter
private const string CreateValueInfoMethodName = "CreateValueInfo";
private const string CtorParamInitMethodNameSuffix = "CtorParamInit";
private const string DefaultOptionsStaticVarName = "s_defaultOptions";
private const string InstanceMemberBindingFlagsVariableName = "InstanceMemberBindingFlags";
private const string OriginatingResolverPropertyName = "OriginatingResolver";
private const string InfoVarName = "info";
private const string NumberHandlingPropName = "NumberHandling";
Expand Down Expand Up @@ -500,6 +501,7 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene

string? propInitMethodName = null;
string? propInitAdapterFunc = null;
string? constructorInfoFactoryFunc = null;
string? ctorParamMetadataInitMethodName = null;
string? serializeMethodName = null;

Expand All @@ -508,10 +510,20 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene
propInitMethodName = $"{typeFriendlyName}{PropInitMethodNameSuffix}";
propInitAdapterFunc = $"_ => {propInitMethodName}({OptionsLocalVariableName})";

if (constructionStrategy == ObjectConstructionStrategy.ParameterizedConstructor)
if (constructionStrategy is ObjectConstructionStrategy.ParameterizedConstructor)
{
ctorParamMetadataInitMethodName = $"{typeFriendlyName}{CtorParamInitMethodNameSuffix}";
}

if (constructionStrategy is ObjectConstructionStrategy.ParameterlessConstructor
or ObjectConstructionStrategy.ParameterizedConstructor)
{
string argTypes = typeMetadata.CtorParamGenSpecs.Count == 0
? "global::System.Array.Empty<global::System.Type>()"
: $$"""new[] {{{string.Join(", ", typeMetadata.CtorParamGenSpecs.Select(p => $"typeof({p.ParameterType.FullyQualifiedName})"))}}}""";

constructorInfoFactoryFunc = $"static () => typeof({typeMetadata.TypeRef.FullyQualifiedName}).GetConstructor({InstanceMemberBindingFlagsVariableName}, binder: null, {argTypes}, modifiers: null)";
}
}

if (ShouldGenerateSerializationLogic(typeMetadata))
Expand All @@ -531,7 +543,8 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene
ObjectWithParameterizedConstructorCreator = {{parameterizedCreatorInvocation}},
PropertyMetadataInitializer = {{propInitAdapterFunc ?? "null"}},
ConstructorParameterMetadataInitializer = {{ctorParamMetadataInitMethodName ?? "null"}},
{{SerializeHandlerPropName}} = {{serializeMethodName ?? "null"}}
ConstructorAttributeProviderFactory = {{constructorInfoFactoryFunc ?? "null"}},
{{SerializeHandlerPropName}} = {{serializeMethodName ?? "null"}},
};

{{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.CreateObjectInfo<{{typeMetadata.TypeRef.FullyQualifiedName}}>({{OptionsLocalVariableName}}, {{ObjectInfoVarName}});
Expand Down Expand Up @@ -651,7 +664,8 @@ private void GeneratePropMetadataInitFunc(SourceWriter writer, string propInitMe
IsExtensionData = {{FormatBoolLiteral(property.IsExtensionData)}},
NumberHandling = {{FormatNumberHandling(property.NumberHandling)}},
PropertyName = {{FormatStringLiteral(property.MemberName)}},
JsonPropertyName = {{FormatStringLiteral(property.JsonPropertyName)}}
JsonPropertyName = {{FormatStringLiteral(property.JsonPropertyName)}},
AttributeProviderFactory = static () => typeof({{property.DeclaringType.FullyQualifiedName}}).GetMember({{FormatStringLiteral(property.MemberName)}}, {{InstanceMemberBindingFlagsVariableName}}) is { Length: > 0 } results ? results[0] : null,
};

properties[{{i}}] = {{JsonMetadataServicesTypeRef}}.CreatePropertyInfo<{{propertyTypeFQN}}>({{OptionsLocalVariableName}}, {{InfoVarName}}{{i}});
Expand Down Expand Up @@ -711,7 +725,7 @@ private static void GenerateCtorParamMetadataInitFunc(SourceWriter writer, strin
ParameterType = typeof({{spec.ParameterType.FullyQualifiedName}}),
Position = {{spec.ParameterIndex}},
HasDefaultValue = {{FormatBoolLiteral(spec.HasDefaultValue)}},
DefaultValue = {{CSharpSyntaxUtilities.FormatLiteral(spec.DefaultValue, spec.ParameterType)}},
DefaultValue = {{(spec.HasDefaultValue ? CSharpSyntaxUtilities.FormatLiteral(spec.DefaultValue, spec.ParameterType) : "null")}},
IsNullable = {{FormatBoolLiteral(spec.IsNullable)}},
},
""");
Expand All @@ -735,6 +749,7 @@ private static void GenerateCtorParamMetadataInitFunc(SourceWriter writer, strin
Name = {{FormatStringLiteral(spec.Name)}},
ParameterType = typeof({{spec.ParameterType.FullyQualifiedName}}),
Position = {{spec.ParameterIndex}},
IsMemberInitializer = true,
},
""");

Expand Down Expand Up @@ -1099,7 +1114,14 @@ private static SourceText GetRootJsonContextImplementation(ContextGenerationSpec

GetLogicForDefaultSerializerOptionsInit(contextSpec.GeneratedOptionsSpec, writer);

writer.WriteLine();
writer.WriteLine($"""

stephentoub marked this conversation as resolved.
Show resolved Hide resolved
private const global::System.Reflection.BindingFlags {InstanceMemberBindingFlagsVariableName} =
global::System.Reflection.BindingFlags.Instance |
global::System.Reflection.BindingFlags.Public |
global::System.Reflection.BindingFlags.NonPublic;

""");

writer.WriteLine($$"""
/// <summary>
Expand Down
21 changes: 21 additions & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1263,19 +1263,34 @@ public static partial class JsonMetadataServices
public sealed partial class JsonObjectInfoValues<T>
{
public JsonObjectInfoValues() { }
public System.Func<System.Reflection.ICustomAttributeProvider>? ConstructorAttributeProviderFactory { get { throw null; } init { } }
public System.Func<System.Text.Json.Serialization.Metadata.JsonParameterInfoValues[]>? ConstructorParameterMetadataInitializer { get { throw null; } init { } }
public System.Text.Json.Serialization.JsonNumberHandling NumberHandling { get { throw null; } init { } }
public System.Func<T>? ObjectCreator { get { throw null; } init { } }
public System.Func<object[], T>? ObjectWithParameterizedConstructorCreator { get { throw null; } init { } }
public System.Func<System.Text.Json.Serialization.JsonSerializerContext, System.Text.Json.Serialization.Metadata.JsonPropertyInfo[]>? PropertyMetadataInitializer { get { throw null; } init { } }
public System.Action<System.Text.Json.Utf8JsonWriter, T>? SerializeHandler { get { throw null; } init { } }
}
public abstract partial class JsonParameterInfo
{
internal JsonParameterInfo() { }
public System.Type DeclaringType { get { throw null; } }
public System.Reflection.ICustomAttributeProvider? AttributeProvider { get { throw null; } }
public object? DefaultValue { get { throw null; } }
public bool HasDefaultValue { get { throw null; } }
public bool IsNullable { get { throw null; } }
public bool IsMemberInitializer { get { throw null; } }
public string Name { get { throw null; } }
public System.Type ParameterType { get { throw null; } }
public int Position { get { throw null; } }
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public sealed partial class JsonParameterInfoValues
{
public JsonParameterInfoValues() { }
public object? DefaultValue { get { throw null; } init { } }
public bool HasDefaultValue { get { throw null; } init { } }
public bool IsMemberInitializer { get { throw null; } init { } }
public bool IsNullable { get { throw null; } init { } }
public string Name { get { throw null; } init { } }
public System.Type ParameterType { get { throw null; } init { } }
Expand All @@ -1293,8 +1308,10 @@ public JsonPolymorphismOptions() { }
public abstract partial class JsonPropertyInfo
{
internal JsonPropertyInfo() { }
public System.Text.Json.Serialization.Metadata.JsonParameterInfo? AssociatedParameter { get { throw null; } }
public System.Reflection.ICustomAttributeProvider? AttributeProvider { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonConverter? CustomConverter { get { throw null; } set { } }
public System.Type DeclaringType { get { throw null; } }
public System.Func<object, object?>? Get { get { throw null; } set { } }
public bool IsExtensionData { get { throw null; } set { } }
public bool IsGetNullable { get { throw null; } set { } }
Expand All @@ -1313,6 +1330,7 @@ internal JsonPropertyInfo() { }
public sealed partial class JsonPropertyInfoValues<T>
{
public JsonPropertyInfoValues() { }
public System.Func<System.Reflection.ICustomAttributeProvider>? AttributeProviderFactory { get; init; }
public System.Text.Json.Serialization.JsonConverter<T>? Converter { get { throw null; } init { } }
public System.Type DeclaringType { get { throw null; } init { } }
public System.Func<object, T?>? Getter { get { throw null; } init { } }
Expand All @@ -1333,7 +1351,10 @@ public abstract partial class JsonTypeInfo
internal JsonTypeInfo() { }
public System.Text.Json.Serialization.JsonConverter Converter { get { throw null; } }
public System.Func<object>? CreateObject { get { throw null; } set { } }
public System.Reflection.ICustomAttributeProvider? ConstructorAttributeProvider { get { throw null; } }
public System.Type? ElementType { get { throw null; } }
public bool IsReadOnly { get { throw null; } }
public System.Type? KeyType { get { throw null; } }
public System.Text.Json.Serialization.Metadata.JsonTypeInfoKind Kind { get { throw null; } }
public System.Text.Json.Serialization.JsonNumberHandling? NumberHandling { get { throw null; } set { } }
public System.Action<object>? OnDeserialized { get { throw null; } set { } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal sealed class CastingConverter<T> : JsonConverter<T>
private readonly JsonConverter _sourceConverter;
internal override Type? KeyType => _sourceConverter.KeyType;
internal override Type? ElementType => _sourceConverter.ElementType;
internal override JsonConverter? NullableElementConverter => _sourceConverter.NullableElementConverter;

public override bool HandleNull { get; }
internal override bool SupportsCreateObjectDelegate => _sourceConverter.SupportsCreateObjectDelegate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal sealed class FSharpOptionConverter<TOption, TElement> : JsonConverter<T
where TOption : class
{
internal override Type? ElementType => typeof(TElement);
internal override JsonConverter? NullableElementConverter => _elementConverter;
// 'None' is encoded using 'null' at runtime and serialized as 'null' in JSON.
public override bool HandleNull => true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal sealed class FSharpValueOptionConverter<TValueOption, TElement> : JsonC
where TValueOption : struct, IEquatable<TValueOption>
{
internal override Type? ElementType => typeof(TElement);
internal override JsonConverter? NullableElementConverter => _elementConverter;
// 'ValueNone' is encoded using 'default' at runtime and serialized as 'null' in JSON.
public override bool HandleNull => true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal sealed class JsonMetadataServicesConverter<T> : JsonResumableConverter<

internal override Type? KeyType => Converter.KeyType;
internal override Type? ElementType => Converter.ElementType;
internal override JsonConverter? NullableElementConverter => Converter.NullableElementConverter;
public override bool HandleNull { get; }

internal override bool ConstructorIsParameterized => Converter.ConstructorIsParameterized;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ internal abstract class JsonObjectConverter<T> : JsonResumableConverter<T>
{
private protected sealed override ConverterStrategy GetDefaultConverterStrategy() => ConverterStrategy.Object;
internal override bool CanPopulate => true;
internal sealed override Type? ElementType => null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ protected sealed override void InitializeConstructorArgumentCaches(ref ReadStack
object?[] arguments = ArrayPool<object>.Shared.Rent(typeInfo.ParameterCache.Count);
foreach (JsonParameterInfo parameterInfo in typeInfo.ParameterCache)
{
arguments[parameterInfo.Position] = parameterInfo.DefaultValue;
arguments[parameterInfo.Position] = parameterInfo.EffectiveDefaultValue;
}

state.Current.CtorArgumentState!.Arguments = arguments;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private static bool TryRead<TArg>(
if (info.IgnoreNullTokensOnRead)
{
// Use default value specified on parameter, if any.
value = info.DefaultValue;
value = info.EffectiveDefaultValue;
}
else if (!info.IsNullable && info.Options.RespectNullableAnnotations)
{
Expand Down Expand Up @@ -102,16 +102,16 @@ protected override void InitializeConstructorArgumentCaches(ref ReadStack state,
switch (parameterInfo.Position)
{
case 0:
arguments.Arg0 = ((JsonParameterInfo<TArg0>)parameterInfo).DefaultValue;
arguments.Arg0 = ((JsonParameterInfo<TArg0>)parameterInfo).EffectiveDefaultValue;
break;
case 1:
arguments.Arg1 = ((JsonParameterInfo<TArg1>)parameterInfo).DefaultValue;
arguments.Arg1 = ((JsonParameterInfo<TArg1>)parameterInfo).EffectiveDefaultValue;
break;
case 2:
arguments.Arg2 = ((JsonParameterInfo<TArg2>)parameterInfo).DefaultValue;
arguments.Arg2 = ((JsonParameterInfo<TArg2>)parameterInfo).EffectiveDefaultValue;
break;
case 3:
arguments.Arg3 = ((JsonParameterInfo<TArg3>)parameterInfo).DefaultValue;
arguments.Arg3 = ((JsonParameterInfo<TArg3>)parameterInfo).EffectiveDefaultValue;
break;
default:
Debug.Fail("More than 4 params: we should be in override for LargeObjectWithParameterizedConstructorConverter.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ protected static bool TryLookupConstructorParameter(
// For case insensitive and missing property support of JsonPath, remember the value on the temporary stack.
state.Current.JsonPropertyName = utf8PropertyName;

jsonParameterInfo = jsonPropertyInfo.ParameterInfo;
jsonParameterInfo = jsonPropertyInfo.AssociatedParameter;
if (jsonParameterInfo != null)
{
state.Current.JsonPropertyInfo = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace System.Text.Json.Serialization.Converters
internal sealed class NullableConverter<T> : JsonConverter<T?> where T : struct
{
internal override Type? ElementType => typeof(T);
internal override JsonConverter? NullableElementConverter => _elementConverter;
public override bool HandleNull => true;
internal override bool CanPopulate => _elementConverter.CanPopulate;
internal override bool ConstructorIsParameterized => _elementConverter.ConstructorIsParameterized;
Expand All @@ -21,7 +22,6 @@ public NullableConverter(JsonConverter<T> elementConverter)
_elementConverter = elementConverter;
IsInternalConverterForNumberType = elementConverter.IsInternalConverterForNumberType;
ConverterStrategy = elementConverter.ConverterStrategy;
ConstructorInfo = elementConverter.ConstructorInfo;
}

internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out T? value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,11 @@ internal JsonConverter<TTarget> CreateCastingConverter<TTarget>()
/// </summary>
internal virtual JsonConverter? SourceConverterForCastingConverter => null;

internal abstract Type? ElementType { get; }
internal virtual Type? ElementType => null;

internal abstract Type? KeyType { get; }
internal virtual Type? KeyType => null;

internal virtual JsonConverter? NullableElementConverter => null;

/// <summary>
/// Cached value of TypeToConvert.IsValueType, which is an expensive call.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ protected JsonConverterFactory() { }
/// </returns>
public abstract JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options);

internal sealed override Type? KeyType => null;

internal sealed override Type? ElementType => null;

internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(CanConvert(typeToConvert));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ internal sealed override JsonTypeInfo CreateJsonTypeInfo(JsonSerializerOptions o
return new JsonTypeInfo<T>(this, options);
}

internal override Type? KeyType => null;

internal override Type? ElementType => null;

/// <summary>
/// Indicates whether <see langword="null"/> should be passed to the converter on serialization,
/// and whether <see cref="JsonTokenType.Null"/> should be passed on deserialization.
Expand Down
Loading
Loading