mirror of https://github.com/boa-dev/boa.git
Browse Source
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 functionpull/2286/head
raskad
2 years ago
7 changed files with 520 additions and 6 deletions
@ -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()) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue