mirror of
				https://github.com/esiur/esiur-dotnet.git
				synced 2025-10-31 07:51:36 +00:00 
			
		
		
		
	Nullables
This commit is contained in:
		
							
								
								
									
										714
									
								
								Esiur/Data/NullabilityInfoContext.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										714
									
								
								Esiur/Data/NullabilityInfoContext.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,714 @@ | ||||
| // Licensed to the .NET Foundation under one or more agreements. | ||||
| // The .NET Foundation licenses this file to you under the MIT license. | ||||
|  | ||||
| using Esiur.Data; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.ObjectModel; | ||||
| using System.Diagnostics; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Reflection; | ||||
| using System.Reflection.Metadata; | ||||
|  | ||||
| namespace Esiur.Data | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Provides APIs for populating nullability information/context from reflection members: | ||||
|     /// <see cref="ParameterInfo"/>, <see cref="FieldInfo"/>, <see cref="PropertyInfo"/> and <see cref="EventInfo"/>. | ||||
|     /// </summary> | ||||
|     public sealed class NullabilityInfoContext | ||||
|     { | ||||
|         private const string CompilerServicesNameSpace = "System.Runtime.CompilerServices"; | ||||
|         private readonly Dictionary<Module, NotAnnotatedStatus> _publicOnlyModules = new(); | ||||
|         private readonly Dictionary<MemberInfo, NullabilityState> _context = new(); | ||||
|  | ||||
|         internal static bool IsSupported { get; } = | ||||
|             AppContext.TryGetSwitch("System.Reflection.NullabilityInfoContext.IsSupported", out bool isSupported) ? isSupported : true; | ||||
|  | ||||
|         [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 null; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Populates <see cref="NullabilityInfo" /> for the given <see cref="ParameterInfo" />. | ||||
|         /// 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. | ||||
|         /// </summary> | ||||
|         /// <param name="parameterInfo">The parameter which nullability info gets populated</param> | ||||
|         /// <exception cref="ArgumentNullException">If the parameterInfo parameter is null</exception> | ||||
|         /// <returns><see cref="NullabilityInfo" /></returns> | ||||
|         public NullabilityInfo Create(ParameterInfo parameterInfo) | ||||
|         { | ||||
|             if (parameterInfo == null) | ||||
|                 throw new ArgumentNullException(); | ||||
|  | ||||
|             EnsureIsSupported(); | ||||
|  | ||||
|             IList<CustomAttributeData> attributes = parameterInfo.GetCustomAttributesData(); | ||||
|             NullableAttributeStateParser parser = parameterInfo.Member is MethodBase method && IsPrivateOrInternalMethodAndAnnotationDisabled(method) | ||||
|                 ? NullableAttributeStateParser.Unknown | ||||
|                 : CreateParser(attributes); | ||||
|             NullabilityInfo nullability = GetNullabilityInfo(parameterInfo.Member, parameterInfo.ParameterType, parser); | ||||
|  | ||||
|             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, parameter.Member.ReflectedType); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private static MethodInfo GetMethodMetadataDefinition(MethodInfo method) | ||||
|         { | ||||
|             if (method.IsGenericMethod && !method.IsGenericMethodDefinition) | ||||
|             { | ||||
|                 method = method.GetGenericMethodDefinition(); | ||||
|             } | ||||
|  | ||||
|             return (MethodInfo)GetMemberMetadataDefinition(method); | ||||
|         } | ||||
|  | ||||
|         private static void CheckNullabilityAttributes(NullabilityInfo nullability, IList<CustomAttributeData> attributes) | ||||
|         { | ||||
|             var codeAnalysisReadState = NullabilityState.Unknown; | ||||
|             var codeAnalysisWriteState = NullabilityState.Unknown; | ||||
|  | ||||
|             foreach (CustomAttributeData attribute in attributes) | ||||
|             { | ||||
|                 if (attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis") | ||||
|                 { | ||||
|                     if (attribute.AttributeType.Name == "NotNullAttribute") | ||||
|                     { | ||||
|                         codeAnalysisReadState = NullabilityState.NotNull; | ||||
|                     } | ||||
|                     else if ((attribute.AttributeType.Name == "MaybeNullAttribute" || | ||||
|                             attribute.AttributeType.Name == "MaybeNullWhenAttribute") && | ||||
|                             codeAnalysisReadState == NullabilityState.Unknown && | ||||
|                             !IsValueTypeOrValueTypeByRef(nullability.Type)) | ||||
|                     { | ||||
|                         codeAnalysisReadState = NullabilityState.Nullable; | ||||
|                     } | ||||
|                     else if (attribute.AttributeType.Name == "DisallowNullAttribute") | ||||
|                     { | ||||
|                         codeAnalysisWriteState = NullabilityState.NotNull; | ||||
|                     } | ||||
|                     else if (attribute.AttributeType.Name == "AllowNullAttribute" && | ||||
|                         codeAnalysisWriteState == NullabilityState.Unknown && | ||||
|                         !IsValueTypeOrValueTypeByRef(nullability.Type)) | ||||
|                     { | ||||
|                         codeAnalysisWriteState = NullabilityState.Nullable; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (codeAnalysisReadState != NullabilityState.Unknown) | ||||
|             { | ||||
|                 nullability.ReadState = codeAnalysisReadState; | ||||
|             } | ||||
|             if (codeAnalysisWriteState != NullabilityState.Unknown) | ||||
|             { | ||||
|                 nullability.WriteState = codeAnalysisWriteState; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Populates <see cref="NullabilityInfo" /> for the given <see cref="PropertyInfo" />. | ||||
|         /// 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. | ||||
|         /// </summary> | ||||
|         /// <param name="propertyInfo">The parameter which nullability info gets populated</param> | ||||
|         /// <exception cref="ArgumentNullException">If the propertyInfo parameter is null</exception> | ||||
|         /// <returns><see cref="NullabilityInfo" /></returns> | ||||
|         public NullabilityInfo Create(PropertyInfo propertyInfo) | ||||
|         { | ||||
|             if (propertyInfo == null) | ||||
|                 throw new ArgumentNullException(); | ||||
|  | ||||
|             EnsureIsSupported(); | ||||
|  | ||||
|             MethodInfo? getter = propertyInfo.GetGetMethod(true); | ||||
|             MethodInfo? setter = propertyInfo.GetSetMethod(true); | ||||
|             bool annotationsDisabled = (getter == null || IsPrivateOrInternalMethodAndAnnotationDisabled(getter)) | ||||
|                 && (setter == null || IsPrivateOrInternalMethodAndAnnotationDisabled(setter)); | ||||
|             NullableAttributeStateParser parser = annotationsDisabled ? NullableAttributeStateParser.Unknown : CreateParser(propertyInfo.GetCustomAttributesData()); | ||||
|             NullabilityInfo nullability = GetNullabilityInfo(propertyInfo, propertyInfo.PropertyType, parser); | ||||
|  | ||||
|             if (getter != null) | ||||
|             { | ||||
|                 CheckNullabilityAttributes(nullability, getter.ReturnParameter.GetCustomAttributesData()); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 nullability.ReadState = NullabilityState.Unknown; | ||||
|             } | ||||
|  | ||||
|             if (setter != null) | ||||
|             { | ||||
|                 CheckNullabilityAttributes(nullability, setter.GetParameters().Last().GetCustomAttributesData()); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 nullability.WriteState = NullabilityState.Unknown; | ||||
|             } | ||||
|  | ||||
|             return nullability; | ||||
|         } | ||||
|  | ||||
|         ///// <summary> | ||||
|         ///// Populates <see cref="NullabilityInfo" /> for the given <see cref="MethodInfo" />. | ||||
|         ///// 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. | ||||
|         ///// </summary> | ||||
|         ///// <param name="propertyInfo">The parameter which nullability info gets populated</param> | ||||
|         ///// <exception cref="ArgumentNullException">If the propertyInfo parameter is null</exception> | ||||
|         ///// <returns><see cref="NullabilityInfo" /></returns> | ||||
|         //public NullabilityInfo Create(MethodInfo memberInfo) | ||||
|         //{ | ||||
|         //    if (memberInfo == null) | ||||
|         //        throw new ArgumentNullException(); | ||||
|  | ||||
|         //    EnsureIsSupported(); | ||||
|  | ||||
|  | ||||
|         //    bool annotationsDisabled = IsPrivateOrInternalMethodAndAnnotationDisabled(memberInfo); | ||||
|  | ||||
|         //    NullableAttributeStateParser parser = annotationsDisabled ? NullableAttributeStateParser.Unknown : CreateParser(memberInfo.GetCustomAttributesData()); | ||||
|         //    NullabilityInfo nullability = GetNullabilityInfo(memberInfo, memberInfo.ReturnType, parser); | ||||
|  | ||||
|  | ||||
|         //    CheckNullabilityAttributes(nullability, memberInfo.ReturnParameter.GetCustomAttributesData()); | ||||
|  | ||||
|         //    return nullability; | ||||
|         //} | ||||
|  | ||||
|         private bool IsPrivateOrInternalMethodAndAnnotationDisabled(MethodBase method) | ||||
|         { | ||||
|             if ((method.IsPrivate || method.IsFamilyAndAssembly || method.IsAssembly) && | ||||
|                IsPublicOnly(method.IsPrivate, method.IsFamilyAndAssembly, method.IsAssembly, method.Module)) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Populates <see cref="NullabilityInfo" /> for the given <see cref="EventInfo" />. | ||||
|         /// 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. | ||||
|         /// </summary> | ||||
|         /// <param name="eventInfo">The parameter which nullability info gets populated</param> | ||||
|         /// <exception cref="ArgumentNullException">If the eventInfo parameter is null</exception> | ||||
|         /// <returns><see cref="NullabilityInfo" /></returns> | ||||
|         public NullabilityInfo Create(EventInfo eventInfo) | ||||
|         { | ||||
|             if (eventInfo == null) | ||||
|                 throw new ArgumentNullException(); | ||||
|  | ||||
|             EnsureIsSupported(); | ||||
|  | ||||
|             return GetNullabilityInfo(eventInfo, eventInfo.EventHandlerType!, CreateParser(eventInfo.GetCustomAttributesData())); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Populates <see cref="NullabilityInfo" /> for the given <see cref="FieldInfo" /> | ||||
|         /// 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. | ||||
|         /// </summary> | ||||
|         /// <param name="fieldInfo">The parameter which nullability info gets populated</param> | ||||
|         /// <exception cref="ArgumentNullException">If the fieldInfo parameter is null</exception> | ||||
|         /// <returns><see cref="NullabilityInfo" /></returns> | ||||
|         public NullabilityInfo Create(FieldInfo fieldInfo) | ||||
|         { | ||||
|             if (fieldInfo == null) | ||||
|                 throw new ArgumentNullException(); | ||||
|  | ||||
|             EnsureIsSupported(); | ||||
|  | ||||
|             IList<CustomAttributeData> attributes = fieldInfo.GetCustomAttributesData(); | ||||
|             NullableAttributeStateParser parser = IsPrivateOrInternalFieldAndAnnotationDisabled(fieldInfo) ? NullableAttributeStateParser.Unknown : CreateParser(attributes); | ||||
|             NullabilityInfo nullability = GetNullabilityInfo(fieldInfo, fieldInfo.FieldType, parser); | ||||
|             CheckNullabilityAttributes(nullability, attributes); | ||||
|             return nullability; | ||||
|         } | ||||
|  | ||||
|         private static void EnsureIsSupported() | ||||
|         { | ||||
|             if (!IsSupported) | ||||
|             { | ||||
|                 throw new InvalidOperationException(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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 static NotAnnotatedStatus PopulateAnnotationInfo(IList<CustomAttributeData> 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, NullableAttributeStateParser parser) | ||||
|         { | ||||
|             int index = 0; | ||||
|             NullabilityInfo nullability = GetNullabilityInfo(memberInfo, type, parser, ref index); | ||||
|  | ||||
|             if (nullability.ReadState != NullabilityState.Unknown) | ||||
|             { | ||||
|                 TryLoadGenericMetaTypeNullability(memberInfo, nullability); | ||||
|             } | ||||
|  | ||||
|             return nullability; | ||||
|         } | ||||
|  | ||||
|         private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, NullableAttributeStateParser parser, ref int index) | ||||
|         { | ||||
|             NullabilityState state = NullabilityState.Unknown; | ||||
|             NullabilityInfo? elementState = null; | ||||
|             NullabilityInfo[] genericArgumentsState = Array.Empty<NullabilityInfo>(); | ||||
|             Type underlyingType = type; | ||||
|  | ||||
|             if (underlyingType.IsByRef || underlyingType.IsPointer) | ||||
|             { | ||||
|                 underlyingType = underlyingType.GetElementType()!; | ||||
|             } | ||||
|  | ||||
|             if (underlyingType.IsValueType) | ||||
|             { | ||||
|                 if (Nullable.GetUnderlyingType(underlyingType) is { } nullableUnderlyingType) | ||||
|                 { | ||||
|                     underlyingType = nullableUnderlyingType; | ||||
|                     state = NullabilityState.Nullable; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     state = NullabilityState.NotNull; | ||||
|                 } | ||||
|  | ||||
|                 if (underlyingType.IsGenericType) | ||||
|                 { | ||||
|                     ++index; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (!parser.ParseNullableState(index++, ref state) | ||||
|                     && GetNullableContext(memberInfo) is { } contextState) | ||||
|                 { | ||||
|                     state = contextState; | ||||
|                 } | ||||
|  | ||||
|                 if (underlyingType.IsArray) | ||||
|                 { | ||||
|                     elementState = GetNullabilityInfo(memberInfo, underlyingType.GetElementType()!, parser, ref index); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (underlyingType.IsGenericType) | ||||
|             { | ||||
|                 Type[] genericArguments = underlyingType.GetGenericArguments(); | ||||
|                 genericArgumentsState = new NullabilityInfo[genericArguments.Length]; | ||||
|  | ||||
|                 for (int i = 0; i < genericArguments.Length; i++) | ||||
|                 { | ||||
|                     genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], parser, ref index); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return new NullabilityInfo(type, state, state, elementState, genericArgumentsState); | ||||
|         } | ||||
|  | ||||
|         private static NullableAttributeStateParser CreateParser(IList<CustomAttributeData> customAttributes) | ||||
|         { | ||||
|             foreach (CustomAttributeData attribute in customAttributes) | ||||
|             { | ||||
|                 if (attribute.AttributeType.Name == "NullableAttribute" && | ||||
|                     attribute.AttributeType.Namespace == CompilerServicesNameSpace && | ||||
|                     attribute.ConstructorArguments.Count == 1) | ||||
|                 { | ||||
|                     return new NullableAttributeStateParser(attribute.ConstructorArguments[0].Value); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return new NullableAttributeStateParser(null); | ||||
|         } | ||||
|  | ||||
|         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, memberInfo.ReflectedType); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private static MemberInfo GetMemberMetadataDefinition(MemberInfo member) | ||||
|         { | ||||
|             Type? type = member.DeclaringType; | ||||
|             if ((type != null) && type.IsGenericType && !type.IsGenericTypeDefinition) | ||||
|             { | ||||
|                 return GetMemberWithSameMetadataDefinitionAs(type.GetGenericTypeDefinition(), member); | ||||
|             } | ||||
|  | ||||
|             return member; | ||||
|         } | ||||
|  | ||||
|         static bool HasSameMetadataDefinitionAs(MemberInfo mi, MemberInfo other) { throw new NotImplementedException(); } | ||||
|  | ||||
|         static MemberInfo GetMemberWithSameMetadataDefinitionAs(Type type, MemberInfo member) | ||||
|         { | ||||
|             if (member == null) | ||||
|                 throw new ArgumentNullException(); | ||||
|  | ||||
|             const BindingFlags all = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; | ||||
|             foreach (MemberInfo myMemberInfo in type.GetMembers(all)) | ||||
|             { | ||||
|                 if (HasSameMetadataDefinitionAs(myMemberInfo, member)) | ||||
|                 { | ||||
|                     return myMemberInfo; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             throw new Exception(); | ||||
|         } | ||||
|  | ||||
|         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, Type? reflectedType) | ||||
|         { | ||||
|             if (metaType.IsGenericParameter) | ||||
|             { | ||||
|                 if (nullability.ReadState == NullabilityState.NotNull) | ||||
|                 { | ||||
|                     TryUpdateGenericParameterNullability(nullability, metaType, reflectedType); | ||||
|                 } | ||||
|             } | ||||
|             else if (metaType.ContainsGenericParameters) | ||||
|             { | ||||
|                 if (nullability.GenericTypeArguments.Length > 0) | ||||
|                 { | ||||
|                     Type[] genericArguments = metaType.GetGenericArguments(); | ||||
|  | ||||
|                     for (int i = 0; i < genericArguments.Length; i++) | ||||
|                     { | ||||
|                         CheckGenericParameters(nullability.GenericTypeArguments[i], metaMember, genericArguments[i], reflectedType); | ||||
|                     } | ||||
|                 } | ||||
|                 else if (nullability.ElementType is { } elementNullability && metaType.IsArray) | ||||
|                 { | ||||
|                     CheckGenericParameters(elementNullability, metaMember, metaType.GetElementType()!, reflectedType); | ||||
|                 } | ||||
|                 // We could also follow this branch for metaType.IsPointer, but since pointers must be unmanaged this | ||||
|                 // will be a no-op regardless | ||||
|                 else if (metaType.IsByRef) | ||||
|                 { | ||||
|                     CheckGenericParameters(nullability, metaMember, metaType.GetElementType()!, reflectedType); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private bool TryUpdateGenericParameterNullability(NullabilityInfo nullability, Type genericParameter, Type? reflectedType) | ||||
|         { | ||||
|             Debug.Assert(genericParameter.IsGenericParameter); | ||||
|  | ||||
|             if (reflectedType is not null | ||||
|                 && !IsGenericMethodParameter(genericParameter) | ||||
|                 && TryUpdateGenericTypeParameterNullabilityFromReflectedType(nullability, genericParameter, reflectedType, reflectedType)) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             if (IsValueTypeOrValueTypeByRef(nullability.Type)) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             var state = NullabilityState.Unknown; | ||||
|             if (CreateParser(genericParameter.GetCustomAttributesData()).ParseNullableState(0, ref state)) | ||||
|             { | ||||
|                 nullability.ReadState = state; | ||||
|                 nullability.WriteState = state; | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             if (GetNullableContext(genericParameter) is { } contextState) | ||||
|             { | ||||
|                 nullability.ReadState = contextState; | ||||
|                 nullability.WriteState = contextState; | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         bool IsGenericMethodParameter(Type genericParameter) => genericParameter.IsGenericParameter && genericParameter.DeclaringMethod != null; | ||||
|  | ||||
|         private bool TryUpdateGenericTypeParameterNullabilityFromReflectedType(NullabilityInfo nullability, Type genericParameter, Type context, Type reflectedType) | ||||
|         { | ||||
|             Debug.Assert(genericParameter.IsGenericParameter && !IsGenericMethodParameter(genericParameter)); | ||||
|  | ||||
|             Type contextTypeDefinition = context.IsGenericType && !context.IsGenericTypeDefinition ? context.GetGenericTypeDefinition() : context; | ||||
|             if (genericParameter.DeclaringType == contextTypeDefinition) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             Type? baseType = contextTypeDefinition.BaseType; | ||||
|             if (baseType is null) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if (!baseType.IsGenericType | ||||
|                 || (baseType.IsGenericTypeDefinition ? baseType : baseType.GetGenericTypeDefinition()) != genericParameter.DeclaringType) | ||||
|             { | ||||
|                 return TryUpdateGenericTypeParameterNullabilityFromReflectedType(nullability, genericParameter, baseType, reflectedType); | ||||
|             } | ||||
|  | ||||
|             Type[] genericArguments = baseType.GetGenericArguments(); | ||||
|             Type genericArgument = genericArguments[genericParameter.GenericParameterPosition]; | ||||
|             if (genericArgument.IsGenericParameter) | ||||
|             { | ||||
|                 return TryUpdateGenericParameterNullability(nullability, genericArgument, reflectedType); | ||||
|             } | ||||
|  | ||||
|             NullableAttributeStateParser parser = CreateParser(contextTypeDefinition.GetCustomAttributesData()); | ||||
|             int nullabilityStateIndex = 1; // start at 1 since index 0 is the type itself | ||||
|             for (int i = 0; i < genericParameter.GenericParameterPosition; i++) | ||||
|             { | ||||
|                 nullabilityStateIndex += CountNullabilityStates(genericArguments[i]); | ||||
|             } | ||||
|             return TryPopulateNullabilityInfo(nullability, parser, ref nullabilityStateIndex); | ||||
|  | ||||
|             static int CountNullabilityStates(Type type) | ||||
|             { | ||||
|                 Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; | ||||
|                 if (underlyingType.IsGenericType) | ||||
|                 { | ||||
|                     int count = 1; | ||||
|                     foreach (Type genericArgument in underlyingType.GetGenericArguments()) | ||||
|                     { | ||||
|                         count += CountNullabilityStates(genericArgument); | ||||
|                     } | ||||
|                     return count; | ||||
|                 } | ||||
|  | ||||
|                 if (underlyingType.HasElementType) | ||||
|                 { | ||||
|                     return (underlyingType.IsArray ? 1 : 0) + CountNullabilityStates(underlyingType.GetElementType()!); | ||||
|                 } | ||||
|  | ||||
|                 return type.IsValueType ? 0 : 1; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private static bool TryPopulateNullabilityInfo(NullabilityInfo nullability, NullableAttributeStateParser parser, ref int index) | ||||
|         { | ||||
|             bool isValueType = IsValueTypeOrValueTypeByRef(nullability.Type); | ||||
|             if (!isValueType) | ||||
|             { | ||||
|                 var state = NullabilityState.Unknown; | ||||
|                 if (!parser.ParseNullableState(index, ref state)) | ||||
|                 { | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|                 nullability.ReadState = state; | ||||
|                 nullability.WriteState = state; | ||||
|             } | ||||
|  | ||||
|             if (!isValueType || (Nullable.GetUnderlyingType(nullability.Type) ?? nullability.Type).IsGenericType) | ||||
|             { | ||||
|                 index++; | ||||
|             } | ||||
|  | ||||
|             if (nullability.GenericTypeArguments.Length > 0) | ||||
|             { | ||||
|                 foreach (NullabilityInfo genericTypeArgumentNullability in nullability.GenericTypeArguments) | ||||
|                 { | ||||
|                     TryPopulateNullabilityInfo(genericTypeArgumentNullability, parser, ref index); | ||||
|                 } | ||||
|             } | ||||
|             else if (nullability.ElementType is { } elementTypeNullability) | ||||
|             { | ||||
|                 TryPopulateNullabilityInfo(elementTypeNullability, parser, ref index); | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         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 | ||||
|             }; | ||||
|  | ||||
|         private static bool IsValueTypeOrValueTypeByRef(Type type) => | ||||
|             type.IsValueType || ((type.IsByRef || type.IsPointer) && type.GetElementType()!.IsValueType); | ||||
|  | ||||
|         private readonly struct NullableAttributeStateParser | ||||
|         { | ||||
|             private static readonly object UnknownByte = (byte)0; | ||||
|  | ||||
|             private readonly object? _nullableAttributeArgument; | ||||
|  | ||||
|             public NullableAttributeStateParser(object? nullableAttributeArgument) | ||||
|             { | ||||
|                 this._nullableAttributeArgument = nullableAttributeArgument; | ||||
|             } | ||||
|  | ||||
|             public static NullableAttributeStateParser Unknown => new(UnknownByte); | ||||
|  | ||||
|             public bool ParseNullableState(int index, ref NullabilityState state) | ||||
|             { | ||||
|                 switch (this._nullableAttributeArgument) | ||||
|                 { | ||||
|                     case byte b: | ||||
|                         state = TranslateByte(b); | ||||
|                         return true; | ||||
|                     case ReadOnlyCollection<CustomAttributeTypedArgument> args | ||||
|                         when index < args.Count && args[index].Value is byte elementB: | ||||
|                         state = TranslateByte(elementB); | ||||
|                         return true; | ||||
|                     default: | ||||
|                         return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user