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

Improving STJ source generator support for record types #68064

Merged
merged 5 commits into from
Apr 19, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -1028,7 +1028,7 @@ void CacheMemberHelper(Location memberLocation)
_implicitlyRegisteredTypes.Add(dataExtensionPropGenSpec);
}

if (!hasInitOnlyProperties && spec.CanUseSetter && spec.IsInitOnlySetter)
if (!hasInitOnlyProperties && spec.CanUseSetter && spec.IsInitOnlySetter && !PropertyIsConstructorParameter(spec, paramGenSpecArray))
{
_sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(InitOnlyPropertyDeserializationNotSupported, memberLocation, new string[] { type.Name }));
hasInitOnlyProperties = true;
Expand Down Expand Up @@ -1119,6 +1119,9 @@ private void CacheMember(
}
}

private static bool PropertyIsConstructorParameter(PropertyGenerationSpec propSpec, ParameterGenerationSpec[]? paramGenSpecArray)
=> paramGenSpecArray != null && paramGenSpecArray.Any(paramSpec => propSpec.ClrName.Equals(paramSpec.ParameterInfo.Name, StringComparison.OrdinalIgnoreCase));

private static bool PropertyIsOverridenAndIgnored(
string currentMemberName,
Type currentMemberType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public interface ITestContext
public JsonTypeInfo<MyTypeWithPropertyOrdering> MyTypeWithPropertyOrdering { get; }
public JsonTypeInfo<MyIntermediateType> MyIntermediateType { get; }
public JsonTypeInfo<HighLowTempsImmutable> HighLowTempsImmutable { get; }
public JsonTypeInfo<HighLowTempsRecord> HighLowTempsRecord { get; }
public JsonTypeInfo<RealWorldContextTests.MyNestedClass> MyNestedClass { get; }
public JsonTypeInfo<RealWorldContextTests.MyNestedClass.MyNestedNestedClass> MyNestedNestedClass { get; }
public JsonTypeInfo<object[]> ObjectArray { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(MyTypeWithPropertyOrdering))]
[JsonSerializable(typeof(MyIntermediateType))]
[JsonSerializable(typeof(HighLowTempsImmutable))]
[JsonSerializable(typeof(HighLowTempsRecord))]
[JsonSerializable(typeof(byte[]))]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass))]
Expand Down Expand Up @@ -70,6 +71,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(MetadataAndSerializationContext.Default.MyTypeWithPropertyOrdering.SerializeHandler);
Assert.NotNull(MetadataAndSerializationContext.Default.MyIntermediateType.SerializeHandler);
Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTempsImmutable.SerializeHandler);
Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTempsRecord.SerializeHandler);
Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedClass.SerializeHandler);
Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedNestedClass.SerializeHandler);
Assert.Null(MetadataAndSerializationContext.Default.ObjectArray.SerializeHandler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(MyTypeWithPropertyOrdering), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(HighLowTempsRecord), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)]
Expand Down Expand Up @@ -67,6 +68,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyType2.SerializeHandler);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyIntermediateType.SerializeHandler);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.HighLowTempsImmutable.SerializeHandler);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.HighLowTempsRecord.SerializeHandler);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyNestedClass.SerializeHandler);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyNestedNestedClass.SerializeHandler);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.ObjectArray.SerializeHandler);
Expand Down Expand Up @@ -109,6 +111,7 @@ public override void EnsureFastPathGeneratedAsExpected()
[JsonSerializable(typeof(MyTypeWithPropertyOrdering))]
[JsonSerializable(typeof(MyIntermediateType))]
[JsonSerializable(typeof(HighLowTempsImmutable))]
[JsonSerializable(typeof(HighLowTempsRecord))]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass))]
[JsonSerializable(typeof(object[]))]
Expand Down Expand Up @@ -178,6 +181,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.Null(MetadataContext.Default.MyTypeWithPropertyOrdering.SerializeHandler);
Assert.Null(MetadataContext.Default.MyIntermediateType.SerializeHandler);
Assert.Null(MetadataContext.Default.HighLowTempsImmutable.SerializeHandler);
Assert.Null(MetadataContext.Default.HighLowTempsRecord.SerializeHandler);
Assert.Null(MetadataContext.Default.MyNestedClass.SerializeHandler);
Assert.Null(MetadataContext.Default.MyNestedNestedClass.SerializeHandler);
Assert.Null(MetadataContext.Default.ObjectArray.SerializeHandler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(MyTypeWithPropertyOrdering), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(HighLowTempsRecord), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)]
Expand Down Expand Up @@ -69,6 +70,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(MixedModeContext.Default.MyTypeWithPropertyOrdering.SerializeHandler);
Assert.NotNull(MixedModeContext.Default.MyIntermediateType.SerializeHandler);
Assert.Null(MixedModeContext.Default.HighLowTempsImmutable.SerializeHandler);
Assert.Null(MixedModeContext.Default.HighLowTempsRecord.SerializeHandler);
Assert.NotNull(MixedModeContext.Default.MyNestedClass.SerializeHandler);
Assert.NotNull(MixedModeContext.Default.MyNestedNestedClass.SerializeHandler);
Assert.Null(MixedModeContext.Default.ObjectArray.SerializeHandler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,18 @@ public virtual void ParameterizedConstructor()
Assert.Equal(2, obj.Low);
}

[Fact]
public virtual void PositionalRecord()
{
string json = JsonSerializer.Serialize(new HighLowTempsRecord(1, 2), DefaultContext.HighLowTempsRecord);
Assert.Contains(@"""High"":1", json);
Assert.Contains(@"""Low"":2", json);

HighLowTempsRecord obj = JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsRecord);
Assert.Equal(1, obj.High);
Assert.Equal(2, obj.Low);
}

[Fact]
public virtual void EnumAndNullable()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(MyTypeWithPropertyOrdering))]
[JsonSerializable(typeof(MyIntermediateType))]
[JsonSerializable(typeof(HighLowTempsImmutable))]
[JsonSerializable(typeof(HighLowTempsRecord))]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass))]
[JsonSerializable(typeof(object[]))]
Expand Down Expand Up @@ -63,6 +64,7 @@ internal partial class SerializationContext : JsonSerializerContext, ITestContex
[JsonSerializable(typeof(MyTypeWithPropertyOrdering), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(HighLowTempsRecord), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)]
Expand Down Expand Up @@ -105,6 +107,7 @@ internal partial class SerializationWithPerTypeAttributeContext : JsonSerializer
[JsonSerializable(typeof(MyTypeWithPropertyOrdering), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(HighLowTempsRecord), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)]
Expand Down Expand Up @@ -158,6 +161,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(SerializationContext.Default.MyTypeWithPropertyOrdering.SerializeHandler);
Assert.NotNull(SerializationContext.Default.MyIntermediateType.SerializeHandler);
Assert.NotNull(SerializationContext.Default.HighLowTempsImmutable.SerializeHandler);
Assert.NotNull(SerializationContext.Default.HighLowTempsRecord.SerializeHandler);
Assert.NotNull(SerializationContext.Default.MyNestedClass.SerializeHandler);
Assert.NotNull(SerializationContext.Default.MyNestedNestedClass.SerializeHandler);
Assert.Null(SerializationContext.Default.ObjectArray.SerializeHandler);
Expand Down Expand Up @@ -436,6 +440,16 @@ public override void ParameterizedConstructor()
JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable), typeof(HighLowTempsImmutable));
}

[Fact]
public override void PositionalRecord()
{
string json = JsonSerializer.Serialize(new HighLowTempsRecord(1, 2), DefaultContext.HighLowTempsRecord);
Assert.Contains(@"""High"":1", json);
Assert.Contains(@"""Low"":2", json);

JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsRecord), typeof(HighLowTempsRecord));
}

[Fact]
public void OnSerializeCallbacks()
{
Expand Down Expand Up @@ -482,6 +496,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyType2.SerializeHandler);
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyIntermediateType.SerializeHandler);
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.HighLowTempsImmutable.SerializeHandler);
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.HighLowTempsRecord.SerializeHandler);
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyNestedClass.SerializeHandler);
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyNestedNestedClass.SerializeHandler);
Assert.Null(SerializationWithPerTypeAttributeContext.Default.ObjectArray.SerializeHandler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ public class HighLowTempsImmutable
public HighLowTempsImmutable(int high, int low) => (High, Low) = (high, low);
}

public record HighLowTempsRecord(int High, int Low);

public class EmptyPoco
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ public class Location

return CreateCompilation(source);
}

public static Compilation CreateCompilationWithInitOnlyProperties()
{
string source = @"
Expand Down Expand Up @@ -305,6 +305,91 @@ public partial class MyJsonContext : JsonSerializerContext
return CreateCompilation(source);
}

public static Compilation CreateCompilationWithConstructorInitOnlyProperties()
{
string source = @"
using System;
using System.Text.Json.Serialization;

namespace HelloWorld
{
public class MyClass
{
public MyClass(int value)
{
Value = value;
}

public int Value { get; init; }
}

[JsonSerializable(typeof(MyClass))]
public partial class MyJsonContext : JsonSerializerContext
{
}
}";

return CreateCompilation(source);
}

public static Compilation CreateCompilationWithMixedInitOnlyProperties()
{
string source = @"
using System;
using System.Text.Json.Serialization;

namespace HelloWorld
{
public class MyClass
{
public MyClass(int value)
{
Value = value;
}

public int Value { get; init; }
public string Orphaned { get; init; }
}

[JsonSerializable(typeof(MyClass))]
public partial class MyJsonContext : JsonSerializerContext
{
}
}";

return CreateCompilation(source);
}

public static Compilation CreateCompilationWithRecordPositionalParameters()
{
string source = @"
using System;
using System.Text.Json.Serialization;

namespace HelloWorld
{
public record Location
(
int Id,
string Address1,
string Address2,
string City,
string State,
string PostalCode,
string Name,
string PhoneNumber,
string Country
);

[JsonSerializable(typeof(Location))]
public partial class MyJsonContext : JsonSerializerContext
{
}
}";

return CreateCompilation(source);
}

public static Compilation CreateCompilationWithInaccessibleJsonIncludeProperties()
{
string source = @"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,52 @@ public void WarnOnClassesWithInitOnlyProperties()
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>());
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/58770", TestPlatforms.Browser)]
public void DoNotWarnOnClassesWithConstructorInitOnlyProperties()
{
Compilation compilation = CompilationHelper.CreateCompilationWithConstructorInitOnlyProperties();
JsonSourceGenerator generator = new JsonSourceGenerator();
CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator);

CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>());
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>());
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>());
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/58770", TestPlatforms.Browser)]
public void WarnOnClassesWithMixedInitOnlyProperties()
{
Compilation compilation = CompilationHelper.CreateCompilationWithMixedInitOnlyProperties();
JsonSourceGenerator generator = new JsonSourceGenerator();
CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator);

Location location = compilation.GetSymbolsWithName("Orphaned").First().Locations[0];

(Location, string)[] expectedWarningDiagnostics = new (Location, string)[]
{
(location, "The type 'MyClass' defines init-only properties, deserialization of which is currently not supported in source generation mode.")
};

CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>());
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, expectedWarningDiagnostics);
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>());
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/58770", TestPlatforms.Browser)]
public void DoNotWarnOnRecordsWithInitOnlyPositionalParameters()
{
Compilation compilation = CompilationHelper.CreateCompilationWithRecordPositionalParameters();
JsonSourceGenerator generator = new JsonSourceGenerator();
CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator);

CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>());
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>());
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>());
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)]
public void WarnOnClassesWithInaccessibleJsonIncludeProperties()
Expand Down