diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 23ffc05ed32a7..4be53fea19734 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -615,6 +615,8 @@
+
+
@@ -2319,4 +2321,4 @@
-
+
\ No newline at end of file
diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfo.cs
new file mode 100644
index 0000000000000..2da593d762863
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfo.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.ObjectModel;
+
+namespace System.Reflection
+{
+ ///
+ /// A class that represents nullability info
+ ///
+ public sealed class NullabilityInfo
+ {
+ internal NullabilityInfo(Type type, NullabilityState readState, NullabilityState writeState,
+ NullabilityInfo? elementType, NullabilityInfo[] typeArguments)
+ {
+ Type = type;
+ ReadState = readState;
+ WriteState = writeState;
+ ElementType = elementType;
+ GenericTypeArguments = typeArguments;
+ }
+
+ ///
+ /// The of the member or generic parameter
+ /// to which this NullabilityInfo belongs
+ ///
+ public Type Type { get; }
+ ///
+ /// The nullability read state of the member
+ ///
+ public NullabilityState ReadState { get; internal set; }
+ ///
+ /// The nullability write state of the member
+ ///
+ public NullabilityState WriteState { get; internal set; }
+ ///
+ /// If the member type is an array, gives the of the elements of the array, null otherwise
+ ///
+ public NullabilityInfo? ElementType { get; }
+ ///
+ /// If the member type is a generic type, gives the array of for each type parameter
+ ///
+ public NullabilityInfo[] GenericTypeArguments { get; }
+ }
+
+ ///
+ /// An enum that represents nullability state
+ ///
+ public enum NullabilityState
+ {
+ ///
+ /// Nullability context not enabled (oblivious)
+ ///
+ Unknown,
+ ///
+ /// Non nullable value or reference type
+ ///
+ NotNull,
+ ///
+ /// Nullable value or reference type
+ ///
+ Nullable
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs
new file mode 100644
index 0000000000000..3d8b3766961c1
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs
@@ -0,0 +1,521 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Reflection
+{
+ ///
+ /// Provides APIs for populating nullability information/context from reflection members:
+ /// , , and .
+ ///
+ public sealed class NullabilityInfoContext
+ {
+ private const string CompilerServicesNameSpace = "System.Runtime.CompilerServices";
+ private readonly Dictionary _publicOnlyModules = new();
+ private readonly Dictionary _context = new();
+
+ [Flags]
+ private enum NotAnnotatedStatus
+ {
+ None = 0x0, // no restriction, all members annotated
+ Private = 0x1, // private members not annotated
+ Internal = 0x2 // internal members not annotated
+ }
+
+ private NullabilityState GetNullableContext(MemberInfo? memberInfo)
+ {
+ while (memberInfo != null)
+ {
+ if (_context.TryGetValue(memberInfo, out NullabilityState state))
+ {
+ return state;
+ }
+
+ foreach (CustomAttributeData attribute in memberInfo.GetCustomAttributesData())
+ {
+ if (attribute.AttributeType.Name == "NullableContextAttribute" &&
+ attribute.AttributeType.Namespace == CompilerServicesNameSpace &&
+ attribute.ConstructorArguments.Count == 1)
+ {
+ state = TranslateByte(attribute.ConstructorArguments[0].Value);
+ _context.Add(memberInfo, state);
+ return state;
+ }
+ }
+
+ memberInfo = memberInfo.DeclaringType;
+ }
+
+ return NullabilityState.Unknown;
+ }
+
+ ///
+ /// Populates for the given .
+ /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
+ /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
+ ///
+ /// The parameter which nullability info gets populated
+ /// If the parameterInfo parameter is null
+ ///
+ [RequiresUnreferencedCode("By default nullability attributes are trimmed by the trimmer")]
+ public NullabilityInfo Create(ParameterInfo parameterInfo)
+ {
+ if (parameterInfo is null)
+ {
+ throw new ArgumentNullException(nameof(parameterInfo));
+ }
+
+ if (parameterInfo.Member is MethodInfo method && IsPrivateOrInternalMethodAndAnnotationDisabled(method))
+ {
+ return new NullabilityInfo(parameterInfo.ParameterType, NullabilityState.Unknown, NullabilityState.Unknown, null, Array.Empty());
+ }
+
+ IList attributes = parameterInfo.GetCustomAttributesData();
+ NullabilityInfo nullability = GetNullabilityInfo(parameterInfo.Member, parameterInfo.ParameterType, attributes);
+
+ if (nullability.ReadState != NullabilityState.Unknown)
+ {
+ CheckParameterMetadataType(parameterInfo, nullability);
+ }
+
+ CheckNullabilityAttributes(nullability, attributes);
+ return nullability;
+ }
+
+ private void CheckParameterMetadataType(ParameterInfo parameter, NullabilityInfo nullability)
+ {
+ if (parameter.Member is MethodInfo method)
+ {
+ MethodInfo metaMethod = GetMethodMetadataDefinition(method);
+ ParameterInfo? metaParameter = null;
+ if (string.IsNullOrEmpty(parameter.Name))
+ {
+ metaParameter = metaMethod.ReturnParameter;
+ }
+ else
+ {
+ ParameterInfo[] parameters = metaMethod.GetParameters();
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ if (parameter.Position == i &&
+ parameter.Name == parameters[i].Name)
+ {
+ metaParameter = parameters[i];
+ break;
+ }
+ }
+ }
+
+ if (metaParameter != null)
+ {
+ CheckGenericParameters(nullability, metaMethod, metaParameter.ParameterType);
+ }
+ }
+ }
+
+ private static MethodInfo GetMethodMetadataDefinition(MethodInfo method)
+ {
+ if (method.IsGenericMethod && !method.IsGenericMethodDefinition)
+ {
+ method = method.GetGenericMethodDefinition();
+ }
+
+ return (MethodInfo)GetMemberMetadataDefinition(method);
+ }
+
+ private void CheckNullabilityAttributes(NullabilityInfo nullability, IList attributes)
+ {
+ foreach (CustomAttributeData attribute in attributes)
+ {
+ if (attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
+ {
+ if (attribute.AttributeType.Name == "NotNullAttribute" &&
+ nullability.ReadState == NullabilityState.Nullable)
+ {
+ nullability.ReadState = NullabilityState.NotNull;
+ break;
+ }
+ else if ((attribute.AttributeType.Name == "MaybeNullAttribute" ||
+ attribute.AttributeType.Name == "MaybeNullWhenAttribute") &&
+ nullability.ReadState == NullabilityState.NotNull &&
+ !nullability.Type.IsValueType)
+ {
+ nullability.ReadState = NullabilityState.Nullable;
+ break;
+ }
+
+ if (attribute.AttributeType.Name == "DisallowNullAttribute" &&
+ nullability.WriteState == NullabilityState.Nullable)
+ {
+ nullability.WriteState = NullabilityState.NotNull;
+ break;
+ }
+ else if (attribute.AttributeType.Name == "AllowNullAttribute" &&
+ nullability.WriteState == NullabilityState.NotNull &&
+ !nullability.Type.IsValueType)
+ {
+ nullability.WriteState = NullabilityState.Nullable;
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Populates for the given .
+ /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
+ /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
+ ///
+ /// The parameter which nullability info gets populated
+ /// If the propertyInfo parameter is null
+ ///
+ [RequiresUnreferencedCode("By default nullability attributes are trimmed by the trimmer")]
+ public NullabilityInfo Create(PropertyInfo propertyInfo)
+ {
+ if (propertyInfo is null)
+ {
+ throw new ArgumentNullException(nameof(propertyInfo));
+ }
+
+ NullabilityInfo nullability = GetNullabilityInfo(propertyInfo, propertyInfo.PropertyType, propertyInfo.GetCustomAttributesData());
+ MethodInfo? getter = propertyInfo.GetGetMethod(true);
+ MethodInfo? setter = propertyInfo.GetSetMethod(true);
+
+ if (getter != null)
+ {
+ if (IsPrivateOrInternalMethodAndAnnotationDisabled(getter))
+ {
+ nullability.ReadState = NullabilityState.Unknown;
+ }
+
+ CheckNullabilityAttributes(nullability, getter.ReturnParameter.GetCustomAttributesData());
+ }
+ else
+ {
+ nullability.ReadState = NullabilityState.Unknown;
+ }
+
+ if (setter != null)
+ {
+ if (IsPrivateOrInternalMethodAndAnnotationDisabled(setter))
+ {
+ nullability.WriteState = NullabilityState.Unknown;
+ }
+
+ CheckNullabilityAttributes(nullability, setter.GetParameters()[0].GetCustomAttributesData());
+ }
+ else
+ {
+ nullability.WriteState = NullabilityState.Unknown;
+ }
+
+ return nullability;
+ }
+
+ private bool IsPrivateOrInternalMethodAndAnnotationDisabled(MethodInfo method)
+ {
+ if ((method.IsPrivate || method.IsFamilyAndAssembly || method.IsAssembly) &&
+ IsPublicOnly(method.IsPrivate, method.IsFamilyAndAssembly, method.IsAssembly, method.Module))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Populates for the given .
+ /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
+ /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
+ ///
+ /// The parameter which nullability info gets populated
+ /// If the eventInfo parameter is null
+ ///
+ [RequiresUnreferencedCode("By default nullability attributes are trimmed by the trimmer")]
+ public NullabilityInfo Create(EventInfo eventInfo)
+ {
+ if (eventInfo is null)
+ {
+ throw new ArgumentNullException(nameof(eventInfo));
+ }
+
+ return GetNullabilityInfo(eventInfo, eventInfo.EventHandlerType!, eventInfo.GetCustomAttributesData());
+ }
+
+ ///
+ /// Populates for the given
+ /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
+ /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
+ ///
+ /// The parameter which nullability info gets populated
+ /// If the fieldInfo parameter is null
+ ///
+ [RequiresUnreferencedCode("By default nullability attributes are trimmed by the trimmer")]
+ public NullabilityInfo Create(FieldInfo fieldInfo)
+ {
+ if (fieldInfo is null)
+ {
+ throw new ArgumentNullException(nameof(fieldInfo));
+ }
+
+ if (IsPrivateOrInternalFieldAndAnnotationDisabled(fieldInfo))
+ {
+ return new NullabilityInfo(fieldInfo.FieldType, NullabilityState.Unknown, NullabilityState.Unknown, null, Array.Empty());
+ }
+
+ IList attributes = fieldInfo.GetCustomAttributesData();
+ NullabilityInfo nullability = GetNullabilityInfo(fieldInfo, fieldInfo.FieldType, attributes);
+ CheckNullabilityAttributes(nullability, attributes);
+ return nullability;
+ }
+
+ private bool IsPrivateOrInternalFieldAndAnnotationDisabled(FieldInfo fieldInfo)
+ {
+ if ((fieldInfo.IsPrivate || fieldInfo.IsFamilyAndAssembly || fieldInfo.IsAssembly) &&
+ IsPublicOnly(fieldInfo.IsPrivate, fieldInfo.IsFamilyAndAssembly, fieldInfo.IsAssembly, fieldInfo.Module))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool IsPublicOnly(bool isPrivate, bool isFamilyAndAssembly, bool isAssembly, Module module)
+ {
+ if (!_publicOnlyModules.TryGetValue(module, out NotAnnotatedStatus value))
+ {
+ value = PopulateAnnotationInfo(module.GetCustomAttributesData());
+ _publicOnlyModules.Add(module, value);
+ }
+
+ if (value == NotAnnotatedStatus.None)
+ {
+ return false;
+ }
+
+ if ((isPrivate || isFamilyAndAssembly) && value.HasFlag(NotAnnotatedStatus.Private) ||
+ isAssembly && value.HasFlag(NotAnnotatedStatus.Internal))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private NotAnnotatedStatus PopulateAnnotationInfo(IList customAttributes)
+ {
+ foreach (CustomAttributeData attribute in customAttributes)
+ {
+ if (attribute.AttributeType.Name == "NullablePublicOnlyAttribute" &&
+ attribute.AttributeType.Namespace == CompilerServicesNameSpace &&
+ attribute.ConstructorArguments.Count == 1)
+ {
+ if (attribute.ConstructorArguments[0].Value is bool boolValue && boolValue)
+ {
+ return NotAnnotatedStatus.Internal | NotAnnotatedStatus.Private;
+ }
+ else
+ {
+ return NotAnnotatedStatus.Private;
+ }
+ }
+ }
+
+ return NotAnnotatedStatus.None;
+ }
+
+ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, IList customAttributes) =>
+ GetNullabilityInfo(memberInfo, type, customAttributes, 0);
+
+ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, IList customAttributes, int index)
+ {
+ NullabilityState state = NullabilityState.Unknown;
+
+ if (type.IsValueType)
+ {
+ if (Nullable.GetUnderlyingType(type) != null)
+ {
+ state = NullabilityState.Nullable;
+ }
+ else
+ {
+ state = NullabilityState.NotNull;
+ }
+
+ return new NullabilityInfo(type, state, state, null, Array.Empty());
+ }
+ else
+ {
+ if (!ParseNullableState(customAttributes, index, ref state))
+ {
+ 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++)
+ {
+ genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], customAttributes, i + 1);
+ }
+ }
+
+ NullabilityInfo nullability = new NullabilityInfo(type, state, state, elementState, genericArgumentsState ?? Array.Empty());
+ if (state != NullabilityState.Unknown)
+ {
+ TryLoadGenericMetaTypeNullability(memberInfo, nullability);
+ }
+
+ return nullability;
+ }
+ }
+
+ private static bool ParseNullableState(IList customAttributes, int index, ref NullabilityState state)
+ {
+ foreach (CustomAttributeData attribute in customAttributes)
+ {
+ if (attribute.AttributeType.Name == "NullableAttribute" &&
+ attribute.AttributeType.Namespace == CompilerServicesNameSpace &&
+ attribute.ConstructorArguments.Count == 1)
+ {
+ object? o = attribute.ConstructorArguments[0].Value;
+
+ if (o is byte b)
+ {
+ state = TranslateByte(b);
+ return true;
+ }
+ else if (o is ReadOnlyCollection args &&
+ index < args.Count &&
+ args[index].Value is byte elementB)
+ {
+ state = TranslateByte(elementB);
+ return true;
+ }
+
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ private void TryLoadGenericMetaTypeNullability(MemberInfo memberInfo, NullabilityInfo nullability)
+ {
+ MemberInfo? metaMember = GetMemberMetadataDefinition(memberInfo);
+ Type? metaType = null;
+ if (metaMember is FieldInfo field)
+ {
+ metaType = field.FieldType;
+ }
+ else if (metaMember is PropertyInfo property)
+ {
+ metaType = GetPropertyMetaType(property);
+ }
+
+ if (metaType != null)
+ {
+ CheckGenericParameters(nullability, metaMember!, metaType);
+ }
+ }
+
+ private static MemberInfo GetMemberMetadataDefinition(MemberInfo member)
+ {
+ Type? type = member.DeclaringType;
+ if ((type != null) && type.IsGenericType && !type.IsGenericTypeDefinition)
+ {
+ return type.GetGenericTypeDefinition().GetMemberWithSameMetadataDefinitionAs(member);
+ }
+
+ return member;
+ }
+
+ private static Type GetPropertyMetaType(PropertyInfo property)
+ {
+ if (property.GetGetMethod(true) is MethodInfo method)
+ {
+ return method.ReturnType;
+ }
+
+ return property.GetSetMethod(true)!.GetParameters()[0].ParameterType;
+ }
+
+ private void CheckGenericParameters(NullabilityInfo nullability, MemberInfo metaMember, Type metaType)
+ {
+ if (metaType.IsGenericParameter)
+ {
+ NullabilityState state = nullability.ReadState;
+
+ if (!ParseNullableState(metaType.GetCustomAttributesData(), 0, ref state))
+ {
+ state = GetNullableContext(metaType);
+ }
+
+ nullability.ReadState = state;
+ nullability.WriteState = state;
+ }
+ else if (metaType.ContainsGenericParameters)
+ {
+ if (nullability.GenericTypeArguments.Length > 0)
+ {
+ Type[] genericArguments = metaType.GetGenericArguments();
+
+ for (int i = 0; i < genericArguments.Length; i++)
+ {
+ if (genericArguments[i].IsGenericParameter)
+ {
+ NullabilityInfo n = GetNullabilityInfo(metaMember, genericArguments[i], genericArguments[i].GetCustomAttributesData(), i + 1);
+ nullability.GenericTypeArguments[i].ReadState = n.ReadState;
+ nullability.GenericTypeArguments[i].WriteState = n.WriteState;
+ }
+ else
+ {
+ UpdateGenericArrayElements(nullability.GenericTypeArguments[i].ElementType, metaMember, genericArguments[i]);
+ }
+ }
+ }
+ else
+ {
+ UpdateGenericArrayElements(nullability.ElementType, metaMember, metaType);
+ }
+ }
+ }
+
+ private void UpdateGenericArrayElements(NullabilityInfo? elementState, MemberInfo metaMember, Type metaType)
+ {
+ if (metaType.IsArray && elementState != null
+ && metaType.GetElementType()!.IsGenericParameter)
+ {
+ Type elementType = metaType.GetElementType()!;
+ NullabilityInfo n = GetNullabilityInfo(metaMember, elementType, elementType.GetCustomAttributesData(), 0);
+ elementState.ReadState = n.ReadState;
+ elementState.WriteState = n.WriteState;
+ }
+ }
+
+ private static NullabilityState TranslateByte(object? value)
+ {
+ return value is byte b ? TranslateByte(b) : NullabilityState.Unknown;
+ }
+
+ private static NullabilityState TranslateByte(byte b) =>
+ b switch
+ {
+ 1 => NullabilityState.NotNull,
+ 2 => NullabilityState.Nullable,
+ _ => NullabilityState.Unknown
+ };
+ }
+}
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index 43ca97b21d3d6..2656da8c99964 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -11933,6 +11933,32 @@ public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo
public virtual System.Type ResolveType(int metadataToken, System.Type[]? genericTypeArguments, System.Type[]? genericMethodArguments) { throw null; }
public override string ToString() { throw null; }
}
+ public sealed class NullabilityInfoContext
+ {
+ [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("By default nullability attributes are trimmed by the trimmer")]
+ public System.Reflection.NullabilityInfo Create(System.Reflection.EventInfo eventInfo) { throw null; }
+ [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("By default nullability attributes are trimmed by the trimmer")]
+ public System.Reflection.NullabilityInfo Create(System.Reflection.FieldInfo fieldInfo) { throw null; }
+ [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("By default nullability attributes are trimmed by the trimmer")]
+ public System.Reflection.NullabilityInfo Create(System.Reflection.ParameterInfo parameterInfo) { throw null; }
+ [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("By default nullability attributes are trimmed by the trimmer")]
+ public System.Reflection.NullabilityInfo Create(System.Reflection.PropertyInfo propertyInfo) { throw null; }
+ }
+ public sealed class NullabilityInfo
+ {
+ internal NullabilityInfo(System.Type type, System.Reflection.NullabilityState readState, System.Reflection.NullabilityState writeState, System.Reflection.NullabilityInfo? elementType, System.Reflection.NullabilityInfo[] genericTypeArguments) { }
+ public System.Type Type { get; }
+ public System.Reflection.NullabilityState ReadState { get; }
+ public System.Reflection.NullabilityState WriteState { get; }
+ public System.Reflection.NullabilityInfo? ElementType { get; }
+ public System.Reflection.NullabilityInfo[] GenericTypeArguments { get; }
+ }
+ public enum NullabilityState
+ {
+ Unknown,
+ NotNull,
+ Nullable
+ }
public delegate System.Reflection.Module ModuleResolveEventHandler(object sender, System.ResolveEventArgs e);
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)]
public sealed partial class ObfuscateAssemblyAttribute : System.Attribute
diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj
index 0ddefe7dcf06e..405fd8b8701b9 100644
--- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj
+++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj
@@ -7,6 +7,8 @@
true
$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser
disable
+
+ $(Features.Replace('nullablePublicOnly', '')
$(DefineConstants);FEATURE_GENERIC_MATH
@@ -123,6 +125,7 @@
+
diff --git a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs
new file mode 100644
index 0000000000000..07f244e9172f7
--- /dev/null
+++ b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs
@@ -0,0 +1,985 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO.Enumeration;
+using System.Text.RegularExpressions;
+using Xunit;
+
+namespace System.Reflection.Tests
+{
+ public class NullabilityInfoContextTests
+ {
+ private static readonly NullabilityInfoContext nullabilityContext = new NullabilityInfoContext();
+ private static readonly Type testType = typeof(TypeWithNotNullContext);
+ private static readonly Type genericType = typeof(GenericTest);
+ private static readonly Type stringType = typeof(string);
+ private static readonly BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
+
+ public static IEnumerable