Skip to content

Sloppy mode block-scoped self-defining functions don't hoist well #162

Closed
@littledan

Description

@littledan

There's an idiom called "self-defining functions". It usually looks like this:

var foo = function() {
  foo = ...
}

Some users (e.g., GWT, which has widely deployed compilation output all over the web), write a slightly different version, used to implement Java static {} class initialization blocks that run just once, but with checks all over the place to run it if it hasn't been run yet:

function foo() {
   foo = ...
}

Many module systems wrap all the definition code in a try/catch. Google does this internally, and the JS code in Reddit seems to have had a similar treatment. If we have a sloppy-mode block-scoped self-defining function as follows:

try {
  function foo() {
    alert("hey!")
    foo = () => {}
  }
} catch (e) { }
foo()
foo()

then (I believe) all browsers pre ES2015 (IE10-, WebKit, Firefox and Chrome) would alert "hey" just once. This is the long-standing web-compatible intersection, as far as I can tell.

However, ES2015 semantics would say that there are two bindings--the inner lexical one inside the try block, and the outer function-hoisted one. The line redefining foo will redefine the lexical binding, and leave the hoisted one in place. So "hey!" will be alerted twice. This is IE11 and Edge semantics, as the first implementors of something approximating Annex B 3.3.

I don't think these semantics are web-compatible. They break Google Inbox, though Inbox does not currently try to work on IE. More generally, the breakage is just the result of a combination of two techniques that independently work and are independently widely used--self-defining functions (through a rather odd syntax that GWT has been emitting for years), and surrounding function definitions by try/catch (which many module systems seem to do). It is, at the very least, a refactoring hazard to prohibit them together.

I think it's possible that this breaks the web even though IE11 has been shipping for a couple years because

  • GWT is used in a lot of deploy-once intranet applications which might be only used on particular browsers, and might hit only old versions of IE or non-IE browsers.
  • Lots of static initialization blocks could be run multiple times, and the program can still sorta get by, but that doesn't make its behavior correct.

I think the second binding, the lexical one, isn't necessary to get something working that will allow references to lexically scoped variables from hoisted functions and be within the intersection of all browsers' semantics. An alternative semantics suggested by adamk@ is to have just one var-like binding, initialized to undefined, which is set at the top of the block where the function declaration occurs to that function value. There would be no conditionality semantics, and no multiple bindings for the same variable name, so the result would be easier for both users and implementors to understand.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs consensusThis needs committee consensus before it can be eligible to be merged.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions