Four static scoping violations in ES5 sloppy

# Domenic Denicola (13 years ago)

I went back to this old es-discuss thread:

www.mail-archive.com/[email protected]/msg18408.html

which references a video segment from Mark Miller:

www.youtube.com/watch?v=Kq4FpMe6cRs&t=42m53s

giving four static scoping violations:

  1. implicit global variable creation
  2. with
  3. deleteing free variables
  4. eval introducing local bindings

2 and 4 make perfect sense, but I don't understand how 1 and 3 interfere with static scoping. In particular, given a language with no with and with ES5-strict semantics for eval, I was unable to contrive scenarios where implicit global variable creation or deleteing a free variable introduced an ambiguity in the scope chain that prevented static knowledge of what an identifier referred to.

Does anyone have any idea how 1 and 3 interfere with static scoping?

# Sam Tobin-Hochstadt (13 years ago)

On Sun, Mar 17, 2013 at 11:44 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:

  1. implicit global variable creation
  2. with
  3. deleteing free variables
  4. eval introducing local bindings

2 and 4 make perfect sense, but I don't understand how 1 and 3 interfere with static scoping. In particular, given a language with no with and with ES5-strict semantics for eval, I was unable to contrive scenarios where implicit global variable creation or deleteing a free variable introduced an ambiguity in the scope chain that prevented static knowledge of what an identifier referred to.

Does anyone have any idea how 1 and 3 interfere with static scoping?

The point is that given these features, you can't predict statically whether a variable reference is bound to a global variable, or is unbound and will produce a ReferenceError.

Here's case 1:

if (something_random()) window.xxx = 7; xxx; // ReferenceError or not?

And here's case 3:

if (something_else_random()) delete xxx; xxx; // ReferenceError or not?

# Domenic Denicola (13 years ago)

On Mar 18, 2013, at 7:54, "Sam Tobin-Hochstadt" <samth at ccs.neu.edu> wrote:

On Sun, Mar 17, 2013 at 11:44 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:

  1. implicit global variable creation
  2. with
  3. deleteing free variables
  4. eval introducing local bindings

2 and 4 make perfect sense, but I don't understand how 1 and 3 interfere with static scoping. In particular, given a language with no with and with ES5-strict semantics for eval, I was unable to contrive scenarios where implicit global variable creation or deleteing a free variable introduced an ambiguity in the scope chain that prevented static knowledge of what an identifier referred to.

Does anyone have any idea how 1 and 3 interfere with static scoping?

The point is that given these features, you can't predict statically whether a variable reference is bound to a global variable, or is unbound and will produce a ReferenceError.

Here's case 1:

if (something_random()) window.xxx = 7; xxx; // ReferenceError or not?

And here's case 3:

if (something_else_random()) delete xxx; xxx; // ReferenceError or not?

Thanks, but what does your example for case 1 have to do with implicit global variable creation? It actually demonstrates how the situation is exactly the same with explicit global variable creation. Strict mode does nothing to change the result.

Similarly, in case 3, the code could be delete window.xxx and we still cannot statically predict whether there's a ReferenceError on the subsequent line or not. Again, strict mode does not change the result.

# Andreas Rossberg (13 years ago)

On 18 March 2013 13:28, Domenic Denicola <domenic at domenicdenicola.com> wrote:

On Mar 18, 2013, at 7:54, "Sam Tobin-Hochstadt" <samth at ccs.neu.edu> wrote:

On Sun, Mar 17, 2013 at 11:44 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:

  1. implicit global variable creation
  2. with
  3. deleteing free variables
  4. eval introducing local bindings

2 and 4 make perfect sense, but I don't understand how 1 and 3 interfere with static scoping. In particular, given a language with no with and with ES5-strict semantics for eval, I was unable to contrive scenarios where implicit global variable creation or deleteing a free variable introduced an ambiguity in the scope chain that prevented static knowledge of what an identifier referred to.

Does anyone have any idea how 1 and 3 interfere with static scoping?

The point is that given these features, you can't predict statically whether a variable reference is bound to a global variable, or is unbound and will produce a ReferenceError.

Here's case 1:

if (something_random()) window.xxx = 7; xxx; // ReferenceError or not?

And here's case 3:

if (something_else_random()) delete xxx; xxx; // ReferenceError or not?

Thanks, but what does your example for case 1 have to do with implicit global variable creation?

Nothing, but Sam probably meant to write:

if (something_random()) xxx = 7; xxx; // ReferenceError or not?

It actually demonstrates how the situation is exactly the same with explicit global variable creation. Strict mode does nothing to change the result.

Similarly, in case 3, the code could be delete window.xxx and we still cannot statically predict whether there's a ReferenceError on the subsequent line or not. Again, strict mode does not change the result.

That is true, and the proper answer is that the global object should be on the above list as well. Not sure why Mark did not include it.

# Sam Tobin-Hochstadt (13 years ago)

On Mon, Mar 18, 2013 at 8:40 AM, Andreas Rossberg <rossberg at google.com> wrote:

Nothing, but Sam probably meant to write:

if (something_random()) xxx = 7; xxx; // ReferenceError or not?

Right, I was confusing the problem this code describes, which is what I think Mark meant, with the problem of the global object being on the scope chain.

It actually demonstrates how the situation is exactly the same with explicit global variable creation. Strict mode does nothing to change the result.

Similarly, in case 3, the code could be delete window.xxx and we still cannot statically predict whether there's a ReferenceError on the subsequent line or not. Again, strict mode does not change the result.

That is true, and the proper answer is that the global object should be on the above list as well. Not sure why Mark did not include it.

I believe that Mark would distinguish 1-4, which are fixed by strict mode, from the global object, which is the remaining issue in ES5. But only Mark can answer this for sure.

# Domenic Denicola (13 years ago)

From: Sam Tobin-Hochstadt

On Mon, Mar 18, 2013 at 8:40 AM, Andreas Rossberg <rossberg at google.com> wrote:

Nothing, but Sam probably meant to write:

if (something_random()) xxx = 7; xxx; // ReferenceError or not?

Right, I was confusing the problem this code describes, which is what I think Mark meant, with the problem of the global object being on the scope chain.

It actually demonstrates how the situation is exactly the same with explicit global variable creation. Strict mode does nothing to change the result.

Similarly, in case 3, the code could be delete window.xxx and we still cannot statically predict whether there's a ReferenceError on the subsequent line or not. Again, strict mode does not change the result.

That is true, and the proper answer is that the global object should be on the above list as well. Not sure why Mark did not include it.

I believe that Mark would distinguish 1-4, which are fixed by strict mode, from the global object, which is the remaining issue in ES5.

Last time this came up:

www.mail-archive.com/[email protected]/msg18463.html

it was clarified that the global object is not a static scoping violation, since it's the top-most in the scope chain. Thus we know that every binding that's not in the nested function environments must be on the global object—or not exist.

That's the essence of my confusion. Cases 1 and 3 do not change things. That is, xxx = 7 versus window.xxx = 7 doesn't change where we determine xxx to be: it must be global (if it exists at all). Similarly, delete xxx versus delete window.xxx doesn't change that xxx must be global or not exist. And strict mode prohibits the former of these two forms, but doesn't prohibit the latter, and so seems to have no impact.

Now, if with and sloppy eval weren't removed, I could see the argument, at least for delete:

www.mail-archive.com/[email protected]/msg18466.html

but with those two gone (i.e. with 2 and 4 gone), 1 and 3 seem to not matter.

But only Mark can answer this for sure.

Indeed :)

# Mark S. Miller (13 years ago)

Interesting thread. After all this work trying to figure out what I meant, I'm sorry to disappoint. Technically, Domenic is right: once we are rid of "with" and non-strict "eval", #1 and #3 do not technically violate the degree of static scoping provided by ES5.

So why are they on this list?

Because many who are not already intimately familiar with JS will be properly outraged at #1 and #3 in non-strict mode, and appreciate that strict mode cleans up scoping to the extent that it can.

And why does ES5/strict impose these restrictions, when they are not necessary for the formal criterion?

Because ES5 strict mode, being an opt-in, gave us a rare opportunity to clean things up in preparation for yet better scoping in ES6. I'm pleased to report that it mostly turned out that way. Because of #1 and #3, ES5 strict code will be easier to refactor into ES6 modules, where the global object is finally not on their scope chain. At the time we did this, we didn't anticipate this specific aspect of ES6, but took the opportunity to clear the ground.

And because implicit creation of global variables was incredibly hazardous, turning misspellings into new global variables.

# Andreas Rossberg (13 years ago)

On 18 March 2013 17:32, Mark S. Miller <erights at google.com> wrote:

And why does ES5/strict impose these restrictions, when they are not necessary for the formal criterion?

Because ES5 strict mode, being an opt-in, gave us a rare opportunity to clean things up in preparation for yet better scoping in ES6. I'm pleased to report that it mostly turned out that way. Because of #1 and #3, ES5 strict code will be easier to refactor into ES6 modules, where the global object is finally not on their scope chain. At the time we did this, we didn't anticipate this specific aspect of ES6, but took the opportunity to clear the ground.

Maybe I misunderstand what you mean, but unfortunately, the global object will remain at the top of the scope chain in ES6, even with modules (though complemented with a lexical environment for new binding forms). We shied away from fixing that mistake.

# Brendan Eich (13 years ago)

Andreas Rossberg wrote:

On 18 March 2013 17:32, Mark S. Miller<erights at google.com> wrote:

And why does ES5/strict impose these restrictions, when they are not necessary for the formal criterion?

Because ES5 strict mode, being an opt-in, gave us a rare opportunity to clean things up in preparation for yet better scoping in ES6. I'm pleased to report that it mostly turned out that way. Because of #1 and #3, ES5 strict code will be easier to refactor into ES6 modules, where the global object is finally not on their scope chain. At the time we did this, we didn't anticipate this specific aspect of ES6, but took the opportunity to clear the ground.

Maybe I misunderstand what you mean, but unfortunately, the global object will remain at the top of the scope chain in ES6, even with modules (though complemented with a lexical environment for new binding forms). We shied away from fixing that mistake.

Don't break the web.

Versioning is an anti-pattern.

I don't think "shied away" is accurate. We couldn't fix that mistake.

# Andreas Rossberg (13 years ago)

On 18 March 2013 17:48, Brendan Eich <brendan at mozilla.com> wrote:

Andreas Rossberg wrote:

On 18 March 2013 17:32, Mark S. Miller<erights at google.com> wrote:

And why does ES5/strict impose these restrictions, when they are not necessary for the formal criterion?

Because ES5 strict mode, being an opt-in, gave us a rare opportunity to clean things up in preparation for yet better scoping in ES6. I'm pleased to report that it mostly turned out that way. Because of #1 and #3, ES5 strict code will be easier to refactor into ES6 modules, where the global object is finally not on their scope chain. At the time we did this, we didn't anticipate this specific aspect of ES6, but took the opportunity to clear the ground.

Maybe I misunderstand what you mean, but unfortunately, the global object will remain at the top of the scope chain in ES6, even with modules (though complemented with a lexical environment for new binding forms). We shied away from fixing that mistake.

Don't break the web.

Versioning is an anti-pattern.

I don't think "shied away" is accurate. We couldn't fix that mistake.

I think we could have fixed it for modules. Whether that would have been worth it I don't know.

# David Bruant (13 years ago)

Le 18/03/2013 17:48, Brendan Eich a écrit :

Andreas Rossberg wrote:

On 18 March 2013 17:32, Mark S. Miller<erights at google.com> wrote:

And why does ES5/strict impose these restrictions, when they are not necessary for the formal criterion?

Because ES5 strict mode, being an opt-in, gave us a rare opportunity to clean things up in preparation for yet better scoping in ES6. I'm pleased to report that it mostly turned out that way. Because of #1 and #3, ES5 strict code will be easier to refactor into ES6 modules, where the global object is finally not on their scope chain. At the time we did this, we didn't anticipate this specific aspect of ES6, but took the opportunity to clear the ground.

Maybe I misunderstand what you mean, but unfortunately, the global object will remain at the top of the scope chain in ES6, even with modules (though complemented with a lexical environment for new binding forms). We shied away from fixing that mistake.

Don't break the web.

Versioning is an anti-pattern.

I don't think "shied away" is accurate. We couldn't fix that mistake.

I don't understand the mention of "don't break the web". Modules aren't used on the web, so whatever rules are chosen for them they can't break the web. I'm probably missing something here.

Maybe "global in the scope chain" has to be divided into 2 different meanings: "read" and "write" to the global scope. My current understanding is as follow:

  • code in a module body can read globals in the global scope
  • code in a module cannot create a global properties (unless being hand off the global object specifically)
  • the module name is part of the newly introduced lexical binding ("file local" global variables).

How far am I in understanding modules and scoping?

# Mark S. Miller (13 years ago)

Sadness. I had misremembered how all that finally resolved. In that moment, I thought we had decided to fix that for modules. But now that you mention it, I remember the discussion. These are difficult issues, and I was not trying to suggest we reopen them.

In any case, regarding the new #1 and #3 restrictions in ES5 strict, the most we can say is that it cleared the ground in hopes of better scoping in ES6, but it didn't turn out that way. Oh well. Was worth a shot.

# Till Schneidereit (13 years ago)

Wasn't there some talk about an option to remove global from a module's scope chain? Probably in the module loader? I seem to remember that being discussed. I don't, however, remember ever seeing a decision for or against such a solution.

# Brendan Eich (13 years ago)

Andreas Rossberg wrote:

On 18 March 2013 17:48, Brendan Eich<brendan at mozilla.com> wrote:

Andreas Rossberg wrote:

Maybe I misunderstand what you mean, but unfortunately, the global object will remain at the top of the scope chain in ES6, even with modules (though complemented with a lexical environment for new binding forms). We shied away from fixing that mistake. Don't break the web.

Versioning is an anti-pattern.

I don't think "shied away" is accurate. We couldn't fix that mistake.

I think we could have fixed it for modules. Whether that would have been worth it I don't know.

For modules with the standard loader, it would be not web-breaking but pretty code-migration-hostile to remove the global.

With a custom loader, sure. Possibly this can be made into a one-liner.

# Sam Tobin-Hochstadt (13 years ago)

On Mon, Mar 18, 2013 at 1:17 PM, Brendan Eich <brendan at mozilla.com> wrote:

With a custom loader, sure. Possibly this can be made into a one-liner.

The constructor for new loaders takes a global as an argument. So yes, this is a one-liner.