]> git.kaiwu.me - njs.git/commitdiff
Parser: classify a "for" declaration header like an expression one
authorDmitry Volyntsev <xeioex@nginx.com>
Tue, 1 Sep 2026 14:26:12 +0000 (07:26 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Fri, 4 Sep 2026 20:56:37 +0000 (13:56 -0700)
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.

src/njs_parser.c
src/njs_parser.h
src/test/njs_unit_test.c

index 9328cea442efe746581a80c99c63304a56df930e..63f6badba6d929620a468c7a5e752d352b737503 100644 (file)
@@ -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);
 
index 3fa821d6059f8bc36794f60a25c9d9d761209b43..1347fb33fb0b0d048f4969723fe8a00124b1d730 100644 (file)
@@ -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)
index 8ed2e1c439ca05b53ca73a6e72ab9155261d4f85..ccf323a28719a4e50eba63b1c7aedae2814d2802 100644 (file)
@@ -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)};"