-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Meta issue for returns in functions with void related return types #33218
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
I agree with almost everything here, except it's not covering all the There are three different return type cases for synchronous functions:
The "is The "contains The "does not contain In other words, define (probably with a less generic name):
and then:
Since For asynchronous functions, we we really just flatten both types and do the same thing:
I haven't checked if there is any +/-1 errors in my counting here, but I think it should work, and it looks remarkably symmetric (which I take as a good sign). Synchronous examples allowed by this: void foo() { return; }
void foo() { return null as void; }
void foo() { }
FutureOr<void> foo() { return; }
FutureOr<void> foo() { return null as void; }
FutureOr<void> foo() { return Future<void>.value(); }
FutureOr<void> foo() { return null as FutureOr<void>; }
FutureOr<FutureOr<void>> foo() { return; }
FutureOr<FutureOr<void>> foo() { return Future<void>.value(); }
FutureOr<FutureOr<void>> foo() { return Future<Future<void>>.value(); }
Object foo() { return 42; } Disallowed examples: void foo() { return 42; }
void foo() { return null as FutureOr<void>; }
FutureOr<void> foo() { return 42; }
FutureOr<void> foo() { return Future<int>.value(); }
FutureOr<void> foo() { return Future<Future<void>>.value(); }
FutureOr<FutureOr<void>> foo() { return Future<Future<Future<void>>>.value(); }
Object foo() { return; }
Object foo() { return null as void; } Asynchronous examples allowed: void foo() async { return; }
void foo() async { return null as void; }
void foo() async { return Future<void>.value(); }
Future<void> foo() async { return; }
Future<void> foo() async { return null as void; }
Future<void> foo() async { return Future<void>.value(); }
Future<void> foo() async { return Future<void>.value() as FutureOr<void>; }
Future<FutureOr<void>> foo() async { return; }
Future<FutureOr<void>> foo() async { return null as void; }
Future<FutureOr<void>> foo() async { return Future<void>.value(); }
Future<FutureOr<void>> foo() async { return Future<FutureOr<void>>.value(); }
Future<FutureOr<void>> foo() async { return Future<Future<void>>.value(); }
Future<FutureOr<void>> foo() async { return Future<Future<void>>.value() as FutureOr<Future<void>>; }
Object foo() async { return 42; } Disallowed examples: void foo() async { return 42; }
void foo() async { return Future<int>.value(); }
Future<void> foo() async { return 42; }
Future<void> foo() async { return Future<Future<void>>.value(); }
Future<FutureOr<void>> foo() async { return 42; }
Future<FutureOr<void>> foo() async { return Future<Future<int>>.value(); }
Future<FutureOr<void>> foo() async { return Future<Future<Future<void>>>.value(); }
Object foo() async { return; } |
I'm surprised that you don't want to allow returning
Note that my definition of We say that
We say that Now we have the following rules:
WDYT? |
I'd still prefer not to return anything from a So a return type of
and nothing else. That exactly matches your contains relation, it's exactly { That allows you to return the result of a function with the same return type (or any part of a union-return type) directly, even if it contains I think the void contains rule must be written as:
Otherwise
(which is more readable than a negated implication, but then, almost anything is) |
SGTM. |
So, summary: Define:
(Effectively this means that the unfolding of the The rules for a synchronous non-generator function with declared return type
The rules for an asynchronous non-generator function with declared return type
|
This LGTM, except that we need to say:
and similarly for the |
I've fleshed this out with some more details here: https://dart-review.googlesource.com/c/sdk/+/52742 . I'm running presubmits now. I see some new errors that are good, but there's one class that I have a bit of a hard time justifying. Specifically, consider: Future<int> firstThing() {...}
Future<int> nextThing() {...}
Future<void> waitForSomethings() async {
var a = await firstThing();
// Do stuff
return nextThing();
} This is now an error. Looking at the examples of this that I see in code, it doesn't look to me like bad code. Futures encapsulate two things: control, and data. It doesn't make much sense to say Do we really care to make this an error? |
Yeah, that seems reasonable to me for exactly the reason you state. "I don't care what this returns, but I do care when it returns." If you go on to await or call |
If we're considering return statements in async functions to be concerned with completion of the returned future then we're out of "returned value" land and back in "calling a method" land: Future<void> f() {
// The following really means `myCompleter.complete(3)`, passing `3`
// to a parameter of type `void`.
return 3; // OK because we always allow passing arguments to such a parameter.
// The following really means
// `let v = await Future<int>.value(3) in myCompleter.complete(v)`
// so we are again just passing an argument (of type `int`) to a parameter
// of type `void`.
return Future<int>.value(3); // OK, same as above
} Using that perspective we would never complain about values being discarded, presumably unintentionally, because they are "returned to void". That's simply not an issue for Another thing is that we could have But I think it would be a really confusing situation if we start mixing the two perspectives (1: |
The specification of So it's 3: |
If we insist that That type argument may be immediate, or it may be more or less hidden. For a declared return type of Let's say that we have determined the "async return type" So could we end up in a bad state with this, e.g., by awaiting a future because of this assignability property, and then fail to complete the returned future because we chose to wait, but it would actually have worked if we had just used the future directly? The run-time type class MyFuture<U> extends Future<U> implements C { ... }
Future<C> f() async {
return new MyFuture<Object>(); // Ignore the static type, this is about run time.
} We could actually complete the returned future with the given Of course, we might also get a |
I've created this CL, which is aimed at handling funny combinations like "return type |
I abandoned said CL: We will suspend that topic for now, leaving implementations as well as specification unchanged. |
* Roll to 549c855 * Clean up return void - see dart-lang/sdk#33218
Agreed on spec has landed. |
This is a meta-issue for tracking errors associated with returns in block bodied sync and async functions whose return type is
void
,Future<void>
, orFutureOr<void>
.Tests are here: https://dart-review.googlesource.com/c/sdk/+/52742
We say that a type T contains
void
ifT
isvoid
, or ifT
isFutureOr<S>
andS
isvoid
.My understanding of the intended rules are as follows:
T
:return;
is an error unlessflatten(T)
isvoid
return exp;
is an error ifexp
has static typeS
andflatten(S)
is not assignable toflatten(T)
return exp;
is an error ifexp
has static typeS
andflatten(T)
isvoid
andflatten(S)
is notvoid
T
return;
is an error unlessT
containsvoid
return exp;
is an error ifexp
has static typeS
andS
is not assignable toT
return exp;
is an error ifexp
has static typeS
andT
isvoid
andS
is notvoid
return exp;
is an error ifexp
has static typeS
andT
isFutureOr<void>
andflatten(S)
is notvoid
cc @lrhn @eernstg @munificent
@lrhn I'm not sure which side of the argument around
return 3
in avoid
function you came down on. The last rule from theasync
clause and the last two rules from thesync
clause are intended to address those cases. Let me know what you think.The text was updated successfully, but these errors were encountered: