mirror of https://github.com/boa-dev/boa.git
Browse Source
* Close active iterators when returning from async generator * Fix remaining async generator tests * Fix remaining async generator tests * Replace some reserved opcodes * Fix for await of loop flag * Add tests for fix * Remove whitespace * Fix test display and improve test * Change `usize` to `u32`pull/2996/head
José Julián Espina
1 year ago
committed by
GitHub
40 changed files with 1460 additions and 1193 deletions
@ -0,0 +1,222 @@ |
|||||||
|
use indoc::indoc; |
||||||
|
|
||||||
|
use crate::{run_test_actions, TestAction}; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn iterator_close_in_continue_before_jobs() { |
||||||
|
run_test_actions([ |
||||||
|
TestAction::run_harness(), |
||||||
|
TestAction::run(indoc! {r#" |
||||||
|
var actual = []; |
||||||
|
|
||||||
|
var iter = { |
||||||
|
[Symbol.iterator]() { |
||||||
|
return this; |
||||||
|
}, |
||||||
|
next() { |
||||||
|
actual.push("call next"); |
||||||
|
return { |
||||||
|
done: false, |
||||||
|
}; |
||||||
|
}, |
||||||
|
get return() { |
||||||
|
actual.push("get return"); |
||||||
|
return function () { |
||||||
|
actual.push("return call"); |
||||||
|
return { |
||||||
|
done: true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Promise.resolve(0) |
||||||
|
.then(() => actual.push("tick 1")) |
||||||
|
.then(() => actual.push("tick 2")); |
||||||
|
|
||||||
|
void async function f() { |
||||||
|
actual.push("async fn start"); |
||||||
|
let count = 0; |
||||||
|
loop: while (count === 0) { |
||||||
|
count++; |
||||||
|
for (_ of iter) { |
||||||
|
continue loop; |
||||||
|
} |
||||||
|
} |
||||||
|
actual.push("async fn end"); |
||||||
|
}(); |
||||||
|
"#}), |
||||||
|
#[allow(clippy::redundant_closure_for_method_calls)] |
||||||
|
TestAction::inspect_context(|ctx| ctx.run_jobs()), |
||||||
|
TestAction::assert(indoc! {r#" |
||||||
|
arrayEquals( |
||||||
|
actual, |
||||||
|
[ |
||||||
|
"async fn start", |
||||||
|
"call next", |
||||||
|
"get return", |
||||||
|
"return call", |
||||||
|
"async fn end", |
||||||
|
"tick 1", |
||||||
|
"tick 2", |
||||||
|
] |
||||||
|
) |
||||||
|
"#}), |
||||||
|
]); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn async_iterator_close_in_continue_is_awaited() { |
||||||
|
run_test_actions([ |
||||||
|
TestAction::run_harness(), |
||||||
|
TestAction::run(indoc! {r#" |
||||||
|
var actual = []; |
||||||
|
|
||||||
|
var asyncIter = { |
||||||
|
[Symbol.asyncIterator]() { |
||||||
|
return this; |
||||||
|
}, |
||||||
|
next() { |
||||||
|
actual.push("async call next"); |
||||||
|
return { |
||||||
|
done: false, |
||||||
|
}; |
||||||
|
}, |
||||||
|
get return() { |
||||||
|
actual.push("get async return"); |
||||||
|
return function () { |
||||||
|
actual.push("async return call"); |
||||||
|
return { |
||||||
|
done: true |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Promise.resolve(0) |
||||||
|
.then(() => actual.push("tick 1")) |
||||||
|
.then(() => actual.push("tick 2")) |
||||||
|
.then(() => actual.push("tick 3")); |
||||||
|
|
||||||
|
void async function f() { |
||||||
|
actual.push("async fn start"); |
||||||
|
let count = 0; |
||||||
|
loop: while (count === 0) { |
||||||
|
count++; |
||||||
|
for await (__ of asyncIter) { |
||||||
|
continue loop; |
||||||
|
} |
||||||
|
} |
||||||
|
actual.push("async fn end"); |
||||||
|
}(); |
||||||
|
"#}), |
||||||
|
#[allow(clippy::redundant_closure_for_method_calls)] |
||||||
|
TestAction::inspect_context(|ctx| ctx.run_jobs()), |
||||||
|
TestAction::assert(indoc! {r#" |
||||||
|
arrayEquals( |
||||||
|
actual, |
||||||
|
[ |
||||||
|
"async fn start", |
||||||
|
"async call next", |
||||||
|
"tick 1", |
||||||
|
"get async return", |
||||||
|
"async return call", |
||||||
|
"tick 2", |
||||||
|
"async fn end", |
||||||
|
"tick 3" |
||||||
|
] |
||||||
|
) |
||||||
|
"#}), |
||||||
|
]); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn mixed_iterators_close_in_continue() { |
||||||
|
run_test_actions([ |
||||||
|
TestAction::run_harness(), |
||||||
|
TestAction::run(indoc! {r#" |
||||||
|
var actual = []; |
||||||
|
|
||||||
|
var iter = { |
||||||
|
[Symbol.iterator]() { |
||||||
|
return this; |
||||||
|
}, |
||||||
|
next() { |
||||||
|
actual.push("call next"); |
||||||
|
return { |
||||||
|
done: false, |
||||||
|
}; |
||||||
|
}, |
||||||
|
get return() { |
||||||
|
actual.push("get return"); |
||||||
|
return function () { |
||||||
|
actual.push("return call"); |
||||||
|
return { |
||||||
|
done: true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
var asyncIter = { |
||||||
|
[Symbol.asyncIterator]() { |
||||||
|
return this; |
||||||
|
}, |
||||||
|
next() { |
||||||
|
actual.push("async call next"); |
||||||
|
return { |
||||||
|
done: false, |
||||||
|
}; |
||||||
|
}, |
||||||
|
get return() { |
||||||
|
actual.push("get async return"); |
||||||
|
return function () { |
||||||
|
actual.push("async return call"); |
||||||
|
return { |
||||||
|
done: true |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Promise.resolve(0) |
||||||
|
.then(() => actual.push("tick 1")) |
||||||
|
.then(() => actual.push("tick 2")) |
||||||
|
.then(() => actual.push("tick 3")); |
||||||
|
|
||||||
|
void async function f() { |
||||||
|
actual.push("async fn start"); |
||||||
|
let count = 0; |
||||||
|
loop: while (count === 0) { |
||||||
|
count++; |
||||||
|
for (_ of iter) { |
||||||
|
for await (__ of asyncIter) { |
||||||
|
continue loop; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
actual.push("async fn end"); |
||||||
|
}(); |
||||||
|
"#}), |
||||||
|
#[allow(clippy::redundant_closure_for_method_calls)] |
||||||
|
TestAction::inspect_context(|ctx| ctx.run_jobs()), |
||||||
|
TestAction::assert(indoc! {r#" |
||||||
|
arrayEquals( |
||||||
|
actual, |
||||||
|
[ |
||||||
|
"async fn start", |
||||||
|
"call next", |
||||||
|
"async call next", |
||||||
|
"tick 1", |
||||||
|
"get async return", |
||||||
|
"async return call", |
||||||
|
"tick 2", |
||||||
|
"get return", |
||||||
|
"return call", |
||||||
|
"async fn end", |
||||||
|
"tick 3", |
||||||
|
] |
||||||
|
) |
||||||
|
"#}), |
||||||
|
]); |
||||||
|
} |
@ -1,25 +1,87 @@ |
|||||||
use crate::{ |
use crate::{ |
||||||
builtins::iterable::create_iter_result_object, |
builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, |
||||||
vm::{opcode::Operation, CompletionType}, |
vm::{opcode::Operation, CompletionRecord, CompletionType, GeneratorResumeKind}, |
||||||
Context, JsResult, |
Context, JsResult, JsValue, |
||||||
}; |
}; |
||||||
|
|
||||||
/// `Yield` implements the Opcode Operation for `Opcode::Yield`
|
/// `GeneratorYield` implements the Opcode Operation for `Opcode::GeneratorYield`
|
||||||
///
|
///
|
||||||
/// Operation:
|
/// Operation:
|
||||||
/// - Yield from the current execution.
|
/// - Yield from the current generator execution.
|
||||||
#[derive(Debug, Clone, Copy)] |
#[derive(Debug, Clone, Copy)] |
||||||
pub(crate) struct Yield; |
pub(crate) struct GeneratorYield; |
||||||
|
|
||||||
impl Operation for Yield { |
impl Operation for GeneratorYield { |
||||||
const NAME: &'static str = "Yield"; |
const NAME: &'static str = "GeneratorYield"; |
||||||
const INSTRUCTION: &'static str = "INST - Yield"; |
const INSTRUCTION: &'static str = "INST - GeneratorYield"; |
||||||
|
|
||||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||||
let value = context.vm.pop(); |
|
||||||
let value = create_iter_result_object(value, false, context); |
|
||||||
context.vm.push(value); |
|
||||||
context.vm.frame_mut().r#yield = true; |
context.vm.frame_mut().r#yield = true; |
||||||
Ok(CompletionType::Return) |
Ok(CompletionType::Return) |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
|
/// `AsyncGeneratorYield` implements the Opcode Operation for `Opcode::AsyncGeneratorYield`
|
||||||
|
///
|
||||||
|
/// Operation:
|
||||||
|
/// - Yield from the current async generator execution.
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
pub(crate) struct AsyncGeneratorYield; |
||||||
|
|
||||||
|
impl Operation for AsyncGeneratorYield { |
||||||
|
const NAME: &'static str = "AsyncGeneratorYield"; |
||||||
|
const INSTRUCTION: &'static str = "INST - AsyncGeneratorYield"; |
||||||
|
|
||||||
|
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||||
|
let value = context.vm.pop(); |
||||||
|
|
||||||
|
let async_gen = context |
||||||
|
.vm |
||||||
|
.frame() |
||||||
|
.async_generator |
||||||
|
.clone() |
||||||
|
.expect("`AsyncGeneratorYield` must only be called inside async generators"); |
||||||
|
let completion = Ok(value); |
||||||
|
let next = async_gen |
||||||
|
.borrow_mut() |
||||||
|
.as_async_generator_mut() |
||||||
|
.expect("must be async generator object") |
||||||
|
.queue |
||||||
|
.pop_front() |
||||||
|
.expect("must have item in queue"); |
||||||
|
|
||||||
|
// TODO: 7. Let previousContext be the second to top element of the execution context stack.
|
||||||
|
AsyncGenerator::complete_step(&next, completion, false, None, context); |
||||||
|
|
||||||
|
let mut generator_object_mut = async_gen.borrow_mut(); |
||||||
|
let gen = generator_object_mut |
||||||
|
.as_async_generator_mut() |
||||||
|
.expect("must be async generator object"); |
||||||
|
|
||||||
|
if let Some(next) = gen.queue.front() { |
||||||
|
let resume_kind = match next.completion.clone() { |
||||||
|
CompletionRecord::Normal(val) => { |
||||||
|
context.vm.push(val); |
||||||
|
GeneratorResumeKind::Normal |
||||||
|
} |
||||||
|
CompletionRecord::Return(val) => { |
||||||
|
context.vm.push(val); |
||||||
|
GeneratorResumeKind::Return |
||||||
|
} |
||||||
|
CompletionRecord::Throw(err) => { |
||||||
|
let err = err.to_opaque(context); |
||||||
|
context.vm.push(err); |
||||||
|
GeneratorResumeKind::Throw |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
context.vm.frame_mut().generator_resume_kind = resume_kind; |
||||||
|
|
||||||
|
Ok(CompletionType::Normal) |
||||||
|
} else { |
||||||
|
gen.state = AsyncGeneratorState::SuspendedYield; |
||||||
|
context.vm.push(JsValue::undefined()); |
||||||
|
GeneratorYield::execute(context) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Loading…
Reference in new issue