google / traceur-compiler

Traceur is a JavaScript.next-to-JavaScript-of-today compiler
Apache License 2.0
8.17k stars 578 forks source link

Bug: can't extend the native Map/WeakMap/Set/WeakSet class #2107

Open stuartZhang opened 8 years ago

stuartZhang commented 8 years ago

Bug: can't extend the native Map/WeakMap/Set/WeakSet class. Description: Only the ES 6 classes can't be extended. However, both the traditional built-in classes (e.g. Date) and the custom classes are able to be successfully extended to instantiate a sub class.

Source code:

class B extends Map{
  get b(){
    return 20;
  }
}
const b = new B();
console.log(b.b);

Run the JavaScript file by:

$ ../node_modules/.bin/traceur test.js

Runtime Error:

ModuleEvaluationError: Constructor Set requires 'new'
    at B.Set (native)
    at tailCall (/home/stzhang/var/webapps/connect/main/node_modules/traceur/bin/traceur.js:932:24)
    at Function.apply (/home/stzhang/var/webapps/connect/main/node_modules/traceur/bin/traceur.js:963:20)
    at new B (/home/stzhang/var/webapps/connect/main/unit_tests/mojioApiTokenRetrieval.bbjs:494:43)
    at /home/stzhang/var/webapps/connect/main/unit_tests/mojioApiTokenRetrieval.bbjs:500:11
johnjbarton commented 8 years ago

repl test case

https://google.github.io/traceur-compiler/demo/repl.html#class%20B%20extends%20Map%7B%0A%20%20get%20b()%7B%0A%20%20%20%20return%2020%3B%0A%20%20%7D%0A%7D%0Aconst%20b%20%3D%20new%20B()%3B%0Aconsole.log(b.b)%3B

stuartZhang commented 8 years ago

I suspect that the below statement throws an error that the "new" operator is required.

$traceurRuntime.superConstructor(B).apply(this, arguments);

In the ES6 program, the above corresponds to the statement "super();".

stuartZhang commented 8 years ago

Referencing the feedback from the previus issue: https://github.com/google/traceur-compiler/issues/1413, it works as design.

arv commented 8 years ago

There is a work around:

Compile:

class M extends Map {
  constructor() {
    super();
  }
}

to:

class M extends Map {
  constructor() {
    const self = new Map();
    self.__proto__ = M.prototype;  // should really be new.targe.prototype
    return self;
  }
}

but there are 2 issues making this hard:

  1. Detecting when to do this transformation.
  2. It needs new.target to work correctly and new.target is too expensive to emulate
zloirock commented 8 years ago
  1. Simple list of built-in constructors by the spec. Isn't perfect, but better solutions will be much harder.
  2. new.target.prototype -> Object.getPrototypeOf(this)
arv commented 8 years ago

You cannot touch this before calling super() in a constructor... but the compiled code is not using a constructor so it seems like it would work.

Another problem would be sub classing M... The way we compile constructors doesn't do the right thing when an object is returned.

zloirock commented 8 years ago

but the compiled code is not using a constructor so it seems like it would work.

Sure, it's approach for a compiled code, I use it for a long time.

Another problem would be sub classing M

Here required complete subclassing reform like in babel@6.