///|
/// Create an async function value. When called, it:
/// 1. Creates a Promise
/// 2. Internally creates a generator from the body (await → yield)
/// 3. Auto-steps the generator, resolving/rejecting the Promise
/// 4. Returns the Promise
fn Interpreter::make_async_function(
self : Interpreter,
name : String?,
params : Array[String],
rest_param : String?,
body : Array[@ast.Stmt],
strict : Bool,
closure : Environment,
has_name_binding? : Bool = false,
is_method? : Bool = false,
source_text? : String? = None,
is_arrow? : Bool = false,
) -> Value {
let func_name = match name {
Some(n) => n
None => ""
}
let internal_slots : Map[InternalSlotKey, Value] = Map([])
match source_text {
Some(text) => internal_slots[SourceText] = String_(text)
None => ()
}
let nf_desc : PropDescriptor = {
writable: false,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
let async_proto = get_function_prototype(self)
let effective_closure : Environment = if has_name_binding {
match name {
Some(n) => {
let ne = Environment::new(parent=Some(closure))
ne.bindings[n] = {
value: Undefined,
kind: FunctionNameBinding,
initialized: true,
annex_b_hoisted: false,
is_parameter: false,
}
ne
}
None => closure
}
} else {
closure
}
// Self-reference cell for arguments.callee (sloppy mapped arguments).
let self_val : Array[Value] = [Undefined]
let async_func = stamp_function_realm(
Object({
bag: {
properties: {
"name": String_(func_name),
"length": Number(params.length().to_double()),
},
symbol_properties: Map([]),
descriptors: { "name": nf_desc, "length": nf_desc },
symbol_descriptors: Map([]),
internal_slots,
host_slots: Map([]),
},
prototype: async_proto,
callable: Some(
InterpreterCallable(func_name, fn(
ip : Interpreter,
this_val : Value,
args : Array[Value],
) -> Value {
run_async_body(
ip,
this_val,
args,
name,
params,
None,
rest_param,
body,
strict,
effective_closure,
is_method~,
is_arrow~,
callee=self_val[0],
)
}),
),
class_name: "AsyncFunction",
extensible: true,
arraybuffer_state: None,
}),
realm_state=Some(self.realm_state),
)
self_val[0] = async_func
if has_name_binding {
match name {
Some(n) =>
match effective_closure.bindings.get(n) {
Some(b) => b.value = async_func
None => ()
}
None => ()
}
}
async_func
}
///|
fn Interpreter::make_async_function_ext(
self : Interpreter,
name : String?,
params : Array[@ast.Param],
rest_param : String?,
body : Array[@ast.Stmt],
strict : Bool,
closure : Environment,
has_name_binding? : Bool = false,
is_method? : Bool = false,
source_text? : String? = None,
is_arrow? : Bool = false,
) -> Value {
let func_name = match name {
Some(n) => n
None => ""
}
let internal_slots : Map[InternalSlotKey, Value] = Map([])
match source_text {
Some(text) => internal_slots[SourceText] = String_(text)
None => ()
}
let nf_desc : PropDescriptor = {
writable: false,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
let async_proto = get_function_prototype(self)
let func_length = expected_argument_count_ext(params)
let effective_closure : Environment = if has_name_binding {
match name {
Some(n) => {
let ne = Environment::new(parent=Some(closure))
ne.bindings[n] = {
value: Undefined,
kind: FunctionNameBinding,
initialized: true,
annex_b_hoisted: false,
is_parameter: false,
}
ne
}
None => closure
}
} else {
closure
}
// Self-reference cell for arguments.callee (sloppy mapped arguments).
let self_val_ext : Array[Value] = [Undefined]
let async_func = stamp_function_realm(
Object({
bag: {
properties: {
"name": String_(func_name),
"length": Number(func_length.to_double()),
},
symbol_properties: Map([]),
descriptors: { "name": nf_desc, "length": nf_desc },
symbol_descriptors: Map([]),
internal_slots,
host_slots: Map([]),
},
prototype: async_proto,
callable: Some(
InterpreterCallable(func_name, fn(
ip : Interpreter,
this_val : Value,
args : Array[Value],
) -> Value {
run_async_body(
ip,
this_val,
args,
name,
[],
Some(params),
rest_param,
body,
strict,
effective_closure,
is_method~,
is_arrow~,
callee=self_val_ext[0],
)
}),
),
class_name: "AsyncFunction",
extensible: true,
arraybuffer_state: None,
}),
realm_state=Some(self.realm_state),
)
self_val_ext[0] = async_func
if has_name_binding {
match name {
Some(n) =>
match effective_closure.bindings.get(n) {
Some(b) => b.value = async_func
None => ()
}
None => ()
}
}
async_func
}
///|
/// Execute an async function body by creating a generator internally
/// and auto-stepping it with Promise resolution.
fn run_async_body(
ip : Interpreter,
this_val : Value,
args : Array[Value],
name : String?,
params : Array[String],
params_ext : Array[@ast.Param]?,
rest_param : String?,
body : Array[@ast.Stmt],
strict : Bool,
closure : Environment,
is_method? : Bool = false,
is_arrow? : Bool = false,
callee? : Value = Undefined,
) -> Value {
let promise_data = new_promise_data()
let promise_val = Promise(promise_data)
let (resolve_fn, reject_fn) = create_resolving_functions(ip, promise_data)
// Create a generator object from the async function body.
// The prototype here is unused for step driving — async_step calls
// generator_resume() directly, bypassing any prototype dispatch.
let gen_proto : Value = match
ip.global.bindings.get("[[GeneratorPrototype]]") {
Some(binding) => binding.value
None => Null
}
let func_proto = Object({
bag: PropertyBag(),
prototype: gen_proto,
callable: None,
class_name: "Object",
extensible: true,
arraybuffer_state: None,
})
let gen_val = create_generator_instance(
ip,
this_val,
args,
name,
params,
params_ext,
rest_param,
body,
strict,
closure,
func_proto,
is_method~,
is_arrow~,
callee~,
is_async=true,
) catch {
JsException(e) => {
// Parameter binding error rejects the promise
let _ = ip.call_value(reject_fn, Undefined, [e], @token.Loc::default()) catch {
_ => Undefined
}
return promise_val
}
e => {
let err_val = js_error_to_value_with_env(e, Some(ip.global))
let _ = ip.call_value(
reject_fn,
Undefined,
[err_val],
@token.Loc::default(),
) catch {
_ => Undefined
}
return promise_val
}
}
// Auto-step the generator
async_step(ip, gen_val, Undefined, resolve_fn, reject_fn, false)
promise_val
}
///|
/// Auto-step an async generator: call next/throw, handle result.
fn async_step(
ip : Interpreter,
gen_val : Value,
value : Value,
resolve_fn : Value,
reject_fn : Value,
is_throw : Bool,
) -> Unit {
let loc = @token.Loc::default()
// Drive the internal generator directly — NOT through prototype dispatch.
// The gen_val here is an implementation artifact (not user-accessible),
// so using ip.get_property would allow user monkey-patching of
// %GeneratorPrototype%.next to hijack async function resolution.
let result : Value = try {
if is_throw {
generator_resume(ip, gen_val, Throw(value))
} else {
generator_resume(ip, gen_val, Next(value))
}
} catch {
JsException(e) => {
// Generator threw — reject the promise
let _ = ip.call_value(reject_fn, Undefined, [e], loc) catch {
_ => Undefined
}
return
}
e => {
let err_val = js_error_to_value_with_env(e, Some(ip.global))
let _ = ip.call_value(reject_fn, Undefined, [err_val], loc) catch {
_ => Undefined
}
return
}
}
// Extract { value, done } from result
let result_value = ip.get_property(result, "value", loc) catch {
_ => Undefined
}
let result_done = ip.get_property(result, "done", loc) catch {
_ => Bool(false)
}
let is_done = match result_done {
Bool(b) => b
_ => false
}
if is_done {
// Generator completed — resolve the promise
// Do NOT call run_microtasks() here; the outer event loop
// (run_microtasks in js_engine.mbt) uses index-based draining
// and will process any newly enqueued reaction microtasks.
let _ = ip.call_value(resolve_fn, Undefined, [result_value], loc) catch {
_ => Undefined
}
} else {
// Generator yielded (await) — schedule continuation via microtask.
// Do NOT call run_microtasks() here to avoid recursive re-entry;
// the outer event loop drains the queue with its while loop.
let awaited = result_value
let gen = gen_val
let res_fn = resolve_fn
let rej_fn = reject_fn
let on_fulfill = make_interp_method_func(
name="",
length=1,
realm_state=Some(ip.realm_state),
fn(i : Interpreter, _this : Value, args : Array[Value]) -> Value {
let v = if args.length() > 0 { args[0] } else { Undefined }
async_step(i, gen, v, res_fn, rej_fn, false)
Undefined
},
)
let on_reject = make_interp_method_func(
name="",
length=1,
realm_state=Some(ip.realm_state),
fn(i : Interpreter, _this : Value, args : Array[Value]) -> Value {
let v = if args.length() > 0 { args[0] } else { Undefined }
async_step(i, gen, v, res_fn, rej_fn, true)
Undefined
},
)
// Wrap the awaited value in Promise.resolve() then schedule .then()
// This ensures all await continuations go through the microtask queue,
// matching spec behavior and preventing stack overflow.
let resolved_promise = try {
let promise_ctor = ip.get_property(
ip.global.get("Promise"),
"resolve",
loc,
)
ip.call_value(promise_ctor, ip.global.get("Promise"), [awaited], loc)
} catch {
_ => {
// Fallback: Promise.resolve() unavailable; inspect awaited directly
match awaited {
Promise(pd) => attach_promise_reactions(ip, pd, on_fulfill, on_reject)
_ => ip.enqueue_microtask(on_fulfill, [awaited])
}
return
}
}
// Fast path: if resolved_promise is a native Promise, bypass the JS-level
// .then property and attach reactions directly via the internal record.
// This avoids issues with overridden .then and matches spec PerformPromiseThen.
match resolved_promise {
Promise(pd) => attach_promise_reactions(ip, pd, on_fulfill, on_reject)
_ => {
// Non-native promise: use JS-level .then() property
let then_method = ip.get_property(resolved_promise, "then", loc) catch {
e => {
// get_property threw — propagate to on_reject
let err_val = js_error_to_value_with_env(e, Some(ip.global))
ip.enqueue_microtask(on_reject, [err_val])
return
}
}
// Check if then_method is callable before invoking
let is_callable = match then_method {
Object(od) => od.callable is Some(_)
_ => false
}
if is_callable {
try {
let _ = ip.call_value(
then_method,
resolved_promise,
[on_fulfill, on_reject],
loc,
)
} catch {
e => {
// .then() threw — propagate to on_reject
let err_val = js_error_to_value_with_env(e, Some(ip.global))
ip.enqueue_microtask(on_reject, [err_val])
}
}
} else {
// Non-callable .then — treat as non-thenable, fulfill directly
ip.enqueue_microtask(on_fulfill, [resolved_promise])
}
}
}
}
}
///|
/// Create an async generator function value (simple params).
fn Interpreter::make_async_generator_function(
self : Interpreter,
name : String?,
params : Array[String],
rest_param : String?,
body : Array[@ast.Stmt],
strict : Bool,
closure : Environment,
has_name_binding? : Bool = false,
is_method? : Bool = false,
source_text? : String? = None,
) -> Value {
make_async_gen_function_inner(
self,
name,
params,
None,
rest_param,
body,
strict,
closure,
has_name_binding,
is_method,
source_text,
)
}
///|
/// Create an async generator function value (extended params).
fn Interpreter::make_async_generator_function_ext(
self : Interpreter,
name : String?,
params : Array[@ast.Param],
rest_param : String?,
body : Array[@ast.Stmt],
strict : Bool,
closure : Environment,
has_name_binding? : Bool = false,
is_method? : Bool = false,
source_text? : String? = None,
) -> Value {
make_async_gen_function_inner(
self,
name,
[],
Some(params),
rest_param,
body,
strict,
closure,
has_name_binding,
is_method,
source_text,
)
}
///|
/// Shared factory for async generator functions.
/// Builds the function object with async generator prototype and class name.
fn make_async_gen_function_inner(
interp : Interpreter,
name : String?,
params : Array[String],
params_ext : Array[@ast.Param]?,
rest_param : String?,
body : Array[@ast.Stmt],
strict : Bool,
closure : Environment,
has_name_binding : Bool,
is_method : Bool,
source_text : String?,
) -> Value {
// Use the shared %AsyncGeneratorPrototype% singleton (set up during interpreter init).
let async_gen_proto : Value = match
interp.global.bindings.get("[[AsyncGeneratorPrototype]]") {
Some(binding) => binding.value
None => Null
}
let func_prototype = Object({
bag: PropertyBag(),
prototype: async_gen_proto,
callable: None,
class_name: "AsyncGenerator",
extensible: true,
arraybuffer_state: None,
})
// Use the shared %AsyncGeneratorFunction%.prototype (not the sync generator's prototype).
let gen_func_proto : Value = match
interp.global.bindings.get("[[AsyncGeneratorFunctionPrototype]]") {
Some(binding) => binding.value
None => Null
}
let func_name = match name {
Some(n) => n
None => ""
}
let func_length = match params_ext {
Some(pext) => expected_argument_count_ext(pext)
None => params.length()
}
let fn_props : Map[String, Value] = Map([])
fn_props["prototype"] = func_prototype
fn_props["name"] = String_(func_name)
fn_props["length"] = Number(func_length.to_double())
let internal_slots : Map[InternalSlotKey, Value] = Map([])
match source_text {
Some(text) => internal_slots[SourceText] = String_(text)
None => ()
}
let nf_desc : PropDescriptor = {
writable: false,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
let effective_closure : Environment = if has_name_binding {
match name {
Some(n) => {
let ne = Environment::new(parent=Some(closure))
ne.bindings[n] = {
value: Undefined,
kind: FunctionNameBinding,
initialized: true,
annex_b_hoisted: false,
is_parameter: false,
}
ne
}
None => closure
}
} else {
closure
}
let async_gen_func = stamp_function_realm(
Object({
bag: {
properties: fn_props,
symbol_properties: Map([]),
descriptors: {
"name": nf_desc,
"length": nf_desc,
"prototype": {
writable: true,
enumerable: false,
configurable: false,
getter: None,
setter: None,
is_accessor: false,
},
},
symbol_descriptors: Map([]),
internal_slots,
host_slots: Map([]),
},
prototype: gen_func_proto,
callable: Some(
InterpreterCallable(func_name, fn(ip, this_val, args) raise {
create_generator_instance(
ip,
this_val,
args,
name,
params,
params_ext,
rest_param,
body,
strict,
effective_closure,
func_prototype,
is_method~,
is_async=true,
)
}),
),
class_name: "AsyncGeneratorFunction",
extensible: true,
arraybuffer_state: None,
}),
realm_state=Some(interp.realm_state),
)
if has_name_binding {
match name {
Some(n) =>
match effective_closure.bindings.get(n) {
Some(b) => b.value = async_gen_func
None => ()
}
None => ()
}
}
async_gen_func
}
///|
/// Attach fulfill/reject reactions to a native Promise, dispatching based on state.
/// Used by both async_step and async_generator_step.
fn attach_promise_reactions(
ip : Interpreter,
pd : PromiseData,
on_fulfill : Value,
on_reject : Value,
) -> Unit {
match pd.state {
Fulfilled => ip.enqueue_microtask(on_fulfill, [pd.result])
Rejected => ip.enqueue_microtask(on_reject, [pd.result])
Pending => {
let noop = make_interp_method_func(
name="",
length=0,
realm_state=Some(ip.realm_state),
fn(_i : Interpreter, _t : Value, _a : Array[Value]) -> Value {
Undefined
},
)
pd.fulfill_reactions.push({
handler: Some(on_fulfill),
resolve: noop,
reject: noop,
reaction_type: Fulfill,
})
pd.reject_reactions.push({
handler: Some(on_reject),
resolve: noop,
reject: noop,
reaction_type: Reject,
})
}
}
}
///|
/// Async generator resume: drives generator and wraps result in a Promise.
fn async_generator_resume(
ip : Interpreter,
this_val : Value,
resume_kind : ResumeKind,
) -> Value {
let promise_data = new_promise_data()
let promise_val = Promise(promise_data)
let (resolve_fn, reject_fn) = create_resolving_functions(ip, promise_data)
async_generator_step(ip, this_val, resume_kind, resolve_fn, reject_fn)
promise_val
}
///|
/// Async generator step: drive generator, handle yields by chaining .then().
fn async_generator_step(
ip : Interpreter,
gen_val : Value,
resume_kind : ResumeKind,
resolve_fn : Value,
reject_fn : Value,
) -> Unit {
let loc = @token.Loc::default()
let result : Value = generator_resume(ip, gen_val, resume_kind) catch {
JsException(e) => {
// If delegate_resume raised after setting gen.state = Executing,
// clean up delegation state to avoid stale flags.
match get_generator_object(ip, gen_val) {
Some(g) => {
g.delegating = false
g.delegate_iterator = Undefined
g.delegate_next = Undefined
}
None => ()
}
let _ = ip.call_value(reject_fn, Undefined, [e], loc) catch {
_ => Undefined
}
return
}
e => {
match get_generator_object(ip, gen_val) {
Some(g) => {
g.delegating = false
g.delegate_iterator = Undefined
g.delegate_next = Undefined
}
None => ()
}
let err = js_error_to_value_with_env(e, Some(ip.global))
let _ = ip.call_value(reject_fn, Undefined, [err], loc) catch {
_ => Undefined
}
return
}
}
let result_value = ip.get_property(result, "value", loc) catch {
_ => Undefined
}
let result_done = ip.get_property(result, "done", loc) catch {
_ => Bool(false)
}
let is_done = match result_done {
Bool(b) => b
_ => false
}
if is_done {
// Generator completed — resolve the promise with a proper iterator result
// Per spec, iterator result objects have %Object.prototype% as [[Prototype]]
let obj_proto = ip.realm_state.get_obj_proto()
let result_value = ip.get_property(result, "value", loc) catch {
_ => Undefined
}
let proper_result = make_iterator_result_object_with_proto(
result_value, true, obj_proto,
)
let _ = ip.call_value(resolve_fn, Undefined, [proper_result], loc) catch {
_ => Undefined
}
} else {
// Generator yielded — check if the value is a native Promise to await
let awaited = result_value
let gen = gen_val
let res_fn = resolve_fn
let rej_fn = reject_fn
match awaited {
Promise(pd) => {
let gen_obj = match get_generator_object(ip, gen) {
Some(g) => g
None => return
}
// Rescue-copy resume_kind before creating closures — the closures fire
// asynchronously (Promise reaction microtask) by which time this stack
// frame is dead and the original binding is unreachable.
let orig_resume_kind = resume_kind
if gen_obj.delegating && gen_obj.is_async {
// Async yield* delegation: Promise resolves to IteratorResult {value, done}
let on_fulfill = make_interp_method_func(
name="",
length=1,
realm_state=Some(ip.realm_state),
fn(
i : Interpreter,
_this : Value,
args : Array[Value],
) -> Value raise {
let ir = if args.length() > 0 { args[0] } else { Undefined }
let ir_done = i.get_property(ir, "done", @token.Loc::default()) catch {
_ => Bool(false)
}
let is_ir_done = match ir_done {
Bool(b) => b
_ => false
}
let gen_obj = match get_generator_object(i, gen) {
Some(g) => g
None => return Undefined
}
if is_ir_done {
// Delegation completed — clear delegate state
gen_obj.delegating = false
gen_obj.delegate_iterator = Undefined
gen_obj.delegate_next = Undefined
let ir_value = i.get_property(
ir,
"value",
@token.Loc::default(),
) catch {
_ => Undefined
}
// Set up generator state and continue body via generator_continue.
// For Return completions, inject ReturnAction to preserve the return value
// through the generator body. For Next/Throw, use the corresponding action.
gen_obj.yield_value = ir_value
gen_obj.resume_action = match orig_resume_kind {
Return(_) => ReturnAction(ir_value)
_ => NextAction
}
let final_result = generator_continue(i, gen_obj)
let final_done = i.get_property(
final_result,
"done",
@token.Loc::default(),
) catch {
_ => Bool(true)
}
let is_final_done = match final_done {
Bool(b) => b
_ => true
}
if is_final_done {
// Create proper IteratorResult with Object.prototype per spec
let obj_proto = i.realm_state.get_obj_proto()
let final_val = i.get_property(
final_result,
"value",
@token.Loc::default(),
) catch {
_ => Undefined
}
let proper_result = make_iterator_result_object_with_proto(
final_val, true, obj_proto,
)
let _ = i.call_value(
res_fn,
Undefined,
[proper_result],
@token.Loc::default(),
) catch {
_ => Undefined
}
} else {
// Generator yielded again (e.g. from finally in Return case or
// a post-delegate yield) — let async_generator_step handle the value
let final_val = i.get_property(
final_result,
"value",
@token.Loc::default(),
) catch {
_ => Undefined
}
async_generator_step(i, gen, Next(final_val), res_fn, rej_fn)
}
} else {
// Delegation yielded — resolve outer promise with IteratorResult
gen_obj.state = SuspendedYield
let _ = i.call_value(
res_fn,
Undefined,
[ir],
@token.Loc::default(),
) catch {
_ => Undefined
}
}
Undefined
},
)
let on_reject = make_interp_method_func(
name="",
length=1,
realm_state=Some(ip.realm_state),
fn(i : Interpreter, _this : Value, args : Array[Value]) -> Value {
let e = if args.length() > 0 { args[0] } else { Undefined }
let gen_obj = match get_generator_object(i, gen) {
Some(g) => g
None => return Undefined
}
gen_obj.delegating = false
gen_obj.delegate_iterator = Undefined
gen_obj.delegate_next = Undefined
gen_obj.state = Completed
let _ = i.call_value(
rej_fn,
Undefined,
[e],
@token.Loc::default(),
) catch {
_ => Undefined
}
Undefined
},
)
attach_promise_reactions(ip, pd, on_fulfill, on_reject)
} else {
// Normal yield: Promise value — attach reactions to resume generator
let on_fulfill = make_interp_method_func(
name="",
length=1,
realm_state=Some(ip.realm_state),
fn(i : Interpreter, _this : Value, args : Array[Value]) -> Value {
let v = if args.length() > 0 { args[0] } else { Undefined }
async_generator_step(i, gen, Next(v), res_fn, rej_fn)
Undefined
},
)
let on_reject = make_interp_method_func(
name="",
length=1,
realm_state=Some(ip.realm_state),
fn(i : Interpreter, _this : Value, args : Array[Value]) -> Value {
let v = if args.length() > 0 { args[0] } else { Undefined }
async_generator_step(i, gen, Throw(v), res_fn, rej_fn)
Undefined
},
)
attach_promise_reactions(ip, pd, on_fulfill, on_reject)
}
}
_ => {
// Plain value — fulfill .next() promise with the iterator result
let _ = ip.call_value(resolve_fn, Undefined, [result], loc) catch {
_ => Undefined
}
}
}
}
}
///|
/// Get the function prototype for async function objects.
fn get_function_prototype(interp : Interpreter) -> Value {
match interp.global.bindings.get("[[AsyncFunctionPrototype]]") {
Some(binding) => binding.value
None =>
match interp.global.bindings.get("[[FunctionPrototype]]") {
Some(binding) => binding.value
None => Null
}
}
}
///|
/// Set up the AsyncFunction constructor and prototype chain.
/// Mirrors setup_generator_function_constructor in generator.mbt.
///
/// Prototype chain (per ES spec):
/// AsyncFunction.__proto__ === Function
/// AsyncFunction.prototype.__proto__ === Function.prototype
/// AsyncFunction.prototype[@@toStringTag] === "AsyncFunction"
/// AsyncFunction.prototype.constructor === AsyncFunction
pub fn setup_async_function_constructor(
env : Environment,
well_known_symbols~ : WellKnownSymbols,
) -> Unit {
// %AsyncFunction.prototype% inherits from Function.prototype
let func_proto : Value = match env.bindings.get("[[FunctionPrototype]]") {
Some(binding) => binding.value
None => Null
}
let af_sym_props : Map[Int, Value] = Map([])
let af_sym_descs : Map[Int, PropDescriptor] = Map([])
let tostringtag_sym = well_known_symbols.to_string_tag
af_sym_props[tostringtag_sym.id] = String_("AsyncFunction")
af_sym_descs[tostringtag_sym.id] = {
writable: false,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
let async_func_proto : Value = Object({
bag: {
properties: Map([]),
symbol_properties: af_sym_props,
descriptors: Map([]),
symbol_descriptors: af_sym_descs,
internal_slots: Map([]),
host_slots: Map([]),
},
prototype: func_proto,
callable: None,
class_name: "AsyncFunction",
extensible: true,
arraybuffer_state: None,
})
// AsyncFunction constructor: AsyncFunction(p1, p2, ..., body)
let async_func_ctor : Value = make_interp_method_func(
name="AsyncFunction",
length=1,
realm_state=env.realm_state,
fn(interp, _this, args) raise {
// §20.2.1.1: coerce all parameter args before the body arg
let param_parts : Array[String] = []
for i in 0..<(args.length() - 1) {
match args[i] {
String_(s) => param_parts.push(s)
_ => param_parts.push(to_js_string(args[i], interp=Some(interp)))
}
}
let body_str = match args {
[] => ""
[.., last] =>
match last {
String_(s) => s
_ => to_js_string(last, interp=Some(interp))
}
}
let params_str = param_parts.join(",")
let source = "async function anonymous(" +
params_str +
"\n) {\n" +
body_str +
"\n}"
let prog = @parser.parse(source)
match prog.stmts {
[AsyncFuncDecl(_, params, body, _, source_text), ..] => {
let body_strict = @static_semantics.has_use_strict(body)
validate_function_constructor_params(true, params, None, body)
interp.validate_block_early_errors(body, body_strict)
return interp.make_async_function(
Some("anonymous"),
params,
None,
body,
body_strict,
interp.global,
source_text~,
)
}
[AsyncFuncDeclExt(_, params, rest_param, body, _, source_text), ..] => {
let body_strict = @static_semantics.has_use_strict(body)
validate_function_constructor_params_ext(params, rest_param, body)
interp.validate_block_early_errors(body, body_strict)
return interp.make_async_function_ext(
Some("anonymous"),
params,
rest_param,
body,
body_strict,
interp.global,
source_text~,
)
}
_ => ()
}
raise @errors.SyntaxError(
message="Invalid AsyncFunction constructor source",
)
},
)
// AsyncFunction.prototype.constructor = AsyncFunction
match async_func_proto {
Object(data) => {
data.bag.properties["constructor"] = async_func_ctor
data.bag.descriptors["constructor"] = {
writable: false,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
}
_ => ()
}
// AsyncFunction.prototype property on the constructor
match async_func_ctor {
Object(data) => {
data.bag.properties["prototype"] = async_func_proto
data.bag.descriptors["prototype"] = {
writable: false,
enumerable: false,
configurable: false,
getter: None,
setter: None,
is_accessor: false,
}
}
_ => ()
}
// §27.7.2: %AsyncFunction%.[[Prototype]] is the intrinsic %Function% constructor.
let func_ctor = match env.bindings.get("Function") {
Some(binding) => binding.value
None => Null
}
match async_func_ctor {
Object(data) => data.prototype = func_ctor
_ => ()
}
// Note: AsyncFunction is NOT a global property per spec (section 19).
// It is only reachable via (async function(){}).constructor.
// We store it as an internal binding so async functions get the right prototype.
env.def_builtin("[[AsyncFunctionPrototype]]", async_func_proto)
}
///|
/// Set up the AsyncGeneratorFunction constructor and prototype chain.
/// Mirrors setup_generator_function_constructor in generator.mbt.
///
/// Prototype chain (per ES spec §27.4):
/// %AsyncGeneratorFunction%.__proto__ === Function
/// %AsyncGeneratorFunction%.prototype.__proto__ === Function.prototype
/// %AsyncGeneratorFunction%.prototype[@@toStringTag] === "AsyncGeneratorFunction"
/// %AsyncGeneratorFunction%.prototype.constructor === %AsyncGeneratorFunction%
/// %AsyncGeneratorFunction%.prototype.prototype === %AsyncGeneratorPrototype%
/// %AsyncIteratorPrototype%.__proto__ === Object.prototype
/// %AsyncIteratorPrototype%[@@asyncIterator] returns this
/// %AsyncGeneratorPrototype%.__proto__ === %AsyncIteratorPrototype%
/// %AsyncGeneratorPrototype%.constructor === %AsyncGeneratorFunction%.prototype
/// %AsyncGeneratorPrototype%[@@toStringTag] === "AsyncGenerator"
pub fn setup_async_generator_function_constructor(
env : Environment,
well_known_symbols~ : WellKnownSymbols,
) -> Unit {
let async_iter_sym = well_known_symbols.async_iterator
let to_string_tag_sym = well_known_symbols.to_string_tag
let object_prototype = env.get("[[ObjectPrototype]]") catch { _ => Null }
let func_proto : Value = match env.bindings.get("[[FunctionPrototype]]") {
Some(binding) => binding.value
None => Null
}
let non_enum : PropDescriptor = {
writable: true,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
let non_enum_non_writable : PropDescriptor = {
writable: false,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
let frozen : PropDescriptor = {
writable: false,
enumerable: false,
configurable: false,
getter: None,
setter: None,
is_accessor: false,
}
// Build %AsyncGeneratorFunction%.prototype first so it can be used as the
// constructor value in %AsyncGeneratorPrototype%'s property map, ensuring
// insertion order follows spec §27.4.3: constructor, prototype (string keys).
let agf_sym_props : Map[Int, Value] = Map([])
let agf_sym_descs : Map[Int, PropDescriptor] = Map([])
agf_sym_props[to_string_tag_sym.id] = String_("AsyncGeneratorFunction")
agf_sym_descs[to_string_tag_sym.id] = non_enum_non_writable
let agf_proto : Value = Object({
bag: {
properties: Map([]),
symbol_properties: agf_sym_props,
descriptors: Map([]),
symbol_descriptors: agf_sym_descs,
internal_slots: Map([]),
host_slots: Map([]),
},
prototype: func_proto,
callable: None,
class_name: "AsyncGeneratorFunction",
extensible: true,
arraybuffer_state: None,
})
// Build %AsyncIteratorPrototype% (§27.1.2) — owns Symbol.asyncIterator only.
// %AsyncGeneratorPrototype% inherits from it; own-key checks on the latter
// must not see Symbol.asyncIterator.
let aip_sym_props : Map[Int, Value] = Map([])
let aip_sym_descs : Map[Int, PropDescriptor] = Map([])
aip_sym_props[async_iter_sym.id] = make_method_func(
name="[Symbol.asyncIterator]",
length=0,
realm_state=env.realm_state,
fn(this_val, _args) { this_val },
)
aip_sym_descs[async_iter_sym.id] = non_enum
let async_iter_proto : Value = Object({
bag: {
properties: Map([]),
symbol_properties: aip_sym_props,
descriptors: Map([]),
symbol_descriptors: aip_sym_descs,
internal_slots: Map([]),
host_slots: Map([]),
},
prototype: object_prototype,
callable: None,
class_name: "AsyncIterator",
extensible: true,
arraybuffer_state: None,
})
// Build %AsyncGeneratorPrototype% — shared singleton for all async gen instances.
// [[Prototype]] = %AsyncIteratorPrototype% (not Object.prototype).
// Own string keys follow spec §27.6: constructor, next, return, throw.
// Own symbol keys: Symbol.toStringTag only — Symbol.asyncIterator is inherited.
let agp_props : Map[String, Value] = Map([])
let agp_sym_props : Map[Int, Value] = Map([])
let agp_descs : Map[String, PropDescriptor] = Map([])
let agp_sym_descs : Map[Int, PropDescriptor] = Map([])
agp_props["constructor"] = agf_proto
agp_descs["constructor"] = non_enum_non_writable
agp_props["next"] = make_interp_method_func(
name="next",
length=1,
realm_state=env.realm_state,
fn(ip, this_val, args) {
let arg = match args {
[first, ..] => first
[] => Undefined
}
async_generator_resume(ip, this_val, Next(arg))
},
)
agp_descs["next"] = non_enum
agp_props["return"] = make_interp_method_func(
name="return",
length=1,
realm_state=env.realm_state,
fn(ip, this_val, args) {
let arg = match args {
[first, ..] => first
[] => Undefined
}
async_generator_resume(ip, this_val, Return(arg))
},
)
agp_descs["return"] = non_enum
agp_props["throw"] = make_interp_method_func(
name="throw",
length=1,
realm_state=env.realm_state,
fn(ip, this_val, args) {
let arg = match args {
[first, ..] => first
[] => Undefined
}
async_generator_resume(ip, this_val, Throw(arg))
},
)
agp_descs["throw"] = non_enum
agp_sym_props[to_string_tag_sym.id] = String_("AsyncGenerator")
agp_sym_descs[to_string_tag_sym.id] = non_enum_non_writable
let async_gen_proto : Value = Object({
bag: {
properties: agp_props,
symbol_properties: agp_sym_props,
descriptors: agp_descs,
symbol_descriptors: agp_sym_descs,
internal_slots: Map([]),
host_slots: Map([]),
},
prototype: async_iter_proto,
callable: None,
class_name: "AsyncGenerator",
extensible: true,
arraybuffer_state: None,
})
// Build AsyncGeneratorFunction constructor
let async_gen_func_ctor : Value = make_interp_method_func(
name="AsyncGeneratorFunction",
length=1,
realm_state=env.realm_state,
fn(interp, _this, args) raise {
// §20.2.1.1: coerce all parameter args before the body arg
let param_parts : Array[String] = []
for i in 0..<(args.length() - 1) {
match args[i] {
String_(s) => param_parts.push(s)
_ => param_parts.push(to_js_string(args[i], interp=Some(interp)))
}
}
let body_str = match args {
[] => ""
[.., last] =>
match last {
String_(s) => s
_ => to_js_string(last, interp=Some(interp))
}
}
let params_str = param_parts.join(",")
let source = "async function* anonymous(" +
params_str +
"\n) {\n" +
body_str +
"\n}"
let prog = @parser.parse(source)
match prog.stmts {
[AsyncGeneratorDecl(_, params, body, _, source_text), ..] => {
let body_strict = @static_semantics.has_use_strict(body)
validate_function_constructor_params(true, params, None, body)
interp.validate_block_early_errors(body, body_strict)
return interp.make_async_generator_function(
Some("anonymous"),
params,
None,
body,
body_strict,
interp.global,
source_text~,
)
}
[AsyncGeneratorDeclExt(_, params, rest_param, body, _, source_text), ..] => {
let body_strict = @static_semantics.has_use_strict(body)
validate_function_constructor_params_ext(params, rest_param, body)
interp.validate_block_early_errors(body, body_strict)
return interp.make_async_generator_function_ext(
Some("anonymous"),
params,
rest_param,
body,
body_strict,
interp.global,
source_text~,
)
}
_ => ()
}
raise @errors.SyntaxError(
message="Invalid AsyncGeneratorFunction constructor source",
)
},
)
// Wire up cross-references between the three objects.
// %AsyncGeneratorPrototype%.constructor was set at construction time (agf_proto).
// §27.4.3: agf_proto string keys must be constructor (§27.4.3.1) then prototype (§27.4.3.2).
match agf_proto {
Object(data) => {
data.bag.properties["constructor"] = async_gen_func_ctor
data.bag.descriptors["constructor"] = non_enum_non_writable
// §27.4.3.2: prototype property on %AsyncGeneratorFunction%.prototype is configurable
data.bag.properties["prototype"] = async_gen_proto
data.bag.descriptors["prototype"] = non_enum_non_writable
}
_ => ()
}
// §27.4.1: %AsyncGeneratorFunction%.[[Prototype]] = Function (not Function.prototype)
let function_ctor = env.get("Function") catch { _ => Null }
match async_gen_func_ctor {
Object(data) => {
data.bag.properties["prototype"] = agf_proto
data.bag.descriptors["prototype"] = frozen
data.prototype = function_ctor
}
_ => ()
}
env.def_builtin("[[AsyncIteratorPrototype]]", async_iter_proto)
env.def_builtin("[[AsyncGeneratorPrototype]]", async_gen_proto)
env.def_builtin("[[AsyncGeneratorFunctionPrototype]]", agf_proto)
}
///|
/// Get an async iterator from an iterable for for-await-of.
/// Tries [Symbol.asyncIterator]() first, falls back to wrapping [Symbol.iterator]()
/// via CreateAsyncFromSyncIterator.
fn get_async_iterator_for_of(
interp : Interpreter,
iterable : Value,
loc : @token.Loc,
) -> Value raise Error {
let async_iter_sym = interp.realm_state.well_known_symbols.async_iterator
let async_iter_method = interp.get_computed_property(
iterable,
Symbol(async_iter_sym),
loc,
)
match async_iter_method {
Object(data) =>
match data.callable {
Some(_) => interp.call_value(async_iter_method, iterable, [], loc)
None =>
raise @errors.TypeError(
message="Result of the Symbol.asyncIterator method is not callable",
)
}
Undefined | Null => {
// Fall back to sync iterator wrapper
let sync_iter_sym = interp.realm_state.well_known_symbols.iterator
let sync_iter_method = interp.get_computed_property(
iterable,
Symbol(sync_iter_sym),
loc,
)
let sync_iter = match sync_iter_method {
Object(data) =>
match data.callable {
Some(_) => interp.call_value(sync_iter_method, iterable, [], loc)
None =>
raise @errors.TypeError(
message="Result of the Symbol.iterator method is not an object",
)
}
_ =>
raise @errors.TypeError(
message="\{type_of(iterable)} is not async iterable",
)
}
let sync_next = interp.get_iterator_next_method(sync_iter, loc)
create_async_from_sync_iterator(interp, sync_iter, sync_next, loc)
}
_ =>
raise @errors.TypeError(
message="Result of the Symbol.asyncIterator method is not an object",
)
}
}