The new blocked scoped binding are let, const, and block functions.
let, const, and block functions declarations should all be allowed in a Program, a FunctionBody, or a Block. However, none should be considered statements by themselves, and so cannot appear in unprotected statement context. For example:
if (b) var x = 9; // legal if (b) let x = 9; // illegal -- must be rejected if (b) { let x = 9; } // legal
To accommodate this, we adjust the following productions from the ES5 grammar:
Block:
{ StatementList? }
SourceElement:
Statement
FunctionDeclaration
to instead be
Block:
{ SourceElements? }
SourceElement:
Statement
Declaration
Declaration:
LetDeclaration
ConstDeclaration
FunctionDeclaration
Note that a VariableStatement remains a kind of statement, and so can appear in unprotected statement context, as in our first if example above. Since the scope of a VariableStatement is hoisted into the containing Program or FunctionBody, the appearance of a VariableStatement in statement context does not cause confusion about its semantics.
Confusingly, what the ES5 grammar refers to as a VariableDeclaration is neither a statement nor a declaration, but rather the name-initializer bindings that occur to the right of the var keyword. The grammar should use VariableDeclarator or something better to distinguish this non-terminal from FunctionDeclaration, etc.
ES5/strict and ES-Harmony are lexically scoped. However, because ES5/strict does not allow a Declaration in a Block, it need only create an Environment Record on each entry to a Program, Function, or catch-clause. (Full ES5 also creates an Environment Record on each entry to a with-statement, but that need not concern us here.) For ES-Harmony, we need to create a Declarative Environment Record on each entry to a Block as well. The new semantics of block entry resembles the ES5 semantics for catch-clause entry (12.14).
NOTE: No matter how control leaves the Block the LexicalEnvironment is always restored to its former state.
Since ConstDeclarations may appear at in global code, we need to promote CreateImmutableBinding(N) and InitializeImmutableBinding(N,V) to the internal supertype, Environment Record, and provide an implementation of these methods for Object Environment Records as well. Since let, like const, has a temporal dead zone, we need the same separation between creating a binding vs. initializing for mutable bindings that we have for immutable bindings.
CreateMutableBinding(N,D) to create an uninitialized mutable binding. InitializeImmutableBinding(N,V) method to simply InitializeBinding(N,V). SetMutableBinding(N,V,S) we assert that the mutable binding must already have been initialized. CreateMutableBinding to ensure that the binding is initialized before it can be observed; to maintain compatibility.CreateMutableBinding(N,D): Create a new but uninitialized mutable binding ...InitializeBinding(N,V): Initialize the value of an already existing but uninitialized binding ...SetMutableBinding(N,V,S): Set the value of an already initialized binding ...The concrete Environment Record method CreateMutableBinding for declarative environment records creates a new uninitialized mutable binding for the name N. ...
3. Create an uninitialized mutable binding in envRec for N.
... If the binding is an immutable binding and S is true, then a TypeError is thrown. ...
4. Else this must be an attempt to change the value of an immutable binding, so if S is true, throw a TypeError exception.
The concrete Environment Record method CreateImmutableBinding for declarative environment records creates a new uninitialized immutable binding for the name N. A binding must not already exist in this environment record for N.
... An uninitialized binding for N must already exist. ...
2. Assert: envRec must have an uninitialized binding for N.
...
4. Record that the binding for N in envRec has been initialized.
The concrete Environment Record method CreateMutableBinding for object environment records creates a new uninitialized mutable binding for the name N. If Boolean argument D is provided and has the value true the new binding is marked as being subject to configuration.
Between 1 and 2. If envRec has an uninitialized binding for N and S is true, throw a ReferenceError exception.
Between 1 and 2. If envRec has an uninitialized binding for N and S is true, throw a ReferenceError exception.
Between 1 and 2. If envRec has an uninitialized binding for N and S is true, throw a ReferenceError exception.
The concrete Environment Record method CreateImmutableBinding for object environment records creates a new uninitialized immutable binding for the name N.
The concrete Environment Record method InitializeBinding for object environment records creates in an environment record’s associated binding object a property whose name is N and initializes it to V. A property named N must not already exist in the binding object. On success, it drops its own separate record that N is uninitialized.
as arguments.
Open question: Parity error applies to the last true argument above. If we adopt my proposed #2, then it should instead be S.
More questions:
let not in a block statement and var interact in global code? Are both allowed to declare the same name? If so, does the let binding shadow the var one?let not in a block statement and var interact in function code? Currently (ES5 and older), var a; in function f(a) {...} restates the argument binding.
We should forbid let and var binding the same name in the same scope. Sorry if I missed that in this strawman.
— Brendan Eich 2010/06/09 14:54
We should also forbid var-declarations that have already been “shadowed” by let-declarations, such as:
var x = "outer"; function f() { { let x = "inner"; { // after hoisting, the initializer mutates the let-binding! var x = "sneaky"; // this should be illegal } print(x); // prints sneaky! } return x; // prints undefined! }
This might be implied by Brendan’s last statement, but I’m just clarifying that it’s more than just forbidding let x; var x;.
— Dave Herman 2010/08/20 16:33
After step 1. Let lexEnv be the environment record component of the running execution context’s LexicalEnvironment.
Step 5-delta. Change all uses of env to lexEnv. Generalize this to apply to each Declaration in code, rather than just each FunctionDeclaration. Rephrase to avoid Declarations in nested blocks and catch-clauses. Refactor so that the particulars for each kind of Declaration are defined by that Declaration.
Step 8-delta. Rephrase to make clear that this step is skipped on entry to blocks and catch-clauses, and that the enumeration of VariableDeclarations in the remaining cases must traverse into nested blocks and catch-clauses.