How should null-aware elements handle coercions?
The null-aware elements proposal deals with type inference and that type inference uses the surrounding context type. That implies that implicit coercions (casts from dynamic, implicit generic function instantiations, and implicit call tear-offs) may come into play. How should they be handled?
On this PR, Lasse says:
Do we need to do something special for coercions here, or will the value be coerced before the null check?
void main() {
// True, but compiler doesn't know that.
var b = DateTime.now().millisecondsSinceEpoch > 0;
var l1 = <Function>[?(b ? CallableObject() : null)];
var l2 = <int Function(int)>[?(b ? id : null)];
}
T id<T>(T value) => value;
class CallableObject {
void call() {}
}
Will these list literals work or fail to compile?
If we coerce before null-checking, then we coerce CallableObject? to Function? which is not supported, so it's a compile-time error. Same for coercing T Function<T>(T)? to int Function(int)?. Neither of these are allowed:
Function? v1 = (b ? CallableObject() : null);
int Function(int) v2 = (b ? id : null);
If we coerce after null-checking, the code should works. And it's probably what the author wanted. So should we?
That would either mean being explicit about where we do coercion, or somehow suggest to inference that the null check is not a coercion-point (a concept we haven't specified other than by the algorithm used.)
WDYT? @stereotype441
The static semantics with a context element type E would be (paraphrased):
Let
Sbe the static type ofein contextE?. LetS'be NonNull(S). It's a compile-time error ifS'is not assignable to the greatest closure ofE.
and runtime semantics of executing ?e would be:
Evaluate
eto a value v. If v is not the valuenull, then
- if the runtime type of v is a subtype of E, let w be v, otherwise coerce v from type
S'to a value w of typeE. (Which is always possible becauseS'is assignable toE.)- Then append w to result.
(Not sure dynamic has an issue, downcasting before or after both work.)
I don't personally have a strong opinion or intuition here. Thus I'm filing a bug so we can discuss outside of that PR.
My guess is that users would expect it to work. That is, coercion happens after checking for null.
I don't know whether we need to say something to ensure that, or it will happen automatically, since I don't actually understand how we've specified where we do coercions. ("Where we introduce a new context type", but I don't know if going from C outside the ? to C? inside the ? counts as introducing a new context type.)
The reasoning I expect is that the ? acts on the value, which skips out if the value is null, what is left is then the value that is put into the collection. That should work exactly the same as if that value had been the expression.
If one can write var c = Callable(); var list = <Function>[c], one should also be able to write Callable? c = test ? Callable() : null; var list = <Function>[?c];, because the latter should be equivalent to either <Function>[] or <Function>[c].
If it works in any other way, I think it'll be very hard to explain.
(I know call-tearoff is a bad example, because we want to get rid of it anyway, it's just so much easier to write as an example than generic instantiation.)
The reason that there might be something to decide at all, is that we currently do not allow tear-off below a nullable type, that is Function? f = c; will not do tear-off as c?.call. We never insert ?.call, only ever .call.
If we say that we coerce before checking the ?, then it becomes like ?(c?.call). I read this code as more like (e ?? <break from element>).call. Or more like if (c case var $tmp?) collection.add($tmp.call);, and the coercion point is the call to add.
If the context type is pushed in, a literal ?(test ? Callable() : null) will probably coerce the branch, but most nullable values are not created literally where they are consumed, they come from somewhere else. (Othewise the control flow that created the null would just be hoisted to avoid doing anythying. A null value is a way to communicate "no value" across an API.)
Was this resolved for the null-aware elements release? @chloestefantsova