Scoping

JS++ programs are made of declarations like variable declarations, class declarations, and so on. A scope acts as a container for these declarations. A declaration's scope determines its visibility and where it can be accessed.

Unlike JavaScript, which is function-scoped, JS++ is block-scoped. This means that each new block ({ ... }) in JS++ creates a new scope. The scope created by the block acts as a container so all declarations inside it are contained by this block container; in essence, the declaration is "scoped" to the block.

If we try to access a variable from outside the block it was declared, it is illegal and the compiler will raise an error.

In the following code, we create a block and declare some variables inside the block:

1
2
3
4
{
    int x = 1;
    int y = x + 2;
}

However, if we try to access x or y from outside the block, we will receive a compile-time error. Consider the following illegal code:

1
2
3
4
5
6
7
{
    int x = 1;
    int y = x + 2;
}
 
x = 2; // error here
y = 3; // error here as well

[ ERROR ] JSPPE0001: 'x' has not been declared at line 6 char 0
[ ERROR ] JSPPE0001: 'y' has not been declared at line 7 char 0
  FAILED  Compiled in 1ms: (2) errors and (0) warnings

We received a compiler error when we tried to access x and y from outside the scope it was declared.

Unlike JavaScript, which "hoists" variables, a variable cannot be accessed before it is declared even if it is "in scope". Therefore, the following code is illegal and will result in a compile-time error:

1
2
3
4
{
    int y = x + 1;
    int x = 1;
}

[ ERROR ] JSPPE0001: 'x' has not been declared at line 2 char 12
  FAILED  Compiled in 1ms: (1) errors and (0) warnings

Nested Blocks and Scoping

When blocks are nested, a nested block can access all the declarations from all the outer blocks containing it (provided that the declaration was declared before the nested block). So the following is completely valid:

1
2
3
4
5
6
{
    int x = 1;
    {
        int y = x + 1;
    }
}

File Scopes and "Global" Scope

JS++ has no concept of a "global" scope. If a declaration is not declared inside the curly braces of a block, it is scoped to the file. Like all other declarations in JS++, even file-scoped declarations cannot be accessed before they are declared.

By the end of a file, the declaration is no longer available. This includes file-scoped declarations that are exported as part of a module. If a module with file-scoped declarations is imported, the file-scoped declarations are not imported.

Loops

Loops which enable variable declarations as part of its syntax (e.g. the initialization part of the for loop) will scope the variables to the loop itself. Therefore, outside of the loop, the variables cannot be accessed.

1
2
3
4
5
for (int i = 0; i < 5; ++i) {
    // ...
}
 
i = 10; // error, cannot access 'i' here

Classes, Interfaces, and Hoisting Exceptions

Classes operate a little differently when it comes to scoping and access rules.

Methods are Hoisted

When a method is declared inside a class, the method can be accessed anywhere in the class - even before the method's declaration.

This is perfectly valid JS++ code:

1
2
3
4
5
6
7
8
9
10
11
class MyClass
{
    public int x = z(); // The method 'z' is called before it is declared
    public int y() {
        return z(); // The method 'z' is called before it is declared
    }
     
    public int z() {
        return 1;
    }
}

Classes are Hoisted

Inner classes and nested classes can also be accessed from its parent class - even before the class's declaration.

1
2
3
4
5
6
class MyClass
{
    public MyClass2 x = new MyClass2(); // 'MyClass2' is instantiated before its declaration
     
    class MyClass2 {}
}

However, if we try to instantiate a class from outside of a class scope, we must declare the class first.

Fields are not Hoisted

The same is not true for fields. A field cannot access another field in its initializer before the other field is declared. Therefore, the following is illegal:

1
2
3
4
5
class MyClass
{
    public int x = y; // 'y' is not declared yet
    public int y = 1;
}

[ ERROR ] JSPPE0001: 'y' has not been declared at line 3 char 19
  FAILED  Compiled in 1ms: (1) errors and (0) warnings

However, since a method is not executed immediately upon instantiation, they can access fields before the fields are declared:

1
2
3
4
5
6
7
8
class MyClass
{
    public int y() {
        return x; // valid
    }
     
    public int x = 1;
}

This rule also applies to constructors since constructors are not executed until the fields are initialized.

Hoisting during Inheritance

When classes or interfaces are attempting to inherit, they can inherit from a class or interface which has not been declared yet (as long as the class or interface gets declared eventually and is in scope).

Differences to JavaScript

Unlike JavaScript, which is function-scoped, JS++ is block-scoped. Furthermore, JS++ does not "hoist" variable declarations.

In a very basic example, this is how JavaScript scopes variables:

1
2
3
4
5
6
7
8
9
10
11
12
13
function myFunction() {
    var x = 1;
     
    {
        var y = 1;
    }
     
    if (true) {
        if (1) {
            var z = 1;
        }
    }
}

In the above code, x, y, and z are all scoped to the same scope created by myFunction. In JS++, x, y, and z are all scoped to different blocks. y can access x in JS++ (but not vise versa), and z can access all of the other variables (but also not vise versa). Experience shows that despite JavaScript's design decision, most JavaScript code is written as though it were a block-scoped language anyway. Thus, most code will remain semantically equivalent when transitioning from JavaScript to JS++ despite the changes in scoping rules.

Disadvantages of JavaScript's Function Scoping

In the following JavaScript code, the function-scoped variable i may have unintended consequences:

1
2
3
4
5
6
7
8
9
10
11
for (var i = 0; i < 5; ++i) {
    // Do something
}
 
var i; // Re-declaration. Essentially does nothing.
if (i != 5) {
    throw new Error("We thought this would never happen!"); // ... but it happens
}
else {
    i = 5;
}

In JavaScript, it can be very difficult to track down "re-declarations" as you see on line 5. If a variable was already declared and has a value, re-declaring it will not reset the value to undefined in JavaScript. The above is a contrived example, but it should be clear how it can become difficult to manage and track in larger and more sophisticated programs.

Furthermore, it can further become very difficult to keep track of which variable names are available or already used. Consider the i variable which is typically used in for loops. In nested loops, the following could happen:

1
2
3
4
5
6
7
for (var i = 0; i <= 5000; ++i) {
    // ...
     
    for (var i = 0; i <= 5000; ++i) {
        // ...
    }
}

This is a common bug because we intuitively use i as the for loop's counter variable. The inner loop will actually be set to zero (0) and effectively reset the outer loop to zero. Unfortunately, JavaScript does not raise errors in this situation. The JS++ compiler will catch these errors.

JavaScript and Hoisting

Unlike JavaScript, JS++ does not "hoist" variable declarations. Hoisting is where a variable is declared to the nearest function (even if it's nested 50 functions deep) so that the variable can be accessed anywhere in the function, even if the access occurs before the declaration. In simpler terms, a variable can be accessed before its declaration in JavaScript as long as it gets declared eventually within the function or global scope; although, this may result in unexpected values or side effects.

The following code is valid in JavaScript:

1
2
var y = x + 1;
var x = 2;

If we run the above JavaScript code, the value of y will be NaN (Not a Number). If we run the same code above through the JS++ compiler, we will receive a compile-time error:

[ ERROR ] JSPPE0001: 'x' has not been declared at line 1 char 8
  FAILED  Compiled in 1ms: (1) errors and (0) warnings

See Also

Share

HTML | BBCode | Direct Link