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 e3e4da36d096b7..9411bad737716e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs @@ -86,35 +86,47 @@ public NullabilityInfo Create(ParameterInfo parameterInfo) private void CheckParameterMetadataType(ParameterInfo parameter, NullabilityInfo nullability) { - if (parameter.Member is MethodInfo method) + ParameterInfo? metaParameter; + MemberInfo metaMember; + + switch (parameter.Member) { - MethodInfo metaMethod = GetMethodMetadataDefinition(method); - ParameterInfo? metaParameter = null; - if (string.IsNullOrEmpty(parameter.Name)) - { - metaParameter = metaMethod.ReturnParameter; - } - else - { - ReadOnlySpan parameters = metaMethod.GetParametersAsSpan(); - for (int i = 0; i < parameters.Length; i++) - { - if (parameter.Position == i && - parameter.Name == parameters[i].Name) - { - metaParameter = parameters[i]; - break; - } - } - } + case ConstructorInfo ctor: + var metaCtor = (ConstructorInfo)GetMemberMetadataDefinition(ctor); + metaMember = metaCtor; + metaParameter = GetMetaParameter(metaCtor, parameter); + break; + + case MethodInfo method: + MethodInfo metaMethod = GetMethodMetadataDefinition(method); + metaMember = metaMethod; + metaParameter = string.IsNullOrEmpty(parameter.Name) ? metaMethod.ReturnParameter : GetMetaParameter(metaMethod, parameter); + break; + + default: + return; + } + + if (metaParameter != null) + { + CheckGenericParameters(nullability, metaMember, metaParameter.ParameterType, parameter.Member.ReflectedType); + } + } - if (metaParameter != null) + private static ParameterInfo? GetMetaParameter(MethodBase metaMethod, ParameterInfo parameter) + { + ReadOnlySpan parameters = metaMethod.GetParametersAsSpan(); + for (int i = 0; i < parameters.Length; i++) + { + if (parameter.Position == i && + parameter.Name == parameters[i].Name) { - CheckGenericParameters(nullability, metaMethod, metaParameter.ParameterType, parameter.Member.ReflectedType); + return parameters[i]; } } - } + return null; + } private static MethodInfo GetMethodMetadataDefinition(MethodInfo method) { if (method.IsGenericMethod && !method.IsGenericMethodDefinition) diff --git a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs index aa0e9c0d464939..77c6984d68e621 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs @@ -1317,6 +1317,146 @@ public void TestNullabilityInfoCreationOnPropertiesWithNestedGenericTypeArgument Assert.Equal(expectedGenericArgumentNullability, info.GenericTypeArguments[2].GenericTypeArguments[0].ReadState); Assert.Equal(NullabilityState.NotNull, info.GenericTypeArguments[2].GenericTypeArguments[1].ReadState); } + + private static Type EnsureReflection([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type) => type; + + public static IEnumerable TestCtorParametersData() => new object[][] + { + [EnsureReflection(typeof(GenericTypeWithCtor<>)), NullabilityState.Nullable, NullabilityState.Nullable], + [EnsureReflection(typeof(GenericTypeWithCtor)), NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericTypeWithCtor)), NullabilityState.Nullable, NullabilityState.Nullable], + [EnsureReflection(typeof(GenericTypeWithCtor)), NullabilityState.Nullable, NullabilityState.Nullable], + + [EnsureReflection(typeof(GenericTypeWithCtor_Disallow<>)), NullabilityState.Nullable, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericTypeWithCtor_Disallow)), NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericTypeWithCtor_Disallow)), NullabilityState.Nullable, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericTypeWithCtor_Disallow)), NullabilityState.Nullable, NullabilityState.NotNull], + + [EnsureReflection(typeof(GenericTypeWithCtor_Maybe<>)), NullabilityState.Nullable, NullabilityState.Nullable], + [EnsureReflection(typeof(GenericTypeWithCtor_Maybe)), NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericTypeWithCtor_Maybe)), NullabilityState.Nullable, NullabilityState.Nullable], + [EnsureReflection(typeof(GenericTypeWithCtor_Maybe)), NullabilityState.Nullable, NullabilityState.Nullable], + + [EnsureReflection(typeof(GenericTypeWithCtor_Allow<>)), NullabilityState.Nullable, NullabilityState.Nullable], + [EnsureReflection(typeof(GenericTypeWithCtor_Allow)), NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericTypeWithCtor_Allow)), NullabilityState.Nullable, NullabilityState.Nullable], + [EnsureReflection(typeof(GenericTypeWithCtor_Allow)), NullabilityState.Nullable, NullabilityState.Nullable], + + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor<>)), NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor)), NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor)), NullabilityState.NotNull, NullabilityState.NotNull], + + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor_Disallow<>)), NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor_Disallow)), NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor_Disallow)), NullabilityState.NotNull, NullabilityState.NotNull], + + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor_Maybe<>)), NullabilityState.Nullable, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor_Maybe)), NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor_Maybe)), NullabilityState.Nullable, NullabilityState.NotNull], + + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor_Allow<>)), NullabilityState.NotNull, NullabilityState.Nullable], + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor_Allow)), NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(GenericNonNullableTypeWithCtor_Allow)), NullabilityState.NotNull, NullabilityState.Nullable], + }; + + [Theory] + [MemberData(nameof(TestCtorParametersData))] + public void TestCtorParameters([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, + NullabilityState expectedRead, NullabilityState expectedWrite) + { + var ctx = new NullabilityInfoContext(); + + ParameterInfo param = type.GetConstructors()[0].GetParameters()[0]; + NullabilityInfo info = ctx.Create(param); + + Assert.Equal(expectedRead, info.ReadState); + Assert.Equal(expectedWrite, info.WriteState); + } + + public static IEnumerable TestMethodsWithOpenGenericParametersData() + { + const string MethodNullable = "GenericMethod"; + const string MethodNonNullable = "GenericNotNullMethod"; + + return new object[][] + { + [EnsureReflection(typeof(ClassWithGenericMethods)), MethodNullable, NullabilityState.Nullable, NullabilityState.Nullable], + [EnsureReflection(typeof(ClassWithGenericMethods)), MethodNonNullable, NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(ClassWithGenericMethods_Disallow)), MethodNullable, NullabilityState.Nullable, NullabilityState.NotNull], + [EnsureReflection(typeof(ClassWithGenericMethods_Disallow)), MethodNonNullable, NullabilityState.NotNull, NullabilityState.NotNull], + [EnsureReflection(typeof(ClassWithGenericMethods_Maybe)), MethodNullable, NullabilityState.Nullable, NullabilityState.Nullable], + [EnsureReflection(typeof(ClassWithGenericMethods_Maybe)), MethodNonNullable, NullabilityState.Nullable, NullabilityState.NotNull], + [EnsureReflection(typeof(ClassWithGenericMethods_Allow)), MethodNullable, NullabilityState.Nullable, NullabilityState.Nullable], + [EnsureReflection(typeof(ClassWithGenericMethods_Allow)), MethodNonNullable, NullabilityState.NotNull, NullabilityState.Nullable], + }; + } + + [Theory] + [MemberData(nameof(TestMethodsWithOpenGenericParametersData))] + public void TestMethodsWithOpenGenericParameters([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, + string methodName, NullabilityState expectedRead, NullabilityState expectedWrite) + { + var ctx = new NullabilityInfoContext(); + + MethodInfo method = type.GetMethod(methodName)!; + + ParameterInfo paramInfo = method.GetParameters()[0]; + NullabilityInfo info = ctx.Create(paramInfo); + + Assert.Equal(expectedRead, info.ReadState); + Assert.Equal(expectedWrite, info.WriteState); + } + + private delegate void GenericMethod(T value); + private delegate void GenericMethodRef([MaybeNull] out T value); + private delegate void GenericMethodDisallow([DisallowNull] T value); + public static IEnumerable TestMethodsWithGenericParametersData() => new object[][] + { + [(GenericMethod)ClassWithGenericMethods.GenericMethod, NullabilityState.NotNull, NullabilityState.NotNull], + [(GenericMethod)ClassWithGenericMethods.GenericMethod, NullabilityState.Nullable, NullabilityState.Nullable], + [(GenericMethod)ClassWithGenericMethods.GenericMethod, NullabilityState.Nullable, NullabilityState.Nullable], + + [(GenericMethod)ClassWithGenericMethods.GenericNotNullMethod, NullabilityState.NotNull, NullabilityState.NotNull], + [(GenericMethod)ClassWithGenericMethods.GenericNotNullMethod, NullabilityState.NotNull, NullabilityState.NotNull], + + [(GenericMethodRef)ClassWithGenericMethods_Maybe.GenericMethod, NullabilityState.NotNull, NullabilityState.NotNull], + [(GenericMethodRef)ClassWithGenericMethods_Maybe.GenericMethod, NullabilityState.Nullable, NullabilityState.Nullable], + [(GenericMethodRef)ClassWithGenericMethods_Maybe.GenericMethod, NullabilityState.Nullable, NullabilityState.Nullable], + + [(GenericMethodRef)ClassWithGenericMethods_Maybe.GenericNotNullMethod, NullabilityState.NotNull, NullabilityState.NotNull], + [(GenericMethodRef)ClassWithGenericMethods_Maybe.GenericNotNullMethod, NullabilityState.Nullable, NullabilityState.NotNull], + + [(GenericMethod)ClassWithGenericMethods_Allow.GenericMethod, NullabilityState.NotNull, NullabilityState.NotNull], + [(GenericMethod)ClassWithGenericMethods_Allow.GenericMethod, NullabilityState.Nullable, NullabilityState.Nullable], + [(GenericMethod)ClassWithGenericMethods_Allow.GenericMethod, NullabilityState.Nullable, NullabilityState.Nullable], + + [(GenericMethod)ClassWithGenericMethods_Allow.GenericNotNullMethod, NullabilityState.NotNull, NullabilityState.NotNull], + [(GenericMethod)ClassWithGenericMethods_Allow.GenericNotNullMethod, NullabilityState.NotNull, NullabilityState.Nullable], + + // Specialized delegates are required due to CS8622 + + [(GenericMethodDisallow)ClassWithGenericMethods_Disallow.GenericMethod, NullabilityState.NotNull, NullabilityState.NotNull], + [(GenericMethodDisallow)ClassWithGenericMethods_Disallow.GenericMethod, NullabilityState.Nullable, NullabilityState.NotNull], + [(GenericMethodDisallow)ClassWithGenericMethods_Disallow.GenericMethod, NullabilityState.Nullable, NullabilityState.NotNull], + + [(GenericMethodDisallow)ClassWithGenericMethods_Disallow.GenericNotNullMethod, NullabilityState.NotNull, NullabilityState.NotNull], + [(GenericMethodDisallow)ClassWithGenericMethods_Disallow.GenericNotNullMethod, NullabilityState.NotNull, NullabilityState.NotNull], + }; + + [Theory] + [MemberData(nameof(TestMethodsWithGenericParametersData))] + public void TestMethodsWithGenericParameters(Delegate @delegate, NullabilityState expectedRead, NullabilityState expectedWrite) + { + var ctx = new NullabilityInfoContext(); + + MethodInfo method = @delegate.Method; + + ParameterInfo paramInfo = method.GetParameters()[0]; + NullabilityInfo info = ctx.Create(paramInfo); + + Assert.Equal(expectedRead, info.ReadState); + Assert.Equal(expectedWrite, info.WriteState); + } } #pragma warning disable CS0649, CS0067, CS0414 @@ -1599,4 +1739,57 @@ public class TypeWithPropertiesNestingItsGenericTypeArgument public Tuple>? Deep4 { get; set; } public Tuple?>? Deep5 { get; set; } } + + public class GenericTypeWithCtor + { + public GenericTypeWithCtor(T value) { } + } + public class GenericTypeWithCtor_Disallow + { + public GenericTypeWithCtor_Disallow([DisallowNull] T value) { } + } + public class GenericTypeWithCtor_Maybe + { + public GenericTypeWithCtor_Maybe([MaybeNull] out T value) { value = default; } + } + public class GenericTypeWithCtor_Allow + { + public GenericTypeWithCtor_Allow([AllowNull] T value) { } + } + public class GenericNonNullableTypeWithCtor where T : notnull + { + public GenericNonNullableTypeWithCtor(T value) { } + } + public class GenericNonNullableTypeWithCtor_Disallow where T : notnull + { + public GenericNonNullableTypeWithCtor_Disallow([DisallowNull] T value) { } + } + public class GenericNonNullableTypeWithCtor_Maybe where T : notnull + { + public GenericNonNullableTypeWithCtor_Maybe([MaybeNull] out T value) { value = default; } + } + public class GenericNonNullableTypeWithCtor_Allow where T : notnull + { + public GenericNonNullableTypeWithCtor_Allow([AllowNull] T value) { } + } + public class ClassWithGenericMethods + { + public static void GenericMethod(T value) => throw new Exception(); + public static void GenericNotNullMethod(T value) where T : notnull => throw new Exception(); + } + public class ClassWithGenericMethods_Disallow + { + public static void GenericMethod([DisallowNull] T value) => throw new Exception(); + public static void GenericNotNullMethod([DisallowNull] T value) where T : notnull => throw new Exception(); + } + public class ClassWithGenericMethods_Maybe + { + public static void GenericMethod([MaybeNull] out T value) => throw new Exception(); + public static void GenericNotNullMethod([MaybeNull] out T value) where T : notnull => throw new Exception(); + } + public class ClassWithGenericMethods_Allow + { + public static void GenericMethod([AllowNull] T value) => throw new Exception(); + public static void GenericNotNullMethod([AllowNull] T value) where T : notnull => throw new Exception(); + } }