vendredi 23 juillet 2021

Discovering the NullabilityInfo of a generic parameter?

I have successfully read and used the NullableAttribute/NullableContextAttribute (based on https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md and too much trial and errors) for properties, fields and method parameters.

However, I'm failing to analyze the nullabilities of a generic parameter. My goal is to obtain the nullability information of the IA type parameter (the tuple):

interface ICommand<T> { }

interface IA : ICommand<(string,List<string?>?)> { }

However, the Nullable and NullableContext attributes are clearly not enough. The code below dumps these attributes on the type tree:

        [Test]
        public void type_in_generic_definition()
        {
            var b = new StringBuilder();
            Dump( typeof( IA ), b, Environment.NewLine );
            Dump( typeof( IA ).GetInterfaces()[0], b, Environment.NewLine );
            Console.WriteLine( b );
        }

        void Dump( Type type, StringBuilder w, string newline )
        {
            newline = newline + "  ";
            w.Append( type.Name ).Append( newline );
            var p = GetNullableProfile( type );
            w.Append( $"Nullable: {(p != null ? string.Join( null, p.Select( v => v.ToString() ) ) : "null")}" ).Append( newline );
            var c = GetNullableContextValue( type );
            w.Append( $"NullableContext: {(c != null ? c.ToString() : "null")}" ).Append( newline );
            var d = GetNullableContextValueFromDeclaringType( type );
            w.Append( $"NullableContext (DeclaringType): {(d != null ? d.ToString() : "null")}" ).Append( newline );
            if( type.IsGenericType )
            {
                foreach( var sub in type.GetGenericArguments() )
                {
                    Dump( sub, w, newline );
                }
            }
            newline = newline.Substring( 0, newline.Length - 2 );
            w.Append( newline );
        }

        byte[]? GetNullableProfile( Type t )
        {
            var a = t.CustomAttributes.FirstOrDefault( a => a.AttributeType.Name == "NullableAttribute" && a.AttributeType.Namespace == "System.Runtime.CompilerServices" );
            if( a == null ) return null;
            object? data = a.ConstructorArguments[0].Value;
            Debug.Assert( data != null );
            if( data is byte b ) return new[] { b };
            return ((IEnumerable<CustomAttributeTypedArgument>)data).Select( a => (byte)a.Value! ).ToArray();
        }

        byte? GetNullableContextValue( Type t )
        {
            var a = t.CustomAttributes.FirstOrDefault( a => a.AttributeType.Name == "NullableContextAttribute" && a.AttributeType.Namespace == "System.Runtime.CompilerServices" );
            return a == null
                    ? null
                    : (byte)a.ConstructorArguments[0].Value!;
        }

        byte? GetNullableContextValueFromDeclaringType( Type t )
        {
            var parent = t.DeclaringType;
            while( parent != null )
            {
                var found = GetNullableContextValue( parent );
                if( found.HasValue ) return found;
                parent = parent.DeclaringType;
            }
            return null;
        }

This gives me this description that doesn't make any sense (at least to me) regarding the actual nullabilities of (string,List<string?>?) (I've commented the lines after the //).

    IA
      Nullable: null
      NullableContext: null
      NullableContext (DeclaringType): 1       // I'm working in a NRT aware context where 
                                               // reference types are by default not annotated
                                               // (not null). Ok.
      
    ICommand`1
      Nullable: null
      NullableContext: 2                       // Q0: On what scope does this apply?
      NullableContext (DeclaringType): 1       // Ok. It's my context.
      ValueTuple`2                             
        Nullable: null                         // Ouch! I was expecting a "122" here :(
        NullableContext: null
        NullableContext (DeclaringType): null
        String
          Nullable: 0                           // Q1: Explicit oblivious here. Was expecting 1!
          NullableContext: 1                    // Q2: Scope? Is it the 1 I'm after?
          NullableContext (DeclaringType): null
          
        List`1
          Nullable: 0                           // The same as the string!
          NullableContext: 1                    // But the list IS nullable!
          NullableContext (DeclaringType): null
          String
            Nullable: 0                            // The inner string is nullable :(
            NullableContext: 1
            NullableContext (DeclaringType): null

Where should I look for this? What did I miss?





Aucun commentaire:

Enregistrer un commentaire