Skip to content
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
21 changes: 17 additions & 4 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,17 @@ private void GeneratePropMetadataInitFunc(SourceWriter writer, string propInitMe
PropertyGenerationSpec property = properties[i];
string propertyName = property.NameSpecifiedInSourceCode;
string declaringTypeFQN = property.DeclaringType.FullyQualifiedName;
string propertyTypeFQN = property.PropertyType.FullyQualifiedName;

// If the property is ignored and its type is not used anywhere else in the type graph,
// emit a JsonPropertyInfo of type 'object' to avoid unnecessarily referencing the type.
// STJ requires that all ignored properties be included so that it can perform
// necessary run-time validations using configuration not known at compile time
// such as the property naming policy and case sensitivity.
bool isIgnoredPropertyOfUnusedType =
property.DefaultIgnoreCondition is JsonIgnoreCondition.Always &&
!_typeIndex.ContainsKey(property.PropertyType);

string propertyTypeFQN = isIgnoredPropertyOfUnusedType ? "object" : property.PropertyType.FullyQualifiedName;

string getterValue = property switch
{
Expand Down Expand Up @@ -653,9 +663,12 @@ private void GeneratePropMetadataInitFunc(SourceWriter writer, string propInitMe
: $"({JsonConverterTypeRef}<{propertyTypeFQN}>){ExpandConverterMethodName}(typeof({propertyTypeFQN}), new {converterFQN}(), {OptionsLocalVariableName})";
}

string attributeProviderFactoryExpr = property.IsProperty
? $"typeof({property.DeclaringType.FullyQualifiedName}).GetProperty({FormatStringLiteral(property.MemberName)}, {InstanceMemberBindingFlagsVariableName}, null, typeof({property.PropertyType.FullyQualifiedName}), {EmptyTypeArray}, null)"
: $"typeof({property.DeclaringType.FullyQualifiedName}).GetField({FormatStringLiteral(property.MemberName)}, {InstanceMemberBindingFlagsVariableName})";
string attributeProviderFactoryExpr = property switch
{
_ when isIgnoredPropertyOfUnusedType => "null",
{ IsProperty: true } => $"typeof({property.DeclaringType.FullyQualifiedName}).GetProperty({FormatStringLiteral(property.MemberName)}, {InstanceMemberBindingFlagsVariableName}, null, typeof({propertyTypeFQN}), {EmptyTypeArray}, null)",
_ => $"typeof({property.DeclaringType.FullyQualifiedName}).GetField({FormatStringLiteral(property.MemberName)}, {InstanceMemberBindingFlagsVariableName})",
};

writer.WriteLine($$"""
var {{InfoVarName}}{{i}} = new {{JsonPropertyInfoValuesTypeRef}}<{{propertyTypeFQN}}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,7 @@ public void NumberHandlingHonoredOnPoco()
[InlineData(MemberTypes.Field, nameof(PocoWithMixedVisibilityMembers.FieldWithCustomName), "customField")]
[InlineData(MemberTypes.Property, nameof(PocoWithMixedVisibilityMembers.BaseProperty))]
[InlineData(MemberTypes.Property, nameof(PocoWithMixedVisibilityMembers.ShadowProperty))]
[InlineData(MemberTypes.Property, nameof(PocoWithMixedVisibilityMembers.ExperimentalProperty))]
public void JsonPropertyInfo_PopulatesAttributeProvider(MemberTypes memberType, string propertyName, string? jsonPropertyName = null)
{
if (DefaultContext.JsonSourceGenerationMode is JsonSourceGenerationMode.Serialization)
Expand All @@ -1134,7 +1135,17 @@ public void JsonPropertyInfo_PopulatesAttributeProvider(MemberTypes memberType,
JsonPropertyInfo prop = typeInfo.Properties.FirstOrDefault(prop => prop.Name == name);
Assert.NotNull(prop);

MemberInfo memberInfo = Assert.IsAssignableFrom<MemberInfo>(prop.AttributeProvider);
MemberInfo memberInfo;
if (prop.AttributeProvider is null)
{
Assert.Equal(typeof(object), prop.PropertyType);
memberInfo = typeof(PocoWithMixedVisibilityMembers).GetMember(propertyName, memberType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Single();
Type actualPropertyType = memberInfo is PropertyInfo pInfo ? pInfo.PropertyType : ((FieldInfo)memberInfo).FieldType;
Assert.False(typeInfo.Options.TryGetTypeInfo(actualPropertyType, out _));
return;
}

memberInfo = Assert.IsAssignableFrom<MemberInfo>(prop.AttributeProvider);
string? actualJsonPropertyName = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name;

Assert.True(memberInfo.DeclaringType.IsAssignableFrom(typeInfo.Type));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Primitives;

Expand Down Expand Up @@ -332,8 +333,18 @@ public class PocoWithMixedVisibilityMembers : PocoWithMixedVisibilityMembersBase
public string FieldWithCustomName;

public new int ShadowProperty { get; set; }

#pragma warning disable EXP0001
[JsonIgnore]
public ExperimentalClass ExperimentalProperty { get; set; }
#pragma warning restore EXP0001
}

#if NET
[Experimental("EXP0001")]
#endif
public class ExperimentalClass;

public sealed class ClassWithConflictingIgnoredProperties
{
[JsonIgnore]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using Xunit;
using Xunit.Abstractions;

namespace System.Text.Json.SourceGeneration.UnitTests
{
Expand Down Expand Up @@ -133,7 +134,10 @@ public static CSharpGeneratorDriver CreateJsonSourceGeneratorDriver(Compilation
#endif
}

public static JsonSourceGeneratorResult RunJsonSourceGenerator(Compilation compilation, bool disableDiagnosticValidation = false)
public static JsonSourceGeneratorResult RunJsonSourceGenerator(
Compilation compilation,
bool disableDiagnosticValidation = false,
ITestOutputHelper? logger = null)
{
var generatedSpecs = ImmutableArray<ContextGenerationSpec>.Empty;
var generator = new JsonSourceGenerator
Expand All @@ -144,6 +148,19 @@ public static JsonSourceGeneratorResult RunJsonSourceGenerator(Compilation compi
CSharpGeneratorDriver driver = CreateJsonSourceGeneratorDriver(compilation, generator);
driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outCompilation, out ImmutableArray<Diagnostic> diagnostics);

if (logger is not null)
{
foreach (Diagnostic diagnostic in outCompilation.GetDiagnostics().Concat(diagnostics))
{
logger.WriteLine(diagnostic.ToString());
}

foreach (var tree in outCompilation.SyntaxTrees)
{
LogGeneratedCode(tree, logger);
}
}

if (!disableDiagnosticValidation)
{
outCompilation.GetDiagnostics().AssertMaxSeverity(DiagnosticSeverity.Info);
Expand Down Expand Up @@ -831,6 +848,59 @@ internal static void AssertMaxSeverity(this IEnumerable<Diagnostic> diagnostics,
{
Assert.DoesNotContain(diagnostics, diagnostic => diagnostic.Severity > maxSeverity);
}

private static void LogGeneratedCode(SyntaxTree tree, ITestOutputHelper logger)
{
logger.WriteLine(FileSeparator);
logger.WriteLine($"{tree.FilePath} content:");
logger.WriteLine(FileSeparator);
using NumberedSourceFileWriter lineWriter = new(logger);
tree.GetRoot().WriteTo(lineWriter);
lineWriter.WriteLine(string.Empty);
}

private static readonly string FileSeparator = new string('=', 140);

private sealed class NumberedSourceFileWriter : TextWriter
{
private readonly ITestOutputHelper _logger;
private readonly StringBuilder _lineBuilder = new StringBuilder();
private int _lineNumber;

internal NumberedSourceFileWriter(ITestOutputHelper logger)
{
_logger = logger;
}

public override Encoding Encoding => Encoding.Unicode;

public override void WriteLine(string? value)
{
_logger.WriteLine($"{++_lineNumber,6}: {_lineBuilder}{value}");
_lineBuilder.Clear();
}

public override void Write(string? value)
{
if (value is null)
{
return;
}

if (value.EndsWith("\r\n", StringComparison.Ordinal))
{
WriteLine(value.Substring(0, value.Length - 2));
}
else if (value.EndsWith("\n", StringComparison.Ordinal))
{
WriteLine(value.Substring(0, value.Length - 1));
}
else
{
_lineBuilder.Append(value);
}
}
}
}

public record struct DiagnosticData(
Expand Down
Loading
Loading