Chapter 27 - The Roslyn Compiler
Syntax Trees
Using the LINQPad Syntax Visualizer
/* In LINQPad, the output window shows you the Roslyn Syntax Tree automatically.
Run this query, then click 'Tree'.
Note that you can update the source code and click the 'Refresh' button to
update the syntax tree without re-running the query. */
class Test
{
static void Main() => Console.WriteLine ("Hello");
}
Obtaining a syntax tree
SyntaxTree tree = CSharpSyntaxTree.ParseText (@"class Test
{
static void Main() => Console.WriteLine (""Hello"");
}");
Console.WriteLine (tree.ToString());
tree.DumpSyntaxTree(); // Displays Syntax Tree Visualizer in LINQPad
Traversing a tree - root node
var tree = CSharpSyntaxTree.ParseText (@"class Test
{
static void Main() => Console.WriteLine (""Hello"");
}");
SyntaxNode root = tree.GetRoot();
Console.WriteLine (root.GetType().Name); // CompilationUnitSyntax
Traversing a tree - ClassDeclarationSyntax members
var tree = CSharpSyntaxTree.ParseText (@"class Test
{
static void Main() => Console.WriteLine (""Hello"");
}");
SyntaxNode root = tree.GetRoot();
var cds = (ClassDeclarationSyntax) root.ChildNodes().Single();
foreach (MemberDeclarationSyntax member in cds.Members)
Console.WriteLine (member.ToString());
Traversing a tree - descendant tokens
var tree = CSharpSyntaxTree.ParseText (@"class Test
{
static void Main() => Console.WriteLine (""Hello"");
}");
SyntaxNode root = tree.GetRoot();
var cds = (ClassDeclarationSyntax) root.ChildNodes().Single();
root.DescendantTokens()
.Select (t => new { Kind = t.Kind(), t.Text })
.Dump();
Traversing a tree - finding a method
var tree = CSharpSyntaxTree.ParseText (@"class Test
{
static void Main() => Console.WriteLine (""Hello"");
}");
SyntaxNode root = tree.GetRoot();
root.DescendantNodes()
.First (m => m.Kind() == SyntaxKind.MethodDeclaration)
.Dump(1);
root.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
.Single()
.Dump(1);
root.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
.Single (m => m.Identifier.Text == "Main")
.Dump(1);
root.DescendantNodes()
.First (m =>
m.Kind() == SyntaxKind.MethodDeclaration &&
m.ChildTokens().Any (t => t.Kind() == SyntaxKind.IdentifierToken && t.Text == "Main"))
.Dump(1);
CSharpSyntaxWalker
void Main()
{
var tree = CSharpSyntaxTree.ParseText (@"class Test
{
static void Main()
{
if (true)
if (true);
};
}");
SyntaxNode root = tree.GetRoot();
var ifCounter = new IfCounter ();
ifCounter.Visit (root);
Console.WriteLine ($"I found {ifCounter.IfCount} if statements");
root.DescendantNodes().OfType<IfStatementSyntax>().Count().Dump ("Functional equivalent");
}
class IfCounter : CSharpSyntaxWalker
{
public int IfCount { get; private set; }
public override void VisitIfStatement (IfStatementSyntax node)
{
IfCount++;
// Call the base method if you want to descend into children.
base.VisitIfStatement (node);
}
}
The White Walker
void Main()
{
var tree = CSharpSyntaxTree.ParseText (@"class Test
{
static void Main()
{
if (true)
if (true);
};
}");
SyntaxNode root = tree.GetRoot();
var whiteWalker = new WhiteWalker ();
whiteWalker.Visit (root);
whiteWalker.SpaceCount.Dump ("spaces");
}
class WhiteWalker : CSharpSyntaxWalker // Counts space characters
{
public int SpaceCount { get; private set; }
public WhiteWalker() : base (SyntaxWalkerDepth.Trivia) { }
public override void VisitTrivia (SyntaxTrivia trivia)
{
SpaceCount += trivia.ToString().Count (char.IsWhiteSpace);
base.VisitTrivia (trivia);
}
}
Trivia
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static /*comment*/ void Main() {}
}");
SyntaxNode root = tree.GetRoot();
// Find the static keyword token:
var method = root.DescendantTokens().Single (t =>
t.Kind() == SyntaxKind.StaticKeyword);
// Print out the trivia around the static keyword token:
foreach (SyntaxTrivia t in method.LeadingTrivia)
Console.WriteLine (new { Kind = "Leading " + t.Kind(), t.Span.Length });
foreach (SyntaxTrivia t in method.TrailingTrivia)
Console.WriteLine (new { Kind = "Trailing " + t.Kind(), t.Span.Length });
Preprocessor directives
// Take a look at the syntax tree in the output window. The grey text is parsed as DisabledTextTrivia.
#define FOO
#if FOO
Console.WriteLine ("FOO is defined");
#else
Console.WriteLine ("FOO is not defined");
#endif
Structured trivia
var tree = CSharpSyntaxTree.ParseText (@"#define FOO");
// In LINQPad:
tree.DumpSyntaxTree(); // LINQPad displays structured trivia in Visualizer
SyntaxNode root = tree.GetRoot();
var trivia = root.DescendantTrivia().First();
Console.WriteLine (trivia.HasStructure); // True
Console.WriteLine (trivia.GetStructure().Kind()); // DefineDirectiveTrivia
Structured trivia - navigating preprocessor directives
var tree = CSharpSyntaxTree.ParseText (@"#define FOO");
SyntaxNode root = tree.GetRoot();
Console.WriteLine (root.ContainsDirectives); // True
// directive is the root node of the structured trivia:
var directive = root.GetFirstDirective();
Console.WriteLine (directive.Kind()); // DefineDirectiveTrivia
Console.WriteLine (directive.ToString()); // #define FOO
// If there were more directives, we could get to them as follows:
Console.WriteLine (directive.GetNextDirective()); // (null)
var hashDefine = (DefineDirectiveTriviaSyntax)root.GetFirstDirective();
Console.WriteLine (hashDefine.Name.Text); // FOO
Transforming trees - handling changes to source code
SourceText sourceText = SourceText.From ("class Program {}");
var tree = CSharpSyntaxTree.ParseText (sourceText);
var newSource = sourceText.Replace (0, 5, "struct");
var newTree = tree.WithChangedText (newSource);
Console.WriteLine (newTree.ToString()); // struct Program {}
Transforming trees - visualizing node
CSharpSyntaxTree.ParseText ("using System.Text;").DumpSyntaxTree();
Transforming trees - creating nodes and tokens
QualifiedNameSyntax qualifiedName = SyntaxFactory.QualifiedName (
SyntaxFactory.IdentifierName ("System"),
SyntaxFactory.IdentifierName ("Text"));
UsingDirectiveSyntax usingDirective =
SyntaxFactory.UsingDirective (qualifiedName);
Console.WriteLine (usingDirective.ToFullString()); // usingSystem.Text;
// Explicitly adding trivia:
usingDirective = usingDirective.WithUsingKeyword (
usingDirective.UsingKeyword.WithTrailingTrivia (
SyntaxFactory.Whitespace (" ")));
Console.WriteLine (usingDirective.ToFullString()); // using System.Text;
var existingTree = CSharpSyntaxTree.ParseText ("class Program {}");
var existingUnit = (CompilationUnitSyntax)existingTree.GetRoot();
var unitWithUsing = existingUnit.AddUsings (usingDirective);
var treeWithUsing = CSharpSyntaxTree.Create (unitWithUsing.NormalizeWhitespace());
treeWithUsing.ToString().Dump ("Final result");
Transforming trees - creating a new tree
QualifiedNameSyntax qualifiedName = SyntaxFactory.QualifiedName (
SyntaxFactory.IdentifierName ("System"),
SyntaxFactory.IdentifierName ("Text"));
UsingDirectiveSyntax usingDirective =
SyntaxFactory.UsingDirective (qualifiedName);
var unit = SyntaxFactory.CompilationUnit().AddUsings (usingDirective);
// Create a simple empty class definition:
unit = unit.AddMembers (SyntaxFactory.ClassDeclaration ("Program"));
var tree = CSharpSyntaxTree.Create (unit.NormalizeWhitespace());
Console.WriteLine (tree.ToString());
Transforming trees - CSharpSyntaxRewriter
void Main()
{
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static void Main() { Test(); }
static void Test() { }
}");
var rewriter = new MyRewriter();
var newRoot = rewriter.Visit (tree.GetRoot());
Console.WriteLine (newRoot.ToFullString());
}
class MyRewriter : CSharpSyntaxRewriter
{
public override SyntaxNode VisitMethodDeclaration
(MethodDeclarationSyntax node)
{
// "Replace" the method’s identifier with an uppercase version:
return node.WithIdentifier (
SyntaxFactory.Identifier (
node.Identifier.LeadingTrivia, // Preserve old trivia
node.Identifier.Text.ToUpperInvariant(),
node.Identifier.TrailingTrivia)); // Preserve old trivia
}
}
Compilations and Semantic Models
Creating a Compilation
var compilation = CSharpCompilation.Create ("test");
compilation = compilation.WithOptions (
new CSharpCompilationOptions (OutputKind.ConsoleApplication));
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static void Main() => System.Console.WriteLine (""Hello"");
}");
compilation = compilation.AddSyntaxTrees (tree);
string trustedAssemblies = (string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES");
var trustedAssemblyPaths = trustedAssemblies.Split (Path.PathSeparator);
var references = trustedAssemblyPaths.Select (path => MetadataReference.CreateFromFile (path));
compilation = compilation.AddReferences (references);
// Or, in one step:
compilation = CSharpCompilation
.Create ("test")
.WithOptions (new CSharpCompilationOptions (OutputKind.ConsoleApplication))
.AddSyntaxTrees (tree)
.AddReferences (references);
compilation.GetDiagnostics().Dump ("Errors and warnings");
Emitting
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static void Main() => System.Console.WriteLine (""Hello"");
}");
string trustedAssemblies = (string)AppContext.GetData ("TRUSTED_PLATFORM_ASSEMBLIES");
var trustedAssemblyPaths = trustedAssemblies.Split (Path.PathSeparator);
var references = trustedAssemblyPaths.Select (path => MetadataReference.CreateFromFile (path));
var compilation = CSharpCompilation
.Create ("test")
.WithOptions (new CSharpCompilationOptions (OutputKind.ConsoleApplication))
.AddSyntaxTrees (tree)
.AddReferences (references);
string outputPath = "test.dll";
EmitResult result = compilation.Emit (outputPath);
Console.WriteLine (result.Success);
// Execute the program we just compiled.
Util.Cmd (@"dotnet.exe", "\"" + outputPath + "\"");
Querying the semantic model
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static void Main() => System.Console.WriteLine (123);
}");
var references = ((string)AppContext.GetData ("TRUSTED_PLATFORM_ASSEMBLIES"))
.Split (Path.PathSeparator)
.Select (path => MetadataReference.CreateFromFile (path));
var compilation = CSharpCompilation.Create ("test")
.AddReferences (references)
.AddSyntaxTrees (tree);
SemanticModel model = compilation.GetSemanticModel (tree);
var writeLineNode = tree.GetRoot().DescendantTokens().Single (
t => t.Text == "WriteLine").Parent;
SymbolInfo symbolInfo = model.GetSymbolInfo (writeLineNode);
Console.WriteLine (symbolInfo.Symbol.ToString()); // System.Console.WriteLine(int)
symbolInfo.Symbol.Dump ("In more detail", 2);
Semantic model - symbols
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static void Main() => System.Console.WriteLine (123);
}");
var references = ((string)AppContext.GetData ("TRUSTED_PLATFORM_ASSEMBLIES"))
.Split (Path.PathSeparator)
.Select (path => MetadataReference.CreateFromFile (path));
var compilation = CSharpCompilation.Create ("test")
.AddReferences (references)
.AddSyntaxTrees (tree);
SemanticModel model = compilation.GetSemanticModel (tree);
var writeLineNode = tree.GetRoot().DescendantTokens().Single (
t => t.Text == "WriteLine").Parent;
ISymbol symbol = model.GetSymbolInfo (writeLineNode).Symbol;
Console.WriteLine (symbol.Name); // WriteLine
Console.WriteLine (symbol.Kind); // Method
Console.WriteLine (symbol.IsStatic); // True
Console.WriteLine (symbol.ContainingType.Name); // Console
var method = (IMethodSymbol)symbol;
Console.WriteLine (method.ReturnType.ToString()); // void
Console.WriteLine (symbol.Language); // C#
var location = symbol.Locations.First();
Console.WriteLine (location.Kind); // MetadataFile
Console.WriteLine (location.MetadataModule
== compilation.References.Single()); // Trye
Console.WriteLine (location.SourceTree == null); // True
Console.WriteLine (location.SourceSpan); // [0..0)
symbol.ContainingType.GetMembers ("WriteLine").OfType<IMethodSymbol>()
.Select (m => m.ToString()).Dump();
Semantic model - declared symbols
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static void Main() => System.Console.WriteLine (123);
}");
var references = ((string)AppContext.GetData ("TRUSTED_PLATFORM_ASSEMBLIES"))
.Split (Path.PathSeparator)
.Select (path => MetadataReference.CreateFromFile (path));
var compilation = CSharpCompilation.Create ("test")
.AddReferences (references)
.AddSyntaxTrees (tree);
SemanticModel model = compilation.GetSemanticModel (tree);
var mainMethod = tree.GetRoot().DescendantTokens().Single (
t => t.Text == "Main").Parent;
SymbolInfo symbolInfo = model.GetSymbolInfo (mainMethod);
Console.WriteLine (symbolInfo.Symbol == null); // True
Console.WriteLine (symbolInfo.CandidateSymbols.Length); // 0
ISymbol symbol = model.GetDeclaredSymbol (mainMethod);
symbol.Dump(1);
Semantic model - declared symbol local variable
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static void Main()
{
int xyz = 123;
}
}");
var references = ((string)AppContext.GetData ("TRUSTED_PLATFORM_ASSEMBLIES"))
.Split (Path.PathSeparator)
.Select (path => MetadataReference.CreateFromFile (path));
var compilation = CSharpCompilation.Create ("test")
.AddReferences (references)
.AddSyntaxTrees (tree);
SemanticModel model = compilation.GetSemanticModel (tree);
SyntaxNode variableDecl = tree.GetRoot().DescendantTokens().Single (
t => t.Text == "xyz").Parent;
var local = (ILocalSymbol) model.GetDeclaredSymbol (variableDecl);
Console.WriteLine (local.Type.ToString()); // int
Console.WriteLine (local.Type.BaseType.ToString()); // System.ValueType
// If the following line throws an InvalidOperationException, you will need to update to the latest LINQPad beta.
local.Type.Dump ("More detail", 1);
Semantic model - TypeInfo
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static void Main()
{
var now = System.DateTime.Now;
System.Console.WriteLine (now - now);
}
}");
var references = ((string)AppContext.GetData ("TRUSTED_PLATFORM_ASSEMBLIES"))
.Split (Path.PathSeparator)
.Select (path => MetadataReference.CreateFromFile (path));
var compilation = CSharpCompilation.Create ("test")
.AddReferences (references)
.AddSyntaxTrees (tree);
SemanticModel model = compilation.GetSemanticModel (tree);
SyntaxNode binaryExpr = tree.GetRoot().DescendantTokens().Single (
t => t.Text == "-").Parent;
var typeInfo = model.GetTypeInfo (binaryExpr);
Console.WriteLine (typeInfo.Type.ToString()); // System.TimeSpan
Console.WriteLine (typeInfo.ConvertedType.ToString()); // object
Semantic model - looking up symbols
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static void Main()
{
int x = 123, y = 234;
}
}");
var references = ((string)AppContext.GetData ("TRUSTED_PLATFORM_ASSEMBLIES"))
.Split (Path.PathSeparator)
.Select (path => MetadataReference.CreateFromFile (path));
var compilation = CSharpCompilation.Create ("test")
.AddReferences (references)
.AddSyntaxTrees (tree);
SemanticModel model = compilation.GetSemanticModel (tree);
// Look for available symbols at start of 6th line:
int index = tree.GetText().Lines[5].Start;
foreach (ISymbol symbol in model.LookupSymbols (index))
Console.WriteLine (symbol.ToString());
// If the following line throws an InvalidOperationException, you will need to update to the latest LINQPad beta.
model.LookupSymbols (index).Dump ("More detail", 1);
Renaming a symbol
void Main()
{
var tree = CSharpSyntaxTree.ParseText (@"class Program
{
static Program() {}
public Program() {}
static void Main()
{
Program p = new Program();
p.Foo();
}
static void Foo() => Bar();
static void Bar() => Foo();
}
");
var references = ((string)AppContext.GetData ("TRUSTED_PLATFORM_ASSEMBLIES"))
.Split (Path.PathSeparator)
.Select (path => MetadataReference.CreateFromFile (path));
var compilation = CSharpCompilation.Create ("test")
.AddReferences (references)
.AddSyntaxTrees (tree);
var model = compilation.GetSemanticModel (tree);
var tokens = tree.GetRoot().DescendantTokens();
// Rename the Program class to Program2:
SyntaxToken program = tokens.First (t => t.Text == "Program");
Console.WriteLine (RenameSymbol (model, program, "Program2").ToString());
// Rename the Foo method to Foo2:
SyntaxToken foo = tokens.Last (t => t.Text == "Foo");
Console.WriteLine (RenameSymbol (model, foo, "Foo2").ToString());
// Rename the p local variable to p2:
SyntaxToken p = tokens.Last (t => t.Text == "p");
Console.WriteLine (RenameSymbol (model, p, "p2").ToString());
}
public SyntaxTree RenameSymbol (SemanticModel model, SyntaxToken token,
string newName)
{
IEnumerable<TextSpan> renameSpans =
GetRenameSpans (model, token).OrderBy (s => s);
SourceText newSourceText = model.SyntaxTree.GetText().WithChanges (
renameSpans.Select (s => new TextChange (s, newName)));
return model.SyntaxTree.WithChangedText (newSourceText);
}
public IEnumerable<TextSpan> GetRenameSpans (SemanticModel model,
SyntaxToken token)
{
var node = token.Parent;
ISymbol symbol =
model.GetSymbolInfo (node).Symbol ??
model.GetDeclaredSymbol (node);
if (symbol == null) return null; // No symbol to rename.
var definitions =
from location in symbol.Locations
where location.SourceTree == node.SyntaxTree
select location.SourceSpan;
var usages =
from t in model.SyntaxTree.GetRoot().DescendantTokens ()
where t.Text == symbol.Name
let s = model.GetSymbolInfo (t.Parent).Symbol
where s == symbol
select t.Span;
if (symbol.Kind != SymbolKind.NamedType)
return definitions.Concat (usages);
var structors =
from type in model.SyntaxTree.GetRoot().DescendantNodes()
.OfType<TypeDeclarationSyntax>()
where type.Identifier.Text == symbol.Name
let declaredSymbol = model.GetDeclaredSymbol (type)
where declaredSymbol == symbol
from method in type.Members
let constructor = method as ConstructorDeclarationSyntax
let destructor = method as DestructorDeclarationSyntax
where constructor != null || destructor != null
let identifier = constructor?.Identifier ?? destructor.Identifier
select identifier.Span;
return definitions.Concat (usages).Concat (structors);
}