ES6 global environment configuration proposal
Goals: Duplicate current browser global semantics Existing ES5 built-ins are properties of "the global object" Global Function and Var declarations create properties of the global object Ad hoc semantics relating explicit Function/Var global bindings and non-declared global object properties include attribute and inheritance issues Don't make the web compatible global object worse New declaration forms (let/const/class) shouldn't pollute the global object Don't add any new ES built-in bindings to the global object Don't force non-browser or nested ECMAScript global environment hosts to duplicate quirky browser global semantics
Basic Approach Define a global environment model that is flexible enough to both maintain compat. with existing browser semantics and allow simpler non-browser global semantics
Three sub-environment of the global environment record: Built-ins: declarative environment record , immutable bindings for standard and impl defined built-in globals Global Object: A Object environment record, that shadows the built-ins. Content is host defined and may replicate global bindings for globals Top Lex: A declarative environment record that shadows the Global Object environment for lexical bindings created by top level declarations
These sub-environments are hidden behind the environment record interface of a single global environment record, hence their existence does not impact the rest of the specification. When a global environment is initialized it can be configured so var and function declaration bindings go into either the Global Object environment or the Top Lex environment
The structuring of Top Lex to support declarations from multiple program/script fragments is an orthogonal issue. However, I favor a single top lex shared by all program/script fragments.
Common ways to configure the global environment:
Browser compat: ES5 built-in global bindings replicated on the global object any new ES6 built-in global bindings are not replicated on the global object global var/function declarations create bindings on global object environment global const/let/class/module declarations create bindings in Top Lex This would be specified in the browser specific optional/normative annex.
Clean Global Object No built-ins replicated on global object all ES top level declarations (including var/function) create binding in Top Lex global object properties are only be created via explicit reference to global this
No Global Object
Global object is undefined
all ES top level declarations (including var/function) create binding in Top Lex
Thoughts? I'm ready to write the spec for this.
Couldn’t all ES6 built-ins come from modules? Then I’d expect them to be in Top Lex instead of their own environment.
Le 04/05/2012 06:42, Allen Wirfs-Brock a écrit :
Goals: Duplicate current browser global semantics Existing ES5 built-ins are properties of "the global object" Global Function and Var declarations create properties of the global object Ad hoc semantics relating explicit Function/Var global bindings and non-declared global object properties include attribute and inheritance issues Don't make the web compatible global object worse New declaration forms (let/const/class) shouldn't pollute the global object Don't add any new ES built-in bindings to the global object Don't force non-browser or nested ECMAScript global environment hosts to duplicate quirky browser global semantics
What are "nested ECMAScript global environment hosts"? Can you provide an example? Is what has been done with the Firefox WebConsole (and how it interacts with a page) such an example?
I don't understand this last goal regarding non-browser environments. As an example, Node.js got away from the shared global declarations mess by defining a module system (where global declarations of each module is local to the module). ES6 is defining a module system with an equivalent design. Moving forward, why would an ES6-compilant non-browser environment have different rules than the standard one for modules? Are there use cases that can't be fulfilled with custom module loaders?
Basic Approach Define a global environment model that is flexible enough to both maintain compat. with existing browser semantics and allow simpler non-browser global semantics
Three sub-environment of the global environment record: Built-ins: declarative environment record , immutable bindings for standard and impl defined built-in globals Global Object: A Object environment record, that shadows the built-ins. Content is host defined and may replicate global bindings for globals Top Lex: A declarative environment record that shadows the Global Object environment for lexical bindings created by top level declarations
These sub-environments are hidden behind the environment record interface of a single global environment record, hence their existence does not impact the rest of the specification. When a global environment is initialized it can be configured so var and function declaration bindings go into either the Global Object environment or the Top Lex environment
The structuring of Top Lex to support declarations from multiple program/script fragments is an orthogonal issue. However, I favor a single top lex shared by all program/script fragments.
If they all share the top lex, the issue that came with web browsers (overlapping global declaration) remains exactly as it is. Is it what you meant? I would naively be more in favor of one top lex per program/script fragments to avoid to inadvertidly share global declaration of each program.
Common ways to configure the global environment:
Browser compat: ES5 built-in global bindings replicated on the global object any new ES6 built-in global bindings are not replicated on the global object
I disagree with this point as it makes web browsers inconsistent. Some code does feature detection by testing presence on the global object (since it's currently equivalent to "typeof builtinGlobalName !== 'undefined'"). You're saying that the equivalent should be broken, so, ES5 and ES6 features won't be feature-detectable the same way. I don't think there is a loss in having ES6 built-in global bindings replicated on the global object. Actually, Firefox does define WeakMap and Map as global object properties.
global var/function declarations create bindings on global object environment global const/let/class/module declarations create bindings in Top Lex This would be specified in the browser specific optional/normative annex. Clean Global Object No built-ins replicated on global object all ES top level declarations (including var/function) create binding in Top Lex global object properties are only be created via explicit reference to global this No Global Object Global object is undefined all ES top level declarations (including var/function) create binding in Top Lex
Thoughts? I'm ready to write the spec for this.
I think it's a very important work. As far as I know, ECMAScript 5 and below did not define anything regarding what happens when several programs interact. We all know how it works in browsers, but I don't think it's specified anywhere neither in ECMAScript, WebIDL or HTML Living Standard. Especially with modules coming along, I think it's important this topic to be standardized at the ECMAScript level. The nomenclature you're defining may actually need to be reused in custom module loaders.
On May 4, 2012, at 2:29 AM, Axel Rauschmayer wrote:
Couldn’t all ES6 built-ins come from modules? Then I’d expect them to be in Top Lex instead of their own environment.
In a browser, assigning to the global object, over-writes binding for built-ins. Try:
<script> this.RegExp='hello'; alert(RegExp); </script>
On May 4, 2012, at 8:33 AM, David Bruant wrote:
Hi,
Le 04/05/2012 06:42, Allen Wirfs-Brock a écrit :
Goals: Duplicate current browser global semantics Existing ES5 built-ins are properties of "the global object" Global Function and Var declarations create properties of the global object Ad hoc semantics relating explicit Function/Var global bindings and non-declared global object properties include attribute and inheritance issues Don't make the web compatible global object worse New declaration forms (let/const/class) shouldn't pollute the global object Don't add any new ES built-in bindings to the global object Don't force non-browser or nested ECMAScript global environment hosts to duplicate quirky browser global semantics What are "nested ECMAScript global environment hosts"? Can you provide an example? Is what has been done with the Firefox WebConsole (and how it interacts with a page) such an example?
The sort of nested top-level environments that can be created using Module Loaders harmony:module_loaders (but they aren't "modules" or "loaders"). The working term I'm using as I rough out spec. language is "Realm" but the terminology is certainly open for discussion.
I don't understand this last goal regarding non-browser environments. As an example, Node.js got away from the shared global declarations mess by defining a module system (where global declarations of each module is local to the module). ES6 is defining a module system with an equivalent design. Moving forward, why would an ES6-compilant non-browser environment have different rules than the standard one for modules? Are there use cases that can't be fulfilled with custom module loaders?
What I'm talking about, is the specification that lies behind what you can do with a custom module loader. It is essentially the interface between global declaration semantics of the language and what module loaders can configure.
I'm also not talking about the top-level inside a module, I'm talking about the top level of ES Program productions and in particular the relationship between the global object and Program top-level declarations.
The sort of "quirk" that I don't think needs to be (mandatorily) forced upon non-browser top-levels includes things like treating placing top-level var/function declarations into a different environment contour (the global object) from the new lexical declaration (const/let etc.). Polluting a top-levels global object with declaration bindings or built-in bindings, or even requiring a top level to have a global object.
Basic Approach Define a global environment model that is flexible enough to both maintain compat. with existing browser semantics and allow simpler non-browser global semantics
Three sub-environment of the global environment record: Built-ins: declarative environment record , immutable bindings for standard and impl defined built-in globals Global Object: A Object environment record, that shadows the built-ins. Content is host defined and may replicate global bindings for globals Top Lex: A declarative environment record that shadows the Global Object environment for lexical bindings created by top level declarations
These sub-environments are hidden behind the environment record interface of a single global environment record, hence their existence does not impact the rest of the specification. When a global environment is initialized it can be configured so var and function declaration bindings go into either the Global Object environment or the Top Lex environment
The structuring of Top Lex to support declarations from multiple program/script fragments is an orthogonal issue. However, I favor a single top lex shared by all program/script fragments. If they all share the top lex, the issue that came with web browsers (overlapping global declaration) remains exactly as it is. Is it what you meant? I would naively be more in favor of one top lex per program/script fragments to avoid to inadvertidly share global declaration of each program.
How to handle the declaration interactions between Programs/scripts was extensively discussed on the threat esdiscuss/2012-January/019807. I think we are going to end up going with the STL (Single Top Level) solution. This is a decision that we need to finalize at the next TC39 meeting. However, browser compatibility requires that var/function declarations create global object properties. That is why the structure of top lex is an orthogonal issue.
Common ways to configure the global environment:
Browser compat: ES5 built-in global bindings replicated on the global object any new ES6 built-in global bindings are not replicated on the global object I disagree with this point as it makes web browsers inconsistent. Some code does feature detection by testing presence on the global object (since it's currently equivalent to "typeof builtinGlobalName !== 'undefined'"). You're saying that the equivalent should be broken, so, ES5 and ES6 features won't be feature-detectable the same way. I don't think there is a loss in having ES6 built-in global bindings replicated on the global object. Actually, Firefox does define WeakMap and Map as global object properties.
"typeof builtinGlobalName" doesn't change in anything I proposing here. From the perspective of ES code and identifier resolution there is simply a single global lexical contour that includes all built-ins, top-level declarations, and properties of the global object. The "sub-environments" I'm talking about is simply a means to define the interactions between language induced bindings and properties of the global object (if there is one). It isn't at all clear to me why WeakMap, Map, or any other future ES global binding should be polluting the property name space of the DOM window object. Why is referencing window.WeakMap a good idea?
Le 04/05/2012 18:37, Allen Wirfs-Brock a écrit :
On May 4, 2012, at 8:33 AM, David Bruant wrote:
Hi,
Le 04/05/2012 06:42, Allen Wirfs-Brock a écrit :
Common ways to configure the global environment:
Browser compat: ES5 built-in global bindings replicated on the global object any new ES6 built-in global bindings are not replicated on the global object I disagree with this point as it makes web browsers inconsistent. Some code does feature detection by testing presence on the global object (since it's currently equivalent to "typeof builtinGlobalName !== 'undefined'"). You're saying that the equivalent should be broken, so, ES5 and ES6 features won't be feature-detectable the same way. I don't think there is a loss in having ES6 built-in global bindings replicated on the global object. Actually, Firefox does define WeakMap and Map as global object properties.
"typeof builtinGlobalName" doesn't change in anything I proposing here.
I understand, but "'WeakMap' in this" or "'WeakMap' in window" is something people will use. That's a way of doing feature detection as well.
From the perspective of ES code and identifier resolution there is simply a single global lexical contour that includes all built-ins, top-level declarations, and properties of the global object. The "sub-environments" I'm talking about is simply a means to define the interactions between language induced bindings and properties of the global object (if there is one). It isn't at all clear to me why WeakMap, Map, or any other future ES global binding should be polluting the property name space of the DOM window object.
It isn't to me either why 'Array' pollutes the DOM window object, but that's how it's been implemented.
Why is referencing window.WeakMap a good idea? From an absolute standpoint, I don't think it is, we agree on that. But
it would just make new features consistent with old features. For a newcomer to the language 15 years from now, it will be inconsistent to realize that for built-ins, some identifier have a global object property counterpart and some don't. The only reason that makes a "WeakMap" global object property a "good idea" is consistency. That's also how it's currently implemented in Firefox and Chrome.
Acknowledging that built-in identifiers are aliased as global object properties in web browsers, what is the downside of continuing?
On 4 May 2012 06:42, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
Goals: Duplicate current browser global semantics Existing ES5 built-ins are properties of "the global object" Global Function and Var declarations create properties of the global object Ad hoc semantics relating explicit Function/Var global bindings and non-declared global object properties include attribute and inheritance issues Don't make the web compatible global object worse New declaration forms (let/const/class) shouldn't pollute the global object Don't add any new ES built-in bindings to the global object Don't force non-browser or nested ECMAScript global environment hosts to duplicate quirky browser global semantics
Sounds reasonable, although the latter points are certainly debatable.
Basic Approach Define a global environment model that is flexible enough to both maintain compat. with existing browser semantics and allow simpler non-browser global semantics
Three sub-environment of the global environment record: Built-ins: declarative environment record , immutable bindings for standard and impl defined built-in globals Global Object: A Object environment record, that shadows the built-ins. Content is host defined and may replicate global bindings for globals Top Lex: A declarative environment record that shadows the Global Object environment for lexical bindings created by top level declarations
These sub-environments are hidden behind the environment record interface of a single global environment record, hence their existence does not impact the rest of the specification.
I understand the role of the GO and the TL, but what is the function of a separate built-ins environment? That is, where and how can it be observed (in a browser or stand-alone environment)? In the module API?
When a global environment is initialized it can be configured so var and function declaration bindings go into either the Global Object environment or the Top Lex environment
Hm, how does making it "configurable" fit in with the 1JS tale? Even if the configuration is pre-selected by external means, it still means that JS-in-the-browser is effectively a different language than JS-standalone.
In any case, I still think we should strive to make the global scope as coherent with module scopes as possible. That is, the global scope itself should be as "module-like" as possible. From that point of view, it makes perfect sense to keep the global object as a reification of the toplevel lexical scope (including new binding forms), analogous to module instance objects reifying module scopes.
I believe that makes for a more consistent (and more compatible) language than introducing schisms between old vs new built-ins, old vs new declaration forms, and toplevel vs module scopes.
Is there any particular reason why you think this is not desirable? What is e.g. the gain of not reflecting new declarations on the global object?
Le 07/05/2012 13:06, Andreas Rossberg a écrit :
On 4 May 2012 06:42, Allen Wirfs-Brock<allen at wirfs-brock.com> wrote:
When a global environment is initialized it can be configured so var and function declaration bindings go into either the Global Object environment or the Top Lex environment Hm, how does making it "configurable" fit in with the 1JS tale? Even if the configuration is pre-selected by external means, it still means that JS-in-the-browser is effectively a different language than JS-standalone.
I don't think 1JS was about JavaScript in different platforms. I think it was about having one language on the web browser and not having an opt-in (with particular script at type) to choose between 2 different (but close) languages.
On 7 May 2012 13:43, David Bruant <bruant.d at gmail.com> wrote:
Le 07/05/2012 13:06, Andreas Rossberg a écrit :
On 4 May 2012 06:42, Allen Wirfs-Brock<allen at wirfs-brock.com> wrote:
When a global environment is initialized it can be configured so var and function declaration bindings go into either the Global Object environment or the Top Lex environment
Hm, how does making it "configurable" fit in with the 1JS tale? Even if the configuration is pre-selected by external means, it still means that JS-in-the-browser is effectively a different language than JS-standalone.
I don't think 1JS was about JavaScript in different platforms. I think it was about having one language on the web browser and not having an opt-in (with particular script at type) to choose between 2 different (but close) languages.
Yes, I am aware of that. Still, I think it is closely related, and it might be strange to aim in completely different directions there.
Whether or not new ES6 built-ins are visible as global object properties isn't the key point of this proposal. I suggest that they should not, but the core of the proposal certainly allows for that possibility. I just want to make sure that we don't get hung up on debating that point while ignoring the details of the "Basic Approach" I proposed.
On May 6, 2012, at 9:18 AM, David Bruant wrote:
Le 04/05/2012 18:37, Allen Wirfs-Brock a écrit :
"typeof builtinGlobalName" doesn't change in anything I proposing here. I understand, but "'WeakMap' in this" or "'WeakMap' in window" is something people will use. That's a way of doing feature detection as well.
Ultimately, people will feature detect new features in whatever way they are detectable. I agree that it is worthwhile look deeper at feature detection implications of these proposals but I don't think that "all old forms of feature detection apply to new features" is necessarily a requirement.
From the perspective of ES code and identifier resolution there is simply a single global lexical contour that includes all built-ins, top-level declarations, and properties of the global object. The "sub-environments" I'm talking about is simply a means to define the interactions between language induced bindings and properties of the global object (if there is one). It isn't at all clear to me why WeakMap, Map, or any other future ES global binding should be polluting the property name space of the DOM window object. It isn't to me either why 'Array' pollutes the DOM window object, but that's how it's been implemented.
it's a precedent, but bad precedents should necessarily be followed. In browsers, Array must be in the DOM window object to maintain compatibility with existing code. That isn't the case for WeakArray. I think as we add new feature we need to balance precedence with other considerations.
This proposal also addresses the scoping of global var and let. You could make a similar argument about them: Global declarations (var and function in ES<=5.1) have always been implemented as polluting the windows object. This is a precedent that all global declarations add properties to the windows object, hence for consistency let/const/module/class/import/etc. at the global level must do so. However, in other threads we have discussed plenty of reasons why that isn't desirable .
Why is referencing window.WeakMap a good idea? From an absolute standpoint, I don't think it is, we agree on that. But it would just make new features consistent with old features. For a newcomer to the language 15 years from now, it will be inconsistent to realize that for built-ins, some identifier have a global object property counterpart and some don't. The only reason that makes a "WeakMap" global object property a "good idea" is consistency. That's also how it's currently implemented in Firefox and Chrome.
I don't think we should accept experimental implementation of ES6 features in an ES5 browser as binding precedent. If we do, then first to implement gets to make all the design decisions and pretty much marginalized the role of TC39.
But the main issue you are bringing up concerns the importance of internal consistency and precedent in evolving ES. I think those are significant factors, but not the only ones. We have to balance them against other considerations including expressibility, robustness, and future friendliness.
The key issue here is the role in ES of the global object. In that past, this object has served dual rules. It is both part of the host environment's domain object model and it is the global scoping contour of the ES language. While this may have been a nice, simplifying assumption in the early days of JS, it just doesn't hold up anymore. Some issues with equating the host global object and the ES global scope contour:
- ES global declarations modify/pollute part of the host domain object model.
- A host independent ES program has no way of knowing what global names can safely declare.
- A host has no way of knowing what property names it safely define that won't conflict with ES code.
- The host may inadvertently modify ES program global bindings.
- The browser isn't the only host environment. T39 has no way to safely assign global names to built-ins.
- Object property semantics may be insufficiently to represent current or future ES global binding semantics (see temporal dead zone discussions)
- In a strict ES programs, the global object is the only mutable (with-like) scope contour. This prevents static detection of unresolvable variable reference.
- The host must provide an extensible global object so new bindings can be added to it.
In the browser environment, we must preserve enough of these characteristic to ensure compatibility with existing code. However, It isn't clear why a new host environment that didn't have legacy code compatibility constraints should be burdened with these issues. In many cases, for such new hosts, the best semantics is probably one that does not even have a global object. Such environments potentially include nested Module Loader created environments within the browser.
What this proposal is trying to do, is to define the new ES6 global scope semantics in way that accommodates the reality of browser legacy compatibility while not forcing its quirks on new uses of ES. I've tried to do this in a manner that, while not 100% invisible, would make no noticeable difference the vast majority of existing or future ES code. This is what I'm most interested in getting feedback on.
Acknowledging that built-in identifiers are aliased as global object properties in web browsers, what is the downside of continuing?
The proposals permit browsers to do so, if that is what they want. I don't think they should, but that is a separable issue. The core question is about allowing non-browsers to avoid global object pollution or even to not have a global object.
On May 7, 2012, at 4:06 AM, Andreas Rossberg wrote:
On 4 May 2012 06:42, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
Basic Approach Define a global environment model that is flexible enough to both maintain compat. with existing browser semantics and allow simpler non-browser global semantics
Three sub-environment of the global environment record: Built-ins: declarative environment record , immutable bindings for standard and impl defined built-in globals Global Object: A Object environment record, that shadows the built-ins. Content is host defined and may replicate global bindings for globals Top Lex: A declarative environment record that shadows the Global Object environment for lexical bindings created by top level declarations
These sub-environments are hidden behind the environment record interface of a single global environment record, hence their existence does not impact the rest of the specification.
I understand the role of the GO and the TL, but what is the function of a separate built-ins environment? That is, where and how can it be observed (in a browser or stand-alone environment)? In the module API?
It permits a host to not provide a GO or to not pollute its GO with ES built-in bindings while still providing a specification of how built-in bindings are resolved relative to explicit global declaration within the program. It is potentially visible by a name (for example WeakArray) being visible via an unqualified reference within a program but not being visible as a property name (for example "WeakArray") on the object that is the top level this value.
When a global environment is initialized it can be configured so var and function declaration bindings go into either the Global Object environment or the Top Lex environment
Hm, how does making it "configurable" fit in with the 1JS tale? Even if the configuration is pre-selected by external means, it still means that JS-in-the-browser is effectively a different language than JS-standalone.
The declaration static semantic rules are the same whether the var/function bindings are placed in GO or in LT: can't have a both global let-like and var-like declarations for the same name in a Program; a Program can't have a var-like global declaration for a name that had a var-like global declaration in a preceding Program; a Program can't have a var-like global declaration for a name that had a let-like global declaration in a preceding Program. Just like with built-ins, the way this configuration option might be visible would be like inspecting the properties of the GO (if there is one). I don't think this is enough of a difference to say that this host controled configuration choice constitutes a different language.
In any case, I still think we should strive to make the global scope as coherent with module scopes as possible. That is, the global scope itself should be as "module-like" as possible. From that point of view, it makes perfect sense to keep the global object as a reification of the toplevel lexical scope (including new binding forms), analogous to module instance objects reifying module scopes.
I think the semantics of the GO is actually quite different from a reified Module object. As a start, my understanding is that a Module object is a sealed object (non-extensible, all properties non-configurable). The GO certainly isn't and (in a browser) arguably can't be. The GO can even be a "host object" with completely non-standard property semantics, so it is hard to make any generalizations about it. This is why I think it is better to try to marginalize the GO rather than giving it the central role in the semantics of the ES6 global declarations.
I believe that makes for a more consistent (and more compatible) language than introducing schisms between old vs new built-ins, old vs new declaration forms, and toplevel vs module scopes.
Is there any particular reason why you think this is not desirable? What is e.g. the gain of not reflecting new declarations on the global object?
I think I covered this above and in one of my replies to David.
I still don't quite understand the semantics of a single toplevel environment (STL) for multiple programs (as opposed to the nesting approach). As far as I can see, because static checking of separate programs is interleaved with their execution, using a single environment would require making lexical environments extensible, i.e. mutable, which is something that does not go well with lexical scoping.
For example, what would be the result of:
<script>
function f() { return eval("x"); } </script>
<script>
let x = 9; alert(f()); </script>
I suppose your answer is "shows 9" -- that seems like the only natural answer for STL, because at the time the eval is called, the top lex has been extended. But I would argue that that violates lexical scoping: x is not bound in the lexical scope in which f was checked and compiled. If its inner eval sees additional (declarative) bindings, then scoping is something else -- especially, when new bindings can also introduce shadowing. Consider:
<script>
var x = 7; function f() { return [x, eval("x")]; } </script>
<script>
let x = 9; alert(f()); </script>
It would be quite surprising if this returned [7, 9]. In particular, it would violate one important expectation I have about eval, namely that an expression E that evaluates successfully always is equivalent to eval("E").
Also, with STL, how would you even specify that the first x still binds to 7 when executed? (I hope nobody is suggesting that the above should give [9, 9]...)
Am I merely confused? Or how would STL add up?
On May 9, 2012, at 8:30 AM, Andreas Rossberg wrote:
I still don't quite understand the semantics of a single toplevel environment (STL) for multiple programs (as opposed to the nesting approach). As far as I can see, because static checking of separate programs is interleaved with their execution, using a single environment would require making lexical environments extensible, i.e. mutable, which is something that does not go well with lexical scoping.
Yes, the STL level needs to be extensible. This is also need for non-strict eval at the top level which can add declarations to the TL.
For example, what would be the result of:
<script> function f() { return eval("x"); } </script>
I don't particularly see why eval is necessary for this example. Direct eval rules essentially meant that this is exactly the same as saying:
function f() {return x;}
In either case, x can't be statically bound to a declaration (or determined to be unreasonable) because the Global Object is dynamically extensible. Some sort runtime resolution must be performed for any statically unresolvable identifier reference. Plus, any reference that is not resolvable in the same function will require a dynamic TDZ check.
<script> let x = 9; alert(f()); </script>
I suppose your answer is "shows 9" -- that seems like the only natural answer for STL, because at the time the eval is called, the top lex has been extended.
yes
But I would argue that that violates lexical scoping: x is not bound in the lexical scope in which f was checked and compiled. If its inner eval sees additional (declarative) bindings, then scoping is something else -- especially, when new bindings can also introduce shadowing. Consider:
<script> var x = 7; function f() { return [x, eval("x")]; } </script>
<script> let x = 9; alert(f()); </script>
Early error loading the second script! At global or function scope the same identifier can't be declared using both let and var. Static semantic validation of a Program requires knowable of what has been declared by already processed Programs. For example, the following is illegal with a STL:
<script>
let x=7; </script> <script>
let x=9; //duplicate let declaration </script>
It would be quite surprising if this returned [7, 9]. In particular, it would violate one important expectation I have about eval, namely that an expression E that evaluates successfully always is equivalent to eval("E").
Does happen with STL. But something like it would happen with per Program nested top levels.
<script>
var x = 7; function f() { return [x, (0,eval)("x")]; } //indirect eval </script>
<script>
let x = 9; alert(f()); </script>
Produces [7,9]?? Indirect eval presumably uses the current top level as it doesn't have any context to choose any other. That TL is presumably the dynamically current "nested" top level when it is invoked.
Also, with STL, how would you even specify that the first x still binds to 7 when executed? (I hope nobody is suggesting that the above should give [9, 9]...)
the early duplicate declaration errors are the key to consistency with STL.
On May 9, 2012, at 8:30 AM, Andreas Rossberg wrote:
<script> var x = 7; function f() { return [x, eval("x")]; } </script>
<script> let x = 9; alert(f()); </script>
It would be quite surprising if this returned [7, 9]. In particular,
My understanding is that the let is entirely lexically scoped -- the x bound by let x = 9 is not assigned as a global property, so would not be visible in the scope chain of function f.
On May 9, 2012, at 10:21 AM, Oliver Hunt wrote:
On May 9, 2012, at 8:30 AM, Andreas Rossberg wrote:
<script> var x = 7; function f() { return [x, eval("x")]; } </script>
<script> let x = 9; alert(f()); </script>
It would be quite surprising if this returned [7, 9]. In particular,
My understanding is that the let is entirely lexically scoped -- the x bound by let x = 9 is not assigned as a global property, so would not be visible in the scope chain of function f.
That's the open issue we need to resolve at the next meeting. see:
The main alternatives we have discussed are enumerated in esdiscuss/2012-January/020067 with lots of discussion in the surrounding thread.
I (among others) have been advocating for the STL alternative Andreas has been advocating for the TLpSn alternative.
What you are describing seems like it may be TLpSi
On 9 May 2012 19:06, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On May 9, 2012, at 8:30 AM, Andreas Rossberg wrote:
I still don't quite understand the semantics of a single toplevel environment (STL) for multiple programs (as opposed to the nesting approach). As far as I can see, because static checking of separate programs is interleaved with their execution, using a single environment would require making lexical environments extensible, i.e. mutable, which is something that does not go well with lexical scoping.
Yes, the STL level needs to be extensible. This is also need for non-strict eval at the top level which can add declarations to the TL.
True. Which is quite unfortunate. Before 1JS I would have argued: so what, non-strict mode isn't lexically scoped anyway. But now it means that no code can be fully lexically scoped.
And by that I don't just mean that bindings can disappear from the global object at the top of the scope chain. That's not great either, but at least leads to failure.
But extensible scopes harm lexical scoping in more serious ways: they can lead to variable references changing between static and dynamic (see below). That is truly bad.
But I would argue that that violates lexical scoping: x is not bound in the lexical scope in which f was checked and compiled. If its inner eval sees additional (declarative) bindings, then scoping is something else -- especially, when new bindings can also introduce shadowing. Consider:
<script> var x = 7; function f() { return [x, eval("x")]; } </script>
<script> let x = 9; alert(f()); </script>
Early error loading the second script! At global or function scope the same identifier can't be declared using both let and var.
Sorry, yes, that was a stupid example. What I should have written is something like:
<script>
Object.getPrototypeOf(this).x = 7; </script>
<script>
function f() { return [x, eval("x")]; } </script>
<script>
let x = 9; alert(f()); </script>
Here, the reference to x in the second script is statically resolvable, and it should give 7, shouldn't it? How would a STL semantics deal with that?
It would be quite surprising if this returned [7, 9]. In particular, it would violate one important expectation I have about eval, namely that an expression E that evaluates successfully always is equivalent to eval("E").
Does happen with STL. But something like it would happen with per Program nested top levels.
<script> var x = 7; function f() { return [x, (0,eval)("x")]; } //indirect eval </script>
<script> let x = 9; alert(f()); </script>
Produces [7,9]?? Indirect eval presumably uses the current top level as it doesn't have any context to choose any other.
Yes, but indirect eval is a different story altogether. It doesn't use the (call site's) lexical scope. Direct eval (aka the eval operator) does. So clearly, with indirect eval you wouldn't expect any such equivalence.
On May 9, 2012, at 11:25 AM, Andreas Rossberg wrote:
On 9 May 2012 19:06, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On May 9, 2012, at 8:30 AM, Andreas Rossberg wrote:
I still don't quite understand the semantics of a single toplevel environment (STL) for multiple programs (as opposed to the nesting approach). As far as I can see, because static checking of separate programs is interleaved with their execution, using a single environment would require making lexical environments extensible, i.e. mutable, which is something that does not go well with lexical scoping.
Yes, the STL level needs to be extensible. This is also need for non-strict eval at the top level which can add declarations to the TL.
True. Which is quite unfortunate. Before 1JS I would have argued: so what, non-strict mode isn't lexically scoped anyway. But now it means that no code can be fully lexically scoped.
And by that I don't just mean that bindings can disappear from the global object at the top of the scope chain. That's not great either, but at least leads to failure.
But extensible scopes harm lexical scoping in more serious ways: they can lead to variable references changing between static and dynamic (see below). That is truly bad.
But I would argue that that violates lexical scoping: x is not bound in the lexical scope in which f was checked and compiled. If its inner eval sees additional (declarative) bindings, then scoping is something else -- especially, when new bindings can also introduce shadowing. Consider:
<script> var x = 7; function f() { return [x, eval("x")]; } </script>
<script> let x = 9; alert(f()); </script>
Early error loading the second script! At global or function scope the same identifier can't be declared using both let and var.
Sorry, yes, that was a stupid example. What I should have written is something like:
<script> Object.getPrototypeOf(this).x = 7; </script>
<script> function f() { return [x, eval("x")]; } </script>
<script> let x = 9; alert(f()); </script>
Here, the reference to x in the second script is statically resolvable, and it should give 7, shouldn't it? How would a STL semantics deal with that?
Key point: all global var/function declarations use properties of the GO as their backing store. But not all properties of the GO correspond to var/function bindings. We can know which GO properties correspond to actual ES declarations and can apply rules accordingly. This is what ES5.1 (corrected by the fix for bug 78) already does. ES6 can do better by recognizing that we aren't limited to the GO property attributes to manage global declarations.
The reference within the function in the second script, at best resolves to a GO property that does not correspond to a declared ES binding. There is no guarantee that the GO property (it's configurable) will exist when the function is evaluated. What an early static binding phase of processing script 2 should do is try to statically resolve the reference to x. When it gets to the global environment it sees that the currently is only a dynamic property binding so it should decide that x can not be statically bound.
The ES5.1 rule that applies to the above example, is that inherited properties of the GO are not considered to be a pre-existing declarations. So variable instantiation for x in the 2nd script (in ES5.1 it would have to be a var instead of a let, but the same principal applies) will create a new binding/own property on the GO. This shadows (rather than overwrites) the x property that would have been inherited from the GO's prototype.
It would be quite surprising if this returned [7, 9]. In particular, it would violate one important expectation I have about eval, namely that an expression E that evaluates successfully always is equivalent to eval("E").
Agreed, it should return [9, 9]
Does happen with STL. But something like it would happen with per Program nested top levels.
<script> var x = 7; function f() { return [x, (0,eval)("x")]; } //indirect eval </script>
<script> let x = 9; alert(f()); </script>
Produces [7,9]?? Indirect eval presumably uses the current top level as it doesn't have any context to choose any other.
Yes, but indirect eval is a different story altogether. It doesn't use the (call site's) lexical scope. Direct eval (aka the eval operator) does. So clearly, with indirect eval you wouldn't expect any such equivalence.
I would expect a variable reference that resolve to a global environment binding to product the same result whether or not the reference was inline, in a direct eval, or in an indirect eval.
Goals: Duplicate current browser global semantics Existing ES5 built-ins are properties of "the global object" Global Function and Var declarations create properties of "the global object" Ad hoc semantics relating explicit Function/Var global bindings and non-declared global object properties include attribute and inheritance issues Don't make the web compatible global object worse New declaration forms (let/const/class) shouldn't pollute "the global object" Don't add any new ES built-in bindings to "the global object" Don't force non-browser or nested global environments hosts to duplicate quirky browser global semantics
Basic Approach Define a global environment model that is flexible enough to both maintain compat. with existing browser semantics and allow simpler non-browser global semantics
Three sub-environment of the global environment record: Built-ins: declarative environment record , immutable bindings for standard and impl defined built-in globals Global Object: A Object environment record, that shadows the built-ins. Content is host defined and may replicate global bindings for globals Top Lex: A declarative environment record for bindings created by top level declarations
These sub-environments are hidden behind the environment record interface of the single global environment record, hence there existence does not impact the rest of the specification. When a global environment is initialized it can be configured so var and function declaration bindings go into either the global object or Top Lex
The structuring of Top Lex to supple multiple program fragments is an orthogonal issue. However, I favor a single top lex shared by all program/script fragments.
Common ways to configure the global environment:
Thoughts? I'm ready to write the spec for this.