14/08/2017

Roslyn tricked me again

Home



A while ago 2 of my SpecFlow tests started failing on the build server. At the same time, on a few local machines no problems were observed. I also didn't find any exceptions in the log, so I decided to log into the server and debug a problem there.

Quite soon I figured out that the problem is in the algorithm that uses Roslyn to analyse and understand the code. Here is a simplified code that finds all local variables within a method body and tries to determine their exact types.
MethodDeclarationSyntax m = ...;

// Let's find all local variables declarations
var statements = m.DescendantNodes().OfType<LocalDeclarationStatementSyntax>().ToList();
// For each local variable let's try to determine its type
foreach(var s in statements)
{
   var typeSymbol = semanticModel.GetSymbolInfo(s.Declaration.Type).Symbol;
   ...
}
The problem was that in some cases GetSymbolInfo was returning null and in other cases it was working ok. For example, it didn't work for such piece of code:
var item = Items.Single(i => i.Id = id);
For a while I was stuck. Then I reminded myself about diagnostics information that are provided by Roslyn. They correspond to warning and errors from Visual Studio and we can read them via Compilation.GetDiagnostics method. I used this method and in the result, I found mainly warnings but also a few compilation errors saying that:

Error CS1061 'IEnumerable<Item>' does not contain a definition for 'Single' and no extension method 'Single' accepting a first argument of type 'IEnumerable<Item>could be found (are you missing a using directive or an assembly reference?)

Now it was much more clearer. Roslyn can't perform semantic analyses and return symbol info for a line with a compilation error. You may remember that in my earlier post I wrote that to perform semantic analyses Roslyn needs to know all assemblies used by a given project. To register all required assemblies, I proposed the following code:
var compilation = await project.GetCompilationAsync();

// Let's register mscorlib
compilation = compilation.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location));

if (Directory.Exists("PATH TO OUTPUT DIRECTORY"))
{
   var files = Directory.GetFiles(directory, "*.dll").ToList(); // You can also look for *.exe files

   foreach (var f in files)
      compilation = compilation.AddReferences(MetadataReference.CreateFromFile(f));
}
In this code, I register mscorlib and all assemblies in the given directory. However, LINQ methods like Single are defined in another system assembly i.e. System.Core which is not taken into account. It's why Roslyn was reporting compilation errors. Here is the fix:
var compilation = await project.GetCompilationAsync();

// Let's register mscorlib
compilation = compilation.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location));
// Let's register System.Core
compilation = compilation.AddReferences(MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location));
// Let's register whatever we need
...
Nonetheless, it doesn't explain why this problem was observed only on the build server. And to be honest I don't know. Probably it's due to some configuration differences but I'm not sure.


If you want to use Roslyn semantic analyses explicitly register all assemblies that are used by a code you want to analyse.


*The picture at the beginning of the post comes from own resources and shows salt sculptures in Wieliczka salt mine.

0 comments:

Post a Comment