diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs index 67ee8e102..a90a8f490 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs @@ -409,11 +409,28 @@ pub(crate) fn async_generator_await_return( // a. Perform AsyncGeneratorCompleteStep(generator, promiseCompletion, true). // b. Perform AsyncGeneratorDrainQueue(generator). // c. Return unused. + let return_value = value.unbind().bind(gc.nogc()); + let promise_completion = Promise::resolve_maybe_abrupt(agent, return_value.unbind(), gc.reborrow()) + .map(|promise| promise.unbind()) + .map_err(|err| err.unbind()); + let promise = match promise_completion { + Err(err) => { + let generator = scoped_generator.get(agent).bind(gc.nogc()); + async_generator_complete_step( + agent, + generator.unbind(), + AsyncGeneratorRequestCompletion::Err(err.unbind()), + true, + None, + gc.nogc(), + ); + async_generator_drain_queue(agent, scoped_generator, gc); + return; + } + Ok(promise) => promise.unbind().bind(gc.nogc()), + }; // 9. Assert: promiseCompletion is a normal completion. // 10. Let promise be promiseCompletion.[[Value]]. - let promise = Promise::resolve(agent, value.unbind(), gc.reborrow()) - .unbind() - .bind(gc.nogc()); // 11. ... onFulfilled ... // 12. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). // 13. ... onRejected ... diff --git a/nova_vm/src/ecmascript/builtins/promise.rs b/nova_vm/src/ecmascript/builtins/promise.rs index 1e88e463d..ab98a4459 100644 --- a/nova_vm/src/ecmascript/builtins/promise.rs +++ b/nova_vm/src/ecmascript/builtins/promise.rs @@ -8,8 +8,8 @@ pub(crate) use data::*; use crate::{ ecmascript::{ - Agent, InternalMethods, InternalSlots, JsError, JsResult, OrdinaryObject, - PromiseCapability, ProtoIntrinsics, Value, object_handle, + Agent, BUILTIN_STRING_MEMORY, InternalMethods, InternalSlots, JsError, JsResult, + OrdinaryObject, PromiseCapability, ProtoIntrinsics, Value, get, object_handle, }, engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::{ @@ -25,6 +25,45 @@ object_handle!(Promise); arena_vec_access!(Promise, 'a, PromiseHeapData, promises); impl<'a> Promise<'a> { + /// ### [27.2.4.7.1 PromiseResolve ( C, x )](https://tc39.es/ecma262/#sec-promise-resolve) + /// + /// Internal variant for call sites that must handle abrupt completion from + /// `PromiseResolve(%Promise%, x)`. + pub fn resolve_maybe_abrupt( + agent: &mut Agent, + x: Value, + mut gc: GcScope<'a, '_>, + ) -> JsResult<'a, Self> { + // 1. If IsPromise(x) is true, then + if let Value::Promise(promise) = x { + let scoped_promise = promise.scope(agent, gc.nogc()); + // a. Let xConstructor be ? Get(x, "constructor"). + let x_constructor = match get( + agent, + promise, + BUILTIN_STRING_MEMORY.constructor.into(), + gc.reborrow(), + ) { + Ok(value) => value.unbind().bind(gc.nogc()), + Err(err) => return Err(err.unbind().bind(gc.into_nogc())), + }; + // b. If SameValue(xConstructor, C) is true, return x. + // NOTE: Ignoring subclasses. + if x_constructor == agent.current_realm_record().intrinsics().promise().into() { + return Ok(scoped_promise.get(agent).bind(gc.into_nogc())); + } + } + // 2. Let promiseCapability be ? NewPromiseCapability(C). + let promise_capability = PromiseCapability::new(agent, gc.nogc()); + let promise = promise_capability.promise().scope(agent, gc.nogc()); + // 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »). + // NOTE: Promise capability resolve never throws in Nova's internal model. + promise_capability.unbind().resolve(agent, x, gc.reborrow()); + // 4. Return promiseCapability.[[Promise]]. + // SAFETY: Not shared. + Ok(unsafe { promise.take(agent) }.bind(gc.into_nogc())) + } + /// Create a new resolved Promise. pub(crate) fn new_resolved(agent: &mut Agent, value: Value<'a>) -> Self { agent.heap.create(PromiseHeapData { diff --git a/tests/expectations.json b/tests/expectations.json index acaec4789..cfcf6dc78 100644 --- a/tests/expectations.json +++ b/tests/expectations.json @@ -226,9 +226,6 @@ "built-ins/AsyncGeneratorFunction/proto-from-ctor-realm-prototype.js": "FAIL", "built-ins/AsyncGeneratorFunction/proto-from-ctor-realm.js": "FAIL", "built-ins/AsyncGeneratorFunction/prototype/constructor.js": "FAIL", - "built-ins/AsyncGeneratorPrototype/return/return-state-completed-broken-promise.js": "FAIL", - "built-ins/AsyncGeneratorPrototype/return/return-suspendedStart-broken-promise.js": "FAIL", - "built-ins/AsyncGeneratorPrototype/return/return-suspendedYield-broken-promise-try-catch.js": "FAIL", "built-ins/AsyncIteratorPrototype/Symbol.asyncDispose/invokes-return.js": "FAIL", "built-ins/AsyncIteratorPrototype/Symbol.asyncDispose/is-function.js": "FAIL", "built-ins/AsyncIteratorPrototype/Symbol.asyncDispose/length.js": "FAIL", @@ -7505,4 +7502,4 @@ "staging/sm/syntax/yield-as-identifier.js": "FAIL", "staging/source-phase-imports/import-source-source-text-module.js": "FAIL", "staging/top-level-await/tla-hang-entry.js": "FAIL" -} \ No newline at end of file +}