Why does the switch expression infer the expression type from the surrounding context, but other ambiguously typed expressions don't?

Viewed 184

As I was answering this question, I observed a very strange behaviour unique to switch expressions - they seem to be able to infer their types when they have an ambiguous type.

For example, This doesn't compile

double a = new Random().Next(2) == 0 ? (short)1 : (uint)1;

because the compiler "doesn't look" at the double a part when type checking, and sees that the expression is either of type short or uint. An expression can't be of two types, so an error is output. This is fine. It is quite well-known that C# doesn't look at the type of variable that you are assigning to, just like in generics:

static T F<T>() => default(T);
double d = F(); // can't infer that T is double

However, switch expressions break this "rule". If I rewrite the first code snippet with switch expressions:

double a = (new Random().Next(2) == 0) switch {
    true => (short)1,
    false => (uint)1
};

Then suddenly it compiles! Unlike the first code snippet, the compiler seems to have paid attention to the double a part and figured out that I want a double. I tried looking at the docs for switch expressions, but it doesn't mention that it will perform any automatic conversions to the result type or anything like that.

Why is C# so smart when dealing with switch expressions, but not with other kind of expressions (such as ternary operator)?

1 Answers

From the C# lang proposal for switch expressions;

The type of the switch_expression is the best common type of the expressions appearing to the right of the => tokens of the switch_expression_arms if such a type exists and the expression in every arm of the switch expression can be implicitly converted to that type. In addition, we add a new switch expression conversion, which is a predefined implicit conversion from a switch expression to every type T for which there exists an implicit conversion from each arm's expression to T.

The best common type?

In some cases, a common type needs to be inferred for a set of expressions. In particular, the element types of implicitly typed arrays and the return types of anonymous functions with block bodies are found in this way.

Intuitively, given a set of expressions E1...Em this inference should be equivalent to calling a method

Tr M<X>(X x1 ... X xm)

with the Ei as arguments.

More precisely, the inference starts out with an unfixed type variable X. Output type inferences are then made from each Ei to X. Finally, X is fixed and, if successful, the resulting type S is the resulting best common type for the expressions. If no such S exists, the expressions have no best common type

Since this doesn't compile, with a "no best type.." error;

var x = (id == 0) switch
{
    true => (short)1,
    false => (uint)1
};

Your example double x ... constrains the output type, changing how the types of the switch expressions are inferred. But that's just me guessing.

Whereas the conditional operator has very strict rules;

The second and third operands, x and y, of the ?: operator control the type of the conditional expression.

  • If x has type X and y has type Y then
    • If an implicit conversion (Implicit conversions) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.
    • If an implicit conversion (Implicit conversions) 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.
  • If only one of x and y has a type, and both x and y, of are implicitly convertible to that type, then that is the type of the conditional expression.
  • Otherwise, no expression type can be determined, and a compile-time error occurs.

TLDR; the type inference / promotion rules are very different.

Related