Skip to content

Commit 417334e

Browse files
committed
Abstract closures, Promises, and such work
1 parent df28e4e commit 417334e

File tree

15 files changed

+637
-53
lines changed

15 files changed

+637
-53
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
pub(crate) mod promise_abstract_operations;
12
pub(crate) mod promise_constructor;
23
pub(crate) mod promise_prototype;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
use std::ops::{Index, IndexMut};
2+
3+
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}};
4+
5+
use self::{promise_capability_records::PromiseCapability, promise_reaction_records::PromiseReaction};
6+
7+
pub(crate) mod promise_capability_records;
8+
pub(crate) mod promise_reaction_records;
9+
10+
pub(crate) struct PromiseResolvingFunctions {
11+
pub(crate) resolve: Function,
12+
pub(crate) reject: BuiltinPromiseRejectFunction,
13+
}
14+
15+
/// ### [27.2.1.3 CreateResolvingFunctions ( promise )]()
16+
///
17+
/// The abstract operation CreateResolvingFunctions takes argument promise (a
18+
/// Promise) and returns a Record with fields \[\[Resolve\]\] (a function
19+
/// object) and \[\[Reject\]\] (a function object).
20+
pub(crate) fn create_resolving_functions(agent: &mut Agent, promise: Promise) -> PromiseResolvingFunctions {
21+
// 1. Let alreadyResolved be the Record { [[Value]]: false }.
22+
let already_resolved = false;
23+
// 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions.
24+
// 3. Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions.
25+
// 4. Let resolve be CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »).
26+
// TODO
27+
let resolve = Function::BoundFunction(BoundFunctionIndex::from_u32_index(0));
28+
// 5. Set resolve.[[Promise]] to promise.
29+
// 6. Set resolve.[[AlreadyResolved]] to alreadyResolved.
30+
// 7. Let stepsReject be the algorithm steps defined in Promise Reject Functions.
31+
// 8. Let lengthReject be the number of non-optional parameters of the function definition in Promise Reject Functions.
32+
// 9. Let reject be CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »).
33+
let reject = PromiseRejectFunctionHeapData {
34+
promise,
35+
already_resolved,
36+
object_index: None,
37+
};
38+
// 10. Set reject.[[Promise]] to promise.
39+
// 11. Set reject.[[AlreadyResolved]] to alreadyResolved.
40+
agent.heap.promise_reject_functions.push(Some(reject));
41+
let reject = BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex::last(&agent.heap.promise_reject_functions));
42+
// 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }.
43+
PromiseResolvingFunctions { resolve, reject }
44+
}
45+
46+
/// ### [27.2.1.3.1 Promise Reject Functions]()
47+
///
48+
/// A promise reject function is an anonymous built-in function that has
49+
/// \[\[Promise\]\] and \[\[AlreadyResolved\]\] internal slots.
50+
///
51+
/// The "length" property of a promise reject function is 1𝔽.
52+
#[derive(Debug, Clone, Copy)]
53+
pub(crate) struct PromiseRejectFunctionHeapData {
54+
/// \[\[Promise\]\]
55+
pub(crate) promise: Promise,
56+
/// \[\[AlreadyResolved\]\]
57+
pub(crate) already_resolved: bool,
58+
pub(crate) object_index: Option<Object>,
59+
}
60+
61+
pub(crate) type BuiltinPromiseRejectFunctionIndex = BaseIndex<PromiseRejectFunctionHeapData>;
62+
63+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64+
pub(crate) struct BuiltinPromiseRejectFunction(pub(crate) BuiltinPromiseRejectFunctionIndex);
65+
66+
impl Index<BuiltinPromiseRejectFunction> for Agent {
67+
type Output = PromiseRejectFunctionHeapData;
68+
69+
fn index(&self, index: BuiltinPromiseRejectFunction) -> &Self::Output {
70+
&self.heap[index]
71+
}
72+
}
73+
74+
impl IndexMut<BuiltinPromiseRejectFunction> for Agent {
75+
fn index_mut(&mut self, index: BuiltinPromiseRejectFunction) -> &mut Self::Output {
76+
&mut self.heap[index]
77+
}
78+
}
79+
80+
impl Index<BuiltinPromiseRejectFunction> for Heap {
81+
type Output = PromiseRejectFunctionHeapData;
82+
83+
fn index(&self, index: BuiltinPromiseRejectFunction) -> &Self::Output {
84+
self.promise_reject_functions
85+
.get(index.0.into_index())
86+
.expect("BuiltinPromiseRejectFunction out of bounds")
87+
.as_ref()
88+
.expect("BuiltinPromiseRejectFunction slot empty")
89+
}
90+
}
91+
92+
impl IndexMut<BuiltinPromiseRejectFunction> for Heap {
93+
fn index_mut(&mut self, index: BuiltinPromiseRejectFunction) -> &mut Self::Output {
94+
self.promise_reject_functions
95+
.get_mut(index.0.into_index())
96+
.expect("BuiltinPromiseRejectFunction out of bounds")
97+
.as_mut()
98+
.expect("BuiltinPromiseRejectFunction slot empty")
99+
}
100+
}
101+
102+
impl PromiseRejectFunctionHeapData {
103+
/// When a promise reject function is called with argument reason, the
104+
/// following steps are taken:
105+
pub(crate) fn call(agent: &mut Agent, reason: Value) {
106+
// 1. Let F be the active function object.
107+
let f = agent.running_execution_context().function.unwrap();
108+
// 2. Assert: F has a [[Promise]] internal slot whose value is an Object.
109+
let Function::BuiltinPromiseRejectFunction(f) = f else {
110+
unreachable!();
111+
};
112+
// 3. Let promise be F.[[Promise]].
113+
// 4. Let alreadyResolved be F.[[AlreadyResolved]].
114+
let PromiseRejectFunctionHeapData {
115+
promise,
116+
already_resolved,
117+
..
118+
} = agent[BuiltinPromiseRejectFunction(f)];
119+
// 5. If alreadyResolved.[[Value]] is true, return undefined.
120+
if !already_resolved {
121+
// 6. Set alreadyResolved.[[Value]] to true.
122+
agent[BuiltinPromiseRejectFunction(f)].already_resolved = true;
123+
// 7. Perform RejectPromise(promise, reason).
124+
reject_promise(agent, promise, reason);
125+
// 8. Return undefined.
126+
}
127+
}
128+
}
129+
130+
131+
/// ### [27.2.1.3.2 Promise Resolve Functions]()
132+
///
133+
/// A promise resolve function is an anonymous built-in function that has [[Promise]] and [[AlreadyResolved]] internal slots.
134+
135+
// When a promise resolve function is called with argument resolution, the following steps are taken:
136+
137+
// 1. Let F be the active function object.
138+
// 2. Assert: F has a [[Promise]] internal slot whose value is an Object.
139+
// 3. Let promise be F.[[Promise]].
140+
// 4. Let alreadyResolved be F.[[AlreadyResolved]].
141+
// 5. If alreadyResolved.[[Value]] is true, return undefined.
142+
// 6. Set alreadyResolved.[[Value]] to true.
143+
// 7. If SameValue(resolution, promise) is true, then
144+
// a. Let selfResolutionError be a newly created TypeError object.
145+
// b. Perform RejectPromise(promise, selfResolutionError).
146+
// c. Return undefined.
147+
// 8. If resolution is not an Object, then
148+
// a. Perform FulfillPromise(promise, resolution).
149+
// b. Return undefined.
150+
// 9. Let then be Completion(Get(resolution, "then")).
151+
// 10. If then is an abrupt completion, then
152+
// a. Perform RejectPromise(promise, then.[[Value]]).
153+
// b. Return undefined.
154+
// 11. Let thenAction be then.[[Value]].
155+
// 12. If IsCallable(thenAction) is false, then
156+
// a. Perform FulfillPromise(promise, resolution).
157+
// b. Return undefined.
158+
// 13. Let thenJobCallback be HostMakeJobCallback(thenAction).
159+
// 14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback).
160+
// 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
161+
// 16. Return undefined.
162+
163+
// The "length" property of a promise resolve function is 1𝔽.
164+
/// ### [27.2.1.4 FulfillPromise ( promise, value )]()
165+
///
166+
/// The abstract operation FulfillPromise takes arguments promise (a Promise)
167+
/// and value (an ECMAScript language value) and returns unused.
168+
pub(crate) fn fulfill_promise(agent: &mut Agent, promise: Promise, value: Value) {
169+
// 1. Assert: The value of promise.[[PromiseState]] is pending.
170+
// 2. Let reactions be promise.[[PromiseFulfillReactions]].
171+
// 3. Set promise.[[PromiseResult]] to value.
172+
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
173+
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
174+
// 6. Set promise.[[PromiseState]] to fulfilled.
175+
// 7. Perform TriggerPromiseReactions(reactions, value).
176+
// 8. Return unused.
177+
}
178+
179+
/// ### [27.2.1.5 NewPromiseCapability ( C )]()
180+
///
181+
/// The abstract operation NewPromiseCapability takes argument C (an ECMAScript
182+
/// language value) and returns either a normal completion containing a
183+
/// PromiseCapability Record or a throw completion. It attempts to use C as a
184+
/// constructor in the fashion of the built-in Promise constructor to create a
185+
/// promise and extract its resolve and reject functions. The promise plus the
186+
/// resolve and reject functions are used to initialize a new PromiseCapability
187+
/// Record.
188+
pub(crate) fn new_promise_capability(agent: &mut Agent, c: Value) -> JsResult<PromiseCapability> {
189+
// 1. If IsConstructor(C) is false, throw a TypeError exception.
190+
if !is_constructor(agent, c) {
191+
return Err(agent.throw_exception(ExceptionType::TypeError, "Not a constructor"));
192+
}
193+
// 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1).
194+
if c == agent.current_realm().intrinsics().promise().into_value() {
195+
todo!("PromiseConstructor quick-route")
196+
}
197+
198+
// 3. Let resolvingFunctions be the Record { [[Resolve]]: undefined, [[Reject]]: undefined }.
199+
struct SettableResolvingFunction {
200+
resolve: Option<Function>,
201+
reject: Option<Function>,
202+
}
203+
let resolving_functions = SettableResolvingFunction {
204+
resolve: None,
205+
reject: None,
206+
};
207+
208+
// 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures resolvingFunctions and performs the following steps when called:
209+
agent.heap.abstract_closures.push(Some(AbstractClosureHeapData {
210+
object_index: None,
211+
length: 2,
212+
realm: agent.current_realm_id(),
213+
initial_name: Some(String::EMPTY_STRING),
214+
behaviour: Box::new(|agent: &mut Agent, this_value: Value, arguments: Option<ArgumentsList>| {
215+
// a. If resolvingFunctions.[[Resolve]] is not undefined, throw a TypeError exception.
216+
217+
// b. If resolvingFunctions.[[Reject]] is not undefined, throw a TypeError exception.
218+
// c. Set resolvingFunctions.[[Resolve]] to resolve.
219+
// d. Set resolvingFunctions.[[Reject]] to reject.
220+
// e. Return undefined.
221+
Ok(Value::Undefined)
222+
}),
223+
}));
224+
// 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »).
225+
// 6. Let promise be ? Construct(C, « executor »).
226+
// 7. If IsCallable(resolvingFunctions.[[Resolve]]) is false, throw a TypeError exception.
227+
// 8. If IsCallable(resolvingFunctions.[[Reject]]) is false, throw a TypeError exception.
228+
// 9. Return the PromiseCapability Record { [[Promise]]: promise, [[Resolve]]: resolvingFunctions.[[Resolve]], [[Reject]]: resolvingFunctions.[[Reject]] }.
229+
todo!();
230+
// Note
231+
232+
// This abstract operation supports Promise subclassing, as it is generic
233+
// on any constructor that calls a passed executor function argument in the
234+
// same way as the Promise constructor. It is used to generalize static
235+
// methods of the Promise constructor to any subclass.
236+
}
237+
238+
/// ### [27.2.1.6 IsPromise ( x )]()
239+
///
240+
/// The abstract operation IsPromise takes argument x (an ECMAScript language
241+
/// value) and returns a Boolean. It checks for the promise brand on an object.
242+
pub(crate) fn is_promise(agent: &mut Agent, x: Value) -> bool {
243+
// 1. If x is not an Object, return false.
244+
// 2. If x does not have a [[PromiseState]] internal slot, return false.
245+
// 3. Return true.
246+
matches!(x, Value::Promise(_))
247+
}
248+
249+
/// ### [27.2.1.7 RejectPromise ( promise, reason )]()
250+
///
251+
/// The abstract operation RejectPromise takes arguments promise (a Promise)
252+
/// and reason (an ECMAScript language value) and returns unused.
253+
pub(crate) fn reject_promise(agent: &mut Agent, promise: Promise, reason: Value) {
254+
// 1. Assert: The value of promise.[[PromiseState]] is pending.
255+
let promise = &mut agent[promise];
256+
// 2. Let reactions be promise.[[PromiseRejectReactions]].
257+
// 3. Set promise.[[PromiseResult]] to reason.
258+
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
259+
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
260+
// 6. Set promise.[[PromiseState]] to rejected.
261+
// 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject").
262+
// 8. Perform TriggerPromiseReactions(reactions, reason).
263+
// 9. Return unused.
264+
}
265+
266+
/// ### [27.2.1.8 TriggerPromiseReactions ( reactions, argument )]()
267+
///
268+
/// The abstract operation TriggerPromiseReactions takes arguments reactions (a
269+
/// List of PromiseReaction Records) and argument (an ECMAScript language
270+
/// value) and returns unused. It enqueues a new Job for each record in
271+
/// reactions. Each such Job processes the \[\[Type\]\] and \[\[Handler\]\] of
272+
/// the PromiseReaction Record, and if the \[\[Handler\]\] is not empty, calls
273+
/// it passing the given argument. If the \[\[Handler\]\] is empty, the
274+
/// behaviour is determined by the \[\[Type\]\].
275+
pub(crate) fn trigger_promise_reactions(agent: &mut Agent, reactions: &[PromiseReaction], argument: Value) {
276+
// 1. For each element reaction of reactions, do
277+
// a. Let job be NewPromiseReactionJob(reaction, argument).
278+
// b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
279+
// 2. Return unused.
280+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//! ## [27.2.1.1 PromiseCapability Records]()
2+
3+
use std::ops::{Index, IndexMut};
4+
5+
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}};
6+
7+
8+
#[derive(Debug, Clone, Copy)]
9+
pub(crate) struct PromiseCapabilityRecord {
10+
/// \[\[Promise\]\]
11+
///
12+
/// an Object
13+
///
14+
/// An object that is usable as a promise.
15+
pub(crate) promise: Object,
16+
/// \[\[Resolve\]\]
17+
///
18+
/// a function object
19+
///
20+
/// The function that is used to resolve the given promise.
21+
pub(crate) resolve: Function,
22+
/// \[\[Reject\]\]
23+
///
24+
/// a function object
25+
///
26+
/// The function that is used to reject the given promise.
27+
pub(crate) reject: Function,
28+
}
29+
30+
pub(crate) type PromiseCapability = BaseIndex<PromiseCapabilityRecord>;
31+
32+
impl Index<PromiseCapability> for Agent {
33+
type Output = PromiseCapabilityRecord;
34+
35+
fn index(&self, index: PromiseCapability) -> &Self::Output {
36+
&self.heap[index]
37+
}
38+
}
39+
40+
impl IndexMut<PromiseCapability> for Agent {
41+
fn index_mut(&mut self, index: PromiseCapability) -> &mut Self::Output {
42+
&mut self.heap[index]
43+
}
44+
}
45+
46+
impl Index<PromiseCapability> for Heap {
47+
type Output = PromiseCapabilityRecord;
48+
49+
fn index(&self, index: PromiseCapability) -> &Self::Output {
50+
self.promise_capability_records
51+
.get(index.into_index())
52+
.expect("PromiseCapability out of bounds")
53+
.as_ref()
54+
.expect("PromiseCapability slot empty")
55+
}
56+
}
57+
58+
impl IndexMut<PromiseCapability> for Heap {
59+
fn index_mut(&mut self, index: PromiseCapability) -> &mut Self::Output {
60+
self.promise_capability_records
61+
.get_mut(index.into_index())
62+
.expect("PromiseCapability out of bounds")
63+
.as_mut()
64+
.expect("PromiseCapability slot empty")
65+
}
66+
}
67+
68+
/// ### [27.2.1.1.1 IfAbruptRejectPromise ( value, capability )](https://tc39.es/ecma262/#sec-ifabruptrejectpromise)
69+
///
70+
/// IfAbruptRejectPromise is a shorthand for a sequence of algorithm steps that
71+
/// use a PromiseCapability Record. An algorithm step of the form:
72+
///
73+
/// ```
74+
/// 1. IfAbruptRejectPromise(value, capability).
75+
/// ```
76+
///
77+
/// means the same thing as:
78+
/// ```
79+
/// 1. Assert: value is a Completion Record.
80+
/// 2. If value is an abrupt completion, then
81+
/// a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
82+
/// b. Return capability.[[Promise]].
83+
/// 3. Else,
84+
/// a. Set value to ! value.
85+
/// ```
86+
#[inline(always)]
87+
pub(crate) fn if_abrupt_reject_promise<T>(agent: &mut Agent, value: JsResult<T>, capability: PromiseCapability) -> JsResult<T> {
88+
value.or_else(|err| {
89+
// If abrupt completion, call reject and make caller return the
90+
// capability promise
91+
let PromiseCapabilityRecord { promise, reject, .. } = agent[capability];
92+
call_function(agent, reject, Value::Undefined, Some(ArgumentsList(&[err.0])))?;
93+
// Note: We return an error here so that caller gets to call this
94+
// function with the ? operator
95+
Err(JsError(promise.into_value()))
96+
})
97+
}

0 commit comments

Comments
 (0)