How can I get the most fully reduced type name of an arbitrary type in a given namespace context using Roslyn?

Viewed 277

I am writing a function that takes any concrete or constructed Type, say for example typeof(ValueTuple<Nullable<System.Int32>, double, List<string>>) and returns a string that is the reduced C# syntax representation of that type (i.e. (int?, double, List<string>) in this example).

Here's what I have so far:

public static string ToCSharpString(this Type type, string[] usingNamespaces = null, Assembly[] usingAssemblies = null)
{
    var compilationUnit = SyntaxFactory.CompilationUnit();
    if (usingNamespaces != null)
    {
        compilationUnit = compilationUnit.AddUsings(
            Array.ConvertAll(usingNamespaces, n => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(n))));
    }
    else
    {
        compilationUnit = compilationUnit.AddUsings(
            SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System")));
    }

    MetadataReference[] metadataReferences;
    if (usingAssemblies != null)
    {
        metadataReferences = Array.ConvertAll(usingAssemblies, u => MetadataReference.CreateFromFile(u.Location));
    }
    else
    {
        metadataReferences = new[]
        {
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            MetadataReference.CreateFromFile(type.Assembly.Location)
        };
    }

    TypeSyntax typeName;
    using (var provider = new CSharpCodeProvider())
    {
        typeName = SyntaxFactory.ParseTypeName(provider.GetTypeOutput(new CodeTypeReference(type)));
    }

    var field = SyntaxFactory.FieldDeclaration(
        SyntaxFactory.VariableDeclaration(typeName).WithVariables(
            SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
                SyntaxFactory.VariableDeclarator(
                    SyntaxFactory.Identifier("field")))));
    compilationUnit = compilationUnit.AddMembers(
        SyntaxFactory.ClassDeclaration("MyClass").AddMembers(
            field))
        .NormalizeWhitespace();

    var tree = compilationUnit.SyntaxTree;
    var compilation = CSharpCompilation.Create("MyAssembly", new[] { tree }, metadataReferences);
    var semanticModel = compilation.GetSemanticModel(tree);
    var root = tree.GetRoot();

    var typeSymbol = semanticModel.GetDeclaredSymbol(compilationUnit
        .DescendantNodes().OfType<ClassDeclarationSyntax>().Single()
        .Members.OfType<FieldDeclarationSyntax>().Single()
        .Declaration.Type);

    return typeSymbol.ToDisplayString(new SymbolDisplayFormat(
        typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
        miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes));
}

I'm attempting to string together a couple of known methods for converting types.

  • Type -> Fully Qualified Name
    • via CodeDOM's CSharpCodeProvider.GetTypeOutput
  • Fully Qualified Name -> TypeSyntax
    • via Rosly's SyntaxFactory.ParseTypeName

Now I would like to use .ToDisplayString() with a few different options, but I can't find a type node for my type that doesn't return null from the semantic model.

How can I format a TypeSyntax using a SymbolDisplayFormat?

In addition, I expect that this will change System.Int32 -> int, however, it won't automatically fix instances of Nullable<T> or ValueTuple<T1...>

How can I execute the appropriate code analysis rules to replace these type names?

1 Answers

The docs state GetDeclaredSymbol only works for

any type derived from MemberDeclarationSyntax, TypeDeclarationSyntax, EnumDeclarationSyntax, NamespaceDeclarationSyntax, ParameterSyntax, TypeParameterSyntax, or the alias part of a UsingDirectiveSyntax

Yours appears to be QualifiedNameSyntax which seems pretty much in line with the expected input but clearly means something else to Roslyn (I will admit I didn't bother checking whether it actually inherits from one of the expected types).

However, getting TypeInfo instead, seems to get your particular example working:

    var typeSymbol = semanticModel.GetTypeInfo(compilationUnit // just changed this method
        .DescendantNodes().OfType<ClassDeclarationSyntax>().Single()
        .Members.OfType<FieldDeclarationSyntax>().Single()
        .Declaration.Type); 
    return typeSymbol.Type.ToDisplayString(new SymbolDisplayFormat(
        typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
        miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes)); // I'm getting "(int?, double, List)" here
Related