You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Return value of super() calls not used for this #7574
If a constructor returns a value other than this, then in subclasses super() calls to that constructor should use the result as this in the subclass constructor.
If you return an object from a constructor like that, you're throwing away the subclass prototype. So it doesn't make sense to make a super() call to such a constructor - if it worked you'd end up with new Bar() creating an object that doesn't have any methods of Bar.
if it worked you'd end up with new Bar() creating an object that doesn't have any methods of Bar.
Jeff McAffer (@jeffmcaffer) This isn't necessarily true, new Bar() can return a Bar even if it's not returning this. It can easily type check - this has nothing to do with types. The return type of super() should be the return type of new in the super constructors interface.
Mohamed Hegazy (@mhegazy) I'd like to point out that Typescript is in direct violation of the ECMAScript spec, and because of this you won't be able to write Custom Elements in Typescript compiled to ES5*. I hope that if this behavior is "By Design" that the design can still be changed so that Typescript correctly implements ES2016 and is actually a superset rather a separate language.
*Targeting ES2015 should probably work, because ES2015 just works this way.
The native implementations of Custom Elements will return something that needs to become this from the HTMLElement constructor.
Separately, the initial polyfill code I'm writing looks something like this:
window.HTMLElement=function(){if(_newInstance){vari=_newInstance;_newInstance=null;_newTagName=null;returni;}vartagName=// some stuff to emulate new.targetreturndocument.createElement(tagName);}
Compiling to var _this = _super.call(this); isn't exactly accurate. Note step 13.a of 9.2.2. The return value of the constructor is only used if the returning value is an ECMAScript object.
Any news here? Chrome, Safari, and the Web Components polyfills are all making progress on implementing custom elements v1 APIs, which rely on spec-compliant SuperCall behavior.
One issue I'm seeing now is that even if you set the value of this in the constructor to the potential return value of super(), the prototype in the current class gets ignored. That means any methods you define in a derived class get ignored.
That would also be surprising for a JavaScript user, so I'd recommend that people don't do it :)
More seriously, TypeScript could catch this:
classA{constructor(){return{a: 10};// OK, because {a: number} is assignable to A}}classBextendsA{foo(){returnthis.a;// Warning: {a: number} not assignable to B}}newB().foo()
Again, the most common use-case for this behavior is to return an instance of the class with the "interesting" constructor. For browsers, the HTMLElement constructor returns a non-this instance of HTMLElement during parsing.
Since return something other that this does work in TypeScript when targeting ES6, what's the behavior there? Can that just be matched?
My original comment wasn't about the fact that a wasn't declared on A, but rather that new B().foo won't be defined.
TypeScript currently can't handle that behavior at the type level. There's no way to know from a construct signature whether a super() call is going to make a new this value whose prototype chain doesn't use the prototype of the current class (meaning that any methods defined on the current class will get ignored).
So people might extend from HTMLElement, and define methods thinking that everything is okay. But their methods will get ignored, and they'll be confused, and ask why TypeScript didn't warn them about this issue.
In fact, from reading this page I'm not sure how createdCallback, attachedCallback, etc. are supposed to be defined if the prototype of the current class isn't used following a super calls of things like HTMLElement. Maybe it is used and then an HTMLElement is returned?
There's definitely more to this than I currently understand. Any chance you can elaborate on that last part Justin Fagnani (@justinfagnani)?
In fact, from reading this page I'm not sure how createdCallback, attachedCallback, etc. are supposed to be defined if the prototype of the current class isn't used following a super calls of things like HTMLElement.
The custom element spec specially makes the result of new MyElement() have a MyElement prototype. That's why there's an explicit "register the function MyElement to produce <my-element> elements" step so that document.createElement("my-element") can return a MyElement.
In the general case of constructors returning new objects, the returned object does not have the prototype chain set to the constructor.
// the element instance has already been created, and the definition looked up_upgradeElement(element,definition,callConstructor){constprototype=definition.constructor.prototype;element.__proto__=prototype;// 3. Set the prototypeif(callConstructor){this._setNewInstance(element);// 4. Store the element in a globalnew(definition.constructor)();// 5. Invoke constructorelement['_upgradedProp']=true;console.assert(this._newInstance==null);}
Inside HTMLElement's constructor we have:
if(customElements._newInstance){consti=customElements._newInstance;customElements._newInstance=null;returni;// 6. Return the element from the global}
Even though in the general case an object returned from a constructor might not have the correct prototype chain, in this critical case it will.
Activity
justinfagnani commentedon Mar 18, 2016
Looking at the Typescript spec, it seems like there's an explicit incompatibility between section 4.9.1 and ECMA-262 12.3.5.1
Specifically the line "The type of a super call expression is Void": https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#4.9.1
jeffreymorlan commentedon Mar 18, 2016
If you
returnan object from a constructor like that, you're throwing away the subclass prototype. So it doesn't make sense to make a super() call to such a constructor - if it worked you'd end up withnew Bar()creating an object that doesn't have any methods ofBar.justinfagnani commentedon Mar 18, 2016
Jeff McAffer (@jeffmcaffer) This isn't necessarily true,
new Bar()can return aBareven if it's not returningthis. It can easily type check - this has nothing to do with types. The return type ofsuper()should be the return type ofnewin the super constructors interface.For instance, this should fully type check:
Mohamed Hegazy (@mhegazy) I'd like to point out that Typescript is in direct violation of the ECMAScript spec, and because of this you won't be able to write Custom Elements in Typescript compiled to ES5*. I hope that if this behavior is "By Design" that the design can still be changed so that Typescript correctly implements ES2016 and is actually a superset rather a separate language.
*Targeting ES2015 should probably work, because ES2015 just works this way.
mhegazy commentedon Mar 18, 2016
can you elaborate?
justinfagnani commentedon Mar 18, 2016
Here's the spec for the callable HTMLElement constructor: https://w3c.github.io/webcomponents/spec/custom/#htmlelement-constructor
The relevant parts are step 5-10
The native implementations of Custom Elements will return something that needs to become
thisfrom theHTMLElementconstructor.Separately, the initial polyfill code I'm writing looks something like this:
(actual code here: https://github.com/webcomponents/webcomponentsjs/blob/v1/src/CustomElements/v1/CustomElements.js#L48 )
Test of Typescript ES5 output that fails: https://github.com/webcomponents/webcomponentsjs/blob/v1/tests/CustomElements/v1/js/typescript.js#L15
Corresponding test of Babel output that passes: https://github.com/webcomponents/webcomponentsjs/blob/v1/tests/CustomElements/v1/js/babel.js#L15
sebmck commentedon Mar 21, 2016
Compiling to
var _this = _super.call(this);isn't exactly accurate. Note step 13.a of 9.2.2. The return value of the constructor is only used if the returning value is an ECMAScript object.falsandtru commentedon Apr 19, 2016
Simplified another case:
This code returns a function on chrome, firefox, and edge, but TypeScript is not.
justinfagnani commentedon May 29, 2016
Any news here? Chrome, Safari, and the Web Components polyfills are all making progress on implementing custom elements v1 APIs, which rely on spec-compliant SuperCall behavior.
mhegazy commentedon Jun 1, 2016
Daniel Rosenwasser (@DanielRosenwasser) should be presenting this again and investigating issues we need to address.
supermoos commentedon Jul 11, 2016
Has this been fixed for TypeScript 2.0@beta? Would really like to use TypeScript with Custom Elements V1.
justinfagnani commentedon Jul 12, 2016
Lasse Moos (@supermoos) if you target ES6 it works fine. Then, if you need, you can compile to ES5 with Babel.
supermoos commentedon Jul 12, 2016
Justin Fagnani (@justinfagnani) oh yeah, you're right. I tried the build from this readme.md:
https://github.com/webcomponents/webcomponentsjs/tree/v1/src/CustomElements/v1 - it work's with extending HTMLElement, but if I try HTMLButtonElement I get the Illegal constructor error. Also, the constructor seems to be fired twice with this code:
I know it's a 14 days old build, just wanted to put it out there :-)
DanielRosenwasser commentedon Sep 4, 2016
One issue I'm seeing now is that even if you set the value of
thisin the constructor to the potential return value ofsuper(), the prototype in the current class gets ignored. That means any methods you define in a derived class get ignored.I think that behavior would be very surprising for TypeScript users, but I don't know of a workaround for that.
justinfagnani commentedon Sep 4, 2016
That would also be surprising for a JavaScript user, so I'd recommend that people don't do it :)
More seriously, TypeScript could catch this:
Again, the most common use-case for this behavior is to return an instance of the class with the "interesting" constructor. For browsers, the HTMLElement constructor returns a non-this instance of HTMLElement during parsing.
Since return something other that
thisdoes work in TypeScript when targeting ES6, what's the behavior there? Can that just be matched?DanielRosenwasser commentedon Sep 4, 2016
My original comment wasn't about the fact that
awasn't declared onA, but rather thatnew B().foowon't be defined.TypeScript currently can't handle that behavior at the type level. There's no way to know from a construct signature whether a
super()call is going to make a newthisvalue whose prototype chain doesn't use the prototype of the current class (meaning that any methods defined on the current class will get ignored).So people might extend from
HTMLElement, and define methods thinking that everything is okay. But their methods will get ignored, and they'll be confused, and ask why TypeScript didn't warn them about this issue.In fact, from reading this page I'm not sure how
createdCallback,attachedCallback, etc. are supposed to be defined if the prototype of the current class isn't used following a super calls of things likeHTMLElement. Maybe it is used and then anHTMLElementis returned?There's definitely more to this than I currently understand. Any chance you can elaborate on that last part Justin Fagnani (@justinfagnani)?
Arnavion commentedon Sep 4, 2016
The custom element spec specially makes the result of
new MyElement()have aMyElementprototype. That's why there's an explicit "register the functionMyElementto produce<my-element>elements" step so thatdocument.createElement("my-element")can return aMyElement.In the general case of constructors returning new objects, the returned object does not have the prototype chain set to the constructor.
justinfagnani commentedon Sep 5, 2016
Arnav Singh (@Arnavion) is correct. In the HTML spec, parser-created elements work like this:
super()intoHTMLElementwhichYou can see the same steps in the polyfill here: https://github.com/webcomponents/webcomponentsjs/blob/v1/src/CustomElements/v1/CustomElements.js#L568
The important bits with some added comments:
Inside HTMLElement's constructor we have:
Even though in the general case an object returned from a constructor might not have the correct prototype chain, in this critical case it will.