Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when there is a value type included in the parameter list, the nullability of that value type is omitted from the nullability byte array

{
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the change to add !type.IsValueType?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API returns NullabilityState.NotNull for non-nullable value types, even if nullability is not enabled (unknown context), the same should happen for generics when the bound concrete type was a value type. Do not need to call TryLoadGenericMetaTypeNullability method to check if the meta definition of the member was a generic type and load the nullability info from there.

{
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!;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found another bug when there is a value type included in the parameter list, the nullability of that value type is omitted from the nullability byte array, updated these test properties to test that fix

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