From: Dmitry Volyntsev Date: Tue, 1 Sep 2026 13:46:16 +0000 (-0700) Subject: Parser: carry the declaration kind across nested parsing X-Git-Tag: 1.0.2~84 X-Git-Url: https://git.kaiwu.me/%7B@url%7D?a=commitdiff_plain;h=af553ec27b9c7e7b17f395b29fc1f2e8e166b7f8;p=njs.git Parser: carry the declaration kind across nested parsing Previously, the kind of a declaration being parsed lived in a mutable parser->var_type, written once when "var", "let" or "const" was seen and read for every declarator in the list. A declaration nested in a declarator initializer overwrote it, so the declarators after it were recorded with the wrong kind and lexical ones lost their per-iteration binding: let out = []; for (let a = function() { var z; }, b = 0; b < 2; b++) out.push(() => b); out[0]() + ',' + out[1]() gave "2,2" instead of "0,1", because "b" was recorded as a var. The fix is to carry the kind in njs_parser_stack_entry_t, as the [In] parameter already is, so a continuation restores the kind in effect when it was pushed. njs_parser_stack_entry_t does not grow. --- diff --git a/src/njs_parser.c b/src/njs_parser.c index 370b4626..9328cea4 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -530,6 +530,7 @@ njs_parser_reject(njs_parser_t *parser) njs_parser_next(parser, entry->state); parser->target = entry->node; parser->allow_in = entry->allow_in; + parser->var_type = entry->var_type; return NJS_DECLINED; } diff --git a/src/njs_parser.h b/src/njs_parser.h index d3ae85bb..3fa821d6 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -95,6 +95,7 @@ typedef struct { njs_parser_node_t *node; + njs_variable_type_t var_type; uint8_t allow_in; uint8_t optional; } njs_parser_stack_entry_t; @@ -304,6 +305,7 @@ njs_parser_stack_pop(njs_parser_t *parser) parser->target = entry->node; parser->allow_in = entry->allow_in; + parser->var_type = entry->var_type; njs_mp_free(parser->vm->mem_pool, entry); @@ -349,6 +351,7 @@ _njs_parser_after(njs_parser_t *parser, njs_queue_link_t *link, void *node, entry->state = state; entry->node = node; entry->allow_in = parser->allow_in; + entry->var_type = parser->var_type; entry->optional = is_optional; njs_queue_insert_before(link, &entry->link); diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 2a801b38..cf5274f7 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -20691,6 +20691,49 @@ static njs_unit_test_t njs_test[] = "} res"), njs_str("0,0,0,0,0") }, + /* + * A declaration nested in a declarator initializer must not change the + * kind of the declarators that follow it. + */ + + { njs_str("let out = [];" + "for (let a = function() { var z; }, b = 0; b < 2; b++) {" + " out.push(() => b);" + "} out[0]() + ',' + out[1]()"), + njs_str("0,1") }, + + { njs_str("let out = [];" + "for (let a = 1, b = function() { var z; }, c = 0; c < 2; c++) {" + " out.push(() => c);" + "} out[0]() + ',' + out[1]()"), + njs_str("0,1") }, + + { njs_str("let out = [];" + "for (var a = function() { let z; }, b = 0; b < 2; b++) {" + " out.push(() => b);" + "} out[0]() + ',' + out[1]()"), + njs_str("2,2") }, + + { njs_str("var r;" + "for (const a = function() { var z; }, b = 2; b > 0;) {" + " try { b = 3 } catch (e) { r = e.name } break" + "} r"), + njs_str("TypeError") }, + + /* The same state serves an ordinary declaration list. */ + + { njs_str("var r;" + "const a = function() { var z; }, b = 2;" + "try { b = 3 } catch (e) { r = e.name } r"), + njs_str("TypeError") }, + + { njs_str("let a = function() { var z; }, b = 0;" + "for (; b < 2; b++) {} b"), + njs_str("2") }, + + { njs_str("let a = function() { var ; }, b = 0;"), + njs_str("SyntaxError: Unexpected token \";\"") }, + { njs_str("let arr = [], res = [];" "for (let i = 0; arr.push(() => i), i < 10; i++) {}" "for (let k = 0; k < 10; k++) {res.push(arr[k]())}"