From: Dmitry Volyntsev Date: Wed, 4 Mar 2026 04:40:09 +0000 (-0800) Subject: Parser: lower property consumers to PROPERTY_REF. X-Git-Tag: 0.9.7~20 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=a2dbceee4155ec666e290197284e897a5ac4af97;p=njs.git Parser: lower property consumers to PROPERTY_REF. Previously, the generator inferred reference intent from raw AST shape. Now the parser marks the relevant property node as PROPERTY_REF before building METHOD_CALL, assignment, or update nodes, and the generator accepts both PROPERTY and PROPERTY_REF via njs_generate_is_property_lvalue(). Introduce NJS_TOKEN_PROPERTY_REF as an explicit parser-side marker for property accesses that carry reference semantics (assignment targets, delete operands, increment/decrement, method-call receivers). --- diff --git a/src/njs_generator.c b/src/njs_generator.c index c2f1856d..58656342 100644 --- a/src/njs_generator.c +++ b/src/njs_generator.c @@ -109,6 +109,14 @@ typedef struct { } njs_generator_log_assign_ctx_t; +njs_inline njs_bool_t +njs_generate_is_property_lvalue(njs_parser_node_t *node) +{ + return node->token_type == NJS_TOKEN_PROPERTY + || node->token_type == NJS_TOKEN_PROPERTY_REF; +} + + static u_char *njs_generate_reserve(njs_vm_t *vm, njs_generator_t *generator, size_t size); static njs_int_t njs_generate_code_map(njs_vm_t *vm, njs_generator_t *generator, @@ -691,6 +699,7 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) case NJS_TOKEN_REMAINDER: case NJS_TOKEN_PROPERTY_DELETE: case NJS_TOKEN_PROPERTY: + case NJS_TOKEN_PROPERTY_REF: return njs_generate_3addr_operation(vm, generator, node, 0); case NJS_TOKEN_IN: @@ -2218,7 +2227,7 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator, foreach = node->left; - if (foreach->left->token_type != NJS_TOKEN_PROPERTY) { + if (!njs_generate_is_property_lvalue(foreach->left)) { name = foreach->left->right; if (name != NULL) { @@ -3334,7 +3343,10 @@ njs_generate_assignment(njs_vm_t *vm, njs_generator_t *generator, njs_generate_assignment_name, NULL, 0); } - /* lvalue->token == NJS_TOKEN_PROPERTY(_INIT) */ + /* + * lvalue->token == NJS_TOKEN_PROPERTY(_REF|_INIT) + * or NJS_TOKEN_PROTO_INIT. + */ return njs_generate_property_lvalue(vm, generator, node, njs_generate_assignment_prop, @@ -3543,7 +3555,10 @@ njs_generate_operation_assignment(njs_vm_t *vm, njs_generator_t *generator, &index, sizeof(njs_index_t)); } - /* lvalue->token == NJS_TOKEN_PROPERTY */ + if (!njs_generate_is_property_lvalue(lvalue)) { + njs_internal_error(vm, "unexpected assignment target"); + return NJS_ERROR; + } return njs_generate_property_lvalue(vm, generator, node, njs_generate_operation_assignment_prop, @@ -3699,7 +3714,10 @@ njs_generate_logical_assignment(njs_vm_t *vm, njs_generator_t *generator, sizeof(njs_generator_log_assign_ctx_t)); } - /* lvalue->token == NJS_TOKEN_PROPERTY */ + if (!njs_generate_is_property_lvalue(lvalue)) { + njs_internal_error(vm, "unexpected logical assignment target"); + return NJS_ERROR; + } /* Object. */ @@ -4155,7 +4173,7 @@ njs_generate_optional_chain(njs_vm_t *vm, njs_generator_t *generator, if (call != NULL) { preserve = call->u.object->left; - if (preserve->token_type == NJS_TOKEN_PROPERTY) { + if (njs_generate_is_property_lvalue(preserve)) { preserve->hoist = 1; } } @@ -4233,7 +4251,7 @@ njs_generate_optional_chain_end(njs_vm_t *vm, njs_generator_t *generator, if (call != NULL) { preserve = call->u.object->left; - if (preserve->token_type != NJS_TOKEN_PROPERTY) { + if (!njs_generate_is_property_lvalue(preserve)) { preserve = NULL; } } else { @@ -4510,7 +4528,10 @@ njs_generate_inc_dec_operation(njs_vm_t *vm, njs_generator_t *generator, return njs_generator_stack_pop(vm, generator, NULL); } - /* lvalue->token == NJS_TOKEN_PROPERTY */ + if (!njs_generate_is_property_lvalue(lvalue)) { + njs_internal_error(vm, "unexpected increment/decrement target"); + return NJS_ERROR; + } return njs_generate_property_lvalue(vm, generator, node, njs_generate_inc_dec_operation_prop, diff --git a/src/njs_lexer.h b/src/njs_lexer.h index 9dd21063..5526be6c 100644 --- a/src/njs_lexer.h +++ b/src/njs_lexer.h @@ -132,6 +132,7 @@ typedef enum { NJS_TOKEN_OBJECT, NJS_TOKEN_OBJECT_VALUE, NJS_TOKEN_PROPERTY, + NJS_TOKEN_PROPERTY_REF, NJS_TOKEN_PROPERTY_INIT, NJS_TOKEN_PROPERTY_DELETE, NJS_TOKEN_PROPERTY_GETTER, diff --git a/src/njs_parser.c b/src/njs_parser.c index 669f619b..b17fe390 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -96,6 +96,8 @@ static njs_int_t njs_parser_call_arguments(njs_parser_t *parser, njs_parser_node_t *func, njs_parser_state_func_t after); static njs_int_t njs_parser_right_link_pop(njs_parser_t *parser); +static njs_parser_node_t *njs_parser_property_ref(njs_parser_node_t *node); +static njs_parser_node_t *njs_parser_lvalue_ref(njs_parser_node_t *node); static njs_parser_node_t *njs_parser_optional_chain_method_call( njs_parser_t *parser, njs_parser_node_t *node, uint32_t token_line); static njs_int_t njs_parser_call_expression(njs_parser_t *parser, @@ -2637,6 +2639,40 @@ njs_parser_member_expression_new_args(njs_parser_t *parser, } +static njs_parser_node_t * +njs_parser_property_ref(njs_parser_node_t *node) +{ + if (node == NULL) { + return NULL; + } + + if (node->token_type == NJS_TOKEN_PROPERTY) { + node->token_type = NJS_TOKEN_PROPERTY_REF; + } + + if (node->token_type == NJS_TOKEN_PROPERTY_REF) { + return node; + } + + return NULL; +} + + +static njs_parser_node_t * +njs_parser_lvalue_ref(njs_parser_node_t *node) +{ + if (node == NULL) { + return NULL; + } + + if (node->token_type == NJS_TOKEN_NAME) { + return node; + } + + return njs_parser_property_ref(node); +} + + static njs_parser_node_t * njs_parser_create_call(njs_parser_t *parser, njs_parser_node_t *node, uint8_t ctor) @@ -2651,6 +2687,12 @@ njs_parser_create_call(njs_parser_t *parser, njs_parser_node_t *node, break; case NJS_TOKEN_PROPERTY: + case NJS_TOKEN_PROPERTY_REF: + node = njs_parser_property_ref(node); + if (node == NULL) { + return NULL; + } + func = njs_parser_node_new(parser, NJS_TOKEN_METHOD_CALL); if (func == NULL) { return NULL; @@ -2714,13 +2756,16 @@ njs_parser_optional_chain_property(njs_parser_node_t *node) return NULL; } - if (node->token_type == NJS_TOKEN_PROPERTY) { + if (node->token_type == NJS_TOKEN_PROPERTY + || node->token_type == NJS_TOKEN_PROPERTY_REF) + { return node; } if (node->token_type == NJS_TOKEN_OPTIONAL_CHAIN && node->right != NULL - && node->right->token_type == NJS_TOKEN_PROPERTY) + && (node->right->token_type == NJS_TOKEN_PROPERTY + || node->right->token_type == NJS_TOKEN_PROPERTY_REF)) { return node->right; } @@ -2740,7 +2785,7 @@ njs_parser_optional_chain_method_call(njs_parser_t *parser, return NULL; } - prop = njs_parser_node_new(parser, NJS_TOKEN_PROPERTY); + prop = njs_parser_node_new(parser, NJS_TOKEN_PROPERTY_REF); if (prop == NULL) { return NULL; } @@ -3492,6 +3537,11 @@ njs_parser_update_expression_post(njs_parser_t *parser, return NJS_DONE; } + parser->node = njs_parser_lvalue_ref(parser->node); + if (parser->node == NULL) { + return NJS_ERROR; + } + node = njs_parser_node_new(parser, type); if (node == NULL) { return NJS_ERROR; @@ -3519,6 +3569,11 @@ njs_parser_update_expression_unary(njs_parser_t *parser, return NJS_DONE; } + parser->node = njs_parser_lvalue_ref(parser->node); + if (parser->node == NULL) { + return NJS_ERROR; + } + parser->target->left = parser->node; parser->node = parser->target; @@ -3623,7 +3678,7 @@ njs_parser_unary_expression_next(njs_parser_t *parser, { double num; njs_token_type_t type; - njs_parser_node_t *node; + njs_parser_node_t *node, *prop; type = parser->target->token_type; node = parser->node; @@ -3653,17 +3708,22 @@ njs_parser_unary_expression_next(njs_parser_t *parser, switch (node->token_type) { case NJS_TOKEN_PROPERTY: + case NJS_TOKEN_PROPERTY_REF: + node = njs_parser_property_ref(node); + if (node == NULL) { + return NJS_ERROR; + } + node->token_type = NJS_TOKEN_PROPERTY_DELETE; node->u.operation = NJS_VMCODE_PROPERTY_DELETE; return njs_parser_stack_pop(parser); case NJS_TOKEN_OPTIONAL_CHAIN: - if (node->right != NULL - && node->right->token_type == NJS_TOKEN_PROPERTY) - { - node->right->token_type = NJS_TOKEN_PROPERTY_DELETE; - node->right->u.operation = NJS_VMCODE_PROPERTY_DELETE; + prop = njs_parser_optional_chain_property(node); + if (prop != NULL) { + prop->token_type = NJS_TOKEN_PROPERTY_DELETE; + prop->u.operation = NJS_VMCODE_PROPERTY_DELETE; } break; @@ -4644,6 +4704,11 @@ njs_parser_assignment_operator(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_DONE; } + 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; @@ -5679,9 +5744,7 @@ njs_parser_for_expression_map_continue(njs_parser_t *parser, /* for-in */ - if (parser->node->token_type != NJS_TOKEN_NAME && - parser->node->token_type != NJS_TOKEN_PROPERTY) - { + if (!njs_parser_is_lvalue(parser->node)) { text = (njs_str_t *) parser->target; njs_parser_ref_error(parser, "Invalid left-hand side \"%V\" " @@ -5692,6 +5755,11 @@ njs_parser_for_expression_map_continue(njs_parser_t *parser, return NJS_DONE; } + parser->node = njs_parser_lvalue_ref(parser->node); + if (parser->node == NULL) { + return NJS_ERROR; + } + operation = NJS_VMCODE_PROPERTY_IN; node = njs_parser_node_new(parser, token->type); @@ -6091,9 +6159,7 @@ njs_parser_for_var_in_of_expression(njs_parser_t *parser, { node = parser->node->left; - if (node->token_type != NJS_TOKEN_NAME && - node->token_type != NJS_TOKEN_PROPERTY) - { + if (!njs_parser_is_lvalue(node)) { text = (njs_str_t *) parser->target; @@ -6105,6 +6171,13 @@ njs_parser_for_var_in_of_expression(njs_parser_t *parser, return NJS_DONE; } + node = njs_parser_lvalue_ref(node); + if (node == NULL) { + return NJS_ERROR; + } + + parser->node->left = node; + njs_parser_next(parser, njs_parser_for_in_statement); return NJS_OK; } @@ -9628,6 +9701,7 @@ njs_parser_serialize_node(njs_chb_t *chain, njs_parser_node_t *node) njs_token_serialize(NJS_TOKEN_REGEXP); njs_token_serialize(NJS_TOKEN_PROPERTY); + njs_token_serialize(NJS_TOKEN_PROPERTY_REF); njs_token_serialize(NJS_TOKEN_PROPERTY_INIT); njs_token_serialize(NJS_TOKEN_PROPERTY_DELETE); njs_token_serialize(NJS_TOKEN_PROPERTY_GETTER); diff --git a/src/njs_parser.h b/src/njs_parser.h index 8a67bdab..8a1bd9df 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -155,7 +155,8 @@ njs_int_t njs_parser_serialize_ast(njs_parser_node_t *node, njs_chb_t *chain); #define njs_parser_is_lvalue(node) \ ((node)->token_type == NJS_TOKEN_NAME \ - || (node)->token_type == NJS_TOKEN_PROPERTY) + || (node)->token_type == NJS_TOKEN_PROPERTY \ + || (node)->token_type == NJS_TOKEN_PROPERTY_REF) #define njs_parser_is_primitive(node) \ diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index aac7e552..38120516 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -1869,6 +1869,10 @@ static njs_unit_test_t njs_test[] = { njs_str("var o = null; (o?.m)()"), njs_str("TypeError: undefined is not a function") }, + { njs_str("var o = {x: 7, t: function(s) {return this.x + s[0]}};" + "o.t`!`"), + njs_str("7!") }, + { njs_str("var o = {a: {b: 1}}; delete o?.a?.b; o.a.b"), njs_str("undefined") }, @@ -3336,6 +3340,12 @@ static njs_unit_test_t njs_test[] = { njs_str("var a = []; for (var k in new Uint8Array([1,2,3])) { a.push(k); }; a"), njs_str("0,1,2") }, + { njs_str("var dst = {}; for (dst.a in {x: 1}) {} dst.a"), + njs_str("x") }, + + { njs_str("var dst = {}; for ((dst.a) in {x: 1}) {} dst.a"), + njs_str("x") }, + { njs_str("var i=0, a=[], r=[], d=[3,5];" "function ret_a() {r.push('ret_a'); return a};" "function ret_d() {r.push('ret_d'); return d};" @@ -11395,6 +11405,9 @@ static njs_unit_test_t njs_test[] = { njs_str("var o = { x: 1, f: function() { return this.x } }; o.f()"), njs_str("1") }, + { njs_str("var o = { x: 1, f: function() { return this.x } }; (o.f)()"), + njs_str("1") }, + { njs_str("var o = { x: 1, f: function(a) { return this.x += a } };" "o.f(5) +' '+ o.x"), njs_str("6 6") }, @@ -15259,6 +15272,12 @@ static njs_unit_test_t njs_test[] = "o.a = 1; o.a"), njs_str("1") }, + { njs_str("var o = {a:1}; (o.a) = 2; o.a"), + njs_str("2") }, + + { njs_str("var o = {a:1}; (o.a)++; o.a"), + njs_str("2") }, + { njs_str("var p = Object.create(Function);" "Object.defineProperty(p, 'length', {writable: true});" "p.length = 32; p.length"), @@ -15278,6 +15297,9 @@ static njs_unit_test_t njs_test[] = "delete o.a; o.a"), njs_str("undefined") }, + { njs_str("var o = {a:1}; delete (o.a); o.a"), + njs_str("undefined") }, + { njs_str("var o = {};" "Object.defineProperty(o, 'a', {value:1, configurable:false});" "delete o.a"),