Browse Source

Implement Async-from-Sync Iterator Objects (#2234)

This Pull Request changes the following:

- Implement [Async-from-Sync Iterator Objects](https://tc39.es/ecma262/#sec-async-from-sync-iterator-objects)
- Give the proper `async` hint to `GetIterator` when executing a delegate yield expression in an async generator function
pull/2286/head
raskad 2 years ago
parent
commit
09e35a85a2
  1. 440
      boa_engine/src/builtins/iterable/async_from_sync_iterator.rs
  2. 25
      boa_engine/src/builtins/iterable/mod.rs
  3. 6
      boa_engine/src/bytecompiler/mod.rs
  4. 36
      boa_engine/src/object/mod.rs
  5. 1
      boa_engine/src/vm/code_block.rs
  6. 9
      boa_engine/src/vm/mod.rs
  7. 9
      boa_engine/src/vm/opcode.rs

440
boa_engine/src/builtins/iterable/async_from_sync_iterator.rs

@ -0,0 +1,440 @@
use crate::{
builtins::{
iterable::{create_iter_result_object, IteratorRecord, IteratorResult},
promise::{if_abrupt_reject_promise, PromiseCapability},
JsArgs, Promise,
},
object::{FunctionBuilder, JsObject, ObjectData},
property::PropertyDescriptor,
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
/// Create the `%AsyncFromSyncIteratorPrototype%` object.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%-object
pub(crate) fn create_async_from_sync_iterator_prototype(context: &mut Context) -> JsObject {
let _timer = Profiler::global().start_event("AsyncFromSyncIteratorPrototype", "init");
let prototype = JsObject::from_proto_and_data(
context
.intrinsics()
.objects()
.iterator_prototypes()
.async_iterator_prototype(),
ObjectData::ordinary(),
);
let next_function = FunctionBuilder::native(context, AsyncFromSyncIterator::next)
.name("next")
.length(1)
.build();
let return_function = FunctionBuilder::native(context, AsyncFromSyncIterator::r#return)
.name("return")
.length(1)
.build();
let throw_function = FunctionBuilder::native(context, AsyncFromSyncIterator::throw)
.name("throw")
.length(1)
.build();
{
let mut prototype_mut = prototype.borrow_mut();
prototype_mut.insert(
"next",
PropertyDescriptor::builder()
.value(next_function)
.writable(true)
.enumerable(false)
.configurable(true),
);
prototype_mut.insert(
"return",
PropertyDescriptor::builder()
.value(return_function)
.writable(true)
.enumerable(false)
.configurable(true),
);
prototype_mut.insert(
"throw",
PropertyDescriptor::builder()
.value(throw_function)
.writable(true)
.enumerable(false)
.configurable(true),
);
}
prototype
}
/// The internal data for `%AsyncFromSyncIterator%` objects.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-properties-of-async-from-sync-iterator-instances
#[derive(Clone, Debug, Finalize, Trace)]
pub struct AsyncFromSyncIterator {
// The [[SyncIteratorRecord]] internal slot.
sync_iterator_record: IteratorRecord,
}
impl AsyncFromSyncIterator {
/// `CreateAsyncFromSyncIterator ( syncIteratorRecord )`
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createasyncfromsynciterator
pub(crate) fn create(
sync_iterator_record: IteratorRecord,
context: &mut Context,
) -> IteratorRecord {
// 1. Let asyncIterator be OrdinaryObjectCreate(%AsyncFromSyncIteratorPrototype%, « [[SyncIteratorRecord]] »).
// 2. Set asyncIterator.[[SyncIteratorRecord]] to syncIteratorRecord.
let async_iterator = JsObject::from_proto_and_data(
context
.intrinsics()
.objects()
.iterator_prototypes()
.async_from_sync_iterator_prototype(),
ObjectData::async_from_sync_iterator(Self {
sync_iterator_record,
}),
);
// 3. Let nextMethod be ! Get(asyncIterator, "next").
let next_method = async_iterator
.get("next", context)
.expect("async from sync iterator prototype must have next method");
// 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: asyncIterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
// 5. Return iteratorRecord.
IteratorRecord::new(async_iterator, next_method, false)
}
/// `%AsyncFromSyncIteratorPrototype%.next ( [ value ] )`
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.next
fn next(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Assert: O is an Object that has a [[SyncIteratorRecord]] internal slot.
// 4. Let syncIteratorRecord be O.[[SyncIteratorRecord]].
let sync_iterator_record = this
.as_object()
.expect("async from sync iterator prototype must be object")
.borrow()
.as_async_from_sync_iterator()
.expect("async from sync iterator prototype must be object")
.sync_iterator_record
.clone();
// 3. Let promiseCapability be ! NewPromiseCapability(%Promise%).
let promise_capability = PromiseCapability::new(
&context
.intrinsics()
.constructors()
.promise()
.constructor()
.into(),
context,
)
.expect("cannot fail with promise constructor");
// 5. If value is present, then
// a. Let result be Completion(IteratorNext(syncIteratorRecord, value)).
// 6. Else,
// a. Let result be Completion(IteratorNext(syncIteratorRecord)).
let result = sync_iterator_record.next(args.get(0).cloned(), context);
// 7. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context);
// 8. Return AsyncFromSyncIteratorContinuation(result, promiseCapability).
Self::continuation(&result, &promise_capability, context)
}
/// `%AsyncFromSyncIteratorPrototype%.return ( [ value ] )`
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.return
fn r#return(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Assert: O is an Object that has a [[SyncIteratorRecord]] internal slot.
// 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]].
let sync_iterator = this
.as_object()
.expect("async from sync iterator prototype must be object")
.borrow()
.as_async_from_sync_iterator()
.expect("async from sync iterator prototype must be object")
.sync_iterator_record
.iterator()
.clone();
// 3. Let promiseCapability be ! NewPromiseCapability(%Promise%).
let promise_capability = PromiseCapability::new(
&context
.intrinsics()
.constructors()
.promise()
.constructor()
.into(),
context,
)
.expect("cannot fail with promise constructor");
// 5. Let return be Completion(GetMethod(syncIterator, "return")).
let r#return = sync_iterator.get_method("return", context);
// 6. IfAbruptRejectPromise(return, promiseCapability).
if_abrupt_reject_promise!(r#return, promise_capability, context);
let result = match (r#return, args.get(0)) {
// 7. If return is undefined, then
(None, _) => {
// a. Let iterResult be CreateIterResultObject(value, true).
let iter_result =
create_iter_result_object(args.get_or_undefined(0).clone(), true, context);
// b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iterResult »).
promise_capability
.resolve()
.call(&JsValue::Undefined, &[iter_result], context)
.expect("cannot fail according to spec");
// c. Return promiseCapability.[[Promise]].
return Ok(promise_capability.promise().clone().into());
}
// 8. If value is present, then
(Some(r#return), Some(value)) => {
// a. Let result be Completion(Call(return, syncIterator, « value »)).
r#return.call(&sync_iterator.clone().into(), &[value.clone()], context)
}
// 9. Else,
(Some(r#return), None) => {
// a. Let result be Completion(Call(return, syncIterator)).
r#return.call(&sync_iterator.clone().into(), &[], context)
}
};
// 10. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context);
// 11. If Type(result) is not Object, then
let result =
if let Some(result) = result.as_object() {
result
} else {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
promise_capability
.reject()
.call(
&JsValue::Undefined,
&[context
.construct_type_error("iterator return function returned non-object")],
context,
)
.expect("cannot fail according to spec");
// b. Return promiseCapability.[[Promise]].
return Ok(promise_capability.promise().clone().into());
};
// 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability).
Self::continuation(
&IteratorResult {
object: result.clone(),
},
&promise_capability,
context,
)
}
/// `%AsyncFromSyncIteratorPrototype%.throw ( [ value ] )`
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.throw
fn throw(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Assert: O is an Object that has a [[SyncIteratorRecord]] internal slot.
// 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]].
let sync_iterator = this
.as_object()
.expect("async from sync iterator prototype must be object")
.borrow()
.as_async_from_sync_iterator()
.expect("async from sync iterator prototype must be object")
.sync_iterator_record
.iterator()
.clone();
// 3. Let promiseCapability be ! NewPromiseCapability(%Promise%).
let promise_capability = PromiseCapability::new(
&context
.intrinsics()
.constructors()
.promise()
.constructor()
.into(),
context,
)
.expect("cannot fail with promise constructor");
// 5. Let throw be Completion(GetMethod(syncIterator, "throw")).
let throw = sync_iterator.get_method("throw", context);
// 6. IfAbruptRejectPromise(throw, promiseCapability).
if_abrupt_reject_promise!(throw, promise_capability, context);
let result = match (throw, args.get(0)) {
// 7. If throw is undefined, then
(None, _) => {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »).
promise_capability
.reject()
.call(
&JsValue::Undefined,
&[args.get_or_undefined(0).clone()],
context,
)
.expect("cannot fail according to spec");
// b. Return promiseCapability.[[Promise]].
return Ok(promise_capability.promise().clone().into());
}
// 8. If value is present, then
(Some(throw), Some(value)) => {
// a. Let result be Completion(Call(throw, syncIterator, « value »)).
throw.call(&sync_iterator.clone().into(), &[value.clone()], context)
}
// 9. Else,
(Some(throw), None) => {
// a. Let result be Completion(Call(throw, syncIterator)).
throw.call(&sync_iterator.clone().into(), &[], context)
}
};
// 10. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context);
// 11. If Type(result) is not Object, then
let result = if let Some(result) = result.as_object() {
result
} else {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
promise_capability
.reject()
.call(
&JsValue::Undefined,
&[context.construct_type_error("iterator throw function returned non-object")],
context,
)
.expect("cannot fail according to spec");
// b. Return promiseCapability.[[Promise]].
return Ok(promise_capability.promise().clone().into());
};
// 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability).
Self::continuation(
&IteratorResult {
object: result.clone(),
},
&promise_capability,
context,
)
}
/// `AsyncFromSyncIteratorContinuation ( result, promiseCapability )`
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-asyncfromsynciteratorcontinuation
fn continuation(
result: &IteratorResult,
promise_capability: &PromiseCapability,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. NOTE: Because promiseCapability is derived from the intrinsic %Promise%,
// the calls to promiseCapability.[[Reject]] entailed by the
// use IfAbruptRejectPromise below are guaranteed not to throw.
// 2. Let done be Completion(IteratorComplete(result)).
let done = result.complete(context);
// 3. IfAbruptRejectPromise(done, promiseCapability).
if_abrupt_reject_promise!(done, promise_capability, context);
// 4. Let value be Completion(IteratorValue(result)).
let value = result.value(context);
// 5. IfAbruptRejectPromise(value, promiseCapability).
if_abrupt_reject_promise!(value, promise_capability, context);
// 6. Let valueWrapper be Completion(PromiseResolve(%Promise%, value)).
let value_wrapper = Promise::promise_resolve(
context.intrinsics().constructors().promise().constructor(),
value,
context,
);
// 7. IfAbruptRejectPromise(valueWrapper, promiseCapability).
if_abrupt_reject_promise!(value_wrapper, promise_capability, context);
// 8. Let unwrap be a new Abstract Closure with parameters (value)
// that captures done and performs the following steps when called:
// 9. Let onFulfilled be CreateBuiltinFunction(unwrap, 1, "", « »).
let on_fulfilled = FunctionBuilder::closure_with_captures(
context,
|_this, args, done, context| {
// a. Return CreateIterResultObject(value, done).
Ok(create_iter_result_object(
args.get_or_undefined(0).clone(),
*done,
context,
))
},
done,
)
.name("")
.length(1)
.build();
// 10. NOTE: onFulfilled is used when processing the "value" property of an
// IteratorResult object in order to wait for its value if it is a promise and
// re-package the result in a new "unwrapped" IteratorResult object.
// 11. Perform PerformPromiseThen(valueWrapper, onFulfilled, undefined, promiseCapability).
value_wrapper
.as_object()
.expect("result of promise resolve must be promise")
.borrow_mut()
.as_promise_mut()
.expect("constructed promise must be a promise")
.perform_promise_then(
&on_fulfilled.into(),
&JsValue::Undefined,
Some(promise_capability.clone()),
context,
);
// 12. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise().clone().into())
}
}

25
boa_engine/src/builtins/iterable/mod.rs

@ -1,3 +1,5 @@
mod async_from_sync_iterator;
use crate::{
builtins::{
regexp::regexp_string_iterator::RegExpStringIterator,
@ -8,14 +10,20 @@ use crate::{
symbol::WellKnownSymbols,
Context, JsResult, JsValue,
};
use async_from_sync_iterator::create_async_from_sync_iterator_prototype;
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
pub(crate) use async_from_sync_iterator::AsyncFromSyncIterator;
#[derive(Debug, Default)]
pub struct IteratorPrototypes {
/// %IteratorPrototype%
iterator_prototype: JsObject,
/// %AsyncIteratorPrototype%
async_iterator_prototype: JsObject,
/// %AsyncFromSyncIteratorPrototype%
async_from_sync_iterator_prototype: JsObject,
/// %MapIteratorPrototype%
array_iterator: JsObject,
/// %SetIteratorPrototype%
@ -36,6 +44,7 @@ impl IteratorPrototypes {
let iterator_prototype = create_iterator_prototype(context);
let async_iterator_prototype = create_async_iterator_prototype(context);
let async_from_sync_iterator_prototype = create_async_from_sync_iterator_prototype(context);
Self {
array_iterator: ArrayIterator::create_prototype(iterator_prototype.clone(), context),
set_iterator: SetIterator::create_prototype(iterator_prototype.clone(), context),
@ -48,6 +57,7 @@ impl IteratorPrototypes {
for_in_iterator: ForInIterator::create_prototype(iterator_prototype.clone(), context),
iterator_prototype,
async_iterator_prototype,
async_from_sync_iterator_prototype,
}
}
@ -66,6 +76,11 @@ impl IteratorPrototypes {
self.async_iterator_prototype.clone()
}
#[inline]
pub fn async_from_sync_iterator_prototype(&self) -> JsObject {
self.async_from_sync_iterator_prototype.clone()
}
#[inline]
pub fn set_iterator(&self) -> JsObject {
self.set_iterator.clone()
@ -154,11 +169,13 @@ impl JsValue {
let sync_method = self
.get_method(WellKnownSymbols::iterator(), context)?
.map_or(Self::Undefined, Self::from);
// 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod).
let _sync_iterator_record =
self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method));
let sync_iterator_record =
self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method))?;
// 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord).
todo!("CreateAsyncFromSyncIterator");
return Ok(AsyncFromSyncIterator::create(sync_iterator_record, context));
}
} else {
// b. Otherwise, set method to ? GetMethod(obj, @@iterator).
@ -257,7 +274,7 @@ impl IteratorResult {
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iterator-records
#[derive(Debug)]
#[derive(Clone, Debug, Finalize, Trace)]
pub struct IteratorRecord {
/// `[[Iterator]]`
///

6
boa_engine/src/bytecompiler/mod.rs

@ -1153,7 +1153,11 @@ impl<'b> ByteCompiler<'b> {
}
if r#yield.delegate() {
self.emit_opcode(Opcode::InitIterator);
if self.in_async_generator {
self.emit_opcode(Opcode::InitIteratorAsync);
} else {
self.emit_opcode(Opcode::InitIterator);
}
self.emit_opcode(Opcode::PushUndefined);
let start_address = self.next_opcode_location();
let start = self.emit_opcode_with_operand(Opcode::GeneratorNextDelegate);

36
boa_engine/src/object/mod.rs

@ -33,6 +33,7 @@ use crate::{
NativeFunctionSignature,
},
generator::Generator,
iterable::AsyncFromSyncIterator,
map::map_iterator::MapIterator,
map::ordered_map::OrderedMap,
object::for_in_iterator::ForInIterator,
@ -165,6 +166,7 @@ pub struct ObjectData {
/// Defines the different types of objects.
#[derive(Debug, Finalize)]
pub enum ObjectKind {
AsyncFromSyncIterator(AsyncFromSyncIterator),
AsyncGenerator(AsyncGenerator),
AsyncGeneratorFunction(Function),
Array,
@ -204,6 +206,7 @@ pub enum ObjectKind {
unsafe impl Trace for ObjectKind {
custom_trace! {this, {
match this {
Self::AsyncFromSyncIterator(a) => mark(a),
Self::ArrayIterator(i) => mark(i),
Self::ArrayBuffer(b) => mark(b),
Self::Map(m) => mark(m),
@ -241,6 +244,14 @@ unsafe impl Trace for ObjectKind {
}
impl ObjectData {
/// Create the `AsyncFromSyncIterator` object data
pub fn async_from_sync_iterator(async_from_sync_iterator: AsyncFromSyncIterator) -> Self {
Self {
kind: ObjectKind::AsyncFromSyncIterator(async_from_sync_iterator),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `AsyncGenerator` object data
pub fn async_generator(async_generator: AsyncGenerator) -> Self {
Self {
@ -536,6 +547,7 @@ impl ObjectData {
impl Display for ObjectKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::AsyncFromSyncIterator(_) => "AsyncFromSyncIterator",
Self::AsyncGenerator(_) => "AsyncGenerator",
Self::AsyncGeneratorFunction(_) => "AsyncGeneratorFunction",
Self::Array => "Array",
@ -603,6 +615,30 @@ impl Object {
&self.data.kind
}
/// Checks if it's an `AsyncFromSyncIterator` object.
#[inline]
pub fn is_async_from_sync_iterator(&self) -> bool {
matches!(
self.data,
ObjectData {
kind: ObjectKind::AsyncFromSyncIterator(_),
..
}
)
}
/// Returns a reference to the `AsyncFromSyncIterator` data on the object.
#[inline]
pub fn as_async_from_sync_iterator(&self) -> Option<&AsyncFromSyncIterator> {
match self.data {
ObjectData {
kind: ObjectKind::AsyncFromSyncIterator(ref async_from_sync_iterator),
..
} => Some(async_from_sync_iterator),
_ => None,
}
}
/// Checks if it's an `AsyncGenerator` object.
#[inline]
pub fn is_async_generator(&self) -> bool {

1
boa_engine/src/vm/code_block.rs

@ -350,6 +350,7 @@ impl CodeBlock {
| Opcode::LoopContinue
| Opcode::LoopEnd
| Opcode::InitIterator
| Opcode::InitIteratorAsync
| Opcode::IteratorNext
| Opcode::IteratorClose
| Opcode::IteratorToArray

9
boa_engine/src/vm/mod.rs

@ -6,7 +6,7 @@ use crate::{
builtins::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
function::{ConstructorKind, Function},
iterable::IteratorRecord,
iterable::{IteratorHint, IteratorRecord},
Array, ForInIterator, JsArgs, Number, Promise,
},
environments::EnvironmentSlots,
@ -2028,6 +2028,13 @@ impl Context {
self.vm.push(iterator.next_method().clone());
self.vm.push(iterator.done());
}
Opcode::InitIteratorAsync => {
let object = self.vm.pop();
let iterator = object.get_iterator(self, Some(IteratorHint::Async), None)?;
self.vm.push(iterator.iterator().clone());
self.vm.push(iterator.next_method().clone());
self.vm.push(iterator.done());
}
Opcode::IteratorNext => {
let done = self
.vm

9
boa_engine/src/vm/opcode.rs

@ -1048,6 +1048,13 @@ pub enum Opcode {
/// Stack: object **=>** iterator, next_method, done
InitIterator,
/// Initialize an async iterator.
///
/// Operands:
///
/// Stack: object **=>** iterator, next_method, done
InitIteratorAsync,
/// Advance the iterator by one and put the value on the stack.
///
/// Operands:
@ -1325,6 +1332,7 @@ impl Opcode {
Self::LoopEnd => "LoopEnd",
Self::ForInLoopInitIterator => "ForInLoopInitIterator",
Self::InitIterator => "InitIterator",
Self::InitIteratorAsync => "InitIteratorAsync",
Self::IteratorNext => "IteratorNext",
Self::IteratorClose => "IteratorClose",
Self::IteratorToArray => "IteratorToArray",
@ -1468,6 +1476,7 @@ impl Opcode {
Self::LoopEnd => "INST - LoopEnd",
Self::ForInLoopInitIterator => "INST - ForInLoopInitIterator",
Self::InitIterator => "INST - InitIterator",
Self::InitIteratorAsync => "INST - InitIteratorAsync",
Self::IteratorNext => "INST - IteratorNext",
Self::IteratorClose => "INST - IteratorClose",
Self::IteratorToArray => "INST - IteratorToArray",

Loading…
Cancel
Save