Roslyn failed to compile code

ramil89 picture ramil89 · Aug 12, 2015 · Viewed 7.2k times · Source

After I have migrated my project from VS2013 to VS2015 the project no longer builds. A compilation error occurs in the following LINQ statement:

static void Main(string[] args)
{
    decimal a, b;
    IEnumerable<dynamic> array = new string[] { "10", "20", "30" };
    var result = (from v in array
                  where decimal.TryParse(v, out a) && decimal.TryParse("15", out b) && a <= b // Error here
                  orderby decimal.Parse(v)
                  select v).ToArray();
}

The compiler returns an error:

Error CS0165 Use of unassigned local variable 'b'

What causes this issue? Is it possible to fix it through a compiler setting?

Answer

Jon Skeet picture Jon Skeet · Aug 12, 2015

What does cause this issue?

Looks like a compiler bug to me. At least, it did. Although the decimal.TryParse(v, out a) and decimal.TryParse(v, out b) expressions are evaluated dynamically, I expected the compiler to still understand that by the time it reaches a <= b, both a and b are definitely assigned. Even with the weirdnesses you can come up with in dynamic typing, I'd expect to only ever evaluate a <= b after evaluating both of the TryParse calls.

However, it turns out that through operator and conversion tricky, it's entirely feasible to have an expression A && B && C which evaluates A and C but not B - if you're cunning enough. See the Roslyn bug report for Neal Gafter's ingenious example.

Making that work with dynamic is even harder - the semantics involved when the operands are dynamic are harder to describe, because in order to perform overload resolution, you need to evaluate operands to find out what types are involved, which can be counter-intuitive. However, again Neal has come up with an example which shows that the compiler error is required... this isn't a bug, it's a bug fix. Huge amounts of kudos to Neal for proving it.

Is it possible to fix it through compiler settings?

No, but there are alternatives which avoid the error.

Firstly, you could stop it from being dynamic - if you know that you'll only ever use strings, then you could use IEnumerable<string> or give the range variable v a type of string (i.e. from string v in array). That would be my preferred option.

If you really need to keep it dynamic, just give b a value to start with:

decimal a, b = 0m;

This won't do any harm - we know that actually your dynamic evaluation won't do anything crazy, so you'll still end up assigning a value to b before you use it, making the initial value irrelevant.

Additionally, it seems that adding parentheses works too:

where decimal.TryParse(v, out a) && (decimal.TryParse("15", out b) && a <= b)

That changes the point at which various pieces of overload resolution are triggered, and happens to make the compiler happy.

There is one issue still remaining - the spec's rules on definite assignment with the && operator need to be clarified to state that they only apply when the && operator is being used in its "regular" implementation with two bool operands. I'll try to make sure this is fixed for the next ECMA standard.