LibJS: Fix await boundaries in class elements

Treat ordinary and generator function expressions inside class static
blocks as boundaries for await binding names, while still rejecting
await as an arrow parameter in the static block itself.

Parse field initializers without inheriting an enclosing async
function's await-expression context, so script field initializers can
resolve await as an identifier while computed field names still use the
enclosing expression context.

Cover static block function expressions, class field initializers, and
the AST shape for an await arrow inside a nested function.
This commit is contained in:
Andreas Kling 2026-05-21 13:52:10 +02:00 committed by Andreas Kling
parent 0c6f86c80d
commit 99596d68ab
6 changed files with 65 additions and 16 deletions

View file

@ -555,7 +555,7 @@ impl Parser<'_> {
"async generator function is not allowed to be called '{name_str}'"
));
}
if self.flags.in_class_static_init_block && fn_name == utf16!("await") {
if self.flags.in_class_static_init_block && is_async && fn_name == utf16!("await") {
self.syntax_error("'await' is a reserved word");
}
}
@ -593,16 +593,15 @@ impl Parser<'_> {
self.scope_collector.close_scope();
self.pattern_bound_names = saved_pattern_bound_names;
self.flags.in_class_static_init_block = saved_static_init;
self.flags.in_class_field_initializer = saved_field_init;
self.flags.new_target_is_valid = saved_new_target;
if name.is_some() {
self.check_identifier_name_for_assignment_validity(fn_name, has_use_strict);
}
if has_use_strict || kind != FunctionKind::Normal {
self.check_parameters_post_body(&parsed.parameter_info, has_use_strict, kind);
}
self.flags.in_class_static_init_block = saved_static_init;
self.flags.in_class_field_initializer = saved_field_init;
self.flags.new_target_is_valid = saved_new_target;
insights.might_need_arguments_object = self.flags.function_might_need_arguments_object;
self.flags.function_might_need_arguments_object = saved_might_need_arguments;
@ -1143,13 +1142,16 @@ impl Parser<'_> {
let init = if self.match_token(TokenType::Equals) {
self.consume();
let saved_await = self.flags.await_expression_is_valid;
let saved_field_init = self.flags.in_class_field_initializer;
let saved_super_lookup = self.flags.allow_super_property_lookup;
self.flags.await_expression_is_valid = false;
self.flags.in_class_field_initializer = true;
self.flags.allow_super_property_lookup = true;
self.scope_collector.open_class_field_scope(None);
let expression = self.parse_assignment_expression();
self.scope_collector.close_scope();
self.flags.await_expression_is_valid = saved_await;
self.flags.in_class_field_initializer = saved_field_init;
self.flags.allow_super_property_lookup = saved_super_lookup;
Some(Box::new(expression))

View file

@ -2300,8 +2300,10 @@ impl Parser<'_> {
} else if self.match_identifier() || self.match_token(TokenType::Await) {
let token = self.consume();
let value = self.token_value(&token).to_vec();
if is_async && value == utf16!("await") {
self.syntax_error("'await' is a reserved identifier in async functions");
if value == utf16!("await")
&& (is_async || self.program_type == ProgramType::Module || self.flags.in_class_static_init_block)
{
self.syntax_error("'await' is not allowed as an identifier in this context");
}
// C++ uses rule_start (arrow function start, which is `async` for async arrows).
let value_id = self.arena.strings.intern(&value);

View file

@ -8,11 +8,14 @@ Program (script) @1:1
└─ elements
└─ StaticInitializer @2:12
└─ FunctionBody @1:1
└─ ExpressionStatement @3:9
└─ FunctionExpression "" [strict] [arrow] @3:9
├─ parameters
│ └─ Identifier "await" [argument:0] @3:9
└─ body
└─ FunctionBody @3:9
└─ ReturnStatement @3:9
└─ NumericLiteral 0 @3:18
└─ FunctionDeclaration "f" [strict] @3:9
└─ body
└─ FunctionBody @4:13
└─ ExpressionStatement @4:13
└─ FunctionExpression "" [strict] [arrow] @4:13
├─ parameters
│ └─ Identifier "await" [argument:0] @4:13
└─ body
└─ FunctionBody @4:13
└─ ReturnStatement @4:13
└─ NumericLiteral 0 @4:22

View file

@ -1,5 +1,7 @@
class C {
static {
await => 0;
function f() {
await => 0;
}
}
}

View file

@ -72,6 +72,23 @@ test("initializer has correct this value", () => {
expect(a.this_name).toBe(a);
});
test("field initializers do not inherit await expressions", () => {
var await = 42;
async function getClass() {
return class {
field = await;
};
}
expect("async () => class { [await] = 1 };").not.toEval();
expect("async () => class { field = await 1 };").not.toEval();
return getClass().then(A => {
expect(new A().field).toBe(42);
});
});
test("static fields", () => {
class A {
static simple = 1;

View file

@ -89,3 +89,26 @@ test("declaring variables", () => {
}
}
});
test("await binding names in nested functions", () => {
class A {
static {
const makeGenerator = function* await(await) {
yield await;
};
const generator = makeGenerator(42);
expect(generator.next()).toEqual({ value: 42, done: false });
expect(generator.next()).toEqual({ value: undefined, done: true });
(function await(await) {
expect(await).toBe(42);
})(42);
}
}
});
test("await binding names in static blocks without a function boundary", () => {
expect(`class A { static { await => 0; } }`).not.toEval();
expect(`class A { static { async function await() {} } }`).not.toEval();
});