hoisting past catch

# David Herman (15 years ago)

ES3 `catch' is block-scoped. At the last face-to-face, we talked about statically disallowing var-declarations from hoisting past let-declarations:

function f() {
    {
        let x = "inner";
        {
            var x = "outer"; // error: redeclaration
        }
    }
}

I just noticed a case I missed in the discussion, which has actually existed since ES3:

function g() {
    try {
        throw "inner";
    } catch (x) {
        var x = "outer";
    }
}

This is allowed, and it binds a function-scoped variable x, while assigning "outer" to the catch-scoped variable x. This is pretty goofy, and almost certainly not what the programmer expects. And it's exactly analogous to the function f above, which SpiderMonkey currently rejects and we all agreed Harmony ought to reject.

It's too late to add this case to ES5's strict mode restrictions, but I propose we ought to reject it in Harmony.

# Mark S. Miller (15 years ago)

Sigh. You are completely correct. This is goofy and strict mode should have disallowed it. It is so goofy that I have to observe: No one has yet shipped even a beta implementation of full strict mode, so practically it is not too late for the browser makers to all agree to reject this case with a static error. If none disagree, perhaps it is not too late to add this to the ES5 errata, even though it is a spec change and not "really" an errata. It won't be our first such.

# Brendan Eich (15 years ago)

On Oct 11, 2010, at 4:40 PM, David Herman wrote:

ES3 `catch' is block-scoped. At the last face-to-face, we talked about statically disallowing var-declarations from hoisting past let-declarations:

function f() { { let x = "inner"; { var x = "outer"; // error: redeclaration } } }

I just noticed a case I missed in the discussion, which has actually existed since ES3:

function g() { try { throw "inner"; } catch (x) { var x = "outer"; } }

This is allowed, and it binds a function-scoped variable x, while assigning "outer" to the catch-scoped variable x. This is pretty goofy, and almost certainly not what the programmer expects. And it's exactly analogous to the function f above, which SpiderMonkey currently rejects and we all agreed Harmony ought to reject.

It's too late to add this case to ES5's strict mode restrictions, but I propose we ought to reject it in Harmony.

Dave caught this glitch in ES5 too late to fix before Ecma submitted the spec to ISO. We can't change the ES5 spec now. We will fix the spec later, but the change won't show up in a normative spec for a while. Meanwhile, what implementations do has more teeth than what a spec says.

Mark Miller makes a good case that implementors should future-proof against Harmony making this case an error by rejecting it from their ES5 strict implementations. For Mozilla we plan to do this. It would be good to get agreement from the rest of the big five, so I'm taking the liberty of mailing you individually, cc'ing es-discuss (where Mark and your avid fans await ;-).

# Peter van der Zee (15 years ago)

Shouldn't any var declared in the catch block be locally scoped as well? It seems that all browsers ignore that.

try {x} catch(){ var y; } alert(y);

The above should throw an error, yet it's undefined. In fact, even if the catch is not thrown y still exists (but if the catch block is not processed as a seperate scope, I suppose that's to be expected).

# Brendan Eich (15 years ago)

On Nov 4, 2010, at 10:32 AM, Peter van der Zee wrote:

Shouldn't any var declared in the catch block be locally scoped as well?

var always hoists to top of function or program. Why would it be different in a catch block? It's not in ES3, which standardized try/catch/finally.

It seems that all browsers ignore that.

try {x} catch(){ var y; } alert(y);

(Missing catch variable there.)

The above should throw an error, yet it's undefined.

"should" by what standard?

var hoists, always. That's what the spec has said since ES1. The 'let' for Harmony will be block scoped (as the catch variable is per ES3 and ES5), and not hoisted (yay).

# Peter van der Zee (15 years ago)

On Nov 4, 2010, at 10:32 AM, Peter van der Zee wrote:

Shouldn't any var declared in the catch block be locally scoped as well?

var always hoists to top of function or program. Why would it be different in a catch block? It's not in ES3, which standardized try/catch/finally.

Hrm. After typing the string of events that es should go through for instantiating a catch clause it dawns to me that it's all after the facts. Variable statements are parsed when the program or the function is parsed, that includes those inside catch statements. So even though catch gets its own scope, the variables are already bound to the nearest function/global scope, not to the scope Catch gets.

I guess my confusion came from the notion that catch gets its own scope and thinking variables in catch statements wouldn't get hoisted until the code in that "scope" was entered. Thanks :)

# Brendan Eich (15 years ago)

On Nov 4, 2010, at 12:00 PM, Peter van der Zee wrote:

I guess my confusion came from the notion that catch gets its own scope and thinking variables in catch statements wouldn't get hoisted until the code in that "scope" was entered. Thanks :)

catch did prefigure let -- lexical scope in ES3 from 1999, but only for catch. Finally we get it everywhere in Harmon, at the price of new syntax.

# Brendan Eich (15 years ago)

On Nov 4, 2010, at 12:18 PM, Brendan Eich wrote:

On Nov 4, 2010, at 12:00 PM, Peter van der Zee wrote:

I guess my confusion came from the notion that catch gets its own scope and thinking variables in catch statements wouldn't get hoisted until the code in that "scope" was entered. Thanks :)

catch did prefigure let -- lexical scope in ES3 from 1999, but only for catch. Finally we get it everywhere in Harmon, at the price of new syntax.

I'm being too kind to ES3. It aspired to make the catch var lexical and block-scoped, but it was terribly specified: it used a new Object for the scope frame, and bound the catch variable via [[Put]], if I recall correctly. Fixed in real world implementations to avoid object-based scoping, and codified in ES5.