]> git.kaiwu.me - njs.git/commitdiff
Introduced const implementation.
authorAlexander Borisov <alexander.borisov@nginx.com>
Fri, 30 Apr 2021 13:02:34 +0000 (16:02 +0300)
committerAlexander Borisov <alexander.borisov@nginx.com>
Fri, 30 Apr 2021 13:02:34 +0000 (16:02 +0300)
src/njs_builtin.c
src/njs_disassembler.c
src/njs_generator.c
src/njs_parser.c
src/njs_scope.h
src/njs_variable.c
src/njs_vmcode.c
src/njs_vmcode.h
src/test/njs_unit_test.c

index abb6ceeecb860ae0189ca06489dbcaab4c323867..5b03e721f831fa8fddd082540e639cc4be18f314 100644 (file)
@@ -972,7 +972,7 @@ njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop,
 
     var = node->variable;
 
-    if (var->type == NJS_VARIABLE_LET) {
+    if (var->type == NJS_VARIABLE_LET || var->type == NJS_VARIABLE_CONST) {
         return NJS_DECLINED;
     }
 
index 3a19387153dde3158ee9dd52ccce7a6f3319509c..acfc5e26754bf16e77015f24e392ff5011a34def 100644 (file)
@@ -150,6 +150,9 @@ static njs_code_name_t  code_names[] = {
 
     { NJS_VMCODE_NOT_INITIALIZED, sizeof(njs_vmcode_variable_t),
           njs_str("NOT INIT        ") },
+
+    { NJS_VMCODE_ASSIGNMENT_ERROR, sizeof(njs_vmcode_variable_t),
+          njs_str("ASSIGNMENT ERROR") },
 };
 
 
index 55f274f764babd467fe881fa47bf58dfea5437e1..56cb6fe279d5d5b278eda71025cf198cc725c7b0 100644 (file)
@@ -287,6 +287,7 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node)
 
     case NJS_TOKEN_VAR:
     case NJS_TOKEN_LET:
+    case NJS_TOKEN_CONST:
         return njs_generate_var_statement(vm, generator, node);
 
     case NJS_TOKEN_IF:
@@ -640,7 +641,7 @@ njs_generate_name(njs_vm_t *vm, njs_generator_t *generator,
         return NJS_OK;
     }
 
-    if (var->type == NJS_VARIABLE_LET) {
+    if (var->type == NJS_VARIABLE_LET || var->type == NJS_VARIABLE_CONST) {
         scope = njs_function_scope(node->scope);
 
         if (scope->dest_disable) {
@@ -692,7 +693,7 @@ njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator,
         return NJS_OK;
     }
 
-    if (var->type == NJS_VARIABLE_LET) {
+    if (var->type == NJS_VARIABLE_LET || var->type == NJS_VARIABLE_CONST) {
         scope = njs_function_scope(node->scope);
 
         if ((!scope->dest_disable && njs_function_scope(var->scope) == scope)) {
@@ -746,6 +747,12 @@ njs_generate_var_statement(njs_vm_t *vm, njs_generator_t *generator,
 
     if (expr == NULL) {
         /* Variable is only declared. */
+
+        if (var->type == NJS_VARIABLE_CONST) {
+            njs_syntax_error(vm, "missing initializer in const declaration");
+            return NJS_ERROR;
+        }
+
         if (var->type == NJS_VARIABLE_LET) {
             ret = njs_generate_let(vm, generator, node, var);
             if (njs_slow_path(ret != NJS_OK)) {
@@ -758,7 +765,7 @@ njs_generate_var_statement(njs_vm_t *vm, njs_generator_t *generator,
         return NJS_OK;
     }
 
-    if (var->type == NJS_VARIABLE_LET) {
+    if (var->type == NJS_VARIABLE_LET || var->type == NJS_VARIABLE_CONST) {
         ret = njs_generate_wo_dest(vm, generator, expr);
         if (njs_slow_path(ret != NJS_OK)) {
             return ret;
@@ -1346,7 +1353,9 @@ njs_generate_for_let_update(njs_vm_t *vm, njs_generator_t *generator,
 
     let = node->right;
 
-    if (let->token_type != NJS_TOKEN_LET) {
+    if (let->token_type != NJS_TOKEN_LET
+        && let->token_type != NJS_TOKEN_CONST)
+    {
         return NJS_OK;
     }
 
@@ -1827,7 +1836,9 @@ njs_generate_statement(njs_vm_t *vm, njs_generator_t *generator,
             goto statement;
         }
 
-        if (!var->init && var->type == NJS_VARIABLE_LET) {
+        if (!var->init && (var->type == NJS_VARIABLE_LET
+            || var->type == NJS_VARIABLE_CONST))
+        {
             njs_generate_code(generator, njs_vmcode_variable_t, code,
                               NJS_VMCODE_INITIALIZATION_TEST, 0, right);
             code->dst = right->index;
@@ -1950,8 +1961,10 @@ njs_generate_assignment(njs_vm_t *vm, njs_generator_t *generator,
 {
     njs_int_t              ret;
     njs_index_t            index, src;
+    njs_variable_t         *var;
     njs_parser_node_t      *lvalue, *expr, *object, *property;
     njs_vmcode_move_t      *move;
+    njs_vmcode_variable_t  *var_code;
     njs_vmcode_prop_set_t  *prop_set;
 
     lvalue = node->left;
@@ -1961,11 +1974,19 @@ njs_generate_assignment(njs_vm_t *vm, njs_generator_t *generator,
     if (lvalue->token_type == NJS_TOKEN_NAME) {
 
         ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION,
-                                    NULL);
+                                    &var);
         if (njs_slow_path(ret != NJS_OK)) {
             return ret;
         }
 
+        if (var != NULL && var->type == NJS_VARIABLE_CONST) {
+            njs_generate_code(generator, njs_vmcode_variable_t, var_code,
+                              NJS_VMCODE_ASSIGNMENT_ERROR, 0, node);
+            var_code->dst = var->index;
+
+            return NJS_OK;
+        }
+
         expr->dest = lvalue;
 
         ret = njs_generator(vm, generator, expr);
@@ -2075,9 +2096,11 @@ njs_generate_operation_assignment(njs_vm_t *vm, njs_generator_t *generator,
 {
     njs_int_t              ret;
     njs_index_t            index, src;
+    njs_variable_t         *var;
     njs_parser_node_t      *lvalue, *expr, *object, *property;
     njs_vmcode_move_t      *move;
     njs_vmcode_3addr_t     *code;
+    njs_vmcode_variable_t  *var_code;
     njs_vmcode_prop_get_t  *prop_get;
     njs_vmcode_prop_set_t  *prop_set;
 
@@ -2086,11 +2109,19 @@ njs_generate_operation_assignment(njs_vm_t *vm, njs_generator_t *generator,
     if (lvalue->token_type == NJS_TOKEN_NAME) {
 
         ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION,
-                                    NULL);
+                                    &var);
         if (njs_slow_path(ret != NJS_OK)) {
             return ret;
         }
 
+        if (var != NULL && var->type == NJS_VARIABLE_CONST) {
+            njs_generate_code(generator, njs_vmcode_variable_t, var_code,
+                              NJS_VMCODE_ASSIGNMENT_ERROR, 0, node);
+            var_code->dst = var->index;
+
+            return NJS_OK;
+        }
+
         index = lvalue->index;
         expr = node->right;
 
@@ -2616,8 +2647,10 @@ njs_generate_inc_dec_operation(njs_vm_t *vm, njs_generator_t *generator,
 {
     njs_int_t              ret;
     njs_index_t            index, dest_index;
+    njs_variable_t         *var;
     njs_parser_node_t      *lvalue;
     njs_vmcode_3addr_t     *code;
+    njs_vmcode_variable_t  *var_code;
     njs_vmcode_prop_get_t  *prop_get;
     njs_vmcode_prop_set_t  *prop_set;
 
@@ -2626,11 +2659,19 @@ njs_generate_inc_dec_operation(njs_vm_t *vm, njs_generator_t *generator,
     if (lvalue->token_type == NJS_TOKEN_NAME) {
 
         ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION,
-                                    NULL);
+                                    &var);
         if (njs_slow_path(ret != NJS_OK)) {
             return ret;
         }
 
+        if (var != NULL && var->type == NJS_VARIABLE_CONST) {
+            njs_generate_code(generator, njs_vmcode_variable_t, var_code,
+                              NJS_VMCODE_ASSIGNMENT_ERROR, 0, node);
+            var_code->dst = var->index;
+
+            return NJS_OK;
+        }
+
         index = njs_generate_dest_index(vm, generator, node);
         if (njs_slow_path(index == NJS_INDEX_ERROR)) {
             return index;
index 616b832f4dbcd398153668a52e37f031b7eed6fc..aae187da4cd98b705441e22edbe7f2089533351d 100644 (file)
@@ -4946,6 +4946,7 @@ static njs_int_t
 njs_parser_expression_statement(njs_parser_t *parser, njs_lexer_token_t *token,
     njs_queue_link_t *current)
 {
+    njs_token_type_t   type;
     njs_lexer_token_t  *next;
 
     switch (token->type) {
@@ -4974,15 +4975,20 @@ njs_parser_expression_statement(njs_parser_t *parser, njs_lexer_token_t *token,
 
         break;
 
+    case NJS_TOKEN_CONST:
     case NJS_TOKEN_LET:
+        type = token->type;
+
         token = njs_lexer_peek_token(parser->lexer, token, 0);
         if (token == NULL) {
             return NJS_ERROR;
         }
 
         if (token->type == NJS_TOKEN_NAME) {
-            njs_parser_syntax_error(parser, "let declaration cannot appear "
-                                            "in a single-statement context");
+            njs_parser_syntax_error(parser, "%s declaration cannot appear "
+                                    "in a single-statement context",
+                                    (type == NJS_TOKEN_CONST ? "const"
+                                                             : "let" ));
             return NJS_DONE;
         }
 
@@ -5307,6 +5313,7 @@ njs_parser_iteration_statement_for_map(njs_parser_t *parser,
 
     case NJS_TOKEN_VAR:
     case NJS_TOKEN_LET:
+    case NJS_TOKEN_CONST:
         token_type = token->type;
 
         token = njs_lexer_peek_token(parser->lexer, token, 0);
@@ -5328,9 +5335,6 @@ njs_parser_iteration_statement_for_map(njs_parser_t *parser,
 
         break;
 
-    case NJS_TOKEN_CONST:
-        return njs_parser_not_supported(parser, token);
-
     default:
         njs_parser_next(parser, njs_parser_expression);
         break;
@@ -5368,6 +5372,10 @@ njs_parser_for_var_binding_or_var_list(njs_parser_t *parser,
         type = NJS_VARIABLE_LET;
         break;
 
+    case NJS_TOKEN_CONST:
+        type = NJS_VARIABLE_CONST;
+        break;
+
     default:
         type = NJS_VARIABLE_VAR;
         break;
index 6771e58b4f9174f60692f48f7f723fea7442cb38..94e58be56007a9ac8c98f6ed7bd98eb86ef2ec51 100644 (file)
@@ -82,7 +82,7 @@ njs_scope_valid_value(njs_vm_t *vm, njs_index_t index)
     value = njs_scope_value(vm, index);
 
     if (!njs_is_valid(value)) {
-        if (njs_scope_index_var(index) == NJS_VARIABLE_LET) {
+        if (njs_scope_index_var(index) <= NJS_VARIABLE_LET) {
             njs_reference_error(vm, "cannot access to variable "
                                     "before initialization");
             return NULL;
index 5a532fb378d427bea7f120f87ef2101feb581dbd..f775aeff5c6b99c4e8aed77a59648fca2667b81e 100644 (file)
@@ -184,6 +184,7 @@ njs_variable_scope_find(njs_parser_t *parser, njs_parser_scope_t *scope,
     }
 
     switch (type) {
+    case NJS_VARIABLE_CONST:
     case NJS_VARIABLE_LET:
         if (scope->type == NJS_SCOPE_GLOBAL
             && parser->undefined_id == unique_id)
@@ -222,7 +223,7 @@ njs_variable_scope_find(njs_parser_t *parser, njs_parser_scope_t *scope,
         return root;
     }
 
-    if (var->type == NJS_VARIABLE_LET) {
+    if (var->type == NJS_VARIABLE_LET || var->type == NJS_VARIABLE_CONST) {
         goto failed;
     }
 
index 1a99fc7336fffb69d60218c8e4bb82986ef02683..947884881b46f057ec96ef3f190f3a778bb74e40 100644 (file)
@@ -938,6 +938,10 @@ next:
                 njs_vmcode_error(vm, pc);
                 goto error;
 
+            case NJS_VMCODE_ASSIGNMENT_ERROR:
+                njs_type_error(vm, "assignment to constant variable");
+                goto error;
+
             default:
                 njs_internal_error(vm, "%d has NO retval", op);
                 goto error;
index f7db97589b993ec211be3aed06a8606e86e7a39e..8a28d9999fa5608f31d956719f47cdcbd4d7c250 100644 (file)
@@ -62,6 +62,7 @@ enum {
     NJS_VMCODE_LET_UPDATE,
     NJS_VMCODE_INITIALIZATION_TEST,
     NJS_VMCODE_NOT_INITIALIZED,
+    NJS_VMCODE_ASSIGNMENT_ERROR,
 
     NJS_VMCODE_ERROR,
 
index f473dc4a7125f5d2a173ee40de964f7083379fb1..4e46e21bdd092616e56593fa1dd72a60429e66c4 100644 (file)
@@ -9481,6 +9481,27 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("function static() {}"),
       njs_str("SyntaxError: Unexpected token \"static\" in 1") },
 
+    { njs_str("var arr = [];"
+              "function fn(one) {"
+              "    var x = one + 1;"
+              "    let y = one + 2;"
+              "    const u = one + 4;"
+              "    {"
+              "        {"
+              "            let z = one + 3;"
+              "            const v = one + 5;"
+              "            function f() {"
+              "                arr.push(one); arr.push(x);"
+              "                arr.push(y);   arr.push(z);"
+              "                arr.push(u);   arr.push(v);"
+              "            }"
+              "            f();"
+              "        }"
+              "    }"
+              "}"
+              "fn(1); arr"),
+      njs_str("1,2,3,4,5,6") },
+
     /* Recursive factorial. */
 
     { njs_str("function f(a) {"
@@ -20009,6 +20030,177 @@ static njs_unit_test_t  njs_test[] =
 
     { njs_str("function static() {}"),
       njs_str("SyntaxError: Unexpected token \"static\" in 1") },
+
+    /* const */
+
+    { njs_str("const x"),
+      njs_str("SyntaxError: missing initializer in const declaration") },
+
+    { njs_str("const x = 1; x"),
+      njs_str("1") },
+
+    { njs_str("const x = 1; x = 1"),
+      njs_str("TypeError: assignment to constant variable") },
+
+    { njs_str("function abc() {const x}"),
+      njs_str("SyntaxError: missing initializer in const declaration") },
+
+    { njs_str("const x = [123]; x"),
+      njs_str("123") },
+
+    { njs_str("const x = () => x; x()"),
+      njs_str("[object Function]") },
+
+    { njs_str("const x = (() => x)()"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("x; const x = 123"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("const x = x + 123"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("const x; var x"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("const x; let x"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("let x; const x"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("const x = 1; function x() {}"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("function x() {} const x = 1"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("function x() {const x; var x}"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("function x() {var x; const x}"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("const x = function f() {const f = 1}"),
+      njs_str("undefined") },
+
+    { njs_str("let res; const x = 1;"
+              "{const x = 2; res = x}"
+              "[x, res]"),
+      njs_str("1,2") },
+
+    { njs_str("let res; const x = 1;"
+              "if (true) {const x = 2; res = x}"
+              "[x, res]"),
+      njs_str("1,2") },
+
+    { njs_str("function func() {return x}"
+              "const x = 123;"
+              "func()"),
+      njs_str("123") },
+
+    { njs_str("function func() {return x}"
+              "func();"
+              "const x = 123"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("function func() {return () => x}"
+              "const x = 123;"
+              "func()()"),
+      njs_str("123") },
+
+    { njs_str("function func() {return () => x++}"
+              "const x = 123;"
+              "func()()"),
+      njs_str("TypeError: assignment to constant variable") },
+
+    { njs_str("for (const i = 0; i < 1; i++) {}"),
+      njs_str("TypeError: assignment to constant variable") },
+
+    { njs_str("let res = [];"
+              "for (const n in [1,2,3]) {res.push(n)}"
+              "res"),
+      njs_str("0,1,2") },
+
+    { njs_str("let arr = [], res = [];"
+              ""
+              "for (const n in [1,2,3]) {"
+              "    arr.push(() => n);"
+              "}"
+              ""
+              "for (let n in arr) {"
+              "    res.push(arr[n]());"
+              "}"
+              "res"),
+      njs_str("0,1,2") },
+
+    { njs_str("let arr = [];"
+              ""
+              "for (const n in [1,2,3]) {"
+              "    let n = 1;"
+              "    arr.push(n);"
+              "}"
+              "arr"),
+      njs_str("1,1,1") },
+
+    { njs_str("for (const n in [1,2,3]) {"
+              "    let n = n + 1;"
+              "}"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("for (const n in [1,2,3]) {}"
+              "n"),
+      njs_str("ReferenceError: \"n\" is not defined") },
+
+    { njs_str("for (const n in [1,n,3]) {}"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("(function() {"
+              "function f() {return x + 1}"
+              "function abc() {f()};"
+              "abc();"
+              "const x = 1;"
+              "}())"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("if (false) const x = 1"),
+      njs_str("SyntaxError: const declaration cannot appear in a single-statement context in 1") },
+
+    { njs_str("while (false) const x = 1"),
+      njs_str("SyntaxError: const declaration cannot appear in a single-statement context in 1") },
+
+    { njs_str("for (;;) const x = 1"),
+      njs_str("SyntaxError: const declaration cannot appear in a single-statement context in 1") },
+
+    { njs_str("try {} catch (e) {const e = 1}"),
+      njs_str("SyntaxError: \"e\" has already been declared in 1") },
+
+    { njs_str("let arr = []; const x = 2;"
+              "switch(true) {default: const x = 1; arr.push(x)}"
+              "arr.push(x); arr"),
+      njs_str("1,2") },
+
+    { njs_str("let res;"
+              "switch(true) {case true: const x = 1; default: x = 2; res = x} res"),
+      njs_str("TypeError: assignment to constant variable") },
+
+    { njs_str("const null"),
+      njs_str("SyntaxError: Unexpected token \"null\" in 1") },
+
+    { njs_str("const continue"),
+      njs_str("SyntaxError: Unexpected token \"continue\" in 1") },
+
+    { njs_str("const undefined"),
+      njs_str("SyntaxError: \"undefined\" has already been declared in 1") },
+
+    { njs_str("const a = 1; globalThis.a"),
+      njs_str("undefined") },
+
+    { njs_str("if (false) {x = 2} else {x = 1} const x = 0"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("const const"),
+      njs_str("SyntaxError: Unexpected token \"const\" in 1") },
 };