Mono + named/optional parameters = compiler bug?
I have encountered some unforeseen consequences of porting to mono 2.8.1. Problem can be boiled down to a sample program (I have been unable to reduce it further, after cutting several classes and ~1000 lines of code to the file quoted below)
public class Person
{
public Person(int age, string name = null){}
public Person(double income, string name = null){}
public Person(double income, int age, string name = null){}
}
class Program
{
static void Main()
{
Person p = new Person(1.0, name: "John Doe");
}
}
Compilation of above code with mcs gives output:
test.cs(22,24): error CS0584: Internal compiler error: Internal error
test.cs(22,20): error CS0266: Cannot implicitly convert type `object' to `NamedParams.Person'.
An expli开发者_开发问答cit conversion exists (are you missing a cast?)
Compilation failed: 2 error(s), 0 warnings
Removing use of optional/named parameter (i.e. calling new Person(1.0, null, "John Doe") or new Person(1.0, null, name:"John Doe"), or new Person(1.0, "John Doe") ) leads to flawless compilation. Also, under VS2010 the file (and whole solution with which I started with) compiles fine. Casting removes error CS0266, but not CS0584 -- so no surprise there.
My question: is it me doing something wrong, or mcs (i.e. bug in mcs is obvious to me -- what else ,,internal error'' would mean, but perhaps it's ok such program won't compile), or maybe Microsoft compiler in VS2010 should not let such code to compile?
I bet it's mcs who's wrong (unable to guess right constructor), but perhaps it's otherwise and I shouldn't know better?
PS. I tried searching for a known bug like this in both Google and Novell's Bugzilla, but was unable to find anything relevant. Again, I may be blind ;)
Okay, here goes. The crash is indeed due to the third overload, Person(double income, int age, string name = null)
.
The compiler sees you are trying to pass less arguments than what are listed in the signature so it goes looking for optional arguments. It happily notices that name
is optional and assumes you are not passing that argument. It does this by appending a placeholder at the end of the supplied arguments. Next, it goes to reorder the named arguments in the list so they end up in their right position. This means John Doe
is now correctly at the last position for name
, but the placeholder gets into the age
position. The compiler then tries to fill in the default values, but is shocked to find a placeholder at a location which has no default value. It thinks this can not happen, since the placeholder was only added for an optional argument and now suddenly it is not optional anymore. Not knowing what to do, it throws an exception.
The following patch seems to fix the issue (however it may break something else, so no warranty):
--- mono-2.6.7.orig/mcs/mcs/ecore.cs 2009-10-02 12:51:12.000000000 +0200
+++ mono-2.6.7/mcs/mcs/ecore.cs 2010-12-21 02:26:44.000000000 +0100
@@ -3803,6 +3803,15 @@
int args_gap = Math.Abs (arg_count - param_count);
if (optional_count != 0) {
+ // readjust for optional arguments passed as named arguments
+ for (int i = 0; i < arguments.Count; i++) {
+ NamedArgument na = arguments[i] as NamedArgument;
+ if (na == null)
+ continue;
+ int index = pd.GetParameterIndexByName (na.Name.Value);
+ if (pd.FixedParameters[index].HasDefaultValue)
+ optional_count--;
+ }
if (args_gap > optional_count)
return int.MaxValue - 10000 + args_gap - optional_count;
For others coming to this thread. The bug is still there in the latest versions of Mono Compiler as of April 2013. I created a work around without needing to modify the compiler using C# overloaded functions.
Foo(int a, bool b = true) {
Foo(a, b, "Default String");
}
Foo(int a, bool b, string c)
精彩评论