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::{ |
||||
builtins::iterable::create_iter_result_object, |
||||
vm::{opcode::Operation, CompletionType}, |
||||
Context, JsResult, |
||||
builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, |
||||
vm::{opcode::Operation, CompletionRecord, CompletionType, GeneratorResumeKind}, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
|
||||
/// `Yield` implements the Opcode Operation for `Opcode::Yield`
|
||||
/// `GeneratorYield` implements the Opcode Operation for `Opcode::GeneratorYield`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Yield from the current execution.
|
||||
/// - Yield from the current generator execution.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct Yield; |
||||
pub(crate) struct GeneratorYield; |
||||
|
||||
impl Operation for Yield { |
||||
const NAME: &'static str = "Yield"; |
||||
const INSTRUCTION: &'static str = "INST - Yield"; |
||||
impl Operation for GeneratorYield { |
||||
const NAME: &'static str = "GeneratorYield"; |
||||
const INSTRUCTION: &'static str = "INST - GeneratorYield"; |
||||
|
||||
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; |
||||
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