diff --git a/Esiur/Data/NullabilityInfo.cs b/Esiur/Data/NullabilityInfo.cs new file mode 100644 index 0000000..92d79a1 --- /dev/null +++ b/Esiur/Data/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; + +namespace Esiur.Data +{ + /// + /// 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 + } +} \ No newline at end of file diff --git a/Esiur/Data/NullabilityInfoContext.cs b/Esiur/Data/NullabilityInfoContext.cs new file mode 100644 index 0000000..57fc431 --- /dev/null +++ b/Esiur/Data/NullabilityInfoContext.cs @@ -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 +{ + /// + /// 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(); + + 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; + } + + /// + /// 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 + /// + public NullabilityInfo Create(ParameterInfo parameterInfo) + { + if (parameterInfo == null) + throw new ArgumentNullException(); + + EnsureIsSupported(); + + IList 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 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; + } + } + + /// + /// 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 + /// + 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; + } + + ///// + ///// 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 + ///// + //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; + } + + /// + /// 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 + /// + public NullabilityInfo Create(EventInfo eventInfo) + { + if (eventInfo == null) + throw new ArgumentNullException(); + + EnsureIsSupported(); + + return GetNullabilityInfo(eventInfo, eventInfo.EventHandlerType!, CreateParser(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 + /// + public NullabilityInfo Create(FieldInfo fieldInfo) + { + if (fieldInfo == null) + throw new ArgumentNullException(); + + EnsureIsSupported(); + + IList 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 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(); + 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 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 args + when index < args.Count && args[index].Value is byte elementB: + state = TranslateByte(elementB); + return true; + default: + return false; + } + } + } + } +} \ No newline at end of file diff --git a/Esiur/Esiur.csproj b/Esiur/Esiur.csproj index 7f38f52..b3dbf05 100644 --- a/Esiur/Esiur.csproj +++ b/Esiur/Esiur.csproj @@ -56,8 +56,16 @@ + + + + + + + + - + diff --git a/Esiur/Resource/Template/ConstantTemplate.cs b/Esiur/Resource/Template/ConstantTemplate.cs index 8395277..ffb68fd 100644 --- a/Esiur/Resource/Template/ConstantTemplate.cs +++ b/Esiur/Resource/Template/ConstantTemplate.cs @@ -72,7 +72,6 @@ public class ConstantTemplate : MemberTemplate public static ConstantTemplate MakeConstantTemplate(Type type, FieldInfo ci, byte index = 0, string customName = null, TypeTemplate typeTemplate = null) { var annotationAttr = ci.GetCustomAttribute(true); - var nullableAttr = ci.GetCustomAttribute(true); var valueType = RepresentationType.FromType(ci.FieldType); diff --git a/Esiur/Resource/Template/EventTemplate.cs b/Esiur/Resource/Template/EventTemplate.cs index 928ae68..15ce70e 100644 --- a/Esiur/Resource/Template/EventTemplate.cs +++ b/Esiur/Resource/Template/EventTemplate.cs @@ -82,29 +82,32 @@ public class EventTemplate : MemberTemplate var annotationAttr = ei.GetCustomAttribute(true); var listenableAttr = ei.GetCustomAttribute(true); - var nullableAttr = ei.GetCustomAttribute(true); - var nullableContextAttr = ei.GetCustomAttribute(true); - var flags = nullableAttr?.Flags?.ToList() ?? new List(); + evtType.Nullable = new NullabilityInfoContext().Create(ei).ReadState is NullabilityState.Nullable; - // skip the eventHandler class - if (flags.Count > 1) - flags = flags.Skip(1).ToList(); + //var nullableAttr = ei.GetCustomAttribute(true); + //var nullableContextAttr = ei.GetCustomAttribute(true); - if (nullableContextAttr?.Flag == 2) - { - if (flags.Count == 1) - evtType.SetNotNull(flags.FirstOrDefault()); - else - evtType.SetNotNull(flags); - } - else - { - if (flags.Count == 1) - evtType.SetNull(flags.FirstOrDefault()); - else - evtType.SetNull(flags); - } + //var flags = nullableAttr?.Flags?.ToList() ?? new List(); + + //// skip the eventHandler class + //if (flags.Count > 1) + // flags = flags.Skip(1).ToList(); + + //if (nullableContextAttr?.Flag == 2) + //{ + // if (flags.Count == 1) + // evtType.SetNotNull(flags.FirstOrDefault()); + // else + // evtType.SetNotNull(flags); + //} + //else + //{ + // if (flags.Count == 1) + // evtType.SetNull(flags.FirstOrDefault()); + // else + // evtType.SetNull(flags); + //} var et = new EventTemplate(typeTemplate, index, customName ?? ei.Name, ei.DeclaringType != type, evtType); et.EventInfo = ei; diff --git a/Esiur/Resource/Template/FunctionTemplate.cs b/Esiur/Resource/Template/FunctionTemplate.cs index 7f4e2cf..cd05102 100644 --- a/Esiur/Resource/Template/FunctionTemplate.cs +++ b/Esiur/Resource/Template/FunctionTemplate.cs @@ -90,36 +90,42 @@ public class FunctionTemplate : MemberTemplate throw new Exception($"Unsupported type `{mi.ReturnType}` in method `{type.Name}.{mi.Name}` return"); var annotationAttr = mi.GetCustomAttribute(true); - var nullableAttr = mi.GetCustomAttribute(true); - var nullableContextAttr = mi.GetCustomAttribute(true); - var flags = nullableAttr?.Flags?.ToList() ?? new List(); + var nullabilityInfoContext = new NullabilityInfoContext(); - var rtNullableAttr = mi.ReturnTypeCustomAttributes.GetCustomAttributes(typeof(NullableAttribute), true).FirstOrDefault() as NullableAttribute; - var rtNullableContextAttr = mi.ReturnTypeCustomAttributes - .GetCustomAttributes(typeof(NullableContextAttribute), true) - .FirstOrDefault() as NullableContextAttribute - ?? nullableContextAttr; + rtType.Nullable = nullabilityInfoContext.Create(mi.ReturnParameter).WriteState is NullabilityState.Nullable; - var rtFlags = rtNullableAttr?.Flags?.ToList() ?? new List(); - if (rtFlags.Count > 0 && genericRtType == typeof(AsyncReply<>)) - rtFlags.RemoveAt(0); + //var nullableAttr = mi.GetCustomAttribute(true); + //var nullableContextAttr = mi.GetCustomAttribute(true); - if (rtNullableContextAttr?.Flag == 2) - { - if (rtFlags.Count == 1) - rtType.SetNotNull(rtFlags.FirstOrDefault()); - else - rtType.SetNotNull(rtFlags); - } - else - { - if (rtFlags.Count == 1) - rtType.SetNull(rtFlags.FirstOrDefault()); - else - rtType.SetNull(rtFlags); - } + //var flags = nullableAttr?.Flags?.ToList() ?? new List(); + + //var rtNullableAttr = mi.ReturnTypeCustomAttributes.GetCustomAttributes(typeof(NullableAttribute), true).FirstOrDefault() as NullableAttribute; + //var rtNullableContextAttr = mi.ReturnTypeCustomAttributes + // .GetCustomAttributes(typeof(NullableContextAttribute), true) + // .FirstOrDefault() as NullableContextAttribute + // ?? nullableContextAttr; + + //var rtFlags = rtNullableAttr?.Flags?.ToList() ?? new List(); + + //if (rtFlags.Count > 0 && genericRtType == typeof(AsyncReply<>)) + // rtFlags.RemoveAt(0); + + //if (rtNullableContextAttr?.Flag == 2) + //{ + // if (rtFlags.Count == 1) + // rtType.SetNotNull(rtFlags.FirstOrDefault()); + // else + // rtType.SetNotNull(rtFlags); + //} + //else + //{ + // if (rtFlags.Count == 1) + // rtType.SetNull(rtFlags.FirstOrDefault()); + // else + // rtType.SetNull(rtFlags); + //} var args = mi.GetParameters(); @@ -136,27 +142,28 @@ public class FunctionTemplate : MemberTemplate if (argType == null) throw new Exception($"Unsupported type `{x.ParameterType}` in method `{type.Name}.{mi.Name}` parameter `{x.Name}`"); + argType.Nullable = nullabilityInfoContext.Create(x).WriteState is NullabilityState.Nullable; - var argNullableAttr = x.GetCustomAttribute(true); - var argNullableContextAttr = x.GetCustomAttribute(true) ?? nullableContextAttr; + //var argNullableAttr = x.GetCustomAttribute(true); + //var argNullableContextAttr = x.GetCustomAttribute(true) ?? nullableContextAttr; - var argFlags = argNullableAttr?.Flags?.ToList() ?? new List(); + //var argFlags = argNullableAttr?.Flags?.ToList() ?? new List(); - if (argNullableContextAttr?.Flag == 2) - { - if (argFlags.Count == 1) - argType.SetNotNull(argFlags.FirstOrDefault()); - else - argType.SetNotNull(argFlags); - } - else - { - if (rtFlags.Count == 1) - argType.SetNull(argFlags.FirstOrDefault()); - else - argType.SetNull(argFlags); - } + //if (argNullableContextAttr?.Flag == 2) + //{ + // if (argFlags.Count == 1) + // argType.SetNotNull(argFlags.FirstOrDefault()); + // else + // argType.SetNotNull(argFlags); + //} + //else + //{ + // if (rtFlags.Count == 1) + // argType.SetNull(argFlags.FirstOrDefault()); + // else + // argType.SetNull(argFlags); + //} return new ArgumentTemplate() { diff --git a/Esiur/Resource/Template/PropertyTemplate.cs b/Esiur/Resource/Template/PropertyTemplate.cs index fd2e38d..028744b 100644 --- a/Esiur/Resource/Template/PropertyTemplate.cs +++ b/Esiur/Resource/Template/PropertyTemplate.cs @@ -139,6 +139,9 @@ public class PropertyTemplate : MemberTemplate RepresentationType valueType, string readAnnotation = null, string writeAnnotation = null, bool recordable = false) : base(template, index, name, inherited) { + if (name == "RecordNullable") + Console.Beep(); + this.Recordable = recordable; //this.Storage = storage; if (readAnnotation != null) @@ -163,28 +166,32 @@ public class PropertyTemplate : MemberTemplate var annotationAttr = pi.GetCustomAttribute(true); var storageAttr = pi.GetCustomAttribute(true); - var nullableContextAttr = pi.GetCustomAttribute(true); - var nullableAttr = pi.GetCustomAttribute(true); + var nullabilityContext = new NullabilityInfoContext(); + propType.Nullable = nullabilityContext.Create(pi).ReadState is NullabilityState.Nullable; - var flags = nullableAttr?.Flags?.ToList() ?? new List(); - if (flags.Count > 0 && genericPropType == typeof(DistributedPropertyContext<>)) - flags.RemoveAt(0); + // var nullableContextAttr = pi.GetCustomAttribute(true); + // var nullableAttr = pi.GetCustomAttribute(true); - if (nullableContextAttr?.Flag == 2) - { - if (flags.Count == 1) - propType.SetNotNull(flags.FirstOrDefault()); - else - propType.SetNotNull(flags); - } - else - { - if (flags.Count == 1) - propType.SetNull(flags.FirstOrDefault()); - else - propType.SetNull(flags); - } + // var flags = nullableAttr?.Flags?.ToList() ?? new List(); + + // if (flags.Count > 0 && genericPropType == typeof(DistributedPropertyContext<>)) + // flags.RemoveAt(0); + + // if (nullableContextAttr?.Flag == 2) + // { + // if (flags.Count == 1) + // propType.SetNotNull(flags.FirstOrDefault()); + // else + // propType.SetNotNull(flags); + // } + // else + // { + // if (flags.Count == 1) + // propType.SetNull(flags.FirstOrDefault()); + // else + // propType.SetNull(flags); + // } var pt = new PropertyTemplate(typeTemplate, index, customName ?? pi.Name, pi.DeclaringType != type, propType); diff --git a/Esiur/Resource/Template/TypeTemplate.cs b/Esiur/Resource/Template/TypeTemplate.cs index 3ad6019..d30961b 100644 --- a/Esiur/Resource/Template/TypeTemplate.cs +++ b/Esiur/Resource/Template/TypeTemplate.cs @@ -376,7 +376,6 @@ public class TypeTemplate public static ConstantTemplate MakeConstantTemplate(Type type, FieldInfo ci, ExportAttribute exportAttr, byte index = 0, TypeTemplate typeTemplate = null) { var annotationAttr = ci.GetCustomAttribute(true); - var nullableAttr = ci.GetCustomAttribute(true); var valueType = RepresentationType.FromType(ci.FieldType); diff --git a/Test/MyService.cs b/Test/MyService.cs index 79123da..80e5e78 100644 --- a/Test/MyService.cs +++ b/Test/MyService.cs @@ -120,6 +120,8 @@ public partial class MyService [Export] public MyRecord Record => new MyRecord() { Id = 33, Name = "Test", Score = 99.33 }; + [Export] public MyRecord? RecordNullable => new MyRecord() { Id = 33, Name = "Test Nullable", Score = 99.33 }; + [Export] public List IntList => new List() { 1, 2, 3, 4, 5 }; [Export] public IRecord[] RecordsArray => new IRecord[] { new MyRecord() { Id = 22, Name = "Test", Score = 22.1 } };