9

I am trying to analyze with roslyn if a type declaration is a "Nullable Reference" type (C#8)

I was planing on looking if the TypeSyntex was a NullableTypeSyntax and if the ITypeSymbol.IsReferenceType is true.

The following code works for the most part, but is fails when the declared type is generic i.e. List<T>?

void Main()
{
    string text = @"
        #nullable enable
        public class MyClass
        {
            public string? Get() => null;
            public List<string>? GetGeneric() => null;
        }";
    SyntaxTree tree = CSharpSyntaxTree.ParseText(text);
    PortableExecutableReference mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
    CSharpCompilation compilation = CSharpCompilation.Create("RefitCompilation", syntaxTrees: new[] { tree }, references: new[] { mscorlib });
    SemanticModel semanticModel = compilation.GetSemanticModel(tree);

    MethodDeclarationSyntax nonGenericMethodSyntax = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().First();
    ITypeSymbol nonGenericReturnType = semanticModel.GetTypeInfo(nonGenericMethodSyntax.ReturnType).Type;
    bool isNullableTypeReference = nonGenericMethodSyntax.ReturnType is NullableTypeSyntax && nonGenericReturnType.IsReferenceType;
    Console.WriteLine($@"NonGeneric Nullalbe Reference: `{nonGenericMethodSyntax}`
        Is Nullable Type Reference: {isNullableTypeReference}
        Original Definition: {nonGenericReturnType.OriginalDefinition}, 
        IsNullableTypeSyntax: {nonGenericMethodSyntax.ReturnType is NullableTypeSyntax}
        Is Reference Type: {nonGenericReturnType.IsReferenceType}");

    Console.WriteLine();

    MethodDeclarationSyntax genericMethodSyntax = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().Last();
    ITypeSymbol genericReturnType = semanticModel.GetTypeInfo(genericMethodSyntax.ReturnType).Type;
    isNullableTypeReference = genericMethodSyntax.ReturnType is NullableTypeSyntax && genericReturnType.IsReferenceType;
    Console.WriteLine($@"Generic Nullalbe Reference: `{genericMethodSyntax}`
        Is Nullable Type Reference: {isNullableTypeReference}
        Original Definition: {genericReturnType.OriginalDefinition}, 
        IsNullableTypeSyntax: {genericMethodSyntax.ReturnType is NullableTypeSyntax}
        Is Reference Type: {genericReturnType.IsReferenceType}");
}

which outputs

NonGeneric Nullalbe Reference: `public string? Get() => null;`
        Is Nullable Type Reference: True
        Original Definition: string, 
        IsNullableTypeSyntax: True
        Is Reference Type: True

Generic Nullalbe Reference: `public List<string>? GetGeneric() => null;`
        Is Nullable Type Reference: False
        Original Definition: System.Nullable<T>, 
        IsNullableTypeSyntax: True
        Is Reference Type: False

Why is List<T>? Original Definition System.Nullable<T>? and how can I determine if a type is a Nullable Reference type?

Joel Weiss
  • 136
  • 8
  • What's about `semanticModel.GetTypeInfo(nonGenericMethodSyntax.ReturnType).Nullability.Annotation`? – George Alexandria Nov 29 '19 at 14:57
  • 1
    `semanticModel.GetTypeInfo(nonGenericMethodSyntax.ReturnType).Nullability.Annotation` is None. `Nullability.FlowState` is None as well – Joel Weiss Dec 01 '19 at 06:54
  • Is still happen? Which version of Rosalyn do you use? It works for me in the latest version (3.5.0 beta 1) - `IsReferenceType = true` – Dudi Keleti Dec 16 '19 at 13:15
  • @DudiKeleti I tried it with [3.5.0 beta 1](https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp/3.5.0-beta1-final) I still get the same output. if you have linqpad open [this](http://share.linqpad.net/r2g8oq.linq) – Joel Weiss Dec 18 '19 at 03:07

1 Answers1

1

This is not a complete answer but I had the same issue and this is my interpretation of things.

In

string text = @"
    #nullable enable
    public class MyClass
    {
        public string? Get() => null;
        public List<string>? GetGeneric() => null;
    }";

the List<string>? is invalid, the System.Collections.Generic using is missing, therefore it is not possible to determine the type of List<string>.

What happens next is that the unknown List<T>? type is interpreted as nullable struct so we arrive at Nullable<List<string>>.

This case can be checked by interrogating the Nullable for its underlying type and checking its TypeKind, i.e.

((INamedTypeSymbol)type).TypeArguments[0].TypeKind

will yield Error.

Zdeněk Jelínek
  • 2,611
  • 1
  • 17
  • 23