meetings/2018/LDM-2018-10-01.md
Generic type inference is also used to determine "best common type", e.g. in anonymously typed arrays, finding the inferred return type of lambdas with multiple returns, etc.
Type inference roughly proceeds as follows:
null, may not have a type to contribute)This third step is a little cryptic. Most of the time, when types are identity convertible to each other, they are the same type. But there are two exceptions in the language today:
object and dynamic are identity convertible to each otherIn step 3 above, in places where the identity convertible types differ by object vs dynamic, choose dynamic. Where they differ by tuple element names, have the tuple element be unnamed.
This is all relevant to nullable reference types, because we are about to introduce a third way in which non-identical types can be identity convertible to each other:
This means we need to say how to construct the result of type inference with regard to nullability of reference types. Where the identity convertible types differ by nullability, we'll determine the nullability based on the variance of the type's position:
For a given type position in the result type, we'll always pick among the nullabilities present in that position, with one exception.
This leads to nice and symmetric rules, where nullable and nonnullable are treated equally, and oblivious isn't too infectious. As far as we can tell, the rules are associative and can be expressed in a pairwise manner, without causing order dependence. If oblivious had dominated nullable and nonnullable in the invariant case, that would have thwarted associativity.
The one thing that's a little odd is where nullable and nonnullable clash in an invariant position. Ideally this would lead to an error, but we only want to allow nullability to lead to warnings, not errors, so we need to have some answer for what comes out. Oblivious seems the right choice, in that we've already warned that something is wrong, and it will lead to suppression of further warnings caused by that. What's odd about it is that oblivious normally comes from legacy code that's explicitly opted out of the nullability feature. This is the only place where it can occur in new code that is all "opted in".
Allowing a more granular in-file choice between nullability contexts (whether nullable annotations are on or off) leads to new kinds of situations. For instance, a type parameter can be declared in an "off" context (oblivious to nullability) but used in an "on" context. This is the topic of Roslyn issue 30214.
The context where the type parameter is declared determines whether it should be sensitive to the nullability implications of its constraints. In the example in the issue, the type parameters are oblivious, and should not lead to nullability diagnostics, because they are declared in a "legacy" context.