From b5148b212ee99f5159741bdbf50e8dde3c5217b8 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sun, 26 Nov 2023 21:11:48 +0100 Subject: [PATCH] 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. --- quickjs.c | 71 ++++++++++++++++++++++++++++++++++++++++++---- test262.conf | 2 +- test262_errors.txt | 6 ++++ 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/quickjs.c b/quickjs.c index e9f5197..df581ee 100644 --- a/quickjs.c +++ b/quickjs.c @@ -17820,6 +17820,7 @@ typedef enum JSParseFunctionEnum { JS_PARSE_FUNC_GETTER, JS_PARSE_FUNC_SETTER, JS_PARSE_FUNC_METHOD, + JS_PARSE_FUNC_CLASS_STATIC_INIT, JS_PARSE_FUNC_CLASS_CONSTRUCTOR, JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, } JSParseFunctionEnum; @@ -18688,7 +18689,13 @@ static __exception int next_token(JSParseState *s) s->token.u.ident.atom = atom; s->token.u.ident.has_escape = ident_has_escape; 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->cur_func->js_mode & JS_MODE_STRICT)) || (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 (next_token(s)) 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: + 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: + pop_scope(s); + s->cur_func = s->cur_func->parent; + continue; + } /* allow "static" field name */ if (s->token.val == ';' || s->token.val == '=') { 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"); 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)) goto fail; 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)) || (s->token.u.ident.atom == JS_ATOM_await && 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); } } @@ -30740,7 +30791,8 @@ static __exception int js_parse_function_decl2(JSParseState *s, func_type == JS_PARSE_FUNC_SETTER || func_type == JS_PARSE_FUNC_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->is_derived_class_constructor = (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR); 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_allowed = fd->parent->super_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 { fd->new_target_allowed = TRUE; 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) goto fail; fd->defined_arg_count = 1; - } else { + } else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) { if (s->token.val == '(') { int skip_bits; /* 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, '{')) - goto fail; + // js_parse_class() already consumed the '{' + if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) + if (js_parse_expect(s, '{')) + goto fail; if (js_parse_directives(s)) goto fail; diff --git a/test262.conf b/test262.conf index eeb83cc..d1ff09a 100644 --- a/test262.conf +++ b/test262.conf @@ -79,7 +79,7 @@ class-fields-private class-fields-private-in=skip class-fields-public class-methods-private -class-static-block=skip +class-static-block class-static-fields-private class-static-fields-public class-static-methods-private diff --git a/test262_errors.txt b/test262_errors.txt index 4fc2d28..1f6178a 100644 --- a/test262_errors.txt +++ b/test262_errors.txt @@ -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/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/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: 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 @@ -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/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/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: 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