Roslyn: Bug: Generic Default Value Literal with == operator

Created on 15 Jun 2019  ·  6Comments  ·  Source: dotnet/roslyn

Version Used: 7.3

Steps to Reproduce:

  1. Attempt to compile the following

```c#
public void Foo(T value){

if (value == default)

}

**Expected Behavior**: Compile error similar to `value == default(T)`

```c#
public void Foo<T>(T value){

    if (value == default(T)) // <--- compile error as expected
}

Actual Behavior: compiles into a null check for all types including value types and leads to situations where when T is int and value=0, value == default evaluates to false.

4 - In Review Area-Compilers Bug Resolution-Fixed

All 6 comments

StackOverflow question with more details: https://stackoverflow.com/q/56602309/

@gafter This looks like a bug to me. Can you confirm?

@agocke

The closest thing we have to a specification is https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/target-typed-default.md which says

The _default_ literal can be the operand of equality operators, as long as the other operand has a type. So default == x and x == default are valid expressions, but default == default is illegal.

This suggests (but does not say) that the default expression is converted to the type of the other operand. This is not what is implemented.

The LDM decided (https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-05-31.md#default-in-operators):

Default in operators

default as an operand to unary or binary operators would sometimes work, and sometimes not, depending on whether there happens to be a best operator across all available predefined or user-defined ones for all types:

c# var a = default + default; // error var b = default - default; // ok var c = default * default; // ok var d = default / default; // error

This feels arbitrary. But worse, it is actually a recipe for future compat disasters. Imagine we added a - operator to, say, arrays in the future. Now the second line above would break, because the int overload of the pre-defined - operator would no longer happen to be best.

Conclusion

Don't allow default as an operand to a unary or binary operator. We need to protect the ability to add new operator overloads in the future.

This is not what is implemented.

What the compiler does (I suspect) is perform overload resolution on operator == and the applicable method it finds is operator ==(object, object). There is no overload for operator == that would cause us to convert the right-hand-side to T, so that's not what we do.

Searching through my emails, I see there was an undocumented decision to permit == and != with a default literal, taking the type from the other side. Here are some snippets:

The LDM notes (May 31st) were that default would be disallowed as operand on all unary and binary operators, including == and !=.
I’d taken notes on that day (and updated the speclet) capturing that default would still be allowed in == and !=. That seems to be from offline exchange with Mads following LDM meeting.

But the implemented behavior is that default is disallowed on == and !=, except if the other operand implement user-defined == or != operator. See demo.

My interpretation is that there is a bug in the LDM notes (== and != should be allowed) and there is a bug in the implementation too (== and != should be allowed even when no user-defined operator exists).
I would like to fix the implementation bug in C# 7.3 (gated by language version).

This was followed by a flood of LDM agreement, and then

The rule would be “default gets the type from the other side”. Then I think the tuple case just works too.

(0, 0) == default is (0, 0) == default((int, int)) which is true.

Fixed in 16.4p2 and documented as a breaking change.

Thanks @avin-kavish for reporting this

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MadsTorgersen picture MadsTorgersen  ·  3Comments

marler8997 picture marler8997  ·  3Comments

johndog picture johndog  ·  3Comments

NikChao picture NikChao  ·  3Comments

vbcodec picture vbcodec  ·  3Comments