Implement static class initializer blocks (#144)

Spec compliance bug: "await" is illegal inside initializer blocks
_except_ when used as an identifier in a function expression, like so:

    class C {
        static {
            var f = function await() {}
        }
    }

It is somewhat complicated to make the parser understand the distinction
and such code is probably rare or non-existent so I decided to leave
well enough alone for now.
This commit is contained in:
Ben Noordhuis 2023-11-26 21:11:48 +01:00 committed by GitHub
parent 51633afe56
commit b5148b212e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 72 additions and 7 deletions

View file

@ -17820,6 +17820,7 @@ typedef enum JSParseFunctionEnum {
JS_PARSE_FUNC_GETTER, JS_PARSE_FUNC_GETTER,
JS_PARSE_FUNC_SETTER, JS_PARSE_FUNC_SETTER,
JS_PARSE_FUNC_METHOD, JS_PARSE_FUNC_METHOD,
JS_PARSE_FUNC_CLASS_STATIC_INIT,
JS_PARSE_FUNC_CLASS_CONSTRUCTOR, JS_PARSE_FUNC_CLASS_CONSTRUCTOR,
JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR,
} JSParseFunctionEnum; } JSParseFunctionEnum;
@ -18688,7 +18689,13 @@ static __exception int next_token(JSParseState *s)
s->token.u.ident.atom = atom; s->token.u.ident.atom = atom;
s->token.u.ident.has_escape = ident_has_escape; s->token.u.ident.has_escape = ident_has_escape;
s->token.u.ident.is_reserved = FALSE; s->token.u.ident.is_reserved = FALSE;
if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || // TODO(bnoordhuis) accept await when used in a function expression
// inside the static initializer block
if (s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT &&
(atom == JS_ATOM_arguments || atom == JS_ATOM_await)) {
s->token.u.ident.is_reserved = TRUE;
s->token.val = TOK_IDENT;
} else if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD ||
(s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD &&
(s->cur_func->js_mode & JS_MODE_STRICT)) || (s->cur_func->js_mode & JS_MODE_STRICT)) ||
(s->token.u.ident.atom == JS_ATOM_yield && (s->token.u.ident.atom == JS_ATOM_yield &&
@ -20836,6 +20843,44 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
if (is_static) { if (is_static) {
if (next_token(s)) if (next_token(s))
goto fail; goto fail;
if (s->token.val == '{') {
ClassFieldsDef *cf = &class_fields[is_static];
if (!cf->fields_init_fd)
if (emit_class_init_start(s, cf))
goto fail;
s->cur_func = cf->fields_init_fd;
// stack is now: <empty>
JSFunctionDef *init;
if (js_parse_function_decl2(s, JS_PARSE_FUNC_CLASS_STATIC_INIT,
JS_FUNC_NORMAL, JS_ATOM_NULL,
s->token.ptr, s->token.line_num,
JS_PARSE_EXPORT_NONE, &init) < 0) {
goto fail;
}
// stack is now: fclosure
push_scope(s);
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
// stack is now: fclosure this
if (class_name != JS_ATOM_NULL) {
// TODO(bnoordhuis) pass as argument to init method?
emit_op(s, OP_dup);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, class_name);
emit_u16(s, s->cur_func->scope_level);
}
emit_op(s, OP_swap);
// stack is now: this fclosure
emit_op(s, OP_call_method);
emit_u16(s, 0);
// stack is now: returnvalue
emit_op(s, OP_drop);
// stack is now: <empty>
pop_scope(s);
s->cur_func = s->cur_func->parent;
continue;
}
/* allow "static" field name */ /* allow "static" field name */
if (s->token.val == ';' || s->token.val == '=') { if (s->token.val == ';' || s->token.val == '=') {
is_static = FALSE; is_static = FALSE;
@ -24085,6 +24130,10 @@ static __exception int js_parse_statement_or_decl(JSParseState *s,
js_parse_error(s, "return not in a function"); js_parse_error(s, "return not in a function");
goto fail; goto fail;
} }
if (s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) {
js_parse_error(s, "return in a static initializer block");
goto fail;
}
if (next_token(s)) if (next_token(s))
goto fail; goto fail;
if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) {
@ -30646,7 +30695,9 @@ static __exception int js_parse_function_decl2(JSParseState *s,
(func_kind & JS_FUNC_GENERATOR)) || (func_kind & JS_FUNC_GENERATOR)) ||
(s->token.u.ident.atom == JS_ATOM_await && (s->token.u.ident.atom == JS_ATOM_await &&
func_type == JS_PARSE_FUNC_EXPR && func_type == JS_PARSE_FUNC_EXPR &&
(func_kind & JS_FUNC_ASYNC))) { (func_kind & JS_FUNC_ASYNC)) ||
(s->token.u.ident.atom == JS_ATOM_await &&
func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT)) {
return js_parse_error_reserved_identifier(s); return js_parse_error_reserved_identifier(s);
} }
} }
@ -30740,7 +30791,8 @@ static __exception int js_parse_function_decl2(JSParseState *s,
func_type == JS_PARSE_FUNC_SETTER || func_type == JS_PARSE_FUNC_SETTER ||
func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR || func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR ||
func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR); func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR);
fd->has_arguments_binding = (func_type != JS_PARSE_FUNC_ARROW); fd->has_arguments_binding = (func_type != JS_PARSE_FUNC_ARROW &&
func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT);
fd->has_this_binding = fd->has_arguments_binding; fd->has_this_binding = fd->has_arguments_binding;
fd->is_derived_class_constructor = (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR); fd->is_derived_class_constructor = (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR);
if (func_type == JS_PARSE_FUNC_ARROW) { if (func_type == JS_PARSE_FUNC_ARROW) {
@ -30748,6 +30800,11 @@ static __exception int js_parse_function_decl2(JSParseState *s,
fd->super_call_allowed = fd->parent->super_call_allowed; fd->super_call_allowed = fd->parent->super_call_allowed;
fd->super_allowed = fd->parent->super_allowed; fd->super_allowed = fd->parent->super_allowed;
fd->arguments_allowed = fd->parent->arguments_allowed; fd->arguments_allowed = fd->parent->arguments_allowed;
} else if (func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) {
fd->new_target_allowed = TRUE; // although new.target === undefined
fd->super_call_allowed = FALSE;
fd->super_allowed = TRUE;
fd->arguments_allowed = FALSE;
} else { } else {
fd->new_target_allowed = TRUE; fd->new_target_allowed = TRUE;
fd->super_call_allowed = fd->is_derived_class_constructor; fd->super_call_allowed = fd->is_derived_class_constructor;
@ -30785,7 +30842,7 @@ static __exception int js_parse_function_decl2(JSParseState *s,
if (add_arg(ctx, fd, name) < 0) if (add_arg(ctx, fd, name) < 0)
goto fail; goto fail;
fd->defined_arg_count = 1; fd->defined_arg_count = 1;
} else { } else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) {
if (s->token.val == '(') { if (s->token.val == '(') {
int skip_bits; int skip_bits;
/* if there is an '=' inside the parameter list, we /* if there is an '=' inside the parameter list, we
@ -31005,8 +31062,10 @@ static __exception int js_parse_function_decl2(JSParseState *s,
} }
} }
if (js_parse_expect(s, '{')) // js_parse_class() already consumed the '{'
goto fail; if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT)
if (js_parse_expect(s, '{'))
goto fail;
if (js_parse_directives(s)) if (js_parse_directives(s))
goto fail; goto fail;

View file

@ -79,7 +79,7 @@ class-fields-private
class-fields-private-in=skip class-fields-private-in=skip
class-fields-public class-fields-public
class-methods-private class-methods-private
class-static-block=skip class-static-block
class-static-fields-private class-static-fields-private
class-static-fields-public class-static-fields-public
class-static-methods-private class-static-methods-private

View file

@ -92,6 +92,8 @@ test262/test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds
test262/test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds.js:22: strict mode: Test262Error: Reflect.set(sample, "-1", 1) must return true Expected SameValue(«false», «true») to be true (Testing with Float64Array.) test262/test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds.js:22: strict mode: Test262Error: Reflect.set(sample, "-1", 1) must return true Expected SameValue(«false», «true») to be true (Testing with Float64Array.)
test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-detached-buffer.js:39: Test262Error: Expected SameValue(«false», «true») to be true (Testing with Float64Array.) test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-detached-buffer.js:39: Test262Error: Expected SameValue(«false», «true») to be true (Testing with Float64Array.)
test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-detached-buffer.js:39: strict mode: Test262Error: Expected SameValue(«false», «true») to be true (Testing with Float64Array.) test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-detached-buffer.js:39: strict mode: Test262Error: Expected SameValue(«false», «true») to be true (Testing with Float64Array.)
test262/test/language/expressions/arrow-function/static-init-await-reference.js:12: unexpected error type: Test262: This statement should not be evaluated.
test262/test/language/expressions/arrow-function/static-init-await-reference.js:12: strict mode: unexpected error type: Test262: This statement should not be evaluated.
test262/test/language/expressions/assignment/target-member-computed-reference-null.js:32: Test262Error: Expected a DummyError but got a TypeError test262/test/language/expressions/assignment/target-member-computed-reference-null.js:32: Test262Error: Expected a DummyError but got a TypeError
test262/test/language/expressions/assignment/target-member-computed-reference-null.js:32: strict mode: Test262Error: Expected a DummyError but got a TypeError test262/test/language/expressions/assignment/target-member-computed-reference-null.js:32: strict mode: Test262Error: Expected a DummyError but got a TypeError
test262/test/language/expressions/assignment/target-member-computed-reference-undefined.js:32: Test262Error: Expected a DummyError but got a TypeError test262/test/language/expressions/assignment/target-member-computed-reference-undefined.js:32: Test262Error: Expected a DummyError but got a TypeError
@ -124,6 +126,10 @@ test262/test/language/expressions/delete/super-property-null-base.js:26: Test262
test262/test/language/expressions/delete/super-property-null-base.js:26: strict mode: Test262Error: Expected a ReferenceError but got a TypeError test262/test/language/expressions/delete/super-property-null-base.js:26: strict mode: Test262Error: Expected a ReferenceError but got a TypeError
test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: TypeError: $DONE() not called test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: TypeError: $DONE() not called
test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: strict mode: TypeError: $DONE() not called test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: strict mode: TypeError: $DONE() not called
test262/test/language/expressions/function/static-init-await-binding.js:16: SyntaxError: 'await' is a reserved identifier
test262/test/language/expressions/function/static-init-await-binding.js:16: strict mode: SyntaxError: 'await' is a reserved identifier
test262/test/language/expressions/generators/static-init-await-binding.js:16: SyntaxError: 'await' is a reserved identifier
test262/test/language/expressions/generators/static-init-await-binding.js:16: strict mode: SyntaxError: 'await' is a reserved identifier
test262/test/language/expressions/logical-assignment/left-hand-side-private-reference-accessor-property-and.js:60: Test262Error: The expression should evaluate to the result Expected SameValue(«undefined», «false») to be true test262/test/language/expressions/logical-assignment/left-hand-side-private-reference-accessor-property-and.js:60: Test262Error: The expression should evaluate to the result Expected SameValue(«undefined», «false») to be true
test262/test/language/expressions/logical-assignment/left-hand-side-private-reference-accessor-property-and.js:60: strict mode: Test262Error: The expression should evaluate to the result Expected SameValue(«undefined», «false») to be true test262/test/language/expressions/logical-assignment/left-hand-side-private-reference-accessor-property-and.js:60: strict mode: Test262Error: The expression should evaluate to the result Expected SameValue(«undefined», «false») to be true
test262/test/language/expressions/logical-assignment/left-hand-side-private-reference-accessor-property-nullish.js:59: Test262Error: The expression should evaluate to the result Expected SameValue(«undefined», «1») to be true test262/test/language/expressions/logical-assignment/left-hand-side-private-reference-accessor-property-nullish.js:59: Test262Error: The expression should evaluate to the result Expected SameValue(«undefined», «1») to be true