Background
We've had a few requests to support this typing (like #229). This proposal addresses the use cases for a function 'this' type, a class 'this' type, and a corresponding feature for interfaces.
Motivating Examples
Extension (lightweight mixin)
interfaceModel{extend<T>(t: T): <typeofcontaininginterface>&T;}interfaceNamedModelextendsModel{name: string;}declarefunctioncreateNameModel(): NamedModel;varmodelWithNameAndAge=createNamedModel().extend({age: 30});modelWithNameAndAge.name;//validmodelWithNameAndAge.age;// validSafe instantiation
functionf(){this.x=3;}varg=newf();// gives type an easy way to check f's body or specify instantiated typeSafe method invocation
functionf(){this.x=3;}varobj={y: "bar",f: f};obj.f();// can’t easily errorWell-typed fluent APIs (also useful for cloning)
classParent{setBase(property,value): <whateverthechildclass is>{this._secretSet(property,value);returnthis;}}classChildextendsParent{name:string;}varc=newChild();console.log(c.setBase("foo",1).setBase("bar",2").name);//validDesign
To support the motivating examples, we introduce a 'this' type. The 'this' type should follow the intuition of the developer. When used with classes, 'this' type refers to the type of the class it finds itself in. With functions, 'this' allows you to further document how the function will be used as a constructor and in what context it can be invoked while in an object.
Classes and 'this'
A class that uses a 'this' is referring to the containing named class. In the simplest example:
classC{myThis(): this {returnthis;}}varc=newC();vard=c.myThis();We trivially map the 'this' type in the invocation of myThis() to the C type, giving 'd' type C.
The type of 'this' will follow with subclasses. A subclass sees any 'this' in the type of its base class as its own type. This allows more a fluent API, as in this example:
classC{myThis(): this {returnthis;}}classDextendsC{name: string;}varD=newD();d.myThis().name="Joe";//validFor this to work correctly, only 'this' or something that resolves to 'this' can be used. Something which looks like it should work correctly, but can't work safely is this example:
classC{newInstance(): this {returnnewC();// error, 'this' only works with this expressions}}Functions and 'this'
Functions gain the ability to talk about the shape of the 'this' pointer that is visible in the function body. The design here leverages the type variables of the function to describe what the shape of 'this' has:
functionf<thisextends{x:number}>(){this.x=3;}This is fairly readable, and we could use syntax coloring/tooling to help signify that the 'this' here is a special type variable that is implied by all functions.
Once the type of 'this' is described for functions, we can check the inside of the function body, where this is used:
functionf<thisextends{x:number}>(){this.x=3;this.y=6;//error: y is not available on this}We can also check invocation sites:
varo={myMethod: f,y: "bob"};o.myMethod();//error: o does not match the shape of 'this' for 'myMethod'We may even want to error on the assignment when object is first created instead of the invocation site.
Interfaces and 'this'
Similarly to classes, interfaces currently lack the the ability for a type to refer to itself. While an interfaces can refer to itself by name, this limits the ability of interfaces that extend the original interface. Here, we introduce 'this' as a way for interfaces to do this:
interfaceModel{clone(): this;}interfaceNamedModelextendsModel{name: string;}vart:NamedModel;t.clone().name;// validFor this to work, 'this' refers to the containing named type. This helps eliminate ambiguities like this:
interfaceI{obj: {myself: this;name: string};}In this example, 'this' refers to I rather than the object literal type. It's trivial to refactor the object literal type out of the class so that you can describe a 'this' that instead binds to the object literal itself.
Motivating examples (Redux)
In this section, we re-write the motivating examples using the proposed functionality.
Extension (lightweight mixin)
interfaceModel{extend<T>(t: T): this &T;}interfaceNamedModelextendsModel{name: string;}declarefunctioncreateNameModel(): NamedModel;varmodelWithNameAndAge=createNamedModel().extend({age: 30});modelWithNameAndAge.name;//validmodelWithNameAndAge.age;// validSafe instantiation
functionf<thisextends{x:number}>(){this.x=3;}varg=newf();// gives type an easy way to check f's body or specify instantiated typeSafe method invocation
functionf<thisextends{x: number}>(){this.x=3;}varobj={y: "bar",f: f};obj.f();// error: f can not be invoked on obj, missing {x: number}Well-typed fluent APIs (also useful for cloning)
classParent{setBase(property,value): this {this._secretSet(property,value);returnthis;}}classChild{name:string;}varc=newChild();console.log(c.setBase("foo",1).setBase("bar",2").name);//valid
Background
We've had a few requests to support this typing (like #229). This proposal addresses the use cases for a function 'this' type, a class 'this' type, and a corresponding feature for interfaces.
Motivating Examples
Extension (lightweight mixin)
Safe instantiation
Safe method invocation
Well-typed fluent APIs (also useful for cloning)
Design
To support the motivating examples, we introduce a 'this' type. The 'this' type should follow the intuition of the developer. When used with classes, 'this' type refers to the type of the class it finds itself in. With functions, 'this' allows you to further document how the function will be used as a constructor and in what context it can be invoked while in an object.
Classes and 'this'
A class that uses a 'this' is referring to the containing named class. In the simplest example:
We trivially map the 'this' type in the invocation of myThis() to the C type, giving 'd' type C.
The type of 'this' will follow with subclasses. A subclass sees any 'this' in the type of its base class as its own type. This allows more a fluent API, as in this example:
For this to work correctly, only 'this' or something that resolves to 'this' can be used. Something which looks like it should work correctly, but can't work safely is this example:
Functions and 'this'
Functions gain the ability to talk about the shape of the 'this' pointer that is visible in the function body. The design here leverages the type variables of the function to describe what the shape of 'this' has:
This is fairly readable, and we could use syntax coloring/tooling to help signify that the 'this' here is a special type variable that is implied by all functions.
Once the type of 'this' is described for functions, we can check the inside of the function body, where this is used:
We can also check invocation sites:
We may even want to error on the assignment when object is first created instead of the invocation site.
Interfaces and 'this'
Similarly to classes, interfaces currently lack the the ability for a type to refer to itself. While an interfaces can refer to itself by name, this limits the ability of interfaces that extend the original interface. Here, we introduce 'this' as a way for interfaces to do this:
For this to work, 'this' refers to the containing named type. This helps eliminate ambiguities like this:
In this example, 'this' refers to I rather than the object literal type. It's trivial to refactor the object literal type out of the class so that you can describe a 'this' that instead binds to the object literal itself.
Motivating examples (Redux)
In this section, we re-write the motivating examples using the proposed functionality.
Extension (lightweight mixin)
Safe instantiation
Safe method invocation
Well-typed fluent APIs (also useful for cloning)