diff --git a/crates/js/src/vm.rs b/crates/js/src/vm.rs index f36cf5a..9bef4fc 100644 --- a/crates/js/src/vm.rs +++ b/crates/js/src/vm.rs @@ -1486,6 +1486,9 @@ impl Vm { if gen.state == GeneratorState::Completed { return Ok(self.make_iterator_result(Value::Undefined, true)); } + if gen.state == GeneratorState::Executing { + return Err(RuntimeError::type_error("Generator is already executing")); + } ( gen.func.clone(), gen.upvalues.clone(), @@ -3412,29 +3415,12 @@ fn generator_symbol_iterator( /// Native callback for async function resume. Called from microtask drain. /// Returns a marker object with `__async_resume__` so the VM can detect it /// and call `drive_async_step`. +/// +/// The resume data (gen_ref, result_promise, is_throw) is stored on the +/// function's `__async_data__` property. The VM extracts it into the +/// `ASYNC_RESUME_DATA` thread-local before invoking this callback. fn async_resume_callback(args: &[Value], ctx: &mut NativeContext) -> Result { - // The resume data is stored on the function's own properties. - // We need to find the function ref from the calling convention. - // Since NativeContext doesn't provide function-self, we store data on the - // function object's properties and read them from global __async_current_fn__. - // - // Alternative approach: the callback itself reads from ctx.gc using a - // known __async_data__ property on the function. However, we don't have - // the function's GcRef here. - // - // Workaround: store the data in a global variable set before calling. - - // Actually, the simplest approach: pass the data as extra information. - // We set __async_data__ as a property of the function, and the VM's - // call_function / Call handler extracts it from the function before - // calling the native callback. - - // For now, use a sentinel return value that the VM intercepts. let value = args.first().cloned().unwrap_or(Value::Undefined); - - // We need the async data (gen_ref, result_promise, is_throw). - // The data was stored on the function's properties. We read from - // a thread-local set by the VM before calling us. let data_ref = ASYNC_RESUME_DATA.with(|cell| cell.take()); if let Some(data) = data_ref { let gen_ref = match gc_get_property(ctx.gc, data, "__gen_ref__") { @@ -7340,7 +7326,7 @@ mod tests { } #[test] - fn test_async_arrow_function() { + fn test_async_function_expression() { match eval_global( "var f = async function() { var x = await Promise.resolve(99); return x; }; f().then(function(v) { result = v; });", @@ -7354,7 +7340,7 @@ mod tests { } #[test] - fn test_async_arrow_concise_body() { + fn test_async_function_expression_with_args() { match eval_global( "var f = async function(x) { return x * 2; }; f(21).then(function(v) { result = v; });", @@ -7367,6 +7353,34 @@ mod tests { } } + #[test] + fn test_async_arrow_function() { + match eval_global( + "var f = async x => { var y = await Promise.resolve(x); return y + 1; }; + f(10).then(function(v) { result = v; });", + "result", + ) + .unwrap() + { + Value::Number(n) => assert_eq!(n, 11.0), + v => panic!("expected 11, got {v:?}"), + } + } + + #[test] + fn test_async_arrow_concise_body() { + match eval_global( + "var f = async x => x * 3; + f(7).then(function(v) { result = v; });", + "result", + ) + .unwrap() + { + Value::Number(n) => assert_eq!(n, 21.0), + v => panic!("expected 21, got {v:?}"), + } + } + #[test] fn test_await_chained_promises() { match eval_global(