njs: block scoped function definitions support.

overview function definitions are block scoped:

root@node:~# cat test.mjs 

function test() {
    console.log('top');
}

{
    setImmediate(test);
    function test() {
        console.log('block1');
    }
}

{
    function test() {
        console.log('block2');
    }
    setImmediate(test);

    {
        function test() {
            console.log('block3');
        }
        setImmediate(test);
    }
}

test();
root@node:~# node --experimental-modules test.mjs 
(node:4894) ExperimentalWarning: The ESM module loader is experimental.
top
block1
block2
block3

only top or block level definitions are allowed:

root@node:~# cat test1.mjs 
if (true) function test() {}
root@node:~# node --experimental-modules test1.mjs 
(node:4976) ExperimentalWarning: The ESM module loader is experimental.
file:///root/test1.mjs:1
if (true) function test() {}
          ^^^^^^^^

SyntaxError: In strict mode code, functions can only be declared at top level or inside a block.
    at translators.set (internal/modules/esm/translators.js:51:18)

and some weird resolution rules:

root@node:~# cat test2.mjs 
// At the top level of a function, or script, 
// function declarations are treated like var declarations
// rather than like lexical declarations.
function fblock() {
    var test;
    function test() {
    }
}

// But not at the top level of a module!
function test() {}
var test;
root@node:~# node --experimental-modules test2.mjs 
(node:5018) ExperimentalWarning: The ESM module loader is experimental.
file:///root/test2.mjs:12
var test;
    ^

SyntaxError: Identifier 'test' has already been declared
    at translators.set (internal/modules/esm/translators.js:51:18)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 34 (34 by maintainers)

Commits related to this issue

Most upvoted comments

@hongzhidao

BTW, how do you get the result like below? the percentage of pass, for example 25.2%.

you need --summary option.

@xeioex

The code above inserts a variable into multiple scopes (into function and block scopes)

I thought about it. Essentially we need two different things to store the added variable information: scope->declared_vars and scope->variables. scope-> declared_vars is required in each scope and will be used to check duplicated declarations as needed, scope->variables is kept the current way. Now we combine them together.

BTW, how do you get the result like below? the percentage of pass, for example 25.2%.

- - Passed 6785 tests (25.0%)
- - Failed 20334 tests (75.0%)
+ - Passed 6831 tests (25.2%)
+ - Failed 20288 tests (74.8%)

@hongzhidao

declaration_exception.js is missing

Problem with lexically declared name:

;{ function f() {} { var f }}

I updated it again, spliting it into separate patches 😃 https://gist.github.com/hongzhidao/4f4e902bfec4a44834f93aa77dbfb19d

Problem with lexically declared name:

;{ function f() {} { var f }}

Is it a problem? Get it, yes.

I follow the instruction, but I don’t know how to filter out the right information.

  1. use --command='<full path to njs cli binary> -q' to disable filenames in output of exceptions (randoms filenames are used by tests)
  2. you need to run test262 2 times before and after the test, to see the diff diff -u test262_njs_orig.log test262_njs_after.log

There are four cases:

  1. function definition at top level of module.
  2. function definition at top level of another function.
  3. function definition inside a block.
  4. function definition at top level of script.
// 1. or 4. depends on vm->options.module
function f() {}
function a() {
    // 2.
    function f() {}
}
function b() {
    {
        // 3.
        function f() {}
    }
}

Cases 1. and 3. are identical. I use block in tests because node’s REPL doesn’t support evaluation as a module.

  • the definition behave like lexically declared variable (let f)
root@node:~# node --use-strict
> ;{ function f() {} function f() {} }
SyntaxError: Identifier 'f' has already been declared
> ;{ function f() { return 1; } { function f() { return 2; } } f(); }
1
> ;{ { function f() { return 1; } f(); } function f() { return 2; } }
1
> ;{ var f; function f() {} }
SyntaxError: Identifier 'f' has already been declared
> ;{ let f; function f() {} }
SyntaxError: Identifier 'f' has already been declared
> ;{ function f() {} f = 1; f}
1
  • the definition is hoisted to the top of the block and initialized there. so there is no TDZ.
> ;{ typeof f; function f() {} }
'function'

The cases 2. and 4. are identical.

  • the definition behave like var declared variable.
> (() => { function f() { return 1; } function f() { return 2; } return f(); })()
2
> (() => { function f() { return 1; } f = 1; return f; })()
1
  • the definition is hoisted to the top of the surrounding function. and initialized before any other var declared stuff.
> (() => { var x = typeof f; function f() { return 1; } return x; })()
'function'
> (() => { var f; var x = typeof f; function f() { return 1; } return x; })()
'function'
> (() => { var f = 1; var x = typeof f; function f() { return 1; } return x; })()
'number'
> (() => { var x = typeof f; function f() { return 1; } var f = 1; return x; })()
'function'

We have to change our module wrapper from

function() {
/* module code */
}

to

() => {
    if (true) {
        /* module code */
    }
}

to support the feature.

@drsm

My understand.

  1. functions can only be declared at top level or inside a block.
  2. functions scope is at where it declared.
  3. with if (1) x, there is no scope inside if.

Right?