From 930fe06abf30a3f53cacd5ee693cb10e12fdb515 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Wed, 8 Jan 2025 09:30:14 -0600 Subject: [PATCH 1/6] Implement `Array.fromAsync` --- core/engine/src/builtins/array/mod.rs | 18 +++ core/engine/src/module/synthetic.rs | 2 +- core/engine/src/native_function/await.rs | 127 ++++++++++++++++++ .../mod.rs} | 4 + core/engine/src/object/builtins/jspromise.rs | 10 +- 5 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 core/engine/src/native_function/await.rs rename core/engine/src/{native_function.rs => native_function/mod.rs} (99%) diff --git a/core/engine/src/builtins/array/mod.rs b/core/engine/src/builtins/array/mod.rs index ebbc0c80f9c..d9cbe5875c9 100644 --- a/core/engine/src/builtins/array/mod.rs +++ b/core/engine/src/builtins/array/mod.rs @@ -661,6 +661,24 @@ impl Array { iterator_record.close(error, context) } + /// [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`][spec] + /// + /// The `Array.fromAsync()` static method creates a new, + /// shallow-copied Array instance from a list or iterator of Promise-like values. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/proposal-array-from-async/#sec-array.fromAsync + #[cfg(feature = "experimental")] + pub(crate) fn from_async(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + use crate::object::JsPromise; + + let (promise, resolvers) = JsPromise::new_pending(context); + + return Ok(promise.into()) + } + /// `Array.isArray( arg )` /// /// The isArray function takes one argument arg, and returns the Boolean value true diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index 99e8f278264..be094fc8833 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -58,7 +58,7 @@ pub struct SyntheticModuleInitializer { impl std::fmt::Debug for SyntheticModuleInitializer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ModuleInitializer").finish_non_exhaustive() + f.debug_struct("SyntheticModuleInitializer").finish_non_exhaustive() } } diff --git a/core/engine/src/native_function/await.rs b/core/engine/src/native_function/await.rs new file mode 100644 index 00000000000..b1a6dc8283b --- /dev/null +++ b/core/engine/src/native_function/await.rs @@ -0,0 +1,127 @@ +use boa_gc::{Finalize, Gc, Trace}; + +use crate::{Context, JsResult, JsValue}; + +trait TraceableContinuation: Trace { + fn call(&self, value: JsResult, context: &mut Context); +} + +#[derive(Trace, Finalize)] +struct Continuation +where + F: Fn(JsResult, &T, &mut Context), + T: Trace, +{ + // SAFETY: `NativeFunction`'s safe API ensures only `Copy` closures are stored; its unsafe API, + // on the other hand, explains the invariants to hold in order for this to be safe, shifting + // the responsibility to the caller. + #[unsafe_ignore_trace] + f: F, + captures: T, +} + +impl TraceableContinuation for Continuation +where + F: Fn(JsResult, &T, &mut Context), + T: Trace, +{ + fn call(&self, result: JsResult, context: &mut Context) { + (self.f)(result, &self.captures, context) + } +} + +/// A callable Rust continuation that can be used to await promises. +/// +/// # Caveats +/// +/// By limitations of the Rust language, the garbage collector currently cannot inspect closures +/// in order to trace their captured variables. This means that only [`Copy`] closures are 100% safe +/// to use. All other closures can also be stored in a `NativeContinuation`, albeit by using an `unsafe` +/// API, but note that passing closures implicitly capturing traceable types could cause +/// **Undefined Behaviour**. +#[derive(Trace, Finalize)] +pub(crate) struct NativeContinuation { + inner: Gc, +} + +impl std::fmt::Debug for NativeContinuation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NativeContinuation").finish_non_exhaustive() + } +} + +impl NativeContinuation { + /// Creates a `NativeFunction` from a `Copy` closure. + pub(crate) fn from_copy_closure(closure: F) -> Self + where + F: Fn(JsResult, &mut Context) + Copy + 'static, + { + // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. + unsafe { Self::from_closure(closure) } + } + + /// Creates a `NativeFunction` from a `Copy` closure and a list of traceable captures. + pub(crate) fn from_copy_closure_with_captures(closure: F, captures: T) -> Self + where + F: Fn(JsResult, &T, &mut Context) + Copy + 'static, + T: Trace + 'static, + { + // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. + unsafe { Self::from_closure_with_captures(closure, captures) } + } + + /// Creates a new `NativeFunction` from a closure. + /// + /// # Safety + /// + /// Passing a closure that contains a captured variable that needs to be traced by the garbage + /// collector could cause an use after free, memory corruption or other kinds of **Undefined + /// Behaviour**. See for a technical explanation + /// on why that is the case. + pub(crate) unsafe fn from_closure(closure: F) -> Self + where + F: Fn(JsResult, &mut Context) + 'static, + { + // SAFETY: The caller must ensure the invariants of the closure hold. + unsafe { + Self::from_closure_with_captures( + move |result, (), context| closure(result, context), + (), + ) + } + } + + /// Create a new `NativeFunction` from a closure and a list of traceable captures. + /// + /// # Safety + /// + /// Passing a closure that contains a captured variable that needs to be traced by the garbage + /// collector could cause an use after free, memory corruption or other kinds of **Undefined + /// Behaviour**. See for a technical explanation + /// on why that is the case. + pub(crate) unsafe fn from_closure_with_captures(closure: F, captures: T) -> Self + where + F: Fn(JsResult, &T, &mut Context) + 'static, + T: Trace + 'static, + { + // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the + // future: https://github.com/rust-lang/rust/issues/18598 + let ptr = Gc::into_raw(Gc::new(Continuation { + f: closure, + captures, + })); + // SAFETY: The pointer returned by `into_raw` is only used to coerce to a trait object, + // meaning this is safe. + unsafe { + Self { + inner: Gc::from_raw(ptr), + } + } + } + + /// Calls this `NativeFunction`, forwarding the arguments to the corresponding function. + #[inline] + pub(crate) fn call(&self, result: JsResult, context: &mut Context) { + self.inner.call(result, context) + } +} diff --git a/core/engine/src/native_function.rs b/core/engine/src/native_function/mod.rs similarity index 99% rename from core/engine/src/native_function.rs rename to core/engine/src/native_function/mod.rs index 75f3b933abc..4456ac3267d 100644 --- a/core/engine/src/native_function.rs +++ b/core/engine/src/native_function/mod.rs @@ -20,6 +20,10 @@ use crate::{ Context, JsNativeError, JsObject, JsResult, JsValue, }; +mod r#await; + +pub(crate) use r#await::NativeContinuation; + /// The required signature for all native built-in function pointers. /// /// # Arguments diff --git a/core/engine/src/object/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index 777113b8cec..f7c0e8205dd 100644 --- a/core/engine/src/object/builtins/jspromise.rs +++ b/core/engine/src/object/builtins/jspromise.rs @@ -7,11 +7,7 @@ use crate::{ builtins::{ promise::{PromiseState, ResolvingFunctions}, Promise, - }, - job::NativeJob, - object::JsObject, - value::TryFromJs, - Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction, + }, job::NativeJob, native_function::NativeContinuation, object::JsObject, value::TryFromJs, Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction }; use boa_gc::{Finalize, Gc, GcRefCell, Trace}; @@ -1152,6 +1148,10 @@ impl JsPromise { } } } + + pub(crate) fn await_native(&self, continuation: NativeContinuation, context: &mut Context) { + + } } impl From for JsObject { From 056f40aaf5d377a0c77e11383adfc82f2f94624d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Wed, 8 Jan 2025 20:44:36 -0600 Subject: [PATCH 2/6] Add state machine methods --- core/engine/src/builtins/array/mod.rs | 432 +++++++++++++++++- core/engine/src/module/synthetic.rs | 3 +- .../{await.rs => continuation.rs} | 59 +-- core/engine/src/native_function/mod.rs | 6 +- core/engine/src/object/builtins/jspromise.rs | 138 +++++- core/engine/src/vm/call_frame/mod.rs | 5 +- core/engine/src/vm/opcode/locals/mod.rs | 4 +- test262_config.toml | 3 - 8 files changed, 591 insertions(+), 59 deletions(-) rename core/engine/src/native_function/{await.rs => continuation.rs} (66%) diff --git a/core/engine/src/builtins/array/mod.rs b/core/engine/src/builtins/array/mod.rs index d9cbe5875c9..200ca0e137b 100644 --- a/core/engine/src/builtins/array/mod.rs +++ b/core/engine/src/builtins/array/mod.rs @@ -106,7 +106,7 @@ impl IntrinsicObject for Array { let unscopables_object = Self::unscopables_object(); - BuiltInBuilder::from_standard_constructor::(realm) + let builder = BuiltInBuilder::from_standard_constructor::(realm) // Static Methods .static_method(Self::from, js_string!("from"), 1) .static_method(Self::is_array, js_string!("isArray"), 1) @@ -177,8 +177,12 @@ impl IntrinsicObject for Array { symbol_unscopables, unscopables_object, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .build(); + ); + + #[cfg(feature = "experimental")] + let builder = builder.static_method(Self::from_async, js_string!("fromAsync"), 1); + + builder.build(); } fn get(intrinsics: &Intrinsics) -> JsObject { @@ -671,12 +675,428 @@ impl Array { /// /// [spec]: https://tc39.es/proposal-array-from-async/#sec-array.fromAsync #[cfg(feature = "experimental")] - pub(crate) fn from_async(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - use crate::object::JsPromise; + pub(crate) fn from_async( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + use std::cell::Cell; + + use crate::builtins::iterable::IteratorRecord; + use crate::builtins::promise::ResolvingFunctions; + use crate::builtins::AsyncFromSyncIterator; + use crate::native_function::{ContinuationState, NativeContinuation}; + use crate::object::{JsFunction, JsPromise}; + use crate::JsError; + + #[derive(Trace, Finalize)] + struct GlobalState { + mapfn: Option, + this_arg: JsValue, + resolvers: ResolvingFunctions, + } + + #[derive(Trace, Finalize)] + #[boa_gc(unsafe_no_drop)] + enum IterableStateMachine { + LoopStart { + a: JsObject, + k: u64, + iterator_record: IteratorRecord, + }, + LoopContinue { + a: JsObject, + k: u64, + iterator_record: IteratorRecord, + }, + LoopEnd { + a: JsObject, + k: u64, + iterator_record: IteratorRecord, + mapped_value: Option>, + }, + AsyncIteratorCloseStart { + err: JsError, + iterator: JsObject, + }, + AsyncIteratorCloseEnd { + err: JsError, + }, + } + + fn from_iterable( + result: JsResult, + global_state: &GlobalState, + state_machine: &Cell>, + context: &mut Context, + ) -> JsResult { + let Some(mut sm) = state_machine.take() else { + return Ok(ContinuationState::Done); + }; + + loop { + match sm { + IterableStateMachine::LoopStart { + a, + k, + iterator_record, + } => { + if k >= 2u64.pow(53) - 1 { + sm = IterableStateMachine::AsyncIteratorCloseStart { + err: JsNativeError::typ() + .with_message( + "Array.fromAsync: \ + reached the maximum number of elements in an array \ + (2^53 - 1)", + ) + .into(), + iterator: iterator_record.iterator().clone(), + }; + } else { + let next_result = iterator_record.next_method().call( + &iterator_record.iterator().clone().into(), + &[], + context, + )?; + + state_machine.set(Some(IterableStateMachine::LoopContinue { + a, + k, + iterator_record, + })); + return Ok(ContinuationState::Yielded(next_result)); + } + } + IterableStateMachine::LoopContinue { + a, + k, + mut iterator_record, + } => { + iterator_record.update_result(result.clone()?, context)?; + + if iterator_record.done() { + a.set(js_string!("length"), k, true, context)?; + global_state + .resolvers + .resolve + .call(&JsValue::undefined(), &[a.into()], context) + .expect("resolving functions cannot fail"); + + return Ok(ContinuationState::Done); + } + + let next_value = iterator_record.value(context)?; + if let Some(mapfn) = &global_state.mapfn { + let mapped_value = match mapfn.call( + &global_state.this_arg, + &[next_value, k.into()], + context, + ) { + Ok(v) => v, + Err(err) => { + sm = IterableStateMachine::AsyncIteratorCloseStart { + err, + iterator: iterator_record.iterator().clone(), + }; + continue; + } + }; + state_machine.set(Some(IterableStateMachine::LoopEnd { + a, + k, + iterator_record, + mapped_value: None, + })); + return Ok(ContinuationState::Yielded(mapped_value)); + } + sm = IterableStateMachine::LoopEnd { + a, + k, + iterator_record, + mapped_value: Some(Ok(next_value)), + } + } + IterableStateMachine::LoopEnd { + a, + k, + iterator_record, + mapped_value, + } => { + let mapped_value = match mapped_value.unwrap_or(result.clone()) { + Ok(value) => value, + Err(err) => { + sm = IterableStateMachine::AsyncIteratorCloseStart { + err, + iterator: iterator_record.iterator().clone(), + }; + continue; + } + }; + + sm = if let Err(err) = + a.create_data_property_or_throw(k, mapped_value, context) + { + IterableStateMachine::AsyncIteratorCloseStart { + err, + iterator: iterator_record.iterator().clone(), + } + } else { + IterableStateMachine::LoopStart { + a, + k: k + 1, + iterator_record, + } + }; + } + IterableStateMachine::AsyncIteratorCloseStart { err, iterator } => { + let Ok(Some(ret)) = iterator.get_method(js_string!("return"), context) + else { + return Err(err); + }; + + let Ok(value) = ret.call(&iterator.into(), &[], context) else { + return Err(err); + }; + + state_machine + .set(Some(IterableStateMachine::AsyncIteratorCloseEnd { err })); + return Ok(ContinuationState::Yielded(value)); + } + IterableStateMachine::AsyncIteratorCloseEnd { err } => { + return Err(err); + } + } + } + } + + #[derive(Trace, Finalize)] + #[boa_gc(unsafe_no_drop)] + enum ArrayLikeStateMachine { + LoopStart { + array_like: JsObject, + a: JsObject, + len: u64, + k: u64, + }, + LoopContinue { + array_like: JsObject, + a: JsObject, + len: u64, + k: u64, + }, + LoopEnd { + array_like: JsObject, + a: JsObject, + len: u64, + k: u64, + mapped_value: Option, + }, + } + + fn from_array_like( + result: JsResult, + global_state: &GlobalState, + state_machine: &Cell>, + context: &mut Context, + ) -> JsResult { + let Some(mut sm) = state_machine.take() else { + return Ok(ContinuationState::Done); + }; + + loop { + match sm { + ArrayLikeStateMachine::LoopStart { + array_like, + a, + len, + k, + } => { + if k >= len { + a.set(js_string!("length"), len, true, context)?; + global_state + .resolvers + .resolve + .call(&JsValue::undefined(), &[a.into()], context) + .expect("resolving functions cannot fail"); + return Ok(ContinuationState::Done); + } + let k_value = array_like.get(k, context)?; + state_machine.set(Some(ArrayLikeStateMachine::LoopContinue { + array_like, + a, + len, + k, + })); + return Ok(ContinuationState::Yielded(k_value)); + } + ArrayLikeStateMachine::LoopContinue { + array_like, + a, + len, + k, + } => { + let k_value = result.clone()?; + if let Some(mapfn) = &global_state.mapfn { + let mapped_value = mapfn.call( + &global_state.this_arg, + &[k_value, k.into()], + context, + )?; + state_machine.set(Some(ArrayLikeStateMachine::LoopEnd { + array_like, + a, + len, + k, + mapped_value: None, + })); + return Ok(ContinuationState::Yielded(mapped_value)); + } + sm = ArrayLikeStateMachine::LoopEnd { + array_like, + a, + len, + k, + mapped_value: Some(k_value), + } + } + ArrayLikeStateMachine::LoopEnd { + array_like, + a, + len, + k, + mapped_value, + } => { + let mapped_value = mapped_value.unwrap_or(result.clone()?); + a.create_data_property_or_throw(k, mapped_value, context)?; + sm = ArrayLikeStateMachine::LoopStart { + array_like, + a, + len, + k: k + 1, + } + } + } + } + } let (promise, resolvers) = JsPromise::new_pending(context); - return Ok(promise.into()) + let async_items = args.get_or_undefined(0); + let mapfn = args.get_or_undefined(1); + let this_arg = args.get_or_undefined(2).clone(); + + let result: JsResult<()> = (|| { + let mapfn = if mapfn.is_undefined() { + None + } else { + let Some(callable) = mapfn.as_callable().cloned() else { + return Err(JsNativeError::typ() + .with_message("Array.fromAsync: mapping function must be callable") + .into()); + }; + Some(JsFunction::from_object_unchecked(callable)) + }; + + let iterator_record = if let Some(method) = + async_items.get_method(JsSymbol::async_iterator(), context)? + { + async_items.get_iterator_from_method(&method, context)? + } else if let Some(method) = async_items.get_method(JsSymbol::iterator(), context)? { + AsyncFromSyncIterator::create( + async_items.get_iterator_from_method(&method, context)?, + context, + ) + } else { + let array_like = async_items.to_object(context)?; + let len = array_like.length_of_array_like(context)?; + let a = if let Some(c) = this.as_constructor() { + c.construct(&[len.into()], None, context)? + } else { + Array::array_create(len, None, context)? + }; + let continuation = NativeContinuation::from_copy_closure_with_captures( + |result, (gs, sm), context| match from_array_like(result, gs, sm, context) { + Ok(cont) => cont, + Err(err) => { + gs.resolvers + .reject + .call(&JsValue::undefined(), &[err.to_opaque(context)], context) + .expect("resolving functions cannot fail"); + ContinuationState::Done + } + }, + ( + GlobalState { + mapfn, + this_arg, + resolvers: resolvers.clone(), + }, + Cell::new(Some(ArrayLikeStateMachine::LoopStart { + array_like, + a, + len, + k: 0, + })), + ), + ); + + if let ContinuationState::Yielded(value) = + continuation.call(Ok(JsValue::undefined()), context) + { + JsPromise::resolve(value, context).await_native(continuation, context); + } + + return Ok(()); + }; + + let a = if let Some(c) = this.as_constructor() { + c.construct(&[], None, context)? + } else { + Array::array_create(0, None, context)? + }; + + let continuation = NativeContinuation::from_copy_closure_with_captures( + |result, (gs, sm), context| match from_iterable(result, gs, sm, context) { + Ok(cont) => cont, + Err(err) => { + gs.resolvers + .reject + .call(&JsValue::undefined(), &[err.to_opaque(context)], context) + .expect("resolving functions cannot fail"); + ContinuationState::Done + } + }, + ( + GlobalState { + mapfn, + this_arg, + resolvers: resolvers.clone(), + }, + Cell::new(Some(IterableStateMachine::LoopStart { + a, + k: 0, + iterator_record, + })), + ), + ); + + if let ContinuationState::Yielded(value) = + continuation.call(Ok(JsValue::undefined()), context) + { + JsPromise::resolve(value, context).await_native(continuation, context); + } + + Ok(()) + })(); + + if let Err(err) = result { + resolvers + .reject + .call(&JsValue::undefined(), &[err.to_opaque(context)], context) + .expect("resolving functions cannot fail"); + } + + return Ok(promise.into()); } /// `Array.isArray( arg )` diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index be094fc8833..909a909c8b8 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -58,7 +58,8 @@ pub struct SyntheticModuleInitializer { impl std::fmt::Debug for SyntheticModuleInitializer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("SyntheticModuleInitializer").finish_non_exhaustive() + f.debug_struct("SyntheticModuleInitializer") + .finish_non_exhaustive() } } diff --git a/core/engine/src/native_function/await.rs b/core/engine/src/native_function/continuation.rs similarity index 66% rename from core/engine/src/native_function/await.rs rename to core/engine/src/native_function/continuation.rs index b1a6dc8283b..96d3dedabec 100644 --- a/core/engine/src/native_function/await.rs +++ b/core/engine/src/native_function/continuation.rs @@ -2,17 +2,24 @@ use boa_gc::{Finalize, Gc, Trace}; use crate::{Context, JsResult, JsValue}; +#[derive(Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] +pub(crate) enum ContinuationState { + Yielded(JsValue), + Done, +} + trait TraceableContinuation: Trace { - fn call(&self, value: JsResult, context: &mut Context); + fn call(&self, value: JsResult, context: &mut Context) -> ContinuationState; } #[derive(Trace, Finalize)] struct Continuation where - F: Fn(JsResult, &T, &mut Context), + F: Fn(JsResult, &T, &mut Context) -> ContinuationState, T: Trace, { - // SAFETY: `NativeFunction`'s safe API ensures only `Copy` closures are stored; its unsafe API, + // SAFETY: `NativeContinuation`'s safe API ensures only `Copy` closures are stored; its unsafe API, // on the other hand, explains the invariants to hold in order for this to be safe, shifting // the responsibility to the caller. #[unsafe_ignore_trace] @@ -22,10 +29,10 @@ where impl TraceableContinuation for Continuation where - F: Fn(JsResult, &T, &mut Context), + F: Fn(JsResult, &T, &mut Context) -> ContinuationState, T: Trace, { - fn call(&self, result: JsResult, context: &mut Context) { + fn call(&self, result: JsResult, context: &mut Context) -> ContinuationState { (self.f)(result, &self.captures, context) } } @@ -39,7 +46,7 @@ where /// to use. All other closures can also be stored in a `NativeContinuation`, albeit by using an `unsafe` /// API, but note that passing closures implicitly capturing traceable types could cause /// **Undefined Behaviour**. -#[derive(Trace, Finalize)] +#[derive(Clone, Trace, Finalize)] pub(crate) struct NativeContinuation { inner: Gc, } @@ -51,46 +58,16 @@ impl std::fmt::Debug for NativeContinuation { } impl NativeContinuation { - /// Creates a `NativeFunction` from a `Copy` closure. - pub(crate) fn from_copy_closure(closure: F) -> Self - where - F: Fn(JsResult, &mut Context) + Copy + 'static, - { - // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. - unsafe { Self::from_closure(closure) } - } - /// Creates a `NativeFunction` from a `Copy` closure and a list of traceable captures. pub(crate) fn from_copy_closure_with_captures(closure: F, captures: T) -> Self where - F: Fn(JsResult, &T, &mut Context) + Copy + 'static, + F: Fn(JsResult, &T, &mut Context) -> ContinuationState + Copy + 'static, T: Trace + 'static, { // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. unsafe { Self::from_closure_with_captures(closure, captures) } } - /// Creates a new `NativeFunction` from a closure. - /// - /// # Safety - /// - /// Passing a closure that contains a captured variable that needs to be traced by the garbage - /// collector could cause an use after free, memory corruption or other kinds of **Undefined - /// Behaviour**. See for a technical explanation - /// on why that is the case. - pub(crate) unsafe fn from_closure(closure: F) -> Self - where - F: Fn(JsResult, &mut Context) + 'static, - { - // SAFETY: The caller must ensure the invariants of the closure hold. - unsafe { - Self::from_closure_with_captures( - move |result, (), context| closure(result, context), - (), - ) - } - } - /// Create a new `NativeFunction` from a closure and a list of traceable captures. /// /// # Safety @@ -101,7 +78,7 @@ impl NativeContinuation { /// on why that is the case. pub(crate) unsafe fn from_closure_with_captures(closure: F, captures: T) -> Self where - F: Fn(JsResult, &T, &mut Context) + 'static, + F: Fn(JsResult, &T, &mut Context) -> ContinuationState + 'static, T: Trace + 'static, { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the @@ -121,7 +98,11 @@ impl NativeContinuation { /// Calls this `NativeFunction`, forwarding the arguments to the corresponding function. #[inline] - pub(crate) fn call(&self, result: JsResult, context: &mut Context) { + pub(crate) fn call( + &self, + result: JsResult, + context: &mut Context, + ) -> ContinuationState { self.inner.call(result, context) } } diff --git a/core/engine/src/native_function/mod.rs b/core/engine/src/native_function/mod.rs index 4456ac3267d..2e05ca40dd3 100644 --- a/core/engine/src/native_function/mod.rs +++ b/core/engine/src/native_function/mod.rs @@ -20,9 +20,11 @@ use crate::{ Context, JsNativeError, JsObject, JsResult, JsValue, }; -mod r#await; +#[cfg(feature = "experimental")] +mod continuation; -pub(crate) use r#await::NativeContinuation; +#[cfg(feature = "experimental")] +pub(crate) use continuation::{ContinuationState, NativeContinuation}; /// The required signature for all native built-in function pointers. /// diff --git a/core/engine/src/object/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index f7c0e8205dd..9aa099cda50 100644 --- a/core/engine/src/object/builtins/jspromise.rs +++ b/core/engine/src/object/builtins/jspromise.rs @@ -1,13 +1,19 @@ //! A Rust API wrapper for Boa's promise Builtin ECMAScript Object -use std::{future::Future, pin::Pin, task}; +use std::{cell::Cell, future::Future, pin::Pin, task}; use super::{JsArray, JsFunction}; use crate::{ builtins::{ + generator::GeneratorContext, promise::{PromiseState, ResolvingFunctions}, Promise, - }, job::NativeJob, native_function::NativeContinuation, object::JsObject, value::TryFromJs, Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction + }, + job::NativeJob, + js_string, + object::{FunctionObjectBuilder, JsObject}, + value::TryFromJs, + Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction, }; use boa_gc::{Finalize, Gc, GcRefCell, Trace}; @@ -1149,8 +1155,132 @@ impl JsPromise { } } - pub(crate) fn await_native(&self, continuation: NativeContinuation, context: &mut Context) { - + #[cfg(feature = "experimental")] + pub(crate) fn await_native( + &self, + continuation: crate::native_function::NativeContinuation, + context: &mut Context, + ) { + use crate::builtins::async_generator::AsyncGenerator; + + let mut frame = context.vm.frame().clone(); + frame.environments = context.vm.environments.clone(); + frame.realm = context.realm().clone(); + + let gen_ctx = GeneratorContext { + call_frame: Some(frame), + stack: context.vm.stack.clone(), + }; + + // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: + // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). + let on_fulfilled = FunctionObjectBuilder::new( + context.realm(), + NativeFunction::from_copy_closure_with_captures( + |_this, args, captures, context| { + // a. Let prevContext be the running execution context. + // b. Suspend prevContext. + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. + let continuation = &captures.0; + let mut gen = captures.1.take().expect("should only run once"); + + // NOTE: We need to get the object before resuming, since it could clear the stack. + let async_generator = gen.async_generator_object(); + + std::mem::swap(&mut context.vm.stack, &mut gen.stack); + let frame = gen.call_frame.take().expect("should have a call frame"); + context.vm.push_frame(frame); + + if let crate::native_function::ContinuationState::Yielded(value) = + continuation.call(Ok(args.get_or_undefined(0).clone()), context) + { + JsPromise::resolve(value, context) + .await_native(continuation.clone(), context); + } + + std::mem::swap(&mut context.vm.stack, &mut gen.stack); + gen.call_frame = context.vm.pop_frame(); + assert!(gen.call_frame.is_some()); + + if let Some(async_generator) = async_generator { + async_generator + .downcast_mut::() + .expect("must be async generator") + .context = Some(gen); + } + + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // f. Return undefined. + Ok(JsValue::undefined()) + }, + (continuation.clone(), Cell::new(Some(gen_ctx.clone()))), + ), + ) + .name(js_string!()) + .length(1) + .build(); + + // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called: + // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). + let on_rejected = FunctionObjectBuilder::new( + context.realm(), + NativeFunction::from_copy_closure_with_captures( + |_this, args, captures, context| { + // a. Let prevContext be the running execution context. + // b. Suspend prevContext. + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // f. Return undefined. + let continuation = &captures.0; + let mut gen = captures.1.take().expect("should only run once"); + + // NOTE: We need to get the object before resuming, since it could clear the stack. + let async_generator = gen.async_generator_object(); + + std::mem::swap(&mut context.vm.stack, &mut gen.stack); + let frame = gen.call_frame.take().expect("should have a call frame"); + context.vm.push_frame(frame); + + if let crate::native_function::ContinuationState::Yielded(value) = continuation + .call( + Err(JsError::from_opaque(args.get_or_undefined(0).clone())), + context, + ) + { + JsPromise::resolve(value, context) + .await_native(continuation.clone(), context); + } + + std::mem::swap(&mut context.vm.stack, &mut gen.stack); + gen.call_frame = context.vm.pop_frame(); + assert!(gen.call_frame.is_some()); + + if let Some(async_generator) = async_generator { + async_generator + .downcast_mut::() + .expect("must be async generator") + .context = Some(gen); + } + + Ok(JsValue::undefined()) + }, + (continuation, Cell::new(Some(gen_ctx))), + ), + ) + .name(js_string!()) + .length(1) + .build(); + + // 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected). + Promise::perform_promise_then( + &self.inner, + Some(on_fulfilled), + Some(on_rejected), + None, + context, + ); } } diff --git a/core/engine/src/vm/call_frame/mod.rs b/core/engine/src/vm/call_frame/mod.rs index f541a0d296e..6d25d1a4661 100644 --- a/core/engine/src/vm/call_frame/mod.rs +++ b/core/engine/src/vm/call_frame/mod.rs @@ -62,7 +62,7 @@ pub struct CallFrame { // SAFETY: Nothing requires tracing, so this is safe. #[unsafe_ignore_trace] - pub(crate) local_binings_initialized: Box<[bool]>, + pub(crate) local_bindings_initialized: Box<[bool]>, /// How many iterations a loop has done. pub(crate) loop_iteration_count: u64, @@ -163,7 +163,7 @@ impl CallFrame { argument_count: 0, iterators: ThinVec::new(), binding_stack: Vec::new(), - local_binings_initialized, + local_bindings_initialized: local_binings_initialized, loop_iteration_count: 0, active_runnable, environments, @@ -235,6 +235,7 @@ impl CallFrame { .cloned() } + #[track_caller] pub(crate) fn promise_capability(&self, stack: &[JsValue]) -> Option { if !self.code_block().is_async() { return None; diff --git a/core/engine/src/vm/opcode/locals/mod.rs b/core/engine/src/vm/opcode/locals/mod.rs index 20d4e626331..989322a7515 100644 --- a/core/engine/src/vm/opcode/locals/mod.rs +++ b/core/engine/src/vm/opcode/locals/mod.rs @@ -14,7 +14,7 @@ impl PopIntoLocal { #[allow(clippy::unnecessary_wraps)] #[allow(clippy::needless_pass_by_value)] fn operation(dst: u32, context: &mut Context) -> JsResult { - context.vm.frame_mut().local_binings_initialized[dst as usize] = true; + context.vm.frame_mut().local_bindings_initialized[dst as usize] = true; let value = context.vm.pop(); let rp = context.vm.frame().rp; @@ -55,7 +55,7 @@ impl PushFromLocal { #[allow(clippy::unnecessary_wraps)] #[allow(clippy::needless_pass_by_value)] fn operation(dst: u32, context: &mut Context) -> JsResult { - if !context.vm.frame().local_binings_initialized[dst as usize] { + if !context.vm.frame().local_bindings_initialized[dst as usize] { return Err(JsNativeError::reference() .with_message("access to uninitialized binding") .into()); diff --git a/test262_config.toml b/test262_config.toml index a9555448817..4c4a9898bdb 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -50,9 +50,6 @@ features = [ # https://github.com/tc39/proposal-duplicate-named-capturing-groups "regexp-duplicate-named-groups", - # https://github.com/tc39/proposal-array-from-async - "Array.fromAsync", - # https://github.com/tc39/proposal-json-parse-with-source "json-parse-with-source", From 772e8b99af0770eee0a0f1d1c34c1237e3c47dc8 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Thu, 9 Jan 2025 01:42:59 -0600 Subject: [PATCH 3/6] Polish documentation --- core/engine/src/builtins/array/from_async.rs | 622 ++++++++++++++++++ core/engine/src/builtins/array/mod.rs | 437 +----------- .../src/native_function/continuation.rs | 50 +- core/engine/src/native_function/mod.rs | 2 +- core/engine/src/object/builtins/jspromise.rs | 6 +- 5 files changed, 652 insertions(+), 465 deletions(-) create mode 100644 core/engine/src/builtins/array/from_async.rs diff --git a/core/engine/src/builtins/array/from_async.rs b/core/engine/src/builtins/array/from_async.rs new file mode 100644 index 00000000000..f149db9de9b --- /dev/null +++ b/core/engine/src/builtins/array/from_async.rs @@ -0,0 +1,622 @@ +use boa_gc::{Finalize, Trace}; + +use super::Array; +use crate::builtins::iterable::IteratorRecord; +use crate::builtins::promise::ResolvingFunctions; +use crate::builtins::AsyncFromSyncIterator; +use crate::native_function::{CoroutineState, NativeCoroutine}; +use crate::object::{JsFunction, JsPromise}; +use crate::{ + js_string, Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsSymbol, JsValue, +}; +use std::cell::Cell; + +impl Array { + /// [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`][spec] + /// + /// The `Array.fromAsync()` static method creates a new, + /// shallow-copied Array instance from a list or iterator of Promise-like values. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/proposal-array-from-async/#sec-array.fromAsync + #[allow(clippy::unnecessary_wraps)] + pub(crate) fn from_async( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let C be the this value. + // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). + let (promise, resolvers) = JsPromise::new_pending(context); + + let async_items = args.get_or_undefined(0); + let mapfn = args.get_or_undefined(1); + let this_arg = args.get_or_undefined(2).clone(); + + // 3. Let fromAsyncClosure be a new Abstract Closure with no parameters that captures C, mapfn, and thisArg and + // performs the following steps when called: + // 4. Perform AsyncFunctionStart(promiseCapability, fromAsyncClosure). + // NOTE: We avoid putting more state onto the coroutines by preprocessing all we can before allocating + // the coroutines. + let result: JsResult<()> = (|| { + // a. If mapfn is undefined, let mapping be false. + let mapfn = if mapfn.is_undefined() { + None + } else { + // b. Else, + // i. If IsCallable(mapfn) is false, throw a TypeError exception. + let Some(callable) = mapfn.as_callable().cloned() else { + return Err(JsNativeError::typ() + .with_message("Array.fromAsync: mapping function must be callable") + .into()); + }; + // ii. Let mapping be true. + Some(JsFunction::from_object_unchecked(callable)) + }; + + // c. Let usingAsyncIterator be ? GetMethod(asyncItems, @@asyncIterator). + // d. If usingAsyncIterator is undefined, then + // i. Let usingSyncIterator be ? GetMethod(asyncItems, @@iterator). + // e. Let iteratorRecord be undefined. + // f. If usingAsyncIterator is not undefined, then + let iterator_record = if let Some(method) = + async_items.get_method(JsSymbol::async_iterator(), context)? + { + // i. Set iteratorRecord to ? GetIterator(asyncItems, async, usingAsyncIterator). + async_items.get_iterator_from_method(&method, context)? + } + // g. Else if usingSyncIterator is not undefined, then + else if let Some(method) = async_items.get_method(JsSymbol::iterator(), context)? { + // i. Set iteratorRecord to ? CreateAsyncFromSyncIterator(GetIterator(asyncItems, sync, usingSyncIterator)). + AsyncFromSyncIterator::create( + async_items.get_iterator_from_method(&method, context)?, + context, + ) + } + // i. Else, + else { + // i. NOTE: asyncItems is neither an AsyncIterable nor an Iterable so assume it is an array-like object. + // ii. Let arrayLike be ! ToObject(asyncItems). + let array_like = async_items.to_object(context)?; + + // iii. Let len be ? LengthOfArrayLike(arrayLike). + let len = array_like.length_of_array_like(context)?; + // iv. If IsConstructor(C) is true, then + let a = if let Some(c) = this.as_constructor() { + // 1. Let A be ? Construct(C, « 𝔽(len) »). + c.construct(&[len.into()], None, context)? + } + // v. Else, + else { + // 1. Let A be ? ArrayCreate(len). + Array::array_create(len, None, context)? + }; + + let coroutine_state = ( + GlobalState { + mapfn, + this_arg, + resolvers: resolvers.clone(), + }, + Cell::new(Some(ArrayLikeStateMachine::LoopStart { + array_like, + a, + len, + // iii. Let k be 0. + k: 0, + })), + ); + + // Try to run the coroutine once to see if it finishes early. + // This avoids allocating a new coroutine that will immediately finish. + // Spec continues on `from_array_like`... + if let CoroutineState::Yielded(value) = + from_array_like(Ok(JsValue::undefined()), &coroutine_state, context) + { + // Coroutine yielded. We need to allocate it for a future execution. + JsPromise::resolve(value, context).await_native( + NativeCoroutine::from_copy_closure_with_captures( + from_array_like, + coroutine_state, + ), + context, + ); + } + + return Ok(()); + }; + + // h. If iteratorRecord is not undefined, then + + // i. If IsConstructor(C) is true, then + let a = if let Some(c) = this.as_constructor() { + // 1. Let A be ? Construct(C). + c.construct(&[], None, context)? + } + // ii. Else, + else { + // 1. Let A be ! ArrayCreate(0). + Array::array_create(0, None, context)? + }; + + let coroutine_state = ( + GlobalState { + mapfn, + this_arg, + resolvers: resolvers.clone(), + }, + Cell::new(Some(AsyncIteratorStateMachine::LoopStart { + // vi. Let k be 0. + k: 0, + a, + iterator_record, + })), + ); + + // Try to run the coroutine once to see if it finishes early. + // This avoids allocating a new coroutine that will immediately finish. + // Spec continues on `from_async_iterator`... + if let CoroutineState::Yielded(value) = + from_async_iterator(Ok(JsValue::undefined()), &coroutine_state, context) + { + JsPromise::resolve(value, context).await_native( + NativeCoroutine::from_copy_closure_with_captures( + from_async_iterator, + coroutine_state, + ), + context, + ); + } + + Ok(()) + })(); + + // AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) + // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start + // -> + // AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) + // https://tc39.es/ecma262/#sec-asyncblockstart + + // i. Assert: result is a throw completion. + if let Err(err) = result { + // ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »). + resolvers + .reject + .call(&JsValue::undefined(), &[err.to_opaque(context)], context) + .expect("resolving functions cannot fail"); + } + + // 5. Return promiseCapability.[[Promise]]. + Ok(promise.into()) + } +} + +#[derive(Trace, Finalize)] +struct GlobalState { + mapfn: Option, + this_arg: JsValue, + resolvers: ResolvingFunctions, +} + +#[derive(Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] +enum AsyncIteratorStateMachine { + LoopStart { + a: JsObject, + k: u64, + iterator_record: IteratorRecord, + }, + LoopContinue { + a: JsObject, + k: u64, + iterator_record: IteratorRecord, + }, + LoopEnd { + a: JsObject, + k: u64, + iterator_record: IteratorRecord, + mapped_value: Option>, + }, + AsyncIteratorCloseStart { + err: JsError, + iterator: JsObject, + }, + AsyncIteratorCloseEnd { + err: JsError, + }, +} + +/// Part of [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`][https://tc39.es/proposal-array-from-async/#sec-array.fromAsync]. +fn from_async_iterator( + mut result: JsResult, + (global_state, state_machine): &(GlobalState, Cell>), + context: &mut Context, +) -> CoroutineState { + let result = (|| { + let Some(mut sm) = state_machine.take() else { + return Ok(CoroutineState::Done); + }; + + // iv. Repeat, + loop { + match sm { + AsyncIteratorStateMachine::LoopStart { + a, + k, + iterator_record, + } => { + // Inverted conditional makes for a simpler code. + if k < 2u64.pow(53) - 1 { + // 2. Let Pk be ! ToString(𝔽(k)). + // 3. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). + let next_result = iterator_record.next_method().call( + &iterator_record.iterator().clone().into(), + &[], + context, + )?; + + state_machine.set(Some(AsyncIteratorStateMachine::LoopContinue { + a, + k, + iterator_record, + })); + + // 4. Set nextResult to ? Await(nextResult). + return Ok(CoroutineState::Yielded(next_result)); + } + + // 1. If k ≥ 2**53 - 1, then + + // a. Let error be ThrowCompletion(a newly created TypeError object). + // b. Return ? AsyncIteratorClose(iteratorRecord, error). + sm = AsyncIteratorStateMachine::AsyncIteratorCloseStart { + err: JsNativeError::typ() + .with_message( + "Array.fromAsync: \ + reached the maximum number of elements in an array \ + (2^53 - 1)", + ) + .into(), + iterator: iterator_record.iterator().clone(), + }; + } + AsyncIteratorStateMachine::LoopContinue { + a, + k, + mut iterator_record, + } => { + // `result` is `Await(nextResult)`. + let result = std::mem::replace(&mut result, Ok(JsValue::undefined())); + + // 5. If nextResult is not an Object, throw a TypeError exception. + // Implicit on the call to `update_result`. + iterator_record.update_result(result?, context)?; + + // 6. Let done be ? IteratorComplete(nextResult). + // 7. If done is true, + if iterator_record.done() { + // a. Perform ? Set(A, "length", 𝔽(k), true). + a.set(js_string!("length"), k, true, context)?; + + // b. Return Completion Record { [[Type]]: return, [[Value]]: A, [[Target]]: empty }. + // AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) + // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start + // -> + // AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) + // https://tc39.es/ecma262/#sec-asyncblockstart + + // g. Else if result is a return completion, then + // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »). + global_state + .resolvers + .resolve + .call(&JsValue::undefined(), &[a.into()], context) + .expect("resolving functions cannot fail"); + + return Ok(CoroutineState::Done); + } + + // 8. Let nextValue be ? IteratorValue(nextResult). + let next_value = iterator_record.value(context)?; + // 9. If mapping is true, then + if let Some(mapfn) = &global_state.mapfn { + // a. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »). + // b. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord). + // https://tc39.es/proposal-array-from-async/#sec-ifabruptcloseasynciterator + let mapped_value = match mapfn.call( + &global_state.this_arg, + &[next_value, k.into()], + context, + ) { + // 1. If value is an abrupt completion, then + Err(err) => { + // a. Perform ? AsyncIteratorClose(iteratorRecord, value). + // b. Return value. + sm = AsyncIteratorStateMachine::AsyncIteratorCloseStart { + err, + iterator: iterator_record.iterator().clone(), + }; + continue; + } + // 2. Else if value is a Completion Record, set value to value.[[Value]]. + Ok(value) => value, + }; + state_machine.set(Some(AsyncIteratorStateMachine::LoopEnd { + a, + k, + iterator_record, + mapped_value: None, + })); + // c. Set mappedValue to Await(mappedValue). + return Ok(CoroutineState::Yielded(mapped_value)); + } + + sm = AsyncIteratorStateMachine::LoopEnd { + a, + k, + iterator_record, + // 10. Else, let mappedValue be nextValue. + mapped_value: Some(Ok(next_value)), + } + } + AsyncIteratorStateMachine::LoopEnd { + a, + k, + iterator_record, + mapped_value, + } => { + // Either awaited `mappedValue` or directly set `mappedValue` to `nextValue`. + let result = std::mem::replace(&mut result, Ok(JsValue::undefined())); + + // d. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord). + // https://tc39.es/proposal-array-from-async/#sec-ifabruptcloseasynciterator + let mapped_value = match mapped_value.unwrap_or(result) { + // 1. If value is an abrupt completion, then + Err(err) => { + // a. Perform ? AsyncIteratorClose(iteratorRecord, value). + // b. Return value. + sm = AsyncIteratorStateMachine::AsyncIteratorCloseStart { + err, + iterator: iterator_record.iterator().clone(), + }; + continue; + } + // 2. Else if value is a Completion Record, set value to value.[[Value]]. + Ok(value) => value, + }; + + // 11. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue). + sm = if let Err(err) = a.create_data_property_or_throw(k, mapped_value, context) + { + // 12. If defineStatus is an abrupt completion, return ? AsyncIteratorClose(iteratorRecord, defineStatus). + AsyncIteratorStateMachine::AsyncIteratorCloseStart { + err, + iterator: iterator_record.iterator().clone(), + } + } else { + AsyncIteratorStateMachine::LoopStart { + a, + // 13. Set k to k + 1. + k: k + 1, + iterator_record, + } + }; + } + // AsyncIteratorClose ( iteratorRecord, completion ) + // https://tc39.es/ecma262/#sec-asynciteratorclose + // Simplified for only error completions. + AsyncIteratorStateMachine::AsyncIteratorCloseStart { err, iterator } => { + // 1. Assert: iteratorRecord.[[Iterator]] is an Object. + // 2. Let iterator be iteratorRecord.[[Iterator]]. + // 3. Let innerResult be Completion(GetMethod(iterator, "return")). + // 4. If innerResult is a normal completion, then + // a. Let return be innerResult.[[Value]]. + // b. If return is undefined, return ? completion. + // c. Set innerResult to Completion(Call(return, iterator)). + // d. If innerResult is a normal completion, set innerResult to Completion(Await(innerResult.[[Value]])). + // 5. If completion is a throw completion, return ? completion. + let Ok(Some(ret)) = iterator.get_method(js_string!("return"), context) else { + return Err(err); + }; + + let Ok(value) = ret.call(&iterator.into(), &[], context) else { + return Err(err); + }; + + state_machine.set(Some(AsyncIteratorStateMachine::AsyncIteratorCloseEnd { + err, + })); + return Ok(CoroutineState::Yielded(value)); + } + AsyncIteratorStateMachine::AsyncIteratorCloseEnd { err } => { + // Awaited `innerResult.[[Value]]`. + // Only need to return the original error. + return Err(err); + } + } + } + })(); + + // AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) + // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start + // -> + // AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) + // https://tc39.es/ecma262/#sec-asyncblockstart + match result { + Ok(cont) => cont, + + // i. Assert: result is a throw completion. + Err(err) => { + // ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »). + global_state + .resolvers + .reject + .call(&JsValue::undefined(), &[err.to_opaque(context)], context) + .expect("resolving functions cannot fail"); + CoroutineState::Done + } + } +} + +#[derive(Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] +#[allow(clippy::enum_variant_names)] +enum ArrayLikeStateMachine { + LoopStart { + array_like: JsObject, + a: JsObject, + len: u64, + k: u64, + }, + LoopContinue { + array_like: JsObject, + a: JsObject, + len: u64, + k: u64, + }, + LoopEnd { + array_like: JsObject, + a: JsObject, + len: u64, + k: u64, + mapped_value: Option, + }, +} + +/// Part of [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`][https://tc39.es/proposal-array-from-async/#sec-array.fromAsync]. +fn from_array_like( + mut result: JsResult, + (global_state, state_machine): &(GlobalState, Cell>), + context: &mut Context, +) -> CoroutineState { + let result: JsResult<_> = (|| { + let Some(mut sm) = state_machine.take() else { + return Ok(CoroutineState::Done); + }; + + loop { + match sm { + ArrayLikeStateMachine::LoopStart { + array_like, + a, + len, + k, + } => { + // vii. Repeat, while k < len, + if k >= len { + // viii. Perform ? Set(A, "length", 𝔽(len), true). + a.set(js_string!("length"), len, true, context)?; + + // ix. Return Completion Record { [[Type]]: return, [[Value]]: A, [[Target]]: empty }. + + // AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) + // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start + // -> + // AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) + // https://tc39.es/ecma262/#sec-asyncblockstart + + // g. Else if result is a return completion, then + // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »). + global_state + .resolvers + .resolve + .call(&JsValue::undefined(), &[a.into()], context) + .expect("resolving functions cannot fail"); + + return Ok(CoroutineState::Done); + } + + // 1. Let Pk be ! ToString(𝔽(k)). + // 2. Let kValue be ? Get(arrayLike, Pk). + let k_value = array_like.get(k, context)?; + state_machine.set(Some(ArrayLikeStateMachine::LoopContinue { + array_like, + a, + len, + k, + })); + + // 3. Set kValue to ? Await(kValue). + return Ok(CoroutineState::Yielded(k_value)); + } + ArrayLikeStateMachine::LoopContinue { + array_like, + a, + len, + k, + } => { + // Awaited kValue + let k_value = std::mem::replace(&mut result, Ok(JsValue::undefined()))?; + + // 4. If mapping is true, then + if let Some(mapfn) = &global_state.mapfn { + // a. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). + let mapped_value = + mapfn.call(&global_state.this_arg, &[k_value, k.into()], context)?; + state_machine.set(Some(ArrayLikeStateMachine::LoopEnd { + array_like, + a, + len, + k, + mapped_value: None, + })); + + // b. Set mappedValue to ? Await(mappedValue). + return Ok(CoroutineState::Yielded(mapped_value)); + } + // 5. Else, let mappedValue be kValue. + sm = ArrayLikeStateMachine::LoopEnd { + array_like, + a, + len, + k, + mapped_value: Some(k_value), + } + } + ArrayLikeStateMachine::LoopEnd { + array_like, + a, + len, + k, + mapped_value, + } => { + // Either awaited `mappedValue` or directly set this from `kValue`. + let result = std::mem::replace(&mut result, Ok(JsValue::undefined()))?; + let mapped_value = mapped_value.unwrap_or(result); + + // 6. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). + a.create_data_property_or_throw(k, mapped_value, context)?; + + // 7. Set k to k + 1. + sm = ArrayLikeStateMachine::LoopStart { + array_like, + a, + len, + k: k + 1, + } + } + } + } + })(); + + // AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) + // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start + // -> + // AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) + // https://tc39.es/ecma262/#sec-asyncblockstart + match result { + Ok(cont) => cont, + // i. Assert: result is a throw completion. + Err(err) => { + // ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »). + global_state + .resolvers + .reject + .call(&JsValue::undefined(), &[err.to_opaque(context)], context) + .expect("resolving functions cannot fail"); + CoroutineState::Done + } + } +} diff --git a/core/engine/src/builtins/array/mod.rs b/core/engine/src/builtins/array/mod.rs index 200ca0e137b..8754b1155b6 100644 --- a/core/engine/src/builtins/array/mod.rs +++ b/core/engine/src/builtins/array/mod.rs @@ -41,6 +41,9 @@ mod array_iterator; use crate::value::JsVariant; pub(crate) use array_iterator::ArrayIterator; +#[cfg(feature = "experimental")] +mod from_async; + #[cfg(test)] mod tests; @@ -665,440 +668,6 @@ impl Array { iterator_record.close(error, context) } - /// [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`][spec] - /// - /// The `Array.fromAsync()` static method creates a new, - /// shallow-copied Array instance from a list or iterator of Promise-like values. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/proposal-array-from-async/#sec-array.fromAsync - #[cfg(feature = "experimental")] - pub(crate) fn from_async( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { - use std::cell::Cell; - - use crate::builtins::iterable::IteratorRecord; - use crate::builtins::promise::ResolvingFunctions; - use crate::builtins::AsyncFromSyncIterator; - use crate::native_function::{ContinuationState, NativeContinuation}; - use crate::object::{JsFunction, JsPromise}; - use crate::JsError; - - #[derive(Trace, Finalize)] - struct GlobalState { - mapfn: Option, - this_arg: JsValue, - resolvers: ResolvingFunctions, - } - - #[derive(Trace, Finalize)] - #[boa_gc(unsafe_no_drop)] - enum IterableStateMachine { - LoopStart { - a: JsObject, - k: u64, - iterator_record: IteratorRecord, - }, - LoopContinue { - a: JsObject, - k: u64, - iterator_record: IteratorRecord, - }, - LoopEnd { - a: JsObject, - k: u64, - iterator_record: IteratorRecord, - mapped_value: Option>, - }, - AsyncIteratorCloseStart { - err: JsError, - iterator: JsObject, - }, - AsyncIteratorCloseEnd { - err: JsError, - }, - } - - fn from_iterable( - result: JsResult, - global_state: &GlobalState, - state_machine: &Cell>, - context: &mut Context, - ) -> JsResult { - let Some(mut sm) = state_machine.take() else { - return Ok(ContinuationState::Done); - }; - - loop { - match sm { - IterableStateMachine::LoopStart { - a, - k, - iterator_record, - } => { - if k >= 2u64.pow(53) - 1 { - sm = IterableStateMachine::AsyncIteratorCloseStart { - err: JsNativeError::typ() - .with_message( - "Array.fromAsync: \ - reached the maximum number of elements in an array \ - (2^53 - 1)", - ) - .into(), - iterator: iterator_record.iterator().clone(), - }; - } else { - let next_result = iterator_record.next_method().call( - &iterator_record.iterator().clone().into(), - &[], - context, - )?; - - state_machine.set(Some(IterableStateMachine::LoopContinue { - a, - k, - iterator_record, - })); - return Ok(ContinuationState::Yielded(next_result)); - } - } - IterableStateMachine::LoopContinue { - a, - k, - mut iterator_record, - } => { - iterator_record.update_result(result.clone()?, context)?; - - if iterator_record.done() { - a.set(js_string!("length"), k, true, context)?; - global_state - .resolvers - .resolve - .call(&JsValue::undefined(), &[a.into()], context) - .expect("resolving functions cannot fail"); - - return Ok(ContinuationState::Done); - } - - let next_value = iterator_record.value(context)?; - if let Some(mapfn) = &global_state.mapfn { - let mapped_value = match mapfn.call( - &global_state.this_arg, - &[next_value, k.into()], - context, - ) { - Ok(v) => v, - Err(err) => { - sm = IterableStateMachine::AsyncIteratorCloseStart { - err, - iterator: iterator_record.iterator().clone(), - }; - continue; - } - }; - state_machine.set(Some(IterableStateMachine::LoopEnd { - a, - k, - iterator_record, - mapped_value: None, - })); - return Ok(ContinuationState::Yielded(mapped_value)); - } - sm = IterableStateMachine::LoopEnd { - a, - k, - iterator_record, - mapped_value: Some(Ok(next_value)), - } - } - IterableStateMachine::LoopEnd { - a, - k, - iterator_record, - mapped_value, - } => { - let mapped_value = match mapped_value.unwrap_or(result.clone()) { - Ok(value) => value, - Err(err) => { - sm = IterableStateMachine::AsyncIteratorCloseStart { - err, - iterator: iterator_record.iterator().clone(), - }; - continue; - } - }; - - sm = if let Err(err) = - a.create_data_property_or_throw(k, mapped_value, context) - { - IterableStateMachine::AsyncIteratorCloseStart { - err, - iterator: iterator_record.iterator().clone(), - } - } else { - IterableStateMachine::LoopStart { - a, - k: k + 1, - iterator_record, - } - }; - } - IterableStateMachine::AsyncIteratorCloseStart { err, iterator } => { - let Ok(Some(ret)) = iterator.get_method(js_string!("return"), context) - else { - return Err(err); - }; - - let Ok(value) = ret.call(&iterator.into(), &[], context) else { - return Err(err); - }; - - state_machine - .set(Some(IterableStateMachine::AsyncIteratorCloseEnd { err })); - return Ok(ContinuationState::Yielded(value)); - } - IterableStateMachine::AsyncIteratorCloseEnd { err } => { - return Err(err); - } - } - } - } - - #[derive(Trace, Finalize)] - #[boa_gc(unsafe_no_drop)] - enum ArrayLikeStateMachine { - LoopStart { - array_like: JsObject, - a: JsObject, - len: u64, - k: u64, - }, - LoopContinue { - array_like: JsObject, - a: JsObject, - len: u64, - k: u64, - }, - LoopEnd { - array_like: JsObject, - a: JsObject, - len: u64, - k: u64, - mapped_value: Option, - }, - } - - fn from_array_like( - result: JsResult, - global_state: &GlobalState, - state_machine: &Cell>, - context: &mut Context, - ) -> JsResult { - let Some(mut sm) = state_machine.take() else { - return Ok(ContinuationState::Done); - }; - - loop { - match sm { - ArrayLikeStateMachine::LoopStart { - array_like, - a, - len, - k, - } => { - if k >= len { - a.set(js_string!("length"), len, true, context)?; - global_state - .resolvers - .resolve - .call(&JsValue::undefined(), &[a.into()], context) - .expect("resolving functions cannot fail"); - return Ok(ContinuationState::Done); - } - let k_value = array_like.get(k, context)?; - state_machine.set(Some(ArrayLikeStateMachine::LoopContinue { - array_like, - a, - len, - k, - })); - return Ok(ContinuationState::Yielded(k_value)); - } - ArrayLikeStateMachine::LoopContinue { - array_like, - a, - len, - k, - } => { - let k_value = result.clone()?; - if let Some(mapfn) = &global_state.mapfn { - let mapped_value = mapfn.call( - &global_state.this_arg, - &[k_value, k.into()], - context, - )?; - state_machine.set(Some(ArrayLikeStateMachine::LoopEnd { - array_like, - a, - len, - k, - mapped_value: None, - })); - return Ok(ContinuationState::Yielded(mapped_value)); - } - sm = ArrayLikeStateMachine::LoopEnd { - array_like, - a, - len, - k, - mapped_value: Some(k_value), - } - } - ArrayLikeStateMachine::LoopEnd { - array_like, - a, - len, - k, - mapped_value, - } => { - let mapped_value = mapped_value.unwrap_or(result.clone()?); - a.create_data_property_or_throw(k, mapped_value, context)?; - sm = ArrayLikeStateMachine::LoopStart { - array_like, - a, - len, - k: k + 1, - } - } - } - } - } - - let (promise, resolvers) = JsPromise::new_pending(context); - - let async_items = args.get_or_undefined(0); - let mapfn = args.get_or_undefined(1); - let this_arg = args.get_or_undefined(2).clone(); - - let result: JsResult<()> = (|| { - let mapfn = if mapfn.is_undefined() { - None - } else { - let Some(callable) = mapfn.as_callable().cloned() else { - return Err(JsNativeError::typ() - .with_message("Array.fromAsync: mapping function must be callable") - .into()); - }; - Some(JsFunction::from_object_unchecked(callable)) - }; - - let iterator_record = if let Some(method) = - async_items.get_method(JsSymbol::async_iterator(), context)? - { - async_items.get_iterator_from_method(&method, context)? - } else if let Some(method) = async_items.get_method(JsSymbol::iterator(), context)? { - AsyncFromSyncIterator::create( - async_items.get_iterator_from_method(&method, context)?, - context, - ) - } else { - let array_like = async_items.to_object(context)?; - let len = array_like.length_of_array_like(context)?; - let a = if let Some(c) = this.as_constructor() { - c.construct(&[len.into()], None, context)? - } else { - Array::array_create(len, None, context)? - }; - let continuation = NativeContinuation::from_copy_closure_with_captures( - |result, (gs, sm), context| match from_array_like(result, gs, sm, context) { - Ok(cont) => cont, - Err(err) => { - gs.resolvers - .reject - .call(&JsValue::undefined(), &[err.to_opaque(context)], context) - .expect("resolving functions cannot fail"); - ContinuationState::Done - } - }, - ( - GlobalState { - mapfn, - this_arg, - resolvers: resolvers.clone(), - }, - Cell::new(Some(ArrayLikeStateMachine::LoopStart { - array_like, - a, - len, - k: 0, - })), - ), - ); - - if let ContinuationState::Yielded(value) = - continuation.call(Ok(JsValue::undefined()), context) - { - JsPromise::resolve(value, context).await_native(continuation, context); - } - - return Ok(()); - }; - - let a = if let Some(c) = this.as_constructor() { - c.construct(&[], None, context)? - } else { - Array::array_create(0, None, context)? - }; - - let continuation = NativeContinuation::from_copy_closure_with_captures( - |result, (gs, sm), context| match from_iterable(result, gs, sm, context) { - Ok(cont) => cont, - Err(err) => { - gs.resolvers - .reject - .call(&JsValue::undefined(), &[err.to_opaque(context)], context) - .expect("resolving functions cannot fail"); - ContinuationState::Done - } - }, - ( - GlobalState { - mapfn, - this_arg, - resolvers: resolvers.clone(), - }, - Cell::new(Some(IterableStateMachine::LoopStart { - a, - k: 0, - iterator_record, - })), - ), - ); - - if let ContinuationState::Yielded(value) = - continuation.call(Ok(JsValue::undefined()), context) - { - JsPromise::resolve(value, context).await_native(continuation, context); - } - - Ok(()) - })(); - - if let Err(err) = result { - resolvers - .reject - .call(&JsValue::undefined(), &[err.to_opaque(context)], context) - .expect("resolving functions cannot fail"); - } - - return Ok(promise.into()); - } - /// `Array.isArray( arg )` /// /// The isArray function takes one argument arg, and returns the Boolean value true diff --git a/core/engine/src/native_function/continuation.rs b/core/engine/src/native_function/continuation.rs index 96d3dedabec..8f671abc0f2 100644 --- a/core/engine/src/native_function/continuation.rs +++ b/core/engine/src/native_function/continuation.rs @@ -4,22 +4,22 @@ use crate::{Context, JsResult, JsValue}; #[derive(Trace, Finalize)] #[boa_gc(unsafe_no_drop)] -pub(crate) enum ContinuationState { +pub(crate) enum CoroutineState { Yielded(JsValue), Done, } -trait TraceableContinuation: Trace { - fn call(&self, value: JsResult, context: &mut Context) -> ContinuationState; +trait TraceableCoroutine: Trace { + fn call(&self, value: JsResult, context: &mut Context) -> CoroutineState; } #[derive(Trace, Finalize)] -struct Continuation +struct Coroutine where - F: Fn(JsResult, &T, &mut Context) -> ContinuationState, + F: Fn(JsResult, &T, &mut Context) -> CoroutineState, T: Trace, { - // SAFETY: `NativeContinuation`'s safe API ensures only `Copy` closures are stored; its unsafe API, + // SAFETY: `NativeCoroutine`'s safe API ensures only `Copy` closures are stored; its unsafe API, // on the other hand, explains the invariants to hold in order for this to be safe, shifting // the responsibility to the caller. #[unsafe_ignore_trace] @@ -27,48 +27,48 @@ where captures: T, } -impl TraceableContinuation for Continuation +impl TraceableCoroutine for Coroutine where - F: Fn(JsResult, &T, &mut Context) -> ContinuationState, + F: Fn(JsResult, &T, &mut Context) -> CoroutineState, T: Trace, { - fn call(&self, result: JsResult, context: &mut Context) -> ContinuationState { + fn call(&self, result: JsResult, context: &mut Context) -> CoroutineState { (self.f)(result, &self.captures, context) } } -/// A callable Rust continuation that can be used to await promises. +/// A callable Rust coroutine that can be used to await promises. /// /// # Caveats /// /// By limitations of the Rust language, the garbage collector currently cannot inspect closures /// in order to trace their captured variables. This means that only [`Copy`] closures are 100% safe -/// to use. All other closures can also be stored in a `NativeContinuation`, albeit by using an `unsafe` +/// to use. All other closures can also be stored in a `NativeCoroutine`, albeit by using an `unsafe` /// API, but note that passing closures implicitly capturing traceable types could cause /// **Undefined Behaviour**. #[derive(Clone, Trace, Finalize)] -pub(crate) struct NativeContinuation { - inner: Gc, +pub(crate) struct NativeCoroutine { + inner: Gc, } -impl std::fmt::Debug for NativeContinuation { +impl std::fmt::Debug for NativeCoroutine { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NativeContinuation").finish_non_exhaustive() + f.debug_struct("NativeCoroutine").finish_non_exhaustive() } } -impl NativeContinuation { - /// Creates a `NativeFunction` from a `Copy` closure and a list of traceable captures. +impl NativeCoroutine { + /// Creates a `NativeCoroutine` from a `Copy` closure and a list of traceable captures. pub(crate) fn from_copy_closure_with_captures(closure: F, captures: T) -> Self where - F: Fn(JsResult, &T, &mut Context) -> ContinuationState + Copy + 'static, + F: Fn(JsResult, &T, &mut Context) -> CoroutineState + Copy + 'static, T: Trace + 'static, { // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. unsafe { Self::from_closure_with_captures(closure, captures) } } - /// Create a new `NativeFunction` from a closure and a list of traceable captures. + /// Create a new `NativeCoroutine` from a closure and a list of traceable captures. /// /// # Safety /// @@ -78,12 +78,12 @@ impl NativeContinuation { /// on why that is the case. pub(crate) unsafe fn from_closure_with_captures(closure: F, captures: T) -> Self where - F: Fn(JsResult, &T, &mut Context) -> ContinuationState + 'static, + F: Fn(JsResult, &T, &mut Context) -> CoroutineState + 'static, T: Trace + 'static, { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the // future: https://github.com/rust-lang/rust/issues/18598 - let ptr = Gc::into_raw(Gc::new(Continuation { + let ptr = Gc::into_raw(Gc::new(Coroutine { f: closure, captures, })); @@ -96,13 +96,9 @@ impl NativeContinuation { } } - /// Calls this `NativeFunction`, forwarding the arguments to the corresponding function. + /// Calls this `NativeCoroutine`, forwarding the arguments to the corresponding function. #[inline] - pub(crate) fn call( - &self, - result: JsResult, - context: &mut Context, - ) -> ContinuationState { + pub(crate) fn call(&self, result: JsResult, context: &mut Context) -> CoroutineState { self.inner.call(result, context) } } diff --git a/core/engine/src/native_function/mod.rs b/core/engine/src/native_function/mod.rs index 2e05ca40dd3..c2dd986e4c1 100644 --- a/core/engine/src/native_function/mod.rs +++ b/core/engine/src/native_function/mod.rs @@ -24,7 +24,7 @@ use crate::{ mod continuation; #[cfg(feature = "experimental")] -pub(crate) use continuation::{ContinuationState, NativeContinuation}; +pub(crate) use continuation::{CoroutineState, NativeCoroutine}; /// The required signature for all native built-in function pointers. /// diff --git a/core/engine/src/object/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index 9aa099cda50..73990a10e1e 100644 --- a/core/engine/src/object/builtins/jspromise.rs +++ b/core/engine/src/object/builtins/jspromise.rs @@ -1158,7 +1158,7 @@ impl JsPromise { #[cfg(feature = "experimental")] pub(crate) fn await_native( &self, - continuation: crate::native_function::NativeContinuation, + continuation: crate::native_function::NativeCoroutine, context: &mut Context, ) { use crate::builtins::async_generator::AsyncGenerator; @@ -1192,7 +1192,7 @@ impl JsPromise { let frame = gen.call_frame.take().expect("should have a call frame"); context.vm.push_frame(frame); - if let crate::native_function::ContinuationState::Yielded(value) = + if let crate::native_function::CoroutineState::Yielded(value) = continuation.call(Ok(args.get_or_undefined(0).clone()), context) { JsPromise::resolve(value, context) @@ -1243,7 +1243,7 @@ impl JsPromise { let frame = gen.call_frame.take().expect("should have a call frame"); context.vm.push_frame(frame); - if let crate::native_function::ContinuationState::Yielded(value) = continuation + if let crate::native_function::CoroutineState::Yielded(value) = continuation .call( Err(JsError::from_opaque(args.get_or_undefined(0).clone())), context, From 57690587a68040934ded655e2043e26a1c3c2cae Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Thu, 9 Jan 2025 02:16:28 -0600 Subject: [PATCH 4/6] cargo clippy fix --- core/engine/src/object/builtins/jspromise.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/engine/src/object/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index 73990a10e1e..5a3714fdaee 100644 --- a/core/engine/src/object/builtins/jspromise.rs +++ b/core/engine/src/object/builtins/jspromise.rs @@ -1,17 +1,15 @@ //! A Rust API wrapper for Boa's promise Builtin ECMAScript Object -use std::{cell::Cell, future::Future, pin::Pin, task}; +use std::{future::Future, pin::Pin, task}; use super::{JsArray, JsFunction}; use crate::{ builtins::{ - generator::GeneratorContext, promise::{PromiseState, ResolvingFunctions}, Promise, }, job::NativeJob, - js_string, - object::{FunctionObjectBuilder, JsObject}, + object::JsObject, value::TryFromJs, Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction, }; @@ -1161,7 +1159,12 @@ impl JsPromise { continuation: crate::native_function::NativeCoroutine, context: &mut Context, ) { - use crate::builtins::async_generator::AsyncGenerator; + use crate::{ + builtins::{async_generator::AsyncGenerator, generator::GeneratorContext}, + js_string, + object::FunctionObjectBuilder, + }; + use std::cell::Cell; let mut frame = context.vm.frame().clone(); frame.environments = context.vm.environments.clone(); From 278271b378e8fc23bc376d594da48970310ebcb3 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Thu, 9 Jan 2025 02:22:16 -0600 Subject: [PATCH 5/6] fix hyperlinks --- core/engine/src/builtins/array/from_async.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/engine/src/builtins/array/from_async.rs b/core/engine/src/builtins/array/from_async.rs index f149db9de9b..0110bb5bfc3 100644 --- a/core/engine/src/builtins/array/from_async.rs +++ b/core/engine/src/builtins/array/from_async.rs @@ -228,7 +228,7 @@ enum AsyncIteratorStateMachine { }, } -/// Part of [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`][https://tc39.es/proposal-array-from-async/#sec-array.fromAsync]. +/// Part of [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`][]. fn from_async_iterator( mut result: JsResult, (global_state, state_machine): &(GlobalState, Cell>), @@ -485,7 +485,7 @@ enum ArrayLikeStateMachine { }, } -/// Part of [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`][https://tc39.es/proposal-array-from-async/#sec-array.fromAsync]. +/// Part of [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`][]. fn from_array_like( mut result: JsResult, (global_state, state_machine): &(GlobalState, Cell>), From 5375b50e4e7d22b4bf645f689b6242a3df711c55 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Fri, 10 Jan 2025 01:12:02 -0600 Subject: [PATCH 6/6] Fix typo --- core/engine/src/vm/call_frame/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/engine/src/vm/call_frame/mod.rs b/core/engine/src/vm/call_frame/mod.rs index 6d25d1a4661..eabc75d5176 100644 --- a/core/engine/src/vm/call_frame/mod.rs +++ b/core/engine/src/vm/call_frame/mod.rs @@ -154,16 +154,15 @@ impl CallFrame { environments: EnvironmentStack, realm: Realm, ) -> Self { - let local_binings_initialized = code_block.local_bindings_initialized.clone(); Self { - code_block, pc: 0, rp: 0, env_fp: 0, argument_count: 0, iterators: ThinVec::new(), binding_stack: Vec::new(), - local_bindings_initialized: local_binings_initialized, + local_bindings_initialized: code_block.local_bindings_initialized.clone(), + code_block, loop_iteration_count: 0, active_runnable, environments,