Skip to content

New meta annotation: @avoidReturnAsync, to warn against generic functions that return Stream/Future #61105

@rrousselGit

Description

@rrousselGit

Context

It is common that a method only supports synchronous execution (such as if it works by doing a try-finally). For example, State.initState is one of those. It purposefully rejects:

@override
Future<void> initState() {...}
// ^ Will throw an assertion error

Similarly, a common design pattern is to rely on closures to execute code before/after. For example:

void fn() {
  benchmark('fn', () {

  });
}

...
T benchmark<T>(T Function() cb) {
  final stopwatch = Stopwatch();
  try {
    stopwatch.start();
    return cb();

  } finally {
    stopwatch.stop();
  }
}

But such a benchmark implementation doesn't handle functions that return a Stream/Future, as those are unawaited. To handle those, we often need a benchmarkAsync variant:

Future<T> benchmarkAsync<T>(Future<T> Function() cb) {
  final stopwatch = Stopwatch();
  try {
    stopwatch.start();
    return await cb();
                 // ^ awaited now

  } finally {
    stopwatch.stop();
  }
}

The problem... It is easy to use benchmark instead of benchmarkAsync by mistake.

You could write:

benchmark(() async {
  await something();
}

Yet the future wouldn't be awaited.

benchmark could, like State.initState, include an assert to catch this mistake. But that's a runtime error, which is not cool!

Proposal: Add some form of annotation to reject Future/Stream

Could we maybe have a @avoidReturnAsync or something similar, placed on a function declaration or parameter?

TL;DR consider:

class State {
  @avoidReturnAsync
  void initState() {}
}

T benchmark<T>(
  @avoidReturnAsync
  T Function() cb,
) {}

Then we'd have:

class A extends State {
  @override
  Future<void> initState() {} // KO
}

class A extends State {
  @override
  Stream<void> initState() {} // KO
}

...

benchmark(() => Future.value(0)); // KO
benchmark(() => Stream.value(0)); // KO

This would avoid unexpected asynchronous override when only a synchronous implementation is supported.

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.type-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