From: Dmitry Volyntsev Date: Tue, 1 Sep 2026 14:26:12 +0000 (-0700) Subject: Parser: classify a "for" declaration header like an expression one X-Git-Tag: 1.0.2~82 X-Git-Url: https://git.kaiwu.me/%7B@url%7D?a=commitdiff_plain;h=6895e9fc51a9e42f890ae1527b3be845d8fb2b87;p=njs.git Parser: classify a "for" declaration header like an expression one Previously, njs_parser_for_var_binding_or_var_list() peeked one token past a declaration binding and, on "in", built the for-in nodes itself, consumed the tokens and used its own right-hand-side and body states. A declaration header therefore never reached the classifier the expression header uses, and the two carried separate copies of the same completion. The fix is to parse an identifier binding once under [~In] and let njs_parser_for_var_in_of_expression() classify the token it stopped at. The "in" case now accepts a declaration result, checks there that a for-in declaration has one binding and no initializer, and continues through the same right-hand-side, body and NJS_TOKEN_FOR_IN construction as an expression target. njs_parser_for_var_in_statement() and its completion go with the duplication, and the restricted-identifier check is left to the ordinary declaration parser, which reports it identically. A binding pattern still leaves this route before the classifier, into the handlers that report it as unsupported. The tree shape is unchanged, so the generator still selects a for-in route the way it did, and the emitted code is identical. The declaration node now takes its source line from the ordinary declaration parser, so a header split across lines reports the line of "in" rather than of the binding; this shows only in the AST dump, not in generated code or backtraces. --- diff --git a/src/njs_parser.c b/src/njs_parser.c index 9328cea4..63f6badb 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -325,10 +325,6 @@ static njs_int_t njs_parser_iteration_statement_for_map(njs_parser_t *parser, static njs_int_t njs_parser_for_var_binding_or_var_list(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current, njs_token_type_t token_type); -static njs_int_t njs_parser_for_var_in_statement(njs_parser_t *parser, - njs_lexer_token_t *token, njs_queue_link_t *current); -static njs_int_t njs_parser_for_var_in_statement_after(njs_parser_t *parser, - njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_for_var_in_of_expression(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_for_in_statement_after(njs_parser_t *parser, @@ -5722,9 +5718,6 @@ njs_parser_for_var_binding_or_var_list(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current, njs_token_type_t token_type) { - njs_int_t ret; - njs_lexer_token_t *next; - njs_parser_node_t *node, *var, *node_type, *statement; njs_variable_type_t type; switch (token_type) { @@ -5752,113 +5745,17 @@ njs_parser_for_var_binding_or_var_list(njs_parser_t *parser, return NJS_DONE; default: - if (njs_lexer_token_is_binding_identifier(token)) { - if (njs_parser_restricted_identifier(token->type)) { - njs_parser_syntax_error(parser, "Identifier \"%V\" is forbidden" - " in var declaration", &token->text); - return NJS_DONE; - } - - next = njs_lexer_peek_token(parser->lexer, token, 0); - if (next == NULL) { - return NJS_ERROR; - } - - if (next->type != NJS_TOKEN_IN) { - parser->var_type = type; - - parser->allow_in = 0; - - njs_parser_next(parser, njs_parser_variable_declaration_list); - return NJS_OK; - } - - statement = njs_parser_node_new(parser, NJS_TOKEN_STATEMENT); - if (njs_slow_path(statement == NULL)) { - return NJS_ERROR; - } - - node_type = njs_parser_node_new(parser, token_type); - if (njs_slow_path(node_type == NULL)) { - return NJS_ERROR; - } - - var = njs_parser_variable_node(parser, token->atom_id, type, NULL); - if (var == NULL) { - return NJS_ERROR; - } - - node_type->token_line = token->line; - var->token_line = token->line; - - statement->right = node_type; - node_type->left = var; - parser->node = NULL; - - node = njs_parser_node_new(parser, NJS_TOKEN_IN); - if (node == NULL) { - return NJS_ERROR; - } - - node->token_line = next->line; - node->left = statement; - - njs_parser_next(parser, njs_parser_expression); - - ret = njs_parser_after(parser, current, node, 1, - njs_parser_for_var_in_statement); - if (ret != NJS_OK) { - return NJS_ERROR; - } - - njs_lexer_consume_token(parser->lexer, 2); - - return NJS_DONE; - - } else { + if (!njs_lexer_token_is_binding_identifier(token)) { return njs_parser_failed(parser); } - } -} - - -static njs_int_t -njs_parser_for_var_in_statement(njs_parser_t *parser, njs_lexer_token_t *token, - njs_queue_link_t *current) -{ - if (token->type != NJS_TOKEN_CLOSE_PARENTHESIS) { - return njs_parser_failed(parser); - } - - njs_lexer_consume_token(parser->lexer, 1); - - parser->target->right = parser->node; - parser->node = NULL; - - njs_parser_next(parser, njs_parser_statement_wo_node); - - return njs_parser_after(parser, current, parser->target, 1, - njs_parser_for_var_in_statement_after); -} + parser->var_type = type; + parser->allow_in = 0; -static njs_int_t -njs_parser_for_var_in_statement_after(njs_parser_t *parser, - njs_lexer_token_t *token, njs_queue_link_t *current) -{ - njs_parser_node_t *foreach; + njs_parser_next(parser, njs_parser_variable_declaration_list); - foreach = njs_parser_node_new(parser, NJS_TOKEN_FOR_IN); - if (foreach == NULL) { - return NJS_ERROR; + return NJS_OK; } - - foreach->left = parser->target; - foreach->right = parser->node; - - parser->node = foreach; - - return njs_parser_stack_pop(parser); } @@ -5903,26 +5800,45 @@ njs_parser_for_var_in_of_expression(njs_parser_t *parser, return NJS_OK; case NJS_TOKEN_IN: - if (!njs_parser_is_lvalue(parser->node)) { - njs_parser_syntax_error(parser, - "Invalid left-hand side in for-loop"); - return NJS_ERROR; - } + if (njs_parser_is_declaration(parser->node)) { + if (parser->node->left != NULL + || parser->node->right->right != NULL) + { + njs_parser_syntax_error(parser, + "Invalid left-hand side in for-loop"); + return NJS_ERROR; + } - parser->node = njs_parser_lvalue_ref(parser->node); - if (parser->node == NULL) { - return NJS_ERROR; - } + node = njs_parser_node_new(parser, token->type); + if (node == NULL) { + return NJS_ERROR; + } - node = njs_parser_node_new(parser, token->type); - if (node == NULL) { - return NJS_ERROR; - } + node->token_line = token->line; + node->left = parser->node; - node->token_line = token->line; - node->u.operation = NJS_VMCODE_PROPERTY_IN; - node->left = parser->node; - node->left->dest = node; + } else { + if (!njs_parser_is_lvalue(parser->node)) { + njs_parser_syntax_error(parser, + "Invalid left-hand side in for-loop"); + return NJS_ERROR; + } + + parser->node = njs_parser_lvalue_ref(parser->node); + if (parser->node == NULL) { + return NJS_ERROR; + } + + node = njs_parser_node_new(parser, token->type); + if (node == NULL) { + return NJS_ERROR; + } + + node->token_line = token->line; + node->u.operation = NJS_VMCODE_PROPERTY_IN; + node->left = parser->node; + node->left->dest = node; + } njs_lexer_consume_token(parser->lexer, 1); diff --git a/src/njs_parser.h b/src/njs_parser.h index 3fa821d6..1347fb33 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -160,6 +160,14 @@ njs_int_t njs_parser_serialize_ast(njs_parser_node_t *node, njs_chb_t *chain); || (node)->token_type == NJS_TOKEN_PROPERTY_REF) +#define njs_parser_is_declaration(node) \ + ((node) != NULL && (node)->token_type == NJS_TOKEN_STATEMENT \ + && (node)->right != NULL \ + && ((node)->right->token_type == NJS_TOKEN_VAR \ + || (node)->right->token_type == NJS_TOKEN_LET \ + || (node)->right->token_type == NJS_TOKEN_CONST)) + + #define njs_parser_is_primitive(node) \ ((node)->token_type >= NJS_TOKEN_NULL \ && (node)->token_type <= NJS_TOKEN_STRING) diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 8ed2e1c4..ccf323a2 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -3463,6 +3463,26 @@ static njs_unit_test_t njs_test[] = { njs_str("var s = ''; for (var p in {a:1, b:2}) {s += p}; s"), njs_str("ab") }, + /* + * A declaration header is parsed once, under [~In], and classified by the + * token it stopped at, like an expression header. + */ + + { njs_str("for (let a, b in []);"), + njs_str("SyntaxError: Invalid left-hand side in for-loop") }, + + { njs_str("for (let a = 0 in {});"), + njs_str("SyntaxError: Invalid left-hand side in for-loop") }, + + { njs_str("for (const a = 0 in {});"), + njs_str("SyntaxError: Invalid left-hand side in for-loop") }, + + { njs_str("for (var a = 0, b in {});"), + njs_str("SyntaxError: Invalid left-hand side in for-loop") }, + + { njs_str("var s = ''; for (const k in {a:1, b:2}) {s += k}; s"), + njs_str("ab") }, + /* The three for-in target routes, each observed over two iterations. */ { njs_str("var r = []; for (let k in {a:1, b:2}) {r.push(() => k)};"