The global object should not be the "global scope instance object"

# Allen Wirfs-Brock (14 years ago)

The following proposal questions the validity of the teeshirt maxim "Let is the new var" as a rationale for treating global |let| declarations similarly to ES<=5 global |var| declarations. While we want people to use |let| (and |const|) in preference to |var| we don't want to unnecessarily perpetuate undesirable aspects of the current browser ES global environment that aren't strictly needed to maintain compatibility with existing code.

One of those undesirable aspects is the use of properties of a host provided object to represent the bindings of ES global declarations. This results in undesirable consequences for both ES and the host environment such as HTML id attributes over-writing declared or built-in ES bindings or program declarations inadvertently causing host environment side-effects by unintentionally writing to host properties. We should not be trying to propagate the undesirable host pollution characteristic of global |var| to global |let| and |const| simply so we can encourage ES6 developers to replace existing uses of |var| with |let|. Instead, this proposal suggests limiting the host pollution semantics to its existing use in |var| (and unfortunately |function|) and keeping a clean semantics for |let| and |const|. Going forward, ES programmer who need to extend the host's global object should be encourage to do so via explicit object operations (such as assignment to |this| properties or the window binding in the browser. They can continue to also do so using |var| but not via |let|, |const|, or global |import|.

Here is a sketch of the proposal:

In ES6, the default (module loaders can change this) "top level" consists of two environment records. The outmost environment is a an object environment record whose binding object is the "global object" (the window object in browsers). We will call this environment record the "host environment". The second environment is a declarative environment record whose outer environment is the host environment. We will call this the "top-level environment".

When evaluating a Program all |var| and |function| declarations at the top level of the program create (if not already present) bindings in the host environment. In other words, top level vars and functions create properties on the global object. So do |this| qualified property assignments at the top level. The declaration instantiation rules for such |var| and |function| declarations are the same as for ES5.1 (with the correction from ecmascript#78 ) with the addition of rules about conflicting let/const/var declarations. There is no temporal dead zone for bindings created in the host environment because all such bindings are immediately initialized when they are instantiated.

For example, these all create or change the value of a property on the global object (unless prevented by property attributes or the extensibility of the global object):

<script>

var foo; print('foo' in this); //should be true </script>

<script>

function bar() {}; </script>

<script>

var bar = "bar"; function bar() {}; </script>

<script>

this.baz="baz"; </script>

We will, for the moment, defer discussing whether or not

<script>

bam="bam"; </script>

creates a property on the global object.

The following would each produce an early error

<script>

var foo; let foo; </script>

<script>

var foo; const foo="foo"; </script>

<script>

var foo() {}; let foo; </script>

<script>

function foo() {}; const foo="foo"; </script>

When evaluating a Program all |let| and |const| declarations at the top level of the program attempt to create bindings in the top-level environment. Bindings imported at the top level from modules are treated similarly to |let| or |const| bindings (as appropriate) except that multiple equivalent imports are allowed . Other than duplicate imports, any attempt to instantiate a binding in the top-level environment will fail if it already has a binding for the same name. This produces what is essentially an early error (it occurs before any user code for the program is evaluated). If the identifier bound by a |let|, |const|, or |import| is also bound in the host environment (ie, it is the name of a property of the global object) the binding in the top-level environment shadows the host binding. All temporal dead-zone rules apply to binding in the top-level environment;

Some examples,

<script>

let foo=1; print('foo' in this); //should be false </script>

<script>

bar = "host"; //assume no "top-level" declarations for bar have yet been processed print('bar' in this); //should be true </script> <script>

const bar = "top-level" print('bar' in this); //should still be true print(bar); //"top-level" print(this.bar); ;/"host" </script>

Now back to

<script>

bam="bam"; </script>

Whether this creates a binding in the global object depends upon what other script blocks have preceded it. If a preceding Program had a let/const/import declaration for "bam" then this reference will resolve to the top-level environment. If it is not preceded by such a Program then it will resolve to the host environment and either create or update a property of the global object.

Additional discussion/issues/concerns:

I believe that this approach is different from other proposal for using declarative environment records for Program scopes in that it does not use a distinct declarative environment record for each Program. Instead it uses a single declarative environment record that is dynamically extended when evaluating each Program. This is similar to what ES5 requires for function declarative environments when processing non-strict direct evals. This requires appropriate semantic rules for dynamic additions of |let|/|const| binding to a declarative environment record. However, this will also be required to support ES6 non-strict direct eval and indirect (or program top level) non-strict evals of Programs that contain |let| or |const| declarations.

There are script block ordering dependency that can affect the meaning of a web app. So far, that seems to be a characteristic of all the proposals we have considered.

It would be really nice to be able to bind |function| declarations in the top-level environment. However, there are undoubtably existing web pages that depend upon such declarations creating properties of the global object.

In the above I haven't addressed which environment is used to bind the ES built-in globals. I'm assuming, that for compatibility reasons, these need to continue to be bound in the host environment (ie, properties of the global object). I would prefer to place them in a separate enviornment record. If that was plausible (which I doubt) I would have to give further thought to whether the environment for built-ins would be between the host environment and the top-level environment (ie shadowing the host environment and shadowed by the top-level) or shadowed by the host environment.

# Andreas Rossberg (14 years ago)

On 26 January 2012 01:11, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

The following proposal questions the validity of the teeshirt maxim "Let is the new var" as a rationale for treating global |let| declarations similarly to ES<=5 global |var| declarations. While we want people to use |let| (and |const|) in preference to |var| we don't want to unnecessarily perpetuate undesirable aspects of the current browser ES global environment  that aren't strictly needed to maintain compatibility with existing code.

I'm happy to see a move towards a more declarative toplevel environment. I think we are on the same page there, and there is no real difference in that respect between what you propose and what I suggested earlier.

If I understand things correctly, then the main observable difference to my proposal is that let/const bindings should not be reflected on the global object at all (and instead, can actually shadow preexisting properties). I was under the impression that people would demand lets to show up, so my main intent was to suggest a safe solution that does not compromise declarative semantics. But if there is consensus on making a more radical break, then I'd be the last to argue against it. :) The ability to shadow pre-existing bindings, and thus avoid scope pollution problems, is a good motivation, and implemented cleaner based on nested environments than on prototype chains.

A couple of questions/comments:

var foo() {};

I haven't seen this syntax being proposed for var before.

When evaluating a Program all |let| and |const| declarations at the top level of the program attempt to create  bindings in the top-level environment. Bindings imported  at the top level from modules are treated similarly to |let| or |const| bindings (as appropriate) except that multiple equivalent imports are allowed .

Overlapping imports? That's the first time I hear about that. That might be rather difficult, given that modules are recursive.

In the above I haven't addressed which environment is used to bind the ES built-in globals.  I'm assuming, that for compatibility reasons, these need to continue to be bound in the host environment (ie, properties of the global object).  I would prefer to place them in a separate enviornment record.

I share the sentiment, but wouldn't that imply that the built-ins no longer show up on the window object? That seems to be a breaking change.

I also wonder how this idea would interact with module loaders, where you can define custom global environments. Would there be a way to customize both environments?

If that was plausible (which I doubt) I would have to give further thought to whether the environment for built-ins would be between the host environment and the top-level environment (ie shadowing the host environment and shadowed by the top-level) or shadowed by the host environment.

The former seems far more desirable, as it makes sure that you cannot accidentally shadow the built-ins by random HTML stuff.

# Allen Wirfs-Brock (14 years ago)

On Jan 26, 2012, at 3:01 AM, Andreas Rossberg wrote:

On 26 January 2012 01:11, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

The following proposal questions the validity of the teeshirt maxim "Let is the new var" as a rationale for treating global |let| declarations similarly to ES<=5 global |var| declarations. While we want people to use |let| (and |const|) in preference to |var| we don't want to unnecessarily perpetuate undesirable aspects of the current browser ES global environment that aren't strictly needed to maintain compatibility with existing code.

I'm happy to see a move towards a more declarative toplevel environment. I think we are on the same page there, and there is no real difference in that respect between what you propose and what I suggested earlier.

If I understand things correctly, then the main observable difference to my proposal is that let/const bindings should not be reflected on the global object at all (and instead, can actually shadow preexisting properties). I was under the impression that people would demand lets to show up, so my main intent was to suggest a safe solution that does not compromise declarative semantics. But if there is consensus on making a more radical break, then I'd be the last to argue against it. :)

There certainly wasn't consensus about this at last week's meeting. But there also isn't any web compatibility requirement for binding let's in the global object. I'm hoping the proponents of doing so will reconsider their position.

The ability to shadow pre-existing bindings, and thus avoid scope pollution problems, is a good motivation, and implemented cleaner based on nested environments than on prototype chains.

A couple of questions/comments:

var foo() {};

I haven't seen this syntax being proposed for var before.

typo, I meant function

When evaluating a Program all |let| and |const| declarations at the top level of the program attempt to create bindings in the top-level environment. Bindings imported at the top level from modules are treated similarly to |let| or |const| bindings (as appropriate) except that multiple equivalent imports are allowed .

Overlapping imports? That's the first time I hear about that. That might be rather difficult, given that modules are recursive.

It's just something I threw in that the module champions need to weigh in on. It seems desirable to allow things like:

<script>

import {create} from "@names"; const foo = create(); </script> <script>

import {create} from "@names"; const bar = create(); </script>

(and, of course, the scripts would most likely be independent files). I don't see why circularities involving the imported module would create any problems in this situation. It probably helps that we are talking about top level scripts that aren't themselves modules.

In the above I haven't addressed which environment is used to bind the ES built-in globals. I'm assuming, that for compatibility reasons, these need to continue to be bound in the host environment (ie, properties of the global object). I would prefer to place them in a separate enviornment record.

I share the sentiment, but wouldn't that imply that the built-ins no longer show up on the window object? That seems to be a breaking change.

Yes, assuming that people do things like: new window.Array();

That's why I said that I doubted its plausibility.

I also wonder how this idea would interact with module loaders, where you can define custom global environments. Would there be a way to customize both environments?

I assume that a module loader could set up an environment exactly like this (assuming we give them the right primitives for constructing environments)

# Andreas Rossberg (14 years ago)

On 26 January 2012 17:47, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jan 26, 2012, at 3:01 AM, Andreas Rossberg wrote:

Overlapping imports? That's the first time I hear about that. That might be rather difficult, given that modules are recursive.

It's just something I threw in that the module champions need to weigh in on.  It seems desirable to allow things like:

<script> import {create} from "@names"; const foo = create(); </script> <script> import {create} from "@names"; const bar = create(); </script>

I agree it's useful, but so are other forms of shadowing.

Module scoping is difficult, especially if you want a semantics that can be decided efficiently. Moreover, shadowing and recursion (and every ES6 scope is recursive) don't go together well. And things get even more interesting with "import *".

(and, of course, the scripts would most likely be independent files). I don't see why circularities involving the imported module would create any problems in this situation. It probably helps that we are talking about top level scripts that aren't themselves modules.

You might get away with duplicate imports in separate scripts, like in your example. But AFAICS, that essentially amounts to reintroducing the multiple-scripts-as-nested-scopes idea through the backdoor. Just consider that in the presence of import shadowing, you could rewrite

let x = e

to

module fresh_name { export let x = e } import {x} from fresh_name

and thereby have the same effect as if shadowing was allowed for let.

I still like the idea of cross-script shadowing (aka nested scopes), but one concern was that it is not clear what names would be in scope for, say, a piece of JS code in a random HTML attribute somewhere on the page. That applies to imports no less than let. I still don't really buy that argument (because it's no clearer if scopes are not nested), but IIRC it was one reason the idea was shot down.

# Brendan Eich (14 years ago)

Andreas Rossberg <mailto:rossberg at google.com> January 26, 2012 11:26 AM On 26 January 2012 17:47, Allen Wirfs-Brock<allen at wirfs-brock.com> wrote:

On Jan 26, 2012, at 3:01 AM, Andreas Rossberg wrote:

Overlapping imports? That's the first time I hear about that. That might be rather difficult, given that modules are recursive. It's just something I threw in that the module champions need to weigh in on. It seems desirable to allow things like:

<script> import {create} from "@names"; const foo = create(); </script> <script> import {create} from "@names";

(Just in case anyone is worrying, I believe (or assert) that the destructuring here is not required. Just |import create from "@name";| would do. Also, we are eschewing plurals in built-in module names, so "@name".)

Module scoping is difficult, especially if you want a semantics that can be decided efficiently. Moreover, shadowing and recursion (and every ES6 scope is recursive) don't go together well. And things get even more interesting with "import *".

These are important points, agreed.

I still like the idea of cross-script shadowing (aka nested scopes), but one concern was that it is not clear what names would be in scope for, say, a piece of JS code in a random HTML attribute somewhere on the page.

We could say event handler attributes are scoped by the most-nested scope -- the scope due to the last <script> element that was closed

before the element with the event handler attribute was processed.

This means generated scripts (document.write or DOM create* calls) do not see the generating script's let and const bindings.

But it's worse than that. What about <script async src="..."> (or the

HTML4-era, not fully supported on all browsers <scriptdefer src="...">?

Async scripts are not supposed to have global effects other than defining functions that the page or app author ensures won't be called till after all content (or at least all scripts, or at least all of the needed async scripts) has finished loading. In what scope do async script let/const top-level bindings go?

The situation is messy enough that I question the win of nesting scopes. True, a global object subject to async-loaded effects is no picnic, but it is the devil we know. And these var and function bindings, however racy, are not supposed to be lexical, as let and const are.

# Allen Wirfs-Brock (14 years ago)

On Jan 26, 2012, at 11:51 AM, Brendan Eich wrote:

Andreas Rossberg <mailto:rossberg at google.com> January 26, 2012 11:26 AM ...

I still like the idea of cross-script shadowing (aka nested scopes), but one concern was that it is not clear what names would be in scope for, say, a piece of JS code in a random HTML attribute somewhere on the page.

We could say event handler attributes are scoped by the most-nested scope -- the scope due to the last <script> element that was closed before the element with the event handler attribute was processed.

This means generated scripts (document.write or DOM create* calls) do not see the generating script's let and const bindings.

Why closed? All declarations within a Program are instantiated before evaluating any code in its <script> element so generated scripts should be able to reference the bindings for those declarations. However, if such reference are actually evaluated they might fall into the bindings' temporal dead zones.

But it's worse than that. What about <script async src="..."> (or the HTML4-era, not fully supported on all browsers <scriptdefer src="...">? Async scripts are not supposed to have global effects other than defining functions that the page or app author ensures won't be called till after all content (or at least all scripts, or at least all of the needed async scripts) has finished loading.

Is this a specified restriction? If it is, I haven't been able to find it anywhere.

In what scope do async script let/const top-level bindings go?

If it is a specified restriction then I would think that when a browser ES implementation actually processes such scripts they should reject any top level declarations other than functions. Regardless, it seems likely that indeterminate load ordering problems created by HTML need to be solved at that level. Why don't script elements have a dependency attribute:

<script src="someLib.js" type="application/javascript" async="async" id="lib1"></script> <script src="anotherLib.js" type="application/javascript" async="async" id="lib2"></script> <script src="baseApp.js" type="application/javascript" async="async" id="base" after="lib1"></script> <script src="extraApp" type="application/javascript" async="async" after="lib2,base"></script>

The situation is messy enough that I question the win of nesting scopes. True, a global object subject to async-loaded effects is no picnic, but it is the devil we know. And these var and function bindings, however racy, are not supposed to be lexical, as let and const are.

I think these are the alternatives we currently have under consideration:

SQ (status quo): every top level declaration are all implemented as properties of the global object STL (Single Top Level): All script blocks share a declarative environment for new (not var and function) kinds of declarations that shadows the global object. Scripts with duplicate declarations are rejected. TLpSi (Top Level per script, isolated): Each script block has a declarative environment for new (not var and function) kinds of declarations. Each shadows the global object but are invisible to each other. TLpSn (Top Level per script, nested): Each script block has a declarative environment for new (not var and function) kinds of declarations. Each is logically nest within and shadows the previously processed script

STL is what I'm proposing. I think that TLpSn corresponds to Andreas preference. One way to look at TLpSn is that uses shadowing to accept the duplicate declarations that STL rejects. This seems like a problem that is better addressed by using modules. TLpBn also raises issues like, what scope does an indirect eval use? I haven't head anyone recently advocating for TLpSi. You can achieve almost the same thing using SQ by wrapping a block around each script body.

In general, when I start think about the ways that lexical declarations in separate scripts might interact I run into issue that are probably best resolved using modules. I'm inclined to favor the simpler solution and leave it to modules to deal with managing actual interdependencies cases.

# Allen Wirfs-Brock (14 years ago)

On Jan 26, 2012, at 11:26 AM, Andreas Rossberg wrote:

On 26 January 2012 17:47, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jan 26, 2012, at 3:01 AM, Andreas Rossberg wrote:

Overlapping imports? That's the first time I hear about that. That might be rather difficult, given that modules are recursive.

It's just something I threw in that the module champions need to weigh in on. It seems desirable to allow things like:

<script> import {create} from "@names"; const foo = create(); </script> <script> import {create} from "@names"; const bar = create(); </script>

I agree it's useful, but so are other forms of shadowing.

Module scoping is difficult, especially if you want a semantics that can be decided efficiently. Moreover, shadowing and recursion (and every ES6 scope is recursive) don't go together well. And things get even more interesting with "import *".

Can you give an example of what you mean by "recursive" in this context? Do you mean that a scope can contain references to bindings defined by the scope?

(and, of course, the scripts would most likely be independent files). I don't see why circularities involving the imported module would create any problems in this situation. It probably helps that we are talking about top level scripts that aren't themselves modules.

You might get away with duplicate imports in separate scripts, like in your example. But AFAICS, that essentially amounts to reintroducing the multiple-scripts-as-nested-scopes idea through the backdoor. Just consider that in the presence of import shadowing, you could rewrite

let x = e

to

module fresh_name { export let x = e } import {x} from fresh_name

and thereby have the same effect as if shadowing was allowed for let.

Huh?

<script>

module fn {export let x = e} import {x} from fn </script> <script>

import {x} from fn </script>

seems quite different from

<script>

let x=e; </script> <script>

let x=e; </script>

with shadowing. The module case only has a single x that is initialized only one. The shadowing case has two x and each is initialize separately. This is easily observable between scripts blocks by calling/passing around closures that capture one of the x or the other.

# Brendan Eich (14 years ago)

Allen Wirfs-Brock wrote:

On Jan 26, 2012, at 11:51 AM, Brendan Eich wrote:

We could say event handler attributes are scoped by the most-nested scope -- the scope due to the last <script> element that was closed before the element with the event handler attribute was processed. This means generated scripts (document.write or DOM create* calls) do not see the generating script's let and const bindings.

Why closed? All declarations within a /Program/ are instantiated before evaluating any code in its <script> element so generated scripts should be able to reference the bindings for those declarations. However, if such reference are actually evaluated they might fall into the bindings' temporal dead zones.

You're right, the entire outer <script> has been parsed and declarations

processed before anything runs. And the script element is appended to the DOM too. It all works, except of course you can't tell what's potentially shadowing anything!

But it's worse than that. What about <script async src="..."> (or the HTML4-era, not fully supported on all browsers <scriptdefer src="...">? Async scripts are not supposed to have global effects other than defining functions that the page or app author ensures won't be called till after all content (or at least all scripts, or at least all of the needed async scripts) has finished loading.

Is this a specified restriction? If it is, I haven't been able to find it anywhere.

No, this is underspecified and so inherently racy -- developers beware. It's advisory but people use onload, DOMContentReady, or better (www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#current-document-readiness).

In what scope do async script let/const top-level bindings go?

If it is a specified restriction then I would think that when a browser ES implementation actually processes such scripts they should reject any top level declarations other than functions.

No, vars must be accepted too.

Regardless, it seems likely that indeterminate load ordering problems created by HTML need to be solved at that level. Why don't script elements have a dependency attribute:

Ok, are we really going to invent new <script> attributes or just solve

the let/const problem as it exists today? Adding attributes doesn't help in the extant case.

<script src="someLib.js" type="application/javascript" async="async" id="lib1"></script> <script src="anotherLib.js" type="application/javascript" async="async"id="lib2"></script> <script src="baseApp.js" type="application/javascript" async="async" id="base" after="lib1"></script> <script src="extraApp" type="application/javascript" async="async" after="lib2,base"></script>

Because that's brittle and slow -- you don't care about order, you typically just want to race and exploit as much parallelism as possible, given host CPU(s), the intervening network, TCP connection sharing/limits/etc.

The situation is messy enough that I question the win of nesting scopes. True, a global object subject to async-loaded effects is no picnic, but it is the devil we know. And these var and function bindings, however racy, are not supposed to be lexical, as let and const are.

I think these are the alternatives we currently have under consideration:

SQ (status quo): every top level declaration are all implemented as properties of the global object STL (Single Top Level): All script blocks share a declarative environment for new (not var and function) kinds of declarations that shadows the global object. Scripts with duplicate declarations are rejected. TLpSi (Top Level per script, isolated): Each script block has a declarative environment for new (not var and function) kinds of declarations. Each shadows the global object but are invisible to each other. TLpSn (Top Level per script, nested): Each script block has a declarative environment for new (not var and function) kinds of declarations. Each is logically nest within and shadows the previously processed script

STL is what I'm proposing.

I'm pretty sure the collisions-are-rejected thing is going to burn. It's the opposite of what people not only abuse (unknown latent bugs), but absolutely use and rely on today, with var and function among scripts.

I think that TLpSn corresponds to Andreas preference. One way to look at TLpSn is that uses shadowing to accept the duplicate declarations that STL rejects. This seems like a problem that is better addressed by using modules. TLpBn

(s/B/S/ right?)

also raises issues like, what scope does an indirect eval use? I haven't head anyone recently advocating for TLpSi. You can achieve almost the same thing using SQ by wrapping a block around each script body.

Right, although TLpSi is still attractive as a parallel to function bodies, where we agreed body-level let has to bind in a body block that shadows parameters and vars. Not much of a plus but it's a plausible alternative.

In general, when I start think about the ways that lexical declarations in separate scripts might interact I run into issue that are probably best resolved using modules. I'm inclined to favor the simpler solution and leave it to modules to deal with managing actual interdependencies cases.

Is STL the simplest solution for users?

# Andreas Rossberg (14 years ago)

On 26 January 2012 20:51, Brendan Eich <brendan at mozilla.org> wrote:

Andreas Rossberg <mailto:rossberg at google.com>

I still like the idea of cross-script shadowing (aka nested scopes), but one concern was that it is not clear what names would be in scope for, say, a piece of JS code in a random HTML attribute somewhere on the page.

We could say event handler attributes are scoped by the most-nested scope -- the scope due to the last <script> element that was closed before the element with the event handler attribute was processed.

Why not the last (most-nested on page) scope?

But it's worse than that. What about <script async src="..."> (or the HTML4-era, not fully supported on all browsers <scriptdefer src="...">? Async scripts are not supposed to have global effects other than defining functions that the page or app author ensures won't be called till after all content (or at least all scripts, or at least all of the needed async scripts) has finished loading. In what scope do async script let/const top-level bindings go?

I suppose they'd nest in the order they get loaded. There is no escaping the fact that you'd still have a stateful notion of "current toplevel scope" on a page that affects the meaning of later scripts, attributes and such. But at worst later scripts can shadow bindings from earlier ones, which does not accidentally affect those earlier ones. Compare with the situation today, where later scripts may modify the meaning of previous ones by accidentally overwriting their bindings.

If you don't want to forbid multiple bindings across scripts altogether (Allen's proposal), then what other alternative is available?

# Andreas Rossberg (14 years ago)

On 26 January 2012 23:20, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

In general, when I start think about the ways that lexical declarations in separate scripts might interact I run into issue that are probably best resolved using modules.

Probably. I suppose the underlying question is: given modules, what are the remaining use cases for multiple scripts?

On the other hand, I'm pretty sure that people will still use multiple scripts, even when there are no good reasons. It's the web, after all.

# Andreas Rossberg (14 years ago)

On 26 January 2012 23:31, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jan 26, 2012, at 11:26 AM, Andreas Rossberg wrote:

Module scoping is difficult, especially if you want a semantics that can be decided efficiently. Moreover, shadowing and recursion (and every ES6 scope is recursive) don't go together well. And things get even more interesting with "import *".

Can you give an example of what you mean by "recursive" in this context? Do you mean that a scope can contain references to bindings defined by the scope?

Yes, every binding sees every other binding in the same scope (including itself). It is not clear to me how that can be combined with intra-scope shadowing. Which instance of a name would be visible where in the same scope?

There are many programming languages that allow shadowing in the same scope, and quite a few that have recursive scopes. But I don't know any that would have both. I believe it's asking for trouble.

You might get away with duplicate imports in separate scripts, like in your example. But AFAICS, that essentially amounts to reintroducing the multiple-scripts-as-nested-scopes idea through the backdoor. Just consider that in the presence of import shadowing, you could rewrite

let x = e

to

module fresh_name { export let x = e }  import {x} from fresh_name

and thereby have the same effect as if shadowing was allowed for let.

Huh?

<script> module fn {export let x = e} import {x} from fn </script> <script> import {x} from fn </script>

seems quite different from

<script> let x=e; </script> <script> let x=e; </script>

Not if you rewrite each let separately. That gives:

<script>

module fn1 {export let x = e} import {x} from fn1 </script> <script>

module fn2 {export let x = e} import {x} from fn2 </script>

And you have two different instances of x, one shadowing the other.

# Andreas Rossberg (14 years ago)

On 27 January 2012 01:57, Brendan Eich <brendan at mozilla.org> wrote:

You're right, the entire outer <script> has been parsed and declarations processed before anything runs. And the script element is appended to the DOM too. It all works, except of course you can't tell what's potentially shadowing anything!

True, but is that any worse than the situation today, where you have overwriting instead of shadowing?

# Axel Rauschmayer (14 years ago)

I’m wondering: Won’t modules make many of these things much easier, given that global state will usually be encapsulated per module? As a programmer, I’d try to get out of global scope and into module scope as quickly as possible (similar to people using IIFEs now). Wouldn’t that suggest migrating some ES.next and browser mechanisms to modules?

# Allen Wirfs-Brock (14 years ago)

On Jan 26, 2012, at 4:57 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

On Jan 26, 2012, at 11:51 AM, Brendan Eich wrote:

We could say event handler attributes are scoped by the most-nested scope -- the scope due to the last <script> element that was closed before the element with the event handler attribute was processed. This means generated scripts (document.write or DOM create* calls) do not see the generating script's let and const bindings.

Why closed? All declarations within a /Program/ are instantiated before evaluating any code in its <script> element so generated scripts should be able to reference the bindings for those declarations. However, if such reference are actually evaluated they might fall into the bindings' temporal dead zones.

You're right, the entire outer <script> has been parsed and declarations processed before anything runs. And the script element is appended to the DOM too. It all works, except of course you can't tell what's potentially shadowing anything!

Yes, this makes references to let and const bindings inherently unpredictable. But also var and function bindings as they can be shadowed by let/const. STL would at least give you an error that hopefully will show up in an error console.

...

My point was that we shouldn't try too hard to fix problems that aren't under our control. We need to be aware of them and should try to make things any worse. But trying to fix async load issues in our spec. seems like address the problem at the wrong level.

<script src="someLib.js" type="application/javascript" async="async" id="lib1"></script> <script src="anotherLib.js" type="application/javascript" async="async"id="lib2"></script> <script src="baseApp.js" type="application/javascript" async="async" id="base" after="lib1"></script> <script src="extraApp" type="application/javascript" async="async" after="lib2,base"></script>

Because that's brittle and slow -- you don't care about order, you typically just want to race and exploit as much parallelism as possible, given host CPU(s), the intervening network, TCP connection sharing/limits/etc.

But the ordering dependencies are real. If you don't explicitly identify them and they also aren't implicitly recognized then your application will be unreliable.

The situation is messy enough that I question the win of nesting scopes. True, a global object subject to async-loaded effects is no picnic, but it is the devil we know. And these var and function bindings, however racy, are not supposed to be lexical, as let and const are.

I think these are the alternatives we currently have under consideration:

SQ (status quo): every top level declaration are all implemented as properties of the global object STL (Single Top Level): All script blocks share a declarative environment for new (not var and function) kinds of declarations that shadows the global object. Scripts with duplicate declarations are rejected. TLpSi (Top Level per script, isolated): Each script block has a declarative environment for new (not var and function) kinds of declarations. Each shadows the global object but are invisible to each other. TLpSn (Top Level per script, nested): Each script block has a declarative environment for new (not var and function) kinds of declarations. Each is logically nest within and shadows the previously processed script

STL is what I'm proposing.

I'm pretty sure the collisions-are-rejected thing is going to burn. It's the opposite of what people not only abuse (unknown latent bugs), but absolutely use and rely on today, with var and function among scripts.

With STL we would be saying that if you need redefinition/multiple definition behavior you need to continue to use var and function or you need to refractor to use modules/export/input. But you can'tuse naked let/const. Some people would get burned by this and conclude that var is always preferable to let. But others would learn about modules. In either case, we would have a consistent semantics for let/const. BTW, we should make sure that modules can actually support the use cases that need such multi/redefinitions. I think it is mostly various ways of doing polyfill like things.

I think that TLpSn corresponds to Andreas preference. One way to look at TLpSn is that uses shadowing to accept the duplicate declarations that STL rejects. This seems like a problem that is better addressed by using modules. TLpBn

(s/B/S/ right?)

yes

also raises issues like, what scope does an indirect eval use? I haven't head anyone recently advocating for TLpSi. You can achieve almost the same thing using SQ by wrapping a block around each script body.

Right, although TLpSi is still attractive as a parallel to function bodies, where we agreed body-level let has to bind in a body block that shadows parameters and vars. Not much of a plus but it's a plausible alternative.

did you just say that this is legal:

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

and interpreted as:

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

That wasn't by understanding of the take aways from the Nov. meeting.

I kind of like TLPSi, but I think it would be counterintuitive for web developers who write in-line scripts such as:

<script>

const debug = true; </script> <-- a bunch of html --> <script>

if (debug) {...} </script>

If we only had src based scripts it might be the right thing...

In general, when I start think about the ways that lexical declarations in separate scripts might interact I run into issue that are probably best resolved using modules. I'm inclined to favor the simpler solution and leave it to modules to deal with managing actual interdependencies cases.

Is STL the simplest solution for users?

It avoids issues like:

<script>

const debug = true; function f1 () { if (debug) log(...); ... } </script> <script>

const debug = false; function f2 () { if (debug) log(...); f1(); // but this logs regardless ... } </script> <script>

let debug=false; //this has no effect f1(); f2(); </script>

the behavior of this under TLpSn is perfectly normal given lexical scoping. However, there is no physical nesting so it is likely to be surprising to many users.

# Allen Wirfs-Brock (14 years ago)

On Jan 27, 2012, at 12:06 AM, Andreas Rossberg wrote:

On 26 January 2012 23:20, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

In general, when I start think about the ways that lexical declarations in separate scripts might interact I run into issue that are probably best resolved using modules.

Probably. I suppose the underlying question is: given modules, what are the remaining use cases for multiple scripts?

On the other hand, I'm pretty sure that people will still use multiple scripts, even when there are no good reasons. It's the web, after all.

Yes, but do we really have to solve the same problems twice? Once for modules and once for module-less multi-scripts.

Why not make the solution to these sorts of issues be the carrot that leads people to using modules.

# Brendan Eich (14 years ago)

Andreas Rossberg <mailto:rossberg at google.com> January 27, 2012 12:06 AM On 26 January 2012 20:51, Brendan Eich<brendan at mozilla.org> wrote:

Andreas Rossberg<mailto:rossberg at google.com>

I still like the idea of cross-script shadowing (aka nested scopes), but one concern was that it is not clear what names would be in scope for, say, a piece of JS code in a random HTML attribute somewhere on the page. We could say event handler attributes are scoped by the most-nested scope -- the scope due to the last<script> element that was closed before the element with the event handler attribute was processed.

Why not the last (most-nested on page) scope?

Because event handlers on elements are interspersed with scripts and so far, order matters. An event handler might run when later scripts have not yet loaded. Progressive rendering and even event handling are part of the relied-upon processing model.

But it's worse than that. What about<script async src="..."> (or the HTML4-era, not fully supported on all browsers<scriptdefer src="...">? Async scripts are not supposed to have global effects other than defining functions that the page or app author ensures won't be called till after all content (or at least all scripts, or at least all of the needed async scripts) has finished loading. In what scope do async script let/const top-level bindings go?

I suppose they'd nest in the order they get loaded. There is no escaping the fact that you'd still have a stateful notion of "current toplevel scope" on a page that affects the meaning of later scripts, attributes and such. But at worst later scripts can shadow bindings from earlier ones, which does not accidentally affect those earlier ones. Compare with the situation today, where later scripts may modify the meaning of previous ones by accidentally overwriting their bindings.

Sure -- that is used in some cases, perhaps for want of better mechanism, but not for want of shadowing.

If you don't want to forbid multiple bindings across script altogether (Allen's proposal), then what other alternative is available?

<script>-level let and const are local to that particular script element.

# Brendan Eich (14 years ago)

Andreas Rossberg wrote:

On 27 January 2012 01:57, Brendan Eich<brendan at mozilla.org> wrote:

You're right, the entire outer<script> has been parsed and declarations processed before anything runs. And the script element is appended to the DOM too. It all works, except of course you can't tell what's potentially shadowing anything!

True, but is that any worse than the situationtoday, where you have overwriting instead of shadowing?

I'm not sure "worse" matters so much as "different". Today you can save/restore, wrap (stack), etc. and JS libraries do this. Adding nesting for let and const at top level (but keeping overwriting for var and function) adds to total complexity. But we have to do something with let and const at top level. The alternative of making let and const script-local at least avoids the total complexity hit from two models across scripts: shadowing and overwriting.

OTOH, people may think "oh, finally! const!" and use const declarations in an early/common script, wanting to see them later on. I still think we could say "sorry, script-local" and then "use a module".

# Brendan Eich (14 years ago)

Axel Rauschmayer wrote:

I’m wondering: Won’t modules make many of these things much easier, given that global state will usually be encapsulated per module? As a programmer, I’d try to get out of global scope and into module scope as quickly as possible (similar to people using IIFEs now). Wouldn’t that suggest migrating some ES.next and browser mechanisms to modules?

No. Downrev browsers will mean non-strict top level code will be around for a good while. Not everyone will get an ES6 to ES5 compiler. We do not want only "use a module" if we can also say "let or const at top-level opts into lexical bindings."

The question then is: what should let and const at top-level do? That leads to global-object (not really tenable with let and const, IMHO), vs. single top-level vs. nesting vs. isolated. I still incline toward isolated but see the appeal of nesting.

# Brendan Eich (14 years ago)

Allen Wirfs-Brock <mailto:allen at wirfs-brock.com> January 27, 2012 11:41 AM On Jan 26, 2012, at 4:57 PM, Brendan Eich wrote:

<script src="someLib.js" type="application/javascript" async="async" id="lib1"></script> <script src="anotherLib.js" type="application/javascript" async="async"id="lib2"></script> <script src="baseApp.js" type="application/javascript" async="async" id="base" after="lib1"></script> <script src="extraApp" type="application/javascript" async="async" after="lib2,base"></script> Because that's brittle and slow -- you don't care about order, you typically just want to race and exploit as much parallelism as possible, given host CPU(s), the intervening network, TCP connection sharing/limits/etc.

But the ordering dependencies are real.

This is not the right argument. Developers generally do not need to partially or totally order dependencies except in the degenerate partial sense of "all must load before I can rely on my functions". There are exceptional cases of progressive loading and dependency, but those are handled via non-async <script> vs. element-with-event-handler order.

Developers know how to turn on their app when DOMContentReady or whatever fires. That means most async script load scheduling can and should be left to the browser implementation.

If you don't explicitly identify them and they also aren't implicitly recognized then your application will be unreliable.

People build reliable web apps all the time using onload et seq.

I'm pretty sure the collisions-are-rejected thing is going to burn. It's the opposite of what people not only abuse (unknown latent bugs), but absolutely use and rely on today, with var and function among scripts.

With STL we would be saying that if you need redefinition/multiple definition behavior you need to continue to use var and function or you need to refractor to use modules/export/input. But you can't use naked let/const.

I understand the proposal, but my response is that instead of a migration path from var to let at top-level (non-strict), you're making a higher barrier: var to let-in-module and proper modularization.

Not the end of the world, mind you -- plausible and perhaps winning on balance.

Some people would get burned by this and conclude that var is always preferable to let. But others would learn about modules. In either case, we would have a consistent semantics for let/const. BTW, we should make sure that modules can actually support the use cases that need such multi/redefinitions. I think it is mostly various ways of doing polyfill like things.

Yup. Those will use var and global property testing for a while to come.

also raises issues like, what scope does an indirect eval use? I haven't head anyone recently advocating for TLpSi. You can achieve almost the same thing using SQ by wrapping a block around each script body. Right, although TLpSi is still attractive as a parallel to function bodies, where we agreed body-level let has to bind in a body block that shadows parameters and vars. Not much of a plus but it's a plausible alternative.

did you just say that this is legal:

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

and interpreted as:

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

I didn't say that. Your

esdiscuss/2012-January/019817

has two alternative rules, either way var vs. let at top level for a given name is an error.

I kind of like TLPSi, but I think it would be counterintuitive for web developers who write in-line scripts such as:

<script> const debug = true; </script> <-- a bunch of html --> <script> if (debug) {...} </script>

If we only had src based scripts it might be the right thing...

Yes, making either or both of those out-of-line may change developer expectations. TLpSi seems better with any out-of-line (out of sight is out of mind!) script.

In general, when I start think about the ways that lexical declarations in separate scripts might interact I run into issue that are probably best resolved using modules. I'm inclined to favor the simpler solution and leave it to modules to deal with managing actual interdependencies cases. Is STL the simplest solution for users?

It avoids issues like:

<script> const debug = true; function f1 () { if (debug) log(...); ... } </script> <script> const debug = false; function f2 () { if (debug) log(...); f1(); // but this logs regardless ... } </script> <script> let debug=false; //this has no effect f1(); f2(); </script>

the behavior of this under TLpSn is perfectly normal given lexical scoping. However, there is no physical nesting so it is likely to be surprising to many users.

But a nasty hard error with STL is better? Again out-of-line scripts and hacking modally file by file may just leave frustrated users. Module bodies are file contents and (without export) have isolated let and const at top level.

# Brendan Eich (14 years ago)

Brendan Eich wrote:

also raises issues like, what scope does an indirect eval use? I haven't head anyone recently advocating for TLpSi. You can achieve almost the same thing using SQ by wrapping a block around each script body. Right, although TLpSi is still attractive as a parallel to function bodies, where we agreed body-level let has to bind in a body block that shadows parameters and vars. Not much of a plus but it's a plausible alternative.

did you just say that this is legal:

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

and interpreted as:

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

I didn't say that. Your

esdiscuss/2012-January/019817

has two alternative rules, either way var vs. let at top level for a given name is an error.

Of course, I did say "shadows ... vars" but I was thinking of parameters. Andreas in followups in the thread cited above argued that such let vs. arg shadowing might be ok, it was a minor point.

To your point, I think we did agree at the meeting with making let vs. arg as well as let vs. var an error.

# Allen Wirfs-Brock (14 years ago)

On Jan 27, 2012, at 3:12 PM, Brendan Eich wrote:

To your point, I think we did agree at the meeting with making let vs. arg as well as let vs. var an error.

Yes, that is also y understand.

# Allen Wirfs-Brock (14 years ago)

On Jan 27, 2012, at 12:07 AM, Andreas Rossberg wrote:

On 26 January 2012 23:31, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jan 26, 2012, at 11:26 AM, Andreas Rossberg wrote:

Module scoping is difficult, especially if you want a semantics that can be decided efficiently. Moreover, shadowing and recursion (and every ES6 scope is recursive) don't go together well. And things get even more interesting with "import *".

Can you give an example of what you mean by "recursive" in this context? Do you mean that a scope can contain references to bindings defined by the scope?

Yes, every binding sees every other binding in the same scope (including itself). It is not clear to me how that can be combined with intra-scope shadowing. Which instance of a name would be visible where in the same scope?

There are many programming languages that allow shadowing in the same scope, and quite a few that have recursive scopes. But I don't know any that would have both. I believe it's asking for trouble.

I played around a bit to see if I could come up with a troublesome example of the sort you may be thinking about. What I came up with is the follow:

<script>

module a { import {x:y} from b; module b{ export let y = x; //essentially this is let y=y } } </script>

1)The script is parsed, and static semantic checks are made. There are no static errors. 2) module instantiation is performed for the block. This instantiates each module defined by the top level of the block, instantiating a module includes producing the list of identifiers exported by the module. Each identifier is associated with a new uninitialized binding. Instantiated modules are not initialized (their body is not executed) at this time. 3) An initialized binding for "a" is is created in the top level environment for the script. (all top level binding are instantiate at this point, if there were any others). Note that the binding for a is initialized (it reference a module instance object) but the module itself is not yet initialized 4) initialize module a 5) module instantiation is performed for the body of module a. This instantiates a module instance for module b with exported identifier "y" and its binding. 6) An initialized binding for "b" is is created in module a's inner environment; (but module b is not yet initialized) 7) An binding for "x" is created in module a's inner environment. The binding is linked to the binding of "y" exported from b. Both bindings share the same initialization state. (currently uninitialized)
8) initialize module b 9) The binding for for "y" that was created when module b was instantiated is added to module b's inner environment 10) evaluate the LHS of the exported let; the binding found for "x" is uninitialized so we throw and the script terminates.

If evaluating LHS didn't have any dependencies upon uninitialized bindings (say it was a constant or a function expression) we would continue as follows:

        11) set the "y" binding to the value of the LHS and mark "y" as initialized, this also mark the "x" binding in module a as initialized
 12) module b is not fully initialized
  1. module a is not fully initialized

I've only mentally walked through the steps but it looks to me like this process will also work for circular dependencies such as harmony:modules_examples#cyclic_dependencies

You might get away with duplicate imports in separate scripts, like in your example. But AFAICS, that essentially amounts to reintroducing the multiple-scripts-as-nested-scopes idea through the backdoor. Just consider that in the presence of import shadowing, you could rewrite

let x = e

to

module fresh_name { export let x = e } import {x} from fresh_name

and thereby have the same effect as if shadowing was allowed for let.

Huh?

<script> module fn {export let x = e} import {x} from fn </script> <script> import {x} from fn </script>

seems quite different from

<script> let x=e; </script> <script> let x=e; </script>

Not if you rewrite each let separately. That gives:

<script> module fn1 {export let x = e} import {x} from fn1 </script> <script> module fn2 {export let x = e} import {x} from fn2 </script>

And you have two different instances of x, one shadowing the other.

or the 2nd import is illegal because it is a duplicate definition in the common top-level scope, or the two scripts don't share a common top-level lexical scope and hence the two imported x bindings are distinct but neither shadow the other

# Andreas Rossberg (14 years ago)

On 27 January 2012 23:49, Brendan Eich <brendan at mozilla.org> wrote:

OTOH, people may think "oh, finally! const!" and use const declarations in an early/common script, wanting to see them later on. I still think we could say "sorry, script-local" and then "use a module".

Yes, I cannot imagine that this would not break a few existing web pages.

What are you proposing for module bindings then? They would have to be visible later on for the "use modules" excuse to work. Do they clash/shadow/overwrite?

And wouldn't it be a bit unfortunate/counter-intuitive to have different rules for modules and other lexical bindings?

Btw, the modules excuse can likewise be applied in defence of STL: "sorry, name clash, wrap in a module".

# Andreas Rossberg (14 years ago)

On 28 January 2012 02:08, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jan 27, 2012, at 12:07 AM, Andreas Rossberg wrote:

Yes, every binding sees every other binding in the same scope (including itself). It is not clear to me how that can be combined with intra-scope shadowing. Which instance of a name would be visible where in the same scope?

There are many programming languages that allow shadowing in the same scope, and quite a few that have recursive scopes. But I don't know any that would have both. I believe it's asking for trouble.

I played around a bit to see if I could come up with a troublesome example of the sort you may be thinking about.  What I came up with is the follow:

<script> module a {    import {x:y} from b;    module b{       export let y = x;  //essentially this is let y=y     } } </script>

Well, there is no shadowing in this example, so this is just an easy case of a cyclic definition that runs into a temporal dead zone violation.

I was referring to something else in the above quote. For ES, the conflict between recursive scoping and shadowing is perhaps best demonstrated by this simple example:

let x = 1 let x = 2  // assume this is allowed function f() { return x } f()

The function will get hoisted over both bindings for x. In the end, which one is it referring to?

And as I said earlier, by allowing import shadowing, you could encode this, even if shadowing was disallowed for let itself.

<script> module fn1 {export let x = e} import {x} from fn1 </script> <script> module fn2 {export let x = e} import {x} from fn2 </script>

And you have two different instances of x, one shadowing the other.

or the 2nd import is illegal because it is a duplicate definition in the common top-level scope, or the two scripts don't share a common top-level lexical scope and hence the two imported x bindings are distinct but neither shadow the other

Well, yes, but that wasn't the point, was it? In your OP proposing STL, you made an exception for allowing imports to shadow each other. With the example, I was demonstrating that that is almost equivalent to allowing shadowing in general.

# Allen Wirfs-Brock (14 years ago)

On Jan 30, 2012, at 4:15 AM, Andreas Rossberg wrote:

On 28 January 2012 02:08, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jan 27, 2012, at 12:07 AM, Andreas Rossberg wrote: ... I was referring to something else in the above quote. For ES, the conflict between recursive scoping and shadowing is perhaps best demonstrated by this simple example:

let x = 1 let x = 2 // assume this is allowed function f() { return x } f()

Assuming this is a single Program or StatementList, the above is current defined as being a static error. The above would require changing decisions that have already been made so I don't see why we should be considering such cases unless we run into an issue that forces us to reconsider that decision.

The function will get hoisted over both bindings for x. In the end, which one is it referring to?

And as I said earlier, by allowing import shadowing, you could encode this, even if shadowing was disallowed for let itself.

I don't think so. Imports are treated as local lexical bindings, just like let and const so:

import {x} from wherever; let x = 2;

is just as illegal as:

let x=1; let x=2;

I think part of our terminology mismatch is that you are willing to consider the possibility of multiple declarations for the same name in a StatementList, for example:

let x=1; let x=2;

and you call this shadowing. I think we have already rejected this semantics and I only use "shadowing" in the context of nested scopes such as:

let x=1; { let x=2}

<script> module fn1 {export let x = e} import {x} from fn1 </script> <script> module fn2 {export let x = e} import {x} from fn2 </script>

And you have two different instances of x, one shadowing the other.

or the 2nd import is illegal because it is a duplicate definition in the common top-level scope, or the two scripts don't share a common top-level lexical scope and hence the two imported x bindings are distinct but neither shadow the other

Well, yes, but that wasn't the point, was it? In your OP proposing STL, you made an exception for allowing imports to shadow each other. With the example, I was demonstrating that that is almost equivalent to allowing shadowing in general.

No, I suggesting allowing duplicate identical imports in separate Programs while I would disallow (for STL) multiple lets for the same identifier.

# Andreas Rossberg (14 years ago)

On 30 January 2012 18:16, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Assuming this is a single Program or StatementList, the above is current defined as being a static error.  The above would require changing decisions that have already been made so I don't see why we should be considering such cases unless we run into an issue that forces us to reconsider that decision.

Yes, and just to be clear, I'm not at all advocating allowing it, quite the opposite.

And as I said earlier, by allowing import shadowing, you could encode this, even if shadowing was disallowed for let itself.

I don't think so.  Imports are treated as local lexical bindings, just like let and const so:

import {x} from wherever; let x = 2;

is just as illegal as:

let x=1; let x=2;

Sure, but I thought the case in question was

import x from wherever1; import x from wherever2;

(perhaps only in separate scripts). And my claim was that allowing this is just as problematic as a duplicate let.

and you call this shadowing. I think we have already rejected this semantics and I only use "shadowing" in the context of nested scopes such as:

OK, thanks for clarifying. Yes, I'm using "shadowing" in the general sense, i.e. one binding hiding the other. And I think I talked about "intra-scope shadowing" somewhere, to make the distinction.

Well, yes, but that wasn't the point, was it? In your OP proposing STL, you made an exception for allowing imports to shadow each other. With the example, I was demonstrating that that is almost equivalent to allowing shadowing in general.

No, I suggesting allowing duplicate identical imports in separate Programs while I would disallow (for STL) multiple lets for the same identifier.

OK, I see. Then I somewhat misinterpreted what you wrote initially. But even with that in mind, I'm still slightly confused what exactly you were suggesting. In particular, did you assume that imports from one script are visible in later ones?

If not, then what environment do they go in? And why have different rules for imports and other bindings?

If they are, then you might still have the problem I was describing, if you want to allow duplicate imports as you suggested (what I call shadowing):

<script>

import x from M1 </script>

<script>

function f() { return x } import x from M2 function g() { return x } </script>

Which x does each function see, and why?

On the other hand, I see that in the OP you said "multiple equivalent imports", presumably ruling out examples like the above if both xs do not refer to the same thing. Was that the idea?

# Allen Wirfs-Brock (14 years ago)

On Jan 30, 2012, at 10:11 AM, Andreas Rossberg wrote:

On 30 January 2012 18:16, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Assuming this is a single Program or StatementList, the above is current defined as being a static error. The above would require changing decisions that have already been made so I don't see why we should be considering such cases unless we run into an issue that forces us to reconsider that decision.

Yes, and just to be clear, I'm not at all advocating allowing it, quite the opposite.

And as I said earlier, by allowing import shadowing, you could encode this, even if shadowing was disallowed for let itself.

I don't think so. Imports are treated as local lexical bindings, just like let and const so:

import {x} from wherever; let x = 2;

is just as illegal as:

let x=1; let x=2;

Sure, but I thought the case in question was

import x from wherever1; import x from wherever2;

(perhaps only in separate scripts). And my claim was that allowing this is just as problematic as a duplicate let.

separate scripts, and the same (or at least equivalent) whatevers)

...

No, I suggesting allowing duplicate identical imports in separate Programs while I would disallow (for STL) multiple lets for the same identifier.

OK, I see. Then I somewhat misinterpreted what you wrote initially. But even with that in mind, I'm still slightly confused what exactly you were suggesting. In particular, did you assume that imports from one script are visible in later ones?

yes, that's what I mean by STL. Binding are progressive added to the single top-level as each program is evaluated.

If not, then what environment do they go in? And why have different rules for imports and other bindings?

If they are, then you might still have the problem I was describing, if you want to allow duplicate imports as you suggested (what I call shadowing):

<script> import x from M1 </script>

<script> function f() { return x } import x from M2 function g() { return x } </script>

Which x does each function see, and why?

All lexical declarations within a StatementList (usually a Block, Function, or Module body) are logically hoisted to the top of the block where they are instantiated on entry. There is only one x in the above script which both function see. For bindings captured by functions, the placement of the function declaration within its statement list isn't particularly relevant. All that matters is that the captured binding is initialized by the time the function is actually called and reference the binding.

On the other hand, I see that in the OP you said "multiple equivalent imports", presumably ruling out examples like the above if both xs do not refer to the same thing. Was that the idea?

Yes, I was really trying to allow under STL for independent (and normally not inlined) scripts both containing things like:

// script 1 import $ from "someURL/jQuery"; ....

//script 2 import $ from "someURL/jQuery"; ...

If the imports were replaced by lets or const, STL would throw when instantiating the second script.

# Brendan Eich (14 years ago)

Andreas Rossberg wrote:

What are you proposing for module bindings then? They would have to be visible later on for the "use modules" excuse to work. Do they clash/shadow/overwrite?

You're right, it doesn't pay to try to fence off 'let' and 'const'.Indeed your next reply (to Allen) convinces me that nested is best.