Open eernstg opened 8 months ago
See also item 2 of https://github.com/dart-lang/language/issues/3257#issuecomment-1682082743
Oh, I actually forgot that we got so close to the same thing in #3257. You could say that this is just a follow-up which was already mentioned in the discussion of that issue:
If we wish to generalize the rules in the future along the lines discussed here then we can handle that in a new issue.
I think it's worth revisiting this idea now. Over the past few months (#3257 was written in August 2023) I've tried out various usages of extension types, and the fact that we can't have something like SafeFuture<int> f() async {...}
sticks out as an unnecessary restriction. (And the obvious workaround has a run-time performance cost, not just a developer inconvenience cost.)
Also note that we support switch (myFuture) { case SafeFuture(): ... }
, which is yet another way to achieve a typing of a given future as an extension type without running a constructor. In that context we will have a lint to warn developers in the case where SafeFuture
doesn't implement Future
, which is an even more permissive approach than the one which is being proposed for returns here.
Note that the proposal in https://github.com/dart-lang/language/issues/3614 would make this proposal more like a special case of a more general feature (details given there).
Certain kinds of function have a return mechanism which is implicit:
async
will return an object typable asFuture<T>
, whereT
is the future value type of the function (which is computed based on the declared return type).sync*
will return an object typable asIterable<T>
, whereT
is the element type of the function (computed from the declared return type).async*
will return an object typable asStream<T>
, whereT
is the element type of the function (computed from the declared return type).The given future/iterable/stream is created as part of the built-in semantics of those functions, and the developer who writes such a function doesn't have an opportunity to specify the kind of object which is being returned.
Consequently, it is a compile-time error for such functions to have a return type that fails to satisfy a certain constraint. For example, it is an error for
T f() async {...}
ifT
isn't a supertype ofFuture<Never>
.This issue is a proposal that we also allow such functions to have a return type which is an extension type that implements a type that satisfies the constraint, and whose extension type erasure also satisfies the constraint. The future value type resp. element type of the function is computed from the extension type erasure.
In other words, everything is unchanged, except that we're also allowed to return an extension type whose erasure is OK, if it also "admits" to being a future (or iterable, or stream) by having a
Future
(Iterable
,Stream
) type as a superinterface. For example:Here is an example where this little snippet of expressive power could be helpful:
The point is that a
SafeFuture
requires a fully typedonError
in the signature of itsthen
method, which will eliminate the run-time type error that the regularFuture
incurs.(OK, we might want a
then
and athenNoStackTrace
to allow anonError
that doesn't receive the stack trace, but that's just something we can play around with, the point is that we can modify and/or add members toFuture
, and we can use that enhanced interface of futures all over the place because all async functions can return it, if we wish to do that.)A similar technique could also be used to provide an invariant future type (
EvenSaferFuture
;-), which is needed in order to avoid a covariance related run-time type error (e.g., if we have aFuture<int>
with static typeFuture<num>
and pass anonError
that has return typenum
).We can just go ahead and do this in most cases, but when it comes to
async
functions it creates a need for an inconvenient wrapper function:This proposal allows us to avoid this wrapper function which saves developer time and execution time. It is type safe, because the return statements will rely on the representation type of the return type which is actually what we have at run time