From fc0553cd185766b9950521e28ce540604667cc7b Mon Sep 17 00:00:00 2001 From: Steven Cole Date: Fri, 28 Aug 2026 11:16:05 -0700 Subject: [PATCH] Add async method definition --- src/agent/mod.rs | 123 ++++++++++++++++++ src/chunk/mod.rs | 6 +- src/compiler/mod.rs | 106 ++++++++++++++- src/compiler/tests.rs | 4 +- src/function_object/mod.rs | 77 ++++++++--- src/parser/async_function_definitions/mod.rs | 6 +- .../mod.rs | 6 +- 7 files changed, 297 insertions(+), 31 deletions(-) diff --git a/src/agent/mod.rs b/src/agent/mod.rs index 235b7c1f..8af91683 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -3793,6 +3793,127 @@ mod insn_impl { push_completion(result.map(NormalCompletion::from)).expect(PUSHABLE); Ok(()) } + + pub(crate) fn define_async_method(chunk: &Rc) -> anyhow::Result<()> { + // Takes two args: idx into function stash and enumerable flag + // stack input: propkey object + // output: err/empty/PrivateElement + let info = sfd_operand(chunk)?; + let enumerable = usize_operand(chunk)? != 0; + let prop_key = pop_functionname()?; + let base_object = pop_obj()?; + + // 2. Let envRecord be the LexicalEnvironment of the running execution context. + let env_record = current_lexical_environment().expect("environment should exist"); + // 3. Let privateEnv be the running execution context's PrivateEnvironment. + let priv_env = current_private_environment(); + // 4. Let sourceText be the source text matched by AsyncMethod. + // 5. Let closure be OrdinaryFunctionCreate(%AsyncFunction.prototype%, sourceText, UniqueFormalParameters, AsyncFunctionBody, non-lexical-this, envRecord, privateEnv). + let to_compile: Rc = info.to_compile.clone().try_into()?; + let fb = &to_compile.body; + let prod_text_loc = to_compile.location().span; + let prod_text = + &info.parent_tree.text[prod_text_loc.starting_index..prod_text_loc.starting_index + prod_text_loc.length]; + let chunk_name = nameify(prod_text, 50); + let mut compiled = Chunk::new(chunk_name, to_compile.location().starting_line); + let compilation_status = fb.compile_body(&mut compiled, &info.parent_tree, info); + if let Err(err) = compilation_status { + let typeerror = create_type_error(err.to_string()); + push_completion(Err(typeerror)).expect(PUSHABLE); + return Ok(()); + } + + #[cfg(debug_assertions)] + for line in compiled.disassemble(&info.parent_tree.text) { + println!("{line}"); + } + + let prototype = intrinsic(IntrinsicId::AsyncFunctionPrototype); + let closure = ordinary_function_create( + prototype, + &info.source_text, + info.params.clone(), + info.body.clone(), + info.this_mode, + env_record, + priv_env, + info.strict, + Rc::new(compiled), + ); + make_method(closure.o.to_function_obj().unwrap(), base_object.clone()); + super::set_function_name(&closure, prop_key.clone(), Some("set".into())); + + // 6. Perform MakeMethod(closure, obj). + // 7. Perform SetFunctionName(closure, propertyKey). + // 8. Return ? DefineMethodProperty(obj, propertyKey, closure, enumerable). + let rval = + super::define_method_property(&base_object, prop_key, closure, enumerable).map(NormalCompletion::from); + push_completion(rval).expect(PUSHABLE); + Ok(()) + } + pub(crate) fn define_async_generator(chunk: &Rc) -> anyhow::Result<()> { + // Takes two args: idx into function stash and enumerable flag + // stack input: propkey object + // output: err/empty/PrivateElement + let info = sfd_operand(chunk)?; + let enumerable = usize_operand(chunk)? != 0; + let prop_key = pop_functionname()?; + let base_object = pop_obj()?; + + // 2. Let envRecord be the LexicalEnvironment of the running execution context. + let env_record = current_lexical_environment().expect("environment should exist"); + // 3. Let privateEnv be the running execution context's PrivateEnvironment. + let priv_env = current_private_environment(); + // 4. Let sourceText be the source text matched by AsyncMethod. + // 5. Let closure be OrdinaryFunctionCreate(%AsyncGeneratorFunction.prototype%, sourceText, UniqueFormalParameters, AsyncFunctionBody, non-lexical-this, envRecord, privateEnv). + let to_compile: Rc = info.to_compile.clone().try_into()?; + let fb = &to_compile.body; + let prod_text_loc = to_compile.location().span; + let prod_text = + &info.parent_tree.text[prod_text_loc.starting_index..prod_text_loc.starting_index + prod_text_loc.length]; + let chunk_name = nameify(prod_text, 50); + let mut compiled = Chunk::new(chunk_name, to_compile.location().starting_line); + let compilation_status = fb.compile_body(&mut compiled, &info.parent_tree, info); + if let Err(err) = compilation_status { + let typeerror = create_type_error(err.to_string()); + push_completion(Err(typeerror)).expect(PUSHABLE); + return Ok(()); + } + + #[cfg(debug_assertions)] + for line in compiled.disassemble(&info.parent_tree.text) { + println!("{line}"); + } + + let prototype = intrinsic(IntrinsicId::AsyncGeneratorFunctionPrototype); + let closure = ordinary_function_create( + prototype, + &info.source_text, + info.params.clone(), + info.body.clone(), + info.this_mode, + env_record, + priv_env, + info.strict, + Rc::new(compiled), + ); + make_method(closure.o.to_function_obj().unwrap(), base_object.clone()); + super::set_function_name(&closure, prop_key.clone(), Some("set".into())); + + let protoproto = intrinsic(IntrinsicId::AsyncGeneratorFunctionPrototypePrototype); + define_property_or_throw( + &closure, + "prototype", + PotentialPropertyDescriptor::new().value(protoproto).writable(true), + ) + .expect(GOODOBJ); + + // 8. Return ? DefineMethodProperty(obj, propertyKey, closure, enumerable). + let rval = + super::define_method_property(&base_object, prop_key, closure, enumerable).map(NormalCompletion::from); + push_completion(rval).expect(PUSHABLE); + Ok(()) + } pub(crate) fn generator_start_from_function(source: &Rc) -> anyhow::Result<()> { // 2. Let G be ? OrdinaryCreateFromConstructor(functionObject, // "%GeneratorFunction.prototype.prototype%", « [[GeneratorState]], [[GeneratorContext]], @@ -4847,6 +4968,8 @@ pub(crate) async fn execute( Insn::DefineMethodProperty => insn_impl::define_method_property(&chunk).expect(GOODCODE), Insn::DefineGetter => insn_impl::define_getter(&chunk).expect(GOODCODE), Insn::DefineSetter => insn_impl::define_setter(&chunk).expect(GOODCODE), + Insn::DefineAsyncGenerator => insn_impl::define_async_generator(&chunk).expect(GOODCODE), + Insn::DefineAsyncMethod => insn_impl::define_async_method(&chunk).expect(GOODCODE), Insn::GetParentsFromSuperclass => insn_impl::get_parents_from_superclass().expect(GOODCODE), Insn::CreateDefaultConstructor => insn_impl::create_default_constructor().expect(GOODCODE), Insn::MakeClassConstructorAndSetName => insn_impl::make_class_constructor_and_set_name().expect(GOODCODE), diff --git a/src/chunk/mod.rs b/src/chunk/mod.rs index 171fa5c8..c2816673 100644 --- a/src/chunk/mod.rs +++ b/src/chunk/mod.rs @@ -395,7 +395,11 @@ impl Chunk { let flags_arg = self.opcodes[idx + 1] as usize; (3, format!(" {insn:<24}/{}/{}", self.strings[pattern_arg], self.strings[flags_arg])) } - Insn::DefineGetter | Insn::DefineSetter | Insn::InstantiateGeneratorMethod => { + Insn::DefineGetter + | Insn::DefineSetter + | Insn::InstantiateGeneratorMethod + | Insn::DefineAsyncMethod + | Insn::DefineAsyncGenerator => { let arg = self.opcodes[idx] as usize; let flag = self.opcodes[idx + 1] != 0; (3, format!(" {:<24}{} {}", insn, arg, if flag { "enumerable" } else { "hidden" })) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 09c91152..c23b3ccf 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -45,6 +45,8 @@ pub(crate) enum Insn { CreateUnmappedArguments, Debugger, Decrement, + DefineAsyncGenerator, + DefineAsyncMethod, DefineGetter, DefineMethod, DefineMethodProperty, @@ -407,6 +409,8 @@ impl fmt::Display for Insn { Insn::EvaluateInitializedClassStaticFieldDefinition => "EVAL_CLS_STC_FLD_DEF", Insn::EvaluateClassStaticBlockDefinition => "EVAL_CLASS_SBLK_DEF", Insn::DefineMethod => "DEFINE_METHOD", + Insn::DefineAsyncMethod => "DEFINE_ASYNC_METH", + Insn::DefineAsyncGenerator => "DEFINE_ASYNC_GEN", Insn::SetFunctionName => "SET_FUNC_NAME", Insn::DefineMethodProperty => "DEF_METH_PROP", Insn::DefineGetter => "DEF_GETTER", @@ -1246,6 +1250,28 @@ impl AsyncFunctionExpression { } } +impl AsyncGeneratorBody { + pub(crate) fn compile_body( + &self, + chunk: &mut Chunk, + source: &Rc, + info: &StashedFunctionData, + ) -> anyhow::Result { + self.compile_evaluate_async_generator_body(chunk, source, info) + } + + #[expect(unused_variables, clippy::unnecessary_wraps)] + pub(crate) fn compile_evaluate_async_generator_body( + &self, + chunk: &mut Chunk, + source: &Rc, + info: &StashedFunctionData, + ) -> anyhow::Result { + chunk.op(Insn::ToDo, self.location().starting_line); + Ok(CompilerStatusFlags::from(AlwaysAbruptResult)) + } +} + impl AsyncArrowFunction { #[cfg(test)] pub(crate) fn params(&self) -> ParamSource { @@ -12408,8 +12434,84 @@ impl MethodDefinition { Ok(AlwaysAbruptResult) } MethodDefinition::Generator(r#gen) => r#gen.method_definition_evaluation(enumerable, chunk, strict, source), - MethodDefinition::Async(_) => todo!(), - MethodDefinition::AsyncGenerator(_) => todo!(), + MethodDefinition::Async(node) => { + // AsyncMethod : async ClassElementName ( UniqueFormalParameters ) { AsyncFunctionBody } + // 1. Let propertyKey be ? Evaluation of ClassElementName. + // 2. Let envRecord be the LexicalEnvironment of the running execution context. + // 3. Let privateEnv be the running execution context's PrivateEnvironment. + // 4. Let sourceText be the source text matched by AsyncMethod. + // 5. Let closure be OrdinaryFunctionCreate(%AsyncFunction.prototype%, sourceText, UniqueFormalParameters, AsyncFunctionBody, non-lexical-this, envRecord, privateEnv). + // 6. Perform MakeMethod(closure, obj). + // 7. Perform SetFunctionName(closure, propertyKey). + // 8. Return ? DefineMethodProperty(obj, propertyKey, closure, enumerable). + + // start: object + // err/propKey object + // JUMP_IF_ABRUPT unwind_1 propKey object + // DEFINE_ASYNC_METH err/empty/PrivateElement + // JUMP exit + // unwind_1: err object + // UNWIND 1 err + // exit: err/empty/PrivateElement + let location = node.location(); + let status = node.ident.compile(chunk, source)?; + let unwind = if status.maybe_abrupt() { Some(chunk.op_jump(Insn::JumpIfAbrupt, line)) } else { None }; + let source_text = source.text + [location.span.starting_index..location.span.starting_index + location.span.length] + .to_string(); + let info = StashedFunctionData { + source_text, + params: node.params.clone().into(), + body: node.body.clone().into(), + to_compile: node.clone().into(), + strict, + this_mode: ThisLexicality::NonLexicalThis, + parent_tree: source.clone(), + }; + let idx = chunk.add_to_func_stash(info)?; + chunk.op_plus_two_args(Insn::DefineAsyncMethod, idx, u16::from(enumerable), line); + if let Some(unwind) = unwind { + let exit = chunk.op_jump(Insn::Jump, line); + chunk.fixup(unwind).expect("jump too short to fail"); + chunk.op_plus_arg(Insn::Unwind, 1, line); + chunk.fixup(exit).expect("jump too short to fail"); + } + Ok(AlwaysAbruptResult) + } + MethodDefinition::AsyncGenerator(node) => { + // start: object + // err/propKey object + // JUMP_IF_ABRUPT unwind_1 propKey object + // DEFINE_ASYNC_GEN err/empty/PrivateElement + // JUMP exit + // unwind_1: err object + // UNWIND 1 err + // exit: err/empty/PrivateElement + let location = node.location(); + let status = node.name.compile(chunk, source)?; + let unwind = if status.maybe_abrupt() { Some(chunk.op_jump(Insn::JumpIfAbrupt, line)) } else { None }; + let source_text = source.text + [location.span.starting_index..location.span.starting_index + location.span.length] + .to_string(); + let info = StashedFunctionData { + source_text, + params: node.params.clone().into(), + body: node.body.clone().into(), + to_compile: node.clone().into(), + strict, + this_mode: ThisLexicality::NonLexicalThis, + parent_tree: source.clone(), + }; + let idx = chunk.add_to_func_stash(info)?; + chunk.op_plus_two_args(Insn::DefineAsyncGenerator, idx, u16::from(enumerable), line); + if let Some(unwind) = unwind { + let exit = chunk.op_jump(Insn::Jump, line); + chunk.fixup(unwind).expect("jump too short to fail"); + chunk.op_plus_arg(Insn::Unwind, 1, line); + chunk.fixup(exit).expect("jump too short to fail"); + } + Ok(AlwaysAbruptResult) + } MethodDefinition::Getter(name, body, location) => { // MethodDefinition : get ClassElementName ( ) { FunctionBody } // 1. Let propKey be ? Evaluation of ClassElementName. diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 2c3b8c2e..789d6b7a 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -13828,8 +13828,8 @@ mod method_definition { )] #[test_case("set a(b){}", true, &[], true => Ok(svec(&["00001: set a(b){}", "STRING 0 (a)", "DEF_SETTER 0 enumerable"])); "setter")] #[test_case("*a(){}", true, &[], true => Ok(svec(&["00001: *a(){}", "STRING 0 (a)", "GEN_METHOD 0 enumerable"])); "generator")] - #[test_case("async a(){}", true, &[], true => panics "not yet implemented"; "async function")] - #[test_case("async *a(){}", true, &[], true => panics "not yet implemented"; "async generator")] + #[test_case("async a(){}", true, &[], true => Ok(svec(&["00001: async a(){}", "STRING 0 (a)", "DEFINE_ASYNC_METH 0 enumerable"])); "async function")] + #[test_case("async *a(){}", true, &[], true => Ok(svec(&["00001: async *a(){}", "STRING 0 (a)", "DEFINE_ASYNC_GEN 0 enumerable"])); "async generator")] fn method_definition_evaluation( src: &str, strict: bool, diff --git a/src/function_object/mod.rs b/src/function_object/mod.rs index 942ec605..c7cbbb0c 100644 --- a/src/function_object/mod.rs +++ b/src/function_object/mod.rs @@ -503,10 +503,11 @@ pub(crate) enum FunctionSource { GeneratorExpression(Rc), AsyncGeneratorExpression(Rc), AsyncFunctionExpression(Rc), + AsyncMethod(Rc), + AsyncGeneratorMethod(Rc), ArrowFunction(Rc), AsyncArrowFunction(Rc), MethodDefinition(Rc), - //HoistableDeclaration(Rc), FieldDefinition(Rc), ClassStaticBlock(Rc), FunctionDeclaration(Rc), @@ -519,21 +520,22 @@ pub(crate) enum FunctionSource { impl fmt::Display for FunctionSource { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - FunctionSource::FunctionExpression(node) => node.fmt(f), - FunctionSource::GeneratorExpression(node) => node.fmt(f), - FunctionSource::AsyncGeneratorExpression(node) => node.fmt(f), - FunctionSource::AsyncFunctionExpression(node) => node.fmt(f), FunctionSource::ArrowFunction(node) => node.fmt(f), FunctionSource::AsyncArrowFunction(node) => node.fmt(f), - FunctionSource::MethodDefinition(node) => node.fmt(f), - //FunctionSource::HoistableDeclaration(node) => node.fmt(f), - FunctionSource::FieldDefinition(node) => node.fmt(f), + FunctionSource::AsyncFunctionDeclaration(node) => node.fmt(f), + FunctionSource::AsyncFunctionExpression(node) => node.fmt(f), + FunctionSource::AsyncGeneratorDeclaration(node) => node.fmt(f), + FunctionSource::AsyncGeneratorExpression(node) => node.fmt(f), + FunctionSource::AsyncGeneratorMethod(node) => node.fmt(f), + FunctionSource::AsyncMethod(node) => node.fmt(f), FunctionSource::ClassStaticBlock(node) => node.fmt(f), + FunctionSource::FieldDefinition(node) => node.fmt(f), FunctionSource::FunctionDeclaration(node) => node.fmt(f), + FunctionSource::FunctionExpression(node) => node.fmt(f), FunctionSource::GeneratorDeclaration(node) => node.fmt(f), + FunctionSource::GeneratorExpression(node) => node.fmt(f), FunctionSource::GeneratorMethod(node) => node.fmt(f), - FunctionSource::AsyncFunctionDeclaration(node) => node.fmt(f), - FunctionSource::AsyncGeneratorDeclaration(node) => node.fmt(f), + FunctionSource::MethodDefinition(node) => node.fmt(f), } } } @@ -556,6 +558,8 @@ impl PartialEq for FunctionSource { (Self::GeneratorMethod(l0), Self::GeneratorMethod(r0)) => Rc::ptr_eq(l0, r0), (Self::AsyncFunctionDeclaration(l0), Self::AsyncFunctionDeclaration(r0)) => Rc::ptr_eq(l0, r0), (Self::AsyncGeneratorDeclaration(l0), Self::AsyncGeneratorDeclaration(r0)) => Rc::ptr_eq(l0, r0), + (Self::AsyncMethod(l0), Self::AsyncMethod(r0)) => Rc::ptr_eq(l0, r0), + (Self::AsyncGeneratorMethod(l0), Self::AsyncGeneratorMethod(r0)) => Rc::ptr_eq(l0, r0), ( Self::FunctionExpression(_) | Self::GeneratorExpression(_) @@ -571,7 +575,8 @@ impl PartialEq for FunctionSource { | Self::AsyncFunctionExpression(_) | Self::AsyncFunctionDeclaration(_) | Self::AsyncGeneratorExpression(_) - | Self::AsyncGeneratorDeclaration(_), + | Self::AsyncGeneratorDeclaration(_) + | Self::AsyncMethod(_)|Self::AsyncGeneratorMethod(_), _, ) => false, } @@ -647,6 +652,16 @@ impl From> for FunctionSource { Self::AsyncGeneratorDeclaration(value) } } +impl From> for FunctionSource { + fn from(value: Rc) -> Self { + Self::AsyncMethod(value) + } +} +impl From> for FunctionSource { + fn from(value: Rc) -> Self { + Self::AsyncGeneratorMethod(value) + } +} impl TryFrom for Rc { type Error = anyhow::Error; @@ -777,24 +792,46 @@ impl TryFrom for Rc { } } } +impl TryFrom for Rc { + type Error = anyhow::Error; + + fn try_from(value: FunctionSource) -> Result { + match value { + FunctionSource::AsyncMethod(node) => Ok(node), + _ => bail!("AsyncMethod expected"), + } + } +} +impl TryFrom for Rc { + type Error = anyhow::Error; + + fn try_from(value: FunctionSource) -> Result { + match value { + FunctionSource::AsyncGeneratorMethod(node) => Ok(node), + _ => bail!("AsyncGeneratorMethod expected"), + } + } +} impl FunctionSource { pub(crate) fn location(&self) -> Location { match self { - FunctionSource::FunctionExpression(node) => node.location(), - FunctionSource::GeneratorExpression(node) => node.location(), - FunctionSource::AsyncGeneratorExpression(node) => node.location(), - FunctionSource::AsyncFunctionExpression(node) => node.location(), FunctionSource::ArrowFunction(node) => node.location(), - FunctionSource::MethodDefinition(node) => node.location(), - FunctionSource::FieldDefinition(node) => node.location(), + FunctionSource::AsyncArrowFunction(node) => node.location(), + FunctionSource::AsyncFunctionDeclaration(node) => node.location(), + FunctionSource::AsyncFunctionExpression(node) => node.location(), + FunctionSource::AsyncGeneratorDeclaration(node) => node.location(), + FunctionSource::AsyncGeneratorExpression(node) => node.location(), + FunctionSource::AsyncGeneratorMethod(node) => node.location(), + FunctionSource::AsyncMethod(node) => node.location(), FunctionSource::ClassStaticBlock(node) => node.location(), + FunctionSource::FieldDefinition(node) => node.location(), FunctionSource::FunctionDeclaration(node) => node.location(), + FunctionSource::FunctionExpression(node) => node.location(), FunctionSource::GeneratorDeclaration(node) => node.location(), + FunctionSource::GeneratorExpression(node) => node.location(), FunctionSource::GeneratorMethod(node) => node.location(), - FunctionSource::AsyncFunctionDeclaration(node) => node.location(), - FunctionSource::AsyncArrowFunction(node) => node.location(), - FunctionSource::AsyncGeneratorDeclaration(node) => node.location(), + FunctionSource::MethodDefinition(node) => node.location(), } } } diff --git a/src/parser/async_function_definitions/mod.rs b/src/parser/async_function_definitions/mod.rs index 3705fce9..044097e1 100644 --- a/src/parser/async_function_definitions/mod.rs +++ b/src/parser/async_function_definitions/mod.rs @@ -463,9 +463,9 @@ impl AsyncFunctionExpression { // async [no LineTerminator here] ClassElementName[?Yield, ?Await] ( UniqueFormalParameters[~Yield, +Await] ) { AsyncFunctionBody } #[derive(Debug)] pub(crate) struct AsyncMethod { - ident: Rc, - params: Rc, - body: Rc, + pub(crate) ident: Rc, + pub(crate) params: Rc, + pub(crate) body: Rc, location: Location, } diff --git a/src/parser/async_generator_function_definitions/mod.rs b/src/parser/async_generator_function_definitions/mod.rs index 4386a1ec..7065a3b4 100644 --- a/src/parser/async_generator_function_definitions/mod.rs +++ b/src/parser/async_generator_function_definitions/mod.rs @@ -7,9 +7,9 @@ use std::io::Write; // async [no LineTerminator here] * ClassElementName[?Yield, ?Await] ( UniqueFormalParameters[+Yield, +Await] ) { AsyncGeneratorBody } #[derive(Debug)] pub(crate) struct AsyncGeneratorMethod { - name: Rc, - params: Rc, - body: Rc, + pub(crate) name: Rc, + pub(crate) params: Rc, + pub(crate) body: Rc, location: Location, }