Use setPrototypeOf for inheritance on any browser with support#2419
Use setPrototypeOf for inheritance on any browser with support#2419ChadKillingsworth wants to merge 1 commit intogoogle:masterfrom
Conversation
|
With things as they are inheritance of static methods in compiled code is not correct by the ES6 spec, but is consistent in old and new browsers. I want to do this, but I want to make sure users are warned of this inconsistency appropriately. I believe we already have checks for stuff like this and treat it as an error in at least some cases. class A {
static foo() {}
}
class B extends A {
}
B.foo();According to @MatrixFrog has some definite opinions on this, so I'm assigning it to him for further discussion. |
|
Correct - this only works back through IE11. Babel uses Here's the Polymer code that doesn't work without this: /**
* Called by `ElementClass.finalize()`. Ensures this `klass` and
* *all superclasses* are finalized by traversing the prototype chain
* and calling `klass.finalize()`.
* @param {HTMLElement} klass
* @private
*/
function finalizeClassAndSuper(klass) {
let proto = klass.prototype;
let superCtor = Object.getPrototypeOf(proto).constructor;
if (superCtor.prototype instanceof PolymerElement) {
superCtor.finalize();
}
finalizeClass(klass);
}It's perfectly valid code. |
|
I'd continue to find work arounds except that EVERY other transpiler uses setPrototypeOf. |
|
Oh also, as in #2375 if you extend code from another transpiler, the properties won't be enumerable and copying the descriptors fails. |
|
We already polyfill setPrototypeOf using |
|
How about this? Add an argument to the |
|
Do we gain anything with the flag? If it's for performance, then sure - this makes sense. |
|
The point of my suggestion is to ensure that folks who are building with ES3 output get consistent behavior across browsers and do not have their code start crashing on older browsers without any warning from us. |
7271ae0 to
7798718
Compare
|
@brad4d @MatrixFrog I've added a parameter to the inherits function to specify whether the languageOut is ES5 or higher. That was a bit trickier than I expected - quite a few places depend on the number of arguments in the inherits function. |
brad4d
left a comment
There was a problem hiding this comment.
Thanks, @ChadKillingsworth
I think this is a step in the right direction.
It'll be great to support the correct ES6 semantics where we can.
@MatrixFrog are you comfortable with this change?
Do you have the bandwidth to shepherd this PR in, or would you rather I did it?
Thanks.
| childCtor.prototype = new tempCtor(); | ||
| /** @override */ | ||
| childCtor.prototype.constructor = childCtor; | ||
| $jscomp.inherits = function(childCtor, parentCtor, es5Out) { |
There was a problem hiding this comment.
Let's be more explicit about what this new argument means.
useSetPrototypeOf
| /** @override */ | ||
| childCtor.prototype.constructor = childCtor; | ||
| $jscomp.inherits = function(childCtor, parentCtor, es5Out) { | ||
| if (es5Out === true && Object.setPrototypeOf) { |
There was a problem hiding this comment.
I'm inclined to drop the && Object.setPrototypeOf check in favor of this:
if (useSetPrototypeOf) {
if ('function' != typeof Object.setPrototypeOf) {
throw new Error('Object.setPrototypeOf function is not defined.')
}The useSetPrototypeOf parameter is basically an assertion that Object.setPrototypeOf should be available.
If it isn't, this code should fail with an exception.
There was a problem hiding this comment.
We can't do that. Because then IE9 won't work at all. IE9 inheritance still works without setPrototypeOf - just not for static methods.
This is a well known restriction and developers are used to this. See https://babeljs.io/docs/usage/caveats/#internet-explorer
There was a problem hiding this comment.
When I suggested the extra argument for $jscomp.inherits() I believed this to be true, which may be false:
- All browsers that support all ES5 features also support
setPrototypeOf(or__proto__which allows our polyfill to work) despite the fact that those aren't actually defined in ES5.
FWIW, MDN says that IE11 was the earliest IE version to support them. I don't know right off how to determine whether any IE < 11 should be considered ES5 compliant.
Based on that belief I was expecting we would support these scenarios.
- Developer asks for ES3 output, basically saying we cannot count on
setPrototypeOfworking. In this case we do not try to usesetPrototypeOf(), because we want to make sure the code we output has the same behavior on all browsers. - Developer asks for ES5 output, which is effectively a promise not to run our output on a browser that doesn't have full support for ES5, and by my assumption above, for
setPrototypeOf(). In this case we usesetPrototypeOf()and expect it to be there. If it isn't there, we need to let the developer know by treating that as an error.
Do you know whether my assumption is true or false?
Do you have other problems with this plan?
Thanks.
There was a problem hiding this comment.
setPrototypeOf and __proto__ weren't defined until ES2015. Ideally we could target ES5+ output for this, but I know of no way to do this.
IE 9 and 10 are considered ES5 compliant browsers. This is the crux of the difficulty.
Here's the state of things:
- ES3 out (IE 8 and lower) ->
setPrototypeOfcan't be counted on. No ES3 browser supports this. - ES5 out (IE 9-11) ->
setPrototypeOfworks in IE11, but not in IE 9 or 10.__proto__only adds in an old set of Opera browsers. - ES6 out -> If only this worked ...
I recognize the desire to protect users, but in this case it's just not possible. Static inheritance doesn't properly work in IE 9 and 10.
There was a problem hiding this comment.
@shicks
Hmmm... Sounds like this may be a first use case for environment-specific targeting.
Either we could add a new language_out value for ES5_WITH_SET_PROTOTYPE (or some much better name), or we could add a flag for "make static inheritance work, I know the results won't work right on browsers without setPrototypeOf".
There was a problem hiding this comment.
Why do we need this complexity? It only adds more features when it works. It should not break any existing code.
This is a well known restriction to JS devs. The fallback is to copy the descriptors exactly like we do now (which gives most of the support). There are only some edge cases that fail.
There was a problem hiding this comment.
I discussed this a bit with @MatrixFrog today.
I think what's needed is a --use_setprototypeof_for_inheritance option to guard this.
If that option isn't given, we pass false to $jscomp.inherits as its useSetPrototypeOf parameter.
$jscomp.inherits throws an error if the argument is true, but Object.setPrototypeOf doesn't exist.
|
@ChadKillingsworth Project Fooey:
Now, suppose we fix things so we support ES6 behavior in browsers where we can, but fall back when we can't. No problems at first for Project Fooey (probably), because they wrote their code assuming the old behavior which is still pretty much the same. Then someone on Project Fooey (or worse on some team that writes a code library Fooey uses), starts writing code that depends on the full ES6 static inheritance working. This is very likely to happen. Not everything gets tested on the older browsers. Suddenly Project Fooey sees mysterious breakages on the older browsers, and they have no idea why. |
|
As discussed, I added a compiler option to control whether |
c0c2be2 to
1946ece
Compare
|
Rebased and updated to account for the transpilation refactoring by @MatrixFrog. |
1946ece to
767093d
Compare
|
Rebased again |
| Object.setPrototypeOf(childCtor, parentCtor); | ||
| } else { | ||
| /** @constructor */ | ||
| function tempCtor() {} |
There was a problem hiding this comment.
This declaration generates an error in strict mode compilation.
[synthetic:es6/util/inherits] :61: ERROR - functions can only be declared at top level or immediately within another function in strict mode
function tempCtor() {}
^^^^^^^^^^^^^^^^^^^^^^
I will fix it by changing the line to
var tempCtor = function() {};|
FYI there will be merge conflicts with #2418 on tests. So which ever is merged first the other PR will need updated. |
|
I've imported this and resolved conflicts in the imported version. |
|
Fixed that test & realized more needed to be done to make sure internal default is to have this option off for now. Added a Running first line of submission tests now, with the "test all the things" run to happen if that passes. |
|
Any reason this can't be done with an |
|
@shicks Is there a way to default an |
|
Just had further discussion on this with @concavelenz and @shicks @ChadKillingsworth I think you'll be happy to hear that the consensus now is we should go with your original intention of just using The problem described in this comment remains. However, our justification for allowing this is that teams that do not test on older browsers must assume their code does not work there. We do need to make an implementation change to this PR, though. I've filed issue #2529 for that. |
|
I have this work done and compiler tests passing, but I want to test it locally before I push it up (since the runtime tests can't be executed externally). |
…Required for proper ES6 class static inheritance.
767093d to
f062eb2
Compare
|
This works on my project - but it needs tested on a project with older browsers (and of course the runtime tests). |
|
imported |
|
tests look mostly good so far Also added some suppression comments to eliminate compiler warnings for the I won't bother pushing these changes back to this PR's branch unless there turns out to be more to do before we submit internally. |
|
Found a few more things to fix, but nothing big. |
|
Working on an internal change to just define |
|
Definition of |
|
Still trying to submit |
Related to #2419 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161527835
|
Submit of final change is processing now. |
|
Fix submitted & should be in today's github push. |
ES6 class static inheritance requires the use of
Object.setPrototypeOf. In #2375 I tried multiple ways to work around this becausesetPrototypeOfcaries performance warnings, but all of the things I tried ended up breaking my code in non-trivial ways.Both Babel and Traceur use
setPrototypeOf.This is becoming a larger issue by the day as more and more code begins to fully take advantage of ES6 style inheritance. I cannot find any real data on the performance hit or if it perhaps it only performs worse when changing a prototype after instantiation.
If this is not acceptable inside Google than we may need to add a flag that opts Google projects out of this, but externally we need to make this change.
Polymer requires this (the code breaks without it).