]> git.kaiwu.me - njs.git/commitdiff
Parser: lower property consumers to PROPERTY_REF.
authorDmitry Volyntsev <xeioex@nginx.com>
Wed, 4 Mar 2026 04:40:09 +0000 (20:40 -0800)
committerDmitry Volyntsev <xeioexception@gmail.com>
Thu, 12 Mar 2026 22:26:01 +0000 (15:26 -0700)
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).

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

index c2f1856d4f2349dce3eb72a03fe2cc1f772c3a35..5865634232365317817421969c55669c7f4162c0 100644 (file)
@@ -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,
index 9dd210635cb31b1787f061f4d44e2cff989d356c..5526be6c7cc0a50a07d14ffdde1a71da9bffa685 100644 (file)
@@ -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,
index 669f619bcf748524710eb7f4a48668559f9bd11b..b17fe3905e381af29e1376bd7fe5a783319a5f7d 100644 (file)
@@ -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);
index 8a67bdab80099e7cbd6dc42bade17c7225e616f8..8a1bd9dfe50bbbf2e14375fb96da5fc59eb5d4eb 100644 (file)
@@ -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)                                         \
index aac7e552b9920896250c7381320d9ecd3677a762..38120516d75560bbb3c07fcfa5094c476a624d96 100644 (file)
@@ -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"),