Skip to content

Commit 1c735ed

Browse files
Add sourcegen support for required & init-only properties. (#79828)
* Add sourcegen support for required & init-only properties. * Add test coverage for required fields & remove a few async void methods. * Remove commented out code. * Tweak JsonSerializerOptions resolution logic in wrapper implementation
1 parent 2541b7b commit 1c735ed

File tree

42 files changed

+1079
-856
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1079
-856
lines changed

src/libraries/System.Text.Json/System.Text.Json.sln

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "System.Text.Json.FSharp.Tes
4747
EndProject
4848
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.TestLibrary.Roslyn3.11", "tests\System.Text.Json.SourceGeneration.TestLibrary\System.Text.Json.TestLibrary.Roslyn3.11.csproj", "{5C0CE30B-DD4A-4F7A-87C0-5243F0C86885}"
4949
EndProject
50-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.TestLibrary.Roslyn4.0", "tests\System.Text.Json.SourceGeneration.TestLibrary\System.Text.Json.TestLibrary.Roslyn4.0.csproj", "{FCA21178-0411-45D6-B597-B7BE145CEE33}"
50+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.TestLibrary.Roslyn4.4", "tests\System.Text.Json.SourceGeneration.TestLibrary\System.Text.Json.TestLibrary.Roslyn4.4.csproj", "{FCA21178-0411-45D6-B597-B7BE145CEE33}"
5151
EndProject
5252
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.SourceGeneration.Roslyn3.11.Tests", "tests\System.Text.Json.SourceGeneration.Tests\System.Text.Json.SourceGeneration.Roslyn3.11.Tests.csproj", "{66AD4B7E-CF15-4A8F-8BF8-7E1BC6176D07}"
5353
EndProject
54-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.SourceGeneration.Roslyn4.0.Tests", "tests\System.Text.Json.SourceGeneration.Tests\System.Text.Json.SourceGeneration.Roslyn4.0.Tests.csproj", "{33599A6C-F340-4E1B-9B4D-CB8946C22140}"
54+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.SourceGeneration.Roslyn4.4.Tests", "tests\System.Text.Json.SourceGeneration.Tests\System.Text.Json.SourceGeneration.Roslyn4.4.Tests.csproj", "{33599A6C-F340-4E1B-9B4D-CB8946C22140}"
5555
EndProject
5656
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.SourceGeneration.Roslyn3.11.Unit.Tests", "tests\System.Text.Json.SourceGeneration.Unit.Tests\System.Text.Json.SourceGeneration.Roslyn3.11.Unit.Tests.csproj", "{256A4653-4287-44B3-BDEF-67FC1522ED2F}"
5757
EndProject
58-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.SourceGeneration.Roslyn4.0.Unit.Tests", "tests\System.Text.Json.SourceGeneration.Unit.Tests\System.Text.Json.SourceGeneration.Roslyn4.0.Unit.Tests.csproj", "{F6A18EB5-A8CC-4A39-9E85-5FA226019C3D}"
58+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.SourceGeneration.Roslyn4.4.Unit.Tests", "tests\System.Text.Json.SourceGeneration.Unit.Tests\System.Text.Json.SourceGeneration.Roslyn4.4.Unit.Tests.csproj", "{F6A18EB5-A8CC-4A39-9E85-5FA226019C3D}"
5959
EndProject
6060
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.Tests", "tests\System.Text.Json.Tests\System.Text.Json.Tests.csproj", "{A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}"
6161
EndProject

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.ExceptionMessages.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ private static class ExceptionMessages
2222
public const string IncompatibleConverterType =
2323
"The converter '{0}' is not compatible with the type '{1}'.";
2424

25-
public const string InitOnlyPropertyDeserializationNotSupported =
26-
"Deserialization of init-only properties is currently not supported in source generation mode.";
25+
public const string InitOnlyPropertySetterNotSupported =
26+
"Setting init-only properties is not supported in source generation mode.";
2727

2828
public const string InvalidJsonConverterFactoryOutput =
2929
"The converter '{0}' cannot return null or a JsonConverterFactory instance.";

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.Diagnostics;
66
using System.Globalization;
7+
using System.Linq;
78
using System.Reflection;
89
using System.Text.Json.Reflection;
910
using System.Text.Json.Serialization;
@@ -699,7 +700,7 @@ private static string GeneratePropMetadataInitFunc(TypeGenerationSpec typeGenera
699700
{
700701
{ DefaultIgnoreCondition: JsonIgnoreCondition.Always } => "null",
701702
{ CanUseSetter: true, IsInitOnlySetter: true }
702-
=> @$"static (obj, value) => throw new {InvalidOperationExceptionTypeRef}(""{ExceptionMessages.InitOnlyPropertyDeserializationNotSupported}"")",
703+
=> @$"static (obj, value) => throw new {InvalidOperationExceptionTypeRef}(""{ExceptionMessages.InitOnlyPropertySetterNotSupported}"")",
703704
{ CanUseSetter: true } when typeGenerationSpec.IsValueType
704705
=> $@"static (obj, value) => {UnsafeTypeRef}.Unbox<{declaringTypeCompilableName}>(obj).{nameSpecifiedInSourceCode} = value!",
705706
{ CanUseSetter: true }
@@ -743,7 +744,8 @@ private static string GeneratePropMetadataInitFunc(TypeGenerationSpec typeGenera
743744
744745
{JsonPropertyInfoTypeRef} {propertyInfoVarName} = {JsonMetadataServicesTypeRef}.CreatePropertyInfo<{memberTypeCompilableName}>({OptionsLocalVariableName}, {infoVarName});");
745746

746-
if (memberMetadata.IsRequired)
747+
if (memberMetadata.HasJsonRequiredAttribute ||
748+
(memberMetadata.IsRequired && !typeGenerationSpec.ConstructorSetsRequiredParameters))
747749
{
748750
sb.Append($@"
749751
{propertyInfoVarName}.IsRequired = true;");
@@ -772,7 +774,8 @@ string GenerateCtorParamMetadataInitFunc(TypeGenerationSpec typeGenerationSpec)
772774
Debug.Assert(typeGenerationSpec.CtorParamGenSpecArray != null);
773775

774776
ParameterGenerationSpec[] parameters = typeGenerationSpec.CtorParamGenSpecArray;
775-
int paramCount = parameters.Length;
777+
List<PropertyInitializerGenerationSpec>? propertyInitializers = typeGenerationSpec.PropertyInitializerSpecList;
778+
int paramCount = parameters.Length + (propertyInitializers?.Count(propInit => !propInit.MatchesConstructorParameter) ?? 0);
776779
Debug.Assert(paramCount > 0);
777780

778781
StringBuilder sb = new($@"
@@ -782,10 +785,9 @@ string GenerateCtorParamMetadataInitFunc(TypeGenerationSpec typeGenerationSpec)
782785
{JsonParameterInfoValuesTypeRef}[] {parametersVarName} = new {JsonParameterInfoValuesTypeRef}[{paramCount}];
783786
{JsonParameterInfoValuesTypeRef} info;
784787
");
785-
786-
for (int i = 0; i < paramCount; i++)
788+
foreach (ParameterGenerationSpec spec in parameters)
787789
{
788-
ParameterInfo reflectionInfo = parameters[i].ParameterInfo;
790+
ParameterInfo reflectionInfo = spec.ParameterInfo;
789791
Type parameterType = reflectionInfo.ParameterType;
790792
string parameterTypeRef = parameterType.GetCompilableName();
791793

@@ -801,8 +803,31 @@ string GenerateCtorParamMetadataInitFunc(TypeGenerationSpec typeGenerationSpec)
801803
HasDefaultValue = {FormatBool(reflectionInfo.HasDefaultValue)},
802804
DefaultValue = {defaultValueAsStr}
803805
}};
804-
{parametersVarName}[{i}] = {InfoVarName};
806+
{parametersVarName}[{spec.ParameterIndex}] = {InfoVarName};
807+
");
808+
}
809+
810+
if (propertyInitializers != null)
811+
{
812+
Debug.Assert(propertyInitializers.Count > 0);
813+
814+
foreach (PropertyInitializerGenerationSpec spec in propertyInitializers)
815+
{
816+
if (spec.MatchesConstructorParameter)
817+
continue;
818+
819+
sb.Append(@$"
820+
{InfoVarName} = new()
821+
{{
822+
Name = ""{spec.Property.JsonPropertyName ?? spec.Property.ClrName}"",
823+
ParameterType = typeof({spec.Property.TypeGenerationSpec.TypeRef}),
824+
Position = {spec.ParameterIndex},
825+
HasDefaultValue = false,
826+
DefaultValue = default({spec.Property.TypeGenerationSpec.TypeRef}),
827+
}};
828+
{parametersVarName}[{spec.ParameterIndex}] = {InfoVarName};
805829
");
830+
}
806831
}
807832

808833
sb.Append(@$"
@@ -959,27 +984,43 @@ private static bool ShouldIncludePropertyForFastPath(PropertyGenerationSpec prop
959984
private static string GetParameterizedCtorInvocationFunc(TypeGenerationSpec typeGenerationSpec)
960985
{
961986
Debug.Assert(typeGenerationSpec.CtorParamGenSpecArray != null);
962-
963987
ParameterGenerationSpec[] parameters = typeGenerationSpec.CtorParamGenSpecArray;
964-
int paramCount = parameters.Length;
965-
Debug.Assert(paramCount != 0);
988+
List<PropertyInitializerGenerationSpec>? propertyInitializers = typeGenerationSpec.PropertyInitializerSpecList;
966989

967990
const string ArgsVarName = "args";
968-
int lastIndex = paramCount - 1;
969991

970992
StringBuilder sb = new($"static ({ArgsVarName}) => new {typeGenerationSpec.TypeRef}(");
971993

972-
for (int i = 0; i < lastIndex; i++)
994+
if (parameters.Length > 0)
973995
{
974-
sb.Append($"{GetParamUnboxing(parameters[i], i)}, ");
996+
foreach (ParameterGenerationSpec param in parameters)
997+
{
998+
int index = param.ParameterIndex;
999+
sb.Append($"{GetParamUnboxing(param.ParameterInfo.ParameterType, index)}, ");
1000+
}
1001+
1002+
sb.Length -= 2; // delete the last ", " token
9751003
}
9761004

977-
sb.Append($"{GetParamUnboxing(parameters[lastIndex], lastIndex)})");
1005+
sb.Append(')');
1006+
1007+
if (propertyInitializers != null)
1008+
{
1009+
Debug.Assert(propertyInitializers.Count > 0);
1010+
sb.Append("{ ");
1011+
foreach (PropertyInitializerGenerationSpec property in propertyInitializers)
1012+
{
1013+
sb.Append($"{property.Property.ClrName} = {GetParamUnboxing(property.Property.TypeGenerationSpec.Type, property.ParameterIndex)}, ");
1014+
}
1015+
1016+
sb.Length -= 2; // delete the last ", " token
1017+
sb.Append(" }");
1018+
}
9781019

9791020
return sb.ToString();
9801021

981-
static string GetParamUnboxing(ParameterGenerationSpec spec, int index)
982-
=> $"({spec.ParameterInfo.ParameterType.GetCompilableName()}){ArgsVarName}[{index}]";
1022+
static string GetParamUnboxing(Type type, int index)
1023+
=> $"({type.GetCompilableName()}){ArgsVarName}[{index}]";
9831024
}
9841025

9851026
private string? GetWriterMethod(Type type)

0 commit comments

Comments
 (0)