-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Supporting 'this' type #3694
Comments
Should methods now all implicitly gain a class C {
prop: number;
method() {
return this.x;
}
}
let c = new C();
let removedMethodHandle = c.method;
// the following would error
console.log(removedMethodHandle()); |
@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. |
'this' is a good idea. Also it could be applied to the 'call', 'bind' and 'apply' scenarios. For Example: class A {
name: string;
setName<this extends { name: string;}>(val:string) {
this.name = val;
}
}
class B {
somethingElse: string;
}
class C { name: string; }
var setName = new A().setName;
var b = new b();
var c = new C();
setName.apply(b, ["hello"]); //error
setName.call(b, "hello"); //error
setName.bind(b)("hello"); //error
setName.apply(c, ["hello"]); //ok
setName.call(c, "hello"); //ok
setName.bind(c)("hello"); //ok |
👍 for the proposal For the interface and class part, it seems |
Some questions that come to mind... (1) Your proposal doesn't specifically say, but I assume within a class class C {
foo() {
var x = this;
x = new C();
}
} (2) The proposal doesn't specify what the type of a method in a class is. For example: class C {
f() { }
}
var c = new C();
var f = c.f; // Ideally of type <this extends C>() => void
f(); // Should be an error the type of (3) What are the type compatibility rules for function types with var c = new C();
var f: () => void = c.f; // Is this allowed? If we treat |
@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 We could instead always take |
Couldn't the above use case be covered by this? interface Foo {
bar(this: Foo & Bar): void;
}
interface Bar {
bar(this: Foo | Bar): void
} That, I believe, fits closer to the emerging syntax consensus in #229. |
@IMPinball could you clarify which use case you're referring to? |
Sorry...I meant the 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). |
@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. @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. |
@jbondc - do you have an example you think would cause problems? |
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: class A extends mixin(B, C, D) {
me(): this { return this } // includes me(), b(), c(), d, e?
} 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. |
(not to say there couldn't be in the future) |
This also allows classical inheritance to be modeled
|
I'm not sure what you're talking about, as far as I understand, none of those languages has self-typing.
Yes, because this is currently under proposal. |
The equivalent doesn't fail with Java 6 or later, correct? It's more about On Sat, Jul 18, 2015, 14:27 Daniel Rosenwasser [email protected]
|
No, it fails in Java 8. |
class A {
foo(): this { return this }
}
class B extends A {
bar(): this { return this }
}
new B().foo().bar() It seems to me that this should always fail to compile. Foo returns |
While it is difficult to figure out at compile time, it is what is actually happening at runtime. Because class A {
foo() { return this }
}
class B extends A {
bar() { return this }
}
new B().foo().bar(); |
Okay. I'll take back most of my claims about other languages. I didn't realize that they also had the same problem. I'll take another approach: could TypeScript finally get this right? It's runtime behavior that should also be possible to infer statically, without a lot of trouble. |
@MgSam yup, it's what @kitsonk says. There's a difference here between 'this' type and returning the class type. Your example without the 'this' type would throw the error: class A {
foo(): A { return this }
}
class B extends A {
bar(): B { return this }
}
new B().foo().bar() // error Property 'bar' does not exist on type 'A'. The 'this' typing is closer to what's happening at runtime, which is that B's instance is what is available after the new call. |
I might point out that supporting the " It even looks like there are plans to support the bind operator in TypeScript, judging by the looks of 8521632. |
@mhegazy I have a couple clarification questions:
|
In other words, this is an implicit this error: function f(n: number) {
return this.m + n; // error -- this: any
} But this is a normal old assignability error: function f(this: void) { ... }
class C {
m(this: this) { ... }
}
let c: C;
c.m = f; // error! |
@sandersn Thanks! 😄 |
@sandersn Thanks! I think the approach to declare the first function parameter as |
IMO the syntax is a bit verbose, instead of function f<this extends {x:number}>() {
this.x = 3;
} why not simply: function f() {
let this: {x:number};
this.x = 3;
} |
@nippur72 no, that's not good. You're actually declaring a variable named this and not initializing it (undefined). The compiled code would have: |
function f<this extends {x:number}>() {
this.x = 3;
}
Sorry, I see that the syntax is now function f(this:{x:number}>() {
this.x = 3;
} |
How can polymorphic
Don't you mean
I presume
Google SoundScript's presentation says on page 24, "method extraction only allowed when
That seems to be a nice generalization of the aforementioned plan for Google's SoundScript.
That would break the aforementioned soundness expected by @DanielRosenwasser, which I see he pointed out.
Why are you inferring the type of |
Can we also propagate this to work with static functions? Suppose, we have following code: class C {
constructor() { console.log("C"); }
static foo() { return new this(); }
}
class D extends C {
constructor() {
super();
console.log("D");
}
bar() { return 42; }
}
var d = D.foo(); // valid and works. Will output C and then D
console.log(d instanceof D); // true
console.log((<D>d).bar()); // 42 Can we add a possibility to type it correctly? |
Of course you can. TypeScript has very advanced type system now. class C {
static foo = function <D extends C>(this: {new(): D}) {return new this()}
}
class D extends C {
bar() { return 42}
}
var d = D.foo()
d.bar() |
That looks like very dirty hack, although it works. |
Is it possible to get the Type of my class I am currently working in? for example export class A{
// constructor + methods
// can use `A` as a return Type
}
export class B extends A{
public b:any = this;
// here in my methods if I use a return type of `B` it implies class `A`type
// how do I use `B` as a type?
fun():B {
const self = this.b;
let hmm:B; // Variable in question
// Logic to fill variable hmm with self like item
return hmm; // Error: Type 'A' is not assignable to type 'B' , property 'some class var' is not in type 'A.
}
} |
@marcusjwhelan I think you want: export class A {
}
export class B extends A {
fun(): this {
return this;
}
} https://egghead.io/lessons/typescript-create-a-fluent-api-using-typescript-classes 🌹 |
@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. |
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)
The text was updated successfully, but these errors were encountered: