Last Comment Bug 585536 - Functions declared in block statements should be block-local and hoisted to top of block
: Functions declared in block statements should be block-local and hoisted to t...
Status: RESOLVED DUPLICATE of bug 1071646
:
Product: Core
Classification: Components
Component: JavaScript Engine (show other bugs)
: unspecified
: All All
: -- normal with 2 votes (vote)
: ---
Assigned To: Nobody; OK to take it and work on it
:
Mentors:
: 593176 705618 727139 788963 790418 868671 1142392 1181251 1229998 (view as bug list)
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2010-08-08 22:34 PDT by David Anderson [:dvander]
Modified: 2015-12-02 18:55 PST (History)
41 users (show)
See Also:
Crash Signature:
(edit)
QA Whiteboard:
Iteration: ---
Points: ---
Has Regression Range: ---
Has STR: ---
-
wanted


Attachments
try_catch test case (901 bytes, text/plain)
2012-10-08 03:15 PDT, Dmitriy Kropachev
no flags Details

Description David Anderson [:dvander] 2010-08-08 22:34:47 PDT
<@brendan> it'll be block local for harmony -- maybe we should do that now
<@brendan> block local, hoisted
Comment 1 Brendan Eich [:brendan] 2010-09-02 15:52:31 PDT
Need to get this done for ES5 future-proofing.

/be
Comment 2 Brendan Eich [:brendan] 2010-09-02 15:52:47 PDT
*** Bug 593176 has been marked as a duplicate of this bug. ***
Comment 3 Brendan Eich [:brendan] 2010-09-12 18:24:57 PDT
Idea is at this stage to make the breaking change only if "use strict" is in force. We are very late in the release cycle to try breaking the default version.

/be
Comment 4 Brendan Eich [:brendan] 2010-09-12 18:25:56 PDT
This should block es5strict and fx4 or we're in trouble with the Ecma purity police (I'm a volunteer deputy).

/be
Comment 5 Brendan Eich [:brendan] 2010-11-05 19:57:43 PDT
I filed bug 609832 on banning functions in blocks from es5strict for fx4, so we can take the time to get this right for the release after.

/be
Comment 6 Brendan Eich [:brendan] 2010-12-22 18:32:29 PST
Removing from es5strict blocker status. Bug 609832 will suffice for ES5 strict mode conformance.

/be
Comment 7 Jim Blandy :jimb 2011-01-07 23:48:32 PST
What about things like this?

{ if (true) function f() { } }

That's in a block, but not directly. I'd assume this and similar things should be forbidden.

How about:

switch (x) { function f() { ... } case 1: ... }

This seems to be as meaningful as a function within any other block. However, the spec doesn't treat the body of a switch statement as a block. Another special case to remember to specify.
Comment 8 Brendan Eich [:brendan] 2011-01-08 00:10:33 PST
A function declaration not directly within a block is not even supported by some browsers, and it won't be specified in Harmony. We could probably ban it in all modes, now (I mean Firefox 5's earliest nightlies).

JS has a more structured switch a la Java, not C's statement with labels anywhere in its body (Duff's Device). So you can't write anything but case labels and one default: directly within the braced switch body (which does delimit block scope for any let bindings in the labeled statements). Of course fall through works or fails just as in C.

/be
Comment 9 Dave Herman [:dherman] 2011-01-08 07:43:38 PST
> That's in a block, but not directly. I'd assume this and similar things should
> be forbidden.

Yes. We should be forbidding all function declarations anywhere except at program or function top-level.

> How about:
> 
> switch (x) { function f() { ... } case 1: ... }
> 
> This seems to be as meaningful as a function within any other block. However,
> the spec doesn't treat the body of a switch statement as a block. Another
> special case to remember to specify.

No need for these special cases. One simple rule: only allowed at function or program top-level.

Dave
Comment 10 Dave Herman [:dherman] 2011-01-08 07:45:01 PST
> No need for these special cases. One simple rule: only allowed at function or
> program top-level.

Dumb Dave. I was thinking of the wrong bug. Disregard my comment, sorry.

Dave
Comment 11 Jim Blandy :jimb 2011-01-08 13:57:09 PST
(In reply to comment #10)
> Dumb Dave. I was thinking of the wrong bug. Disregard my comment, sorry.

No problem. The patch I submitted for bug 609832 ensures that we reject function definitions within all sorts of statements.
Comment 12 Brendan Eich [:brendan] 2011-02-02 17:26:41 PST
Fixing this bug should allow block-scoped functions in strict mode code.

/be
Comment 13 Sean Stangl [:sstangl] 2011-11-28 13:37:43 PST
*** Bug 705618 has been marked as a duplicate of this bug. ***
Comment 14 John P Baker 2012-02-15 06:45:09 PST
*** Bug 727139 has been marked as a duplicate of this bug. ***
Comment 15 Jim Blandy :jimb 2012-05-08 18:45:55 PDT
I am not actively working on this. De-assigning myself.
Comment 16 Fabrício Matté 2012-09-06 14:45:00 PDT
*** Bug 788963 has been marked as a duplicate of this bug. ***
Comment 17 Ray Blaak 2012-09-06 16:10:35 PDT
I am struck by this from comment 8:
> A function declaration not directly within a block is not even supported by
> some browsers, and it won't be specified in Harmony. We could probably ban
> it in all modes, now (I mean Firefox 5's earliest nightlies).

And from bug 788963:
> It just struck me that FF is the only one to implement this block-level
> behavior, seeing as JS has no such thing as block-level scope AFAIK (IF
> blocks do not create new scopes), unless it's something new in the ES5.

"Some browsers" == only FF as far as I know. 

Just simply hosting inner func declarations to the top level would be so much more polite, and would let one declare functions closer to their actual use, since that would effectively and finally become portable.
Comment 18 Dmitriy Kropachev 2012-10-08 03:15:10 PDT
Created attachment 669086 [details]
try_catch test case

Hello Guys,

I have attached test case which represent js issue which is only actual for FF , other browsers (Ie7-10,CHROME,OPERA) doesn't meet issue there.

This test case is very simple:
try{
alert(B2());
function B2(){
	alert('DOES_NOT_WORK.JS: If this has appeared then there is no issue');
	}
}finally{}

Actually I have found bug https://bugzilla.mozilla.org/show_bug.cgi?id=593176 with almost simillar problem, which is marked as duplicate of this one, but I do not see direct connection there possibly because I am not experienced developer and do not understand many things written here.

Can someone answer if there was decided to fix such behavior or there was decided to leave it as it is ?

Thanks.
Comment 19 Boris Zbarsky [:bz] 2012-11-15 09:31:18 PST
*** Bug 790418 has been marked as a duplicate of this bug. ***
Comment 20 Boris Zbarsky [:bz] 2012-11-15 09:33:03 PST
So this is becoming more and more of a compat problem, especially on mobile.  We really need to change things here.  I assume the spec is still totally in limbo?
Comment 21 David Mandelin [:dmandelin] 2012-11-15 18:32:38 PST
Updated description to make it clear what's needed. bz, please correct it if I got it wrong.
Comment 22 :Benjamin Peterson 2012-11-19 18:04:20 PST
Can we come to some agreement about where definitions should be hoisted to? Other browsers seem to just lift everything to the (function) body level.
Comment 23 Ray Blaak 2012-11-20 12:26:51 PST
Lifting to the function top body level is certainly exactly what I am looking for. 

That would preserve the existing semantics, e.g. instead of forcing users to do it manually, which is the existing FF behaviour. It seems like a relatively simple approach that just solves the problem.
Comment 24 :Benjamin Peterson 2012-11-20 20:34:19 PST
This is quite a thorny issue. Hoisting to body level probably makes the most sense for web compatibility, but it makes no sense if you have "let" like SM. For example,

function f(a) {
    var i = 3;
    if (a) {
        let i = 4;
        function k() { return i; }
        return k;
    }
}
f(true)

would evaluate to 3 under hoisting to body level! Still, it's hard to stomach considering the web can't use |let|.

It seems declaring a non-body-level function should act as if the function was a |let| variable in its block. Even if we say that, there are questions open. Is redefining a function equivalent to redeclaring a let and thus banned?

function f(a) {
    var g = 42;
    if (a) {
        function g() {}
    }
    return g;
}

What does |f(true)| evaluate to?

I wish there was a spec!
Comment 25 Ray Blaak 2012-11-20 20:44:24 PST
If |let| is equivalent to creating an additional lambda scope, then any declarations within it proceed as before.

So f above is equivalent the following, which shows no problem with the local k:

function f(a) {
    var i = 3;
    if (a) {
        return let_i(4);
    }

    function let_i(i) {
        function k() { return i; }
        return k;
    }
}

The moral, to me at least, is that you hoist to the top of the nearest enclosing scope. Regular control-blocks are not scopes.
Comment 26 Ray Blaak 2012-11-20 21:03:14 PST
As for the other f example, you have the same question (and answer) with this example I would think:

function f(a) {
    var g = 42;
    if (a) {
    }
    function g() {}
    return g;
}

Also, what do other browsers do? The overall issue is a lack of compatability. Let's all solve it with the same semantics, and the web is a better place. There might not be a spec, but there is de-facto behaviour.
Comment 27 :Benjamin Peterson 2012-11-20 21:04:19 PST
There's not de facto behavior wrt let.
Comment 28 Florian Bender 2012-11-21 05:35:10 PST
Please make this "uplifting behaviour" (if you implement it) *non-strict-mode* only, please (and work with the other vendors to do the same). It looks like this needs to be spec-ed somehow (even if it's not ECMA-approved), so you can specify a different (non-legacy non-uplifting) behaviour for strict mode.
Comment 29 Ray Blaak 2012-11-21 09:14:56 PST
I am curious, what do other browsers do in strict mode? Do they hoist local functions? If they do, and they remain technically compliant, could not FF do the same?
Comment 30 :Benjamin Peterson 2012-11-21 09:41:39 PST
V8 lifts to the body level except in "harmony scoping mode" (which I don't think is ever enabled on the web) in which case it scopes at the block level.
Comment 31 Florian Bender 2012-11-21 13:45:44 PST
Please, lets not promote bad/confusing programming behaviour. Even if V8 hoists local functions in strict mode, *Monkey should not do the same. Strict mode is opt-in already, so IMO there is no need to expand this curious behaviour for compat.
Comment 32 Jeff Walden [:Waldo] (remove +bmo to email) 2012-11-26 13:41:17 PST
Function "declarations" not at the top level of a function or overall program are a syntax error in strict mode in SpiderMonkey, per bug 609832.  I don't think that's going to change, regardless what happens here.  If ECMA decides to change that at some point we would probably follow, but I'm pretty sure we're not changing without their say-so.
Comment 33 Boris Zbarsky [:bz] 2012-11-28 09:27:14 PST
See bug 811421 comment 8 for another example of this causing compat problems on mobile...
Comment 34 Florian Bender 2012-12-07 08:07:46 PST
(In reply to Boris Zbarsky (:bz) from comment #33)
> See bug 811421 comment 8 for another example of this causing compat problems
> on mobile...

(In reply to John Schoenick [:johns] from comment #8)
> - m.doctissimo.fr […] uses a non-standard JS syntax for functions that could
> be fairly easily fixed by the authors (bug 585536)

So this is something for TE.
Comment 35 Jason Orendorff [:jorendorff] 2012-12-13 08:46:37 PST
V8's behavior:

1. Normally, functions in statement context are hoisted to the enclosing function.
   Later function declarations overwrite earlier ones in the same scope.

   As with standard local functions, "executing" the function "statement"
   does nothing; these functions are created and bound when the script (or innermost
   enclosing function, if any) is called.

2. With both "use strict" and --harmony-scoping, functions in statement context are
   hoisted to the enclosing block. TC39 once wanted this to be the standard. I don't
   think it's standard-track anymore, and --harmony-scoping is not a web-visible
   option, so we shouldn't emulate it.

So we will ignore (2).

The problem is that fully switching to (1) will break existing Mozilla-only code that does stuff like:

    if (monkey) {
        function yum(x) { return x + " banana"; }
    } else {
        function yum(x) { return x + " cookie"; }
    }
Comment 36 Ray Blaak 2012-12-13 09:52:48 PST
> The problem is that fully switching to (1) will break existing Mozilla-only
> code that does stuff like:
> 
>     if (monkey) {
>         function yum(x) { return x + " banana"; }
>     } else {
>         function yum(x) { return x + " cookie"; }
>     }

I think that Mozilla has to decide how portable it wants to be. That code will not work on any other browser (will it?). Does it even work in NodeJS?
Comment 37 Juriy "kangax" Zaytsev 2012-12-13 10:42:56 PST
AFAIK, the only other implementations mimicking Mozilla's function statements are ancient Safari (up to 3.0.4). See http://kangax.github.com/nfe/#function-statements
Comment 38 Florian Bender 2012-12-13 13:02:05 PST
(In reply to Jason Orendorff [:jorendorff] from comment #35)
> The problem is that fully switching to (1) will break existing Mozilla-only 
> code that does stuff like:
>     if (monkey) {
>         function yum(x) { return x + " banana"; }
>     } else {
>         function yum(x) { return x + " cookie"; }
>     }

Well that can be fixed, can't it? ;)
Comment 39 Jeff Walden [:Waldo] (remove +bmo to email) 2012-12-13 13:53:57 PST
(In reply to Florian Bender from comment #38)
> Well that can be fixed, can't it? ;)

Um, yes.  Just as easily as sites depending on any other semantics can be changed, to not depend on function statement -- which in every engine that implement them as anything other than a syntax error, implement them as an extension.  (Function *declarations* are not the same as function *statements*.)  It's not helpful to simply say that code can be fixed, because if that were the simple answer, nobody would be using function statements at all.
Comment 40 Florian Bender 2012-12-13 14:12:34 PST
Jason was talking about Mozilla-only code – which can easily be fixed before changing the engine. Thus the argument agains his first choice is not really valid. That's all what I was saying.
Comment 41 Jeff Walden [:Waldo] (remove +bmo to email) 2012-12-13 14:17:07 PST
Mozilla-only code includes code in mozilla-central, which is fixable.  (Although there might be a lot of it.)  But it also includes code in the universe of addons controlled by a multitude of developers, not all actively updating their code, not all of whom we could even contact if we wanted to.  That's no more easily fixed than use of function statements on the web is.  Plus you have sites that have Mozilla-specific code paths that happen to rely on this -- perhaps in Mozilla-specific scripts and code.  (Big sites do have partially distinct implementations for different browser engines, so it's not out of the question.)
Comment 42 Jason Orendorff [:jorendorff] 2012-12-18 16:50:38 PST
In V8, functions are indeed transplanted in a way that flouts lexical scoping.

    try {
        throw 0;
    } catch (x) {
        function f() { return x++; }
    }
    print(f());

The function is transplanted to toplevel, so the "x" inside the function does not refer to the "x" that is lexically in scope. So this gives a ReferenceError.

I think this is too horrible to consider implementing in SpiderMonkey. Instead I am just going to implement exactly what the summary says and hoist to the enclosing block.

Ray Blaak suggests instead hoisting to the enclosing "scope", but that makes me feel sick inside. Blocks should act like scopes, it's the only way.

Drafting an email to es-discuss to ask TC39 to resolve this.
Comment 43 Ray Blaak 2012-12-18 17:09:43 PST
Except that in JS, blocks are not scopes, so we can't pretend that they are. Closures are scopes, and I guess catch blocks are scopes. Anything else?

In Opera, the example above gives an error that is consistent with simply hoisting it. The call to f() doesn't even work inside of the catch block, which is interesting. It seems like the rule is really to blindly hoist it to the top (of the current closure?).

E.g. this:

    try {
        throw 0;
    } catch (x) {
        function f() { return x++; }
        alert("inside of catch: " + f());
    }
    alert("outside of catch: " + f());

gives:

Uncaught exception: ReferenceError: Undefined variable: x
Error thrown at line 8, column 8 in [file...]
    throw 0;
Error initially occurred at unknown location in f() in [file...]
called from line 11, column 8 in [file...]
    alert("inside of catch: " + f());
Comment 44 Jason Orendorff [:jorendorff] 2012-12-18 17:36:16 PST
(In reply to Ray Blaak from comment #43)
> Except that in JS, blocks are not scopes, so we can't pretend that they are.

They are in ES6.

I think the current ES6 draft actually specifies "hoist to enclosing block". Need to read more closely.
Comment 45 Ray Blaak 2012-12-18 19:22:58 PST
This bug only makes sense in the context of regular JS. If blocks have lexical scopes then you cannot hoist anything at all.

With lexical scoping, these are two distinct functions, inaccessible (by name) outside of the scopes:

if (test) {
  var a = 1;
  function f() {return a}
} else {
  var a = 2; // a different var!
  function f() {return a}
}
f() // error, f not found

You have to approach
Comment 46 Jason Orendorff [:jorendorff] 2012-12-19 11:36:21 PST
There's not a distinction to be drawn between ES6 and "regular JS". ES6 is the spec for JS.

'var' hoists to the enclosing function or top-level scope (or, in strict eval code, the eval scope, which is its own special thing). The binding is hoisted, that is. Initialization only happens when the var-statement executes. ('var' is never block-scoped, the way you have it in comment 45 -- that's not what I meant to say.)

'let' and 'const' bindings hoist to the enclosing block or top-level scope.

Function declarations will behave like 'let' and 'const'. This is what ES6 specifies, and I think implementations will converge to that. We'll go first.
Comment 47 Jason Orendorff [:jorendorff] 2012-12-19 13:04:01 PST
olliej pointed out these:
    https://bugs.webkit.org/show_bug.cgi?id=27226#c4

He doesn't think Apple can change the behavior of block-level function-declarations in non-strict mode. My understanding of the discussion is that Apple won't implement the spec.
Comment 48 Sam Tobin-Hochstadt [:samth] 2012-12-19 14:53:28 PST
Just for posterity, here's the (lightly edited) IRC log.

* samth summons olliej
<jorendorff> it's funny, the bug for this https://bugzilla.mozilla.org/show_bug.cgi?id=585536 contains two or three people separately discovering that hoisting, in a language that has let, just makes no sense
<samth> and implement block scope for functions!
<dherman> jorendorff: what hoisting do you mean?
 I think it makes sense with block-hoisting
<jorendorff> var-like hoisting for functions, i mean.
<dherman> ok yeah
 agreed
<olliej> samth: noooooooo
<jorendorff> even without let!
 what it does right now in a catch-block or with statement
<olliej> samth: it’s non trivial for us currently
<jorendorff> oh it's just so wrong
<dherman> yep
<olliej> wait
<olliej> samth: do you mean outside of strict mode????
<samth> olliej: yes
<olliej> samth: because we _cannot_ change that behaviour
<jorendorff> ruh roh
<samth> olliej: wait a second ...
<olliej> samth: jorendorff: this has been discussed on numerous occasions, outside of strict mode, the behavior of nested function statements is not reconcilable
 samth: no one can change to anyone else’s behavior without breaking some set of content
<samth> olliej: this is not my recollection of what's been said in the room at tc39 meetings
<olliej> samth: no
<olliej> samth: the agreement was we could get sane behavior in strict mode and in modules 
<dherman> I think olliej is right
<olliej> samth: but outside of strict mode we’re kind of hosed
<dherman> that sounds right
<olliej> i think ES5 even says that explicitly
 i had a great set of examples somewhere
 just a sec
<dherman> yes, ES5 strict mode poisoned block-local functions
<jorendorff> i'm going to implement the spec anyway
* dherman approves
<dherman> :)
<olliej> dherman: samth jorendorff https://bugs.webkit.org/show_bug.cgi?id=27226#c4
<jorendorff> i don't think you even need the eval in the last example
 and it might even pass strict mode
<olliej> comments 4, 5 and 7
 4,5 give the ways where any engine changing non-strict behavior will break _some_ code
Comment 49 Jeff Walden [:Waldo] (remove +bmo to email) 2013-05-03 18:31:44 PDT
*** Bug 868671 has been marked as a duplicate of this bug. ***
Comment 50 Rob Campbell [:rc] (:robcee) 2013-05-28 13:51:43 PDT
fascinating.
Comment 51 Jason Orendorff [:jorendorff] 2014-09-23 09:42:31 PDT
It's time to retire this bug.

All the different JS implementations are too constrained by existing content to be able to implement breaking changes and establish a single common behavior. Once we discovered that, the urgency here evaporated.

It's unfortunate, but we've hit this a million times in other areas, where browser-specific is totally baked into the Web. Each time we die a little inside, but we've got to move on.

Now for the tiny glimmer of remaining hope: There *is* a set of use cases that do work in all implementations, and TC39 is trying to standardize just those few common use cases, in an appendix to ES6. See <http://people.mozilla.org/~jorendorff/es6-draft.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics>. I've filed a new bug, bug 1071646, to make sure we follow that spec.
Comment 52 Jason Orendorff [:jorendorff] 2014-09-23 14:02:48 PDT
Right, the other good news: under "use strict", block-local functions will do the right thing---exactly what this bug's summary is asking for. Filed bug 1071877 for that.
Comment 53 Nickolay_Ponomarev 2015-03-31 19:14:35 PDT
*** Bug 1142392 has been marked as a duplicate of this bug. ***
Comment 54 Alon Zakai (:azakai) 2015-07-07 16:17:22 PDT
*** Bug 1181251 has been marked as a duplicate of this bug. ***
Comment 55 Jan de Mooij [:jandem] 2015-10-27 05:14:19 PDT
*** Bug 1217837 has been marked as a duplicate of this bug. ***
Comment 56 Jason Orendorff [:jorendorff] 2015-10-27 06:22:17 PDT
We're going to fix this after all.

*** This bug has been marked as a duplicate of bug 1071646 ***
Comment 57 Boris Zbarsky [:bz] 2015-12-02 18:55:14 PST
*** Bug 1229998 has been marked as a duplicate of this bug. ***

Note You need to log in before you can comment on or make changes to this bug.