Roslyn の Semantic Model でシンボル情報を取得

.NET Compiler Platform (Roslyn) で、フィールドやプロパティなどのシンボル情報を取得する方法。

ISymbol から I????Symbol にキャストすれば欲しい情報が得られるかもしれないというお話。

Compilation や Context から取得した Semantic Model から、ノードが示すシンボル情報を取得する場合は、 GetSymbolInfo() メソッドを使います。この情報の中には、正しく取得できた場合は、 Symbol プロパティにそのシンボル情報の本体が入っています。
不完全なコード等、もし何らかの理由で取得できなかった場合は、 CandidateReason プロパティに理由が、 CandidateSymbols プロパティにはシンボルのすべての候補に関する情報が入っています。

シンボル情報の本体からは、それに関するさまざまな情報が得られます。ただ、これは ISymbol インターフェースとして取得するのですが、得たい情報を見るには派生クラスにキャストしなければならない場合もあります。

今回も全体が長いため分割してコードを載せていきます。全体のソースコードはこちらから
まずは解析対象となる埋め込まれたコードを整形して載せます。

var code = @"...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;

class Class1
{
const int A_Constant = 1;
int b_field = 1;
int C_Property { get; set; } = 1;
static int D_StaticProperty { get; set; } = 1;
Action<int> E_FieldAction = (_) => Console.WriteLine();

void Method1()
{
int f_Local = 1;
var c2 = new Class2();
Method2(A_Constant);
Method2(b_field);
Method2(C_Property);
Method2(D_StaticProperty);
Method2(f_Local);
Method2(c2.G_Class2Property);
Method2(Class2.H_Class2StaticProperty);
Method3(E_FieldAction);
Method3(Method2);
}

// these are not checked
void Method2(int arg) { }
void Method3(Action<int> method) { }
}

class Class2
{
public int G_Class2Property { get; set; } = 1;
public static int H_Class2StaticProperty { get; set; } = 1;
}

このプログラムは、様々な種類の要素を引数に渡してメソッドを呼んでいます。

シンボル情報は、誤解を恐れず簡単に言ってしまえば定義元の情報を得られる、というものです。
下の解析を行うプログラムでは、定義の情報を使って、引数に何の要素が渡されているかを調べています。

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
var tree = CSharpSyntaxTree.ParseText(code);
var mscorlib = MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location);
var compilation = CSharpCompilation.Create("SymbolInfo", new[] { tree }, new[] { mscorlib });
var model = compilation.GetSemanticModel(tree);

var root = tree.GetRoot();
var variables = root.DescendantNodes().OfType<ExpressionStatementSyntax>()
.Select(x => x.DescendantNodes().OfType<ArgumentSyntax>().First().Expression);
var symbolInfos = variables.Select(x => model.GetSymbolInfo(x));

foreach (var infos in symbolInfos)
{
var symbol = infos.Symbol;
string typeString = string.Empty;
switch (symbol.Kind)
{
case SymbolKind.Field:
var fieldSymbol = symbol as IFieldSymbol;
typeString = fieldSymbol.Type.ToString();
break;

case SymbolKind.Local:
var localSymbol = symbol as ILocalSymbol;
typeString = localSymbol.Type.ToString();
break;

case SymbolKind.Method:
var methodSymbol = symbol as IMethodSymbol;
typeString
= $"({string.Join(", ", methodSymbol.Parameters.Select(x => x.Type.ToString()))})"
+ $" -> {methodSymbol.ReturnType}";
break;

case SymbolKind.Property:
var propertySymbol = symbol as IPropertySymbol;
typeString = propertySymbol.Type.ToString();
break;

default:
break;
}

Console.WriteLine(symbol.Name);
Console.WriteLine($" -> {symbol.Kind}, {symbol.ContainingType}, {typeString}");
Console.WriteLine();
}

お決まりの処理で Semantic Model を取得し、メソッドに渡されている引数を列挙し、それぞれについてシンボル情報を取得しています。
シンボル情報については種類( Kind プロパティ)で場合分けをして、その先でそれぞれダウンキャストしています。
SymbolKind 列挙体には他にも項目がありますが、メジャーと思われる 4 つのものだけ使っています。

結果は下のようになりました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
A_Constant
-> Field, Class1, int

b_field
-> Field, Class1, int

C_Property
-> Property, Class1, int

D_StaticProperty
-> Property, Class1, int

f_Local
-> Local, Class1, int

G_Class2Property
-> Property, Class2, int

H_Class2StaticProperty
-> Property, Class2, int

E_FieldAction
-> Field, Class1, System.Action<int>

Method2
-> Method, Class1, (int) -> void

左から、シンボルの種類、属しているクラス、型の名前です。メソッドを渡している場合は型情報を直接は得られないので、別のプロパティを参照して擬似的に表示しています。