Uh oh!
There was an error while loading. Please reload this page.
Polymorphic 'this' type - #4910
Conversation
Anders Hejlsberg (ahejlsberg)
commented
Sep 22, 2015
Jon (@jbondc) Agreed, it might be good to show which class a particular |
Deleted user (ghost)
commented
Sep 22, 2015
👏 |
Conflicts: src/compiler/diagnosticInformationMap.generated.ts
There was a problem hiding this comment.
How does this handle the case where an interface extends a class that uses this ?
There was a problem hiding this comment.
If one of the base types is a class (i.e. not an interface) we simply return true (because every class has a this type). We only return false from this function if the interface itself doesn't reference this, if every base type is an interface, and if no base interface has a this type.
Ryan Cavanaugh (RyanCavanaugh)
commented
Sep 22, 2015
Need to un-comment the fourslash tests that were edited |
Anders Hejlsberg (ahejlsberg)
commented
Sep 22, 2015
Ryan Cavanaugh (@RyanCavanaugh) Yes, Mohamed Hegazy (@mhegazy) is looking at fixes for the failing fourslash tests. |
There was a problem hiding this comment.
Ideally these should not fail right? But only on instantiation.
There was a problem hiding this comment.
No, they should fail. The first declaration infers type this for v, the second declaration specifies type MyGenericTestClass<T, U> for v. Those are not identical types, so error.
Anders Hejlsberg (@ahejlsberg) I spoke with Ryan Cavanaugh (@RyanCavanaugh), Wesley Wigham (@weswigham), and Dan Quirk (@danquirk) about this, and I think there are some issues with the meaning of interfaceThing{getResource(): {methodA(): this;methodB(): this;}}In the current implementation, For either one semantics of the However, if interfaceThing{getResource<OwnerTextendsThing>(): {a(): OwnerT;b(): this;}}I think that we should go with the semantics that most-closely matches the runtime: in this case, the |
Anders Hejlsberg (ahejlsberg)
commented
Sep 23, 2015
Daniel Rosenwasser (@DanielRosenwasser) You're incorrect about the current behavior, your example is actually an error: interfaceThing{getResource(): {methodA(): this;// ErrormethodB(): this;// Error}}The error reported is In order to reference an outer interfaceResource<T>{methodA(): T;methodB(): T;}interfaceThing{getResource(): Resource<this>;}Note, however, that you can capture the outer this in an object literal: classA{foo(){return{x: this,f: ()=>this};}}In order to write down the type inferred for the above, you'd have to resort to a temporary type: interfaceFoo<T>{x: T;f(): T;}classA{foo(): Foo<this>{return{x: this,f: ()=>this};}} |
Anders Hejlsberg (ahejlsberg)
commented
Sep 23, 2015
Jon (@jbondc) I think the |
I actually was asking others about the implementation and it was our understanding that it was allowed; sorry about that. I didn't get the chance to try the branch out yesterday.
But the .d.ts emit for that is currently not valid: declareclassA{foo(): {x: this;f: ()=> this;};}If I try to reuse that |
Anders Hejlsberg (ahejlsberg)
commented
Sep 23, 2015
Daniel Rosenwasser (@DanielRosenwasser)Mohamed Hegazy (@mhegazy) The .d.ts issue is tricky. Personally I think this is one of those situations where we should issue an error if a .d.ts is requested with guidance that a type annotation is required. |
There was a problem hiding this comment.
Maybe clarify that this is not a built-in type keyword.
Anatoly Ressin (Artazor)
commented
Sep 25, 2015
As I understand, polymorphic // interface Cloneable<T extends Cloneable<T>> {interfaceCloneable<TextendsCloneable<any>>{clone(): T;}turns it into interfaceCloneable{clone(): this;}It's cool! However It looks like that it solves only a half of a problem (ok, let it be 90% in common practices) Am I right, that the system // interface Vertex<V extends Vertex<V,E>, E extends Edge<V,E>> {interfaceVertex<VextendsVertex<any,any>,EextendsEdge<any,any>>{incoming: E[];outgoing: E[];}//interface Edge<V extends Vertex<V,E>, E extends Edge<V,E>> {interfaceEdge<VextendsVertex<any,any>,EextendsEdge<any,any>>{from: V;to: V;}classCityextendsVertex<City,Road>{constructor(publicname: string){this.incoming=[];this.outgoing=[];}}classRoadextendsEdge<City,Road>{constructor(publicdistance: number,publicfrom: City,publicto: City){this.from.outgoing.push(this);this.to.incoming.push(this);}}varLA=newCity('Los Angeles');varSF=newCity('San Francisco');varhyperloop=[newRoad(558.68,LA,SF),newRoad(558.68,SF,LA)]console.log(hyperloop[0].from.outgoing[0].to.name)// stronly typedstill could not benefit from I could imagine the following implementation // interface Vertex<E<T> extends Edge<T>> -or-interfaceVertex<EextendsEdge>{incomming: E<this>[];outgoing: E<this>;}// interface Edge<V<T> extends Vertex<T>> -or-interfaceEdge<VextendsVertex>{from: V<this>;to: V<this>;}classCityextendsVertex<Road>{ ... }classRoadextendsEdge<City>{ ... }where for high order types the constraint
Am I right? Or there is a simpler solution that I've missed? |
Anatoly Ressin (Artazor)
commented
Sep 25, 2015
I mean that |
Anders Hejlsberg (ahejlsberg)
commented
Sep 25, 2015
Anatoly Ressin (@Artazor) Correct, the polymorphic |
vilicvane (vilicvane)
commented
Oct 1, 2015
🎉 👍 |
Mitja P (mitjap)
commented
Oct 26, 2015
should this also support this syntax? |
Mohamed Hegazy (mhegazy)
commented
Oct 26, 2015
Mitja P (@mitjap): see #2225 |
Marcus (mjohnsonengr)
commented
Oct 28, 2015
This seems to cause the following issue. In the example below in the Is this expected? |
Ryan Cavanaugh (RyanCavanaugh)
commented
Oct 28, 2015
Marcus (@mjohnsonengr) this is the intended behavior. If there were a subclass of I'd recommend writing |
Marcus (mjohnsonengr)
commented
Oct 28, 2015
Nevermind, saw the referenced issue, #5056 |
Marcus (mjohnsonengr)
commented
Oct 28, 2015
Ahh, but thank your for your reply Ryan Cavanaugh (@RyanCavanaugh)! :) |
so what should I do to use this instead of state below functionNavigatableRecordCreator(defaultValues: {[key: string]: any;},name?: string){abstractclassNavigatableRecord<PextendsNavigatableRecord<P>>extendsRecord(defaultValues,name){SetValue<T>(fn: (x: NavigableObject<P>)=>NavigableObject<T>,value: T,state: P){returnthis.setIn(fn(newNavigableObject<P>(state)).getPath(),value)// can I use this instead of state here}}returnNavigatableRecord;} |
Anders Hejlsberg (@ahejlsberg)wrote:
As I wrote in #3694, how can polymorphic |

This PR implements polymorphic typing of
thisfor classes and interfaces as inspired by #3694. The new features are:thisin an expression within a non-static class or interface member is considered to be an instance of some class that derives from the containing class as opposed to simply an instance of the containing class.thiskeyword can be used in a type position within a non-static class or interface member to reference the type ofthis.thistype within the class (including those inherited from base classes) are replaced with the type itself.This feature makes patterns such as fluent interfaces and covariant return types much easier to express and implement. Languages such as C++, Java, and C# use a somewhat involved generic pattern to emulate this feature, as described here.
An example:
In a non-static member of a class or interface,
thisin a type position refers to the type ofthis. For example:Each subclass of the above
Entitywill have aclonemethod that returns an instance of the subclass, and anequalsmethod that takes another instance of the subclass.The
thistype is a subtype of and assignable to the instance type of the containing class or interface, but not vice-versa (becausethismight actually be a subclass). That is a breaking change, and certain code patterns that previously compiled may now need an extra type annotation:The example above now errors because the inferred return type of
getInstanceinAisthisand the inferred type ofgetInstanceinBisB, which is not assignable tothis. The fix is to add a return type annotation forgetInstanceinA.The polymorphic
thistype is implemented by providing every class and interface with an implied type parameter that is constrained to the containing type itself (except when the compiler can can tell thatthisis never referenced within the class or interface). That in particular turns a lot of previously non-generic classes into generic equivalents, causing more symbols and types to be created due to generic instantiation. The observed cost in batch compile time ranges from 0% in code that uses no classes to 6-7% in very class-heavy code.Note that this PR specifically doesn't aim to implement other parts of #3694 such as
thistype annotations for functions. Those will be covered by other PRs if we choose to implement them.