How it works...

In this recipe, we wrote a C# console application based on the Roslyn Compiler API to create a compilation and analyze and display the diagnostics and overload resolution semantics for invocation expressions. These operations are very similar to what a C# compiler would do when compiling the source code. Let's walk through the code and understand how we implemented these operations:

public static void Main(string[] args)
{
// Parse arguments to get source file.
var filePath = ParseArguments(args);
if (filePath == null)
{
return;
}

// Parse text and create a compilation.
var compilation = CreateCompilation(filePath);

// Display diagnostics in the compilation.
DisplayDiagnostics(compilation);

// Display semantic information about invocations in the file.
DisplayInvocations(compilation);
}

The Main method invokes individual methods to perform the following operations:

  • ParseArguments to scan for the input file to parse and transform.
  • CreateCompilation to create a C# compilation from a parsed syntax tree created with the text from this source file.
  • DisplayDiagnostics to compute the compiler diagnostics and then display them.
  • DisplayInvocations to analyze each invocation (method call) in a syntax tree and display the overload resolution result and bound symbols.

Implementation of ParseArguments is identical to the one in the previous recipe, Writing an application based on Compiler Syntax API to parse and transform source files. Refer to the How it works..., section of that recipe for further explanation on this method.

The CreateCompilation method first reads the file contents from the input file and parses it using the parsing API CSharpSyntaxTree.ParseText. Then, it creates a metadata reference for the system assembly (corlib) using the location of the assembly containing the object type. It creates compilation options with output kind DynamicallyLinkedLibrary (.dll) and uses these inputs to create a C# compilation:

private static Compilation CreateCompilation(string filePath)
{
var text = File.ReadAllText(filePath);
var tree = CSharpSyntaxTree.ParseText(text);
var systemAssembly = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var options = new CSharpCompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary);
return CSharpCompilation.Create("TestAssembly",
syntaxTrees: new[] { tree },
references: new[] { systemAssembly },
options: options);
}

The DisplayDiagnostics method computes the compilation diagnostics using the Compilation.GetDiagnostics API and then displays the diagnostic count and string representation of each diagnostic.

private static void DisplayDiagnostics(Compilation compilation)
{
var diagnostics = compilation.GetDiagnostics();
Console.WriteLine($"Number of diagnostics: {diagnostics.Length}");
foreach (var diagnostic in diagnostics)
{
Console.WriteLine(diagnostic.ToString());
}

Console.WriteLine();
}

The DisplayInvocations method first gets the semantic model for the syntax tree in the compilation. Then, it iterates over the descendant nodes of the root to get all InvocationExpressionSyntax nodes. For each such invocation, it queries the symbol info of the expression from the semantic model. The symbol info contains information about the semantics of the invocation. We display the overload resolution success/fail result based on whether or not the candidate reason is CandidateReason.None. Then, we display the bound symbol or candidate symbols for the success and failure cases, respectively:

private static void DisplayInvocations(Compilation compilation)
{
var tree = compilation.SyntaxTrees.Single();
var semanticModel = compilation.GetSemanticModel(tree);
var invocations = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocations)
{
Console.WriteLine($"Invocation: '{invocation.ToString()}'");
var symbolInfo = semanticModel.GetSymbolInfo(invocation);

var overloadResolutionResult = symbolInfo.CandidateReason == CandidateReason.None ? "Succeeded" : symbolInfo.CandidateReason.ToString();
Console.WriteLine($" Overload resolution result: {overloadResolutionResult}");

if (symbolInfo.Symbol != null)
{
Console.WriteLine($" Method Symbol: {symbolInfo.Symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}");
}
else if (!symbolInfo.CandidateSymbols.IsDefaultOrEmpty)
{
Console.WriteLine($" {symbolInfo.CandidateSymbols.Length} candidate symbols:");
foreach (var candidate in symbolInfo.CandidateSymbols)
{
Console.WriteLine($" Candidate Symbol: {candidate.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}");
}
}

Console.WriteLine();
}
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset