You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The common type (C# 5 ECMA spec section 12.6.3.1.5) says
12.6.3.15 Finding the best common type of a set of expressions
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.
The best common type for a set of expressions E1…Em is determined as follows:
A new unfixed type variable X is introduced.
For each expression Ei an output type inference (§12.6.3.7) is performed from it to X.
X is fixed (§12.6.3.12), if possible, and the resulting type is the best common type.
Otherwise inference fails.
[Note: Intuitively this inference is equivalent to calling a method
void M<X>(X x1 … X xm)
with the Ei as arguments and inferring X. end note]
First, we note that the code would be an error if any of the expressions Ei cannot be converted to the type to which X was fixed. In order to recognize this as a form of failure, we modify the penultimate bullet to be
X is fixed (§12.6.3.12), if possible. If every Ei can be implicitly converted to the resulting type then it is the best common type.
This change has no effect on the legality or meaning of any C# 8 program, but it simplifies the generalization to target typing and the discussion below.
This best common type algorithm is used for three things in the specification:
implicitly typed array creation (new [] { e1, e2 }),
finding the return type of a lambda ((b, e1, e2) => {if (b) return e1; else return e2; }), and
finding the implicit type of a switch expression (b switch { true => e1, false => e2 }).
It is not used for the conditional expression b?e1:e2, or the null coalescing operator e1??e2 which each has has its own specification for the common type that is based on the existence of a conversion from expression rather than a conversion from type.
These distinct common type specifications can produce different results. For example, there is no best common type for a value of an enumeration type and the integer constant zero, but the specification for the conditional operator selects the enumeration type as the result. Given that we have these three different specifications for best common type (for different constructs) that produce different results, I propose that we not attempt to unify them.
Proposed changes
There are five pending proposals to make changes to these common type algorithms:
There was some concern expressed in the LDM that once we enable target typing for a construct (whether as a fallback or always available), adding new enhancements to the common type algorithm would be a breaking change because it would give the expression a type, which could affect overload resolution. For this reason we fear that if we add target typing to a construct this might be our last opportunity to add any further enhancements to the common type algorithm.
Moreover, the fact that we have target typing for the switch-expression may mean that our window to enhance the common type algorithm has already closed. Here is an illustration of the kind of break that would concern us:
In C# 8, we select the second overload (it is a better conversion from expression because the conversion to short? is a better conversion than the conversion to int?). However, if we extend the common type algorithm to assign the type int? to the switch expression, we would select the first overload (it is a better conversion from expression because int? is the type of the expression).
Let us survey the set of user requests for enhancements:
The common type of two class types being their most derived common base type.
The common type of two types being their common interface type. If more than one, possibly an intersection type (a concept that does not yet exist).
The common type of two unrelated types being a union type (a concept that does not yet exist). This is also needed to produce a common type for two distinct instances of a variant interface.
The common type of two members of a discriminated union being the common DU type. This is related to (2) but for situations that do not arise today. Because these will be new scenarios, we are not concerned about breaking changes.
Target-typing only as a fallback?
There was some concern expressed in the LDM that it would be nice to align the handling of the conditional and null coalescing expressions (where target typing is proposed only as a fallback) with the handling of the switch expression (where target typing is available even if there is a common type). However, it is a breaking change to add target typing to an existing construct that has a type. Here is a demonstration:
classC{staticvoidMain(){boolc=true;M(c?1:2);// first M in C# 8, ambiguous if ?: is target-typed}staticvoidM(Xx){}staticvoidM(Yy){}}classX{publicstaticimplicitoperatorX(intv)=>null;}classY{publicstaticimplicitoperatorY(shortv)=>null;}
The incompatibility arises when there is a language-defined conversion from expression for an expression that has a type and that depends on some aspect of the expression aside from its type (e.g. its value). It arises due to
switch expression conversions where the contained expressions all qualify based on this list.
If we elect to permit target-typing of conditional expressions and null coalescing expressions (when they have a common type), those would also be added to this list.
We can avoid this breaking change by adding support for target-typing of conditional expressions and null coalescing expressions only when they have no common type.
Recommendation
I believe that the proposed enhancements to the three common type algorithms are less compelling once the relevant constructs are target-typed. I believe we should add target-typing as a fallback to the conditional expression and the null coalescing expression to minimize the break to existing code. The other enhancements to the common type algorithms invent result types not appearing among the inputs (e.g. long? for input types int? and long), which violates one of the principle design invariants for type inference (only produce as a result a type which was one of the inputs), and which can also change the meaning of existing code.
The disadvantage of taking target-typing as a fallback only is that code such as this will not compile:
shorts=b?1:2;
This is something we have long lived with, and (perhaps surprisingly) it not among the use cases called out by customers in public discussions.
The text was updated successfully, but these errors were encountered:
Enhancing the Common type spec
The common type (C# 5 ECMA spec section 12.6.3.1.5) says
First, we note that the code would be an error if any of the expressions
Ei
cannot be converted to the type to whichX
was fixed. In order to recognize this as a form of failure, we modify the penultimate bullet to beThis change has no effect on the legality or meaning of any C# 8 program, but it simplifies the generalization to target typing and the discussion below.
This best common type algorithm is used for three things in the specification:
new [] { e1, e2 }
),(b, e1, e2) => {if (b) return e1; else return e2; }
), andb switch { true => e1, false => e2 }
).It is not used for the conditional expression
b?e1:e2
, or the null coalescing operatore1??e2
which each has has its own specification for the common type that is based on the existence of a conversion from expression rather than a conversion from type.These distinct common type specifications can produce different results. For example, there is no best common type for a value of an enumeration type and the integer constant zero, but the specification for the conditional operator selects the enumeration type as the result. Given that we have these three different specifications for best common type (for different constructs) that produce different results, I propose that we not attempt to unify them.
Proposed changes
There are five pending proposals to make changes to these common type algorithms:
null
such asc?1:null
and expressions with typeint?
anddouble
.??
) expression #2473 Similarly for the null coalescing expression.new[]
#2701 Similarly for the array creation expression.There was some concern expressed in the LDM that once we enable target typing for a construct (whether as a fallback or always available), adding new enhancements to the common type algorithm would be a breaking change because it would give the expression a type, which could affect overload resolution. For this reason we fear that if we add target typing to a construct this might be our last opportunity to add any further enhancements to the common type algorithm.
Moreover, the fact that we have target typing for the switch-expression may mean that our window to enhance the common type algorithm has already closed. Here is an illustration of the kind of break that would concern us:
In C# 8, we select the second overload (it is a better conversion from expression because the conversion to
short?
is a better conversion than the conversion toint?
). However, if we extend the common type algorithm to assign the typeint?
to the switch expression, we would select the first overload (it is a better conversion from expression becauseint?
is the type of the expression).Let us survey the set of user requests for enhancements:
Target-typing only as a fallback?
There was some concern expressed in the LDM that it would be nice to align the handling of the conditional and null coalescing expressions (where target typing is proposed only as a fallback) with the handling of the switch expression (where target typing is available even if there is a common type). However, it is a breaking change to add target typing to an existing construct that has a type. Here is a demonstration:
The incompatibility arises when there is a language-defined conversion from expression for an expression that has a type and that depends on some aspect of the expression aside from its type (e.g. its value). It arises due to
If we elect to permit target-typing of conditional expressions and null coalescing expressions (when they have a common type), those would also be added to this list.
We can avoid this breaking change by adding support for target-typing of conditional expressions and null coalescing expressions only when they have no common type.
Recommendation
I believe that the proposed enhancements to the three common type algorithms are less compelling once the relevant constructs are target-typed. I believe we should add target-typing as a fallback to the conditional expression and the null coalescing expression to minimize the break to existing code. The other enhancements to the common type algorithms invent result types not appearing among the inputs (e.g.
long?
for input typesint?
andlong
), which violates one of the principle design invariants for type inference (only produce as a result a type which was one of the inputs), and which can also change the meaning of existing code.The disadvantage of taking target-typing as a fallback only is that code such as this will not compile:
This is something we have long lived with, and (perhaps surprisingly) it not among the use cases called out by customers in public discussions.
The text was updated successfully, but these errors were encountered: