Skip to content
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

Fix null safe tests #37

Merged
merged 4 commits into from
Jun 8, 2020
Merged

Fix null safe tests #37

merged 4 commits into from
Jun 8, 2020

Conversation

jakemac53
Copy link
Contributor

@jakemac53 jakemac53 commented Jun 8, 2020

This is really really nasty and took a couple hours to trace down.

The catchError callback is now required to return a valid value for the future, or else you get a TypeError from deep within the bowels of the future implementation, and it is really really hard to trace it back to the root cause.

cc @lrhn is there anything we can do about this? With no static enforcement on this callback I am worried how many other people will lose many hours or days trying to track this down...

Example stack trace, which given the known solution makes sense but if you aren't aware of this new requirement is quite cryptic:

type 'Null' is not a subtype of type 'FutureOr<List<void>>'
  dart:async/future_impl.dart 157:7                              _FutureListener.handleError
  dart:async/future_impl.dart 708:47                             Future._propagateToListeners.handleError
  dart:async/future_impl.dart 729:24                             Future._propagateToListeners
  dart:async/future_impl.dart 537:5                              Future._completeError
  dart:async/future.dart 388:18                                  Future.wait.handleError
  package:stack_trace/src/stack_zone_specification.dart 138:26   StackZoneSpecification._registerBinaryCallback.<fn>.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15   StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 138:14   StackZoneSpecification._registerBinaryCallback.<fn>
  dart:async/zone.dart 1214:47                                   _rootRunBinary
  dart:async/zone.dart 1107:19                                   _CustomZone.runBinary
  dart:async/future_impl.dart 157:20                             _FutureListener.handleError
  dart:async/future_impl.dart 708:47                             Future._propagateToListeners.handleError
  dart:async/future_impl.dart 729:24                             Future._propagateToListeners
  dart:async/future_impl.dart 537:5                              Future._completeError
  dart:async/future_impl.dart 593:7                              Future._asyncCompleteError.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15   StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 116:48   StackZoneSpecification._registerCallback.<fn>
  dart:async/zone.dart 1190:13                                   _rootRun
  dart:async/zone.dart 1093:19                                   _CustomZone.run
  dart:async/zone.dart 997:7                                     _CustomZone.runGuarded
  dart:async/zone.dart 1037:23                                   _CustomZone.bindCallbackGuarded.<fn>
  dart:async/schedule_microtask.dart 41:21                       _microtaskLoop
  dart:async/schedule_microtask.dart 50:5                        _startMicrotaskLoop
  dart:isolate-patch/isolate_patch.dart 118:13                   _runPendingImmediateCallback
  dart:isolate-patch/timer_impl.dart 404:11                      _Timer._runTimers
  dart:isolate-patch/timer_impl.dart 428:5                       _Timer._handleMessage
  dart:isolate-patch/isolate_patch.dart 168:12                   _RawReceivePortImpl._handleMessage
  ===== asynchronous gap ===========================
  dart:async/zone.dart 1129:19                                   _CustomZone.registerBinaryCallback
  dart:async-patch/async_patch.dart 94:8                         _asyncErrorWrapperHelper
  package:pool/pool.dart                                         Pool.forEach.<fn>
  dart:async/stream_controller.dart 729:38                       _StreamController._recordCancel
  dart:async/stream_controller.dart 859:24                       _ControllerSubscription._onCancel
  dart:async/stream_impl.dart 264:21                             _BufferingStreamSubscription._cancel
  dart:async/stream_impl.dart 385:7                              _BufferingStreamSubscription._sendError
  dart:async/stream_impl.dart 294:7                              _BufferingStreamSubscription._addError
  dart:async/stream_controller.dart 788:19                       _SyncStreamControllerDispatch._sendError
  dart:async/stream_controller.dart 663:7                        _StreamController._addError
  dart:async/stream_controller.dart 615:5                        _StreamController.addError
  package:stack_trace/src/stack_zone_specification.dart 138:26   StackZoneSpecification._registerBinaryCallback.<fn>.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15   StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 138:14   StackZoneSpecification._registerBinaryCallback.<fn>
  dart:async/zone.dart 1214:47                                   _rootRunBinary
  dart:async/zone.dart 1107:19                                   _CustomZone.runBinary
  dart:async/future_impl.dart 157:20                             _FutureListener.handleError
  dart:async/future_impl.dart 708:47                             Future._propagateToListeners.handleError
  dart:async/future_impl.dart 729:24                             Future._propagateToListeners
  dart:async/future_impl.dart 537:5                              Future._completeError
  dart:async/future.dart 388:18                                  Future.wait.handleError
  package:stack_trace/src/stack_zone_specification.dart 138:26   StackZoneSpecification._registerBinaryCallback.<fn>.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15   StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 138:14   StackZoneSpecification._registerBinaryCallback.<fn>
  dart:async/zone.dart 1214:47                                   _rootRunBinary
  dart:async/zone.dart 1107:19                                   _CustomZone.runBinary
  dart:async/future_impl.dart 157:20                             _FutureListener.handleError
  dart:async/future_impl.dart 708:47                             Future._propagateToListeners.handleError
  dart:async/future_impl.dart 729:24                             Future._propagateToListeners
  dart:async/future_impl.dart 537:5                              Future._completeError
  dart:async/future_impl.dart 593:7                              Future._asyncCompleteError.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15   StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 116:48   StackZoneSpecification._registerCallback.<fn>
  dart:async/zone.dart 1190:13                                   _rootRun
  dart:async/zone.dart 1093:19                                   _CustomZone.run
  dart:async/zone.dart 997:7                                     _CustomZone.runGuarded
  dart:async/zone.dart 1037:23                                   _CustomZone.bindCallbackGuarded.<fn>
  dart:async/schedule_microtask.dart 41:21                       _microtaskLoop
  dart:async/schedule_microtask.dart 50:5                        _startMicrotaskLoop
  dart:isolate-patch/isolate_patch.dart 118:13                   _runPendingImmediateCallback
  dart:isolate-patch/timer_impl.dart 404:11                      _Timer._runTimers
  dart:isolate-patch/timer_impl.dart 428:5                       _Timer._handleMessage
  dart:isolate-patch/isolate_patch.dart 168:12                   _RawReceivePortImpl._handleMessage
  ===== asynchronous gap ===========================
  dart:async/zone.dart 1129:19                                   _CustomZone.registerBinaryCallback
  dart:async/future_impl.dart 823:10                             _registerErrorHandler
  dart:async/future_impl.dart 308:17                             Future.catchError
  package:pool/pool.dart 201:12                                  Pool.forEach.onListen
  dart:async/stream_controller.dart 823:24                       _runGuarded
  dart:async/stream_controller.dart 699:7                        _StreamController._subscribe.<fn>
  dart:async/stream_impl.dart 435:13                             _BufferingStreamSubscription._guardCallback
  dart:async/stream_controller.dart 698:18                       _StreamController._subscribe
  dart:async/stream_controller.dart 836:19                       _ControllerStream._createSubscription
  dart:async/stream_impl.dart 493:9                              _StreamImpl.listen
  dart:async/stream.dart 1016:10                                 Stream.toList
  test/pool_test.dart 482:21                                     main.<fn>.<fn>
  package:stack_trace/src/stack_zone_specification.dart 126:26   StackZoneSpecification._registerUnaryCallback.<fn>.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15   StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 126:14   StackZoneSpecification._registerUnaryCallback.<fn>
  dart:async/zone.dart 1198:47                                   _rootRunUnary
  dart:async/zone.dart 1100:19                                   _CustomZone.runUnary
  dart:async/future_impl.dart 143:18                             _FutureListener.handleValue
  dart:async/future_impl.dart 696:45                             Future._propagateToListeners.handleValueCallback
  dart:async/future_impl.dart 725:32                             Future._propagateToListeners
  dart:async/future_impl.dart 529:5                              Future._completeWithValue
  dart:async/future_impl.dart 567:7                              Future._asyncCompleteWithValue.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15   StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 116:48   StackZoneSpecification._registerCallback.<fn>
  dart:async/zone.dart 1190:13                                   _rootRun
  dart:async/zone.dart 1093:19                                   _CustomZone.run
  dart:async/zone.dart 997:7                                     _CustomZone.runGuarded
  dart:async/zone.dart 1037:23                                   _CustomZone.bindCallbackGuarded.<fn>
  dart:async/schedule_microtask.dart 41:21                       _microtaskLoop
  dart:async/schedule_microtask.dart 50:5                        _startMicrotaskLoop
  dart:isolate-patch/isolate_patch.dart 118:13                   _runPendingImmediateCallback
  dart:isolate-patch/timer_impl.dart 404:11                      _Timer._runTimers
  dart:isolate-patch/timer_impl.dart 428:5                       _Timer._handleMessage
  dart:isolate-patch/isolate_patch.dart 168:12                   _RawReceivePortImpl._handleMessage
  ===== asynchronous gap ===========================
  dart:async/zone.dart 1121:19                                   _CustomZone.registerUnaryCallback
  dart:async-patch/async_patch.dart 83:23                        _asyncThenWrapperHelper
  test/pool_test.dart                                            main.<fn>.<fn>
  package:test_api/src/backend/declarer.dart 176:27              Declarer.test.<fn>.<fn>.<fn>
  ===== asynchronous gap ===========================
  dart:async/zone.dart 1121:19                                   _CustomZone.registerUnaryCallback
  dart:async-patch/async_patch.dart 83:23                        _asyncThenWrapperHelper
  package:test_api/src/backend/declarer.dart                     Declarer.test.<fn>.<fn>.<fn>
  package:test_api/src/backend/invoker.dart 248:15               Invoker.waitForOutstandingCallbacks.<fn>
  dart:async/zone.dart 1190:13                                   _rootRun
  dart:async/zone.dart 1093:19                                   _CustomZone.run
  dart:async/zone.dart 1630:10                                   _runZoned
  dart:async/zone.dart 1550:10                                   runZoned
  package:test_api/src/backend/invoker.dart 245:5                Invoker.waitForOutstandingCallbacks
  package:test_api/src/backend/declarer.dart 174:34              Declarer.test.<fn>.<fn>
  dart:async/zone.dart 1190:13                                   _rootRun
  dart:async/zone.dart 1093:19                                   _CustomZone.run
  dart:async/zone.dart 1630:10                                   _runZoned
  dart:async/zone.dart 1550:10                                   runZoned
  package:test_api/src/backend/declarer.dart 173:13              Declarer.test.<fn>
  package:test_api/src/backend/invoker.dart 402:30               Invoker._onRun.<fn>.<fn>.<fn>.<fn>
  dart:async/future.dart 174:37                                  new Future.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15   StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 116:48   StackZoneSpecification._registerCallback.<fn>
  dart:async/zone.dart 1182:47                                   _rootRun
  dart:async/zone.dart 1093:19                                   _CustomZone.run
  dart:async/zone.dart 997:7                                     _CustomZone.runGuarded
  dart:async/zone.dart 1037:23                                   _CustomZone.bindCallbackGuarded.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15   StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 116:48   StackZoneSpecification._registerCallback.<fn>
  dart:async/zone.dart 1190:13                                   _rootRun
  dart:async/zone.dart 1093:19                                   _CustomZone.run
  dart:async/zone.dart 1021:23                                   _CustomZone.bindCallback.<fn>
  dart:async-patch/timer_patch.dart 18:15                        Timer._createTimer.<fn>
  dart:isolate-patch/timer_impl.dart 397:19                      _Timer._runTimers
  dart:isolate-patch/timer_impl.dart 428:5                       _Timer._handleMessage
  dart:isolate-patch/isolate_patch.dart 168:12                   _RawReceivePortImpl._handleMessage
  ===== asynchronous gap ===========================
  dart:async/zone.dart 1114:19                                   _CustomZone.registerCallback
  dart:async/zone.dart 1036:22                                   _CustomZone.bindCallbackGuarded
  dart:async/timer.dart 52:45                                    new Timer
  dart:async/timer.dart 89:9                                     Timer.run
  dart:async/future.dart 172:11                                  new Future
  package:test_api/src/backend/invoker.dart 401:21               Invoker._onRun.<fn>.<fn>.<fn>
  dart:async/zone.dart 1190:13                                   _rootRun
  dart:async/zone.dart 1093:19                                   _CustomZone.run
  dart:async/zone.dart 1630:10                                   _runZoned
  dart:async/zone.dart 1550:10                                   runZoned
  package:test_api/src/backend/invoker.dart 389:9                Invoker._onRun.<fn>.<fn>
  package:test_api/src/backend/invoker.dart 440:15               Invoker._guardIfGuarded
  package:test_api/src/backend/invoker.dart 388:7                Invoker._onRun.<fn>
  package:stack_trace/src/chain.dart 104:24                      Chain.capture.<fn>
  dart:async/zone.dart 1190:13                                   _rootRun
  dart:async/zone.dart 1093:19                                   _CustomZone.run
  dart:async/zone.dart 1630:10                                   _runZoned
  dart:async/zone.dart 1550:10                                   runZoned
  package:stack_trace/src/chain.dart 102:12                      Chain.capture
  package:test_api/src/backend/invoker.dart 387:11               Invoker._onRun
  package:test_api/src/backend/live_test_controller.dart 153:11  LiveTestController.run
  package:test_api/src/remote_listener.dart 265:16               RemoteListener._runLiveTest.<fn>
  dart:async/zone.dart 1190:13                                   _rootRun
  dart:async/zone.dart 1093:19                                   _CustomZone.run
  dart:async/zone.dart 1630:10                                   _runZoned
  dart:async/zone.dart 1550:10                                   runZoned
  package:test_api/src/remote_listener.dart 264:5                RemoteListener._runLiveTest
  package:test_api/src/remote_listener.dart 217:7                RemoteListener._serializeTest.<fn>
  dart:async/zone.dart 1198:47                                   _rootRunUnary
  dart:async/zone.dart 1100:19                                   _CustomZone.runUnary
  dart:async/zone.dart 1005:7                                    _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 357:11                             _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 285:7                              _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 784:19                       _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 655:7                        _StreamController._add
  dart:async/stream_controller.dart 597:5                        _StreamController.add
  dart:async/zone.dart 1206:13                                   _rootRunUnary
  dart:async/zone.dart 1100:19                                   _CustomZone.runUnary
  dart:async/zone.dart 1005:7                                    _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 357:11                             _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 285:7                              _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 784:19                       _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 655:7                        _StreamController._add
  dart:async/stream_controller.dart 597:5                        _StreamController.add
  dart:async/stream_controller.dart 876:13                       _StreamSinkWrapper.add
  package:stream_channel/src/guarantee_channel.dart 125:12       _GuaranteeSink.add
  package:stream_channel/src/multi_channel.dart 159:31           new _MultiChannel.<fn>
  dart:async/zone.dart 1384:10                                   _RootZone.runUnaryGuarded
  dart:_internal/async_cast.dart 85:11                           CastStreamSubscription._onData
  dart:async/zone.dart 1384:10                                   _RootZone.runUnaryGuarded
  dart:async/stream_impl.dart 357:11                             _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 285:7                              _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 784:19                       _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 655:7                        _StreamController._add
  dart:async/stream_controller.dart 597:5                        _StreamController.add
  dart:async/zone.dart 1384:10                                   _RootZone.runUnaryGuarded
  dart:async/stream_impl.dart 357:11                             _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 285:7                              _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 784:19                       _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 655:7                        _StreamController._add
  dart:async/stream_controller.dart 597:5                        _StreamController.add
  dart:async/stream_controller.dart 876:13                       _StreamSinkWrapper.add
  dart:async/zone.dart 1384:10                                   _RootZone.runUnaryGuarded
  dart:async/stream_impl.dart 357:11                             _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 285:7                              _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 784:19                       _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 655:7                        _StreamController._add
  dart:async/stream_controller.dart 597:5                        _StreamController.add
  dart:async/zone.dart 1384:10                                   _RootZone.runUnaryGuarded
  dart:async/stream_impl.dart 357:11                             _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 285:7                              _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 784:19                       _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 655:7                        _StreamController._add
  dart:async/stream_controller.dart 597:5                        _StreamController.add
  dart:async/stream_controller.dart 876:13                       _StreamSinkWrapper.add
  dart:async/zone.dart 1384:10                                   _RootZone.runUnaryGuarded
  dart:_internal/async_cast.dart 85:11                           CastStreamSubscription._onData
  dart:async/zone.dart 1384:10                                   _RootZone.runUnaryGuarded
  dart:async/stream_impl.dart 357:11                             _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 285:7                              _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 784:19                       _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 655:7                        _StreamController._add
  dart:async/stream_controller.dart 597:5                        _StreamController.add
  dart:isolate-patch/isolate_patch.dart 168:12                   _RawReceivePortImpl._handleMessage

@jakemac53
Copy link
Contributor Author

cc @kevmoo all tests pass now with strong null safety

@jakemac53
Copy link
Contributor Author

jakemac53 commented Jun 8, 2020

One possible area of improvement would be to improve the type checks here https://github.com/dart-lang/sdk/blob/master/sdk_nnbd/lib/async/future_impl.dart#L820 to actually check the return type? Something like this:

Function _registerErrorHandler<R>(Function errorHandler, Zone zone) {
  if (errorHandler is R Function(Object, StackTrace)) {
    return zone
        .registerBinaryCallback<dynamic, Object, StackTrace>(errorHandler);
  }
  if (errorHandler is R Function(Object)) {
    return zone.registerUnaryCallback<dynamic, Object>(errorHandler);
  }
  throw new ArgumentError.value(
      errorHandler,
      "onError",
      "Error handler must accept one Object or one Object and a StackTrace"
          " as arguments, and return a a valid result");
}

I think that should be sufficient to at least highlight the true error better.

lib/pool.dart Outdated Show resolved Hide resolved
lib/pool.dart Outdated Show resolved Hide resolved
@jakemac53 jakemac53 requested a review from natebosch June 8, 2020 18:03
@jakemac53 jakemac53 merged commit 40a45c8 into null_safety Jun 8, 2020
@jakemac53 jakemac53 deleted the fix-null-safe-tests branch June 8, 2020 18:23
@lrhn
Copy link
Member

lrhn commented Jun 8, 2020

I don't have any good not-very-very-breaking way to fix catchError (or any other function taking Function as an error handler).

Checking the function type is dangerous because existing code my pass in functions with return type dynamic which happens to return correctly typed values. Rejecting those functions wouldbe a breaking change.

My personal wish is that one day we drop the one-parameter error handler and require two parameters on all error handlers. Then we can give the parameter a proper type.
That would change the API of Future and require all Future implementations to be updated.

Another option is to allow int Function as a type ranging over int-returning functions of any arity.
Then the catchError parameter would be T Function onError. I don't see language versioning helping very much. It would still be a rewrite of all code implementing Future, and then we might as well go with the proper solution above.

An intermediate approach we could take is to add an extension method like:

extension FX<T> on Future<T> {
  Future<T> onError<E>(T Function(E error, StackTrace stack), [bool test(E error)?]) { ... }
}

and convince people to move from catchError to that. Might want a replacement for then as well, since that has an onError parameter too (then I'd include timeout as well, so you can do all handling in one function call). Not something that fixes the problem immediately.

So, for now, use async/await and they'll do the right thing for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants