diff --git a/pkg/compiler/test/sourcemaps/stacktrace_test.dart b/pkg/compiler/test/sourcemaps/stacktrace_test.dart index 091dd86fc89..00ea52f6a3b 100644 --- a/pkg/compiler/test/sourcemaps/stacktrace_test.dart +++ b/pkg/compiler/test/sourcemaps/stacktrace_test.dart @@ -125,8 +125,7 @@ const List afterExceptions = const [ const LineException('_asyncAwait.', 'async_patch.dart'), const LineException('_asyncStart.', 'async_patch.dart'), const LineException('_RootZone.runUnary', 'zone.dart'), - const LineException('_FutureListener.propagate', 'future_impl.dart'), - const LineException('_FutureListener.propagateResults', 'future_impl.dart'), + const LineException('_FutureListener.handleValue', 'future_impl.dart'), const LineException('_Future._completeWithValue', 'future_impl.dart'), const LineException( '_Future._propagateToListeners.handleValueCallback', 'future_impl.dart'), @@ -137,7 +136,6 @@ const List afterExceptions = const [ const LineException('_startMicrotaskLoop', 'schedule_microtask.dart'), const LineException('_AsyncRun._scheduleImmediateJsOverride.internalCallback', 'async_patch.dart'), - const LineException('Closure.cspForwardCall', 'js_helper.dart'), const LineException('invokeClosure.', 'js_helper.dart'), const LineException('invokeClosure', 'js_helper.dart'), const LineException('convertDartClosureToJS', 'js_helper.dart'), diff --git a/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect b/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect index ec6a7f6922c..e2189027e1f 100644 --- a/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect +++ b/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect @@ -28,19 +28,19 @@ static method main() → dynamic Extra constant evaluation status: -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:814:13 -> SymbolConstant(#catchError) -Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:814:13 -> ListConstant(const []) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:814:13 -> SymbolConstant(#test) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:865:13 -> SymbolConstant(#whenComplete) -Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:865:13 -> ListConstant(const []) -Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:865:13 -> MapConstant(const {}) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:909:13 -> SymbolConstant(#timeout) -Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:909:13 -> ListConstant(const []) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:909:13 -> SymbolConstant(#onTimeout) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:763:13 -> SymbolConstant(#then) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:763:13 -> SymbolConstant(#onError) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> SymbolConstant(#asStream) -Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> ListConstant(const []) -Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> ListConstant(const []) -Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> MapConstant(const {}) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:821:13 -> SymbolConstant(#catchError) +Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:821:13 -> ListConstant(const []) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:821:13 -> SymbolConstant(#test) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:872:13 -> SymbolConstant(#whenComplete) +Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:872:13 -> ListConstant(const []) +Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:872:13 -> MapConstant(const {}) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:916:13 -> SymbolConstant(#timeout) +Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:916:13 -> ListConstant(const []) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:916:13 -> SymbolConstant(#onTimeout) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:770:13 -> SymbolConstant(#then) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:770:13 -> SymbolConstant(#onError) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> SymbolConstant(#asStream) +Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> ListConstant(const []) +Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> ListConstant(const []) +Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> MapConstant(const {}) Extra constant evaluation: evaluated: 61, effectively constant: 15 diff --git a/pkg/vm_service/test/common/test_helper.dart b/pkg/vm_service/test/common/test_helper.dart index b9adcb43c9e..9f46c4d5e55 100644 --- a/pkg/vm_service/test/common/test_helper.dart +++ b/pkg/vm_service/test/common/test_helper.dart @@ -474,28 +474,3 @@ Future runVMTests( pause_on_unhandled_exceptions: pause_on_unhandled_exceptions); } } - -/// Waits until the breakpoint map has been updated with the given -/// [breakpoint]. -/// -/// The `Isolate.addBreakpoint()` call will do a RPC call to the VM and return -/// the added breakpoint. -/// -/// Though the `Isolate.breakpoints` list will *not* reflect this immediately. -/// This list is updated asynchronously by listening for -/// `ServiceEvent.kBreakpointAdded` events from the VM's `kDebugStream` (see -/// [VM] class) -Future waitUntilBreakpointIsReady( - Isolate isolate, Breakpoint breakpoint) async { - for (int i = 0; i < 100; ++i) { - var breakpoints = isolate.breakpoints; - if (breakpoints != null && - breakpoints - .any((p) => p.breakpointNumber == breakpoint.breakpointNumber)) { - return; - } - await Future.delayed(const Duration(milliseconds: 1)); - } - throw TimeoutException( - 'The expected breakpoint has not been advertised by the VM in time'); -} diff --git a/pkg/vm_service/test/debugging_inlined_finally_test.dart b/pkg/vm_service/test/debugging_inlined_finally_test.dart index 6e65fe59e73..c6ebeda5f1b 100644 --- a/pkg/vm_service/test/debugging_inlined_finally_test.dart +++ b/pkg/vm_service/test/debugging_inlined_finally_test.dart @@ -60,7 +60,6 @@ var tests = [ LINE_A, ); isolate = await service.getIsolate(isolateId); - await waitUntilBreakpointIsReady(isolate, bpt); expect(isolate.breakpoints!.length, 1); } @@ -74,7 +73,6 @@ var tests = [ LINE_B, ); isolate = await service.getIsolate(isolateId); - await waitUntilBreakpointIsReady(isolate, bpt); expect(isolate.breakpoints!.length, 2); } @@ -88,7 +86,6 @@ var tests = [ LINE_C, ); isolate = await service.getIsolate(isolateId); - await waitUntilBreakpointIsReady(isolate, bpt); expect(isolate.breakpoints!.length, 3); } // Wait for breakpoint events. diff --git a/pkg/vm_service/test/debugging_test.dart b/pkg/vm_service/test/debugging_test.dart index 773dc13acad..cf56e3eaea1 100644 --- a/pkg/vm_service/test/debugging_test.dart +++ b/pkg/vm_service/test/debugging_test.dart @@ -94,7 +94,6 @@ var tests = [ expect(location.line, 16); isolate = await service.getIsolate(isolateId); - await waitUntilBreakpointIsReady(isolate, bpt); expect(isolate.breakpoints!.length, 1); await completer.future; // Wait for breakpoint events. @@ -218,7 +217,6 @@ var tests = [ // Refresh isolate state. isolate = await service.getIsolate(isolateId); - await waitUntilBreakpointIsReady(isolate, bpt); expect(isolate.breakpoints!.length, 1); await completer.future; // Wait for breakpoint events. diff --git a/pkg/vm_service/test/get_stack_test.dart b/pkg/vm_service/test/get_stack_test.dart index 2743ca1835b..f1df2ba9496 100644 --- a/pkg/vm_service/test/get_stack_test.dart +++ b/pkg/vm_service/test/get_stack_test.dart @@ -95,7 +95,7 @@ final tests = [ (VmService service, IsolateRef isolateRef) async { final result = await service.getStack(isolateRef.id!); - expect(result.frames, hasLength(8)); // Must match frames below. + expect(result.frames, hasLength(10)); expect(result.asyncCausalFrames, hasLength(26)); expect(result.awaiterFrames, hasLength(13)); @@ -107,6 +107,8 @@ final tests = [ [equals('Regular'), anything], [equals('Regular'), anything], [equals('Regular'), anything], + [equals('Regular'), anything], + [equals('Regular'), anything], [equals('Regular'), endsWith(' _RawReceivePortImpl._handleMessage')], ]); diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart index 2be0a6deb40..181fd2b9059 100644 --- a/runtime/observatory/lib/src/service/object.dart +++ b/runtime/observatory/lib/src/service/object.dart @@ -1793,10 +1793,8 @@ class Isolate extends ServiceObjectOwner implements M.Isolate { case ServiceEvent.kPausePostRequest: case ServiceEvent.kNone: case ServiceEvent.kResume: - assert( - (pauseEvent == null) || - !event.timestamp!.isBefore(pauseEvent!.timestamp), - "${event.timestamp} is before ${pauseEvent!.timestamp}"); + assert((pauseEvent == null) || + !event.timestamp!.isBefore(pauseEvent!.timestamp)); pauseEvent = createEventFromServiceEvent(event) as M.DebugEvent; _updateRunState(); break; diff --git a/runtime/observatory/tests/service/debugging_inlined_finally_test.dart b/runtime/observatory/tests/service/debugging_inlined_finally_test.dart index 45965702f3c..e2e1bbb3542 100644 --- a/runtime/observatory/tests/service/debugging_inlined_finally_test.dart +++ b/runtime/observatory/tests/service/debugging_inlined_finally_test.dart @@ -54,7 +54,6 @@ var tests = [ expect(bpt.location!.script.id, equals(script.id)); expect(bpt.location!.script.tokenToLine(bpt.location!.tokenPos), equals(LINE_A)); - await waitUntilBreakpointIsReady(isolate.breakpoints, result); expect(isolate.breakpoints.length, equals(1)); } @@ -66,7 +65,6 @@ var tests = [ expect(bpt.location!.script.id, equals(script.id)); expect(bpt.location!.script.tokenToLine(bpt.location!.tokenPos), equals(LINE_B)); - await waitUntilBreakpointIsReady(isolate.breakpoints, result); expect(isolate.breakpoints.length, equals(2)); } @@ -78,7 +76,6 @@ var tests = [ expect(bpt.location!.script.id, equals(script.id)); expect(bpt.location!.script.tokenToLine(bpt.location!.tokenPos), equals(LINE_C)); - await waitUntilBreakpointIsReady(isolate.breakpoints, result); expect(isolate.breakpoints.length, equals(3)); } diff --git a/runtime/observatory/tests/service/debugging_test.dart b/runtime/observatory/tests/service/debugging_test.dart index 5e2a6693d6d..5d4b6307c76 100644 --- a/runtime/observatory/tests/service/debugging_test.dart +++ b/runtime/observatory/tests/service/debugging_test.dart @@ -80,7 +80,6 @@ var tests = [ expect(bpt.location!.script.id, equals(script.id)); expect( bpt.location!.script.tokenToLine(bpt.location!.tokenPos), equals(15)); - await waitUntilBreakpointIsReady(isolate.breakpoints, bpt); expect(isolate.breakpoints.length, equals(1)); await completer.future; // Wait for breakpoint events. @@ -191,7 +190,6 @@ var tests = [ expect(bpt.location!.script.name, equals('debugging_test.dart')); expect( bpt.location!.script.tokenToLine(bpt.location!.tokenPos), equals(12)); - await waitUntilBreakpointIsReady(isolate.breakpoints, bpt); expect(isolate.breakpoints.length, equals(1)); await completer.future; // Wait for breakpoint events. diff --git a/runtime/observatory/tests/service/get_stack_limit_rpc_test.dart b/runtime/observatory/tests/service/get_stack_limit_rpc_test.dart index b720f16266e..b3e47e21703 100644 --- a/runtime/observatory/tests/service/get_stack_limit_rpc_test.dart +++ b/runtime/observatory/tests/service/get_stack_limit_rpc_test.dart @@ -53,6 +53,7 @@ var tests = [ var frames = stack['frames']; var asyncFrames = stack['asyncCausalFrames']; var awaiterFrames = stack['awaiterFrames']; + expect(frames.length, greaterThanOrEqualTo(20)); expect(asyncFrames.length, greaterThan(frames.length)); expect(awaiterFrames.length, greaterThan(frames.length)); expect(stack['truncated'], false); diff --git a/runtime/observatory/tests/service/test_helper.dart b/runtime/observatory/tests/service/test_helper.dart index 1ced062be2e..bf6ce7341bb 100644 --- a/runtime/observatory/tests/service/test_helper.dart +++ b/runtime/observatory/tests/service/test_helper.dart @@ -716,23 +716,3 @@ Future runDDSTests(List mainArgs, List tests, ); } } - -/// Waits until the breakpoint map has been updated with the given -/// [breakpoint]. -/// -/// The `Isolate.addBreakpoint()` call will do a RPC call to the VM and return -/// the added breakpoint. -/// -/// Though the `Isolate.breakpoints` map will *not* reflect this immediately. -/// This map is updated asynchronously by listening for -/// `ServiceEvent.kBreakpointAdded` events from the VM's `kDebugStream` (see -/// [VM] class) -Future waitUntilBreakpointIsReady( - Map map, Breakpoint breakpoint) async { - for (int i = 0; i < 100; ++i) { - if (map.containsKey(breakpoint.number)) return; - await Future.delayed(const Duration(milliseconds: 1)); - } - throw TimeoutException( - 'The expected breakpoint has not been advertised by the VM in time'); -} diff --git a/runtime/observatory_2/tests/service_2/debugging_inlined_finally_test.dart b/runtime/observatory_2/tests/service_2/debugging_inlined_finally_test.dart index 7a084108a62..149f610f118 100644 --- a/runtime/observatory_2/tests/service_2/debugging_inlined_finally_test.dart +++ b/runtime/observatory_2/tests/service_2/debugging_inlined_finally_test.dart @@ -54,7 +54,6 @@ var tests = [ expect(bpt.location.script.id, equals(script.id)); expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), equals(LINE_A)); - await waitUntilBreakpointIsReady(isolate.breakpoints, result); expect(isolate.breakpoints.length, equals(1)); } @@ -66,7 +65,6 @@ var tests = [ expect(bpt.location.script.id, equals(script.id)); expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), equals(LINE_B)); - await waitUntilBreakpointIsReady(isolate.breakpoints, result); expect(isolate.breakpoints.length, equals(2)); } @@ -78,7 +76,6 @@ var tests = [ expect(bpt.location.script.id, equals(script.id)); expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), equals(LINE_C)); - await waitUntilBreakpointIsReady(isolate.breakpoints, result); expect(isolate.breakpoints.length, equals(3)); } diff --git a/runtime/observatory_2/tests/service_2/debugging_test.dart b/runtime/observatory_2/tests/service_2/debugging_test.dart index b3efdaa6b6e..15daf6b8866 100644 --- a/runtime/observatory_2/tests/service_2/debugging_test.dart +++ b/runtime/observatory_2/tests/service_2/debugging_test.dart @@ -79,7 +79,6 @@ var tests = [ expect(bpt.type, equals('Breakpoint')); expect(bpt.location.script.id, equals(script.id)); expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), equals(15)); - await waitUntilBreakpointIsReady(isolate.breakpoints, bpt); expect(isolate.breakpoints.length, equals(1)); await completer.future; // Wait for breakpoint events. @@ -189,7 +188,6 @@ var tests = [ expect(bpt.type, equals('Breakpoint')); expect(bpt.location.script.name, equals('debugging_test.dart')); expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), equals(12)); - await waitUntilBreakpointIsReady(isolate.breakpoints, bpt); expect(isolate.breakpoints.length, equals(1)); await completer.future; // Wait for breakpoint events. diff --git a/runtime/observatory_2/tests/service_2/get_stack_limit_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_stack_limit_rpc_test.dart index 9e6faf679f5..dc9f2533599 100644 --- a/runtime/observatory_2/tests/service_2/get_stack_limit_rpc_test.dart +++ b/runtime/observatory_2/tests/service_2/get_stack_limit_rpc_test.dart @@ -53,6 +53,7 @@ var tests = [ var frames = stack['frames']; var asyncFrames = stack['asyncCausalFrames']; var awaiterFrames = stack['awaiterFrames']; + expect(frames.length, greaterThanOrEqualTo(20)); expect(asyncFrames.length, greaterThan(frames.length)); expect(awaiterFrames.length, greaterThan(frames.length)); expect(stack['truncated'], false); diff --git a/runtime/observatory_2/tests/service_2/test_helper.dart b/runtime/observatory_2/tests/service_2/test_helper.dart index 07db6af689f..b2667bf4827 100644 --- a/runtime/observatory_2/tests/service_2/test_helper.dart +++ b/runtime/observatory_2/tests/service_2/test_helper.dart @@ -715,23 +715,3 @@ Future runDDSTests(List mainArgs, List tests, ); } } - -/// Waits until the breakpoint map has been updated with the given -/// [breakpoint]. -/// -/// The `Isolate.addBreakpoint()` call will do a RPC call to the VM and return -/// the added breakpoint. -/// -/// Though the `Isolate.breakpoints` map will *not* reflect this immediately. -/// This map is updated asynchronously by listening for -/// `ServiceEvent.kBreakpointAdded` events from the VM's `kDebugStream` (see -/// [VM] class) -Future waitUntilBreakpointIsReady( - Map map, Breakpoint breakpoint) async { - for (int i = 0; i < 100; ++i) { - if (map.containsKey(breakpoint.number)) return; - await Future.delayed(const Duration(milliseconds: 1)); - } - throw TimeoutException( - 'The expected breakpoint has not been advertised by the VM in time'); -} diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h index 07688207600..98e305ee6ac 100644 --- a/runtime/vm/compiler/recognized_methods_list.h +++ b/runtime/vm/compiler/recognized_methods_list.h @@ -281,10 +281,10 @@ namespace dart { V(::, reachabilityFence, ReachabilityFence, 0x730f2b7f) \ V(::, _asyncThenWrapperHelper, AsyncThenWrapperHelper, 0x0c17f838) \ V(_Utf8Decoder, _scan, Utf8DecoderScan, 0xf296c901) \ - V(_Future, timeout, FutureTimeout, 0x8d415c97) \ + V(_Future, timeout, FutureTimeout, 0xa7cb3294) \ V(Future, wait, FutureWait, 0xb0b596bd) \ V(_RootZone, runUnary, RootZoneRunUnary, 0xb607f8bf) \ - V(_FutureListener, propagate, FutureListenerPropagate, 0x8a29635e) \ + V(_FutureListener, handleValue, FutureListenerHandleValue, 0x438115a8) \ V(::, has63BitSmis, Has63BitSmis, 0xf61b56f1) \ V(::, get:extensionStreamHasListener, ExtensionStreamHasListener, 0xfab46343)\ diff --git a/runtime/vm/stack_trace.cc b/runtime/vm/stack_trace.cc index 7cc318cb957..671f58b7b4f 100644 --- a/runtime/vm/stack_trace.cc +++ b/runtime/vm/stack_trace.cc @@ -21,8 +21,8 @@ const intptr_t k_FutureListener_stateCatchError = 2; // - sdk/lib/async/future_impl.dart:_FutureListener.stateWhenComplete. const intptr_t k_FutureListener_stateWhenComplete = 8; -// Keep in sync with sdk/lib/async/future_impl.dart:_FutureListener.propagate. -const intptr_t kNumArgsFutureListenerPropagate = 0; +// Keep in sync with sdk/lib/async/future_impl.dart:_FutureListener.handleValue. +const intptr_t kNumArgsFutureListenerHandleValue = 1; // Find current yield index from async closure. // Async closures contains a variable, :await_jump_var that holds the index into @@ -538,19 +538,19 @@ ClosurePtr StackTraceUtils::ClosureFromFrameFunction( return caller_closure_finder->FindCaller(closure); } - // May have been called from `_FutureListener.propagate`, which means its + // May have been called from `_FutureListener.handleValue`, which means its // receiver holds the Future chain. DartFrameIterator future_frames(frames); if (function.recognized_kind() == MethodRecognizer::kRootZoneRunUnary) { frame = future_frames.NextFrame(); function = frame->LookupDartFunction(); if (function.recognized_kind() != - MethodRecognizer::kFutureListenerPropagate) { + MethodRecognizer::kFutureListenerHandleValue) { return Closure::null(); } } if (function.recognized_kind() == - MethodRecognizer::kFutureListenerPropagate) { + MethodRecognizer::kFutureListenerHandleValue) { *is_async = true; *skip_frame = true; @@ -558,9 +558,9 @@ ClosurePtr StackTraceUtils::ClosureFromFrameFunction( // before the arguments to the call. Object& receiver = Object::Handle(*(reinterpret_cast(frame->GetCallerSp()) + - kNumArgsFutureListenerPropagate)); + kNumArgsFutureListenerHandleValue)); if (receiver.ptr() == Symbols::OptimizedOut().ptr()) { - // In the very rare case that _FutureListener.propagate has deoptimized + // In the very rare case that _FutureListener.handleValue has deoptimized // it may override the receiver slot in the caller frame with "" due to the `this` no longer being needed. return Closure::null(); diff --git a/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart index d5afd3eb50d..b9cf8050d51 100644 --- a/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart +++ b/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart @@ -27,7 +27,7 @@ _async(Function() initGenerator) { late Object? Function(Object?) onValue; late Object Function(Object, StackTrace?) onError; - _Future onAwait(Object? value) { + onAwait(Object? value) { _Future f; if (value is _Future) { f = value; @@ -84,15 +84,31 @@ _async(Function() initGenerator) { var iteratorValue = JS('', '#.next(null)', iter); var value = JS('', '#.value', iteratorValue); if (JS('!', '#.done', iteratorValue)) { - if (isRunningAsEvent) { - asyncFuture._completeUnchecked(value); + // TODO(jmesserly): this is a workaround for ignored cast failures. + // Remove it once we've fixed those. We should be able to call: + // + // if (isRunningAsEvent) { + // asyncFuture._complete(value); + // } else { + // asyncFuture._asyncComplete(value); + // } + // + // But if the user code returns `Future` instead of + // `Future`, that function won't recognize it as a future and will + // instead treat it as a completed value. + if (value is Future) { + if (value is _Future) { + _Future._chainCoreFuture(value, asyncFuture); + } else { + asyncFuture._chainForeignFuture(value); + } + } else if (isRunningAsEvent) { + asyncFuture._completeWithValue(JS('', '#', value)); } else { - asyncFuture._asyncCompleteUnchecked(value); + asyncFuture._asyncComplete(JS('', '#', value)); } } else { - _Future awaitedFuture = onAwait(value); // _Future. - awaitedFuture.then(asyncFuture._completeWithValueUnchecked, - onError: asyncFuture._completeError); + _Future._chainCoreFuture(onAwait(value), asyncFuture); } } catch (e, s) { if (isRunningAsEvent) { diff --git a/sdk/lib/_internal/js_runtime/lib/async_patch.dart b/sdk/lib/_internal/js_runtime/lib/async_patch.dart index 8ff71840d09..5f8f5af99fe 100644 --- a/sdk/lib/_internal/js_runtime/lib/async_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/async_patch.dart @@ -198,20 +198,27 @@ class _AsyncAwaitCompleter implements Completer { void complete([FutureOr? value]) { // All paths require that if value is null, null as T succeeds. - value ??= value as T; + value = (value == null) ? value as T : value; if (!isSync) { - _future._asyncCompleteUnchecked(value); + _future._asyncComplete(value); + } else if (value is Future) { + assert(!_future._isComplete); + _future._chainFuture(value); } else { - _future._completeUnchecked(value); + // TODO(40014): Remove cast when type promotion works. + // This would normally be `as T` but we use `as dynamic` to make the + // unneeded check be implicit to match dart2js unsound optimizations in + // the user code. + _future._completeWithValue(value as dynamic); } } void completeError(Object e, [StackTrace? st]) { - var error = AsyncError(e, st); + st ??= AsyncError.defaultStackTrace(e); if (isSync) { - _future._completeErrorObject(error); + _future._completeError(e, st); } else { - _future._asyncCompleteErrorObject(error); + _future._asyncCompleteError(e, st); } } diff --git a/sdk/lib/_internal/vm/lib/async_patch.dart b/sdk/lib/_internal/vm/lib/async_patch.dart index ec8c2f8b693..238cbb6aef0 100644 --- a/sdk/lib/_internal/vm/lib/async_patch.dart +++ b/sdk/lib/_internal/vm/lib/async_patch.dart @@ -236,7 +236,7 @@ class _AsyncStarStreamController { if ((future != null) && future._mayComplete) { // If the stream has been cancelled, complete the cancellation future // with the error. - future._completeWithValueUnchecked(null); + future._completeWithValue(null); } controller.close(); } @@ -292,7 +292,7 @@ void _completeOnAsyncReturn(_Future _future, Object? value, bool is_sync) { if (!is_sync || value is Future) { _future._asyncCompleteUnchecked(value); } else { - _future._completeWithValueUnchecked(value); + _future._completeWithValue(value); } } @@ -303,9 +303,9 @@ void _completeWithNoFutureOnAsyncReturn( // allow then and error handlers to be attached. // async_jump_var=0 is prior to first await, =1 is first await. if (!is_sync) { - _future._asyncCompleteWithValueUnchecked(value); + _future._asyncCompleteUncheckedNoFuture(value); } else { - _future._completeWithValueUnchecked(value); + _future._completeWithValue(value); } } diff --git a/sdk/lib/async/async.dart b/sdk/lib/async/async.dart index efcb3d11195..ca05fc66a65 100644 --- a/sdk/lib/async/async.dart +++ b/sdk/lib/async/async.dart @@ -108,7 +108,6 @@ import "dart:_internal" CastStream, CastStreamTransformer, checkNotNullable, - checkUnsoundType, EmptyIterator, IterableElementError, nullFuture, diff --git a/sdk/lib/async/future.dart b/sdk/lib/async/future.dart index 1e662ae50cc..f83386d30a3 100644 --- a/sdk/lib/async/future.dart +++ b/sdk/lib/async/future.dart @@ -303,13 +303,18 @@ abstract class Future { if (result is Future) { return result; } else { - return _Future().._setValue(result); + // TODO(40014): Remove cast when type promotion works. + return new _Future.value(result as dynamic); } } catch (error, stackTrace) { - var currentZone = Zone._current; - return _Future.zone(currentZone) - .._asyncCompleteErrorObject( - _interceptSyncError(currentZone, error, stackTrace)); + var future = new _Future(); + AsyncError? replacement = Zone.current.errorCallback(error, stackTrace); + if (replacement != null) { + future._asyncCompleteError(replacement.error, replacement.stackTrace); + } else { + future._asyncCompleteError(error, stackTrace); + } + return future; } } @@ -341,10 +346,7 @@ abstract class Future { @pragma("vm:entry-point") @pragma("vm:prefer-inline") factory Future.value([FutureOr? value]) { - if (value != null) { - return _Future().._asyncCompleteUnchecked(value); - } - return _Future().._setValue(value as T); + return new _Future.immediate(value == null ? value as T : value); } /// Creates a future that completes with an error. @@ -367,10 +369,15 @@ abstract class Future { factory Future.error(Object error, [StackTrace? stackTrace]) { // TODO(40614): Remove once non-nullability is sound. checkNotNullable(error, "error"); - var currentZone = Zone._current; - return _Future.zone(currentZone) - .._asyncCompleteErrorObject( - _interceptSyncError(currentZone, error, stackTrace)); + if (!identical(Zone.current, _rootZone)) { + AsyncError? replacement = Zone.current.errorCallback(error, stackTrace); + if (replacement != null) { + error = replacement.error; + stackTrace = replacement.stackTrace; + } + } + stackTrace ??= AsyncError.defaultStackTrace(error); + return new _Future.immediateError(error, stackTrace); } /// Creates a future that runs its computation after a delay. @@ -1223,13 +1230,28 @@ abstract class Completer { // for error replacement and missing stack trace first. void _completeWithErrorCallback( _Future result, Object error, StackTrace? stackTrace) { - result._completeErrorObject( - _interceptSyncError(result._zone, error, stackTrace)); + AsyncError? replacement = Zone.current.errorCallback(error, stackTrace); + if (replacement != null) { + error = replacement.error; + stackTrace = replacement.stackTrace; + } else { + stackTrace ??= AsyncError.defaultStackTrace(error); + } + result._completeError(error, stackTrace); } // Like [_completeWithErrorCallback] but completes asynchronously. void _asyncCompleteWithErrorCallback( _Future result, Object error, StackTrace? stackTrace) { - result._asyncCompleteErrorObject( - _interceptSyncError(result._zone, error, stackTrace)); + AsyncError? replacement = Zone.current.errorCallback(error, stackTrace); + if (replacement != null) { + error = replacement.error; + stackTrace = replacement.stackTrace; + } else { + stackTrace ??= AsyncError.defaultStackTrace(error); + } + if (stackTrace == null) { + throw "unreachable"; // TODO(lrn): Remove when type promotion works. + } + result._asyncCompleteError(error, stackTrace); } diff --git a/sdk/lib/async/future_impl.dart b/sdk/lib/async/future_impl.dart index e289b8aed64..44b390658fa 100644 --- a/sdk/lib/async/future_impl.dart +++ b/sdk/lib/async/future_impl.dart @@ -4,18 +4,7 @@ part of dart.async; -/// Implementation of [Completer] based on a [_Future]. -/// -/// The [_Future] class implements the functionality internally as -/// private methods, that this completer can call into. -/// -/// The completer is either synchronous or asynchronous. In the latter -/// case, it asks the `_Future` to delay completion. abstract class _Completer implements Completer { - /// The future completed by this completer. - /// - /// Only ever completed through calling [complete] or [completeError]. - /// which can only be done once. final _Future future = new _Future(); void complete([FutureOr? value]); @@ -24,67 +13,45 @@ abstract class _Completer implements Completer { // TODO(40614): Remove once non-nullability is sound. checkNotNullable(error, "error"); if (!future._mayComplete) throw new StateError("Future already completed"); - _completeError(_interceptSyncError(Zone.current, error, stackTrace)); + AsyncError? replacement = Zone.current.errorCallback(error, stackTrace); + if (replacement != null) { + error = replacement.error; + stackTrace = replacement.stackTrace; + } else { + stackTrace ??= AsyncError.defaultStackTrace(error); + } + _completeError(error, stackTrace); } - void _completeError(AsyncError error); + void _completeError(Object error, StackTrace stackTrace); - /// Whether calling [complete] or [completeError] is disallowed. - /// - /// The [_Future._mayComplete] differs from [_Future._isComplete]. - /// The former means calling [_Future._complete] is allowed. - /// The latter means it has a value or an error. - /// If the [_Future] has been "completed" with another future, - /// or asynchronously completed with an error, - /// the two differs while waiting for that the final result to be set. + // The future's _isComplete doesn't take into account pending completions. + // We therefore use _mayComplete. bool get isCompleted => !future._mayComplete; } class _AsyncCompleter extends _Completer { void complete([FutureOr? value]) { if (!future._mayComplete) throw new StateError("Future already completed"); - // Ensure that value is a `FutureOr`, which means it can only be - // null (or omitted) if [T] is nullable. - FutureOr checkedValue = value as dynamic; - future._asyncCompleteUnchecked(checkedValue); + future._asyncComplete(value == null ? value as dynamic : value); } - void _completeError(AsyncError error) { - future._asyncCompleteErrorObject(error); + void _completeError(Object error, StackTrace stackTrace) { + future._asyncCompleteError(error, stackTrace); } } class _SyncCompleter extends _Completer { void complete([FutureOr? value]) { if (!future._mayComplete) throw new StateError("Future already completed"); - // Ensure that value is a `FutureOr`, which means it can only be - // null (or omitted) if [T] is nullable. - FutureOr checkedValue = value as dynamic; - future._completeUnchecked(checkedValue); + future._complete(value == null ? value as dynamic : value); } - void _completeError(AsyncError error) { - future._completeErrorObject(error); + void _completeError(Object error, StackTrace stackTrace) { + future._completeError(error, stackTrace); } } -/// A listener on a `Future` which creates a `Future`. -/// -/// The listener knows the type of both the [source] and [result] futures, -/// and is the class which calls user code and enforces types around it, -/// where `_Future` itself is mostly untyped. -/// -/// If the listener is not handling values (not related to a `.then` call), -/// the [S] and [T] types are always the same. -/// -/// When the [source] future is completed, the listener's -/// [propagate] function will be called at some later point -/// (but often very soon). -/// The [propagate] function takes the result of [source] and -/// calls the appropriate user callbacks, then completes [result] -/// with the result of that call, and returns the listeners of -/// the newly completed [result] future, so they too can be -/// processed. class _FutureListener { // Keep in sync with sdk/runtime/vm/stack_trace.cc. static const int maskValue = 1; @@ -105,47 +72,14 @@ class _FutureListener { static const int maskType = maskValue | maskError | maskTestError | maskWhenComplete; - // Listeners on the same future as single-liked list. - // - // New listeners are "pushed" at the head of the list, so the - // list is in reverse listening order. The list is reveresed - // before being used, so we preserve listening order in the callbacks. - // - // Type parameters are both top types because: - // - The first, source, type is contravariant. When listeners - // are added to a chained future, they may accept a supertype - // of what the source provides (`super S`). - // - The second, target, type differes between listeners on the same - // future, so the types are unrelated. - _FutureListener? _nextListener; + // Listeners on the same future are linked through this link. + _FutureListener? _nextListener; - // The future that this listener gets its result from. - // - // Can be changed if the listener is moved to a different future. - // Has the type `Future`, which allows `S` to be a supertype - // of the actual future's type. - _Future source; - - /// The future to complete when this listener is activated. - /// - /// The [result] future of a future listener is *only ever* completed - /// through the listener's [propagate] method. After calling that, - /// the [result] is either completed, chained to a native future, - /// or pending the result of a non-native future. - /// Before calling [propagate], the future is always incomplete - /// ([_stateIncomplete], possibly [_stateIgnoreError]). + // The future to complete when this listener is activated. @pragma("vm:entry-point") final _Future result; - /// Which fields means what. - /// - /// Always satisfies precisely one of - /// * [_mayAddListener] ([_stateIncomplete], - /// possibly including [_statePendingComplete] and [_stateIgnoreError]). - /// * [_isChained] ([_stateChained]). - /// * [_isComplete] ([_stateValue] or [_stateError]). - /// - /// Transitions by calling [_setError], [_setValue], [_chainCoreFuture]. + // Which fields means what. @pragma("vm:entry-point") final int state; @@ -156,44 +90,30 @@ class _FutureListener { // Used for error callbacks. final Function? errorCallback; - _FutureListener.then(this.source, this.result, - FutureOr Function(S) onValue, Function? errorCallback) + _FutureListener.then( + this.result, FutureOr Function(S) onValue, Function? errorCallback) : callback = onValue, errorCallback = errorCallback, state = (errorCallback == null) ? stateThen : stateThenOnerror; - _FutureListener.thenAwait(this.source, this.result, - FutureOr Function(S) onValue, Function errorCallback) + _FutureListener.thenAwait( + this.result, FutureOr Function(S) onValue, Function errorCallback) : callback = onValue, errorCallback = errorCallback, state = stateThenOnerror; - _FutureListener.catchError( - this.source, this.result, this.errorCallback, this.callback) - : assert(S == T), - state = (callback == null) ? stateCatchError : stateCatchErrorTest; + _FutureListener.catchError(this.result, this.errorCallback, this.callback) + : state = (callback == null) ? stateCatchError : stateCatchErrorTest; - _FutureListener.whenComplete(this.source, this.result, this.callback) - : assert(S == T), - errorCallback = null, + _FutureListener.whenComplete(this.result, this.callback) + : errorCallback = null, state = stateWhenComplete; _Zone get _zone => result._zone; - @pragma("vm:prefer-inline") - @pragma("dart2js:tryInline") bool get handlesValue => (state & maskValue != 0); - - @pragma("vm:prefer-inline") - @pragma("dart2js:tryInline") bool get handlesError => (state & maskError != 0); - - @pragma("vm:prefer-inline") - @pragma("dart2js:tryInline") bool get hasErrorTest => (state & maskType == stateCatchErrorTest); - - @pragma("vm:prefer-inline") - @pragma("dart2js:tryInline") bool get handlesComplete => (state & maskType == stateWhenComplete); FutureOr Function(S) get _onValue { @@ -201,6 +121,8 @@ class _FutureListener { return unsafeCast Function(S)>(callback); } + Function? get _onError => errorCallback; + bool Function(Object) get _errorTest { assert(hasErrorTest); return unsafeCast(callback); @@ -211,17 +133,18 @@ class _FutureListener { return unsafeCast(callback); } - void setSourceUnchecked(_Future source) { - assert(source is Future); - source = unsafeCast<_Future>(source); - } - /// Whether this listener has an error callback. /// /// This function must only be called if the listener [handlesError]. bool get hasErrorCallback { assert(handlesError); - return errorCallback != null; + return _onError != null; + } + + @pragma("vm:recognized", "other") + @pragma("vm:never-inline") + FutureOr handleValue(S sourceResult) { + return _zone.runUnary, S>(_onValue, sourceResult); } bool matchesErrorTest(AsyncError asyncError) { @@ -271,243 +194,8 @@ class _FutureListener { // with its result, rather than just completing the [future] directly // with the [value]. bool shouldChain(Future value) => value is Future || value is! T; - - /// Processes all listeners in this listener linked list. - /// - /// Processing a listener means taking the result of [source], - /// which must be [_Future._isComplete] or [_Future._isChained] - /// (to a completed future, and which will then be completed as the first - /// step of [_FutureListener.propagate]), passing the result through the - /// callbacks of the listener, and then completing [target] if possible. - /// - /// If [target] is completed, continue processing *its* listeners as well. - /// The new listeners are handled before other already completed listeners - /// (mainly for backwards compatability with the previous implementation). - /// - /// Runs in a loop until there are no further listeners with a completed - /// [source] future. - void propagateResults() { - _FutureListener listeners = this; - if (listeners._nextListener == null) { - // Single listener, just call it directly. - var newListeners = listeners.propagate(); - if (newListeners == null) return; - listeners = newListeners; - } - // Maintain linked list of pending listeners. - listeners = _reverseListeners(listeners, null); - while (true) { - // Propagate to first pending listener. - var newListeners = listeners.propagate(); - // Remember next pending listener. - var nextListener = listeners._nextListener; - if (newListeners != null) { - // Prepend the reversed newListeners. - // Special case a single listener, because it is the majority - // of cases in practice. - var nextNewListener = newListeners._nextListener; - listeners = (nextNewListener == null) - ? newListeners - : _reverseListeners(nextNewListener, newListeners); - newListeners._nextListener = nextListener; - } else if (nextListener != null) { - listeners = nextListener; - } else { - return; - } - } - } - - /// Reverses [listeners], and returns new first element. - /// - /// Set the new last element (original [listeners]) to point to [next]. - static _FutureListener _reverseListeners( - _FutureListener listeners, _FutureListener? next) { - var cursor = listeners; - while (true) { - var prev = cursor._nextListener; - cursor._nextListener = next; - if (prev != null) { - next = cursor; - cursor = prev; - } else { - return cursor; - } - } - } - - /// Calls listener callbacks with [source] result and completes [result]. - /// - /// If the callback throws synchronously, we pass that error into - /// [_zone.errorCallback] before completing [result] with resulting error. - /// (For error handlers, if they throw the original error object, we - /// treat it as a rethrow.) - /// - /// Returns the listeners of [result] if it is completed, so that - /// those listeners can also have their result propagated. - @pragma("vm:recognized", "other") - @pragma("vm:never-inline") - _FutureListener? propagate() { - _Future target = this.result; - _Future source = this.source; - if (source._isChained) { - // Find chained source, which must be complete, and copy result. - _Future originalSource = source; - do { - originalSource = unsafeCast<_Future>(originalSource._resultOrListeners); - } while (originalSource._isChained); - assert(originalSource._isComplete); - // Clone result of source. - source._setState( - originalSource._state, originalSource._resultOrListeners); - } - assert(source._isComplete); - assert(target._mayAddListener); - - if (source._hasError && !source._zone.inSameErrorZone(target._zone)) { - // Target future will never complete. We can't help it, for now we - // mark it as pending complete. - // TODO(lrn): Stop doing this. - return null; - } - - // Four different modes: - // * whenComplete - // * has value and handles value - // * has error and handles error (potentially tests error) - // * passes result through unchanged. - if (!handlesComplete) { - if (!source._hasError) { - // Propagate a value result. - if (handlesValue) { - S value = unsafeCast(source._resultOrListeners); - FutureOr result; - try { - result = _zone.runUnary, S>(_onValue, value); - } catch (e, s) { - var errorObject = _interceptSyncError(target._zone, e, s); - return target._setError(errorObject); - } - return target._completeWithFutureOr(result); - } - // If future doesn't handle values, its `S` and `T` types - // are the same. - assert(checkUnsoundType(source._resultOrListeners)); - return target._setValue(source._resultOrListeners); - } else if (handlesError) { - AsyncError errorObject = - unsafeCast(source._resultOrListeners); - try { - if (matchesErrorTest(errorObject)) { - var result = handleError(errorObject); - return target._completeWithFutureOr(result); - } - } catch (e, s) { - AsyncError newErrorObject = errorObject; - if (!identical(e, errorObject.error)) { - newErrorObject = _interceptSyncError(target._zone, e, s); - } - return target._setError(newErrorObject); - } - return target._setError(errorObject); - } - } else { - // Handles whenComplete. - FutureOr result; - try { - result = target._zone.run(_whenCompleteAction); - } catch (e, s) { - // Callback throws synchronously. - var errorObject = _interceptSyncError(target._zone, e, s); - return target._setError(errorObject); - } - if (result is Future) { - // Wait for result of whenComplete callback. - // If it throws, complete with that, - // otherwise complete with same result as source. - result.then((_) { - target._setNativeFutureResult(source)?.propagateResults(); - }, onError: (e, s) { - target._completeErrorObject(AsyncError(e, s)); - }); - return null; - } - } - return target._setNativeFutureResult(source); - } - - /// Filters list of listeners to only include results in same error zone. - /// - /// Works on a list of listeners (this one and the ones linked through - /// [_nextListener]), and filters that list to only include listeners - /// with [result] futures with the same error zone as [sourceZone]. - _FutureListener? filterByErrorZone(Zone sourceZone) { - // Optimize for simple case of one listener. - if (_nextListener == null) { - if (sourceZone.inSameErrorZone(result._zone)) return this; - return null; - } - // Find first listener to retain. - _FutureListener cursor = this; - while (!sourceZone.inSameErrorZone(cursor.result._zone)) { - var next = cursor._nextListener; - if (next == null) return null; - cursor = next; - } - // Cursor is first element to retain. - var first = cursor; - var last = cursor; - // Link other listeners to retain to first. - while (true) { - var next = cursor._nextListener; - if (next == null) { - last._nextListener = null; - return first; - } - if (sourceZone.inSameErrorZone(next.result._zone)) { - last._nextListener = next; - last = next; - } - cursor = next; - } - } } -/// An implementation of [Future] with callbacks. -/// -/// The future is in one of a number of states. -/// * Incomplete -/// * Pending complete (being asynchronously completed, either by a microtask -/// or waiting for another non-native future). -/// * Chained (completed with a native future) -/// * Completed -/// * With a value -/// * With an error -/// -/// The state determines the value of the [_resultOrListeners] variable. -/// That variable is in one of three modes: -/// * [_mayAddListener] (incomplet or pending complete) -/// - The variable can contain a chain of [_FutureListener]s. -/// - All these have [_FutureListener.source] pointing to this future. -/// * [_isChained] (chained) -/// - The variable contains another [_Future], which is always a -/// [_Future]. -/// * [_isComplete] (completed) -/// - the variable contains either the value (of type [T]) or -/// and [AsyncError] object, determined by [_hasError]. -/// -/// The [_zone] is the zone used by this future for everything zone -/// related. When completed with an error, the error belongs to the -/// "errorzone" of [_zone]. When a microtask needs to be scheduled, -/// it uses [_zone.scheduleMicrotask]. -/// -/// Most internal operations are *untyped* (as far as possible). -/// -/// A number of operations are marked as for external use in this library -/// (but outside this file and `future.dart`). -/// Those check types on entry. -/// The types are mainly used inside [_FutureListener], where a -/// `_FutureListener` handles all the conversion from [S] to [T]. class _Future implements Future { /// Initial state, waiting for a result. In this state, the /// [_resultOrListeners] field holds a single-linked list of @@ -521,13 +209,9 @@ class _Future implements Future { /// Only relevant until the future is completed. static const int _stateIgnoreError = 1; - /// Pending completion. - /// - /// Set when completed using [_asyncComplete] or - /// [_asyncCompleteError] or similar asynchronous completions. - /// Used by [_Completer] to see if you can call [Completer.complete]. - /// It is an error to try to complete it again. - /// [_resultOrListeners] still hold listeners. + /// Pending completion. Set when completed using [_asyncComplete] or + /// [_asyncCompleteError]. It is an error to try to complete it again. + /// [_resultOrListeners] holds listeners. static const int _statePendingComplete = 2; /// The future has been chained to another "source" [_Future]. @@ -537,9 +221,6 @@ class _Future implements Future { /// This future cannot be completed again. /// [_resultOrListeners] contains the source future. /// Listeners have been moved to the chained future. - /// - /// The [_stateIgnoreError] bit may be set, but [_statePendingComplete] - /// is not. static const int _stateChained = 4; /// The future has been completed with a value result. @@ -559,6 +240,13 @@ class _Future implements Future { /// Whether the future is complete, and as what. int _state = _stateIncomplete; + /// Zone that the future was completed from. + /// This is the zone that an error result belongs to. + /// + /// Until the future is completed, the field may hold the zone that + /// listener callbacks used to create this future should be run in. + final _Zone _zone; + /// Either the result, a list of listeners or another future. /// /// The result of the future is either a value or an error. @@ -573,233 +261,45 @@ class _Future implements Future { /// this future will complete with the same result. /// All listeners are forwarded to the other future. @pragma("vm:entry-point") - Object? _resultOrListeners; - - @pragma("vm:prefer-inline") - @pragma("dart2js:tryInline") - void _setState(int state, Object? stateValue) { - assert(state & _stateError == 0 || stateValue is AsyncError); - assert(state & _stateValue == 0 || checkUnsoundType(stateValue)); - assert(state & _stateChained == 0 || stateValue is _Future); - assert(state & (_stateError | _stateValue | _stateChained) != 0 || - stateValue is _FutureListener?); - _state = state; - _resultOrListeners = stateValue; - } - - /// Zone that the future was completed from. - /// This is the zone that an error result belongs to. - /// - /// Until the future is completed, the field may hold the zone that - /// listener callbacks used to create this future should be run in. - final _Zone _zone; + var _resultOrListeners; // This constructor is used by async/await. _Future() : _zone = Zone._current; - /// Creates fresh future in a specific zone. - _Future.zone(this._zone); - _Future.immediate(FutureOr result) : _zone = Zone._current { - _asyncCompleteUnchecked(result); + _asyncComplete(result); } /// Creates a future with the value and the specified zone. _Future.zoneValue(T value, this._zone) { - _setState(_stateValue, value); + _setValue(value); } - /// Creates a future with an asynchronously completed error. _Future.immediateError(var error, StackTrace stackTrace) : _zone = Zone._current { - // Completes asynchronously. - _asyncCompleteErrorObject(AsyncError(error, stackTrace)); + _asyncCompleteError(error, stackTrace); } /// Creates a future that is already completed with the value. _Future.value(T value) : this.zoneValue(value, Zone._current); - // PUBLIC API, called by classes in `dart:async` other than - // `Future` and `_Completer`. - // - // Should not be called from inside the implementation. - // Use implementation-specific functions instead. - - /// Completes this future synchronously with the future or value. - /// - /// Even if the value is an already completed `_Future` with an error - /// result, the completion happens immediately. - void _complete(FutureOr value) { - _completeUnchecked(value); - } - - /// Completes this future synchronously with the future or value. - /// - /// Even if the value is an already completed `_Future` with an error - /// result, the completion happens immediately. - /// - /// Argument must be assignable to `FutureOr`. - void _completeUnchecked(/* FutureOr */ Object? value) { - assert(checkUnsoundType>(value)); - assert(_mayComplete); - // Linked list of listeners whose source has been completed, if any. - _FutureListener? completedListeners; - if (value is Future) { - Object nativeFuture = value as Object; - if (nativeFuture is _Future) { - completedListeners = _chainCoreFuture(nativeFuture); - } else { - _chainForeignFuture(value); - return; - } - } else { - completedListeners = _setValue(value); - } - if (completedListeners != null) completedListeners.propagateResults(); - } - - /// Completes this future synchronously with an error and stack trace. - void _completeError(Object error, StackTrace stackTrace) { - _completeErrorObject(AsyncError(error, stackTrace)); - } - - /// Completes this future synchronously with an asynchronous error. - void _completeErrorObject(AsyncError error) { - assert(_mayComplete); - _setError(error)?.propagateResults(); - } - - /// Asynchronously completes this future with the value or future. - /// - /// Ensures completion won't happen until a later microtask. - /// Tries to not introduce extra delays if [value] is already an - /// incomplete future. - void _asyncComplete(FutureOr value) { - _asyncCompleteUnchecked(value); - } - - /// Internal helper function used by the implementation of `async` functions. - /// - /// Like [_asyncComplete], but avoids type checks that are guaranteed to - /// succeed by the way the function is called. - /// Should be used judiciously. - void _asyncCompleteUnchecked(/*FutureOr*/ dynamic value) { - assert(_mayComplete); - - // Ensure [value] is FutureOr, do so using an `as` check so it works - // also correctly in non-sound null-safety mode. - assert(checkUnsoundType>(value)); - - // Two corner cases if the value is a future: - // 1. the future is already completed and is an error. - // 2. the future is not yet completed but might become an error. - // The first case means that we must not immediately complete the Future, - // as our code would immediately start propagating the error without - // giving the time to install error-handlers. - // However the second case requires us to deal with the value immediately. - // Otherwise the value could complete with an error and report an - // unhandled error, even though we know we are already going to listen to - // it. - - if (value is Future) { - var nativeFuture = value as Object; - if (nativeFuture is _Future) { - _asyncCompleteWithNativeFuture(nativeFuture); - return; - } - // Always asynchronous, based on calling `.then`. - _chainForeignFuture(value); - return; - } - assert(checkUnsoundType(value)); - _asyncCompleteWithValueUnchecked(value); - } - - /// Completes asynchronously with an error and stack trace. - /// - /// Completes with an error in a later microtask step. - /// Sets [_isPendingComplete] to true while waiting. - void _asyncCompleteError(Object error, StackTrace stackTrace) { - _asyncCompleteErrorObject(AsyncError(error, stackTrace)); - } - - /// Completes asynchronously with an asynchronous error. - /// - /// Completes with an error in a later microtask step. - /// Sets [_isPendingComplete] to true while waiting. - void _asyncCompleteErrorObject(AsyncError error) { - assert(_mayComplete); // _mayAddListener && !_isPendingComplete - if (_resultOrListeners == null && _ignoreError) { - _setState(_stateError, error); - return; - } - _state |= _statePendingComplete; - _zone.scheduleMicrotask(() { - _setError(error)?.propagateResults(); - }); - } - - /// Registers a system-created result and error continuation. - /// - /// Used by the implementation of `await` to listen to a future. - /// The system created listeners are pre-registered in the zone, - /// to avoid registering the same listener more than once. - Future _thenAwait(FutureOr f(T value), Function onError) { - _Future result = _Future(); - _addListener(_FutureListener.thenAwait(this, result, f, onError)); - return result; - } - - // --------------------------------------------------------------------- - // INTERNAL API. DO NOT USE WITHOUT PERMISSION. SUBJECT TO CHANGE. - - /// Used by [_Completer] for [Completer.isComplete]. - /// - /// Should not be used internally. bool get _mayComplete => (_state & _completionStateMask) == _stateIncomplete; - - /// Used by [_Completer] for [Completer.isComplete]. - /// - /// It's a state where the completer may not complete, but the - /// [_Future] does not contain a result ([_isComplete]) yet. - /// - /// Set when completing with a non-native future (waiting for a - /// `.then` callback) or while waiting for a microtask to asyncronously - /// complete the future. - /// (Or if you ever complete a future with itself.) bool get _isPendingComplete => (_state & _statePendingComplete) != 0; - - /// The [_resultOrListeners] contains listeners. - /// - /// True until the future is either completed with a value, - /// or it's chained to another native future (at which point - /// the listeners are stored onthat other future instead). - /// Mutually exclusive with [_isComplete] and [_isChained]. bool get _mayAddListener => _state <= (_statePendingComplete | _stateIgnoreError); - - /// The [resultOrListeners] contains another native future. - /// - /// This future will complete with the exact same result as - /// the other future - /// Mutually exclusive with [_mayAddListener] and [_isComplete]. - bool get _isChained => ((_state & _stateChained) != 0); - - /// The [resultOrListeners] contains the final result. - /// - /// That's either a value of type [T] or an [AsyncError]. - /// Whether it's an error can be seen from [_hasError]. - /// Mutually exclusive with [_mayAddListener] and [_isChained]. + bool get _isChained => (_state & _stateChained) != 0; bool get _isComplete => (_state & (_stateValue | _stateError)) != 0; bool get _hasError => (_state & _stateError) != 0; bool get _ignoreError => (_state & _stateIgnoreError) != 0; - /// Is complete with error, and does not ignore error. - bool get _needsHandlingError => _state == _stateError; + void _setChained(_Future source) { + assert(_mayAddListener); + _state = _stateChained | (_state & _stateIgnoreError); + _resultOrListeners = source; + } Future then(FutureOr f(T value), {Function? onError}) { - _Future result = new _Future(); - Zone currentZone = result._zone; + Zone currentZone = Zone.current; if (identical(currentZone, _rootZone)) { if (onError != null && onError is! Function(Object, StackTrace) && @@ -819,14 +319,23 @@ class _Future implements Future { onError = _registerErrorHandler(onError, currentZone); } } - _addListener(_FutureListener.then(this, result, f, onError)); + _Future result = new _Future(); + _addListener(new _FutureListener.then(result, f, onError)); + return result; + } + + /// Registers a system created result and error continuation. + /// + /// Used by the implementation of `await` to listen to a future. + /// The system created listeners are not registered in the zone. + Future _thenAwait(FutureOr f(T value), Function onError) { + _Future result = new _Future(); + _addListener(new _FutureListener.thenAwait(result, f, onError)); return result; } void _ignore() { - // If already chained or completed, we don't check for listeners - // locally any more. - if (_mayAddListener) _state |= _stateIgnoreError; + _state |= _stateIgnoreError; } Future catchError(Function onError, {bool test(Object error)?}) { @@ -834,381 +343,507 @@ class _Future implements Future { if (!identical(result._zone, _rootZone)) { onError = _registerErrorHandler(onError, result._zone); if (test != null) test = result._zone.registerUnaryCallback(test); - } else { - _checkErrorHandler(onError); } - _addListener(_FutureListener.catchError(this, result, onError, test)); + _addListener(new _FutureListener.catchError(result, onError, test)); return result; } Future whenComplete(dynamic action()) { _Future result = new _Future(); - var currentZone = result._zone; - if (!identical(currentZone, _rootZone)) { - action = currentZone.registerCallback(action); + if (!identical(result._zone, _rootZone)) { + action = result._zone.registerCallback(action); } - _addListener(_FutureListener.whenComplete(this, result, action)); + _addListener(new _FutureListener.whenComplete(result, action)); return result; } Stream asStream() => new Stream.fromFuture(this); - /// Adds a new listener to this future. - /// - /// The listener was created by a call to [Future.then], - /// [Future.catchError] or [Future.whenComplete] on this future. - /// It contains the callbacks from that call, - /// this future as [_FutureListener.source], and the future - /// returned by the call as [_FutureListener.result]. - /// - /// If this future is chained to another future, then the - /// listener is instead added to the source future of the chain. - /// If this future is already complete, a microtask on [_zone] - /// is scheduled to have the result delivered. - /// Otherwise the listener is linked into the linked list of listeners - /// on this future. - void _addListener(_FutureListener listener) { - assert(identical(listener.source, this)); - assert(listener._nextListener == null); - _Future source = this; - if (source._isChained) { - do { - source = unsafeCast<_Future>(source._resultOrListeners); - assert(source is _Future); - } while (source._isChained); - if (source._isComplete) { - _setState(source._state, source._resultOrListeners); - source = this; - } - } - if (source._mayAddListener) { - source._prependListeners(listener); - return; - } - assert(source._isComplete); - assert(identical(listener.source, source)); - if (_hasError && !_zone.inSameErrorZone(listener.result._zone)) { - // If completed with an error, and the listener is in another - // error zone, we can discard the listener immediately. - return; - } - // The chained source has completed, so copy the result here - // instead of forwarding new listeners to the source future. - // Handle late listeners asynchronously. - _zone.scheduleMicrotask(listener.propagateResults); + void _setPendingComplete() { + assert(_mayComplete); // Aka _statIncomplete + _state ^= _stateIncomplete ^ _statePendingComplete; } - /// Wait for a non-native future to complete and use its result. + void _clearPendingComplete() { + assert(_isPendingComplete); + _state ^= _statePendingComplete ^ _stateIncomplete; + } + + AsyncError get _error { + assert(_hasError); + return _resultOrListeners; + } + + _Future get _chainSource { + assert(_isChained); + return _resultOrListeners; + } + + // This method is used by async/await. + void _setValue(T value) { + assert(!_isComplete); // But may have a completion pending. + _state = _stateValue; + _resultOrListeners = value; + } + + void _setErrorObject(AsyncError error) { + assert(!_isComplete); // But may have a completion pending. + _state = _stateError | (_state & _stateIgnoreError); + _resultOrListeners = error; + } + + void _setError(Object error, StackTrace stackTrace) { + _setErrorObject(new AsyncError(error, stackTrace)); + } + + /// Copy the completion result of [source] into this future. /// - /// Always called with a `Future`. - /// - /// Waits for [source] to complete, by calling `.then` on it. - /// (If that fails, which it never should, - /// asynchronously complete with that error instead, - /// that way this function is always asynchronous.) - /// - /// Completes this future with the same result. - /// - /// Only intended for non-native futures. - /// Use [_chainCoreFuture] for a [_Future]. - void _chainForeignFuture(Future source) { - assert(_mayAddListener); - assert(!_isPendingComplete); + /// Used when a chained future notices that its source is completed. + void _cloneResult(_Future source) { + assert(!_isComplete); + assert(source._isComplete); + _state = + (source._state & _completionStateMask) | (_state & _stateIgnoreError); + _resultOrListeners = source._resultOrListeners; + } + + void _addListener(_FutureListener listener) { + assert(listener._nextListener == null); + if (_mayAddListener) { + listener._nextListener = _resultOrListeners; + _resultOrListeners = listener; + } else { + if (_isChained) { + // Delegate listeners to chained source future. + // If the source is complete, instead copy its values and + // drop the chaining. + _Future source = _chainSource; + if (!source._isComplete) { + source._addListener(listener); + return; + } + _cloneResult(source); + } + assert(_isComplete); + // Handle late listeners asynchronously. + _zone.scheduleMicrotask(() { + _propagateToListeners(this, listener); + }); + } + } + + void _prependListeners(_FutureListener? listeners) { + if (listeners == null) return; + if (_mayAddListener) { + _FutureListener? existingListeners = _resultOrListeners; + _resultOrListeners = listeners; + if (existingListeners != null) { + _FutureListener cursor = listeners; + _FutureListener? next = cursor._nextListener; + while (next != null) { + cursor = next; + next = cursor._nextListener; + } + cursor._nextListener = existingListeners; + } + } else { + if (_isChained) { + // Delegate listeners to chained source future. + // If the source is complete, instead copy its values and + // drop the chaining. + _Future source = _chainSource; + if (!source._isComplete) { + source._prependListeners(listeners); + return; + } + _cloneResult(source); + } + assert(_isComplete); + listeners = _reverseListeners(listeners); + _zone.scheduleMicrotask(() { + _propagateToListeners(this, listeners); + }); + } + } + + _FutureListener? _removeListeners() { + // Reverse listeners before returning them, so the resulting list is in + // subscription order. + assert(!_isComplete); + _FutureListener? current = _resultOrListeners; + _resultOrListeners = null; + return _reverseListeners(current); + } + + _FutureListener? _reverseListeners(_FutureListener? listeners) { + _FutureListener? prev = null; + _FutureListener? current = listeners; + while (current != null) { + _FutureListener? next = current._nextListener; + current._nextListener = prev; + prev = current; + current = next; + } + return prev; + } + + // Take the value (when completed) of source and complete this future with that + // value (or error). This function could chain all Futures, but is slower + // for _Future than _chainCoreFuture, so you must use _chainCoreFuture + // in that case. + void _chainForeignFuture(Future source) { + assert(!_isComplete); assert(source is! _Future); // Mark the target as chained (and as such half-completed). - _state |= _statePendingComplete; + _setPendingComplete(); try { - source.then((T value) { + source.then((value) { assert(_isPendingComplete); + _clearPendingComplete(); // Clear this first, it's set again. try { - _setValue(value)?.propagateResults(); + _completeWithValue(value as T); } catch (error, stackTrace) { - _setError(AsyncError(error, stackTrace))?.propagateResults(); + _completeError(error, stackTrace); } }, onError: (Object error, StackTrace stackTrace) { assert(_isPendingComplete); - _setError(AsyncError(error, stackTrace))?.propagateResults(); + _completeError(error, stackTrace); }); - } catch (error, stackTrace) { + } catch (e, s) { // This only happens if the `then` call threw synchronously when given // valid arguments. // That requires a non-conforming implementation of the Future interface, // which should, hopefully, never happen. - - // Foreign futures are assume asynchronous, so delay the completion. scheduleMicrotask(() { - _setError(AsyncError(error, stackTrace))?.propagateResults(); + _completeError(e, s); }); } } - /// Take the value (when completed) of source and complete this future - /// with that result (value or error). - /// - /// If the [source] is already completed, - /// so is [this] future, and its listeners are returned. - /// - /// If chaining a future to itself, we now complete the future with - /// an error, and return its listeners. - _FutureListener? _chainCoreFuture(_Future source) { - assert(_mayAddListener); // Not completed, not already chained. + // Take the value (when completed) of source and complete target with that + // value (or error). This function expects that source is a _Future. + static void _chainCoreFuture(_Future source, _Future target) { + assert(target._mayAddListener); // Not completed, not already chained. while (source._isChained) { - source = unsafeCast<_Future>(source._resultOrListeners); - } - assert(source is _Future); - if (identical(this, source)) { - // Completing a future with itself. - // The future will never complete with a result. - // We make it complete with an error instead. - return _setError( - AsyncError(_FutureCyclicDependencyError(this), StackTrace.empty)); + source = source._chainSource; } if (source._isComplete) { - return _setNativeFutureResult(source); - } - assert(source._mayAddListener); - var listeners = unsafeCast<_FutureListener?>(_resultOrListeners); - _setState(_stateChained, source); - if (listeners != null) { + _FutureListener? listeners = target._removeListeners(); + target._cloneResult(source); + _propagateToListeners(target, listeners); + } else { + _FutureListener? listeners = target._resultOrListeners; + target._setChained(source); source._prependListeners(listeners); } - return null; } - /// Moves a chain of [listeners] to this [_Future]. - /// - /// The listeners are prepended to the current listeners (meaning they - /// will be called later than existing listeners). - /// - /// User to chain a native future to the native future source of its result, - /// by [chainCoreFuture] and similar code in other places. - void _prependListeners(_FutureListener listeners) { - assert(_mayAddListener); - var existingListeners = unsafeCast<_FutureListener?>(_resultOrListeners); - _FutureListener cursor = listeners; - while (true) { - cursor.setSourceUnchecked(this); - var next = cursor._nextListener; - if (next == null) { - cursor._nextListener = existingListeners; - break; + void _complete(FutureOr value) { + assert(!_isComplete); + if (value is Future) { + if (value is _Future) { + _chainCoreFuture(value, this); + } else { + _chainForeignFuture(value); } - cursor = next; + } else { + _FutureListener? listeners = _removeListeners(); + // TODO(40014): Remove cast when type promotion works. + // This would normally be `as T` but we use `as dynamic` to make the + // unneeded check be implicit to match dart2js unsound optimizations in + // the user code. + _setValue(value as dynamic); // Value promoted to T. + _propagateToListeners(this, listeners); } - _resultOrListeners = listeners; } - /// Completes this future synchronously with a value. void _completeWithValue(T value) { assert(!_isComplete); - _setValue(value)?.propagateResults(); + + _FutureListener? listeners = _removeListeners(); + _setValue(value); + _propagateToListeners(this, listeners); } - /// Completes this future synchronously with a value. - /// - /// The value must be a [T]. - void _completeWithValueUnchecked(Object? value) { - assert(checkUnsoundType(value)); + void _completeError(Object error, StackTrace stackTrace) { assert(!_isComplete); - _setValue(value)?.propagateResults(); + + _FutureListener? listeners = _removeListeners(); + _setError(error, stackTrace); + _propagateToListeners(this, listeners); } - /// Asynchronously completes this future with a native future. - /// - /// If the native future is already complete, a microtask is scheduled - /// to set the result. - /// If not, this future is chained to the other future - /// (moving all listeners to the source, and forwarding all new - /// listeners to the source as well, until the source completes). - /// - /// Helper function for [_asyncComplete]/[_asyncCompleteUnchecked]. - /// Not used internally. - void _asyncCompleteWithNativeFuture(_Future source) { - assert(_mayComplete); // Not completed, chained or pending. - while (source._isChained) { - source = unsafeCast<_Future>(source._resultOrListeners); - assert(source is _Future); - } - if (source._isComplete) { - if (_resultOrListeners == null && (!source._hasError || _ignoreError)) { - // No listeners, no need to report errors, - // just copy the result eagerly. - _setNativeFutureResult(source); - return; - } - } else if (!identical(this, source)) { - // Source is not complete, and not identical to this, - // so chain directly to it. - var listeners = unsafeCast<_FutureListener?>(_resultOrListeners); - _setState(_stateChained, source); - if (listeners != null) { - source._prependListeners(listeners); - } + void _asyncComplete(FutureOr value) { + assert(!_isComplete); + // Two corner cases if the value is a future: + // 1. the future is already completed and an error. + // 2. the future is not yet completed but might become an error. + // The first case means that we must not immediately complete the Future, + // as our code would immediately start propagating the error without + // giving the time to install error-handlers. + // However the second case requires us to deal with the value immediately. + // Otherwise the value could complete with an error and report an + // unhandled error, even though we know we are already going to listen to + // it. + + if (value is Future) { + _chainFuture(value); return; } - // No fast path, complete later. - _state |= _statePendingComplete; + // TODO(40014): Remove cast when type promotion works. + // This would normally be `as T` but we use `as dynamic` to make the + // unneeded check be implicit to match dart2js unsound optimizations in the + // user code. + _asyncCompleteWithValue(value as dynamic); // Value promoted to T. + } + + /// Internal helper function used by the implementation of `async` functions. + /// + /// Like [_asyncComplete], but avoids type checks that are guaranteed to + /// succeed by the way the function is called. + /// Should be used judiciously. + void _asyncCompleteUnchecked(/*FutureOr*/ dynamic value) { + // Ensure [value] is FutureOr, do so using an `as` check so it works + // also correctly in non-sound null-safety mode. + assert(identical(value as FutureOr, value)); + final typedValue = unsafeCast>(value); + + // Doing just "is Future" is not sufficient. + // If `T` is Object` and `value` is `Future.value(null)`, + // then value is a `Future`, but not a `Future`, and going through the + // `_chainFuture` branch would end up assigning `null` to `Object`. + if (typedValue is Future) { + _chainFuture(typedValue); + return; + } + _asyncCompleteWithValue(unsafeCast(typedValue)); + } + + /// Internal helper function used to implement `async` functions. + /// + /// Like [_asyncCompleteUnchecked], but avoids a `is Future` check due to + /// having a static guarantee on the callsite that the [value] cannot be a + /// [Future]. + /// Should be used judiciously. + void _asyncCompleteUncheckedNoFuture(/*T*/ dynamic value) { + // Ensure [value] is T, do so using an `as` check so it works also correctly + // in non-sound null-safety mode. + assert(identical(value as T, value)); + _asyncCompleteWithValue(unsafeCast(value)); + } + + void _asyncCompleteWithValue(T value) { + _setPendingComplete(); _zone.scheduleMicrotask(() { - _chainCoreFuture(source)?.propagateResults(); + _completeWithValue(value); }); } - /// Asynchronously complete with a value. - /// - /// The value is untyped, but must be assignable to [T]. - /// - /// Also used to implement `async` functions. - /// - /// Like [_asyncCompleteUnchecked], but avoids an `is Future` check - /// due to having a static guarantee on the callsite that - /// the [value] cannot be a [Future]. - /// Should be used judiciously. - void _asyncCompleteWithValueUnchecked(/*T*/ Object? value) { - assert(checkUnsoundType(value)); - assert(_mayAddListener); - var listeners = _setValue(value); - if (listeners != null) { - _zone.scheduleMicrotask(listeners.propagateResults); - } - } - - /// Completes with an [AsyncError] object without downcasting. - /// - /// The [AsyncError] object is usually taken from another [_Future] - /// which is completed with an error. It's known to be an [AsyncError], - /// but is stored in a field of type `dynamic`. - void _completeErrorUnchecked(AsyncError error) { - assert(_mayComplete, "SetError($_state)"); - _setError(error)?.propagateResults(); - } - - /// Completes this future with a value. - /// - /// Returns the listeners of this future, in reverse order. - // This method is used by async/await. (Where?) - _FutureListener? _setValue(Object? value) { - assert(checkUnsoundType(value)); - assert(!_isComplete); - var listeners = unsafeCast<_FutureListener?>(_resultOrListeners); - - _setState(_stateValue, value); - - return listeners; - } - - /// Completes this future with an error. - /// - /// Returns the listeners of this future (in reverse order). - /// If there are no listeners, and this future doesn't ignore errors, - /// the error is reported as uncaught to [_zone]. - _FutureListener? _setError(AsyncError error) { - assert(_mayAddListener); // If chained, don't call this method. - - int oldState = _state; - var listeners = unsafeCast<_FutureListener?>(_resultOrListeners); - - _setState(_stateError, error); - - return listeners?.filterByErrorZone(_zone) ?? _onUnhandledError(oldState); - } - - /// Reports unhandled error if the future doens't ignore unhandled errors. - /// - /// The [oldState] must be the [_Future._state] from before completing - /// the future with an error. - /// It contains the flag which says to ignore unhandled errors. - /// - /// Called after completing with error, - /// and finding no listeners in the same error zone. - /// - /// Returns `Null` to allow it to be used after a `??` guard, - /// since it's only called when the list of (error-zone filtered) - /// listeners is `null`. - Null _onUnhandledError(int oldState) { - assert(_hasError); - if (oldState & _stateIgnoreError == 0) { - var error = unsafeCast(_resultOrListeners); - _zone.handleUncaughtError(error.error, error.stackTrace); - } - return null; - } - - /// Completes this future with the result of the completed [source]. - /// - /// Use [_setValue] or [_setError] instead if the type of value - /// is known, since they can skip some checks when they know whether - /// it's an error result or not, - /// and use this method if completing with another completed future, - /// without already knowing or caring whether it's an error or value. - /// - /// Returns the listeners of this future (in reverse order). - /// If there are no listeners, [source] was completed with an error, - /// and this future doesn't ignore errors, - /// the error is reported as uncaught to [_zone]. - _FutureListener? _setNativeFutureResult(_Future source) { - assert(_mayAddListener); // Not complete or chained. - assert(source is _Future || source._hasError); - assert(source._isComplete); - - int oldState = _state; - var listeners = unsafeCast<_FutureListener?>(_resultOrListeners); - _setState(source._state, source._resultOrListeners); - - if (!_hasError) return listeners; - return listeners?.filterByErrorZone(_zone) ?? _onUnhandledError(oldState); - } - - /// Completes (entirely or partially) this future with [source]. - /// - /// If [source] is a non-native future, this future is marked partially - /// completed ([_isPendingComplete]) - /// while we wait for a result from [source]. - /// - /// If [source] is a native [_Future], then we either copy its - /// result (if it's completed) or chains to it (if not). - /// Otherwise [source] must be a [T] object, and we complete - /// this future with it as a value. - /// - /// Returns the listeners of this future if we complete it. - /// (If we chaing to a native future, the listeners are moved to - /// that future. If we have a partial/pending completion, - /// the listeners stay on the future until we have a result. - /// In either case, no listeners are returned.) - /// - /// Only used by [_FutureListener.propagate] for the results returned by the - /// [Future.then]/[Future.catchError] callbacks. - /// As async callback results, this future can always complete - /// immeditely with the result. - _FutureListener? _completeWithFutureOr(/* FutureOr */ Object? source) { - assert(checkUnsoundType>(source)); - if (source is Future) { - var nativeSource = - source as Object; // Demote so we can promote to _Future. - if (nativeSource is _Future) { - _Future nativeFuture = nativeSource; // Prevent demotion by assignment. - while (nativeFuture._isChained) { - nativeFuture = unsafeCast<_Future>(nativeFuture._resultOrListeners); - } - assert(nativeFuture is _Future); - if (nativeFuture._isComplete) { - return _setNativeFutureResult(nativeFuture); - } - assert(nativeFuture._mayAddListener); - return _chainCoreFuture(nativeFuture); + void _chainFuture(Future value) { + if (value is _Future) { + if (value._hasError) { + // Delay completion to allow the user to register callbacks. + _setPendingComplete(); + _zone.scheduleMicrotask(() { + _chainCoreFuture(value, this); + }); + } else { + _chainCoreFuture(value, this); } - _chainForeignFuture(source); - return null; + return; + } + // Just listen on the foreign future. This guarantees an async delay. + _chainForeignFuture(value); + } + + void _asyncCompleteError(Object error, StackTrace stackTrace) { + assert(!_isComplete); + + _setPendingComplete(); + _zone.scheduleMicrotask(() { + _completeError(error, stackTrace); + }); + } + + /// Propagates the value/error of [source] to its [listeners], executing the + /// listeners' callbacks. + static void _propagateToListeners( + _Future source, _FutureListener? listeners) { + while (true) { + assert(source._isComplete); + bool hasError = source._hasError; + if (listeners == null) { + if (hasError && !source._ignoreError) { + AsyncError asyncError = source._error; + source._zone + .handleUncaughtError(asyncError.error, asyncError.stackTrace); + } + return; + } + // Usually futures only have one listener. If they have several, we + // call handle them separately in recursive calls, continuing + // here only when there is only one listener left. + _FutureListener listener = listeners; + _FutureListener? nextListener = listener._nextListener; + while (nextListener != null) { + listener._nextListener = null; + _propagateToListeners(source, listener); + listener = nextListener; + nextListener = listener._nextListener; + } + + final dynamic sourceResult = source._resultOrListeners; + // Do the actual propagation. + // Set initial state of listenerHasError and listenerValueOrError. These + // variables are updated with the outcome of potential callbacks. + // Non-error results, including futures, are stored in + // listenerValueOrError and listenerHasError is set to false. Errors + // are stored in listenerValueOrError as an [AsyncError] and + // listenerHasError is set to true. + bool listenerHasError = hasError; + var listenerValueOrError = sourceResult; + + // Only if we either have an error or callbacks, go into this, somewhat + // expensive, branch. Here we'll enter/leave the zone. Many futures + // don't have callbacks, so this is a significant optimization. + if (hasError || listener.handlesValue || listener.handlesComplete) { + _Zone zone = listener._zone; + if (hasError && !source._zone.inSameErrorZone(zone)) { + // Don't cross zone boundaries with errors. + AsyncError asyncError = source._error; + source._zone + .handleUncaughtError(asyncError.error, asyncError.stackTrace); + return; + } + + _Zone? oldZone; + if (!identical(Zone._current, zone)) { + // Change zone if it's not current. + oldZone = Zone._enter(zone); + } + + // These callbacks are abstracted to isolate the try/catch blocks + // from the rest of the code to work around a V8 glass jaw. + void handleWhenCompleteCallback() { + // The whenComplete-handler is not combined with normal value/error + // handling. This means at most one handleX method is called per + // listener. + assert(!listener.handlesValue); + assert(!listener.handlesError); + var completeResult; + try { + completeResult = listener.handleWhenComplete(); + } catch (e, s) { + if (hasError && identical(source._error.error, e)) { + listenerValueOrError = source._error; + } else { + listenerValueOrError = new AsyncError(e, s); + } + listenerHasError = true; + return; + } + if (completeResult is _Future && completeResult._isComplete) { + if (completeResult._hasError) { + listenerValueOrError = completeResult._error; + listenerHasError = true; + } + // Otherwise use the existing result of source. + return; + } + if (completeResult is Future) { + // We have to wait for the completeResult future to complete + // before knowing if it's an error or we should use the result + // of source. + var originalSource = source; + listenerValueOrError = completeResult.then((_) => originalSource); + listenerHasError = false; + } + } + + void handleValueCallback() { + try { + listenerValueOrError = listener.handleValue(sourceResult); + } catch (e, s) { + listenerValueOrError = new AsyncError(e, s); + listenerHasError = true; + } + } + + void handleError() { + try { + AsyncError asyncError = source._error; + if (listener.matchesErrorTest(asyncError) && + listener.hasErrorCallback) { + listenerValueOrError = listener.handleError(asyncError); + listenerHasError = false; + } + } catch (e, s) { + if (identical(source._error.error, e)) { + listenerValueOrError = source._error; + } else { + listenerValueOrError = new AsyncError(e, s); + } + listenerHasError = true; + } + } + + if (listener.handlesComplete) { + handleWhenCompleteCallback(); + } else if (!hasError) { + if (listener.handlesValue) { + handleValueCallback(); + } + } else { + if (listener.handlesError) { + handleError(); + } + } + + // If we changed zone, oldZone will not be null. + if (oldZone != null) Zone._leave(oldZone); + + // If the listener's value is a future we *might* need to chain it. Note that + // this can only happen if there is a callback. + if (listenerValueOrError is Future && + listener.shouldChain(listenerValueOrError)) { + Future chainSource = listenerValueOrError; + // Shortcut if the chain-source is already completed. Just continue + // the loop. + _Future result = listener.result; + if (chainSource is _Future) { + if (chainSource._isComplete) { + listeners = result._removeListeners(); + result._cloneResult(chainSource); + source = chainSource; + continue; + } else { + _chainCoreFuture(chainSource, result); + } + } else { + result._chainForeignFuture(chainSource); + } + return; + } + } + + _Future result = listener.result; + listeners = result._removeListeners(); + if (!listenerHasError) { + result._setValue(listenerValueOrError); + } else { + AsyncError asyncError = listenerValueOrError; + result._setErrorObject(asyncError); + } + // Prepare for next round. + source = result; } - assert(checkUnsoundType(source)); - return _setValue(source); } @pragma("vm:recognized", "other") @pragma("vm:entry-point") Future timeout(Duration timeLimit, {FutureOr onTimeout()?}) { - // Use .immediate - if (_isComplete) return new _Future().._asyncComplete(this); + if (_isComplete) return new _Future.immediate(this); // This is a VM recognised method, and the _future variable is deliberately // allocated in a specific slot in the closure context for stack unwinding. _Future _future = new _Future(); @@ -1216,7 +851,7 @@ class _Future implements Future { if (onTimeout == null) { timer = new Timer(timeLimit, () { _future._completeError( - TimeoutException("Future not completed", timeLimit), + new TimeoutException("Future not completed", timeLimit), StackTrace.empty); }); } else { @@ -1224,7 +859,7 @@ class _Future implements Future { FutureOr Function() onTimeoutHandler = zone.registerCallback(onTimeout); - timer = Timer(timeLimit, () { + timer = new Timer(timeLimit, () { try { _future._complete(zone.run(onTimeoutHandler)); } catch (e, s) { @@ -1247,18 +882,17 @@ class _Future implements Future { } } -/// Registers [errorHandler] in [_zone] if it has the correct type. +/// Registers errorHandler in zone if it has the correct type. /// /// Checks that the function accepts either an [Object] and a [StackTrace] /// or just one [Object]. Does not check the return type. -/// /// The actually returned value must be `FutureOr` where `R` is the /// value type of the future that the call will complete (either returned /// by [Future.then] or [Future.catchError]). We check the returned value /// dynamically because the functions are passed as arguments in positions /// without inference, so a function expression won't infer the return type. /// -/// Throws if the signature or parameter types are not valid. +/// Throws if the type is not valid. Function _registerErrorHandler(Function errorHandler, Zone zone) { if (errorHandler is dynamic Function(Object, StackTrace)) { return zone @@ -1273,43 +907,3 @@ Function _registerErrorHandler(Function errorHandler, Zone zone) { "Error handler must accept one Object or one Object and a StackTrace" " as arguments, and return a value of the returned future's type"); } - -/// Checks the type of an error handler. -/// -/// Checks that the function accepts either an [Object] and a [StackTrace] -/// or just one [Object]. Does not check the return type. -/// -/// The actually returned value must be `FutureOr` where `R` is the -/// value type of the future that the call will complete (either returned -/// by [Future.then] or [Future.catchError]). We check the returned value -/// dynamically because the functions are passed as arguments in positions -/// without inference, so a function expression won't infer the return type. -/// -/// Throws if the signature or parameter types are not valid. -void _checkErrorHandler(Function errorHandler) { - if (errorHandler is! dynamic Function(Object, StackTrace) && - errorHandler is! dynamic Function(Object)) { - throw ArgumentError.value( - errorHandler, - "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"); - } -} - -/// Runs the [zone.errorCallback] on the [error] and [stackTrace]. -/// -/// Returns an [AsyncError] containing either the returned error, -/// or if [Zone.errorCallback] returned `null`, -/// then an object containing the original error and stacktrace, -/// or a default stack trace if [stackTrace] is `null`. -AsyncError _interceptSyncError( - Zone zone, Object error, StackTrace? stackTrace) => - zone.errorCallback(error, stackTrace) ?? AsyncError(error, stackTrace); - -/// Thrown when a [_Future] is completed with itself, leading to a deadlock. -class _FutureCyclicDependencyError extends UnsupportedError { - final Future future; - _FutureCyclicDependencyError(this.future) - : super("Future completed with itself"); -} diff --git a/sdk/lib/async/stream_impl.dart b/sdk/lib/async/stream_impl.dart index 932f8423f82..42b60829284 100644 --- a/sdk/lib/async/stream_impl.dart +++ b/sdk/lib/async/stream_impl.dart @@ -1079,15 +1079,14 @@ class _StreamIterator implements StreamIterator { void _onDone() { var subscription = _subscription; - _Future moveNextFuture = _stateData as dynamic; - assert(moveNextFuture is _Future); + _Future moveNextFuture = _stateData as dynamic; _subscription = null; _stateData = null; if (subscription != null) { moveNextFuture._completeWithValue(false); } else { // Event delivered during `listen` call. - moveNextFuture._asyncCompleteWithValueUnchecked(false); + moveNextFuture._asyncCompleteWithValue(false); } } } diff --git a/sdk/lib/async/stream_pipe.dart b/sdk/lib/async/stream_pipe.dart index 139eecf88f6..cd707be13e4 100644 --- a/sdk/lib/async/stream_pipe.dart +++ b/sdk/lib/async/stream_pipe.dart @@ -5,7 +5,7 @@ part of dart.async; /// Runs user code and takes actions depending on success or failure. -void _runUserCode(T userCode(), onSuccess(T value), +_runUserCode(T userCode(), onSuccess(T value), onError(Object error, StackTrace stackTrace)) { try { onSuccess(userCode()); diff --git a/sdk/lib/internal/internal.dart b/sdk/lib/internal/internal.dart index a29f4dadbfb..ff3d890b188 100644 --- a/sdk/lib/internal/internal.dart +++ b/sdk/lib/internal/internal.dart @@ -762,17 +762,6 @@ T checkNotNullable(T value, String name) { return value; } -/// Used in asserts to check that [object] has *unsound* type [T]. -/// -/// Throws if [object] does not have type [T] in sound null safe mode, -/// or it does not have type `T?` in unsound null safe mode. -/// -/// Returns `true` if it doesn't throw, so it can be used in an `assert`. -bool checkUnsoundType(Object? object) { - object as T; - return true; -} - /// A [TypeError] thrown by [checkNotNullable]. class NotNullableError extends Error implements TypeError { final String _name; diff --git a/tests/dartdevc/debugger/debugger_test_golden.txt b/tests/dartdevc/debugger/debugger_test_golden.txt index f5a755c28dd..a879c6e7db2 100644 --- a/tests/dartdevc/debugger/debugger_test_golden.txt +++ b/tests/dartdevc/debugger/debugger_test_golden.txt @@ -4981,6 +4981,57 @@ Value: ] ] ], + [ + "li", + { + "style": "padding-left: 13px;" + }, + [ + "span", + {}, + [ + "span", + { + "style": "" + }, + "" + ] + ] + ], + [ + "li", + { + "style": "padding-left: 13px;" + }, + [ + "span", + {}, + [ + "span", + { + "style": "" + }, + "" + ] + ] + ], + [ + "li", + { + "style": "padding-left: 13px;" + }, + [ + "span", + {}, + [ + "span", + { + "style": "" + }, + "" + ] + ] + ], [ "li", { diff --git a/tests/dartdevc_2/debugger/debugger_test_golden.txt b/tests/dartdevc_2/debugger/debugger_test_golden.txt index f5a755c28dd..a879c6e7db2 100644 --- a/tests/dartdevc_2/debugger/debugger_test_golden.txt +++ b/tests/dartdevc_2/debugger/debugger_test_golden.txt @@ -4981,6 +4981,57 @@ Value: ] ] ], + [ + "li", + { + "style": "padding-left: 13px;" + }, + [ + "span", + {}, + [ + "span", + { + "style": "" + }, + "" + ] + ] + ], + [ + "li", + { + "style": "padding-left: 13px;" + }, + [ + "span", + {}, + [ + "span", + { + "style": "" + }, + "" + ] + ] + ], + [ + "li", + { + "style": "padding-left: 13px;" + }, + [ + "span", + {}, + [ + "span", + { + "style": "" + }, + "" + ] + ] + ], [ "li", { diff --git a/tests/lib/async/future_self_complete_test.dart b/tests/lib/async/future_self_complete_test.dart deleted file mode 100644 index 8cf45923468..00000000000 --- a/tests/lib/async/future_self_complete_test.dart +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; - -import 'package:async_helper/async_helper.dart'; -import "package:expect/expect.dart"; - -void main() async { - asyncStart(); - var completer = Completer(); - // Should complete with error, but not synchronously. - completer.complete(completer.future); - await completer.future.then((value) { - Expect.fail("Completed with value $value."); - }, onError: (e, _) { - Expect.type(e); - }).timeout(const Duration(milliseconds: 1), onTimeout: () { - Expect.fail("Did not complete"); - }); - - // Also if going through indirections. - var completer1 = Completer(); - var completer2 = Completer(); - - // Should complete with error, but not synchronously. - completer1.complete(completer2.future); - completer2.complete(completer1.future); - - completer1.future.ignore(); - completer2.future.ignore(); - await completer1.future.then((value) { - Expect.fail("Completed with value $value."); - }, onError: (e, _) { - Expect.type(e); - }).timeout(const Duration(milliseconds: 1), onTimeout: () { - Expect.fail("Did not complete"); - }); - - await completer2.future.then((value) { - Expect.fail("Completed with value $value."); - }, onError: (e, _) { - Expect.type(e); - }).timeout(const Duration(milliseconds: 1), onTimeout: () { - Expect.fail("Did not complete"); - }); - - // Also if coming from a callback. - completer1 = Completer(); - completer2 = Completer(); - completer2.complete(completer1.future.then((_) => completer2.future)); - completer1.complete(1); - await completer2.future.then((value) { - Expect.fail("Completed with value $value."); - }, onError: (e, _) { - Expect.type(e); - }).timeout(const Duration(milliseconds: 1), onTimeout: () { - Expect.fail("Did not complete"); - }); - - asyncEnd(); -} diff --git a/tests/lib_2/async/future_self_complete_test.dart b/tests/lib_2/async/future_self_complete_test.dart deleted file mode 100644 index 8cf45923468..00000000000 --- a/tests/lib_2/async/future_self_complete_test.dart +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; - -import 'package:async_helper/async_helper.dart'; -import "package:expect/expect.dart"; - -void main() async { - asyncStart(); - var completer = Completer(); - // Should complete with error, but not synchronously. - completer.complete(completer.future); - await completer.future.then((value) { - Expect.fail("Completed with value $value."); - }, onError: (e, _) { - Expect.type(e); - }).timeout(const Duration(milliseconds: 1), onTimeout: () { - Expect.fail("Did not complete"); - }); - - // Also if going through indirections. - var completer1 = Completer(); - var completer2 = Completer(); - - // Should complete with error, but not synchronously. - completer1.complete(completer2.future); - completer2.complete(completer1.future); - - completer1.future.ignore(); - completer2.future.ignore(); - await completer1.future.then((value) { - Expect.fail("Completed with value $value."); - }, onError: (e, _) { - Expect.type(e); - }).timeout(const Duration(milliseconds: 1), onTimeout: () { - Expect.fail("Did not complete"); - }); - - await completer2.future.then((value) { - Expect.fail("Completed with value $value."); - }, onError: (e, _) { - Expect.type(e); - }).timeout(const Duration(milliseconds: 1), onTimeout: () { - Expect.fail("Did not complete"); - }); - - // Also if coming from a callback. - completer1 = Completer(); - completer2 = Completer(); - completer2.complete(completer1.future.then((_) => completer2.future)); - completer1.complete(1); - await completer2.future.then((value) { - Expect.fail("Completed with value $value."); - }, onError: (e, _) { - Expect.type(e); - }).timeout(const Duration(milliseconds: 1), onTimeout: () { - Expect.fail("Did not complete"); - }); - - asyncEnd(); -}