Revert "Refactor _Future."

This reverts commit 69f32d6ad7.

Reason for revert: We seem to have a number of tests failing with timeouts in CBUILD after this change, please see logs at
69f32d6ad7

Original change's description:
> Refactor `_Future`.
>
> This is a major rewrite of the `_Future` class,
> which is the default implementation of the `Future` interface.
>
> The main goal was to reduce the number of expensive type checks
> in the internal passing around of data.
> Expensive type checks are things like
> * `is _Future<T>` (more expensive than just `is _Future`, the latter
>   can be a single class-ID check.
> * Covariant generic parameter checks (using `T` covariantly in a
>   parameter forces a run-time type check).
>
> Also removed some plain unnecessary casts and turned some
> implicit casts from `dynamic` into `unsafeCast`s.
>
> This seems to be an success, at least on very primitive benchmarks, according to Golem:
> FutureCatchErrorTest    41.22% (1.9 noise)
> FutureValueTest         46.51% (2.8 noise)
> EmptyFutureTest         59.15% (3.1 noise)
> FutureWhenCompleteTest  51.10% (3.2 noise)
>
> A secondary goal was to clean up a very old and messy class,
> and make it clearer for other `dart:async` how to interact
> with the future.
>
> The change has a memory cost: The `_FutureListener<S,T>` class,
> which represents a `then`, `catchError` or `whenComplete`
> call on a `_Future`, now contains a reference to its source future,
> the one which provides the inputs to the callbacks,
> as well as the result future returned by the call.
> That's one extra memory slot per listener.
>
> In return, the `_FutureListener` now does not need to
> get its source future as an argument, which needs a covariant
> generic type check, and the methods of `_Future` can be written
> in a way which ignores the type parameters of both `_Future`
> and `_FutureListener`, which reduces complex type checks
> significantly.
>
> In general, typed code is in `_FutureListener`, which knows both
> the source and target types of the listener callbacks, and which
> contains the futures already at that type, so no extra type checking
> is needed.
> The `_Future` class is mostly untyped, except for its "public"
> API, called by other classes, which checks inputs,
> and code interacting with non-native futures.
> Invariants ensure that only correctly typed values
> are stored in the untyped shared `_resultOrListeners` field
> on `_Future`, as determined by its `_state` integer.
> (This was already partially true, and has simply been made
> more consistent.)
>
> Further, we now throw an error in a situation that was previously
> unhandled: When a `_Future` is completed with *itself*.
> That would ensure that the future would never complete
> (it waits for itself to complete before it can complete),
> and may potentially have caused weird loops in the representation.
> In practice, it probably never happens. Now it makes the error
> fail with an error.
> Currently a private `_FutureCyclicDependencyError` which presents
> as an `UnsupportedError`.
> That avoids code like
> ```dart
> import "dart:async";
> void main() {
>   var c = Completer();
>   c.complete(c.future); // bad.
>   print("well!");
>   var d = Completer();
>   d.complete(c.future);
>   print("shucks!");
> }
> ```
> from hanging the runtime by busily searching for the end of a cycle.
>
> See https://github.com/dart-lang/sdk/issues/48225
> Fixes #48225
>
> TEST= refactoring covered by existing tests, few new tests.
>
> Change-Id: Id9fc5af5fe011deb0af3e1e8a4ea3a91799f9da4
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/244241
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Lasse Nielsen <lrn@google.com>

TBR=lrn@google.com,kustermann@google.com,sra@google.com,sigmund@google.com,nshahan@google.com

Change-Id: I455be5a04b4c346df26d4ded0fa7388baccb0f8c
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/247762
Reviewed-by: Siva Annamalai <asiva@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
Siva Annamalai 2022-06-09 16:51:55 +00:00 committed by Commit Bot
parent 83b50a70c8
commit 5cb3b37c74
30 changed files with 727 additions and 1205 deletions

View file

@ -125,8 +125,7 @@ const List<LineException> afterExceptions = const [
const LineException('_asyncAwait.<anonymous function>', 'async_patch.dart'),
const LineException('_asyncStart.<anonymous function>', '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<LineException> 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.<anonymous function>', 'js_helper.dart'),
const LineException('invokeClosure', 'js_helper.dart'),
const LineException('convertDartClosureToJS', 'js_helper.dart'),

View file

@ -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 <Type*>[])
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 <Type*>[])
Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:865:13 -> MapConstant(const <Symbol*, dynamic>{})
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 <Type*>[])
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 <Type*>[])
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> ListConstant(const <dynamic>[])
Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> MapConstant(const <Symbol*, dynamic>{})
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 <Type*>[])
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 <Type*>[])
Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:872:13 -> MapConstant(const <Symbol*, dynamic>{})
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 <Type*>[])
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 <Type*>[])
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> ListConstant(const <dynamic>[])
Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> MapConstant(const <Symbol*, dynamic>{})
Extra constant evaluation: evaluated: 61, effectively constant: 15

View file

@ -474,28 +474,3 @@ Future<void> 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');
}

View file

@ -60,7 +60,6 @@ var tests = <IsolateTest>[
LINE_A,
);
isolate = await service.getIsolate(isolateId);
await waitUntilBreakpointIsReady(isolate, bpt);
expect(isolate.breakpoints!.length, 1);
}
@ -74,7 +73,6 @@ var tests = <IsolateTest>[
LINE_B,
);
isolate = await service.getIsolate(isolateId);
await waitUntilBreakpointIsReady(isolate, bpt);
expect(isolate.breakpoints!.length, 2);
}
@ -88,7 +86,6 @@ var tests = <IsolateTest>[
LINE_C,
);
isolate = await service.getIsolate(isolateId);
await waitUntilBreakpointIsReady(isolate, bpt);
expect(isolate.breakpoints!.length, 3);
}
// Wait for breakpoint events.

View file

@ -94,7 +94,6 @@ var tests = <IsolateTest>[
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 = <IsolateTest>[
// Refresh isolate state.
isolate = await service.getIsolate(isolateId);
await waitUntilBreakpointIsReady(isolate, bpt);
expect(isolate.breakpoints!.length, 1);
await completer.future; // Wait for breakpoint events.

View file

@ -95,7 +95,7 @@ final tests = <IsolateTest>[
(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 = <IsolateTest>[
[equals('Regular'), anything],
[equals('Regular'), anything],
[equals('Regular'), anything],
[equals('Regular'), anything],
[equals('Regular'), anything],
[equals('Regular'), endsWith(' _RawReceivePortImpl._handleMessage')],
]);

View file

@ -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;

View file

@ -54,7 +54,6 @@ var tests = <IsolateTest>[
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 = <IsolateTest>[
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 = <IsolateTest>[
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));
}

View file

@ -80,7 +80,6 @@ var tests = <IsolateTest>[
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 = <IsolateTest>[
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.

View file

@ -53,6 +53,7 @@ var tests = <IsolateTest>[
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);

View file

@ -716,23 +716,3 @@ Future runDDSTests(List<String> mainArgs, List<DDSTest> 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<int, Breakpoint> 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');
}

View file

@ -54,7 +54,6 @@ var tests = <IsolateTest>[
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 = <IsolateTest>[
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 = <IsolateTest>[
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));
}

View file

@ -79,7 +79,6 @@ var tests = <IsolateTest>[
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 = <IsolateTest>[
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.

View file

@ -53,6 +53,7 @@ var tests = <IsolateTest>[
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);

View file

@ -715,23 +715,3 @@ Future runDDSTests(List<String> mainArgs, List<DDSTest> 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<int, Breakpoint> 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');
}

View file

@ -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)\

View file

@ -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<ObjectPtr*>(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 "<optimized
// out>" due to the `this` no longer being needed.
return Closure::null();

View file

@ -27,7 +27,7 @@ _async<T>(Function() initGenerator) {
late Object? Function(Object?) onValue;
late Object Function(Object, StackTrace?) onError;
_Future<Object?> onAwait(Object? value) {
onAwait(Object? value) {
_Future<Object?> f;
if (value is _Future) {
f = value;
@ -84,15 +84,31 @@ _async<T>(Function() initGenerator) {
var iteratorValue = JS('', '#.next(null)', iter);
var value = JS('', '#.value', iteratorValue);
if (JS<bool>('!', '#.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<dynamic>` instead of
// `Future<T>`, 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<dynamic> awaitedFuture = onAwait(value); // _Future<Object?>.
awaitedFuture.then(asyncFuture._completeWithValueUnchecked,
onError: asyncFuture._completeError);
_Future._chainCoreFuture(onAwait(value), asyncFuture);
}
} catch (e, s) {
if (isRunningAsEvent) {

View file

@ -198,20 +198,27 @@ class _AsyncAwaitCompleter<T> implements Completer<T> {
void complete([FutureOr<T>? 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<T>) {
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);
}
}

View file

@ -236,7 +236,7 @@ class _AsyncStarStreamController<T> {
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);
}
}

View file

@ -108,7 +108,6 @@ import "dart:_internal"
CastStream,
CastStreamTransformer,
checkNotNullable,
checkUnsoundType,
EmptyIterator,
IterableElementError,
nullFuture,

View file

@ -303,13 +303,18 @@ abstract class Future<T> {
if (result is Future<T>) {
return result;
} else {
return _Future<T>().._setValue(result);
// TODO(40014): Remove cast when type promotion works.
return new _Future<T>.value(result as dynamic);
}
} catch (error, stackTrace) {
var currentZone = Zone._current;
return _Future<T>.zone(currentZone)
.._asyncCompleteErrorObject(
_interceptSyncError(currentZone, error, stackTrace));
var future = new _Future<T>();
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<T> {
@pragma("vm:entry-point")
@pragma("vm:prefer-inline")
factory Future.value([FutureOr<T>? value]) {
if (value != null) {
return _Future<T>().._asyncCompleteUnchecked(value);
}
return _Future<T>().._setValue(value as T);
return new _Future<T>.immediate(value == null ? value as T : value);
}
/// Creates a future that completes with an error.
@ -367,10 +369,15 @@ abstract class Future<T> {
factory Future.error(Object error, [StackTrace? stackTrace]) {
// TODO(40614): Remove once non-nullability is sound.
checkNotNullable(error, "error");
var currentZone = Zone._current;
return _Future<T>.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<T>.immediateError(error, stackTrace);
}
/// Creates a future that runs its computation after a delay.
@ -1223,13 +1230,28 @@ abstract class Completer<T> {
// 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);
}

File diff suppressed because it is too large Load diff

View file

@ -1079,15 +1079,14 @@ class _StreamIterator<T> implements StreamIterator<T> {
void _onDone() {
var subscription = _subscription;
_Future moveNextFuture = _stateData as dynamic;
assert(moveNextFuture is _Future<bool>);
_Future<bool> 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);
}
}
}

View file

@ -5,7 +5,7 @@
part of dart.async;
/// Runs user code and takes actions depending on success or failure.
void _runUserCode<T>(T userCode(), onSuccess(T value),
_runUserCode<T>(T userCode(), onSuccess(T value),
onError(Object error, StackTrace stackTrace)) {
try {
onSuccess(userCode());

View file

@ -762,17 +762,6 @@ T checkNotNullable<T extends Object>(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<T>(Object? object) {
object as T;
return true;
}
/// A [TypeError] thrown by [checkNotNullable].
class NotNullableError<T> extends Error implements TypeError {
final String _name;

View file

@ -4981,6 +4981,57 @@ Value:
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{

View file

@ -4981,6 +4981,57 @@ Value:
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{

View file

@ -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<int>();
// 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<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
// Also if going through indirections.
var completer1 = Completer<int>();
var completer2 = Completer<int>();
// 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<UnsupportedError>(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<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
// Also if coming from a callback.
completer1 = Completer<int>();
completer2 = Completer<int>();
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<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
asyncEnd();
}

View file

@ -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<int>();
// 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<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
// Also if going through indirections.
var completer1 = Completer<int>();
var completer2 = Completer<int>();
// 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<UnsupportedError>(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<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
// Also if coming from a callback.
completer1 = Completer<int>();
completer2 = Completer<int>();
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<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
asyncEnd();
}