More elaborate error when a future error handler returns something of the wrong type.
Bug: https://github.com/dart-lang/sdk/issues/44386, https://github.com/dart-lang/sdk/issues/41313
Change-Id: I87b10c3cd03475f4cd80b7a7c5cba6a558167748
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175062
Reviewed-by: Nate Bosch <nbosch@google.com>
Commit-Queue: Lasse R.H. Nielsen <lrn@google.com>
diff --git a/sdk/lib/async/future_impl.dart b/sdk/lib/async/future_impl.dart
index c89d345..d005f24 100644
--- a/sdk/lib/async/future_impl.dart
+++ b/sdk/lib/async/future_impl.dart
@@ -170,13 +170,32 @@
var errorCallback = this.errorCallback; // To enable promotion.
// If the errorCallback returns something which is not a FutureOr<T>,
// this return statement throws, and the caller handles the error.
+ dynamic result;
if (errorCallback is dynamic Function(Object, StackTrace)) {
- return _zone.runBinary<dynamic, Object, StackTrace>(
+ result = _zone.runBinary<dynamic, Object, StackTrace>(
errorCallback, asyncError.error, asyncError.stackTrace);
} else {
- return _zone.runUnary<dynamic, Object>(
+ result = _zone.runUnary<dynamic, Object>(
errorCallback as dynamic, asyncError.error);
}
+ // Give better error messages if the result is not a valid
+ // FutureOr<T>.
+ try {
+ return result;
+ } on TypeError {
+ if (handlesValue) {
+ // This is a `.then` callback with an `onError`.
+ throw ArgumentError(
+ "The error handler of Future.then"
+ " must return a value of the returned future's type",
+ "onError");
+ }
+ // This is a `catchError` callback.
+ throw ArgumentError(
+ "The error handler of "
+ "Future.catchError must return a value of the future's type",
+ "onError");
+ }
}
dynamic handleWhenComplete() {
@@ -315,10 +334,20 @@
Future<R> then<R>(FutureOr<R> f(T value), {Function? onError}) {
Zone currentZone = Zone.current;
- if (!identical(currentZone, _rootZone)) {
+ if (identical(currentZone, _rootZone)) {
+ if (onError != null &&
+ onError is! Function(Object, StackTrace) &&
+ onError is! Function(Object)) {
+ throw ArgumentError.value(
+ onError,
+ "onError",
+ "Error handler must accept one Object or one Object and a StackTrace"
+ " as arguments, and return a value of the returned future's type");
+ }
+ } else {
f = currentZone.registerUnaryCallback<FutureOr<R>, T>(f);
if (onError != null) {
- // In checked mode, this checks that onError is assignable to one of:
+ // This call also checks that onError is assignable to one of:
// dynamic Function(Object)
// dynamic Function(Object, StackTrace)
onError = _registerErrorHandler(onError, currentZone);
@@ -873,9 +902,9 @@
if (errorHandler is dynamic Function(Object)) {
return zone.registerUnaryCallback<dynamic, Object>(errorHandler);
}
- throw new ArgumentError.value(
+ throw ArgumentError.value(
errorHandler,
"onError",
"Error handler must accept one Object or one Object and a StackTrace"
- " as arguments, and return a valid result");
+ " as arguments, and return a value of the returned future's type");
}
diff --git a/sdk/lib/async/stream.dart b/sdk/lib/async/stream.dart
index 4851883..d03e095 100644
--- a/sdk/lib/async/stream.dart
+++ b/sdk/lib/async/stream.dart
@@ -661,6 +661,14 @@
/// If a broadcast stream is listened to more than once, each subscription
/// will individually perform the `test` and handle the error.
Stream<T> handleError(Function onError, {bool test(error)?}) {
+ if (onError is! void Function(Object, StackTrace) &&
+ onError is! void Function(Object)) {
+ throw ArgumentError.value(
+ onError,
+ "onError",
+ "Error handler must accept one Object or one Object and a StackTrace"
+ " as arguments.");
+ }
return new _HandleErrorStream<T>(this, onError, test);
}
diff --git a/sdk/lib/async/stream_pipe.dart b/sdk/lib/async/stream_pipe.dart
index 60aff7e..4533043 100644
--- a/sdk/lib/async/stream_pipe.dart
+++ b/sdk/lib/async/stream_pipe.dart
@@ -243,14 +243,11 @@
/// A stream pipe that converts or disposes error events
/// before passing them on.
class _HandleErrorStream<T> extends _ForwardingStream<T, T> {
- final Function _transform;
+ final Function _onError;
final bool Function(Object)? _test;
- _HandleErrorStream(
- Stream<T> source, Function onError, bool test(Object error)?)
- : this._transform = onError,
- this._test = test,
- super(source);
+ _HandleErrorStream(Stream<T> source, this._onError, this._test)
+ : super(source);
void _handleData(T data, _EventSink<T> sink) {
sink._add(data);
@@ -269,7 +266,7 @@
}
if (matches) {
try {
- _invokeErrorHandler(_transform, error, stackTrace);
+ _invokeErrorHandler(_onError, error, stackTrace);
} catch (e, s) {
if (identical(e, error)) {
sink._addError(error, stackTrace);
diff --git a/sdk/lib/core/errors.dart b/sdk/lib/core/errors.dart
index 683267d..3770a2b 100644
--- a/sdk/lib/core/errors.dart
+++ b/sdk/lib/core/errors.dart
@@ -146,11 +146,16 @@
/// Existing code may be using `message` to hold the invalid value.
/// If the `message` is not a [String], it is assumed to be a value instead
/// of a message.
+ ///
+ /// If [name] is provided, it should be the name of the parameter
+ /// which was invalid.
+ ///
+ /// Consider using [ArgumentError.value] instead to retain and document the
+ /// invalid value as well.
@pragma("vm:entry-point")
- ArgumentError([this.message])
+ ArgumentError([this.message, @Since("2.14") this.name])
: invalidValue = null,
- _hasValue = false,
- name = null;
+ _hasValue = false;
/// Creates error containing the invalid [value].
///