From 1ac8b85a324512e388010d9dfcfdb64f0283bedc Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Tue, 24 Aug 2021 09:55:14 -0700 Subject: [PATCH 1/5] Set generic type arguments nullability for value types --- .../Reflection/NullabilityInfoContext.cs | 45 +++++++++++-------- .../Reflection/NullabilityInfoContextTests.cs | 39 ++++++++++++++-- 2 files changed, 63 insertions(+), 21 deletions(-) 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 203bafd4d66c72..a9e84c786a2f32 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,24 @@ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, ILi private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, IList customAttributes, int index) { NullabilityState state = NullabilityState.Unknown; + NullabilityInfo? elementState = null; + NullabilityInfo nullability; + 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 +373,36 @@ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, ILi state = GetNullableContext(memberInfo); } - NullabilityInfo? elementState = null; - NullabilityInfo[]? genericArgumentsState = null; - if (type.IsArray) { elementState = GetNullabilityInfo(memberInfo, type.GetElementType()!, customAttributes, index + 1); } - else if (type.IsGenericType) - { - Type[] genericArguments = type.GetGenericArguments(); - genericArgumentsState = new NullabilityInfo[genericArguments.Length]; + } - for (int i = 0; i < genericArguments.Length; i++) + if (underlyingType.IsGenericType) + { + Type[] genericArguments = underlyingType.GetGenericArguments(); + genericArgumentsState = new NullabilityInfo[genericArguments.Length]; + + for (int i = 0, offset = 0; i < genericArguments.Length; i++) + { + if (!genericArguments[i].IsValueType) { - 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, offset); } + } - return nullability; + nullability = new NullabilityInfo(type, state, state, elementState, genericArgumentsState); + + if (!type.IsValueType && state != NullabilityState.Unknown) + { + TryLoadGenericMetaTypeNullability(memberInfo, nullability); } + + return nullability; } private static bool ParseNullableState(IList customAttributes, int index, ref NullabilityState state) diff --git a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs index 1651b1817a4ab7..b508dba7e19702 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs @@ -812,6 +812,33 @@ public void RefReturnTestTest(string methodName, NullabilityState retReadState, 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[] { "NullNonNonValueTuple", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; + // public (string?, object)? NullNonNullValueTuple; [0, 2, 1] + yield return new object[] { "NullNonNullValueTuple", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + // public (int, int?)? NonNullNullValueTuple; [0] + yield return new object[] { "NonNullNullValueTuple", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.Nullable }; + // public (int, string) NonNonNonValueTuple; [0, 1] + yield return new object[] { "NonNonNonValueTuple", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull }; + // public (int, string?) NonNullNonValueTuple; [0, 2] + yield return new object[] { "NonNullNonValueTuple", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull }; + } + + [Theory] + [MemberData(nameof(ValueTupleTestData))] + 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 @@ -865,6 +892,7 @@ public class TypeWithNotNullContext protected Tuple PropertyTupleUnknown { get; set; } protected internal IDictionary PropertyDictionaryUnknown { get; set; } + public (int, string) UnknownValueTuple; internal TypeWithNotNullContext FieldUnknown; public int FieldValueTypeUnknown; @@ -915,6 +943,11 @@ public void MethodParametersUnknown(string s, IDictionary dict) private IDictionary? PropertyDictionaryNonNonNonNull { get; set; } public IDictionary PropertyDictionaryNonNonNonNon { get; set; } = null!; + public (string?, object) NullNonNonValueTuple; + public (string?, object)? NullNonNullValueTuple; + public (int, int?)? NonNullNullValueTuple; + public (int, string) NonNonNonValueTuple; + public (int, string?) NonNullNonValueTuple; private const string? FieldNullable = null; protected static NullabilityInfoContextTests FieldNonNullable = null!; public static double FieldValueTypeNotNull; @@ -969,14 +1002,14 @@ public void MethodParametersUnknown(T s, IDictionary dict) { } 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!; From 4348b08b078458ad5190e92d2d6508995b45ebc6 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 25 Aug 2021 10:27:26 -0700 Subject: [PATCH 2/5] Skip test on mono --- .../tests/System/Reflection/NullabilityInfoContextTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs index b508dba7e19702..0e47b5b0d7a3a5 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs @@ -831,6 +831,7 @@ public static IEnumerable ValueTupleTestData() [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)!); From 28e6038a5d227e35ee82d6138ee84c0575b508f6 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 25 Aug 2021 13:08:56 -0700 Subject: [PATCH 3/5] Apply commment --- .../src/System/Reflection/NullabilityInfoContext.cs | 3 +-- .../tests/System/Reflection/NullabilityInfoContextTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) 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 a9e84c786a2f32..c7e3ef85a9f581 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs @@ -348,7 +348,6 @@ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, ILi { NullabilityState state = NullabilityState.Unknown; NullabilityInfo? elementState = null; - NullabilityInfo nullability; NullabilityInfo[] genericArgumentsState = Array.Empty(); Type? underlyingType = type; @@ -395,7 +394,7 @@ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, ILi } } - nullability = new NullabilityInfo(type, state, state, elementState, genericArgumentsState); + NullabilityInfo nullability = new NullabilityInfo(type, state, state, elementState, genericArgumentsState); if (!type.IsValueType && state != NullabilityState.Unknown) { diff --git a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs index 0e47b5b0d7a3a5..934eeb1a55f07f 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs @@ -480,8 +480,8 @@ public void GenericListTest() 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); From 84f7073dce8657c6b40f8ad51c941f3f27816b03 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Sun, 29 Aug 2021 21:47:50 -0700 Subject: [PATCH 4/5] Separate var name parts with _ --- .../Reflection/NullabilityInfoContextTests.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs index 934eeb1a55f07f..a1ec3c648137c4 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs @@ -818,15 +818,15 @@ 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[] { "NullNonNonValueTuple", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; - // public (string?, object)? NullNonNullValueTuple; [0, 2, 1] - yield return new object[] { "NullNonNullValueTuple", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; - // public (int, int?)? NonNullNullValueTuple; [0] - yield return new object[] { "NonNullNullValueTuple", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.Nullable }; - // public (int, string) NonNonNonValueTuple; [0, 1] - yield return new object[] { "NonNonNonValueTuple", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull }; - // public (int, string?) NonNullNonValueTuple; [0, 2] - yield return new object[] { "NonNullNonValueTuple", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull }; + 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] @@ -944,11 +944,11 @@ public void MethodParametersUnknown(string s, IDictionary dict) private IDictionary? PropertyDictionaryNonNonNonNull { get; set; } public IDictionary PropertyDictionaryNonNonNonNon { get; set; } = null!; - public (string?, object) NullNonNonValueTuple; - public (string?, object)? NullNonNullValueTuple; - public (int, int?)? NonNullNullValueTuple; - public (int, string) NonNonNonValueTuple; - public (int, string?) NonNullNonValueTuple; + 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; From 1bf4caf29dca9434fb8bd322be53f7e5ddcccbe8 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Fri, 10 Sep 2021 18:08:49 -0700 Subject: [PATCH 5/5] Reflection nullability API: improve test coverage, fix bug found (#58479) * Add/remove some tests, fix generics indexing bug --- .../Reflection/NullabilityInfoContext.cs | 8 +- .../Reflection/NullabilityInfoContextTests.cs | 179 +++++++++++++++--- 2 files changed, 155 insertions(+), 32 deletions(-) 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 c7e3ef85a9f581..b09bb83a5a6870 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs @@ -385,12 +385,14 @@ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, ILi for (int i = 0, offset = 0; i < genericArguments.Length; i++) { - if (!genericArguments[i].IsValueType) + Type t = Nullable.GetUnderlyingType(genericArguments[i]) ?? genericArguments[i]; + + if (!t.IsValueType || t.IsGenericType) { offset++; } - genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], customAttributes, offset); + genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], customAttributes, index + offset); } } @@ -480,7 +482,7 @@ private void CheckGenericParameters(NullabilityInfo nullability, MemberInfo meta { 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 a1ec3c648137c4..f444a5624c6bb6 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 @@ public static IEnumerable FieldTestData() 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 @@ public static IEnumerable ArrayPropertyTestData() } [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 @@ public static IEnumerable GenericArrayPropertyTestData() } [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 @@ public static IEnumerable JaggedArrayPropertyTestData() } [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 @@ public static IEnumerable TuplePropertyTestData() } [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 @@ public static IEnumerable GenericTuplePropertyTestData() } [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 @@ public static IEnumerable DictionaryPropertyTestData() } [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 @@ public static IEnumerable GenericDictionaryPropertyTestData() } [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 @@ public void GenericFieldNullableValueTypeTest(string fieldName, NullabilityState 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 @@ public void GenericNotNullConstraintTest(string fieldName, NullabilityState read 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 @@ public static IEnumerable GenericStructConstraintTestData() } [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 @@ public void GenericStructConstraintTest(string fieldName, NullabilityState readS 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() @@ -544,7 +580,7 @@ public static IEnumerable MethodReturnParameterTestData() } [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 @@ public void MethodReturnParameterTest(string methodName, NullabilityState elemen 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 @@ public static IEnumerable MethodGenericReturnParameterTestData() } [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 @@ public static IEnumerable MethodParametersTestData() } [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 @@ public static IEnumerable MethodGenericParametersTestData() } [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 @@ public void NullablePublicOnlyOtherTypesTest() 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 @@ public void AttributedParametersTest() 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 @@ public void AttributedParametersTest() 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 }; @@ -846,6 +954,8 @@ public void TestValueTupleGenericTypeParameters(string fieldName, NullabilitySta 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 @@ -858,12 +968,13 @@ public class TypeWithNullableContext 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 @@ -880,8 +991,6 @@ public class TypeWithNoContext 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 @@ -970,14 +1079,24 @@ public void MethodParametersUnknown(string s, IDictionary dict) [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 @@ -1041,6 +1160,7 @@ internal class GenericTestConstrainedNotNull where T : notnull 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 @@ -1053,5 +1173,6 @@ internal class GenericTestConstrainedStruct where T : struct public T FieldNullableEnabled; public T? FieldNullable; public T PropertyNullableEnabled { get; set; } + public T? PropertyNullable { get; set; } } }