-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Abstract closures, Promises, and such work
- Loading branch information
Showing
15 changed files
with
637 additions
and
53 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
pub(crate) mod promise_abstract_operations; | ||
pub(crate) mod promise_constructor; | ||
pub(crate) mod promise_prototype; |
280 changes: 280 additions & 0 deletions
280
...cript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,280 @@ | ||
use std::ops::{Index, IndexMut}; | ||
|
||
use crate::{ecmascript::{abstract_operations::testing_and_comparison::is_constructor, builtins::{promise::Promise, ArgumentsList}, execution::{agent::ExceptionType, Agent, JsResult}, types::{AbstractClosureHeapData, Function, IntoValue, Object, String, Value}}, heap::{indexes::{BaseIndex, BoundFunctionIndex}, Heap}}; | ||
|
||
use self::{promise_capability_records::PromiseCapability, promise_reaction_records::PromiseReaction}; | ||
|
||
pub(crate) mod promise_capability_records; | ||
pub(crate) mod promise_reaction_records; | ||
|
||
pub(crate) struct PromiseResolvingFunctions { | ||
pub(crate) resolve: Function, | ||
pub(crate) reject: BuiltinPromiseRejectFunction, | ||
} | ||
|
||
/// ### [27.2.1.3 CreateResolvingFunctions ( promise )]() | ||
/// | ||
/// The abstract operation CreateResolvingFunctions takes argument promise (a | ||
/// Promise) and returns a Record with fields \[\[Resolve\]\] (a function | ||
/// object) and \[\[Reject\]\] (a function object). | ||
pub(crate) fn create_resolving_functions(agent: &mut Agent, promise: Promise) -> PromiseResolvingFunctions { | ||
// 1. Let alreadyResolved be the Record { [[Value]]: false }. | ||
let already_resolved = false; | ||
// 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions. | ||
// 3. Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions. | ||
// 4. Let resolve be CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »). | ||
// TODO | ||
let resolve = Function::BoundFunction(BoundFunctionIndex::from_u32_index(0)); | ||
// 5. Set resolve.[[Promise]] to promise. | ||
// 6. Set resolve.[[AlreadyResolved]] to alreadyResolved. | ||
// 7. Let stepsReject be the algorithm steps defined in Promise Reject Functions. | ||
// 8. Let lengthReject be the number of non-optional parameters of the function definition in Promise Reject Functions. | ||
// 9. Let reject be CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »). | ||
let reject = PromiseRejectFunctionHeapData { | ||
promise, | ||
already_resolved, | ||
object_index: None, | ||
}; | ||
// 10. Set reject.[[Promise]] to promise. | ||
// 11. Set reject.[[AlreadyResolved]] to alreadyResolved. | ||
agent.heap.promise_reject_functions.push(Some(reject)); | ||
let reject = BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex::last(&agent.heap.promise_reject_functions)); | ||
// 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }. | ||
PromiseResolvingFunctions { resolve, reject } | ||
} | ||
|
||
/// ### [27.2.1.3.1 Promise Reject Functions]() | ||
/// | ||
/// A promise reject function is an anonymous built-in function that has | ||
/// \[\[Promise\]\] and \[\[AlreadyResolved\]\] internal slots. | ||
/// | ||
/// The "length" property of a promise reject function is 1𝔽. | ||
#[derive(Debug, Clone, Copy)] | ||
pub(crate) struct PromiseRejectFunctionHeapData { | ||
/// \[\[Promise\]\] | ||
pub(crate) promise: Promise, | ||
/// \[\[AlreadyResolved\]\] | ||
pub(crate) already_resolved: bool, | ||
pub(crate) object_index: Option<Object>, | ||
} | ||
|
||
pub(crate) type BuiltinPromiseRejectFunctionIndex = BaseIndex<PromiseRejectFunctionHeapData>; | ||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
pub(crate) struct BuiltinPromiseRejectFunction(pub(crate) BuiltinPromiseRejectFunctionIndex); | ||
|
||
impl Index<BuiltinPromiseRejectFunction> for Agent { | ||
type Output = PromiseRejectFunctionHeapData; | ||
|
||
fn index(&self, index: BuiltinPromiseRejectFunction) -> &Self::Output { | ||
&self.heap[index] | ||
} | ||
} | ||
|
||
impl IndexMut<BuiltinPromiseRejectFunction> for Agent { | ||
fn index_mut(&mut self, index: BuiltinPromiseRejectFunction) -> &mut Self::Output { | ||
&mut self.heap[index] | ||
} | ||
} | ||
|
||
impl Index<BuiltinPromiseRejectFunction> for Heap { | ||
type Output = PromiseRejectFunctionHeapData; | ||
|
||
fn index(&self, index: BuiltinPromiseRejectFunction) -> &Self::Output { | ||
self.promise_reject_functions | ||
.get(index.0.into_index()) | ||
.expect("BuiltinPromiseRejectFunction out of bounds") | ||
.as_ref() | ||
.expect("BuiltinPromiseRejectFunction slot empty") | ||
} | ||
} | ||
|
||
impl IndexMut<BuiltinPromiseRejectFunction> for Heap { | ||
fn index_mut(&mut self, index: BuiltinPromiseRejectFunction) -> &mut Self::Output { | ||
self.promise_reject_functions | ||
.get_mut(index.0.into_index()) | ||
.expect("BuiltinPromiseRejectFunction out of bounds") | ||
.as_mut() | ||
.expect("BuiltinPromiseRejectFunction slot empty") | ||
} | ||
} | ||
|
||
impl PromiseRejectFunctionHeapData { | ||
/// When a promise reject function is called with argument reason, the | ||
/// following steps are taken: | ||
pub(crate) fn call(agent: &mut Agent, reason: Value) { | ||
// 1. Let F be the active function object. | ||
let f = agent.running_execution_context().function.unwrap(); | ||
// 2. Assert: F has a [[Promise]] internal slot whose value is an Object. | ||
let Function::BuiltinPromiseRejectFunction(f) = f else { | ||
unreachable!(); | ||
}; | ||
// 3. Let promise be F.[[Promise]]. | ||
// 4. Let alreadyResolved be F.[[AlreadyResolved]]. | ||
let PromiseRejectFunctionHeapData { | ||
promise, | ||
already_resolved, | ||
.. | ||
} = agent[BuiltinPromiseRejectFunction(f)]; | ||
// 5. If alreadyResolved.[[Value]] is true, return undefined. | ||
if !already_resolved { | ||
// 6. Set alreadyResolved.[[Value]] to true. | ||
agent[BuiltinPromiseRejectFunction(f)].already_resolved = true; | ||
// 7. Perform RejectPromise(promise, reason). | ||
reject_promise(agent, promise, reason); | ||
// 8. Return undefined. | ||
} | ||
} | ||
} | ||
|
||
|
||
/// ### [27.2.1.3.2 Promise Resolve Functions]() | ||
/// | ||
/// A promise resolve function is an anonymous built-in function that has [[Promise]] and [[AlreadyResolved]] internal slots. | ||
// When a promise resolve function is called with argument resolution, the following steps are taken: | ||
|
||
// 1. Let F be the active function object. | ||
// 2. Assert: F has a [[Promise]] internal slot whose value is an Object. | ||
// 3. Let promise be F.[[Promise]]. | ||
// 4. Let alreadyResolved be F.[[AlreadyResolved]]. | ||
// 5. If alreadyResolved.[[Value]] is true, return undefined. | ||
// 6. Set alreadyResolved.[[Value]] to true. | ||
// 7. If SameValue(resolution, promise) is true, then | ||
// a. Let selfResolutionError be a newly created TypeError object. | ||
// b. Perform RejectPromise(promise, selfResolutionError). | ||
// c. Return undefined. | ||
// 8. If resolution is not an Object, then | ||
// a. Perform FulfillPromise(promise, resolution). | ||
// b. Return undefined. | ||
// 9. Let then be Completion(Get(resolution, "then")). | ||
// 10. If then is an abrupt completion, then | ||
// a. Perform RejectPromise(promise, then.[[Value]]). | ||
// b. Return undefined. | ||
// 11. Let thenAction be then.[[Value]]. | ||
// 12. If IsCallable(thenAction) is false, then | ||
// a. Perform FulfillPromise(promise, resolution). | ||
// b. Return undefined. | ||
// 13. Let thenJobCallback be HostMakeJobCallback(thenAction). | ||
// 14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback). | ||
// 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). | ||
// 16. Return undefined. | ||
|
||
// The "length" property of a promise resolve function is 1𝔽. | ||
/// ### [27.2.1.4 FulfillPromise ( promise, value )]() | ||
/// | ||
/// The abstract operation FulfillPromise takes arguments promise (a Promise) | ||
/// and value (an ECMAScript language value) and returns unused. | ||
pub(crate) fn fulfill_promise(agent: &mut Agent, promise: Promise, value: Value) { | ||
// 1. Assert: The value of promise.[[PromiseState]] is pending. | ||
// 2. Let reactions be promise.[[PromiseFulfillReactions]]. | ||
// 3. Set promise.[[PromiseResult]] to value. | ||
// 4. Set promise.[[PromiseFulfillReactions]] to undefined. | ||
// 5. Set promise.[[PromiseRejectReactions]] to undefined. | ||
// 6. Set promise.[[PromiseState]] to fulfilled. | ||
// 7. Perform TriggerPromiseReactions(reactions, value). | ||
// 8. Return unused. | ||
} | ||
|
||
/// ### [27.2.1.5 NewPromiseCapability ( C )]() | ||
/// | ||
/// The abstract operation NewPromiseCapability takes argument C (an ECMAScript | ||
/// language value) and returns either a normal completion containing a | ||
/// PromiseCapability Record or a throw completion. It attempts to use C as a | ||
/// constructor in the fashion of the built-in Promise constructor to create a | ||
/// promise and extract its resolve and reject functions. The promise plus the | ||
/// resolve and reject functions are used to initialize a new PromiseCapability | ||
/// Record. | ||
pub(crate) fn new_promise_capability(agent: &mut Agent, c: Value) -> JsResult<PromiseCapability> { | ||
// 1. If IsConstructor(C) is false, throw a TypeError exception. | ||
if !is_constructor(agent, c) { | ||
return Err(agent.throw_exception(ExceptionType::TypeError, "Not a constructor")); | ||
} | ||
// 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1). | ||
if c == agent.current_realm().intrinsics().promise().into_value() { | ||
todo!("PromiseConstructor quick-route") | ||
} | ||
|
||
// 3. Let resolvingFunctions be the Record { [[Resolve]]: undefined, [[Reject]]: undefined }. | ||
struct SettableResolvingFunction { | ||
resolve: Option<Function>, | ||
reject: Option<Function>, | ||
} | ||
let resolving_functions = SettableResolvingFunction { | ||
resolve: None, | ||
reject: None, | ||
}; | ||
|
||
// 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures resolvingFunctions and performs the following steps when called: | ||
agent.heap.abstract_closures.push(Some(AbstractClosureHeapData { | ||
object_index: None, | ||
length: 2, | ||
realm: agent.current_realm_id(), | ||
initial_name: Some(String::EMPTY_STRING), | ||
behaviour: Box::new(|agent: &mut Agent, this_value: Value, arguments: Option<ArgumentsList>| { | ||
// a. If resolvingFunctions.[[Resolve]] is not undefined, throw a TypeError exception. | ||
|
||
// b. If resolvingFunctions.[[Reject]] is not undefined, throw a TypeError exception. | ||
// c. Set resolvingFunctions.[[Resolve]] to resolve. | ||
// d. Set resolvingFunctions.[[Reject]] to reject. | ||
// e. Return undefined. | ||
Ok(Value::Undefined) | ||
}), | ||
})); | ||
// 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). | ||
// 6. Let promise be ? Construct(C, « executor »). | ||
// 7. If IsCallable(resolvingFunctions.[[Resolve]]) is false, throw a TypeError exception. | ||
// 8. If IsCallable(resolvingFunctions.[[Reject]]) is false, throw a TypeError exception. | ||
// 9. Return the PromiseCapability Record { [[Promise]]: promise, [[Resolve]]: resolvingFunctions.[[Resolve]], [[Reject]]: resolvingFunctions.[[Reject]] }. | ||
todo!(); | ||
// Note | ||
|
||
// This abstract operation supports Promise subclassing, as it is generic | ||
// on any constructor that calls a passed executor function argument in the | ||
// same way as the Promise constructor. It is used to generalize static | ||
// methods of the Promise constructor to any subclass. | ||
} | ||
|
||
/// ### [27.2.1.6 IsPromise ( x )]() | ||
/// | ||
/// The abstract operation IsPromise takes argument x (an ECMAScript language | ||
/// value) and returns a Boolean. It checks for the promise brand on an object. | ||
pub(crate) fn is_promise(agent: &mut Agent, x: Value) -> bool { | ||
// 1. If x is not an Object, return false. | ||
// 2. If x does not have a [[PromiseState]] internal slot, return false. | ||
// 3. Return true. | ||
matches!(x, Value::Promise(_)) | ||
} | ||
|
||
/// ### [27.2.1.7 RejectPromise ( promise, reason )]() | ||
/// | ||
/// The abstract operation RejectPromise takes arguments promise (a Promise) | ||
/// and reason (an ECMAScript language value) and returns unused. | ||
pub(crate) fn reject_promise(agent: &mut Agent, promise: Promise, reason: Value) { | ||
// 1. Assert: The value of promise.[[PromiseState]] is pending. | ||
let promise = &mut agent[promise]; | ||
// 2. Let reactions be promise.[[PromiseRejectReactions]]. | ||
// 3. Set promise.[[PromiseResult]] to reason. | ||
// 4. Set promise.[[PromiseFulfillReactions]] to undefined. | ||
// 5. Set promise.[[PromiseRejectReactions]] to undefined. | ||
// 6. Set promise.[[PromiseState]] to rejected. | ||
// 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject"). | ||
// 8. Perform TriggerPromiseReactions(reactions, reason). | ||
// 9. Return unused. | ||
} | ||
|
||
/// ### [27.2.1.8 TriggerPromiseReactions ( reactions, argument )]() | ||
/// | ||
/// The abstract operation TriggerPromiseReactions takes arguments reactions (a | ||
/// List of PromiseReaction Records) and argument (an ECMAScript language | ||
/// value) and returns unused. It enqueues a new Job for each record in | ||
/// reactions. Each such Job processes the \[\[Type\]\] and \[\[Handler\]\] of | ||
/// the PromiseReaction Record, and if the \[\[Handler\]\] is not empty, calls | ||
/// it passing the given argument. If the \[\[Handler\]\] is empty, the | ||
/// behaviour is determined by the \[\[Type\]\]. | ||
pub(crate) fn trigger_promise_reactions(agent: &mut Agent, reactions: &[PromiseReaction], argument: Value) { | ||
// 1. For each element reaction of reactions, do | ||
// a. Let job be NewPromiseReactionJob(reaction, argument). | ||
// b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). | ||
// 2. Return unused. | ||
} |
97 changes: 97 additions & 0 deletions
97
...raction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
//! ## [27.2.1.1 PromiseCapability Records]() | ||
use std::ops::{Index, IndexMut}; | ||
|
||
use crate::{ecmascript::{abstract_operations::operations_on_objects::{call, call_function}, builtins::ArgumentsList, execution::{agent::JsError, Agent, JsResult}, types::{Function, IntoValue, Object, Value}}, heap::{indexes::BaseIndex, Heap}}; | ||
|
||
|
||
#[derive(Debug, Clone, Copy)] | ||
pub(crate) struct PromiseCapabilityRecord { | ||
/// \[\[Promise\]\] | ||
/// | ||
/// an Object | ||
/// | ||
/// An object that is usable as a promise. | ||
pub(crate) promise: Object, | ||
/// \[\[Resolve\]\] | ||
/// | ||
/// a function object | ||
/// | ||
/// The function that is used to resolve the given promise. | ||
pub(crate) resolve: Function, | ||
/// \[\[Reject\]\] | ||
/// | ||
/// a function object | ||
/// | ||
/// The function that is used to reject the given promise. | ||
pub(crate) reject: Function, | ||
} | ||
|
||
pub(crate) type PromiseCapability = BaseIndex<PromiseCapabilityRecord>; | ||
|
||
impl Index<PromiseCapability> for Agent { | ||
type Output = PromiseCapabilityRecord; | ||
|
||
fn index(&self, index: PromiseCapability) -> &Self::Output { | ||
&self.heap[index] | ||
} | ||
} | ||
|
||
impl IndexMut<PromiseCapability> for Agent { | ||
fn index_mut(&mut self, index: PromiseCapability) -> &mut Self::Output { | ||
&mut self.heap[index] | ||
} | ||
} | ||
|
||
impl Index<PromiseCapability> for Heap { | ||
type Output = PromiseCapabilityRecord; | ||
|
||
fn index(&self, index: PromiseCapability) -> &Self::Output { | ||
self.promise_capability_records | ||
.get(index.into_index()) | ||
.expect("PromiseCapability out of bounds") | ||
.as_ref() | ||
.expect("PromiseCapability slot empty") | ||
} | ||
} | ||
|
||
impl IndexMut<PromiseCapability> for Heap { | ||
fn index_mut(&mut self, index: PromiseCapability) -> &mut Self::Output { | ||
self.promise_capability_records | ||
.get_mut(index.into_index()) | ||
.expect("PromiseCapability out of bounds") | ||
.as_mut() | ||
.expect("PromiseCapability slot empty") | ||
} | ||
} | ||
|
||
/// ### [27.2.1.1.1 IfAbruptRejectPromise ( value, capability )](https://tc39.es/ecma262/#sec-ifabruptrejectpromise) | ||
/// | ||
/// IfAbruptRejectPromise is a shorthand for a sequence of algorithm steps that | ||
/// use a PromiseCapability Record. An algorithm step of the form: | ||
/// | ||
/// ``` | ||
/// 1. IfAbruptRejectPromise(value, capability). | ||
/// ``` | ||
/// | ||
/// means the same thing as: | ||
/// ``` | ||
/// 1. Assert: value is a Completion Record. | ||
/// 2. If value is an abrupt completion, then | ||
/// a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). | ||
/// b. Return capability.[[Promise]]. | ||
/// 3. Else, | ||
/// a. Set value to ! value. | ||
/// ``` | ||
#[inline(always)] | ||
pub(crate) fn if_abrupt_reject_promise<T>(agent: &mut Agent, value: JsResult<T>, capability: PromiseCapability) -> JsResult<T> { | ||
value.or_else(|err| { | ||
// If abrupt completion, call reject and make caller return the | ||
// capability promise | ||
let PromiseCapabilityRecord { promise, reject, .. } = agent[capability]; | ||
call_function(agent, reject, Value::Undefined, Some(ArgumentsList(&[err.0])))?; | ||
// Note: We return an error here so that caller gets to call this | ||
// function with the ? operator | ||
Err(JsError(promise.into_value())) | ||
}) | ||
} |
Oops, something went wrong.