C#, The Ternary Operator, and Mono

Posted by Adam on August 18th, 2009 — Posted in General

The Quiz
One of my coworkers recently sent out this C# programming quiz:

static void Main(string[] args)
{
    object x = null;
    object y = (short)4;
    x = (y is System.Int32) ? (System.Int32)y : (System.Int16)y;
    Console.WriteLine(x.GetType());
}

What is printed out?
Try it.
Explain why you were wrong.

If you run the code, you get this output:

System.Int32

The code snippet as it stands doesn’t make it clear exactly where the unexpected behavior is. This is a little more clear:

static void Main(string[] args)
{
    object x = null;
    object y = (short)4;
    x = false ? (System.Int32)y : (System.Int16)y;
    Console.WriteLine(x.GetType());
}

This code outputs the same thing as the above snippet. So why does this snippet output System.Int32, when x clearly gets set to (System.Int16)4 ? The answer is in the C# implementation of the ternary operator.

The Answer
In line 5 of the second example above, x is set to the result of the expression on the right side of the equals sign. Since the thing on the right is an expression, it must have a single type.

The C# Language Specification, in section 14.13, spells out how the type of the expression is determined:

The second and third operands of the ?: operator control the type of the conditional expression. Let X and Y be the types of the second and third operands. Then,

  • If X and Y are the same type, then this is the type of the conditional expression.
  • Otherwise, if an implicit conversion exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.
  • Otherwise, if an implicit conversion exists from Y to X, but not from X to Y, then X is the type of the conditional expression.
  • Otherwise, no expression type can be determined, and a compile-time error occurs.

In our example, since there’s an implicit cast from an Int16 to an Int32, but not an implicit cast from an Int32 to an Int16, the compiler says the type of the expression must be Int32. Then, when our Int16 is returned, it’s typecast to an Int32.

More Types
The spec makes it pretty clear what is supposed to happen if there’s an implicit conversion from one of the operands to the other, but not the other way around. But what happens if there’s an implicit conversion in both directions? According to the spec, none of the first three conditions are met, so the compiler must output an error. There is an implicit conversion from a byte to int, and also one from const int to byte, as long as the int is small enough to fit into the byte. So, let’s try compiling this:

static void Main(string[] args)
{
    const int i = 1;
    const byte b = 2;

    object x = null;
    x = true ? i : b;

    Console.WriteLine(x.GetType());
}

The Bug
If you compile this with the .NET 3.5 compiler, it compiles without errors. There’s a warning about hardcoding true into the ternary operator, but nothing about the types. So, the compiler does not conform to the C# language spec. That’s a bug, but it’s not that shocking. There are other places where the .NET compiler doesn’t conform to the spec. It seems that Microsoft has a policy to leave such bugs in, so as to not break compatibility with existing code, so it will probably stay that way for the foreseeable future.

Mono
This got me wondering if the Mono compiler properly supports the spec. So, I tried compiling with the Mono 2.0 C# compiler. Here’s what you get:

Program.cs(13,17): error CS0172: Can not compute type of conditional expression as `int’ and `byte’ convert implicitly to each other
Compilation failed: 1 error(s), 0 warnings

So it looks like Mono conforms to the spec in this case. It’s a bit amusing that an open source project supports the spec better than Microsoft itself, but there are probably also cases where it goes the other way. However, this means that the Mono implementation is incompatible with the .NET implementation of C#. Now, this particular incompatibility is unlikely to come up that often, since there aren’t many types that have two-way implicit conversions with each other, but it’s something to consider.

The Law
The legal implications of this bug are perhaps the most interesting part. A few weeks ago, there was a lot of talk about the Mono patent situation. This has now been largely resolved with Microsoft putting C# and the CLR under its Community Promise. However, the Community Promise only applies to a given implementation “to the extent it conforms to one of the Covered Specifications.” If an implementation does not conform to the specification, it is not covered by the promise.

You can probably see where I’m going with this. If Mono decided to support compatibility with the .NET compiler by breaking from the spec and implementing the ternary type bug the same way Microsoft has, it might be giving up its protection against patent lawsuits. In order to be legally safer, it’s probably wiser for Mono to stick to the spec and break compatibility with the .NET compiler. This is significant, because the more situations like this crop up, the harder it will be for programmers to port their .NET code to Mono. There’s not much that the mono project can do about this, but it’s unfortunate that the legal situation forces their hand on compatibility.

9 Comments »

Comment by Mike

Great article Adam. I didn’t know about the Microsoft policy on bugs in the compiler.

Posted on August 19, 2009 at 7:21 am

Comment by James

I don’t think this is right, at least on the legal front. That “to the extent that it conforms to the specifications” just means that you can’t put arbitrary code (potentially violating some other Microsoft patent) and expect it to be covered. If Microsoft had a patent on the bug, then maybe you’d have a point, but as it stands I don’t think Mono would be giving up any amount of safety by matching Microsoft’s implementation.

Posted on August 19, 2009 at 3:28 pm

Comment by Adam

James, I suspect you’re right about your interpretation of the meaning of the phrase in the Community Promise, though I don’t have legal training, so I can’t know for sure. I wasn’t suggesting that breaking with the spec in one detail would void the whole Promise.

All I’m saying is that conforming to the .NET implementation instead of the spec has the potential to create more surface area for a lawsuit, so conforming to the spec is safer. It’s perfectly reasonable to assume that a compiler bug might be in patented code, which might create some legal uncertainty. Whether or not Microsoft would choose to act on that is a completely separate matter and I suspect they wouldn’t want to do so.

Posted on August 19, 2009 at 4:30 pm

Comment by Rodrigo Kumpera

All standards bodies require some sort of reasonable patent arrangement from the members of the spec. RAND (ECMA) and RAND-ZERO (W3C) are the most common.

If what you state did hold any truth, this would mean that any implementation of any standard would not be protected by those arrangements by simply having a bug.

Let’s not forget that Microsoft holds patents on HTML, TCP, IPv4 and all sort of XML spec. Last time I checked, all browsers had some sort of non-standard behavior.

Everyone agrees that the Community Promise is not the nirvana of patent safety, but arguing that by not been so it worsen the situation or constitutes a threat is a bit too exaggerated.

I find it hard to follow that by been under the CP, mono is actually less safe against MS than any other software that is not.

Posted on August 19, 2009 at 7:30 pm

Comment by Mike

None of this would be an issue if patents on software didn’t exist. Patents were not designed for abstract concepts like software but for actual physical contraptions. Software patents are the bane of the software development industry and before too long things will come to a head and something will need to be done or the software industry will be in dire straits.

All Software Patents need to be invalidated.

Posted on August 19, 2009 at 10:53 pm

Comment by Crosbie Fitch

Nothing seems untoward to me.

true?(int)a:(short)b; // is of type int

true?(int)a:(byte)b; // is of type int

Whether a is type byte or b is type int is immaterial.

Are you sure that, despite being a shortening, const int to byte is an implicit conversion (for variables, not numeric literals)? That would surprise me.

const int x=1;
byte b=x; // No shortening warning?

true?x:b; // I’d expect this to have type int – I wouldn’t expect an error because the numeric literal/value held/represented by x could assign/initialise without warning to a byte.

Posted on August 20, 2009 at 9:47 am

Comment by Adam

Crosbie,

That’s a reasonable assumption, but it’s not the way C# works. See part 6.1.8 of the spec. The key is that if the int is a const, the compiler will look at the value of the int and make sure it fits in the byte. If the int is small enough to fit, then the compiler does the implicit conversion without warnings or errors.

By the way, you can test it with numeric literals as well, and the bug also exists that way.

Posted on August 20, 2009 at 10:00 am

Comment by Crosbie Fitch

You never did say what the output in your last example was, just that it compiled without error.

I presume it outputs ‘byte’, much to this C++ programmer’s surprise.

You learn something every day.

Posted on August 20, 2009 at 10:34 am

RSS feed for comments on this post. TrackBack URI

Leave a comment