I'm trying to figure out whether some behavior I'm seeing in Node v4.1.1 (V8 v4.5.103.33) regarding super
and arrow functions is specified behavior, and if so (or indeed, if not), where it is in the specification that it says it should (or should not) work in the various cases I have.
In brief: Using super
in an arrow function (inner
) inside another arrow function (outer
) inside a method works unless outer
has arguments or variables inner
references, even if inner
references arguments or variables of method
. I want to know what the spec says about that: Should it work all the time, even where V8 is failing? None of the time? Only in the specific cases where V8 is currently letting it work, and not where it isn't?
Here's an MCVE:
"use strict";
class Parent {
show(msg) {
console.log(`Parent#show: ${msg}`);
}
}
class Child extends Parent {
method(arg) {
let outer = (x) => {
console.log(`outer: x = ${x}`);
let inner = () => {
super.show(`arg = ${arg}, x = ${x}`);
};
inner();
};
outer(42);
}
}
new Child().method("arg");
That fails with:
$ node test.js /path/test.js:13 super.show(`arg = ${arg}, x = ${x}`); ^^^^^ SyntaxError: 'super' keyword unexpected here at outer (/path/test.js:16:13) at Child.method (/path/test.js:18:9) at Object. (/path/test.js:22:13) at Module._compile (module.js:434:26) at Object.Module._extensions..js (module.js:452:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:475:10) at startup (node.js:117:18) at node.js:951:3
If you remove the reference to x
that's in inner
:
let inner = () => {
super.show(`arg = ${arg}`); // <== removed x from this
};
it works and outputs:
outer: x = 42 Parent#show: arg = arg
To prove to myself that the "works" case wasn't that the functions were being optimized away, I returned them out of the method and called them. Here's that slightly-more complex case (note the comments); this version works:
"use strict";
class Parent2 {
show(msg) {
console.log(`Parent2#show: ${msg}`);
}
}
class Child2 extends Parent2 {
method(arg) {
let flag = Math.random() < 0.5;
console.log(`method called with ${arg}, flag is ${flag}`);
let x = "A"; // **A**
let outer2 = (/*x*/) => { // **B**
//let x = "C"; // **C**
let inner2 = () => {
super.show(`${x}: ${arg} (${flag})`);
};
return inner2;
};
return outer2;
}
}
let o = new Child2().method("arg");
console.log(`type of outer2: ${typeof o}`);
let i = o();
console.log(`type of inner2: ${typeof i}`);
i("B");
Output:
method called with arg, flag is false type of outer2: function type of inner2: function Parent2#show: A: arg (false)
But if we comment out the line labelled A
and uncomment either B
or C
, it fails like the MCVE does.
More notes:
I should emphasize that you need to have the arrow functions nested.
outer
has no trouble accessingsuper
. I don't want to clutter up the question with another big code block, but if you addsuper.show(`outer: arg = ${arg}, x = ${x}`);
at the top ofouter
, it works just fine.As you can see,
inner
uses both an argument and a variable frommethod
(well, the MCVE just uses an arg), and that's fine, but as soon asinner
tries to use an argument or variable fromouter
, things blow up.Babel and Traceur are both perfectly happy to transpile the case that V8 won't run (here and here), but that could just be them getting something wrong that V8 gets right (or, of course, vice-versa).
It doesn't relate to template strings; the pre-MCVE version of this didn't use them (and did use promises, which is how we ended up with arrows inside arrows).
Just to emphasize, the question is what's the specified behavior here, and where in the spec is it specified.
My gut tells me this is just a V8 bug — it's early days for this stuff, after all, fair 'nuff. But either way, I'm just trying to figure out what the behavior should be, what the spec says. I've tried to follow its various and sundry sections talking about super
and "base objects" and such, and frankly I'm just not getting it.