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
Dart has the three implicit coercions: Implicit downcast from dynamic, implicit call method tear-off and implicit generic method instantiation.
The coercions are triggered by having an expression whose static type is not a subtype of its context type, but which is coercible to the context type.
However, we do not perform those coercions at the first possible chance. Sometimes we omit doing the coercion, and allow the value to flow out from a subexpression into becoming the value of a containing expression instead, knowing that the value will be coerced there instead (or even further out).
The reasoning is that we can do this if:
The value of the subexpression becomes the value of the containing expression (the subexpression is in tail position).
The context type of the subexpression is the same as the context type of the containing expression (which is mostly the case for subexpressions in tail position).
The static type of the subexpression is also the static type of the containing expression.
If all those are true, then it "doesn't matter" whether we coerce now or later, the result will be the same.
The reason it's even visible, is cascades. Foo f = (fooAndBar as dynamic)..barMethod(); works because the implicit downcast from dynamic to Foo happens after the ..barMethod() is called, as a dynamic invocation.
The receiver expression of a cascade satisfies all three points above, and it also makes it detectable where coercion happens.
The problem is that we also delay coercion for expressions in tail position of conditional expressions and null-guards: e ? tail : tail and e ?? tail, and now switch-expression cases.
It is a problem because those expressions do not satisfy the third point, the static type of the expression is not necessarily the same as that of the tail expression, it's the LUB of that and something else, which means that the coercion which made os accept an expression, because its static type was coercible to the context type, might not happen.
It's not unsound, it's just that LUB gives us something guaranteed to be useless, instead of doing the coercion at the latest point where we knew it would definitely happen.
I've made some examples below, but the short point is that you should always do coercions before changing the static type, which means always do coercions before doing LUB.
Turns out CFE and analyzer disagrees on when to instantiate generic function values.
Both agree on generic tear-offs being instantiated immediately.
Both agree on .call-tear-off and downcast from dynamic happening late (too late when after LUB).
Analyzer instantiates generic function values as early as possible, CFE as late as possible.
(And when you instantiate a generic call-tear-off, it's after the call-tear-off, so late.)
Analyzer also seems to perform .call tear-off for switch cases, but not downcast from dynamic,
where CFE delays both.
// Function declaration.Tid<T>(T value) => value;
// "Unpredictable" GetterTFunction<T>(T) get vf => id;
// Callable class.classIntId {
intcall(int x) => x;
}
// Generic callable class.classGenId {
Tcall<T>(T x) => x;
}
// Just some value.dynamic d =42;
voidmain() {
test(true, id);
}
voidtest(bool b, TFunction<T>(T) fn) {
intFunction(int) f;
f = id<int>; // Valid.
f = id; // Valid, implicitly instantiated to be the same as Id<int>.
f = (b ? id<int>: id); // Accepted in both CFE and Analyzer.
f =switch (b) {true=> id<int>, false=> id}; // Same
f = vf<int>; // Valid.
f = vf; // Valid, implicitly instantiated to be the same as Id<int>.
f = (b ? vf<int>: vf); // LUB is `Function` in CFE, accepted in Analyzer!
f =switch (b) {true=> vf<int>, false=> vf}; // Same
f = fn<int>; // Valid.
f = fn; // Valid, implicitly instantiated to be the same as Id<int>.
f = (b ? fn<int>: fn); // LUB is `Function` in CFE, accepted in Analyzer!
f =switch (b) {true=> fn<int>, false=> fn}; // Same
f =IntId().call; // Valid.
f =IntId(); // Valid, implicit `call` tear-off.
f = b ?IntId().call :IntId(); // Error. LUB is `Object`, both CFE and Analyzer.
f =switch (b) {true=>IntId().call, false=>IntId()}; // Error in CFE, accepted in Analyzer.
f =GenId().call<int>; // Valid.
f =GenId().call; // Valid, implicit instantiated.
f =GenId(); // Valid, implicit `call` tear-off, then instantiated.
f = b ?GenId().call<int>:GenId().call; // Accepted in both CFE and Analyzer
f =switch (b) { true=>GenId().call<int>, false=>GenId().call}; // Accepted in both CFE and Analyzer
f = b ?GenId().call :GenId(); // Error. LUB is `Object`, both CFE and Analyzer.
f =switch (b) {true=>GenId().call, false=>GenId()}; // Error in CFE, accepted in Analyzer.int i;
i =1; // Valid.
i = d; // Valid, implicit downcast to dynamic.
i = b ?"Not an int": d; // "Valid", LUB is `dynamic`, forgets the string.
i =switch (b) {true=>"Not an int", false=> d}; // Same.intFunction(int)? p;
// Using `if (b)` to avoid promotion afterwardsif (b) p = id; // Valid, implicit instantiation.if (b) p =IntId(); // Valid, implicit `call` tear-offif (b) p = p ?? id; // Valid, tear-off is instantiated eagerly.if (b) p = p ?? vf; // LUB is `Function` in CFE, accepted in Analyzer!if (b) p = p ?? fn; // LUB is `Function` in CFE, accepted in Analyzer!if (b) p = p ??IntId(); // Invalid, LUB is `Object`, both CFE and Analyzerif (b) p = ("abc"asString?) ?? d; // "Valid", LUB is `dynamic`.// Tear-off instantiation always happens eagerly.
f = id..isIntFunc;
f =GenId().call..isIntFunc;
// Function value instantiation is eager in analyzer, late in CFE.
f = fn..isIntFunc; // Error in CFE, not declared on `T Function<T>(T)`
f = vf..isIntFunc; // Error in CFE, not declared on `T Function<T>(T)`
}
extensiononintFunction(int) {
// The cascade knows!voidget isIntFunc {}
}
The text was updated successfully, but these errors were encountered:
Dart has the three implicit coercions: Implicit downcast from dynamic, implicit
call
method tear-off and implicit generic method instantiation.The coercions are triggered by having an expression whose static type is not a subtype of its context type, but which is coercible to the context type.
However, we do not perform those coercions at the first possible chance. Sometimes we omit doing the coercion, and allow the value to flow out from a subexpression into becoming the value of a containing expression instead, knowing that the value will be coerced there instead (or even further out).
The reasoning is that we can do this if:
If all those are true, then it "doesn't matter" whether we coerce now or later, the result will be the same.
The reason it's even visible, is cascades.
Foo f = (fooAndBar as dynamic)..barMethod();
works because the implicit downcast fromdynamic
toFoo
happens after the..barMethod()
is called, as a dynamic invocation.The receiver expression of a cascade satisfies all three points above, and it also makes it detectable where coercion happens.
The problem is that we also delay coercion for expressions in tail position of conditional expressions and null-guards:
e ? tail : tail
ande ?? tail
, and nowswitch
-expression cases.It is a problem because those expressions do not satisfy the third point, the static type of the expression is not necessarily the same as that of the tail expression, it's the LUB of that and something else, which means that the coercion which made os accept an expression, because its static type was coercible to the context type, might not happen.
It's not unsound, it's just that LUB gives us something guaranteed to be useless, instead of doing the coercion at the latest point where we knew it would definitely happen.
I've made some examples below, but the short point is that you should always do coercions before changing the static type, which means always do coercions before doing LUB.
Turns out CFE and analyzer disagrees on when to instantiate generic function values.
Both agree on generic tear-offs being instantiated immediately.
Both agree on
.call
-tear-off and downcast fromdynamic
happening late (too late when after LUB).Analyzer instantiates generic function values as early as possible, CFE as late as possible.
(And when you instantiate a generic
call
-tear-off, it's after thecall
-tear-off, so late.)Analyzer also seems to perform
.call
tear-off for switch cases, but not downcast fromdynamic
,where CFE delays both.
The text was updated successfully, but these errors were encountered: