|
| 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 | +} |
0 commit comments