-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
change class inheritance code #12488
Conversation
@@ -3338,7 +3338,7 @@ namespace ts { | |||
priority: 0, | |||
text: ` | |||
var __extends = (this && this.__extends) || function (d, b) { | |||
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; | |||
Object.setPrototypeOf(d, b); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with the new transformer based emitter. But it seems Object.setPrototypeOf
is not supported in ES3 environment and it is emitted in ES3 target. Does it need transformed in other transformers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use d.__proto__ = b;
.
but it is not official spec in ES3 and ES5.
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
I misunderstood, Object.setPrototypeOf is a specification from ES2015. not ES5...
We should not use Object.setPrototypeOf
.
Is this code correct?
if(typeof Object.setPrototypeOf === "function") {
Object.setPrototypeOf(d, b);
} else {
d.__proto__ = b;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__proto__
can be widely used but still not IE. https://babeljs.io/docs/usage/caveats/
I'm afraid we must do special emission for ES3.😞
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related
#9925 (comment)
Even if this change will be optional, if someone adds and exposes this incompatibility with IE to npm, it will pollute all downstream packages. Then many packages won't work with IE because of this change. I am very worried about it. |
that is why we are not planning on doing it. |
I should clarify. this is about emitting methods and accessors as |
f968f1b
to
570575a
Compare
I updated my PR to fit #12622 proposal |
var __extendStatics = (this && this.__extendStatics) || | ||
Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is our first helper that is depended on by another helper. Additionally, if we port this to tslib
, you have a strange issue which is that even if you create your own __extendStatics
helper, you can't change the behavior of an already-imported __extends
helper.
This is because even after you import your helper, __extendStatics
will have been captured by __extends
's closure within tslib
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point. then we do not need the (this && this.__extendStatics)
part?
You can always override the __extends
behavior, so it is not that we are restricting access t this functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are using --importHelpers
then you can't override any imported helper in any case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You would need the (this && this.__extendStatics)
just like any other helper for when --importHelpers
is not present, and this needs to be added to tslib.
@DanielRosenwasser @mhegazy @rbuckton thank you for review. I tried below code. How is it?
|
@vvakame From a quick look at It might also be important to set the |
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; This could use EDIT: ah, preserving setters and configurability could be troublesome when trying to actually override in a subclass. |
@Kovensky good point.
this implementations is inherit from current |
@vvakame the current code is probably safer; actually copying the descriptors could be dangerous if the original descriptor is |
@DanielRosenwasser and @rbuckton what do you think? |
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
};` | ||
var __extends = (this && this.__extends) || (function () { | ||
var __extendStatics = Object.setPrototypeOf || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this isn't a helper, can we just call it staticExtend
, extendStatic
, or extendStatics
?
@vvakame can you update the name of I have pushed a similar PR to tslib at: microsoft/tslib#20 |
updated! |
Does this allow transpiled classes to properly inherit symbols from parent classes? I seem to recall that previously that worked in ES6 and in Babel, but not in TypeScript (see #7529) |
For static properties with symbols as keys, yes. |
I hope that this really is an improvement and not makes the situation even worse: Could someone do a writeup as to how typescript generated code will behave differently depending on the target switch and the browser it is being executed in? As far as I can see, now code will not only behave differently if compiled to es6 and then babelized to es5, but also it will behave differently if run on those browsers that don't support setPrototypeOf and those that do (which is IE9/10 and not so old Android and Safari browsers: compat table). |
This is a best effort change we can achieve in TypeScript. Please refer to #9925 (comment) to see why changing inheritance code in even pure TypeScript environment will be hard. However, this change is required for ES6 compatibility. For now this change seems to make life harder to work with JavaScript code, but it will be no less difficult in future to write ES6 along with legacy ES5. So just think it as an investment in future. TLDR; |
It appears this change just took us by surprise, can anyone verify here if this is related? class A {
private static test = [];
do() {
B.test.push("A");
}
}
class B extends A {
} In the online playground this works as expected (weird, but expected). But with this change it appears that B.test is undefined. |
@stevehansen can you provide more details.. this seems to be working fine for me on node. c:\test>type a.ts
class A {
private static test = [];
do() {
return B.test.push("A");
}
}
class B extends A {
}
console.log("new A().do() => " + new A().do());
console.log("new B().do() => "+ new B().do());
c:\test>tsc a.ts --t es5
c:\test>type a.js
var __extends = ...
var A = (function () {
function A() {
}
A.prototype.do = function () {
return B.test.push("A");
};
return A;
}());
A.test = [];
var B = (function (_super) {
__extends(B, _super);
function B() {
return _super.apply(this, arguments) || this;
}
return B;
}(A));
console.log("new A().do() => " + new A().do());
console.log("new B().do() => " + new B().do());
c:\test>node a.js
new A().do() => 1
new B().do() => 2 |
@mhegazy, I'm sorry, it seems you are right. Going to have to figure out was is exactly wrong in our case, what we are seeing is that B.test is undefined, and when I looked at the possible differences I saw the extendStatics call inside _extends which led me here. I was at least able to reproduce our issue but it seems that the new code from this pull request actually fixes the problem instead of causing it, I'll have to verify with the colleague tomorrow that first got the issue. https://jsbin.com/yosamebahu/edit?js,console function register(name: string): (obj: any) => void {
return (obj: any) => document.registerElement(name, obj);
}
@register("x-a")
class A {
private static test = [];
do() {
return B.test.push("A");
}
}
class B extends A {
}
console.log(typeof A.test);
console.log(typeof B.test); |
As I've been asking for this change for a long time, sure I'll explain the underlying issue and consequences. IntroThis only touches inheritance of static members. class B {
static x = 42;
}
class D extends B {
}
D.x; // = 42 The problem is that you can't transpile that to ES5 in a compliant way for older browsers (think IE10-). Until now (TS < 2.2), MS preferred having a consistently incorrect ES5 emit, i.e. the same non-ES6 compliant behavior in all browsers. What browsers are impacted?When targeting ES5 or ES3 What is the difference in behavior?With 2.2+ codegen, static members are inherited through prototype chaining.
This may look like narrow edge cases, but it can causes major headaches, e.g. when polyfilling If you have code that depends on the old behavior, this is a breaking change. In that case, note that changing the target to ES6 would also have been a breaking change for you. |
Actually: now (2.2) ES5 code emitted by TS will behave like Babel in modern browsers and like native ES6. In legacy browsers, TS and babel are both wrong but in different ways. Babel doesn't give a shit about inherited static members (
There is a second fallback, Safari 5.1 supports setting
|
@jods4 - thanks for the writeup. |
It's not that simple.
First: own properties are a real issue that causes major trouble. See for example aurelia/metadata#22. Second: correct me if I'm wrong but if I understand correctly the "broken static inherited getter" is that currently a static getter is not inherited, rather its value when the derived class is declared is copied, right? This is not a "trade". It's also fixed by the prototype chaining (i.e. fixed everywhere except in IE10- where the old codegen is still applied). It seems to me that the new codegen is compliant with the ES6 specs. It is also pretty much the same as what Babel does. So I'm unsure what could be better in modern browsers. As a final note, remember you can easily substitue your own |
Fixes / #3610 partially
This change makes happy with babel generated code.
related skatejs/skatejs#936
In ECMAScript 2015.
class member must be
enumerable: false
, but tsc generated es5 code isenumerable: true
.TypeScript's helper function.
this function copies
enumerable: true
property only.I replace
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
toObject.setPrototypeOf(d, b);
.I do not know whether it is complicated and understanding is correct or not...
14.5.14 Runtime Semantics: ClassDefinitionEvaluation -> 14.5.14.12. ... DefineMethod... -> 14.3.8 Runtime Semantics: DefineMethod -> 14.3.8.7. ... MakeMethod ... -> 9.2.10 MakeMethod -> 9.2.10.3 ...[[HomeObject]] ... -> 9.2 ECMAScript Function Objects
If the function uses super, this is the object whose [[GetPrototypeOf]] provides the object where super property lookups begin.