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

Support JsonExtensionData in src-gen fast-path #77880

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 2 additions & 0 deletions src/libraries/System.Text.Json/gen/JsonConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ internal static partial class JsonConstants

public const string IJsonOnSerializedFullName = "System.Text.Json.Serialization.IJsonOnSerialized";
public const string IJsonOnSerializingFullName = "System.Text.Json.Serialization.IJsonOnSerializing";

public const string JsonSerializableAttributeFullName = "System.Text.Json.Serialization.JsonSerializableAttribute";
}
}
65 changes: 52 additions & 13 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,20 @@ private sealed partial class Emitter
// global::fully.qualified.name for referenced types
private const string ArrayTypeRef = "global::System.Array";
private const string InvalidOperationExceptionTypeRef = "global::System.InvalidOperationException";
private const string StringTypeRef = "global::System.String";
private const string TypeTypeRef = "global::System.Type";
private const string UnsafeTypeRef = "global::System.Runtime.CompilerServices.Unsafe";
private const string EqualityComparerTypeRef = "global::System.Collections.Generic.EqualityComparer";
private const string IDictionaryTypeRef = "global::System.Collections.Generic.IDictionary";
private const string IListTypeRef = "global::System.Collections.Generic.IList";
private const string KeyValuePairTypeRef = "global::System.Collections.Generic.KeyValuePair";
private const string JsonEncodedTextTypeRef = "global::System.Text.Json.JsonEncodedText";
private const string JsonNamingPolicyTypeRef = "global::System.Text.Json.JsonNamingPolicy";
private const string JsonSerializerTypeRef = "global::System.Text.Json.JsonSerializer";
private const string JsonSerializerOptionsTypeRef = "global::System.Text.Json.JsonSerializerOptions";
private const string JsonSerializerContextTypeRef = "global::System.Text.Json.Serialization.JsonSerializerContext";
private const string JsonNodeTypeRef = "global::System.Text.Json.Nodes.JsonNode";
private const string Utf8JsonWriterTypeRef = "global::System.Text.Json.Utf8JsonWriter";
private const string JsonSerializerContextTypeRef = "global::System.Text.Json.Serialization.JsonSerializerContext";
private const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter";
private const string JsonConverterFactoryTypeRef = "global::System.Text.Json.Serialization.JsonConverterFactory";
private const string JsonCollectionInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues";
Expand Down Expand Up @@ -268,10 +271,16 @@ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec)
}
}

TypeGenerationSpec? extPropTypeSpec = typeGenerationSpec.ExtensionDataPropertyTypeSpec;
TypeGenerationSpec? extPropTypeSpec = typeGenerationSpec.ExtensionDataPropertySpec?.TypeGenerationSpec;
if (extPropTypeSpec != null)
{
GenerateTypeInfo(extPropTypeSpec);

if (extPropTypeSpec.Type == _generationSpec.JsonObjectType)
{
GenerateTypeInfo(extPropTypeSpec.CollectionKeyTypeMetadata);
GenerateTypeInfo(extPropTypeSpec.CollectionValueTypeMetadata);
}
}
}
break;
Expand Down Expand Up @@ -541,6 +550,17 @@ private string GenerateFastPathFuncForEnumerable(TypeGenerationSpec typeGenerati
}

private string GenerateFastPathFuncForDictionary(TypeGenerationSpec typeGenerationSpec)
{
string serializationLogic = $@"{WriterVarName}.WriteStartObject();

{IndentSource(GenerateFastPathForDictionaryKeyValuePairs(typeGenerationSpec, ValueVarName), numIndentations: 1)}

{WriterVarName}.WriteEndObject();";

return GenerateFastPathFuncForType(typeGenerationSpec, serializationLogic, emitNullCheck: typeGenerationSpec.CanBeNull);
}

private string GenerateFastPathForDictionaryKeyValuePairs(TypeGenerationSpec typeGenerationSpec, string dictionaryValueVarName)
{
TypeGenerationSpec keyTypeGenerationSpec = typeGenerationSpec.CollectionKeyTypeMetadata;
TypeGenerationSpec valueTypeGenerationSpec = typeGenerationSpec.CollectionValueTypeMetadata;
Expand Down Expand Up @@ -568,16 +588,7 @@ private string GenerateFastPathFuncForDictionary(TypeGenerationSpec typeGenerati
{GetSerializeLogicForNonPrimitiveType(valueTypeGenerationSpec, valueToWrite)}";
}

string serializationLogic = $@"{WriterVarName}.WriteStartObject();

foreach ({KeyValuePairTypeRef}<{keyTypeGenerationSpec.TypeRef}, {valueTypeGenerationSpec.TypeRef}> {pairVarName} in {ValueVarName})
{{
{elementSerializationLogic}
}}

{WriterVarName}.WriteEndObject();";

return GenerateFastPathFuncForType(typeGenerationSpec, serializationLogic, emitNullCheck: typeGenerationSpec.CanBeNull);
return $"foreach ({KeyValuePairTypeRef}<{keyTypeGenerationSpec.TypeRef}, {valueTypeGenerationSpec.TypeRef}> {pairVarName} in {dictionaryValueVarName})\r\n {{\r\n {elementSerializationLogic}\r\n }}";
}

private string GenerateForObject(TypeGenerationSpec typeMetadata)
Expand Down Expand Up @@ -832,7 +843,7 @@ private string GenerateFastPathFuncForObject(TypeGenerationSpec typeGenSpec)

foreach (PropertyGenerationSpec propertyGenSpec in serializableProperties.Values)
{
if (!ShouldIncludePropertyForFastPath(propertyGenSpec, options))
if (propertyGenSpec.IsExtensionData || !ShouldIncludePropertyForFastPath(propertyGenSpec, options))
{
continue;
}
Expand Down Expand Up @@ -892,6 +903,34 @@ private string GenerateFastPathFuncForObject(TypeGenerationSpec typeGenSpec)
sb.Append(WrapSerializationLogicInDefaultCheckIfRequired(serializationLogic, propValue, propertyTypeSpec.TypeRef, defaultCheckType));
}

PropertyGenerationSpec? extDataPropSpec = typeGenSpec.ExtensionDataPropertySpec;
if (extDataPropSpec != null)
{
TypeGenerationSpec extDataPropTypeSpec = extDataPropSpec.TypeGenerationSpec;
string dictionaryValue = $"{ValueVarName}.{extDataPropSpec.ClrName}";

ClassType classType = extDataPropTypeSpec.ClassType;

if (_currentContext.ContextTypeRef.Contains("Extension"))
{
}

if (classType != ClassType.Dictionary)
{
Debug.Assert(extDataPropTypeSpec.Type == _generationSpec.JsonObjectType);
dictionaryValue = $"({IDictionaryTypeRef}<{StringTypeRef}, {JsonNodeTypeRef}>){dictionaryValue}";
}

sb.Append($@"

// Writing extension data members
if ({dictionaryValue} != null)
{{
{GenerateFastPathForDictionaryKeyValuePairs(extDataPropSpec.TypeGenerationSpec, dictionaryValue)}
}}
");
}

// End method logic.
sb.Append($@"

Expand Down
39 changes: 26 additions & 13 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ private sealed class Parser
private const string JsonSerializerContextFullName = "System.Text.Json.Serialization.JsonSerializerContext";
private const string JsonSourceGenerationOptionsAttributeFullName = "System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute";

internal const string JsonSerializableAttributeFullName = "System.Text.Json.Serialization.JsonSerializableAttribute";

private const string DateOnlyFullName = "System.DateOnly";
private const string TimeOnlyFullName = "System.TimeOnly";
private const string IAsyncEnumerableFullName = "System.Collections.Generic.IAsyncEnumerable`1";
Expand Down Expand Up @@ -251,7 +249,7 @@ public Parser(Compilation compilation, in JsonSourceGenerationContext sourceGene
{
Compilation compilation = _compilation;
INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetBestTypeByMetadataName(JsonSerializerContextFullName);
INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonSerializableAttributeFullName);
INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonConstants.JsonSerializableAttributeFullName);
INamedTypeSymbol jsonSourceGenerationOptionsAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonSourceGenerationOptionsAttributeFullName);
INamedTypeSymbol jsonConverterOfTAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonConverterOfTFullName);

Expand Down Expand Up @@ -392,6 +390,7 @@ public Parser(Compilation compilation, in JsonSourceGenerationContext sourceGene
BooleanType = _booleanType,
ByteArrayType = _byteArrayType,
CharType = _charType,
JsonObjectType = _jsonObjectType,
DateTimeType = _dateTimeType,
DateTimeOffsetType = _dateTimeOffsetType,
GuidType = _guidType,
Expand Down Expand Up @@ -705,7 +704,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
TypeGenerationSpec? collectionKeyTypeSpec = null;
TypeGenerationSpec? collectionValueTypeSpec = null;
TypeGenerationSpec? nullableUnderlyingTypeGenSpec = null;
TypeGenerationSpec? dataExtensionPropGenSpec = null;
PropertyGenerationSpec? dataExtPropGenSpec = null;
string? runtimeTypeRef = null;
List<PropertyGenerationSpec>? propGenSpecList = null;
ObjectConstructionStrategy constructionStrategy = default;
Expand Down Expand Up @@ -761,6 +760,13 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
}
}

// Populate info required to support JsonObject as extension data in fast-path.
if (type == _jsonObjectType)
{
collectionKeyTypeSpec = GetOrAddTypeGenerationSpec(_stringType, generationMode);
collectionValueTypeSpec = GetOrAddTypeGenerationSpec(_jsonNodeType, generationMode);
}

if (foundDesignTimeCustomConverter)
{
classType = converterInstatiationLogic != null
Expand Down Expand Up @@ -1029,6 +1035,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
for (Type? currentType = type; currentType != null; currentType = currentType.BaseType)
{
PropertyGenerationSpec spec;
bool seenExtDataAttr = false;

foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags))
{
Expand Down Expand Up @@ -1077,19 +1084,25 @@ void CacheMemberHelper(Location memberLocation)

if (spec.IsExtensionData)
{
if (dataExtensionPropGenSpec != null)
if (seenExtDataAttr)
{
_typeLevelDiagnostics.Add((type, MultipleJsonExtensionDataAttribute, new string[] { type.Name }));
}

Type propType = spec.TypeGenerationSpec.Type;
if (!IsValidDataExtensionPropertyType(propType))
else
{
_sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(DataExtensionPropertyInvalid, memberLocation, new string[] { type.Name, spec.ClrName }));
seenExtDataAttr = true;
Type propType = spec.TypeGenerationSpec.Type;

if (!IsValidDataExtensionPropertyType(propType))
{
_sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(DataExtensionPropertyInvalid, memberLocation, new string[] { type.Name, spec.ClrName }));
}
else
{
dataExtPropGenSpec = spec;
_implicitlyRegisteredTypes.Add(GetOrAddTypeGenerationSpec(propType, generationMode));
}
}

dataExtensionPropGenSpec = GetOrAddTypeGenerationSpec(propType, generationMode);
_implicitlyRegisteredTypes.Add(dataExtensionPropGenSpec);
}

if (!hasInitOnlyProperties && spec.CanUseSetter && spec.IsInitOnlySetter && !PropertyIsConstructorParameter(spec, paramGenSpecArray))
Expand Down Expand Up @@ -1125,7 +1138,7 @@ void CacheMemberHelper(Location memberLocation)
constructionStrategy,
nullableUnderlyingTypeMetadata: nullableUnderlyingTypeGenSpec,
runtimeTypeRef,
dataExtensionPropGenSpec,
dataExtPropGenSpec,
converterInstatiationLogic,
implementsIJsonOnSerialized : implementsIJsonOnSerialized,
implementsIJsonOnSerializing : implementsIJsonOnSerializing,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@

//#define LAUNCH_DEBUGGER
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text.Json.Reflection;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -109,7 +105,7 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType;
string fullName = attributeContainingTypeSymbol.ToDisplayString();

if (fullName == Parser.JsonSerializableAttributeFullName)
if (fullName == JsonConstants.JsonSerializableAttributeFullName)
{
return classDeclarationSyntax;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@
//#define LAUNCH_DEBUGGER
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text.Json.Reflection;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
#if !ROSLYN4_4_OR_GREATER
Expand All @@ -32,7 +27,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
#if !ROSLYN4_4_OR_GREATER
context,
#endif
Parser.JsonSerializableAttributeFullName,
JsonConstants.JsonSerializableAttributeFullName,
(node, _) => node is ClassDeclarationSyntax,
(context, _) => (ClassDeclarationSyntax)context.TargetNode);

Expand All @@ -42,7 +37,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
context.RegisterSourceOutput(compilationAndClasses, (spc, source) => Execute(source.Item1, source.Item2, spc));
}

#pragma warning disable CA1822 // Mark members as static
private void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> contextClasses, SourceProductionContext sourceProductionContext)
#pragma warning restore CA1822 // Mark members as static
{
#if LAUNCH_DEBUGGER
if (!Diagnostics.Debugger.IsAttached)
Expand Down
4 changes: 1 addition & 3 deletions src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Reflection;
using Microsoft.CodeAnalysis;

namespace System.Text.Json.SourceGeneration
{
Expand All @@ -19,6 +16,7 @@ internal sealed class SourceGenerationSpec
public Type BooleanType { get; init; }
public Type ByteArrayType { get; init; }
public Type CharType { get; init; }
public Type JsonObjectType { get; init; }
public Type DateTimeType { private get; init; }
public Type DateTimeOffsetType { private get; init; }
public Type GuidType { private get; init; }
Expand Down
11 changes: 3 additions & 8 deletions src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ internal sealed class TypeGenerationSpec
/// </summary>
public string? RuntimeTypeRef { get; private set; }

public TypeGenerationSpec? ExtensionDataPropertyTypeSpec { get; private set; }
public PropertyGenerationSpec? ExtensionDataPropertySpec { get; private set; }

public string? ConverterInstantiationLogic { get; private set; }

Expand Down Expand Up @@ -126,7 +126,7 @@ public void Initialize(
ObjectConstructionStrategy constructionStrategy,
TypeGenerationSpec? nullableUnderlyingTypeMetadata,
string? runtimeTypeRef,
TypeGenerationSpec? extensionDataPropertyTypeSpec,
PropertyGenerationSpec? extDataPropSpec,
string? converterInstantiationLogic,
bool implementsIJsonOnSerialized,
bool implementsIJsonOnSerializing,
Expand All @@ -152,7 +152,7 @@ public void Initialize(
ConstructionStrategy = constructionStrategy;
NullableUnderlyingTypeMetadata = nullableUnderlyingTypeMetadata;
RuntimeTypeRef = runtimeTypeRef;
ExtensionDataPropertyTypeSpec = extensionDataPropertyTypeSpec;
ExtensionDataPropertySpec = extDataPropSpec;
ConverterInstantiationLogic = converterInstantiationLogic;
ImplementsIJsonOnSerialized = implementsIJsonOnSerialized;
ImplementsIJsonOnSerializing = implementsIJsonOnSerializing;
Expand Down Expand Up @@ -253,11 +253,6 @@ private bool FastPathIsSupported()

if (ClassType == ClassType.Object)
{
if (ExtensionDataPropertyTypeSpec != null)
{
return false;
}

foreach (PropertyGenerationSpec property in PropertyGenSpecList)
{
if (property.TypeGenerationSpec.Type.IsObjectType() ||
Expand Down