Repository navigation
Supporting 'this' type #3694
Description
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)
interface Model {
extend<T>(t: T): <type of containing interface> & T;
}
interface NamedModel extends Model {
name: string;
}
declare function createNameModel(): NamedModel;
var modelWithNameAndAge = createNamedModel().extend({
age: 30
});
modelWithNameAndAge.name; //valid
modelWithNameAndAge.age; // validSafe instantiation
function f() {
this.x = 3;
}
var g = new f(); // gives type an easy way to check f's body or specify instantiated typeSafe method invocation
function f() {
this.x = 3;
}
var obj = {y: "bar", f: f};
obj.f(); // can’t easily errorWell-typed fluent APIs (also useful for cloning)
class Parent {
setBase(property, value): <whatever the child class is> {
this._secretSet(property, value);
return this;
}
}
class Child extends Parent {
name:string;
}
var c = new Child();
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:
class C {
myThis(): this { return this; }
}
var c = new C();
var d = 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:
class C {
myThis(): this { return this; }
}
class D extends C {
name: string;
}
var D = new D();
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:
class C {
newInstance(): this {
return new C(); // 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:
function f<this extends {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:
function f<this extends {x:number}>() {
this.x = 3;
this.y = 6; //error: y is not available on this
}We can also check invocation sites:
var o = {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:
interface Model {
clone(): this;
}
interface NamedModel extends Model {
name: string;
}
var t:NamedModel;
t.clone().name; // validFor this to work, 'this' refers to the containing named type. This helps eliminate ambiguities like this:
interface I {
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)
interface Model {
extend<T>(t: T): this & T;
}
interface NamedModel extends Model {
name: string;
}
declare function createNameModel(): NamedModel;
var modelWithNameAndAge = createNamedModel().extend({
age: 30
});
modelWithNameAndAge.name; //valid
modelWithNameAndAge.age; // validSafe instantiation
function f<this extends {x:number}>() {
this.x = 3;
}
var g = new f(); // gives type an easy way to check f's body or specify instantiated typeSafe method invocation
function f<this extends {x: number}>() {
this.x = 3;
}
var obj = {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)
class Parent {
setBase(property, value): this {
this._secretSet(property, value);
return this;
}
}
class Child {
name:string;
}
var c = new Child();
console.log(c.setBase("foo", 1).setBase("bar", 2").name); //valid
Activity
DanielRosenwasser commentedon Jul 1, 2015
Should methods now all implicitly gain a
thistype variable? This would prevent users from shooting themselves in the foot. For example:sophiajt commentedon Jul 1, 2015
Daniel Rosenwasser (@DanielRosenwasser) possibly? Though I suspect that won't give you what you need. If the 'this' type shows up in the type of c.method, if you pass it to a callback that forgets the 'this' piece of the type, you won't catch the error.
prabhjots commentedon Jul 1, 2015
'this' is a good idea. Also it could be applied to the 'call', 'bind' and 'apply' scenarios.
For Example:
HerringtonDarkholme commentedon Jul 3, 2015
👍 for the proposal
For the interface and class part, it seems
this typeoverlaps a lot with F-bounded polymorphism. Canthis typecover the latter?ahejlsberg commentedon Jul 4, 2015
Some questions that come to mind...
(1) Your proposal doesn't specifically say, but I assume within a class
thiswould denote a type that behaves like a subtype of the containing class (effectively like a type parameter with the current class as a constraint). That means we'd break the following code:(2) The proposal doesn't specify what the type of a method in a class is. For example:
the type of
fcurrently is() => void, but I assume it would now be<this extends C>() => void. That would ensurefcan't accidentally be called without a properthis.(3) What are the type compatibility rules for function types with
thistype arguments? For example:If we treat
thisas a type parameter, the above assignment would be allowed because we erase type parameters when checking assignment compatibility of signatures. That might in fact be the right thing to do because anything stricter would break a lot of existing code. But it is something we need to consider.DanielRosenwasser commentedon Jul 4, 2015
Anders Hejlsberg (@ahejlsberg), my comment brings up (2). From what I understand, jonathandturner responded that because of your point in (3), we lose the benefit resulting from (2) because of cases where
c.fis fed into a callback.We could instead always take
thisconstraints into account, and say that if an instance method never accessesthis, it does not receive athisconstraint. This way, we'd only break code in cases where athishandle was actually used, though, I could see it as slightly awkward to enforce this consistency.dead-claudia commentedon Jul 7, 2015
Couldn't the above use case be covered by this?
That, I believe, fits closer to the emerging syntax consensus in #229.
DanielRosenwasser commentedon Jul 7, 2015
IMPinball could you clarify which use case you're referring to?
dead-claudia commentedon Jul 7, 2015
Sorry...I meant the
thisin function types in the main proposal. It wasn't specifically about your comment...probably should have mentioned jonathandturner.Also, as for that mixin model, there's already half a dozen mixin proposals out there, but this specific version proposed in the main bug is probably not the best way to go (feels incomplete).
sophiajt commentedon Jul 7, 2015
Anders Hejlsberg (@ahejlsberg) - yeah, good to call out (1). Though I allude to it in the proposal by talking about assignment compat with 'this', I think the way you spell it out works better and gives a better intuition.
Daniel Rosenwasser (@DanielRosenwasser) - We should explore implied 'this' constraint more, and try it out on some real world code to see what impact it might have.
IMPinball - agreed. This isn't intended to be used as a complete mixin solution, rather a way of modeling extension like how some libraries (like Ember) do extensibility. For a full mixin story, you'd want the type-checking part (including something like intersection types) combined with a code generation piece.
sophiajt commentedon Jul 17, 2015
Jon (@jbondc) - do you have an example you think would cause problems?
sophiajt commentedon Jul 17, 2015
I'm not sure exactly what your semantics are here, since this isn't valid JS/TS.
A related example in valid JS should show it a bit better:
So yeah, from the original example in the writeup a subclass would have a 'this' type that reflects the shape of the class. Unfortunately, I don't think there's enough descriptive power in the type system to cover the mixin forms.
sophiajt commentedon Jul 17, 2015
(not to say there couldn't be in the future)
51 remaining items
dead-claudia commentedon Apr 8, 2016
Mohamed Hegazy (@mhegazy) I have a couple clarification questions:
func(function (x: string) { ... })triggernoImplicitThisif the callee doesn't define athistype for the argument, such as infunc(f: (x: string) => any): any)?sandersn commentedon Apr 8, 2016
Methods can also declare a this parameter:
noImplicitThisis triggered by this expressions in the body of a function. It is independent of assignability checks, which default toanyif no this type is declared.In other words, this is an implicit this error:
But this is a normal old assignability error:
dead-claudia commentedon Apr 8, 2016
Nathan Shively-Sanders (@sandersn) Thanks! 😄
vitaly-t commentedon May 9, 2016
Nathan Shively-Sanders (@sandersn) Thanks! I think the approach to declare the first function parameter as
this: myTypeis the best one.nippur72 commentedon Jun 21, 2016
IMO the syntax is a bit verbose, instead of
why not simply:
Zorgatone commentedon Jun 21, 2016
nino-porcino (@nippur72) no, that's not good. You're actually declaring a variable named this and not initializing it (undefined). The compiled code would have:
var this;in it and shoud cause a runtime errornippur72 commentedon Jul 15, 2016
has this landed in 2.0 ? I just copied this in a 2.0 project:but it tells me:Sorry, I see that the syntax is now
shelby3 commentedon Sep 10, 2016
jonathandturner wrote:
How can polymorphic
clone(): thisbe implemented if the above is not allowed? A class whichimplementsthe interface or abstractclone()method, could be extended by another class. It seems the compiler will need to deduce that inheritance fromCtypesnewInstance()as an abstract method even though the base has an implementation, so as to insure that each subclass provides a unique implementation.Don't you mean
implementsinstead ofextends, sincefis not allowed to add new member properties?I presume
new o.myMethod()is legal.jonathandturner wrote:
Google SoundScript's presentation says on page 24, "method extraction only allowed when
this : any".Anders Hejlsberg (@ahejlsberg) wrote:
That seems to be a nice generalization of the aforementioned plan for Google's SoundScript.
That would break the aforementioned soundness expected by Daniel Rosenwasser (@DanielRosenwasser), which I see he pointed out.
Why are you inferring the type of
x: thisinstead ofx: C?yhaskell commentedon Oct 5, 2016
Can we also propagate this to work with static functions?
Suppose, we have following code:
Can we add a possibility to type it correctly?
HerringtonDarkholme commentedon Oct 5, 2016
Of course you can. TypeScript has very advanced type system now.
yhaskell commentedon Oct 5, 2016
That looks like very dirty hack, although it works.
marcusjwhelan commentedon Apr 18, 2017
Is it possible to get the Type of my class I am currently working in? for example
basarat commentedon Apr 18, 2017
Marcus (@marcusjwhelan) I think you want:
https://egghead.io/lessons/typescript-create-a-fluent-api-using-typescript-classes 🌹
marcusjwhelan commentedon Apr 18, 2017
Basarat Ali Syed (@basarat) ah thank you for the quick reply. Although I actually figured out that it was fault code on my part. The reason I could not use the class as a type was an issue with me assigning the variable that was to be returned as the parent type, copy paste error.