Description
The lint unawaited_futures
is concerned with situations where a future is computed and then discarded (for example, a statement is foo();
rather than await foo();
, and foo
returns a future). This situation is considered error-prone because it is likely to cause some computations to occur "too late". The situation is hard to detect without tool support, because it is an implicit and perfectly normal thing to discard an object (of any type).
However, #57769 makes the point that this lint is currently ignoring many situations where a future is discarded: If the future is assigned to a variable or passed to a parameter whose declared type is void
or Object
or a top type, any static knowledge that there exists a future that may need to be awaited is lost, and in the case where the static type of the future will be void
we even have support to ensure that the object is discarded.
Another incompleteness in the checking performed by unawaited_futures
is the fact that higher order occurrences are ignored:
-
If, for any
T
, we assign a function of typeFuture<T> Function()
to a variable/parameter of typevoid Function()
orObject Function()
then it is likely that every invocation of said function will discard a future, which is just as error-prone as the first-order case. -
Another higher-order case arises when
Future<...>
is used as a type argument; for instance, assigning aList<Future<T>>
to a variable/parameter of typeList<void>
orList<Object>
will destroy the information that each element in that list might need to be awaited.
Issue #57653 notes that all the above lints will ignore functions whose body isn't marked async
. This means that developers will not get the heads up in the situation where a function foo
is not marked async
, but it calls bar
, and bar
has now been updated to return a future.
Future<void> bar() => ...; // Used to return `void`, was then updated.
void foo() {
...
bar(); // No lint, but discards a `Future<void>`.
...
}
We have a similar situation with return statements, for arbitrary types (not just futures, but anything which doesn't have type void
or dynamic
): void foo() { return 1; }
is an error (not just a lint or hint!), because it is considered error-prone to make it look like a value of type int
is being delivered to a caller, but it is effectively discarded because of the return type void
. Similar rules kick in when a non-void expression is returned from an async
function whose return type is void
or Future<void>
.
Finally, we have an object named useResult
in package 'meta', and this object is used as metadata on functions whose returned value should not be discarded. In this case we would also have higher-order variants of the situations which are currently linted, and those higher-order variants are not detected.
Could we generalize the lint support for flagging error-prone situations where an object is discarded, such that it is at least possible to detect higher-order cases, and the mechanism can be used for other types than Future
and/or void
? Is it just a matter of making useResult
more powerful, and perhaps adding support for a useType
annotation such that we can do @useType
on the class Future
, or implicitly add @useResult
on all async
functions whose return type isn't void
?