How to match type symbol names as returned by Roslyn semantic model to those returned by Mono.Cecil?

Viewed 283

I have the following piece of code:

var paramDeclType = m_semanticModel.GetTypeInfo(paramDecl.Type).Type;

Where paramDeclType.ToString() returns

System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<int>>

I also have the same information from Mono.Cecil - paramDef.ParameterType.FullName returns

System.Collections.Generic.Dictionary`2<System.String,System.Collections.Generic.List`1<System.Int32>>

I would like to compare the type names coming from these two different sources.

I would like to avoid elaborate regex parsing, if possible.

Ideas are welcome.

P.S.

In the meantime, I am going to look for Mono.Cecil replacement from Microsoft - System.Reflection.Metadata. Maybe two libraries from Microsoft would be able to produce compatible type names.

2 Answers

AFAIK, SRM is way more low-level than Mono.Cecil. I've not used it enough to be 100% sure, but AFICS, in SRM you need to provide your own type system, meaning, you'll get type tokens, method tokens, etc, and how you represent that in your program is up to you (this looks way more complex than using Cecil). (You can take a look in ILSpy code https://github.com/icsharpcode/ILSpy/tree/master/ICSharpCode.Decompiler to get an idea)

I don't think that converting from Roslyn names -> Cecil names is that hard (of course, depending on your needs). You could try:

  1. see if it is possible to ask Roslyn to always use the BLC type names (instead of the C# primitive type names)

  2. Remove the ranking information (1, 2, etc) and maybe add <> if necessary

  3. Replace the nested type separator used by Cecil (/) with a dot (.)

  4. Maybe some other cases... :)

That being said, another alternative is to try to map the names to its System.Type equivalent; once you have a System.Type you can call Module.Import() in Cecil and get a TypeRefernce back and compare its full name (unfortunately cannot compare two TypeReferences by equality).

Obs: tried to add this as a comment, but it is too long :)

You can use something similar to the code below to figure out the fully qualified type name:

using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var st = CSharpSyntaxTree.ParseText("class Foo { int i; }");
            var v = st.GetRoot().DescendantNodesAndSelf().OfType<VariableDeclarationSyntax>().Single().Type;
            var pts = (PredefinedTypeSyntax) v;

            var Mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
            var compilation = CSharpCompilation.Create("MyCompilation", syntaxTrees: new[] { st }, references: new[] { Mscorlib });
            var model = compilation.GetSemanticModel(st);

            var symbol = model.GetTypeInfo(pts);

            System.Console.WriteLine(symbol.Type.ContainingNamespace.Name + "." + symbol.Type.Name);
            
            // or even something like...
            var intType = model.Compilation.GetTypeByMetadataName(typeof(int).FullName);
            Console.WriteLine(v.ToString() + "  " + v.GetType().FullName + " " + (symbol.Type.SpecialType == intType.SpecialType));
        }
    }
}

I would like to share the function I currently use:

private const string GENERIC_ARG_COUNT_PATTERN = @"`\d+";
private static readonly Regex s_genericArgCountRegex = new Regex(GENERIC_ARG_COUNT_PATTERN);


public static bool IsSameType(this TypeReference typeRef, ITypeSymbol typeSymbol, bool isByRef)
{
    if (isByRef || typeRef.IsByReference)
    {
        return isByRef && typeRef.IsByReference && DoIsSameType(((ByReferenceType)typeRef).ElementType, typeSymbol);
    }

    return DoIsSameType(typeRef, typeSymbol);

    static bool DoIsSameType(TypeReference typeRef, ITypeSymbol typeSymbol)
    {
        var arrayTypeSymbol = typeSymbol as IArrayTypeSymbol;
        var arrayTypeRef = typeRef as ArrayType;
        if (arrayTypeSymbol != null || arrayTypeRef != null)
        {
            return arrayTypeSymbol != null
                && arrayTypeRef != null
                && arrayTypeSymbol.Rank == arrayTypeRef.Rank
                && DoIsSameType(arrayTypeRef.ElementType, arrayTypeSymbol.ElementType);
        }

        // Optimistic approach
        var declFullName = typeSymbol.ToString();
        var typeRefFullName = typeRef.FullName;
        if (typeRefFullName.Contains('`'))
        {
            typeRefFullName = s_genericArgCountRegex.Replace(typeRef.FullName, "");
        }
        if (declFullName == typeRefFullName)
        {
            return true;
        }

        // Built-in types - either top level or as generic arguments.
        if (typeRef is GenericInstanceType genTypeRef)
        {
            if (!(typeSymbol is INamedTypeSymbol namedTypeSymbol) ||
                !namedTypeSymbol.IsGenericType ||
                genTypeRef.ElementType.Name != namedTypeSymbol.ConstructedFrom.MetadataName ||
                genTypeRef.GenericArguments.Count != namedTypeSymbol.TypeArguments.Length ||
                genTypeRef.ElementType.Namespace != namedTypeSymbol.ConstructedFrom.ContainingNamespace.ToString())
            {
                return false;
            }
            for (int i = 0; i < genTypeRef.GenericArguments.Count; ++i)
            {
                if (!DoIsSameType(genTypeRef.GenericArguments[i], namedTypeSymbol.TypeArguments[i]))
                {
                    return false;
                }
            }
            return true;
        }

        return
            typeRef.Name == typeSymbol.Name &&
            typeRef.IsBuiltInType() &&
            typeSymbol.ContainingNamespace.Name == "System";
    }
}

public static bool IsBuiltInType(this TypeReference t)
{
    return t.Scope.Name == "mscorlib" && DoIsBuiltInType(t);

    static bool DoIsBuiltInType(TypeReference t) => t switch
    {
        ArrayType arrayType => DoIsBuiltInType(arrayType.ElementType),
        ByReferenceType byReferenceType => DoIsBuiltInType(byReferenceType.ElementType),
        _ => t.IsPrimitive || t.Name == "Object" || t.Name == "Decimal" || t.Name == "String",
    };
}
Related