catch vs function scope; var declaration vs initialization

# Claus Reinke (13 years ago)

What should be the output of the following code?

(function(){

try { throw "hi"; } catch (e) { var e = "ho"; var o = "hu"; var u; console.log(e); } console.log(e,u,o);

}());

It seems clear that the first console.log should output 'ho'. Implementations seem to disagree on the second console.log, though.

From my current understanding of the spec, I expected:

undefined undefined 'hu'

which I consider unfortunate; especially the first 'undefined' was intended to demonstrate the bad effects of separating declaration from initialization while treating catch and function differently, in terms of scoping.

node, opera: undefined undefined 'hu'

firefox 12 complains: 'e is not defined'

ie 9 outputs: houndefinedhu

It looks as if firefox misses the hoisted declaration of 'e' (it doesn't complain about 'u', only about 'e') while ie9 somehow merges the declaration and the catch parameter.

Claus

# Peter van der Zee (13 years ago)

On Mon, May 14, 2012 at 11:57 AM, Claus Reinke <claus.reinke at talk21.com> wrote:

What should be the output of the following code?

(function(){

try {  throw "hi"; } catch (e) {  var e = "ho";  var o = "hu";  var u;  console.log(e); } console.log(e,u,o);

}());

It seems clear that the first console.log should output 'ho'. Implementations seem to disagree on the second console.log, though.

From my current understanding of the spec, I expected:   undefined undefined 'hu'

Inside the catch, the catch-scope is first for reading and writing. But the catch scopes are ignored for declaring new variables. So your expectation seems to be the correct one. e is created in the scope of the anonymous function. Likewise, o and u are created in that scope too (so neither throw at the second console.log). "ho" is assigned to the catch-scope e, since that's the first scope in the scope traversal lookup at that point. Catch scopes are weird, yo.

# T.J. Crowder (13 years ago)

Inside the catch, the catch-scope is first for reading and writing. But the catch scopes are ignored for declaring new variables. So your expectation seems to be the correct one.

That was my analysis as well.

§10.5 tells us that var creates bindings in the anonymous function's environment, so the function's env has e, o, and u bindings.

§12.14 tells us that within the catch, a new environment is created with the function's environment being the parent. So the assignment to e is to the catch's e, not the function's.

Annotating that code a bit:

(function(){ var e; // Where these actually happen var o; var u;

try {
    throw "hi";
} catch (e) {
    e = "ho"; // Assigns to the `catch` env's `e`
    o = "hu"; // Assigns to the function's `o`
    console.log(e);
}
console.log(e, u, o); // Expect undefined undefined "hu"

}());

-- T.J.

# Allen Wirfs-Brock (13 years ago)

undefined undefined hu

is what should be produced according to the spec.

Divergence among implementations is a good sign that there is an opportunity to clean up this particular mess.

The current ES6 draft requires an early error for this function because it is trying to hoist a var declaration across a block-level declaration of the same identifier. EG,

function f() { { let x=1; //early error let declaration of x in a block that contains a nested var declaration of x { var x=2; } } }

Because let is a new feature, this restriction doesn't introduce any backwards compatibility issues. However, for consistency, the draft also currently applies the same let declaration restrictions to catch parameters. Your experiment suggests that that we might actually be able to to that ...

# John J Barton (13 years ago)

On Mon, May 14, 2012 at 3:25 AM, T.J. Crowder <tj at crowdersoftware.com> wrote:

Inside the catch, the catch-scope is first for reading and writing. But the catch scopes are ignored for declaring new variables. So your expectation seems to be the correct one.

That was my analysis as well.

§10.5 tells us that var creates bindings in the anonymous function's environment, so the function's env has e, o, and u bindings.

§12.14 tells us that within the catch, a new environment is created with the function's environment being the parent. So the assignment to e is to the catch's e, not the function's.

Annotating that code a bit:

(function(){     var e; // Where these actually happen     var o;     var u;

try {         throw "hi";     } catch (e) {         e = "ho"; // Assigns to the catch env's e         o = "hu"; // Assigns to the function's o         console.log(e);     }     console.log(e, u, o); // Expect undefined undefined "hu"

I believe that -- if we had any practical way to measure this -- 'most' web developers would expect 'ho undefined hu'. This is the sensible output, the one given by ie9. It is sensible because the simple synopsis of the funky var rules is "variables get values when assignments are executed". In my opinion the spec-ed answer here is less useful to developers than the ie9 answer. It may be true that specifications which allowed the sensible answer would result in other more serious nonsense of course.

jjb

# Claus Reinke (13 years ago)

Inside the catch, the catch-scope is first for reading and writing.

In "practice" (as in: what implementations permit; not: what is used), there is also this variation:

try { throw "oops" } catch (f) { function f() { console.log(f) } console.log(f) } console.log(f)

This isn't permitted in ES5, and 'f' would be block-scoped if permitted in ES6. In common non-standard extensions to ES5, the function and references to 'f' in its body are bound outside the catch. I don't even want to look at what happens with a function declaration in a catch where the body references the catch parameter..

I'm currently looking at these edge cases for implementing renaming (program transformation) - no fun.

§10.5 tells us that var creates bindings in the anonymous function's environment, so the function's env has e, o, and u bindings.

§12.14 tells us that within the catch, a new environment is created with the function's environment being the parent. So the assignment to e is to the catch's e, not the function's.

Which means that the single occurrence of 'e' in 'var e = "ho"' is bound to two different scopes: outside the catch for creating a binding, in the catch for assignment. As Allen anticipated, I was wondering about the effects on mixing 'let' and 'var', too.

Claus

# Claus Reinke (13 years ago)

undefined undefined hu

is what should be produced according to the spec.

Divergence among implementations is a good sign that there is an opportunity to clean up this particular mess.

Yes, please!-)

The current ES6 draft requires an early error for this function because it is trying to hoist a var declaration across a block-level declaration of the same identifier. EG,

'let' vs 'var' would have been my next question. Good to know that this particular issue does not propagate to 'let'!

Because let is a new feature, this restriction doesn't introduce any backwards compatibility issues. However, for consistency, the draft also currently applies the same let declaration restrictions to catch parameters.

Even better!-) And in line with treating catch as an anonymous function.

Claus

# Allen Wirfs-Brock (13 years ago)

On May 14, 2012, at 2:44 PM, Claus Reinke wrote:

Because let is a new feature, this restriction doesn't introduce any backwards compatibility issues. However, for consistency, the draft also currently applies the same let declaration restrictions to catch parameters.

Even better!-) And in line with treating catch as an anonymous function.

An analogy with inner functions isn't needed and wouldn't be correct. This is simply appling the static semantics of Block level declarations. A Block is not allowed to var declare (including via nested blocks) any identifier that is lexically declared in the block (but not including nested blocks).

function f() { try { throw "hi" } catch (e) { var v; }; }

is legal and binds "v" at the top-level of function f. If the catch clause was treated like an anonymous function, "v" would be bound at the level of the catch clause. It isn't...

In terms of scope contours the above is equivalent to

function f() { { //throw "hi" } { let e; var v; } }

# Brendan Eich (13 years ago)

Claus Reinke wrote:

firefox 12 complains: 'e is not defined'

Thanks for pointing this out. It is a fairly recent regression. I used hg bisect to find it and filed

bugzilla.mozilla.org/show_bug.cgi?id=755099

I agree with Allen, we should clear this dark corner of the deck with vigorous application of early-error solution and scrub in nightly builds to prove no real code relies on var e; inside catch (e) {...} hoisting.