Skip to content

Commit

Permalink
Support JsonExtensionData in src-gen fast-path
Browse files Browse the repository at this point in the history
  • Loading branch information
layomia committed Nov 4, 2022
1 parent f5c67ac commit 0ba4482
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 48 deletions.
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

0 comments on commit 0ba4482

Please sign in to comment.