-
Notifications
You must be signed in to change notification settings - Fork 213
Desire for a way to indicate to the type system that the return value can't be nullable if some arguments are null #1166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This looks closely related to #836 |
The general case where the nullability of the result depends on the nullability of the arguments, but where the type doesn't follow the arguments (like here where a You need a type system which either allows dependent types in general, or a language with method overloading, or some kind of type-metadata-system where you can abstract over the nullability, say: double* lerpDouble<* extends ?>(num* a, num* b, double t) {
if (a == null && b == null) return null as double*;
...
} (This is still bad for typing this particular function because you'd want a non-nullable result unless both arguments are nullable, and then I think you're in full dependent type mode, and you can probably implement Turing machines in the type system.) I'd probably just make it an error to have both |
There are lots of these examples and it is very common for static FractionalOffset? lerp(FractionalOffset? a, FractionalOffset? b, double t) {
assert(t != null);
if (a == null && b == null)
return null;
if (a == null)
return FractionalOffset(ui.lerpDouble(0.5, b!.dx, t)!, ui.lerpDouble(0.5, b.dy, t)!);
if (b == null)
return FractionalOffset(ui.lerpDouble(a.dx, 0.5, t)!, ui.lerpDouble(a.dy, 0.5, t)!);
return FractionalOffset(ui.lerpDouble(a.dx, b.dx, t)!, ui.lerpDouble(a.dy, b.dy, t)!);
} Here's another: static Border? lerp(Border? a, Border? b, double t) {
assert(t != null);
if (a == null && b == null)
return null;
if (a == null)
return b!.scale(t);
if (b == null)
return a.scale(1.0 - t);
return Border(
top: BorderSide.lerp(a.top, b.top, t),
right: BorderSide.lerp(a.right, b.right, t),
bottom: BorderSide.lerp(a.bottom, b.bottom, t),
left: BorderSide.lerp(a.left, b.left, t),
);
} |
We could do it with case functions, #148: static FractionalOffset? lerp(FractionalOffset? a, FractionalOffset? b, double t) {
FractionalOffset case(FractionalOffset a, FractionalOffset b, double t) {
assert(t != null);
return FractionalOffset(ui.lerpDouble(a.dx, b.dx, t)!, ui.lerpDouble(a.dy, b.dy, t)!);
}
FractionalOffset case(FractionalOffset a, Null b, double t) {
assert(t != null);
return FractionalOffset(ui.lerpDouble(a.dx, 0.5, t)!, ui.lerpDouble(a.dy, 0.5, t)!);
}
FractionalOffset case(Null a, FractionalOffset b, double t) {
assert(t != null);
return FractionalOffset(ui.lerpDouble(0.5, b!.dx, t)!, ui.lerpDouble(0.5, b.dy, t)!);
}
Null default => null;
} The point is that the cases are visible during static analysis at call sites, and if a case is statically guaranteed to apply then the compiler generates a direct invocation of that case, and hence the return type may be better. If the static information is insufficient to make the choice then the function as a whole is invoked, and that works like an if-chain. |
Or we could do full overloading. It's non-trivial to figure out what that would mean in a language like Dart, but I don't think it's completely insurmountable. It's a major change to the language and the object model, though. |
I used to be in favour of overloading, but my recent experiences with Kotlin and Swift have converted me. The developer experience is significantly better when there's no ambiguity as to what version of a method you are intending to call. |
I think this should probably be WONTFIX. Having done more code migration it really doesn't come up that much. |
Consider this method from
dart:ui
:When used, it is very common to know for a fact that
a
andb
are not null, e.g. because they are literals. It is unfortunate that one nonetheless has to ceremoniously accompany these calls with a trailing!
, as inlerpDouble(0.0, 100.0, t)!
.It would be nice if there was a way to say that if
a
andb
are known to be statically non-null, then the return value can also be guaranteed to be statically non-null, and the!
can be dropped.The text was updated successfully, but these errors were encountered: