diff --git a/interpreter/runtime/async.mbt b/interpreter/runtime/async.mbt index 9582ce21..7816e12e 100644 --- a/interpreter/runtime/async.mbt +++ b/interpreter/runtime/async.mbt @@ -853,6 +853,15 @@ pub fn setup_async_function_constructor( } _ => () } + // §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. diff --git a/interpreter/runtime/generator.mbt b/interpreter/runtime/generator.mbt index 634b005c..b0214875 100644 --- a/interpreter/runtime/generator.mbt +++ b/interpreter/runtime/generator.mbt @@ -105,6 +105,162 @@ fn complete_generator(gen : GeneratorObject) -> Unit { gen.try_resume_pending_error = None } +///| +/// §20.2.1.1.1 step 17a: Returns true if expr contains a YieldExpression at +/// any level, stopping at nested function boundaries (which have their own +/// generator context and may use yield freely). +fn expr_contains_yield(expr : @ast.Expr) -> Bool { + match expr { + YieldExpr(_, _, _) => true + Binary(_, a, b, _) => expr_contains_yield(a) || expr_contains_yield(b) + Unary(_, e, _) + | UpdateExpr(_, e, _, _) + | Grouping(e, _) + | SpreadExpr(e, _) => expr_contains_yield(e) + Assign(_, e, _) => expr_contains_yield(e) + MemberAssign(obj, _, val, _) => + expr_contains_yield(obj) || expr_contains_yield(val) + ComputedAssign(obj, key, val, _) => + expr_contains_yield(obj) || + expr_contains_yield(key) || + expr_contains_yield(val) + CompoundAssign(_, a, b, _) + | Comma(a, b, _) + | WebCompatCallAssign(a, b, _) => + expr_contains_yield(a) || expr_contains_yield(b) + Member(obj, _, _) | OptionalMember(obj, _, _) | ChainMember(obj, _, _) => + expr_contains_yield(obj) + ComputedMember(obj, key, _) + | OptionalComputedMember(obj, key, _) + | ChainComputedMember(obj, key, _) => + expr_contains_yield(obj) || expr_contains_yield(key) + SuperComputedMember(e, _) => expr_contains_yield(e) + Ternary(cond, t, f, _) => + expr_contains_yield(cond) || + expr_contains_yield(t) || + expr_contains_yield(f) + Call(f, args, _) | NewExpr(f, args, _) | OptionalCall(f, args, _) => + expr_contains_yield(f) || + args.iter().any(fn(a) { expr_contains_yield(a) }) + SuperCall(args, _) => args.iter().any(fn(a) { expr_contains_yield(a) }) + ArrayLit(elems, _) => elems.iter().any(fn(e) { expr_contains_yield(e) }) + ObjectLit(props, _) => + props + .iter() + .any(fn(p) { expr_contains_yield(p.key) || expr_contains_yield(p.value) }) + TemplateLit(_, exprs, _) => + exprs.iter().any(fn(e) { expr_contains_yield(e) }) + TaggedTemplate(tag, _, exprs, _) => + expr_contains_yield(tag) || + exprs.iter().any(fn(e) { expr_contains_yield(e) }) + // DestructureAssign LHS pattern can contain computed keys or defaults with + // yield expressions ({[yield]: a} = {}, {a = yield} = {}) — scan both sides. + DestructureAssign(pat, rhs, _) => + pattern_contains_yield(pat) || expr_contains_yield(rhs) + // Arrow params (including defaults) are evaluated in the enclosing scope, + // so ArrowFuncExt/AsyncArrowFuncExt are NOT full boundaries for yield. + // Simple ArrowFunc/AsyncArrowFunc have no defaults, so false is safe. + ArrowFuncExt(params, _, _, _, _) | AsyncArrowFuncExt(params, _, _, _, _) => + params_contain_yield(params) + // Nested function boundaries — yield inside is not the outer generator's + FuncExpr(_, _, _, _, _) + | FuncExprExt(_, _, _, _, _, _) + | GeneratorExpr(_, _, _, _, _) + | GeneratorExprExt(_, _, _, _, _, _) + | AsyncFuncExpr(_, _, _, _, _) + | AsyncFuncExprExt(_, _, _, _, _, _) + | AsyncArrowFunc(_, _, _, _) + | AsyncGeneratorExpr(_, _, _, _, _) + | AsyncGeneratorExprExt(_, _, _, _, _, _) + | ArrowFunc(_, _, _, _) => false + // Class is NOT a full boundary: heritage and computed member keys are + // evaluated in the surrounding scope (ES §15.7.1 ClassElementName). + ClassExpr(_, superclass, members, _, _) => { + let heritage = match superclass { + Some(e) => expr_contains_yield(e) + None => false + } + let computed = members + .iter() + .any(fn(m) { + match m { + Method(cm) => cm.computed && expr_contains_yield(cm.key) + Field(cf) => cf.computed && expr_contains_yield(cf.key) + } + }) + heritage || computed + } + _ => false + } +} + +///| +// Walk a binding pattern for yield expressions in default values and computed +// property keys. Property name strings (e.g. the "yield" in {yield: x}) are +// not expressions and are intentionally not flagged here. +fn pattern_contains_yield(pat : @ast.Pattern) -> Bool { + match pat { + IdentPat(_) => false + DefaultPat(inner, default_expr) => + pattern_contains_yield(inner) || expr_contains_yield(default_expr) + ArrayPat(elements, rest) => { + let elems_yield = elements + .iter() + .any(fn(e) { + match e { + Some(p) => pattern_contains_yield(p) + None => false + } + }) + let rest_yield = match rest { + Some(r) => pattern_contains_yield(r) + None => false + } + elems_yield || rest_yield + } + AssignTarget(e) => expr_contains_yield(e) + ObjectPat(props, rest) => { + let props_yield = props + .iter() + .any(fn(prop) { + // Check computed key expression (e.g. {[yield]: x}), shorthand or + // keyed default (e.g. {x = yield} or {a: b = yield}), and nested pat. + let computed_yield = match prop.computed_key { + Some(e) => expr_contains_yield(e) + None => false + } + let default_yield = match prop.default_val { + Some(e) => expr_contains_yield(e) + None => false + } + computed_yield || default_yield || pattern_contains_yield(prop.value) + }) + let rest_yield = match rest { + Some(r) => pattern_contains_yield(r) + None => false + } + props_yield || rest_yield + } + } +} + +///| +fn params_contain_yield(params : Array[@ast.Param]) -> Bool { + params + .iter() + .any(fn(p) { + let default_has_yield = match p.default_val { + Some(expr) => expr_contains_yield(expr) + None => false + } + let pattern_has_yield = match p.pattern { + Some(pat) => pattern_contains_yield(pat) + None => false + } + default_has_yield || pattern_has_yield + }) +} + ///| /// Set up the GeneratorFunction constructor on the given interpreter. /// GeneratorFunction("a", "yield a") creates a generator function, @@ -240,22 +396,22 @@ pub fn setup_generator_function_constructor( length=1, realm_state=env.realm_state, fn(interp, _this, args) raise { - // Last arg is body, previous args are parameter names. With no - // arguments, the synthesized body and parameter list are both empty. - let body_str = if args.length() == 0 { - "" - } else { - match args[args.length() - 1] { - String_(s) => s - _ => args[args.length() - 1].to_string() - } - } + // §20.2.1.1.1: coerce params first (step 5a), body last (step 6). + // Last arg is body, previous args are parameter names. let param_count = if args.length() == 0 { 0 } else { args.length() - 1 } let param_parts : Array[String] = [] for i = 0; i < param_count; i = i + 1 { match args[i] { String_(s) => param_parts.push(s) - _ => param_parts.push(args[i].to_string()) + _ => param_parts.push(interp.to_js_string(args[i])) + } + } + let body_str = if args.length() == 0 { + "" + } else { + match args[args.length() - 1] { + String_(s) => s + _ => interp.to_js_string(args[args.length() - 1]) } } let params_str = param_parts.join(",") @@ -282,6 +438,14 @@ pub fn setup_generator_function_constructor( ) } GeneratorDeclExt(_, params, rest_param, body, _, source_text) => { + // §20.2.1.1.1 step 17a: YieldExpression in generator params → SyntaxError. + // The parser now enters generator context for params, so `yield` in + // default values is parsed as YieldExpr and caught here via AST walk. + if params_contain_yield(params) { + raise @errors.SyntaxError( + message="Generator function parameters may not contain yield expressions", + ) + } 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) diff --git a/interpreter/stdlib/builtins_function.mbt b/interpreter/stdlib/builtins_function.mbt index 294d91aa..4f1a7357 100644 --- a/interpreter/stdlib/builtins_function.mbt +++ b/interpreter/stdlib/builtins_function.mbt @@ -8,6 +8,42 @@ fn func_kind_prefix(class_name : String) -> String { } } +///| +// §20.2.1.1.1 step 27a: create a .prototype object that inherits from +// Object.prototype. Extracted so all three Function-constructor return paths +// stay consistent if the construction ever changes. +fn make_function_prototype(realm_state : @runtime.RealmState) -> Value { + let object_proto = @runtime.get_obj_proto(realm_state=Some(realm_state)) + Value::Object({ + bag: @runtime.PropertyBag(), + prototype: object_proto, + callable: None, + class_name: "Object", + extensible: true, + arraybuffer_state: None, + }) +} + +///| +// §20.2.1.1.1 step 27a: wire result.prototype.constructor = result so the +// constructed function's prototype correctly back-references the function. +fn wire_constructor_to_prototype(proto : Value, result : Value) -> Unit { + match proto { + Object(proto_data) => { + proto_data.bag.properties["constructor"] = result + proto_data.bag.descriptors["constructor"] = { + writable: true, + enumerable: false, + configurable: true, + getter: None, + setter: None, + is_accessor: false, + } + } + _ => () + } +} + ///| /// ES262 §10.2.4 ExpectedArgumentCount for extended parameter lists: /// count parameters before the first default initializer or rest parameter. @@ -422,9 +458,11 @@ fn setup_function_builtins( if prog.stmts.length() > 0 { match prog.stmts[0] { FuncDecl(_, params, body, _, _) => { + let proto = make_function_prototype(realm_state) let fn_props : Map[String, Value] = {} fn_props["length"] = Value::Number(params.length().to_double()) fn_props["name"] = Value::String_("anonymous") + fn_props["prototype"] = proto // Validate static early errors against the function's strict context. let body_strict = @static_semantics.has_use_strict(body) @runtime.validate_function_constructor_params( @@ -434,12 +472,23 @@ fn setup_function_builtins( body, ) interp.validate_block_early_errors(body, body_strict) - return @runtime.stamp_function_realm( + let result = @runtime.stamp_function_realm( Value::Object({ bag: { properties: fn_props, symbol_properties: {}, - descriptors: { "length": nf_desc, "name": nf_desc }, + descriptors: { + "length": nf_desc, + "name": nf_desc, + "prototype": { + writable: true, + enumerable: false, + configurable: false, + getter: None, + setter: None, + is_accessor: false, + }, + }, symbol_descriptors: {}, internal_slots: {}, }, @@ -463,24 +512,39 @@ fn setup_function_builtins( }), realm_state=Some(realm_state), ) + wire_constructor_to_prototype(proto, result) + return result } FuncDeclExt(_, params, rest_param, body, _, _) => { + let proto = make_function_prototype(realm_state) let fn_props : Map[String, Value] = {} let fn_length = expected_argument_count_ext(params) fn_props["length"] = Value::Number(fn_length.to_double()) fn_props["name"] = Value::String_("anonymous") + fn_props["prototype"] = proto // Validate static early errors against the function's strict context. let body_strict = @static_semantics.has_use_strict(body) @runtime.validate_function_constructor_params_ext( params, rest_param, body, ) interp.validate_block_early_errors(body, body_strict) - return @runtime.stamp_function_realm( + let result = @runtime.stamp_function_realm( Value::Object({ bag: { properties: fn_props, symbol_properties: {}, - descriptors: { "length": nf_desc, "name": nf_desc }, + descriptors: { + "length": nf_desc, + "name": nf_desc, + "prototype": { + writable: true, + enumerable: false, + configurable: false, + getter: None, + setter: None, + is_accessor: false, + }, + }, symbol_descriptors: {}, internal_slots: {}, }, @@ -504,20 +568,35 @@ fn setup_function_builtins( }), realm_state=Some(realm_state), ) + wire_constructor_to_prototype(proto, result) + return result } _ => () } } - // Fallback: empty function - @runtime.stamp_function_realm( + // Fallback: empty function (defensive — synthesized source always parses) + let fb_proto = make_function_prototype(realm_state) + let fb_result = @runtime.stamp_function_realm( Value::Object({ bag: { properties: { "length": Value::Number(0.0), "name": Value::String_("anonymous"), + "prototype": fb_proto, }, symbol_properties: {}, - descriptors: { "length": nf_desc, "name": nf_desc }, + descriptors: { + "length": nf_desc, + "name": nf_desc, + "prototype": { + writable: true, + enumerable: false, + configurable: false, + getter: None, + setter: None, + is_accessor: false, + }, + }, symbol_descriptors: {}, internal_slots: {}, }, @@ -529,6 +608,8 @@ fn setup_function_builtins( }), realm_state=Some(realm_state), ) + wire_constructor_to_prototype(fb_proto, fb_result) + fb_result }), ), class_name: "Function", diff --git a/parser/early_errors.mbt b/parser/early_errors.mbt new file mode 100644 index 00000000..31ef3b97 --- /dev/null +++ b/parser/early_errors.mbt @@ -0,0 +1,275 @@ +///| +// Early-error helpers for formal-parameter validation. +// +// §15.5.1: generator formal parameters must not Contains(YieldExpression). +// §15.8.1: async formal parameters must not Contains(AwaitExpression). +// +// These walkers mirror the runtime versions in interpreter/runtime/generator.mbt +// so the parser can enforce early errors at parse time rather than at the +// dynamic GeneratorFunction/AsyncFunction constructor call site. + +///| +fn params_contain_yield(params : Array[@ast.Param]) -> Bool { + params + .iter() + .any(fn(p) { + let dv = match p.default_val { + Some(e) => expr_has_yield(e) + None => false + } + let pt = match p.pattern { + Some(pat) => pat_has_yield(pat) + None => false + } + dv || pt + }) +} + +///| +fn params_contain_await(params : Array[@ast.Param]) -> Bool { + params + .iter() + .any(fn(p) { + let dv = match p.default_val { + Some(e) => expr_has_await(e) + None => false + } + let pt = match p.pattern { + Some(pat) => pat_has_await(pat) + None => false + } + dv || pt + }) +} + +///| +fn expr_has_yield(expr : @ast.Expr) -> Bool { + match expr { + YieldExpr(_, _, _) => true + Binary(_, a, b, _) => expr_has_yield(a) || expr_has_yield(b) + Unary(_, e, _) + | UpdateExpr(_, e, _, _) + | Grouping(e, _) + | SpreadExpr(e, _) => expr_has_yield(e) + Assign(_, e, _) => expr_has_yield(e) + MemberAssign(obj, _, val, _) => expr_has_yield(obj) || expr_has_yield(val) + ComputedAssign(obj, key, val, _) => + expr_has_yield(obj) || expr_has_yield(key) || expr_has_yield(val) + CompoundAssign(_, a, b, _) + | Comma(a, b, _) + | WebCompatCallAssign(a, b, _) => expr_has_yield(a) || expr_has_yield(b) + Member(obj, _, _) | OptionalMember(obj, _, _) | ChainMember(obj, _, _) => + expr_has_yield(obj) + ComputedMember(obj, key, _) + | OptionalComputedMember(obj, key, _) + | ChainComputedMember(obj, key, _) => + expr_has_yield(obj) || expr_has_yield(key) + SuperComputedMember(e, _) => expr_has_yield(e) + Ternary(cond, t, f, _) => + expr_has_yield(cond) || expr_has_yield(t) || expr_has_yield(f) + Call(f, args, _) | NewExpr(f, args, _) | OptionalCall(f, args, _) => + expr_has_yield(f) || args.iter().any(fn(a) { expr_has_yield(a) }) + SuperCall(args, _) => args.iter().any(fn(a) { expr_has_yield(a) }) + ArrayLit(elems, _) => elems.iter().any(fn(e) { expr_has_yield(e) }) + ObjectLit(props, _) => + props + .iter() + .any(fn(p) { expr_has_yield(p.key) || expr_has_yield(p.value) }) + TemplateLit(_, exprs, _) => exprs.iter().any(fn(e) { expr_has_yield(e) }) + TaggedTemplate(tag, _, exprs, _) => + expr_has_yield(tag) || exprs.iter().any(fn(e) { expr_has_yield(e) }) + DestructureAssign(pat, rhs, _) => pat_has_yield(pat) || expr_has_yield(rhs) + // Arrow param defaults are evaluated in the enclosing generator scope. + ArrowFuncExt(params, _, _, _, _) | AsyncArrowFuncExt(params, _, _, _, _) => + params_contain_yield(params) + // Class heritage and computed member keys are in the enclosing scope. + ClassExpr(_, superclass, members, _, _) => { + let h = match superclass { + Some(e) => expr_has_yield(e) + None => false + } + let m = members + .iter() + .any(fn(mbr) { + match mbr { + Method(cm) => cm.computed && expr_has_yield(cm.key) + Field(cf) => cf.computed && expr_has_yield(cf.key) + } + }) + h || m + } + // Full function boundaries — yield inside is not the outer generator's. + ArrowFunc(_, _, _, _) + | AsyncArrowFunc(_, _, _, _) + | FuncExpr(_, _, _, _, _) + | FuncExprExt(_, _, _, _, _, _) + | GeneratorExpr(_, _, _, _, _) + | GeneratorExprExt(_, _, _, _, _, _) + | AsyncFuncExpr(_, _, _, _, _) + | AsyncFuncExprExt(_, _, _, _, _, _) + | AsyncGeneratorExpr(_, _, _, _, _) + | AsyncGeneratorExprExt(_, _, _, _, _, _) => false + _ => false + } +} + +///| +fn pat_has_yield(pat : @ast.Pattern) -> Bool { + match pat { + IdentPat(_) => false + DefaultPat(inner, default_expr) => + pat_has_yield(inner) || expr_has_yield(default_expr) + AssignTarget(e) => expr_has_yield(e) + ArrayPat(elements, rest) => { + let ee = elements + .iter() + .any(fn(e) { + match e { + Some(p) => pat_has_yield(p) + None => false + } + }) + let rr = match rest { + Some(r) => pat_has_yield(r) + None => false + } + ee || rr + } + ObjectPat(props, rest) => { + let pp = props + .iter() + .any(fn(prop) { + let ck = match prop.computed_key { + Some(e) => expr_has_yield(e) + None => false + } + let dv = match prop.default_val { + Some(e) => expr_has_yield(e) + None => false + } + ck || dv || pat_has_yield(prop.value) + }) + let rr = match rest { + Some(r) => pat_has_yield(r) + None => false + } + pp || rr + } + } +} + +///| +fn expr_has_await(expr : @ast.Expr) -> Bool { + match expr { + AwaitExpr(_, _) => true + Binary(_, a, b, _) => expr_has_await(a) || expr_has_await(b) + Unary(_, e, _) + | UpdateExpr(_, e, _, _) + | Grouping(e, _) + | SpreadExpr(e, _) => expr_has_await(e) + Assign(_, e, _) => expr_has_await(e) + MemberAssign(obj, _, val, _) => expr_has_await(obj) || expr_has_await(val) + ComputedAssign(obj, key, val, _) => + expr_has_await(obj) || expr_has_await(key) || expr_has_await(val) + CompoundAssign(_, a, b, _) + | Comma(a, b, _) + | WebCompatCallAssign(a, b, _) => expr_has_await(a) || expr_has_await(b) + Member(obj, _, _) | OptionalMember(obj, _, _) | ChainMember(obj, _, _) => + expr_has_await(obj) + ComputedMember(obj, key, _) + | OptionalComputedMember(obj, key, _) + | ChainComputedMember(obj, key, _) => + expr_has_await(obj) || expr_has_await(key) + SuperComputedMember(e, _) => expr_has_await(e) + Ternary(cond, t, f, _) => + expr_has_await(cond) || expr_has_await(t) || expr_has_await(f) + Call(f, args, _) | NewExpr(f, args, _) | OptionalCall(f, args, _) => + expr_has_await(f) || args.iter().any(fn(a) { expr_has_await(a) }) + SuperCall(args, _) => args.iter().any(fn(a) { expr_has_await(a) }) + ArrayLit(elems, _) => elems.iter().any(fn(e) { expr_has_await(e) }) + ObjectLit(props, _) => + props + .iter() + .any(fn(p) { expr_has_await(p.key) || expr_has_await(p.value) }) + TemplateLit(_, exprs, _) => exprs.iter().any(fn(e) { expr_has_await(e) }) + TaggedTemplate(tag, _, exprs, _) => + expr_has_await(tag) || exprs.iter().any(fn(e) { expr_has_await(e) }) + DestructureAssign(pat, rhs, _) => pat_has_await(pat) || expr_has_await(rhs) + // Regular arrow param defaults are in the enclosing async scope. + ArrowFuncExt(params, _, _, _, _) => params_contain_await(params) + // Class heritage and computed member keys are in the enclosing scope. + ClassExpr(_, superclass, members, _, _) => { + let h = match superclass { + Some(e) => expr_has_await(e) + None => false + } + let m = members + .iter() + .any(fn(mbr) { + match mbr { + Method(cm) => cm.computed && expr_has_await(cm.key) + Field(cf) => cf.computed && expr_has_await(cf.key) + } + }) + h || m + } + // Full function/async boundaries — await inside is not the outer async's. + ArrowFunc(_, _, _, _) + | AsyncArrowFunc(_, _, _, _) + | AsyncArrowFuncExt(_, _, _, _, _) + | FuncExpr(_, _, _, _, _) + | FuncExprExt(_, _, _, _, _, _) + | GeneratorExpr(_, _, _, _, _) + | GeneratorExprExt(_, _, _, _, _, _) + | AsyncFuncExpr(_, _, _, _, _) + | AsyncFuncExprExt(_, _, _, _, _, _) + | AsyncGeneratorExpr(_, _, _, _, _) + | AsyncGeneratorExprExt(_, _, _, _, _, _) => false + _ => false + } +} + +///| +fn pat_has_await(pat : @ast.Pattern) -> Bool { + match pat { + IdentPat(_) => false + DefaultPat(inner, default_expr) => + pat_has_await(inner) || expr_has_await(default_expr) + AssignTarget(e) => expr_has_await(e) + ArrayPat(elements, rest) => { + let ee = elements + .iter() + .any(fn(e) { + match e { + Some(p) => pat_has_await(p) + None => false + } + }) + let rr = match rest { + Some(r) => pat_has_await(r) + None => false + } + ee || rr + } + ObjectPat(props, rest) => { + let pp = props + .iter() + .any(fn(prop) { + let ck = match prop.computed_key { + Some(e) => expr_has_await(e) + None => false + } + let dv = match prop.default_val { + Some(e) => expr_has_await(e) + None => false + } + ck || dv || pat_has_await(prop.value) + }) + let rr = match rest { + Some(r) => pat_has_await(r) + None => false + } + pp || rr + } + } +} diff --git a/parser/expr.mbt b/parser/expr.mbt index a15634b9..6aa31c3c 100644 --- a/parser/expr.mbt +++ b/parser/expr.mbt @@ -1406,6 +1406,10 @@ fn Parser::parse_object_literal(self : Parser) -> @ast.Expr raise Error { let method_start = obj_member_start let saved_pos = self.pos let meth_name = if computed { None } else { Some(key_name) } + // Push context before params so method params see a clean scope, + // isolating them from any outer generator's context. + self.push_generator_context(false) + self.push_async_context(obj_is_async) let func_data : @ast.Expr = try { let params = self.parse_params() // Validate getter/setter arity @@ -1424,9 +1428,15 @@ fn Parser::parse_object_literal(self : Parser) -> @ast.Expr raise Error { } @ast.Init | @ast.Spread => () } - self.push_async_context(obj_is_async) - let body = self.parse_block_body() + let body = self.parse_block_body() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } self.pop_async_context() + self.pop_generator_context() if obj_is_async { @ast.Expr::AsyncFuncExpr( meth_name, @@ -1450,10 +1460,29 @@ fn Parser::parse_object_literal(self : Parser) -> @ast.Expr raise Error { msg == "DEFAULT_PARAM_FOUND" || msg == "DESTRUCTURE_PARAM_FOUND" { self.pos = saved_pos - let (ext_params, rest_param) = self.parse_params_ext() - self.push_async_context(obj_is_async) - let body = self.parse_block_body() + let (ext_params, rest_param) = self.parse_params_ext() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } + if obj_is_async && params_contain_await(ext_params) { + self.pop_async_context() + self.pop_generator_context() + raise @errors.SyntaxError( + message="Async method formal parameters may not contain await expressions", + ) + } + let body = self.parse_block_body() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } self.pop_async_context() + self.pop_generator_context() if obj_is_async { AsyncFuncExprExt( meth_name, @@ -1474,9 +1503,15 @@ fn Parser::parse_object_literal(self : Parser) -> @ast.Expr raise Error { ) } } else { + self.pop_async_context() + self.pop_generator_context() raise @errors.SyntaxError(message=msg) } - e => raise e + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } } props.push({ key: key_expr, @@ -1560,16 +1595,31 @@ fn Parser::parse_func_expr(self : Parser) -> @ast.Expr raise Error { _ => None } let saved_pos = self.pos + // Push non-generator context before params so that `yield` is a valid + // parameter name in nested non-generator function expressions even when + // this expression appears inside an outer generator's parameter list. + self.push_generator_context(false) + self.push_async_context(false) let params = self.parse_params() catch { Failure::Failure(msg) => if msg == "REST_PARAM_FOUND" || msg == "DEFAULT_PARAM_FOUND" || msg == "DESTRUCTURE_PARAM_FOUND" { self.pos = saved_pos - let (ext_params, rest_param) = self.parse_params_ext() - self.push_generator_context(false) - self.push_async_context(false) - let body = self.parse_block_body() + let (ext_params, rest_param) = self.parse_params_ext() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } + let body = self.parse_block_body() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } self.pop_async_context() self.pop_generator_context() return FuncExprExt( @@ -1581,13 +1631,23 @@ fn Parser::parse_func_expr(self : Parser) -> @ast.Expr raise Error { self.consume_source_text(tok.loc.offset), ) } else { + self.pop_async_context() + self.pop_generator_context() raise @errors.SyntaxError(message=msg) } - e => raise e + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } + let body = self.parse_block_body() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } } - self.push_generator_context(false) - self.push_async_context(false) - let body = self.parse_block_body() self.pop_async_context() self.pop_generator_context() FuncExpr( @@ -1700,16 +1760,38 @@ fn Parser::parse_async_func_body( loc : @token.Loc, ) -> @ast.Expr raise Error { let saved_pos = self.pos + // Push context before params so nested async-function params are isolated + // from any outer generator's context (mirrors fix in parse_func_expr). + self.push_generator_context(is_generator) + self.push_async_context(true) let params = self.parse_params() catch { Failure::Failure(msg) => if msg == "REST_PARAM_FOUND" || msg == "DEFAULT_PARAM_FOUND" || msg == "DESTRUCTURE_PARAM_FOUND" { self.pos = saved_pos - let (ext_params, rest_param) = self.parse_params_ext() - self.push_generator_context(is_generator) - self.push_async_context(true) - let body = self.parse_block_body() + let (ext_params, rest_param) = self.parse_params_ext() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } + if params_contain_await(ext_params) || + (is_generator && params_contain_yield(ext_params)) { + self.pop_async_context() + self.pop_generator_context() + raise @errors.SyntaxError( + message="Async formal parameters may not contain await/yield expressions", + ) + } + let body = self.parse_block_body() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } self.pop_async_context() self.pop_generator_context() return if is_generator { @@ -1732,13 +1814,23 @@ fn Parser::parse_async_func_body( ) } } else { + self.pop_async_context() + self.pop_generator_context() raise @errors.SyntaxError(message=msg) } - e => raise e + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } + let body = self.parse_block_body() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } } - self.push_generator_context(is_generator) - self.push_async_context(true) - let body = self.parse_block_body() self.pop_async_context() self.pop_generator_context() if is_generator { diff --git a/parser/stmt.mbt b/parser/stmt.mbt index 8a6257b7..f84c79da 100644 --- a/parser/stmt.mbt +++ b/parser/stmt.mbt @@ -175,16 +175,39 @@ fn Parser::parse_generator_decl(self : Parser) -> @ast.Stmt raise Error { let _ = self.expect(Star) // consume * let name = self.expect_ident() let saved_pos = self.pos + // §15.5.1: params and body share generator context so that `yield` in + // default-parameter expressions is parsed as YieldExpression, enabling + // the §20.2.1.1.1 step 17a Contains check via the AST. + self.push_generator_context(true) + self.push_async_context(false) let params = self.parse_params() catch { Failure::Failure(msg) => if msg == "REST_PARAM_FOUND" || msg == "DEFAULT_PARAM_FOUND" || msg == "DESTRUCTURE_PARAM_FOUND" { self.pos = saved_pos - let (ext_params, rest_param) = self.parse_params_ext() - self.push_generator_context(true) - self.push_async_context(false) - let body = self.parse_block_body() + // Wrap each inner call so cleanup runs even if parsing fails. + let (ext_params, rest_param) = self.parse_params_ext() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } + if params_contain_yield(ext_params) { + self.pop_async_context() + self.pop_generator_context() + raise @errors.SyntaxError( + message="Generator formal parameters may not contain yield expressions", + ) + } + let body = self.parse_block_body() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } self.pop_async_context() self.pop_generator_context() return GeneratorDeclExt( @@ -196,13 +219,23 @@ fn Parser::parse_generator_decl(self : Parser) -> @ast.Stmt raise Error { self.consume_source_text(tok.loc.offset), ) } else { + self.pop_async_context() + self.pop_generator_context() raise @errors.SyntaxError(message=msg) } - e => raise e + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } + } + let body = self.parse_block_body() catch { + e => { + self.pop_async_context() + self.pop_generator_context() + raise e + } } - self.push_generator_context(true) - self.push_async_context(false) - let body = self.parse_block_body() self.pop_async_context() self.pop_generator_context() GeneratorDecl( @@ -1000,7 +1033,18 @@ fn Parser::parse_object_pattern(self : Parser) -> @ast.Pattern raise Error { } (name, tok.lex_form, tok.loc, None) } - _ => (self.expect_ident(), @token.LexForm::LexNormal, key_start_loc, None) + // §12.2.6: LiteralPropertyName = IdentifierName, so keywords are valid as + // property keys when a colon follows ({yield: x}). Without a colon the key + // is also the BindingIdentifier — enforce IdentifierReference rules there. + _ => { + let next_is_colon = self.peek_kind_at(1) == Colon + let key_str = if next_is_colon { + self.expect_ident_name() + } else { + self.expect_ident() + } + (key_str, @token.LexForm::LexNormal, key_start_loc, None) + } } let (value, default_val) : (@ast.Pattern, @ast.Expr?) = if self.eat(Colon) { // { key: pattern } or { [expr]: pattern }