Open
Description
... best explained through a simple example (real issue: angulardart/angular#1533):
void main() {
var comp1 = Comp1();
var comp2 = Comp2();
comp2.registerEvent(eventHandler1(comp1.onEventInt));
}
class Comp1 {
void onEventInt(int event) {}
}
class Comp2 {
void registerEvent(void Function(String) listener) {
listener('Hello');
}
}
void Function(E) eventHandler1<E, F extends E>(void Function(F) handler) {
print('Function($E) for eventHandler1<$E, $F>(void Function($F))');
return (E event) {
handler(event as F);
};
}
See the issue? If you run this program, you get the following:
Function(String) for eventHandler1<String, Null>(void Function(Null))
Uncaught exception:
CastError: "Hello": type 'JSString' is not a subtype of type 'Null'
It looks like type inference realizes (correctly) that int
is not a sub-type of String
, and recovers by falling back to bottom (i.e. Null
). While this is correct, the user experience is degraded significantly - something that could have been a compile-time error is now a (very confusing) runtime one.
We slightly improved the runtime error by adding an assert
:
assert(
E == Null || F != Null,
"Event handler '$handler' isn't assignable to expected type "
"'($E) => void'");
... of course this is a hack, and I'm not sure it is technically always correct.
I wish we could either:
- Ban inferring
Null
, i.e. require the user to type<Null>
. - Find a replacement pattern for the above use case that has correct static type checking