using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using bgen; using NUnit.Framework; #nullable enable namespace GeneratorTests { [TestFixture] [Parallelizable (ParallelScope.None)] // dotnet implementation is not thread safe.. public class NullabilityContextTests : ReflectionTest { interface IInterfaceTest { } class ObjectTest : IInterfaceTest { public int NotNullableValueTypeField = 0; public string NotNullableRefTypeField = string.Empty; public int? NullableValueTypeField = 0; public string? NullableRefTypeField = string.Empty; public int NotNullableValueTypeProperty { get; set; } public string NotNullableRefProperty { get; set; } = string.Empty; public int? NullableValueTypeProperty { get; set; } public string? NullableRefProperty { get; set; } public int? this [int i] { get => i; set { } } public string? this [string? i] { get => i; set { } } public string this [double? i] { get => string.Empty; set { } } public void NotNullableValueTypeParameter (int myParam) { } public void NotNullableOutValueTypeParameter (out int myParam) { myParam = 1; } public void NotNullableRefValueTypeParameter (ref int myParam) { myParam = 1; } public void NotNullableRefParameter (string myParam) { } public void NotNullableOutRefParameter (out string myParam) { myParam = string.Empty; } public void NotNullableRefRefParameter (ref string myParam) { myParam = string.Empty; } public void NullableValueTypeParameter (int? myParam) { } public void NullableOutValueTypeParameter (out int? myParam) { myParam = null; } public void NullableRefValueTypeParameter (ref int? myParam) { myParam = null; } public void NullableRefTypeParameter (string? myParam) { } public void NullableOutRefTypeParameter (out string? myParam) { myParam = null; } public void NullableRefRefTypeParameter (out string? myParam) { myParam = null; } public int NotNullableValueTypeReturn () => 0; public string NotNullableRefTypeReturn () => string.Empty; public int? NullableValueTypeReturn () => null; public string? NullableRefTypeReturn () => null; // array return type tests public int [] NotNullableValueTypeArray () => Array.Empty (); public int? [] NotNullableNullableValueTypeArray () => Array.Empty (); public int []? NullableValueTypeArray () => null; public int? []? NullableNullableValueTypeArray () => null; public string [] NotNullableRefTypeArray () => Array.Empty (); public string? [] NotNullableNullableRefTypeArray () => Array.Empty (); public string []? NullableRefTypeArray () => null; public string? []? NullableNullableRefTypeArray () => null; public int [] [] NotNullableNestedArrayNotNullableValueType () => Array.Empty (); public int? [] [] NotNullableNestedArrayNullableValueType () => Array.Empty (); public int? []? [] NotNullableNestedNullableArrayNullableValueType () => Array.Empty (); public int? []? []? NullableNestedNullableArrayNullableValueType () => null; public string [] [] NotNullableNestedArrayNotNullableRefType () => Array.Empty (); public string? [] [] NotNullableNestedArrayNullableRefType () => Array.Empty (); public string? []? [] NotNullableNestedNullableArrayNullableRefType () => Array.Empty (); public string? []? []? NullableNestedNullableArrayNullableRefType () => null; public List NotNullableValueTypeList () => new (); public List NullableValueTypeList () => new (); public List? NullableListNullableValueType () => null; public List NotNullableRefTypeList () => new (); public List NullableRefTypeList () => new (); public List? NullableListNullableRefType () => null; public List> NestedGenericNotNullableValueType () => new (); public List> NestedGenericNullableValueType () => new (); public List?> NestedNullableGenericNullableValueType () => new (); public List?>? NullableNestedNullableGenericNullableValueType () => new (); public List> NestedGenericNotNullableRefType () => new (); public List> NestedGenericNullableRefType () => new (); public List?> NestedNullableGenericNullableRefType () => new (); public List?>? NullableNestedNullableGenericNullableRefType () => new (); public Dictionary DictionaryStringInt () => new (); public Dictionary DictionaryStringNullableInt () => new (); public Dictionary DictionaryNullableStringNullableInt () => new (); public Dictionary? NullableDictionaryNullableStringNullableInt () => new (); public T GenericNotNullConstrain () where T : notnull => default!; public T? GenericNullableClassConstrain () where T : class? => default; public T? GenericNullableRefTypeConstrain () where T : ObjectTest => null; public T GenericNotNullableRefTypeConstrain () where T : ObjectTest => default!; public T? GenericNullableInterface () where T : IInterfaceTest => default; public void GenericNotNullParameterConstrain (T param) where T : notnull { } public void GenericNullableClassParameterConstrain (T param) where T : class? { } public void GenericNullableRefTypeParameterConstrain (T? param) where T : ObjectTest { } public void GenericNullableInterfaceParameterConstrain (T? param) where T : IInterfaceTest { } } public static PropertyInfo GetIndexer (Type type, params Type [] arguments) => type.GetProperties ().First ( x => x.GetIndexParameters ().Select (y => y.ParameterType).SequenceEqual (arguments)); Type testType = typeof (object); NullabilityInfoContext context = new (); [SetUp] public void SetUp () { testType = typeof (ObjectTest); context = new (); } [TestCase ("NotNullableValueTypeField", typeof (int))] [TestCase ("NotNullableRefTypeField", typeof (string))] public void NotNullableFieldTest (string fieldName, Type expectedType) { var fieldInfo = GetField (fieldName, testType); var info = context.Create (fieldInfo); Assert.False (info.IsNullable (), "isNullable"); Assert.AreEqual (expectedType, info.Type); } [TestCase ("NullableValueTypeField", typeof (Nullable))] [TestCase ("NullableRefTypeField", typeof (string))] public void NullableFieldTest (string fieldName, Type expectedType) { var fieldInfo = GetField (fieldName, testType); var info = context.Create (fieldInfo); Assert.True (info.IsNullable (), "isNullable"); Assert.AreEqual (expectedType, info.Type); } [TestCase ("NotNullableValueTypeProperty", typeof (int))] [TestCase ("NotNullableRefProperty", typeof (string))] public void NotNullablePropertyTest (string propertyName, Type expectedType) { var propertyInfo = GetProperty (propertyName, testType); var info = context.Create (propertyInfo); Assert.False (info.IsNullable (), "isNullable"); Assert.AreEqual (expectedType, info.Type); } // public int? this [int i] { [TestCase (typeof (int), typeof (Nullable), true, typeof (int), false)] // public string? this [string? i] [TestCase (typeof (string), typeof (string), true, typeof (string), true)] // public string this [double? i] { [TestCase (typeof (double?), typeof (string), false, typeof (Nullable), true)] public void IndexersTests (Type indexersTypes, Type resultType, bool isNullable, Type indexParameter, bool isIndexParameterNull) { var propertyInfo = GetIndexer (testType, indexersTypes); Assert.NotNull (propertyInfo, "propertyInto is not null"); var info = context.Create (propertyInfo.GetMethod!.ReturnParameter!); Assert.AreEqual (isNullable, info.IsNullable (), "info.IsNullable"); Assert.AreEqual (resultType, info.Type, "info.Type"); var parameters = propertyInfo.SetMethod!.GetParameters (); var paramInfo = context.Create (parameters [0]); Assert.AreEqual (isIndexParameterNull, paramInfo.IsNullable (), "paramInfo.IsNullable"); Assert.AreEqual (indexParameter, paramInfo.Type, "paramInfo.Type"); } [TestCase ("NullableValueTypeProperty", typeof (Nullable))] [TestCase ("NullableRefProperty", typeof (string))] public void NullablePropertyTest (string propertyName, Type expectedType) { var propertyInfo = GetProperty (propertyName, testType); var info = context.Create (propertyInfo); Assert.True (info.IsNullable (), "isNullable"); Assert.AreEqual (expectedType, info.Type); } [TestCase ("NotNullableValueTypeParameter", typeof (int))] [TestCase ("NotNullableRefParameter", typeof (string))] public void NotNullableParameterTest (string methodName, Type expectedType) { var memberInfo = GetMethod (methodName, testType); var paramInfo = memberInfo.GetParameters () [0]; var info = context.Create (paramInfo); Assert.False (info.IsNullable (), "isNullable"); Assert.AreEqual (expectedType, info.Type); } [TestCase ("NotNullableOutValueTypeParameter", typeof (int))] [TestCase ("NotNullableRefValueTypeParameter", typeof (int))] [TestCase ("NotNullableOutRefParameter", typeof (string))] [TestCase ("NotNullableRefRefParameter", typeof (string))] public void NotNullableRefParameterTest (string methodName, Type expectedType) { var memberInfo = GetMethod (methodName, testType); var paramInfo = memberInfo.GetParameters () [0]; var info = context.Create (paramInfo); Assert.False (info.IsNullable (), "isNullable"); Assert.AreEqual (expectedType.MakeByRefType (), info.Type); } [TestCase ("NullableValueTypeParameter", typeof (Nullable))] [TestCase ("NullableRefTypeParameter", typeof (string))] public void NullableParameterTest (string methdName, Type expectedType) { var memberInfo = GetMethod (methdName, testType); var paramInfo = memberInfo.GetParameters () [0]; var info = context.Create (paramInfo); Assert.True (info.IsNullable (), "isNullable"); Assert.AreEqual (expectedType, info.Type); } [TestCase ("NullableOutValueTypeParameter", typeof (Nullable))] [TestCase ("NullableRefValueTypeParameter", typeof (Nullable))] [TestCase ("NullableOutRefTypeParameter", typeof (string))] [TestCase ("NullableRefRefTypeParameter", typeof (string))] public void NullableRefParameterTest (string methdName, Type expectedType) { var memberInfo = GetMethod (methdName, testType); var paramInfo = memberInfo.GetParameters () [0]; var info = context.Create (paramInfo); Assert.True (info.IsNullable (), "isNullable"); Assert.AreEqual (expectedType.MakeByRefType (), info.Type); } [TestCase ("NotNullableValueTypeReturn", typeof (int))] [TestCase ("NotNullableRefTypeReturn", typeof (string))] public void NotNullableReturnTypeTest (string methodName, Type expectedType) { var memberInfo = GetMethod (methodName, testType); var info = context.Create (memberInfo.ReturnParameter!); Assert.IsFalse (info.IsNullable (), "isNullable"); Assert.AreEqual (expectedType, info.Type); } [TestCase ("NullableValueTypeReturn", typeof (Nullable))] [TestCase ("NullableRefTypeReturn", typeof (string))] public void NullableReturnTypeTest (string methodName, Type expectedType) { var memberInfo = GetMethod (methodName, testType); var info = context.Create (memberInfo.ReturnParameter!); Assert.IsTrue (info.IsNullable (), "isNullable"); Assert.AreEqual (expectedType, info.Type); } [TestCase ("NotNullableValueTypeArray", typeof (int []), false, typeof (int), false)] [TestCase ("NotNullableNullableValueTypeArray", typeof (int []), false, typeof (Nullable), true)] [TestCase ("NullableValueTypeArray", typeof (int []), true, typeof (int), false)] [TestCase ("NullableNullableValueTypeArray", typeof (int []), true, typeof (Nullable), true)] [TestCase ("NotNullableRefTypeArray", typeof (string []), false, typeof (string), false)] [TestCase ("NotNullableNullableRefTypeArray", typeof (string []), false, typeof (string), true)] public void ReturnTypeArrayTests (string methodName, Type? expectedType, bool arrayIsNullable, Type expectedElementType, bool elementTypeIsNullable) { var memberInfo = GetMethod (methodName, testType); var info = context.Create (memberInfo.ReturnParameter!); Assert.AreEqual (arrayIsNullable, info.IsNullable (), "info.IsNullable"); Assert.AreEqual (expectedElementType, info.ElementType!.Type, "info.ElementType.Type"); Assert.AreEqual (elementTypeIsNullable, info.ElementType.IsNullable (), "info.ElementTyps.IsNullable"); } [TestCase ("NotNullableNestedArrayNotNullableValueType", typeof (int [] []), false, false, typeof (int), false)] [TestCase ("NotNullableNestedArrayNullableValueType", typeof (int? [] []), false, false, typeof (Nullable), true)] [TestCase ("NotNullableNestedNullableArrayNullableValueType", typeof (int? [] []), false, true, typeof (Nullable), true)] [TestCase ("NullableNestedNullableArrayNullableValueType", typeof (int? [] []), true, true, typeof (Nullable), true)] [TestCase ("NotNullableNestedArrayNotNullableRefType", typeof (string [] []), false, false, typeof (string), false)] [TestCase ("NotNullableNestedArrayNullableRefType", typeof (string? [] []), false, false, typeof (string), true)] [TestCase ("NotNullableNestedNullableArrayNullableRefType", typeof (string? [] []), false, true, typeof (string), true)] [TestCase ("NullableNestedNullableArrayNullableRefType", typeof (string? [] []), true, true, typeof (string), true)] public void ReturnNestedArrayTests (string methodName, Type? expectedType, bool isArrayNullable, bool isNestedArrayNullable, Type nestedArrayType, bool isNestedArrayElementNullable) { var memberInfo = GetMethod (methodName, testType); var info = context.Create (memberInfo.ReturnParameter!); Assert.AreEqual (isArrayNullable, info.IsNullable (), "isArrayNullable"); Assert.AreEqual (isNestedArrayNullable, info.ElementType!.IsNullable (), "isNestedArrayNullable"); Assert.AreEqual (nestedArrayType, info.ElementType!.ElementType!.Type, "nestedArrayType"); Assert.AreEqual (isNestedArrayElementNullable, info.ElementType.ElementType.IsNullable (), "isNestedArrayElementNullable"); } [TestCase ("NotNullableValueTypeList", typeof (List), false, typeof (int), false)] [TestCase ("NullableValueTypeList", typeof (List>), false, typeof (Nullable), true)] [TestCase ("NullableListNullableValueType", typeof (List>), true, typeof (Nullable), true)] [TestCase ("NotNullableRefTypeList", typeof (List), false, typeof (string), false)] [TestCase ("NullableRefTypeList", typeof (List), false, typeof (string), true)] [TestCase ("NullableListNullableRefType", typeof (List), true, typeof (string), true)] public void ReturnSimpleGenericType (string methodName, Type? expectedType, bool isGenericTypeNullable, Type? genericParameterType, bool isGenericParameterNull) { var memberInfo = GetMethod (methodName, testType); var info = context.Create (memberInfo.ReturnParameter!); Assert.AreEqual (expectedType, info.Type, "info.Type"); Assert.AreEqual (isGenericTypeNullable, info.IsNullable (), "info.IsNullable"); Assert.AreEqual (genericParameterType, info.GenericTypeArguments! [0].Type, "genericParameterType"); Assert.AreEqual (isGenericParameterNull, info.GenericTypeArguments [0].IsNullable (), "isGenericParameterNull"); } [TestCase ("NestedGenericNotNullableValueType", typeof (List>), false, typeof (HashSet), false, typeof (int), false)] [TestCase ("NestedGenericNullableValueType", typeof (List>>), false, typeof (HashSet>), false, typeof (Nullable), true)] [TestCase ("NestedNullableGenericNullableValueType", typeof (List>>), false, typeof (HashSet>), true, typeof (Nullable), true)] [TestCase ("NullableNestedNullableGenericNullableValueType", typeof (List>>), true, typeof (HashSet>), true, typeof (Nullable), true)] [TestCase ("NestedGenericNotNullableRefType", typeof (List>), false, typeof (HashSet), false, typeof (string), false)] [TestCase ("NestedGenericNullableRefType", typeof (List>), false, typeof (HashSet), false, typeof (string), true)] [TestCase ("NestedNullableGenericNullableRefType", typeof (List>), false, typeof (HashSet), true, typeof (string), true)] [TestCase ("NullableNestedNullableGenericNullableRefType", typeof (List>), true, typeof (HashSet), true, typeof (string), true)] public void ReturnNestedGeneric (string methodName, Type? expectedType, bool isGenericNullable, Type? nestedGenericType, bool isNestedGenericNullable, Type? nestedGenericArgumentType, bool isNullableNestedGenericArgument) { var memberInfo = GetMethod (methodName, testType); var info = context.Create (memberInfo.ReturnParameter!); Assert.AreEqual (expectedType, info.Type, "info.Type"); Assert.AreEqual (isGenericNullable, info.IsNullable (), "info.IsNullable"); Assert.AreEqual (nestedGenericType, info.GenericTypeArguments! [0].Type, "nestedGenericType"); Assert.AreEqual (isNestedGenericNullable, info.GenericTypeArguments [0].IsNullable (), "isNestedGenericNullable"); Assert.AreEqual (nestedGenericArgumentType, info.GenericTypeArguments [0].GenericTypeArguments! [0].Type, "nestedGenericArgumentType"); Assert.AreEqual (isNullableNestedGenericArgument, info.GenericTypeArguments [0].GenericTypeArguments! [0].IsNullable (), "isNullableNestedGenericArgument"); } [TestCase ("DictionaryStringInt", typeof (Dictionary), false, typeof (string), false, typeof (int), false)] [TestCase ("DictionaryStringNullableInt", typeof (Dictionary>), false, typeof (string), false, typeof (Nullable), true)] [TestCase ("DictionaryNullableStringNullableInt", typeof (Dictionary>), false, typeof (string), false, typeof (Nullable), true)] [TestCase ("NullableDictionaryNullableStringNullableInt", typeof (Dictionary>), true, typeof (string), false, typeof (Nullable), true)] public void ReturnDictionary (string methodName, Type? dictionaryType, bool isDictionaryNullable, Type? keyType, bool isKeyNullable, Type? valueType, bool isValueNullable) { var memberInfo = GetMethod (methodName, testType); var info = context.Create (memberInfo.ReturnParameter!); Assert.AreEqual (dictionaryType, info.Type, "info.Type"); Assert.AreEqual (isDictionaryNullable, info.IsNullable (), "info.IsNullable"); Assert.AreEqual (keyType, info.GenericTypeArguments! [0].Type, "keyType"); Assert.AreEqual (isKeyNullable, info.GenericTypeArguments [0].IsNullable (), "isKeyNullable"); Assert.AreEqual (valueType, info.GenericTypeArguments [1].Type, "valueType"); Assert.AreEqual (isValueNullable, info.GenericTypeArguments [1].IsNullable (), "isValueNullable"); } [TestCase ("GenericNotNullConstrain", false)] [TestCase ("GenericNullableClassConstrain", true)] [TestCase ("GenericNullableRefTypeConstrain", true)] [TestCase ("GenericNullableInterface", true)] // limitation in the lang ? is ignore public void GenericReturnConstrainTests (string methodName, bool returnTypeIsNull) { var memberInfo = GetMethod (methodName, testType); var info = context.Create (memberInfo.ReturnParameter!); Assert.AreEqual (returnTypeIsNull, info.IsNullable ()); } [TestCase ("GenericNotNullParameterConstrain", false)] [TestCase ("GenericNullableClassParameterConstrain", true)] [TestCase ("GenericNullableRefTypeParameterConstrain", true)] [TestCase ("GenericNullableInterfaceParameterConstrain", true)] // limitation in the lang ? is ignore public void GenericParamConstrainTests (string methodName, bool returnTypeIsNull) { var memberInfo = GetMethod (methodName, testType); var info = context.Create (memberInfo.GetParameters () [0]); Assert.AreEqual (returnTypeIsNull, info.IsNullable ()); } } }