Skip to content

Commit 7730ad9

Browse files
committed
Implement new spec changes for AsyncGenerator (#3950)
* Implement new spec changes for `AsyncGenerator` * Add panic docs
1 parent 532c008 commit 7730ad9

File tree

7 files changed

+303
-247
lines changed

7 files changed

+303
-247
lines changed

core/engine/src/builtins/async_generator/mod.rs

Lines changed: 230 additions & 199 deletions
Large diffs are not rendered by default.

core/engine/src/vm/completion_record.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ unsafe impl Trace for CompletionRecord {
2929

3030
// ---- `CompletionRecord` methods ----
3131
impl CompletionRecord {
32+
pub(crate) const fn is_throw_completion(&self) -> bool {
33+
matches!(self, Self::Throw(_))
34+
}
35+
3236
/// This function will consume the current `CompletionRecord` and return a `JsResult<JsValue>`
3337
// NOTE: rustc bug around evaluating destructors that prevents this from being a const function.
3438
// Related issue(s):

core/engine/src/vm/opcode/await/mod.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,6 @@ impl Operation for Await {
4848

4949
let gen = GeneratorContext::from_current(context);
5050

51-
// Even though it would be great to avoid cloning, we need to ensure
52-
// the original async generator has a copy of the context in case it is resumed
53-
// by a `return` or `throw` call instead of a continuation.
54-
if let Some(async_generator) = gen.async_generator_object() {
55-
async_generator
56-
.downcast_mut::<AsyncGenerator>()
57-
.expect("must be async generator")
58-
.context = Some(gen.clone());
59-
}
60-
6151
let captures = Gc::new(Cell::new(Some(gen)));
6252

6353
// 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
@@ -111,7 +101,6 @@ impl Operation for Await {
111101
// d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
112102
// 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.
113103
// f. Return undefined.
114-
115104
let mut gen = captures.take().expect("should only run once");
116105

117106
// NOTE: We need to get the object before resuming, since it could clear the stack.

core/engine/src/vm/opcode/generator/mod.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{
1515
opcode::{Operation, ReThrow},
1616
CallFrame, CompletionType,
1717
},
18-
Context, JsError, JsObject, JsResult, JsValue,
18+
Context, JsError, JsObject, JsResult,
1919
};
2020

2121
pub(crate) use yield_stm::*;
@@ -128,26 +128,30 @@ impl Operation for AsyncGeneratorClose {
128128

129129
let mut gen = generator.borrow_mut();
130130

131-
gen.data.state = AsyncGeneratorState::Completed;
132-
gen.data.context = None;
131+
// e. Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return.
132+
// f. Remove acGenContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
133133

134-
let next = gen.data.queue.pop_front().expect("must have item in queue");
134+
// g. Set acGenerator.[[AsyncGeneratorState]] to draining-queue.
135+
gen.data.state = AsyncGeneratorState::DrainingQueue;
135136

136-
let return_value = context.vm.get_return_value();
137-
context.vm.set_return_value(JsValue::undefined());
137+
// h. If result is a normal completion, set result to NormalCompletion(undefined).
138+
// i. If result is a return completion, set result to NormalCompletion(result.[[Value]]).
139+
let return_value = context.vm.take_return_value();
138140

139-
let completion = context
141+
let result = context
140142
.vm
141143
.pending_exception
142144
.take()
143145
.map_or(Ok(return_value), Err);
144146

145147
drop(gen);
146148

147-
AsyncGenerator::complete_step(&next, completion, true, None, context);
148-
// TODO: Upgrade to the latest spec when the problem is fixed.
149-
AsyncGenerator::resume_next(&generator, context);
149+
// j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true).
150+
AsyncGenerator::complete_step(&generator, result, true, None, context);
151+
// k. Perform AsyncGeneratorDrainQueue(acGenerator).
152+
AsyncGenerator::drain_queue(&generator, context);
150153

154+
// l. Return undefined.
151155
Ok(CompletionType::Normal)
152156
}
153157
}

core/engine/src/vm/opcode/generator/yield_stm.rs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,36 +36,40 @@ impl Operation for AsyncGeneratorYield {
3636
const COST: u8 = 8;
3737

3838
fn execute(context: &mut Context) -> JsResult<CompletionType> {
39-
let value = context.vm.pop();
39+
// AsyncGeneratorYield ( value )
40+
// https://tc39.es/ecma262/#sec-asyncgeneratoryield
4041

42+
// 1. Let genContext be the running execution context.
43+
// 2. Assert: genContext is the execution context of a generator.
44+
// 3. Let generator be the value of the Generator component of genContext.
45+
// 4. Assert: GetGeneratorKind() is async.
4146
let async_generator_object = context
4247
.vm
4348
.frame()
4449
.async_generator_object(&context.vm.stack)
4550
.expect("`AsyncGeneratorYield` must only be called inside async generators");
46-
let completion = Ok(value);
4751
let async_generator_object = async_generator_object
4852
.downcast::<AsyncGenerator>()
4953
.expect("must be async generator object");
50-
let next = async_generator_object
51-
.borrow_mut()
52-
.data
53-
.queue
54-
.pop_front()
55-
.expect("must have item in queue");
5654

55+
// 5. Let completion be NormalCompletion(value).
56+
let value = context.vm.pop();
57+
let completion = Ok(value);
58+
59+
// TODO: 6. Assert: The execution context stack has at least two elements.
5760
// TODO: 7. Let previousContext be the second to top element of the execution context stack.
58-
AsyncGenerator::complete_step(&next, completion, false, None, context);
61+
// TODO: 8. Let previousRealm be previousContext's Realm.
62+
// 9. Perform AsyncGeneratorCompleteStep(generator, completion, false, previousRealm).
63+
AsyncGenerator::complete_step(&async_generator_object, completion, false, None, context);
5964

60-
// TODO: Upgrade to the latest spec when the problem is fixed.
6165
let mut gen = async_generator_object.borrow_mut();
62-
if gen.data.state == AsyncGeneratorState::Executing {
63-
let Some(next) = gen.data.queue.front() else {
64-
gen.data.state = AsyncGeneratorState::SuspendedYield;
65-
context.vm.set_return_value(JsValue::undefined());
66-
return Ok(CompletionType::Yield);
67-
};
6866

67+
// 10. Let queue be generator.[[AsyncGeneratorQueue]].
68+
// 11. If queue is not empty, then
69+
// a. NOTE: Execution continues without suspending the generator.
70+
// b. Let toYield be the first element of queue.
71+
if let Some(next) = gen.data.queue.front() {
72+
// c. Let resumptionValue be Completion(toYield.[[Completion]]).
6973
let resume_kind = match next.completion.clone() {
7074
CompletionRecord::Normal(val) => {
7175
context.vm.push(val);
@@ -84,17 +88,20 @@ impl Operation for AsyncGeneratorYield {
8488

8589
context.vm.push(resume_kind);
8690

91+
// d. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).
8792
return Ok(CompletionType::Normal);
8893
}
8994

90-
assert!(matches!(
91-
gen.data.state,
92-
AsyncGeneratorState::AwaitingReturn | AsyncGeneratorState::Completed
93-
));
95+
// 12. Else,
9496

95-
AsyncGenerator::resume_next(&async_generator_object, context);
97+
// a. Set generator.[[AsyncGeneratorState]] to suspended-yield.
98+
gen.data.state = AsyncGeneratorState::SuspendedYield;
9699

97-
async_generator_object.borrow_mut().data.state = AsyncGeneratorState::SuspendedYield;
100+
// TODO: b. Remove genContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
101+
// TODO: c. Let callerContext be the running execution context.
102+
// d. Resume callerContext passing undefined. If genContext is ever resumed again, let resumptionValue be the Completion Record with which it is resumed.
103+
// e. Assert: If control reaches here, then genContext is the running execution context again.
104+
// f. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).
98105
context.vm.set_return_value(JsValue::undefined());
99106
Ok(CompletionType::Yield)
100107
}

test262_config.toml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
commit = "3a7a72aef5009eb22117231d40f9a5a66a9a595a"
1+
commit = "12307f5c20a4c4211e69823939fd1872212894c5"
22

33
[ignored]
44
# Not implemented yet:
@@ -50,13 +50,25 @@ features = [
5050
# https://github.com/tc39/proposal-json-parse-with-source
5151
"json-parse-with-source",
5252

53+
# RegExp.escape
54+
# https://github.com/tc39/proposal-regex-escaping
55+
"RegExp.escape",
56+
5357
# https://github.com/tc39/proposal-iterator-helpers
5458
"iterator-helpers",
5559

5660
# Set methods
5761
# https://github.com/tc39/proposal-set-methods
5862
"set-methods",
5963

64+
# Uint8Array Base64
65+
# https://github.com/tc39/proposal-arraybuffer-base64
66+
"uint8array-base64",
67+
68+
# Atomics.pause
69+
# https://github.com/tc39/proposal-atomics-microwait
70+
"Atomics.pause",
71+
6072
### Non-standard
6173
"caller",
6274
]

tests/tester/src/edition.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! {
7373
// https://github.com/tc39/proposal-json-parse-with-source
7474
"json-parse-with-source" => SpecEdition::ESNext,
7575

76+
// RegExp.escape
77+
// https://github.com/tc39/proposal-regex-escaping
78+
"RegExp.escape" => SpecEdition::ESNext,
79+
7680
// Regular expression modifiers
7781
// https://github.com/tc39/proposal-regexp-modifiers
7882
"regexp-modifiers" => SpecEdition::ESNext,
@@ -85,10 +89,6 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! {
8589
// https://github.com/tc39/proposal-promise-try
8690
"promise-try" => SpecEdition::ESNext,
8791

88-
// Set methods
89-
// https://github.com/tc39/proposal-set-methods
90-
"set-methods" => SpecEdition::ESNext,
91-
9292
// Explicit Resource Management
9393
// https://github.com/tc39/proposal-explicit-resource-management
9494
"explicit-resource-management" => SpecEdition::ESNext,
@@ -107,6 +107,14 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! {
107107
// test262 special specifier
108108
"source-phase-imports-module-source" => SpecEdition::ESNext,
109109

110+
// Uint8Array Base64
111+
// https://github.com/tc39/proposal-arraybuffer-base64
112+
"uint8array-base64" => SpecEdition::ESNext,
113+
114+
// Atomics.pause
115+
// https://github.com/tc39/proposal-atomics-microwait
116+
"Atomics.pause" => SpecEdition::ESNext,
117+
110118
// Part of the next ES15 edition
111119
"Atomics.waitAsync" => SpecEdition::ESNext,
112120
"regexp-v-flag" => SpecEdition::ESNext,
@@ -115,6 +123,7 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! {
115123
"resizable-arraybuffer" => SpecEdition::ESNext,
116124
"promise-with-resolvers" => SpecEdition::ESNext,
117125
"array-grouping" => SpecEdition::ESNext,
126+
"set-methods" => SpecEdition::ESNext,
118127

119128
// Standard language features
120129
"AggregateError" => SpecEdition::ES12,

0 commit comments

Comments
 (0)