Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

use crate::{
ecmascript::{
Agent, ECMAScriptFunction, ExecutionContext, Promise, PromiseCapability,
PromiseReactionHandler, PromiseReactionType, SourceTextModule, Value, inner_promise_then,
Agent, ECMAScriptFunction, ExecutionContext, PromiseCapability, PromiseReactionHandler,
PromiseReactionType, SourceTextModule, Value, inner_promise_then,
},
engine::{
Bindable, Executable, ExecutionResult, GcScope, Scopable, SuspendedVm, bindable_handle,
Expand Down Expand Up @@ -105,21 +105,20 @@ impl AwaitReaction<'_> {
.clone()
.reject(agent, err.value().unbind(), gc.nogc());
}
ExecutionResult::Await { vm, awaited_value } => {
ExecutionResult::Await { vm, promise } => {
let promise = promise.unbind();
let gc = gc.into_nogc();
let promise = promise.bind(gc);
// [27.7.5.3 Await ( value )](https://tc39.es/ecma262/#await)
// 8. Remove asyncContext from the execution context stack and
// restore the execution context that is at the top of the
// execution context stack as the running execution context.
let execution_context = agent.pop_execution_context().unwrap();
let data = reaction.get(agent).bind(gc.nogc()).get_mut(agent);
let data = reaction.get(agent).bind(gc).get_mut(agent);
data.vm = Some(vm);
data.execution_context = Some(execution_context);

// 2. Let promise be ? PromiseResolve(%Promise%, value).
let promise =
Promise::resolve(agent, awaited_value.unbind(), gc.reborrow()).unbind();
let gc = gc.into_nogc();
let promise = promise.bind(gc);

// SAFETY: not shared.
let reaction = unsafe { reaction.take(agent) }.bind(gc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,14 @@ pub(super) fn resume_handle_result(
// NOTE: Await is performed in the bytecode.
async_generator_yield(agent, yielded_value, scoped_generator, vm, gc);
}
ExecutionResult::Await { vm, awaited_value } => {
ExecutionResult::Await { vm, promise } => {
async_generator_perform_await(
agent,
scoped_generator,
vm,
awaited_value,
promise,
AsyncGeneratorAwaitKind::Await,
gc,
gc.into_nogc(),
);
}
}
Expand All @@ -249,25 +249,22 @@ fn async_generator_perform_await(
agent: &mut Agent,
scoped_generator: Scoped<AsyncGenerator>,
vm: SuspendedVm,
awaited_value: Value,
promise: Promise,
kind: AsyncGeneratorAwaitKind,
mut gc: GcScope,
gc: NoGcScope,
) {
// [27.7.5.3 Await ( value )](https://tc39.es/ecma262/#await)
let execution_context = agent.pop_execution_context().unwrap();
let generator = scoped_generator.get(agent).bind(gc.nogc());
let generator = scoped_generator.get(agent).bind(gc);
generator.transition_to_awaiting(agent, vm, kind, execution_context);
// 8. Remove asyncContext from the execution context stack and
// restore the execution context that is at the top of the
// execution context stack as the running execution context.
let handler = PromiseReactionHandler::AsyncGenerator(generator.unbind());
// 2. Let promise be ? PromiseResolve(%Promise%, value).
let promise = Promise::resolve(agent, awaited_value, gc.reborrow())
.unbind()
.bind(gc.nogc());

// Performed by caller.
// 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
inner_promise_then(agent, promise, handler, handler, None, gc.nogc());
inner_promise_then(agent, promise, handler, handler, None, gc);
}

/// ### [27.6.3.7 AsyncGeneratorUnwrapYieldResumption ( resumptionValue )](https://tc39.es/ecma262/#sec-asyncgeneratorunwrapyieldresumption)
Expand Down Expand Up @@ -405,24 +402,44 @@ pub(crate) fn async_generator_await_return(
unreachable!()
};
// 7. Let promiseCompletion be Completion(PromiseResolve(%Promise%, completion.[[Value]])).
// 8. If promiseCompletion is an abrupt completion, then
// a. Perform AsyncGeneratorCompleteStep(generator, promiseCompletion, true).
// b. Perform AsyncGeneratorDrainQueue(generator).
// c. Return unused.
// 9. Assert: promiseCompletion is a normal completion.
// 10. Let promise be promiseCompletion.[[Value]].
let promise = Promise::resolve(agent, value.unbind(), gc.reborrow())
let promise_completion = Promise::resolve(agent, value.unbind(), gc.reborrow())
.unbind()
.bind(gc.nogc());
// 11. ... onFulfilled ...
// 12. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
// 13. ... onRejected ...
// 14. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
// 15. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
let handler =
PromiseReactionHandler::AsyncGenerator(scoped_generator.get(agent).bind(gc.nogc()));
inner_promise_then(agent, promise, handler, handler, None, gc.nogc());
// 16. Return unused.
let promise_completion = promise_completion.bind(gc.nogc());
match promise_completion {
// 8. If promiseCompletion is an abrupt completion, then
Err(promise_completion) => {
let generator = scoped_generator.get(agent).bind(gc.nogc());
// a. Perform AsyncGeneratorCompleteStep(generator, promiseCompletion, true).
async_generator_complete_step(
agent,
generator.unbind(),
AsyncGeneratorRequestCompletion::Err(promise_completion.unbind()),
true,
None,
gc.nogc(),
);
// b. Perform AsyncGeneratorDrainQueue(generator).
async_generator_drain_queue(agent, scoped_generator, gc);
// c. Return unused.
}
// 9. Assert: promiseCompletion is a normal completion.
Ok(promise) => {
let promise = promise.unbind();
let gc = gc.into_nogc();
let promise = promise.bind(gc);
let generator = unsafe { scoped_generator.take(agent) }.bind(gc);
// 10. Let promise be promiseCompletion.[[Value]].
// 11. ... onFulfilled ...
// 12. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
// 13. ... onRejected ...
// 14. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let handler = PromiseReactionHandler::AsyncGenerator(generator);
// 15. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
inner_promise_then(agent, promise, handler, handler, None, gc);
// 16. Return unused.
}
};
}

pub(crate) fn async_generator_await_return_on_fulfilled(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use crate::{
Agent, ArgumentsList, BUILTIN_STRING_MEMORY, ExceptionType, IteratorRecord,
MaybeInvalidIteratorRecord, Object, Promise, PromiseCapability, PromiseReactionHandler,
Value, call_function, create_iter_result_object, get_object_method,
if_abrupt_reject_promise_m, inner_promise_then, iterator_close_with_value,
iterator_complete, iterator_next, iterator_value, unwrap_try,
if_abrupt_reject_promise_m, inner_promise_then, iterator_close_with_error,
iterator_close_with_value, iterator_complete, iterator_next, iterator_value, unwrap_try,
},
engine::{Bindable, GcScope, Scopable, VmIteratorRecord},
};
Expand Down Expand Up @@ -375,7 +375,7 @@ pub(crate) fn async_from_sync_iterator_continuation<'a>(
.unbind()
.bind(gc.nogc());
// 3. IfAbruptRejectPromise(done, promiseCapability).
let promise_capability = PromiseCapability {
let mut promise_capability = PromiseCapability {
promise: scoped_promise.get(agent).bind(gc.nogc()),
must_be_unresolved,
};
Expand All @@ -387,18 +387,37 @@ pub(crate) fn async_from_sync_iterator_continuation<'a>(
.unbind()
.bind(gc.nogc());
// 5. IfAbruptRejectPromise(value, promiseCapability).
let promise_capability = PromiseCapability {
promise_capability = PromiseCapability {
promise: scoped_promise.get(agent).bind(gc.nogc()),
must_be_unresolved,
};
let value = if_abrupt_reject_promise_m!(agent, value, promise_capability, gc);
// 6. Let valueWrapper be Completion(PromiseResolve(%Promise%, value)).
let value_wrapper = Promise::resolve(agent, value.unbind(), gc.reborrow())
let mut value_wrapper = Promise::resolve(agent, value.unbind(), gc.reborrow())
.unbind()
.bind(gc.nogc());
// 7. If valueWrapper is an abrupt completion, done is false, and closeOnRejection is true, then
// a. Set valueWrapper to Completion(IteratorClose(syncIteratorRecord, valueWrapper)).
// 7. If valueWrapper is an abrupt completion, done is false, and
// closeOnRejection is true, then
if let Err(err) = value_wrapper
&& !done
&& close_on_rejection
{
// a. Set valueWrapper to Completion(IteratorClose(syncIteratorRecord, valueWrapper)).
value_wrapper = Err(iterator_close_with_error(
agent,
sync_iterator.get(agent),
err.unbind(),
gc.reborrow(),
)
.unbind()
.bind(gc.nogc()));
}
promise_capability = PromiseCapability {
promise: scoped_promise.get(agent).bind(gc.nogc()),
must_be_unresolved,
};
// 8. IfAbruptRejectPromise(valueWrapper, promiseCapability).
let value_wrapper = if_abrupt_reject_promise_m!(agent, value_wrapper, promise_capability, gc);
// 9. Let unwrap be a new Abstract Closure with parameters (v) that
// captures done and performs the following steps when called:
// a. Return CreateIteratorResultObject(v, done).
Expand All @@ -414,10 +433,13 @@ pub(crate) fn async_from_sync_iterator_continuation<'a>(
PromiseReactionHandler::Empty
} else {
// 13. Else,
// a. Let closeIterator be a new Abstract Closure with parameters (error) that captures syncIteratorRecord and performs the following steps when called:
// a. Let closeIterator be a new Abstract Closure with parameters
// (error) that captures syncIteratorRecord and performs the
// following steps when called:
// i. Return ? IteratorClose(syncIteratorRecord, ThrowCompletion(error)).
// b. Let onRejected be CreateBuiltinFunction(closeIterator, 1, "", « »).
// c. NOTE: onRejected is used to close the Iterator when the "value" property of an IteratorResult object it yields is a rejected promise.
// c. NOTE: onRejected is used to close the Iterator when the "value"
// property of an IteratorResult object it yields is a rejected promise.
PromiseReactionHandler::AsyncFromSyncIteratorClose(
unsafe { sync_iterator.take(agent) }.bind(gc.nogc()),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl<'a> FunctionInternalProperties<'a> for BuiltinPromiseFinallyFunction<'a> {
let _c = unsafe { c.take(agent) }.bind(gc.nogc());
// ii. Let p be ? PromiseResolve(C, result).
let p = Promise::resolve(agent, result.unbind(), gc.reborrow())
.unbind()
.unbind()?
.bind(gc.nogc());
// SAFETY: not shared.
let value = unsafe { value.take(agent) }.bind(gc.nogc());
Expand Down Expand Up @@ -175,10 +175,12 @@ impl<'a> FunctionInternalProperties<'a> for BuiltinPromiseFinallyFunction<'a> {
let _c = unsafe { c.take(agent) }.bind(gc.nogc());
// ii. Let p be ? PromiseResolve(C, result).
let p = Promise::resolve(agent, result.unbind(), gc.reborrow())
.unbind()
.unbind()?
.bind(gc.nogc());
let reason = unsafe { reason.take(agent) }.bind(gc.nogc());
// iii. Let throwReason be a new Abstract Closure with no parameters that captures reason and performs the following steps when called:
// iii. Let throwReason be a new Abstract Closure with no
// parameters that captures reason and performs the following
// steps when called:
// iv. Let thrower be CreateBuiltinFunction(throwReason, 0, "", « »).
let thrower = agent.heap.create(PromiseFinallyFunctionHeapData {
backing_object: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ impl PromiseConstructor {
}

// 3. Return ? PromiseResolve(C, x).
Ok(Promise::resolve(agent, arguments.get(0), gc).into())
Promise::resolve(agent, arguments.get(0), gc).map(Value::from)
}

/// ### [27.2.4.8 Promise.try ( callback, ...args )](https://tc39.es/ecma262/#sec-promise.try)
Expand Down Expand Up @@ -573,7 +573,7 @@ impl PromiseConstructor {
// 6. Else,
Ok(result) => {
// a. Perform ? Call(promiseCapability.[[Resolve]], undefined, « status.[[Value]] »).
Promise::resolve(agent, result.unbind(), gc)
Promise::resolve(agent, result.unbind(), gc)?
}
};
// 7. Return promiseCapability.[[Promise]].
Expand Down
55 changes: 40 additions & 15 deletions nova_vm/src/ecmascript/builtins/promise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -99,23 +99,48 @@ impl<'a> Promise<'a> {
}

///### [27.2.4.7.1 PromiseResolve ( C, x )](https://tc39.es/ecma262/#sec-promise-resolve)
pub(crate) fn resolve(agent: &mut Agent, x: Value, mut gc: GcScope<'a, '_>) -> Self {
pub(crate) fn resolve(
agent: &mut Agent,
x: Value,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Self> {
let x = x.bind(gc.nogc());
// 1. If IsPromise(x) is true, then
if let Value::Promise(promise) = x {
let x = if let Value::Promise(x) = x {
let scoped_x = x.scope(agent, gc.nogc());
// a. Let xConstructor be ? Get(x, "constructor").
let x_constructor = match get(
agent,
x.unbind(),
BUILTIN_STRING_MEMORY.constructor.into(),
gc.reborrow(),
)
.unbind()
.bind(gc.nogc())
{
Ok(v) => v,
Err(err) => return Err(err.unbind()),
};
// SAFETY: not shared.
let x = unsafe { scoped_x.take(agent) }.bind(gc.nogc());
// b. If SameValue(xConstructor, C) is true, return x.
// NOTE: Ignoring subclasses.
promise.unbind()
if x_constructor == agent.current_realm_record().intrinsics().promise().into() {
return Ok(x.unbind().bind(gc.into_nogc()));
}
x.into()
} else {
// 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 »).
promise_capability.unbind().resolve(agent, x, gc.reborrow());
// 4. Return promiseCapability.[[Promise]].
// SAFETY: Not shared.
unsafe { promise.take(agent) }
}
x
};
// 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 »).
promise_capability
.unbind()
.resolve(agent, x.unbind(), gc.reborrow());
// 4. Return promiseCapability.[[Promise]].
// SAFETY: Not shared.
Ok(unsafe { promise.take(agent).bind(gc.into_nogc()) })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1572,7 +1572,10 @@ fn async_module_start(
// ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »).
promise_capability.reject(agent, err.value(), gc.nogc());
}
ExecutionResult::Await { vm, awaited_value } => {
ExecutionResult::Await {
vm,
promise: resolve_promise,
} => {
let async_context = agent.pop_execution_context().unwrap();
// SAFETY: not shared.
let (bytecode, promise, module) = unsafe {
Expand All @@ -1596,9 +1599,6 @@ fn async_module_start(
// `handler` corresponds to the `fulfilledClosure` and `rejectedClosure` functions,
// which resume execution of the function.
// 2. Let promise be ? PromiseResolve(%Promise%, value).
let resolve_promise = Promise::resolve(agent, awaited_value.unbind(), gc.reborrow())
.unbind()
.bind(gc.nogc());

module.set_executable(agent, bytecode);

Expand Down
Loading