Skip to content

Commit

Permalink
Set generic type arguments nullability for value types (#58036)
Browse files Browse the repository at this point in the history
* Set generic type arguments nullability for value types
  • Loading branch information
buyaa-n authored Aug 30, 2021
1 parent 5694f94 commit 4cf44d0
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -347,19 +347,23 @@ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, ILi
private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, IList<CustomAttributeData> customAttributes, int index)
{
NullabilityState state = NullabilityState.Unknown;
NullabilityInfo? elementState = null;
NullabilityInfo[] genericArgumentsState = Array.Empty<NullabilityInfo>();
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<NullabilityInfo>());
}
else
{
Expand All @@ -368,32 +372,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<NullabilityInfo>());
if (state != NullabilityState.Unknown)
{
TryLoadGenericMetaTypeNullability(memberInfo, nullability);
genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], customAttributes, offset);
}
}

return nullability;
NullabilityInfo nullability = new NullabilityInfo(type, state, state, elementState, genericArgumentsState);

if (!type.IsValueType && state != NullabilityState.Unknown)
{
TryLoadGenericMetaTypeNullability(memberInfo, nullability);
}

return nullability;
}

private static bool ParseNullableState(IList<CustomAttributeData> customAttributes, int index, ref NullabilityState state)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,8 @@ public void GenericListTest()
Assert.Equal(NullabilityState.Nullable, nullability.WriteState);
Assert.Equal(typeof(string), nullability.Type);

Type lisNontNull = typeof(List<string>);
MethodInfo addNotNull = lisNontNull.GetMethod("Add")!;
Type listNotNull = typeof(List<string>);
MethodInfo addNotNull = listNotNull.GetMethod("Add")!;
nullability = nullabilityContext.Create(addNotNull.GetParameters()[0]);
Assert.Equal(NullabilityState.Nullable, nullability.ReadState);
Assert.Equal(typeof(string), nullability.Type);
Expand Down Expand Up @@ -812,6 +812,34 @@ public void RefReturnTestTest(string methodName, NullabilityState retReadState,
Assert.Equal(paramReadState, paramNullability.ReadState);
Assert.Equal(paramWriteState, paramNullability.WriteState);
}

public static IEnumerable<object[]> 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
Expand Down Expand Up @@ -865,6 +893,7 @@ public class TypeWithNotNullContext
protected Tuple<string, string, string> PropertyTupleUnknown { get; set; }
protected internal IDictionary<Type, string[]> PropertyDictionaryUnknown { get; set; }

public (int, string) UnknownValueTuple;
internal TypeWithNotNullContext FieldUnknown;
public int FieldValueTypeUnknown;

Expand Down Expand Up @@ -915,6 +944,11 @@ public void MethodParametersUnknown(string s, IDictionary<Type, string[]> dict)
private IDictionary<Type, string[]>? PropertyDictionaryNonNonNonNull { get; set; }
public IDictionary<Type, string[]> 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;
Expand Down Expand Up @@ -969,14 +1003,14 @@ public void MethodParametersUnknown(T s, IDictionary<Type, T> dict) { }
public Tuple<T?, string?, string?>? PropertyTupleNullNullNullNull { get; set; }
public Tuple<T, T?, string> PropertyTupleNonNullNonNon { get; set; } = null!;
Tuple<string?, T, T?>? PropertyTupleNullNonNullNull { get; set; }
public Tuple<T, string?, string>? PropertyTupleNonNullNonNull { get; set; }
public Tuple<string, string, T> PropertyTupleNonNonNonNon { get; set; } = null!;
public Tuple<T, int?, string>? PropertyTupleNonNullNonNull { get; set; }
public Tuple<int, string, T> PropertyTupleNonNonNonNon { get; set; } = null!;
private IDictionary<T?, string?[]?> PropertyDictionaryNullNullNullNon { get; set; } = null!;
static IDictionary<Type, T?[]>? PropertyDictionaryNonNullNonNull { get; set; }
public static IDictionary<T?, T[]>? PropertyDictionaryNullNonNonNull { get; set; }
public IDictionary<Type, T?[]> PropertyDictionaryNonNullNonNon { get; set; } = null!;
protected IDictionary<T, T[]>? PropertyDictionaryNonNonNonNull { get; set; }
public IDictionary<T, string[]> PropertyDictionaryNonNonNonNon { get; set; } = null!;
public IDictionary<T, int[]> PropertyDictionaryNonNonNonNon { get; set; } = null!;

static T? FieldNullable = default;
public T FieldNonNullable = default!;
Expand Down

0 comments on commit 4cf44d0

Please sign in to comment.