Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inheritance problem with ES6 class transpiled with BabelJS #12059

Closed
ghoullier opened this issue Nov 4, 2016 · 5 comments
Closed

Inheritance problem with ES6 class transpiled with BabelJS #12059

ghoullier opened this issue Nov 4, 2016 · 5 comments
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@ghoullier
Copy link

TypeScript Version: 2.0.7

TypeScript seems not correctly inherit static properties from a class transpiled with BabelJS

Code

class BaseClass {
    static get STATIC_VALUE() {
        return 'VALUE'
    }
}

class DerivedClass extends BaseClass {}

console.log(BaseClass.STATIC_VALUE === DerivedClass.STATIC_VALUE);

Expected behavior:

The comparison is falsy.

Actual behavior:

If BaseClass is transpiled via BabelJS the comparison is falsy.

Pure TypeScript behavior:

var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};

var BaseClass = (function () {
    function BaseClass() {
    }
    Object.defineProperty(BaseClass, "STATIC_VALUE", {
        get: function () {
            return 'VALUE';
        },
        enumerable: true,
        configurable: true
    });
    return BaseClass;
}());

var DerivedClass = (function (_super) {
    __extends(DerivedClass, _super);
    function DerivedClass() {
        _super.apply(this, arguments);
    }
    return DerivedClass;
}(BaseClass));

console.log(BaseClass.STATIC_VALUE === DerivedClass.STATIC_VALUE);
// comparison is truthy

BabelJS transpiled BaseClass and TypeScript inheritance behavior:

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var BaseClass = function () {
  function BaseClass() {
    _classCallCheck(this, BaseClass);
  }

  _createClass(BaseClass, null, [{
    key: 'STATIC_VALUE',
    get: function get() {
      return 'HELLO';
    }
  }]);

  return BaseClass;
}();

var DerivedClass = (function (_super) {
    __extends(DerivedClass, _super);
    function DerivedClass() {
        _super.apply(this, arguments);
    }
    return DerivedClass;
}(BaseClass));

console.log(BaseClass.STATIC_VALUE === DerivedClass.STATIC_VALUE);
// comparison is falsy

TypeScript transpiled BaseClass and BabelJS inheritance behavior:

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var BaseClass = function () {
    function BaseClass() {}
    Object.defineProperty(BaseClass, "STATIC_VALUE", {
        get: function get() {
            return 'VALUE';
        },
        enumerable: true,
        configurable: true
    });
    return BaseClass;
}();

var DerivedClass = function (_BaseClass) {
    _inherits(DerivedClass, _BaseClass);

    function DerivedClass() {
        _classCallCheck(this, DerivedClass);

        return _possibleConstructorReturn(this, (DerivedClass.__proto__ || Object.getPrototypeOf(DerivedClass)).apply(this, arguments));
    }

    return DerivedClass;
}(BaseClass);

console.log(BaseClass.STATIC_VALUE === DerivedClass.STATIC_VALUE);
// comparison is truthy

The problem is not reproduced in the other side, BabelJS correctly inherit static properties from a class transpiled via TypeScript

@Jessidhia
Copy link

Jessidhia commented Nov 10, 2016

The problem is that, while __extends sets up the .prototype (i.e. the prototype of constructed instances), it doesn't set the prototype of the constructor itself (the part with Object.setPrototypeOf in babel's _inherits helper); so the constructors end up not related.

__extends does copy the enumerable own string-keyed properties in the constructor (not the descriptors!), though, so in the pure TypeScript build, DerivedClass.STATIC_VALUE is the literal 'VALUE' string (not the getter!). Since they are copies, updating the value in the BaseClass also won't be reflected in the DerivedClass.

For some reason, in your "BabelJS transpiled BaseClass and TypeScript inheritance behavior", the STATIC_VALUE key is not marked enumerable, so the for .. in loop in __extends does not see it. The spec says they should be, so that's probably the cause.

@Aryk
Copy link

Aryk commented Dec 14, 2016

Will this be something that eventually gets fixed so we don't need to use (http://babeljs.io/docs/plugins/transform-es2015-classes/)?

@cletusw
Copy link

cletusw commented Jan 4, 2017

I just ran into what I believe to be the same problem. While this test case works fine, if you make foo a getter instead:

class A {}
Object.defineProperty(A, 'foo', {
  get: function() {
    return 1;
  }
})
class B extends A {}
console.log(B.foo);

TypeScript compiled code outputs undefined while Babel code correctly outputs 1.

As @Kovensky mentioned, this is because Babel sets up a prototype chain on the constructor functions themselves as well as the instances, while TypeScript just copies over enumerable properties.

@Jessidhia
Copy link

@cletusw this just got fixed with #12488

@cletusw
Copy link

cletusw commented Jan 4, 2017

Haha, yeah I literally just saw that as well while checking before making the change myself. I was just pulling master to test it locally. Pretty sure this can be closed now!

EDIT: Yup, worked locally for my test case. Thanks!

@mhegazy mhegazy closed this as completed Jan 4, 2017
@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Jan 4, 2017
@mhegazy mhegazy added this to the TypeScript 2.2 milestone Jan 4, 2017
@DanielRosenwasser DanielRosenwasser added the Bug A bug in TypeScript label Jan 4, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

No branches or pull requests

6 participants