Share via


When is a non-breaking language fix, breaking?

In VS2012 we fixed some method-resolution problems from VS2010. These were method-calls that failed to compile in VS2010, but now compile cleanly in VS2012.

 

VB:

Dim numbers As IEnumerable(Of Integer) = {1, 2, 3}
Sub f(Of T)(x As T)
Sub f(Of T)(x As IEnumerable(Of T))

f(numbers) ' Should obviously pick the second overload, since it’s more specific (but VS2010 didn't)

C#:

static string mg(this string x) { return ""; }
static void f<T>(System.Func<T> lambda) { }

f("".mg); // Should be able to infer that T=string (but VS2010 couldn't)

 

The change in VB was a new rule we added to the language spec and compiler, needed for Task.Run(...) to be usable. The change in C# was a bugfix to make the compiler behave how the language spec said it should.

You'd think that these improvements would be safe... Their only effect is that code which used to give compile-time errors in VS2010 will now compile cleanly in VS2012. No one should be adversely impacted, right?

Wrong.

Whenever we improve method-resolution, there exists the theoretical possibility of code that uses overload-resolution and lambdas in a particular way, and which used to work (more by accident than design), and which will now fail to compile. Credit goes to Vladimir Reshetnikov, who pointed out this class of problems a few years ago, and whom we hired to work in the VB/C# language QA team. Although we're normally loathe to take any breaking changes at all, this particular class of breaks seems so convoluted and obscure that we're not as concerned about it.

 


Module Module1
Dim numbers As IEnumerable ( Of Integer ) = {1, 2, 3}
 
Sub Main()
   ' VS2010 used to give an ambiguity error for this call.
   ' We fixed the spec+compiler by adding a new tie-breaker rule.
   ' VS2012 now picks the second "f" due to improved overload-resolution rules...
   f(numbers)
 
 
   ' But confusingly, even though the improvement meant that normal code
   ' now works which used to give an ambiguity error,
   ' there are also some subtle cases involving lambdas and
   ' "overload-resolution-within-overload-resolution" which used to compile
   ' but which now give an ambiguity error.
 
 
   ' VS2010 used to pick the second "g" and the first "f" for this call:
   ' VS2012 now gives an ambiguity error...
   g( Sub (x) f(x))
 
 
   ' Explanation: the compiler looks at the two candidates of "g".
   ' With the first candidate, "Sub(x) f(x)" would have x typed as IEnumerable(Of Integer).
   ' In VS2010 this would have meant the call to f(x)
   ' was ambiguous, and so the lambda couldn't be passed to the first candidate for "g",
   ' and so VS2010 rejected the first candidate for "g", and opted for the second (which works).
   ' In VS2012 this call to f(x) is not ambiguous, and so the lambda could happily be
   ' pased to the first candidate for "g" as well as to the second.
   ' That's why VS2012 reports an error on which overload of "g" to pick.
   '
   ' This affects cases when you pass a lambda as an argument to a method
   ' with multiple overloads, and when that lambda itself contains a call with
   ' multiple overloads, and when the interplay of outer and inner overloads
   ' happen to hit one of the cases of improved overload-resolution.
End Sub
 
Sub f( Of T )(x As T )
End Sub
Sub f( Of T )(x As IEnumerable ( Of T ))
End Sub
 
Sub g(lambda As Action ( Of IEnumerable ( Of Integer )))
   lambda(numbers)
End Sub 
Sub g(lambda As Action ( Of Integer ))
   lambda(1)
End Sub
 
End Module


static class Program  {

static void Main()  {
   // VS2010 used to give a compile error because, due to a extension-method bug, it couldn't infer T
   // We fixed the compiler bug.
   // VS2012 now compiles this correctly, inferring T=string.
   f( "" .mg);
 
   // VS2010 used to produce an invalid executable for the following code, due to the same bug.
   // Our same bugfix made this work.
   // VS2012 now compiles this correctly, producing valid EXE that calls the second overload of "g".
   g( "" .mg);
 
 
   // But confusingly, even though the fix meant that normal code
   // now works which used to give a compiler error (or invalid executable),
   // there are also some subtle cases involving lambdas and
   // "overload-resolution-within-overload-resolution" which used to compile
   // but which now give an ambiguity error.
 
 
   // VS2010 used to pick the second overload of "h", passing the instance method C.mg to f.
   // VS2012 now gives an ambiguity error
   h(x => f(x.mg));
 
 
   // Explanation: The compiler looks at the two candidates of "h".
   // With the first candidate, "x => f(x.mg)" would have x typed as string.
   // In VS2010 this would run foul of the bug, and produced an error, and so VS2010
   // rejected the first candidate for "h", and opted for the second one (which worked).
   // In VS2012 this call to "f(x.mg)" no longer suffers from the bug, and so the
   // two candidates for "h" work fine.
   // That's why VS2012 reports an error for which overload of "h" to pick.
 
   // This affects cases where you pass a lambda as an argument to a method with
   // multiple overloads, and when that lambda itself contains a call that experienced
   // the bugfix.
}
 
static string mg( this string x) { return "" ; }
static void f<T>(System. Func <T> lambda) { }
 
static void g(System. Func < object > lambda) { }
static void g<T>(System. Func <T> lambda) { }
 
static void h(System. Action < string > action) { }
static void h(System. Action < C > action) { }
}
 
class C { public string mg() { return "" ; } }


Comments

  • Anonymous
    May 21, 2016
    BTW Actually causing a compiler error is the better option, when the alternative is it compiling and doing something different! Can the latter happen?