-
Notifications
You must be signed in to change notification settings - Fork 903
Description
This is one of the tasks outline in #2163
The EcmaScript spec makes a firm distinction between Objects and environment records (which are what the spec calls scopes), but there are two places where these concepts collide in subtle ways. To explain that let's start by looking at what operations the spec defines on environment records:
Environment methods
The spec
defines
a number of methods on environment records:
- HasBinding
- CreateMutableBinding
- CreateImmutableBinding
- InitializeBinding
- SetMutableBinding
- GetBindingValue
- DeleteBinding
- HasThisBinding
- HasSuperBinding
- WithBaseObject
The definition of var, and let and const, are then specified in terms of these operations. Speciifically var uses SetMutableBinding if the binding does not already exists, while let and const use the CreateMutableBinding and CreateImmutableBinding operations.
There is also a hierarchy of environment record types:
- Environment Record
- Declaration Environment Record
- Function Environment Record
- Module Environment Record
- Object Environment Record
- Global Environment Record
- Declaration Environment Record
We need to talk about a couple of these to make sense of things later on.
Declaration Environment Record
This is pretty much what we think of when we think about a scope. It binds names (which are always strings) to values along with information about whether those bindings are mutable, initialised, and strict.
The Object Environment Record
The Object Environment Record is used in with expressions where the properties of an object are exposed as a scope, but it is also used as part of the Global Environment Record. There is one extremely important thing to note about it however, and that is the definition of CreateImmutableBinding
The CreateImmutableBinding concrete method of an Object Environment Record is never used within this specification.
So, from that statement we can tell that we will never have a statement like
const foo = 'bar'which creates a binding on an Object Environment Record. So something interesting is happening in with statements. If you do
let o = {foo: 'bar'}
with (o) {
const foo = 'baz';
console.log(foo);
}
console.log(o);Will run, and log 'baz' and 'bar'. The block statement introduces a new Declaration Environment Record as the current execution environment, and so const foo = 'bar' is never executes against the Object Environment Record, and it isn't possible to have that const declaration without introducing that new scope.
Global Environment Record
Now we know about Declaration and Object environment records we can talk about Global Environment Records. They are defined as a composition of a Declaration Environment Record, a global object, and an Object Environment Record. Why a global object and a separate Object Environment Record? Well, the specification allows for the global object, and globalThis to be different objects. I'm unsure of the original rationale for allowing that difference, but I can see good reasons for it to support historical behaviour, ensure security, or make performance guarantees.
The operations on the Global Environment Record are generally defined by checking for bindings on the declaration have and then delegating to the object half, except for the CreateMutableBinding and CreateImmutableB which never delegate to the Object Environment Record. This means that, assuming the global object and globalThis are the same then:
var a = 'Hello!';
console.log(Object.getOwnPropertyDescriptor(this, 'a'));should give us
{ value: 'Hello!', writable: true, enumerable: true, configurable: false }
while
let a = 'Hello!';
console.log(Object.getOwnPropertyDescriptor(this, 'a'));will log undefined.
Realms
The Global Environment Record doesn't define everything that's at the top of an EcmaScript execution context. A Realm Record holds a Global Environment Record along with the set of intrinsics (such as %Object.prototype% and some other information.
TopLevel in Rhino
TopLevel currently serves multiple roles. It is:
- The realm as it is the place where we hold intrinsic definitions
- The Declaration Environment Record half of the Global Environment Record as it holds any global
constdefinitions. - The Object Environment Record half of the Global Environment Record as it hold all the
varand function definitions that are not created via the create binding methods. - It is
globalThisand thethispassed to functions under various circumstances
Decomposing TopLevel into the scope and the globalThis part allows us to later make TopLevel scope, while keeping globalThis as an object.
It would also allow us to make context initialisation always take happen on a TopLevel while still supporting a custom global object as the globalThis object. This would allow use to always put intrinsics on the TopLevel and have more reliable behaviour than we currently do for generator functions and similar which only have intrinsics.