]> git.kaiwu.me - njs.git/commitdiff
Added support for ??= operator.
authorDmitry Volyntsev <xeioex@nginx.com>
Wed, 11 Feb 2026 16:16:00 +0000 (08:16 -0800)
committerDmitry Volyntsev <xeioexception@gmail.com>
Mon, 2 Mar 2026 21:01:04 +0000 (13:01 -0800)
src/njs_generator.c
src/njs_lexer.c
src/njs_lexer.h
src/njs_parser.c
src/test/njs_unit_test.c

index a3a0adafadfcb1fadf80c9677789ba9a6bc35146..9de1a145085c9004be05d721e734fcdcda22c5ea 100644 (file)
@@ -665,6 +665,7 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node)
 
     case NJS_TOKEN_LOGICAL_OR_ASSIGNMENT:
     case NJS_TOKEN_LOGICAL_AND_ASSIGNMENT:
+    case NJS_TOKEN_COALESCE_ASSIGNMENT:
         return njs_generate_logical_assignment(vm, generator, node);
 
     case NJS_TOKEN_BITWISE_OR:
index 30febb1433a6c5b572805222a6efdc80afd25ae8..b5c2ce121049b93666f0fcfcda84670bf96e6e93 100644 (file)
@@ -276,8 +276,13 @@ static const njs_lexer_multi_t  njs_greater_token[] = {
 };
 
 
+static const njs_lexer_multi_t  njs_coalesce_assignment_token[] = {
+    { '=', NJS_TOKEN_COALESCE_ASSIGNMENT, 0, NULL },
+};
+
+
 static const njs_lexer_multi_t  njs_conditional_token[] = {
-    { '?', NJS_TOKEN_COALESCE, 0, NULL },
+    { '?', NJS_TOKEN_COALESCE, 1, njs_coalesce_assignment_token },
 };
 
 
index 331eb52eb28251fc7cee4f3fb4599271b5e16648..9dd210635cb31b1787f061f4d44e2cff989d356c 100644 (file)
@@ -52,6 +52,7 @@ typedef enum {
     NJS_TOKEN_BITWISE_AND_ASSIGNMENT,
     NJS_TOKEN_LOGICAL_OR_ASSIGNMENT,
     NJS_TOKEN_LOGICAL_AND_ASSIGNMENT,
+    NJS_TOKEN_COALESCE_ASSIGNMENT,
 
     NJS_TOKEN_INCREMENT,
     NJS_TOKEN_DECREMENT,
index 8d2a778eb6b4bba9ce2dc39cecc7676151b9f73d..669f619bcf748524710eb7f4a48668559f9bd11b 100644 (file)
@@ -4618,6 +4618,11 @@ njs_parser_assignment_operator(njs_parser_t *parser, njs_lexer_token_t *token,
         operation = NJS_VMCODE_TEST_IF_FALSE;
         break;
 
+    case NJS_TOKEN_COALESCE_ASSIGNMENT:
+        njs_thread_log_debug("JS: ?\?=");
+        operation = NJS_VMCODE_COALESCE;
+        break;
+
     default:
         return njs_parser_stack_pop(parser);
     }
@@ -9568,6 +9573,7 @@ njs_parser_serialize_node(njs_chb_t *chain, njs_parser_node_t *node)
     njs_token_serialize(NJS_TOKEN_BITWISE_AND_ASSIGNMENT);
     njs_token_serialize(NJS_TOKEN_LOGICAL_OR_ASSIGNMENT);
     njs_token_serialize(NJS_TOKEN_LOGICAL_AND_ASSIGNMENT);
+    njs_token_serialize(NJS_TOKEN_COALESCE_ASSIGNMENT);
     njs_token_serialize(NJS_TOKEN_EQUAL);
     njs_token_serialize(NJS_TOKEN_NOT_EQUAL);
     njs_token_serialize(NJS_TOKEN_STRICT_EQUAL);
index bff8486847cce3165b31f00ccd852c23ad666950..bbe70ebbb83a6db51578568b7893153b7d9ed2a6 100644 (file)
@@ -1625,6 +1625,75 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("1 &&= 2"),
       njs_str("ReferenceError: Invalid left-hand side in assignment") },
 
+    /* Logical assignment: ??= */
+
+    { njs_str("var a = null; a ?\?= 5; a"),
+      njs_str("5") },
+    { njs_str("var a = undefined; a ?\?= 5; a"),
+      njs_str("5") },
+    { njs_str("var a = 0; a ?\?= 5; a"),
+      njs_str("0") },
+    { njs_str("var a = ''; a ?\?= 'x'; a"),
+      njs_str("") },
+    { njs_str("var a = false; a ?\?= true; a"),
+      njs_str("false") },
+    { njs_str("var a = 1; a ?\?= 5; a"),
+      njs_str("1") },
+
+    /* ??= short-circuit: RHS not evaluated */
+
+    { njs_str("var a = 1; var b = 0; a ?\?= (b = 2); b"),
+      njs_str("0") },
+    { njs_str("var a = null; var b = 0; a ?\?= (b = 2); b"),
+      njs_str("2") },
+
+    /* ??= property targets */
+
+    { njs_str("var o = {a: null}; o.a ?\?= 5; o.a"),
+      njs_str("5") },
+    { njs_str("var o = {a: 0}; o.a ?\?= 5; o.a"),
+      njs_str("0") },
+    { njs_str("var o = {a: null}; o['a'] ?\?= 5; o.a"),
+      njs_str("5") },
+
+    /* ??= expression result value */
+
+    { njs_str("var a = 1; (a ?\?= 5)"),
+      njs_str("1") },
+    { njs_str("var a = null; (a ?\?= 5)"),
+      njs_str("5") },
+
+    /* ??= const error */
+
+    { njs_str("const a = 1; a ?\?= 2"),
+      njs_str("1") },
+    { njs_str("const a = null; a ?\?= 2"),
+      njs_str("TypeError: assignment to constant variable") },
+
+    /* ??= getter/setter short-circuit */
+
+    { njs_str("var log = '';"
+              "var o = {"
+              "    get x() {log += 'g'; return 1},"
+              "    set x(v) {log += 's'}"
+              "};"
+              "o.x ?\?= 2;"
+              "log"),
+      njs_str("g") },
+    { njs_str("var log = '';"
+              "var o = {"
+              "    get x() {log += 'g'; return null},"
+              "    set x(v) {log += 's'}"
+              "};"
+              "o.x ?\?= 2;"
+              "log"),
+      njs_str("gs") },
+
+    /* ??= non-lvalue error */
+
+    { njs_str("1 ?\?= 2"),
+      njs_str("ReferenceError: Invalid left-hand side in assignment") },
+
     /* Optional chaining: property access. */
 
     { njs_str("var o = {a: 1}; o?.a"),