Skip to content

Commit ce51262

Browse files
Fix SG nullability annotations for required and init properties. (#108013)
Co-authored-by: Eirik Tsarpalis <[email protected]>
1 parent 31b8a18 commit ce51262

File tree

6 files changed

+41
-1
lines changed

6 files changed

+41
-1
lines changed

src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ public static bool IsNullable(this IParameterSymbol parameter)
323323
{
324324
// Because System.Text.Json cannot distinguish between nullable and non-nullable type parameters,
325325
// (e.g. the same metadata is being used for both KeyValuePair<string, string?> and KeyValuePair<string, string>),
326-
// we derive nullability annotations from the original definition of the field and not instation.
326+
// we derive nullability annotations from the original definition of the field and not its instantiation.
327327
// This preserves compatibility with the capabilities of the reflection-based NullabilityInfo reader.
328328
parameter = parameter.OriginalDefinition;
329329
return !IsInputTypeNonNullable(parameter, parameter.Type);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@ private static void GenerateCtorParamMetadataInitFunc(SourceWriter writer, strin
754754
Name = {{FormatStringLiteral(spec.Name)}},
755755
ParameterType = typeof({{spec.ParameterType.FullyQualifiedName}}),
756756
Position = {{spec.ParameterIndex}},
757+
IsNullable = {{FormatBoolLiteral(spec.IsNullable)}},
757758
IsMemberInitializer = true,
758759
},
759760
""");

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,7 @@ private void ProcessMember(
15341534
ParameterType = property.PropertyType,
15351535
MatchesConstructorParameter = matchingConstructorParameter is not null,
15361536
ParameterIndex = matchingConstructorParameter?.ParameterIndex ?? paramCount++,
1537+
IsNullable = property.PropertyType.CanBeNull && !property.IsSetterNonNullableAnnotation,
15371538
};
15381539

15391540
(propertyInitializers ??= new()).Add(propertyInitializer);

src/libraries/System.Text.Json/gen/Model/PropertyInitializerGenerationSpec.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,7 @@ public sealed record PropertyInitializerGenerationSpec
3131
public required int ParameterIndex { get; init; }
3232

3333
public required bool MatchesConstructorParameter { get; init; }
34+
35+
public required bool IsNullable { get; init; }
3436
}
3537
}

src/libraries/System.Text.Json/tests/Common/NullableAnnotationsTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public static IEnumerable<object[]> GetTypesWithNonNullablePropertyGetter()
7272
yield return Wrap(typeof(NotNullableSpecialTypePropertiesClass), nameof(NotNullableSpecialTypePropertiesClass.JsonDocument));
7373
yield return Wrap(typeof(NullableObliviousConstructorParameter), nameof(NullableObliviousConstructorParameter.Property));
7474
yield return Wrap(typeof(NotNullGenericPropertyClass<string>), nameof(NotNullGenericPropertyClass<string>.Property));
75+
yield return Wrap(typeof(ClassWithNonNullableInitProperty), nameof(ClassWithNonNullableInitProperty.Property));
76+
yield return Wrap(typeof(ClassWithNonNullableInitProperty), nameof(ClassWithNonNullableRequiredProperty.Property));
7577

7678
static object[] Wrap(Type type, string propertyName) => [type, propertyName];
7779
}
@@ -125,6 +127,8 @@ public static IEnumerable<object[]> GetTypesWithNullablePropertyGetter()
125127
yield return Wrap(typeof(NullableObliviousPropertyClass), nameof(NullableObliviousPropertyClass.Property));
126128
yield return Wrap(typeof(GenericPropertyClass<string>), nameof(GenericPropertyClass<string>.Property));
127129
yield return Wrap(typeof(NullableGenericPropertyClass<string>), nameof(NullableGenericPropertyClass<string>.Property));
130+
yield return Wrap(typeof(ClassWithNullableInitProperty), nameof(ClassWithNullableInitProperty.Property));
131+
yield return Wrap(typeof(ClassWithNullableInitProperty), nameof(ClassWithNullableRequiredProperty.Property));
128132

129133
static object[] Wrap(Type type, string propertyName) => [type, propertyName];
130134
}
@@ -191,6 +195,8 @@ public static IEnumerable<object[]> GetTypesWithNonNullablePropertySetter()
191195
yield return Wrap(typeof(DisallowNullConstructorParameter), nameof(DisallowNullConstructorParameter.Property));
192196
yield return Wrap(typeof(DisallowNullConstructorParameter<string>), nameof(DisallowNullConstructorParameter<string>.Property));
193197
yield return Wrap(typeof(NotNullGenericConstructorParameter<string>), nameof(NotNullGenericConstructorParameter<string>.Property));
198+
yield return Wrap(typeof(ClassWithNonNullableInitProperty), nameof(ClassWithNonNullableInitProperty.Property));
199+
yield return Wrap(typeof(ClassWithNonNullableInitProperty), nameof(ClassWithNonNullableRequiredProperty.Property));
194200

195201
static object[] Wrap(Type type, string propertyName) => [type, propertyName];
196202
}
@@ -249,6 +255,8 @@ public static IEnumerable<object[]> GetTypesWithNullablePropertySetter()
249255
yield return Wrap(typeof(AllowNullConstructorParameter<string>), nameof(AllowNullConstructorParameter<string>.Property));
250256
yield return Wrap(typeof(GenericConstructorParameter<string>), nameof(GenericConstructorParameter<string>.Property));
251257
yield return Wrap(typeof(NullableGenericConstructorParameter<string>), nameof(NullableGenericConstructorParameter<string>.Property));
258+
yield return Wrap(typeof(ClassWithNullableInitProperty), nameof(ClassWithNullableInitProperty.Property));
259+
yield return Wrap(typeof(ClassWithNullableInitProperty), nameof(ClassWithNullableRequiredProperty.Property));
252260

253261
static object[] Wrap(Type type, string propertyName) => [type, propertyName];
254262
}
@@ -765,5 +773,25 @@ public class NullableFieldClass
765773
[JsonInclude]
766774
public string? Field;
767775
}
776+
777+
public class ClassWithNullableInitProperty
778+
{
779+
public string? Property { get; init; }
780+
}
781+
782+
public class ClassWithNonNullableInitProperty
783+
{
784+
public string Property { get; init; }
785+
}
786+
787+
public class ClassWithNullableRequiredProperty
788+
{
789+
public required string? Property { get; set; }
790+
}
791+
792+
public class ClassWithNonNullableRequiredProperty
793+
{
794+
public required string Property { get; set; }
795+
}
768796
}
769797
}

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/NullableAnnotationsTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ protected NullableAnnotationsTests_Metadata(JsonSerializerWrapper serializer)
6565
[JsonSerializable(typeof(NullableGenericConstructorParameter<string>))]
6666
[JsonSerializable(typeof(NotNullGenericConstructorParameter<string>))]
6767
[JsonSerializable(typeof(NotNullablePropertyWithIgnoreConditions))]
68+
[JsonSerializable(typeof(ClassWithNullableInitProperty))]
69+
[JsonSerializable(typeof(ClassWithNonNullableInitProperty))]
70+
[JsonSerializable(typeof(ClassWithNullableRequiredProperty))]
71+
[JsonSerializable(typeof(ClassWithNonNullableRequiredProperty))]
6872
internal sealed partial class NullableAnnotationsTestsContext_Metadata
6973
: JsonSerializerContext { }
7074
}
@@ -128,6 +132,10 @@ protected NullableAnnotationsTests_Default(JsonSerializerWrapper serializer)
128132
[JsonSerializable(typeof(NullableGenericConstructorParameter<string>))]
129133
[JsonSerializable(typeof(NotNullGenericConstructorParameter<string>))]
130134
[JsonSerializable(typeof(NotNullablePropertyWithIgnoreConditions))]
135+
[JsonSerializable(typeof(ClassWithNullableInitProperty))]
136+
[JsonSerializable(typeof(ClassWithNonNullableInitProperty))]
137+
[JsonSerializable(typeof(ClassWithNullableRequiredProperty))]
138+
[JsonSerializable(typeof(ClassWithNonNullableRequiredProperty))]
131139
internal sealed partial class NullableAnnotationsTestsContext_Default
132140
: JsonSerializerContext
133141
{ }

0 commit comments

Comments
 (0)