Skip to content

Generalize handling of error-prone situations where an object is discarded #58650

Open
@eernstg

Description

@eernstg

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 type Future<T> Function() to a variable/parameter of type void Function() or Object 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 a List<Future<T>> to a variable/parameter of type List<void> or List<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?

Cf. #57653, #57769, #58622.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3A lower priority bug or feature requestarea-devexpFor issues related to the analysis server, IDE support, linter, `dart fix`, and diagnostic messages.devexp-linterIssues with the analyzer's support for the linter packagelinter-lint-requesttype-enhancementA request for a change that isn't a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions