From: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 06:52:58 +0000 (-0700) Subject: [release/6.0] Set generic type arguments nullability for value types (#58390) X-Git-Tag: accepted/tizen/unified/20220110.054933~158 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f860bcef1808fb1e5a1d7471fe1926436c8a6148;p=platform%2Fupstream%2Fdotnet%2Fruntime.git [release/6.0] Set generic type arguments nullability for value types (#58390) * Set generic type arguments nullability for value types * Skip test on mono * Apply commment * Separate var name parts with _ * Reflection nullability API: improve test coverage, fix bug found (#58479) * Add/remove some tests, fix generics indexing bug Co-authored-by: Buyaa Namnan --- diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs index 203bafd..b09bb83 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs @@ -347,19 +347,23 @@ namespace System.Reflection private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, IList customAttributes, int index) { NullabilityState state = NullabilityState.Unknown; + NullabilityInfo? elementState = null; + NullabilityInfo[] genericArgumentsState = Array.Empty(); + Type? underlyingType = type; if (type.IsValueType) { - if (Nullable.GetUnderlyingType(type) != null) + underlyingType = Nullable.GetUnderlyingType(type); + + if (underlyingType != null) { state = NullabilityState.Nullable; } else { + underlyingType = type; state = NullabilityState.NotNull; } - - return new NullabilityInfo(type, state, state, null, Array.Empty()); } else { @@ -368,32 +372,38 @@ namespace System.Reflection state = GetNullableContext(memberInfo); } - NullabilityInfo? elementState = null; - NullabilityInfo[]? genericArgumentsState = null; - if (type.IsArray) { elementState = GetNullabilityInfo(memberInfo, type.GetElementType()!, customAttributes, index + 1); } - else if (type.IsGenericType) + } + + if (underlyingType.IsGenericType) + { + Type[] genericArguments = underlyingType.GetGenericArguments(); + genericArgumentsState = new NullabilityInfo[genericArguments.Length]; + + for (int i = 0, offset = 0; i < genericArguments.Length; i++) { - Type[] genericArguments = type.GetGenericArguments(); - genericArgumentsState = new NullabilityInfo[genericArguments.Length]; + Type t = Nullable.GetUnderlyingType(genericArguments[i]) ?? genericArguments[i]; - for (int i = 0; i < genericArguments.Length; i++) + if (!t.IsValueType || t.IsGenericType) { - genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], customAttributes, i + 1); + offset++; } - } - NullabilityInfo nullability = new NullabilityInfo(type, state, state, elementState, genericArgumentsState ?? Array.Empty()); - if (state != NullabilityState.Unknown) - { - TryLoadGenericMetaTypeNullability(memberInfo, nullability); + genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], customAttributes, index + offset); } + } + + NullabilityInfo nullability = new NullabilityInfo(type, state, state, elementState, genericArgumentsState); - return nullability; + if (!type.IsValueType && state != NullabilityState.Unknown) + { + TryLoadGenericMetaTypeNullability(memberInfo, nullability); } + + return nullability; } private static bool ParseNullableState(IList customAttributes, int index, ref NullabilityState state) @@ -472,7 +482,7 @@ namespace System.Reflection { NullabilityState state = nullability.ReadState; - if (!ParseNullableState(metaType.GetCustomAttributesData(), 0, ref state)) + if (state == NullabilityState.NotNull && !ParseNullableState(metaType.GetCustomAttributesData(), 0, ref state)) { state = GetNullableContext(metaType); } diff --git a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs index 1651b18..f444a56 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs @@ -33,7 +33,7 @@ namespace System.Reflection.Tests yield return new object[] { "FieldAllowNull2", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; yield return new object[] { "FieldNotNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; yield return new object[] { "FieldMaybeNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; - yield return new object[] { "FieldMaybeNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "FieldMaybeNull2", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; yield return new object[] { "FieldNotNull2", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; } @@ -121,7 +121,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(ArrayPropertyTestData))] public void ArrayPropertyTest(string propertyName, NullabilityState elementState, NullabilityState propertyState) { @@ -143,7 +143,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(GenericArrayPropertyTestData))] public void GenericArrayPropertyTest(string propertyName, NullabilityState elementState, NullabilityState propertyState) { @@ -167,7 +167,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(JaggedArrayPropertyTestData))] public void JaggedArrayPropertyTest(string propertyName, NullabilityState innermodtElementState, NullabilityState elementState, NullabilityState propertyState) { @@ -192,7 +192,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(TuplePropertyTestData))] public void TuplePropertyTest(string propertyName, NullabilityState genericParam1, NullabilityState genericParam2, NullabilityState genericParam3, NullabilityState propertyState) { @@ -217,7 +217,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(GenericTuplePropertyTestData))] public void GenericTuplePropertyTest(string propertyName, NullabilityState genericParam1, NullabilityState genericParam2, NullabilityState genericParam3, NullabilityState propertyState) { @@ -243,7 +243,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(DictionaryPropertyTestData))] public void DictionaryPropertyTest(string propertyName, NullabilityState keyState, NullabilityState valueElement, NullabilityState valueState, NullabilityState propertyState) { @@ -269,7 +269,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(GenericDictionaryPropertyTestData))] public void GenericDictionaryPropertyTest(string propertyName, NullabilityState keyState, NullabilityState valueElement, NullabilityState valueState, NullabilityState propertyState) { @@ -433,16 +433,16 @@ namespace System.Reflection.Tests Assert.Equal(type, nullability.Type); } - public static IEnumerable GenericNotnullConstraintTestData() + public static IEnumerable GenericNotnullConstraintFieldsTestData() { - yield return new object[] { "FieldNullable", NullabilityState.NotNull, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "FieldNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(string) }; yield return new object[] { "FieldUnknown", NullabilityState.Unknown, NullabilityState.Unknown, typeof(string) }; yield return new object[] { "FieldNullableEnabled", NullabilityState.NotNull, NullabilityState.NotNull, typeof(string) }; } [Theory] - [MemberData(nameof(GenericNotnullConstraintTestData))] - public void GenericNotNullConstraintTest(string fieldName, NullabilityState readState, NullabilityState writeState, Type type) + [MemberData(nameof(GenericNotnullConstraintFieldsTestData))] + public void GenericNotNullConstraintFieldsTest(string fieldName, NullabilityState readState, NullabilityState writeState, Type type) { FieldInfo field = typeof(GenericTestConstrainedNotNull).GetField(fieldName, flags)!; NullabilityInfo nullability = nullabilityContext.Create(field); @@ -451,7 +451,25 @@ namespace System.Reflection.Tests Assert.Equal(type, nullability.Type); } - public static IEnumerable GenericStructConstraintTestData() + public static IEnumerable GenericNotnullConstraintPropertiesTestData() + { + yield return new object[] { "PropertyNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "PropertyUnknown", NullabilityState.Unknown, NullabilityState.Unknown, typeof(string) }; + yield return new object[] { "PropertyNullableEnabled", NullabilityState.NotNull, NullabilityState.NotNull, typeof(string) }; + } + + [Theory] + [MemberData(nameof(GenericNotnullConstraintPropertiesTestData))] + public void GenericNotNullConstraintPropertiesTest(string propertyName, NullabilityState readState, NullabilityState writeState, Type type) + { + PropertyInfo property = typeof(GenericTestConstrainedNotNull).GetProperty(propertyName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + } + + public static IEnumerable GenericStructConstraintFieldsTestData() { yield return new object[] { "FieldNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(int?) }; yield return new object[] { "FieldUnknown", NullabilityState.NotNull, NullabilityState.NotNull, typeof(int) }; @@ -459,8 +477,8 @@ namespace System.Reflection.Tests } [Theory] - [MemberData(nameof(GenericStructConstraintTestData))] - public void GenericStructConstraintTest(string fieldName, NullabilityState readState, NullabilityState writeState, Type type) + [MemberData(nameof(GenericStructConstraintFieldsTestData))] + public void GenericStructConstraintFieldsTest(string fieldName, NullabilityState readState, NullabilityState writeState, Type type) { FieldInfo field = typeof(GenericTestConstrainedStruct).GetField(fieldName, flags)!; NullabilityInfo nullability = nullabilityContext.Create(field); @@ -469,6 +487,24 @@ namespace System.Reflection.Tests Assert.Equal(type, nullability.Type); } + public static IEnumerable GenericStructConstraintPropertiesTestData() + { + yield return new object[] { "PropertyNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(int?) }; + yield return new object[] { "PropertyUnknown", NullabilityState.NotNull, NullabilityState.NotNull, typeof(int) }; + yield return new object[] { "PropertyNullableEnabled", NullabilityState.NotNull, NullabilityState.NotNull, typeof(int) }; + } + + [Theory] + [MemberData(nameof(GenericStructConstraintPropertiesTestData))] + public void GenericStructConstraintPropertiesTest(string propertyName, NullabilityState readState, NullabilityState writeState, Type type) + { + PropertyInfo property = typeof(GenericTestConstrainedStruct).GetProperty(propertyName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + } + [Fact] [SkipOnMono("Nullability attributes trimmed on Mono")] public void GenericListTest() @@ -480,8 +516,8 @@ namespace System.Reflection.Tests Assert.Equal(NullabilityState.Nullable, nullability.WriteState); Assert.Equal(typeof(string), nullability.Type); - Type lisNontNull = typeof(List); - MethodInfo addNotNull = lisNontNull.GetMethod("Add")!; + Type listNotNull = typeof(List); + MethodInfo addNotNull = listNotNull.GetMethod("Add")!; nullability = nullabilityContext.Create(addNotNull.GetParameters()[0]); Assert.Equal(NullabilityState.Nullable, nullability.ReadState); Assert.Equal(typeof(string), nullability.Type); @@ -544,7 +580,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(MethodReturnParameterTestData))] public void MethodReturnParameterTest(string methodName, NullabilityState elementState, NullabilityState readState) { @@ -557,6 +593,31 @@ namespace System.Reflection.Tests Assert.Empty(nullability.GenericTypeArguments); } + public static IEnumerable MethodReturnsTupleTestData() + { + // public Tuple? MethodReturnsTupleNullNonNull() => null; + yield return new object[] { "MethodReturnsTupleNullNonNull", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + // public Tuple MethodReturnsTupleNullNonNot() => null! + yield return new object[] { "MethodReturnsTupleNullNonNot", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; + // public (int?, string)? MethodReturnsValueTupleNullNonNull() => null; + yield return new object[] { "MethodReturnsValueTupleNullNonNull", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + // public (string?, string) MethodReturnsValueTupleNullNonNon() => (null, string.Empty); + yield return new object[] { "MethodReturnsValueTupleNullNonNon", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; + } + + [Theory] + [SkipOnMono("Nullability attributes trimmed on Mono")] + [MemberData(nameof(MethodReturnsTupleTestData))] + public void MethodReturnsTupleTest(string methodName, NullabilityState param1, NullabilityState param2, NullabilityState readState) + { + MethodInfo method = testType.GetMethod(methodName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(method.ReturnParameter); + Assert.Equal(readState, nullability.ReadState); + Assert.Null(nullability.ElementType); + Assert.Equal(param1, nullability.GenericTypeArguments[0].ReadState); + Assert.Equal(param2, nullability.GenericTypeArguments[1].ReadState); + } + public static IEnumerable MethodGenericReturnParameterTestData() { yield return new object[] { "MethodReturnsUnknown", NullabilityState.Unknown, NullabilityState.Unknown }; @@ -569,7 +630,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(MethodGenericReturnParameterTestData))] public void MethodGenericReturnParameterTest(string methodName, NullabilityState readState, NullabilityState elementState) { @@ -592,7 +653,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(MethodParametersTestData))] public void MethodParametersTest(string methodName, NullabilityState stringState, NullabilityState dictKey, NullabilityState dictValueElement, NullabilityState dictValue, NullabilityState dictionaryState) { @@ -616,7 +677,7 @@ namespace System.Reflection.Tests } [Theory] - [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [SkipOnMono("Nullability attributes trimmed on Mono")] [MemberData(nameof(MethodGenericParametersTestData))] public void MethodGenericParametersTest(string methodName, NullabilityState param1State, NullabilityState dictKey, NullabilityState dictValue, NullabilityState dictionaryState) { @@ -705,6 +766,8 @@ namespace System.Reflection.Tests public static IEnumerable DifferentContextTestData() { yield return new object[] { "PropertyDisabled", NullabilityState.Unknown, NullabilityState.Unknown, typeof(string) }; + yield return new object[] { "PropertyDisabledAllowNull", NullabilityState.Unknown, NullabilityState.Unknown, typeof(string) }; + yield return new object[] { "PropertyDisabledMaybeNull", NullabilityState.Unknown, NullabilityState.Unknown, typeof(string) }; yield return new object[] { "PropertyEnabledAllowNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; yield return new object[] { "PropertyEnabledNotNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; yield return new object[] { "PropertyEnabledMaybeNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; @@ -766,15 +829,15 @@ namespace System.Reflection.Tests Assert.Equal(NullabilityState.Nullable, notNullIfNotNull.WriteState); Assert.Equal(NullabilityState.Nullable, nullabilityContext.Create(allowNullParameter[1]).ReadState); - // [return: NotNullIfNotNull("nullable")] public string? NullablNotNullIfNotNullReturn(string? nullable, [NotNull] ref string? readNotNull) - ParameterInfo[] nullablNotNullIfNotNullReturn = type.GetMethod("NullablNotNullIfNotNullReturn", flags)!.GetParameters(); - NullabilityInfo returnNotNullIfNotNull = nullabilityContext.Create(type.GetMethod("NullablNotNullIfNotNullReturn", flags)!.ReturnParameter); - NullabilityInfo readNotNull = nullabilityContext.Create(nullablNotNullIfNotNullReturn[1]); + // [return: NotNullIfNotNull("nullable")] public string? NullableNotNullIfNotNullReturn(string? nullable, [NotNull] ref string? readNotNull) + ParameterInfo[] nullableNotNullIfNotNullReturn = type.GetMethod("NullableNotNullIfNotNullReturn", flags)!.GetParameters(); + NullabilityInfo returnNotNullIfNotNull = nullabilityContext.Create(type.GetMethod("NullableNotNullIfNotNullReturn", flags)!.ReturnParameter); + NullabilityInfo readNotNull = nullabilityContext.Create(nullableNotNullIfNotNullReturn[1]); Assert.Equal(NullabilityState.Nullable, returnNotNullIfNotNull.ReadState); Assert.Equal(NullabilityState.Nullable, returnNotNullIfNotNull.WriteState); Assert.Equal(NullabilityState.NotNull, readNotNull.ReadState); Assert.Equal(NullabilityState.Nullable, readNotNull.WriteState); - Assert.Equal(NullabilityState.Nullable, nullabilityContext.Create(nullablNotNullIfNotNullReturn[0]).ReadState); + Assert.Equal(NullabilityState.Nullable, nullabilityContext.Create(nullableNotNullIfNotNullReturn[0]).ReadState); // public bool TryGetOutParameters(string id, [NotNullWhen(true)] out string? value, [MaybeNullWhen(false)] out string value2) ParameterInfo[] tryGetOutParameters = type.GetMethod("TryGetOutParameters", flags)!.GetParameters(); @@ -787,6 +850,51 @@ namespace System.Reflection.Tests Assert.Equal(NullabilityState.NotNull, nullabilityContext.Create(tryGetOutParameters[0]).ReadState); } + public static IEnumerable NestedGenericsReturnParameterData() + { + // public IEnumerable?> MethodReturnsEnumerableNonTupleNonNonNullValueTupleNonNullNon() => null!; + yield return new object[] { "MethodReturnsEnumerableNonTupleNonNonNullValueTupleNonNullNon", + NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + + // public IEnumerable?>? MethodReturnsEnumerableNullTupleNullNonNullValueTupleNullNonNull() => null!; + yield return new object[] { "MethodReturnsEnumerableNullTupleNullNonNullValueTupleNullNonNull", + NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull }; + + // public IEnumerable, int>?> MethodReturnsEnumerableNonTupleNonNonNullTupleNonNullNon() => null!; + yield return new object[] { "MethodReturnsEnumerableNonTupleNonNonNullTupleNonNullNon", + NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + + // public IEnumerable?, int>?>? MethodReturnsEnumerableNullStructNullNonNonTupleNonNullNull() => null; + yield return new object[] { "MethodReturnsEnumerableNullStructNullNonNullTupleNonNullNull", + NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + + // public IEnumerable?, int>?>? MethodReturnsEnumerableNullTupleNullNonNullStructNonNullNull() => null; + yield return new object[] { "MethodReturnsEnumerableNullTupleNullNonNullStructNonNullNull", + NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + + // public IEnumerable<(GenericStruct str, int? count)> MethodReturnsEnumerableNonValueTupleNonNullNonTupleNonNullNon() => null!; + yield return new object[] { "MethodReturnsEnumerableNonValueTupleNonNullNonStructNonNullNon", + NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable }; + } + + [Theory] + [MemberData(nameof(NestedGenericsReturnParameterData))] + [SkipOnMono("Nullability attributes trimmed on Mono")] + public void NestedGenericsReturnParameterTest(string methodName, NullabilityState enumState, NullabilityState innerTupleState, + NullabilityState intState, NullabilityState outerTupleState, NullabilityState stringState, NullabilityState objectState) + { + NullabilityInfo enumerabeNullability = nullabilityContext.Create(typeof(TypeWithNotNullContext).GetMethod(methodName, flags)!.ReturnParameter); + Assert.Equal(enumState, enumerabeNullability.ReadState); + NullabilityInfo tupleNullability = enumerabeNullability.GenericTypeArguments[0]; + Assert.Equal(outerTupleState, tupleNullability.ReadState); + Assert.Equal(innerTupleState, tupleNullability.GenericTypeArguments[0].ReadState); + Assert.Equal(intState, tupleNullability.GenericTypeArguments[1].ReadState); + NullabilityInfo valueTupleNullability = tupleNullability.GenericTypeArguments[0]; + Assert.Equal(innerTupleState, valueTupleNullability.ReadState); + Assert.Equal(stringState, valueTupleNullability.GenericTypeArguments[0].ReadState); + Assert.Equal(objectState, valueTupleNullability.GenericTypeArguments[1].ReadState); + } + public static IEnumerable RefReturnData() { yield return new object[] { "RefReturnUnknown", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown }; @@ -812,12 +920,42 @@ namespace System.Reflection.Tests Assert.Equal(paramReadState, paramNullability.ReadState); Assert.Equal(paramWriteState, paramNullability.WriteState); } + + public static IEnumerable ValueTupleTestData() + { + // public (int, string) UnknownValueTuple; [0] + yield return new object[] { "UnknownValueTuple", NullabilityState.NotNull, NullabilityState.Unknown, NullabilityState.NotNull }; + // public (string?, object) NullNonNonValueTuple; [0, 2, 1] + yield return new object[] { "Null_Non_Non_ValueTuple", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; + // public (string?, object)? Null_Non_Null_ValueTuple; [0, 2, 1] + yield return new object[] { "Null_Non_Null_ValueTuple", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + // public (int, int?)? Non_Null_Null_ValueTuple; [0] + yield return new object[] { "Non_Null_Null_ValueTuple", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.Nullable }; + // public (int, string) Non_Non_Non_ValueTuple; [0, 1] + yield return new object[] { "Non_Non_Non_ValueTuple", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull }; + // public (int, string?) Non_Null_Non_ValueTuple; [0, 2] + yield return new object[] { "Non_Null_Non_ValueTuple", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull }; + } + + [Theory] + [MemberData(nameof(ValueTupleTestData))] + [SkipOnMono("Nullability attributes trimmed on Mono")] + public void TestValueTupleGenericTypeParameters(string fieldName, NullabilityState param1, NullabilityState param2, NullabilityState fieldState) + { + var tupleInfo = nullabilityContext.Create(testType.GetField(fieldName)!); + + Assert.Equal(fieldState, tupleInfo.ReadState); + Assert.Equal(param1, tupleInfo.GenericTypeArguments[0].ReadState); + Assert.Equal(param2, tupleInfo.GenericTypeArguments[1].ReadState); + } } #pragma warning disable CS0649, CS0067, CS0414 public class TypeWithNullableContext { #nullable disable + [AllowNull] public string PropertyDisabledAllowNull { get; set; } + [MaybeNull] public string PropertyDisabledMaybeNull { get; set; } public string PropertyDisabled { get; set; } public ref string RefReturnUnknown(ref string id) { return ref id; } #nullable enable @@ -830,12 +968,13 @@ namespace System.Reflection.Tests bool NotNullWhenParameter([DisallowNull] string? disallowNull, [NotNullWhen(true)] ref string? notNullWhen, Type? nullableType) { return false; } public bool MaybeNullParameters([MaybeNull] string maybeNull, [MaybeNullWhen(false)] out string maybeNullWhen, Type? nullableType) { maybeNullWhen = null; return false; } public string? AllowNullParameter([AllowNull] string allowNull, [NotNullIfNotNull("allowNull")] string? notNullIfNotNull) { return null; } - [return: NotNullIfNotNull("nullable")] public string? NullablNotNullIfNotNullReturn(string? nullable, [NotNull] ref string? readNotNull) { readNotNull = string.Empty; return null!; } + [return: NotNullIfNotNull("nullable")] public string? NullableNotNullIfNotNullReturn(string? nullable, [NotNull] ref string? readNotNull) { readNotNull = string.Empty; return null!; } public ref string? RefReturnNullable([AllowNull] ref string id) { return ref id!; } [return: MaybeNull] public ref string RefReturnMaybeNull([DisallowNull] ref string? id) { return ref id; } [return: NotNull] public ref string? RefReturnNotNull([NotNull] ref string? id) { id = string.Empty; return ref id!; } public ref string RefReturnNotNullable([MaybeNull] ref string id) { return ref id; } public bool TryGetOutParameters(string id, [NotNullWhen(true)] out string? value, [MaybeNullWhen(false)] out string value2) { value = null; value2 = null; return false; } + public IEnumerable?> MethodReturnsEnumerableNonTupleNonNonNullValueTupleNonNullNon() => null!; } public class TypeWithNoContext @@ -852,8 +991,6 @@ namespace System.Reflection.Tests public string? PropertyEnabledNullable { get; set; } public string PropertyEnabledNonNullable { get; set; } = null!; #nullable disable - [return: NotNull, MaybeNull] - public string MethodNullableDisabled([AllowNull] string value, string ret) { return null; } } public class TypeWithNotNullContext @@ -865,6 +1002,7 @@ namespace System.Reflection.Tests protected Tuple PropertyTupleUnknown { get; set; } protected internal IDictionary PropertyDictionaryUnknown { get; set; } + public (int, string) UnknownValueTuple; internal TypeWithNotNullContext FieldUnknown; public int FieldValueTypeUnknown; @@ -915,6 +1053,11 @@ namespace System.Reflection.Tests private IDictionary? PropertyDictionaryNonNonNonNull { get; set; } public IDictionary PropertyDictionaryNonNonNonNon { get; set; } = null!; + public (string?, object) Null_Non_Non_ValueTuple; + public (string?, object)? Null_Non_Null_ValueTuple; + public (int, int?)? Non_Null_Null_ValueTuple; + public (int, string) Non_Non_Non_ValueTuple; + public (int, string?) Non_Null_Non_ValueTuple; private const string? FieldNullable = null; protected static NullabilityInfoContextTests FieldNonNullable = null!; public static double FieldValueTypeNotNull; @@ -936,14 +1079,24 @@ namespace System.Reflection.Tests [return: NotNull, MaybeNull] public string[]? MethodReturnsNonNotNull() => null!; // only NotNull is applicable [return: MaybeNull] public string[] MethodReturnsNonMaybeNull() => null; public string[] MethodReturnsNonNon() => null!; - public Tuple? MethodTupleNullNonNull() => null; - public IEnumerable?> MethodEnumerableNonNonNullUnknownNullNonNullNon() => null!; + public Tuple? MethodReturnsTupleNullNonNull() => null; + public Tuple MethodReturnsTupleNullNonNot() => null!; + public (int?, string)? MethodReturnsValueTupleNullNonNull() => null; + public (string?, string) MethodReturnsValueTupleNullNonNon() => (null, string.Empty); + public IEnumerable?> MethodReturnsEnumerableNonTupleNonNonNullValueTupleNonNullNon() => null!; + public IEnumerable?>? MethodReturnsEnumerableNullTupleNullNonNullValueTupleNullNonNull() => null!; + public IEnumerable, int>?> MethodReturnsEnumerableNonTupleNonNonNullTupleNonNullNon() => null!; + public IEnumerable?, int>?>? MethodReturnsEnumerableNullStructNullNonNullTupleNonNullNull() => null; + public IEnumerable?, int>?>? MethodReturnsEnumerableNullTupleNullNonNullStructNonNullNull() => null; + public IEnumerable<(GenericStruct str, int? count)> MethodReturnsEnumerableNonValueTupleNonNullNonStructNonNullNon() => null!; public void MethodNullNonNullNonNon(string? s, IDictionary dict) { } public void MethodNonNullNonNullNotNull(string s, [NotNull] IDictionary? dict) { dict = new Dictionary(); } public void MethodNullNonNullNullNon(string? s, IDictionary dict) { } public void MethodAllowNullNonNonNonNull([AllowNull] string s, IDictionary? dict) { } } + public struct GenericStruct { } + internal class GenericTest { #nullable disable @@ -969,14 +1122,14 @@ namespace System.Reflection.Tests public Tuple? PropertyTupleNullNullNullNull { get; set; } public Tuple PropertyTupleNonNullNonNon { get; set; } = null!; Tuple? PropertyTupleNullNonNullNull { get; set; } - public Tuple? PropertyTupleNonNullNonNull { get; set; } - public Tuple PropertyTupleNonNonNonNon { get; set; } = null!; + public Tuple? PropertyTupleNonNullNonNull { get; set; } + public Tuple PropertyTupleNonNonNonNon { get; set; } = null!; private IDictionary PropertyDictionaryNullNullNullNon { get; set; } = null!; static IDictionary? PropertyDictionaryNonNullNonNull { get; set; } public static IDictionary? PropertyDictionaryNullNonNonNull { get; set; } public IDictionary PropertyDictionaryNonNullNonNon { get; set; } = null!; protected IDictionary? PropertyDictionaryNonNonNonNull { get; set; } - public IDictionary PropertyDictionaryNonNonNonNon { get; set; } = null!; + public IDictionary PropertyDictionaryNonNonNonNon { get; set; } = null!; static T? FieldNullable = default; public T FieldNonNullable = default!; @@ -1007,6 +1160,7 @@ namespace System.Reflection.Tests public T FieldNullableEnabled = default!; public T? FieldNullable; public T PropertyNullableEnabled { get; set; } = default!; + public T? PropertyNullable { get; set; } = default!; } internal class GenericTestConstrainedStruct where T : struct @@ -1019,5 +1173,6 @@ namespace System.Reflection.Tests public T FieldNullableEnabled; public T? FieldNullable; public T PropertyNullableEnabled { get; set; } + public T? PropertyNullable { get; set; } } }