Duplicate super call behaviour

# Sebastian McKenzie (8 years ago)

I was recently reading the specification on the behaviour of duplicate super() calls in derived constructors. Reading the grammar the following is valid:

class Foo {
  constructor() {
    console.log("foobar);
  }
}

class Bar extends Foo {
  constructor() {
    super();
    super();
  }
}

new Bar;

However my reading of the runtime semantics, it will actually execute the constructor body *twice. *And so "foo" will be printed two times.

This is because the only thing I've found stopping duplicate super calls is step 10 of SuperCall in 12.3.5.1 www.ecma-international.org/ecma-262/6.0/index.html#sec-super-keyword-runtime-semantics-evaluation :

  1. Let newTarget be GetNewTarget().
  2. If newTarget is undefined, throw a ReferenceError exception.
  3. Let func be GetSuperConstructor().
  4. ReturnIfAbrupt(func).
  5. Let argList be ArgumentListEvaluation of Arguments.
  6. ReturnIfAbrupt(argList).
  7. Let result be Construct(func, argList, newTarget).
  8. ReturnIfAbrupt(result).
  9. Let thisER be GetThisEnvironment( ).
  10. Return thisER.BindThisValue(result).

Since BindThisValue will throw a ReferenceError if this has already been initialised:

  1. Let envRec be the function Environment Record for which the method was invoked.
  2. Assert: envRec.[[thisBindingStatus]] is not "lexical".
  3. If envRec.[[thisBindingStatus]] is "initialized", throw a ReferenceError exception.

But this check is performed at step 10 whereas the super constructor is actually evaluated at step 7.

Is my reading correct? If so, to me this behaviour seems quite unexpected.

Any insight (or correction) is extremely appreciated, thank you!

# Mark S. Miller (8 years ago)

On Sat, Oct 24, 2015 at 8:00 PM, Sebastian McKenzie <sebmck at gmail.com>

wrote:

I was recently reading the specification on the behaviour of duplicate super() calls in derived constructors. Reading the grammar the following is valid:

class Foo {
  constructor() {
    console.log("foobar);
  }
}

class Bar extends Foo {
  constructor() {
    super();
    super();
  }
}

new Bar;

However my reading of the runtime semantics, it will actually execute the constructor body *twice. *And so "foo" will be printed two times.

This is because the only thing I've found stopping duplicate super calls is step 10 of SuperCall in 12.3.5.1 www.ecma-international.org/ecma-262/6.0/index.html#sec-super-keyword-runtime-semantics-evaluation :

  1. Let newTarget be GetNewTarget().
  2. If newTarget is undefined, throw a ReferenceError exception.
  3. Let func be GetSuperConstructor().
  4. ReturnIfAbrupt(func).
  5. Let argList be ArgumentListEvaluation of Arguments.
  6. ReturnIfAbrupt(argList).
  7. Let result be Construct(func, argList, newTarget).
  8. ReturnIfAbrupt(result).
  9. Let thisER be GetThisEnvironment( ).
  10. Return thisER.BindThisValue(result).

Since BindThisValue will throw a ReferenceError if this has already been initialised:

  1. Let envRec be the function Environment Record for which the method was invoked.
  2. Assert: envRec.[[thisBindingStatus]] is not "lexical".
  3. If envRec.[[thisBindingStatus]] is "initialized", throw a ReferenceError exception.

But this check is performed at step 10 whereas the super constructor is actually evaluated at step 7.

Is my reading correct? If so, to me this behaviour seems quite unexpected.

I'm surprised. Good catch! Frankly, this case had never occurred to me and I do not remember discussing it. I would certainly prefer that it be an error without constructing twice.

# Allen Wirfs-Brock (8 years ago)

On Oct 24, 2015, at 5:45 PM, Mark S. Miller <erights at google.com> wrote:

On Sat, Oct 24, 2015 at 8:00 PM, Sebastian McKenzie <sebmck at gmail.com <mailto:sebmck at gmail.com>> wrote: ...

But this check is performed at step 10 whereas the super constructor is actually evaluated at step 7.

Is my reading correct? If so, to me this behaviour seems quite unexpected.

I'm surprised. Good catch! Frankly, this case had never occurred to me and I do not remember discussing it. I would certainly prefer that it be an error without constructing twice.

Any insight (or correction) is extremely appreciated, thank you!

Sebastian, you reading is correct.

Mark, I’m pretty sure that I brought attention to this in some venue and there was agreement that the possibility of observable side-effects with post call error check really didn’t matter as long as implementations concisely followed the spec. and all did it that way.

But, I agree that pre-check seems more intuitive. I think it ended of this way largely because within the spec. the check for attempting to rebind this only occurs within the BindThisValue abstract operation. So doing the post call check was the easier think to specify (and we were working against the clock at that time). To do a pre-check we’d have to add an additional internal method to FunctionEnviornmentRecord (and probably also replace the check in BindThisValue with an assertion).

Such a change for ES2016 seems reasonable to me.