Repository navigation
Return value of super() calls not used for this #7574
Description
Activity
justinfagnani commented
on Mar 18, 2016 AuthorMore actionsLooking 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
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.- addedBy DesignDeprecated - use "Working as Intended" or "Design Limitation" insteadDeprecated - use "Working as Intended" or "Design Limitation" instead
on Mar 18, 2016 justinfagnani commented
on Mar 18, 2016 AuthorMore actionsif it worked you'd end up with
new Bar()creating an object that doesn't have any methods ofBar.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:
class A { static _singleton : A; constructor() : A { if (!_singleton) { _singleton = this; } return _singleton; } }
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.
Reacted by Bradley Ayersand because of this you won't be able to write Custom Elements in Typescript compiled to ES5*.
can you elaborate?
justinfagnani commented
on Mar 18, 2016 AuthorMore actionsHere's the spec for the callable HTMLElement constructor: https://w3c.github.io/webcomponents/spec/custom/#htmlelement-constructor
The relevant parts are step 5-10
- Let instance be...
- Return instance
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:
window.HTMLElement = function() { if (_newInstance) { var i = _newInstance; _newInstance = null; _newTagName = null; return i; } var tagName = // some stuff to emulate new.target return document.createElement(tagName); }
(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
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.- addedNeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.ES6Relates to the ES6 SpecRelates to the ES6 SpecSuggestionAn idea for TypeScriptAn idea for TypeScriptand removedBy DesignDeprecated - use "Working as Intended" or "Design Limitation" insteadDeprecated - use "Working as Intended" or "Design Limitation" instead
on Mar 21, 2016 Simplified another case:
new class extends class { constructor() { return function () {}; } } { constructor() { super(); } }
This code returns a function on chrome, firefox, and edge, but TypeScript is not.
justinfagnani commented
on May 29, 2016 AuthorMore actionsAny 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.
Daniel Rosenwasser (@DanielRosenwasser) should be presenting this again and investigating issues we need to address.
Has this been fixed for TypeScript 2.0@beta? Would really like to use TypeScript with Custom Elements V1.
justinfagnani commented
on Jul 12, 2016 AuthorMore actionsLasse Moos (@supermoos) if you target ES6 it works fine. Then, if you need, you can compile to ES5 with Babel.
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:this.testButton = new ButtonTest(); document.body.appendChild(this.testButton);I know it's a 14 days old build, just wanted to put it out there :-)
- removedNeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.
on Aug 6, 2016 DanielRosenwasser commented
on Sep 4, 2016 MemberMore actionsOne 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.class A { constructor() { return { a: 10 }; } } class B extends A { foo() { return this.a; } } new B().foo()
> Uncaught TypeError: (intermediate value).foo is not a functionI think that behavior would be very surprising for TypeScript users, but I don't know of a workaround for that.
justinfagnani commented
on Sep 4, 2016 AuthorMore actionsThat would also be surprising for a JavaScript user, so I'd recommend that people don't do it :)
More seriously, TypeScript could catch this:
class A { constructor() { return { a: 10 }; // OK, because {a: number} is assignable to A } } class B extends A { foo() { return this.a; // Warning: {a: number} not assignable to B } } new B().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
thisdoes work in TypeScript when targeting ES6, what's the behavior there? Can that just be matched?DanielRosenwasser commented
on Sep 4, 2016 MemberMore actionsMy 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)?
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.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 commented
on Sep 5, 2016 AuthorMore actionsArnav Singh (@Arnavion) is correct. In the HTML spec, parser-created elements work like this:
- Create an element object
- Look up its constructor by tagname
- Set the prototype of the element to the constructors .prototype
- Store the element in a global
- Invoke the constructor to initialize it, which will eventually
super()intoHTMLElementwhich - Returns the element from the global
- The constructor chain runs, initializing the element
You 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:
// the element instance has already been created, and the definition looked up _upgradeElement(element, definition, callConstructor) { const prototype = definition.constructor.prototype; element.__proto__ = prototype; // 3. Set the prototype if (callConstructor) { this._setNewInstance(element); // 4. Store the element in a global new (definition.constructor)(); // 5. Invoke constructor element['_upgradedProp'] = true; console.assert(this._newInstance == null); }
Inside HTMLElement's constructor we have:
if (customElements._newInstance) { const i = customElements._newInstance; customElements._newInstance = null; return i; // 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.
- addedFixedA PR has been merged for this issueA PR has been merged for this issue
on Sep 30, 2016 - addedBugA bug in TypeScriptA bug in TypeScriptand removedSuggestionAn idea for TypeScriptAn idea for TypeScript
on Sep 30, 2016 - locked and limited conversation to collaborators
on Jun 19, 2018
If a constructor returns a value other than
this, then in subclassessuper()calls to that constructor should use the result asthisin the subclass constructor.I believe this is specified in 12.3.5.1, step 10 of SuperCall: http://www.ecma-international.org/ecma-262/6.0/index.html#sec-super-keyword
Getting this behavior correct is going to be very important for supporting Custom Elements, which takes advantage of this to initialize browser-allocated elements with user-written constructors. See https://w3c.github.io/webcomponents/spec/custom/#htmlelement-constructor
Note, Babel 6 implements this correctly.
TypeScript Version:
1.8.9
Code
Expected behavior:
No assertion error
The constructor for
Barshould compile to this:Actual behavior:
Assertion error
The constructor for Bar compiles to this: