Supporting 'this' type
sophiajt opened this issue Β· 73 comments
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); //validShould methods now all implicitly gain a this type variable? This would prevent users from shooting themselves in the foot. For example:
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 this type overlaps a lot with F-bounded polymorphism. Can this type cover the latter?
Some questions that come to mind...
(1) Your proposal doesn't specifically say, but I assume within a class this would 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:
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 errorthe type of f currently is () => void, but I assume it would now be <this extends C>() => void. That would ensure f can't accidentally be called without a proper this.
(3) What are the type compatibility rules for function types with this type arguments? For example:
var c = new C();
var f: () => void = c.f; // Is this allowed?If we treat this as 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.
@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.f is fed into a callback.
We could instead always take this constraints into account, and say that if an instance method never accesses this, it does not receive a this constraint. This way, we'd only break code in cases where a this handle was actually used, though, I could see it as slightly awkward to enforce this consistency.
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 this in 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).
@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.
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
βββ correctly :star::star::star:.
It's not just a mixin problem.
(Yes, that amount of emphasis is necessary. I know of no other type system that fails at this, not even Java, C++, or C#. To me, the current behavior exposes a broken OO type system, and really is a bug in of itself.)
It's required to type libraries with deep object hierarchies. One such example, Lazy.js, cannot be typed without full type inheritance of delegated methods. It uses inheritance very frequently, with a very deep inheritance hierarchy. Its API uses a relatively deep hierarchy (although I also still need a way to override the return type of an inherited method for a couple specific cases in its API, namely the async equivalents of each). Lodash has a similar problem. Another example is the following code:
class A {
foo(): this { return this }
}
class B extends A {
bar(): this { return this }
}
new B().foo().bar()If you change the above to this, it will fail to compile. That is surprising behavior for anyone used to most other static OO type systems.
class A {
foo(): A { return this }
}
class B extends A {
bar(): B { return this }
}
new B().foo().bar() // Property 'bar' does not exist on type 'A'.This is kinda required for a few too many things.
I know of no other type system that fails at this, not even Java, C++, or C#
...
That is surprising behavior for anyone used to most other static OO type systems.
I'm not sure what you're talking about, as far as I understand, none of those languages has self-typing.
If you change the above to this, it will fail to compile.
Yes, because this is currently under proposal.
The equivalent doesn't fail with Java 6 or later, correct? It's more about
the B still being a B, after being returned from an A.
On Sat, Jul 18, 2015, 14:27 Daniel Rosenwasser notifications@github.com
wrote:
I know of no other type system that fails at this, not even Java, C++, or
C#
...
That is surprising behavior for anyone used to most other static OO type
systems.I'm not sure what you're talking about, as far as I understand, none of
those languages has self-typing.If you change the above to this, it will fail to compile.
Yes, because this is currently under proposal.
β
Reply to this email directly or view it on GitHub
#3694 (comment)
.
The equivalent doesn't fail with Java 6 or later, correct?
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 this, which is A. Why should parent class A know anything about B's method bar?
It seems to me that this should always fail to compile. Foo returns this, which is A. Why should parent class A know anything about B's method bar?
While it is difficult to figure out at compile time, it is what is actually happening at runtime. Because this is simply a reference to the instance, means at runtime, the last statement is valid. So the argument is why couldn't TypeScript support this. This is valid ES6:
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 "this" type will be super helpful when using the bind operator.
It even looks like there are plans to support the bind operator in TypeScript, judging by the looks of 8521632.
+1 +1 +1 I just came across this issue working with decorators, and was trying to do stuff like this to solve the problem:
function bar(): void {
declare this: Foo;
}But after reading the proposals, now I like this:
function bar<this: Foo>(): void {}over this:
function bar(this: Foo): void;or this:
function bar<this extends Foo>(): void {}@MgSam Why should that example fail to compile? It's a perfectly valid use of chaining. Here's something more concrete:
/// <reference path="../typings/angularjs/angular.d.ts" />
interface CachingModule extends angular.IModule {
cache(object: Object): CachingModule;
}
var m:CachingModule;
m.cache({}).constant("foo", "bar"); //valid, but
m.constant("foo", "bar").cache({}); //errorclass Example {
func(): this;
static func(): this;
}- Should the static method return type be the constructor type
typeof Example? or the instance type? - If the static
thisis alternatively supported say, through,typeof thiswouldn't that be confusing? - If the
thistype is supported, how will a natural extension likethis.funchandle this ambiguity? - If the class or function is merged with a namespace, or an ambient class is merged with an interface, what will
thismean on those scenarios? - Will the
thistype of a class instance or function be also accessible from the outside? what syntax is planned for that?
Here's my current solution for this problem, although I don't like it (I needed this for a lot of interface definitions):
interface Foo<T extends Foo<any>> {
// ...
}This idiom is very common in other languages with generics/templates, which also suffer from this same problem.
// Java
class Foo<T extends Foo> {
// ...
}- It returns the type of the actual instance, much like ES6's
new.target, but statically determined. It's resolved on the caller's side, not the callee's side. - That would be redundant in any and all cases.
typeof thisandthiswould refer to the same type. Currently,typeof thisis the class type in classes, andanyin any other context, but it's not fully typed yet. - Types and values are parsed in different contexts, and are kept in independent namespaces. For this reason, you can still have a variable named
string. - See 1.
- Highly doubt it. The function itself has no clue what
thisrepresents in it unless it's constrained (methods'thistypes are already implicitly constrained to their containing class) or explicitly typed.
My questions are mostly related to ambiguities in the semantics, not necessarily the syntax. This includes ambiguities between references to run-time entities like namespaces and pure types like interfaces, static and instance member types, etc. Even if the syntax may not be ambiguous (at least from the point of view the compiler), the semantics could be, from the perspective of the developer.
Now:
- The feature is for type positions, E.g.
let x: this, which in TypeScript is purely a compile-time construct, there is no concept or "callee" or "caller", any piece of metadata that is available within any scope, including thethistype, and can be unambiguously made available in outside the declaration class/interface declaration. - The question was about the semantics of
thiswhen referenced from a static position in the class declaration. However, there is also a purely syntactic problem here in the case of a generic class:
class Example<T> {
x: this;
static y: this; // This is impossible [edit: assuming exact same semantics]
}It is impossible to resolve the type of the expression Example.y because the type parameter is only valid for instances, E.g. Example<number>.y is not valid. Edit: It could technically return the generic class type Example<T>, though the semantics in this case would not point to a "real" instance because the generic parameters would be unresolved. I guess the question, in this case, would be whether this<number> would be possible, and point out it wouldn't be consistent syntax/semantics since it'd only be available at static positions.
3. The question was about semantics from the point of view of a human, not a mechanical difficulty of the compiler to disambiguate between different statements.
4. The question was about semantics.
5. Like 1. This is just compile time metadata, it can unambiguously be made available at any point and position of the code.
Okay, to revisit your initial questions:
- Yes. The type should be
typeof Example. - I see no reason why it shouldn't be identical to
typeof Example.
That's the least surprising outcome. - For one, I don't think it should be supported in the first place. Two,
even if it is, that's not a straightforward thing for even a human to
figure out. There's also the problem that the implementation could provide
a more permissive type than the interface itself. That would bring
nightmares for bug catching, as the type can't be determined by merely
looking at it. In some cases, you would have to look across files to figure
out all the pieces (they would be pieces). - It would represent the context it's defined in. If it's in a class, it
refers to the type of the instance. If it's in an interface, it refers to
an instance of that interface. If it's the return type of a function, it
deals with thethisinside the function. - Probably not. It's defined per-function. And I doubt there's much of a
use case for getting that context outside the function.
Sorry for not quite understanding your question. Is this better?
On Thu, Sep 17, 2015, 04:00 Rotem Dan notifications@github.com wrote:
@IMPinball https://github.com/impinball
My questions are mostly related to ambiguities in the semantics, not
necessarily the syntax. This includes ambiguities between references to
run-time entities like namespaces and pure types like interfaces, static
and instance member types, etc. Even if the syntax may not be ambiguous (at
least from the point of view the compiler), the semantics could be, from
the perspective of the developer.Now:
- The feature is for type positions, E.g. let x: this, which in
TypeScript is purely a compile-time construct, there is no concept or
"callee" or "caller", any piece of metadata that is available within any
scope, including the this type can be unambiguously made available in
outside the declaration class/interface declaration.- The question was about the semantics of this when referenced from a
static position in the declaration. However, there is also a purely
syntactic problem here in the case of a generic class:class Example {
x: this;
static y: this; // This is impossible
}It is impossible to resolve the type of the expression Example.y because
the type parameter is only valid for instances, E.g.. Example.y
is not valid.
3. The question was about semantics from the point of view of a human, not
a mechanical difficulty of the compiler to disambiguate between different
statements.
4. The question was about semantics.
5. Like 1. This is just compile time metadata, it can unambiguously be
made available at any point and position of the code.β
Reply to this email directly or view it on GitHub
#3694 (comment)
.
One reason I'm mentioning this is because this proposal is relatively short sighted when it comes to allowing future extensions, like referencing the types of properties:
class Example {
member: number;
getMember(): this.member {
..
};
static member: string;
static getMember(): this.member {
..
}
}If using different semantics at static and instance contexts, the problem then would be this.member at an instance position would mean the instance member (typed number) yet typeof this.member at the same position would be static member (typed string). This would be very confusing. It also has the problem that it doesn't naturally extend to a way of referencing those types from the outside (this also includes function this type, which can be useful, especially if that function is used as a constructor [I mean, in a way that's not possible to model through a class]).
In any case, having this mean the class type and typeof this mean the constructor type is also not great syntax in general.
This problem also surfaces in the current proposal, regardless of any extensions, and even when this is entirely disallowed at static positions:
class Example {
member: number;
// Technically this should reference the static member's type. But other than the compiler
// and those deeply familiar with the language spec, no one would guess this would be the case:
func(): typeof this.member;
static member: string;
}You're talking to someone who really doesn't like the idea of a this.prop type or similar. All I see is a _gigantic_ footgun with no decent use case. Could you come up with a compelling, real-world example where the very idea itself could be useful?
// This should fail to compile. Happy hunting, even in this contrived example.
interface Foo {
foo: string;
member: this.foo;
func(): this.member;
}
interface Bar extends Foo {
bar: number;
member: this.bar;
}Do we _really_ need to have strong implicit coupling between types?
There is already a way to achieve strong implicit coupling between property types (although I wouldn't say it is very 'implicit'):
class Example {
static a: string[];
static b: typeof Example.a;
}or more indirectly in interfaces (same approach can be use to reference class instance members):
interface A {
a: number;
b: typeof x.a; // Note this wouldn't work if A.a is a generic type.
}
let x: A;More generally, any type alias is a essentially a strong coupling between types:
type A = number;
type B = A;
type C = B;
...
// Generic aliases (introduced in 1.6):
type A<T> = Array<T>;
type B<T> = { a: A<T> }
type C<T> = B<T>;
...You don't have to use any of this if you think it is a bad practice. As for better syntax for property type references, see my proposal at #4640, which also includes a more constrained approach to a this type, that tries to address some of the issues I mentioned here.
In any case, this isn't really related to the issue of typeof this.member pointing to a static member type:
- Modifying it to point to an instance member instead would change the semantics of
thisto reference the 'isolated' class instance type - a type that doesn't exist in typescript at the moment, and if this approach is taken, wouldn't be directly referenceable from the outside, though that would be possible with a simple trick:
class Example {
a: this;
static b: number;
}
let x: Example;
// The type of 'instance' is the isolated instance type of 'Example', which doesn't have a name
// or a way to describe it e.g. the closest way would be instance: { a: ??? }
let instance: typeof x.a; - Disallowing it completely (and also assuming
thisis disallowed at static positions) would weaken the classthistype to the point it can only be used in very trivial cases, and wouldn't be loyal to the semantics of the containing class type.
- Do take into account that other than a standard
thistype to refer to the instance's type, the implementation will be hard. - I'm going to re-ask my question: could you demonstrate a real-world example of where this would be useful?
- Not sure what is referenced as 'hard' to implement.
- I'm not exactly sure what you are referring to, anyway, I was never mentioning that 'trick' for useful applications. The reason I mentioned that was because of semantic correctness:
class Example {
func(): this {
...
}
a: number;
static a: string;
}
let instance = new Example();
let x = instance.func();Assuming this points to the isolated instance type of the class (i.e. typeof this.a refers to the instance member type) . What should the type of x be inferred here? would it be semantically correct resolve it to class type Example (where typeof Example.a would refer to a static member type)?
Also, if it is will be possible to declare:
class Example {
static a: string;
a: number;
b: typeof this.a;
}and have b reference to the instance property type (number), why not also allow to do this from the outside? (this includes static positions and references from anonymous interfaces within the class itself), but then any syntax that would be proposed would not be consistent with the typeof this syntax because typeof Example.a would point to the static property type. This is a part of the complex chain of reasoning that lead to formulating the typeon operator, as a more 'complete' package, that is consistent both for internal and external references.
@rotemdan Are you wanting any of this we're debating about yourself?
@IMPinball I find myself using the Curiously recurring template pattern in most OO languages to represent this, which, in my opinion, is a hack workaround. Full fledged support for a this type would be immensely welcome.
π
An implementation of polymorphic 'this' typing is now available in #4910.
I totally need to static type the this var in some functions π’
π some browser apis define the custom this object.
+1
+1
π It's been a couple months since the last real discussion on this. Is there any updates?
@eggers That's what I've gotten as well. It's mostly for TypeScript's solution to the CRTP (example from Wikipedia):
// Java
public interface Comparable<T extends Comparable> {
int compareTo(final T other);
}
public class Item implements Comparable<Item> {
private String name;
constructor(String name) {
this.name = name;
}
@Override
public int compareTo(final Item other) {
return name.compareTo(other.name);
}
}// Scala
trait Comparable[T <: Comparable[_]] {
def compareTo(other: T): Int
}
class Item(private val name: String) extends Comparable[Item] {
def compareTo(other: Item): Int = name compareTo other.name
}// TypeScript
interface Comparable {
compareTo(other: this): number;
}
class Item extends Comparable {
constructor(private name: string) {}
compareTo(other: Item): number {
return name.compareTo(other.name);
}
}The rest of it is still yet to be done.
As @IMPinball and @eggers noted #4901 only handles the class and interface case but not the function one. This is why the issue is still open. The next step is the ability to declare a constraint on the type of this argument in a function and check against that at call sites.
ok. I could really use that feature. i'm using koa, which binds everything to this when it invokes the handler function.
+1
+2
Hi, please merge following syntax into this discussion.
var o = {
method() {
this; // `this` is `o` object
},
anon: function () {
this; // `this` is `o` object
}
};I put up a proposal for this-function types at #6018 (with a link to a prototype as well).
It appears #4910 did not address the possibility of typeof this though it did allow this in plenty of type expressions.
What was the resolution on that idea? It would allow for resolution of #3841 with syntax like:
class A {
"constructor": typeof this;
}
let a = new A();
let b = new a.constructor();@LPGhatguy see #1554 and #2542.
@LPGhatguy Also, as @ahejlsberg rightly mentioned here, not all JS constructors are well-behaved in this area. Too many people unintelligently do this (I admit I have a few times in small scripts and pages, out of laziness, but not in anything big):
function Foo() {
// ...
}
Foo.prototype = {
bar: function () {
// ...
},
}In this case, new Foo().constructor === Object, because the object literal's constructor is Object. It was never actually set in the prototype object itself. That's the other problem.
We have:
class C {
a: any
}
function fn(cb: (a:C) => void) {}
fn((c) => { c./code completion kicks in here/ })Could we also have...?
function fn(cb: (this:C) => void) {}
fn(() => { this./code completion should kick in here/ })Arrow functions don't have their own this, but inherit from the parent scope. I'm guessing you meant this?
function fn(cb: (this:C) => void) {}
fn(function () { this./* code completion should kick in here */ })@KamilSzot @isiahmeadows I just wrote a test for what you want in my ongoing work to implement this for functions: 41d133f
As Isiah points out, it only works with function, not =>.
with #6739, this types can be specified for functions. a new compiler flag, --noImplicitThis makes it an error if this is used without being defined in the containing function.
@mhegazy I have a couple clarification questions:
- I'm guessing you're just referring to literal ES5 functions?
- Does
func(function (x: string) { ... })triggernoImplicitThisif the callee doesn't define athistype for the argument, such as infunc(f: (x: string) => any): any)?
-
Methods can also declare a this parameter:
class C { totalCallback(this: void, e: Event); // assign to me! }
-
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:
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 this: myType is the best one.
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: var this; in it and shoud cause a runtime error
has this landed in 2.0 ? I just copied this in a 2.0 project:
function f<this extends {x:number}>() {
this.x = 3;
}but it tells me:
index.ts(1,12): error TS1139: Type parameter declaration expected.
index.ts(1,17): error TS1005: ';' expected.
index.ts(1,35): error TS1109: Expression expected.
index.ts(1,37): error TS1109: Expression expected.
index.ts(1,39): error TS1005: ';' expected.
Sorry, I see that the syntax is now
function f(this:{x:number}>() {
this.x = 3;
}class C { newInstance(): this { return new C(); // error, 'this' only works with this expressions } }
How can polymorphic clone(): this be implemented if the above is not allowed? A class which implements the interface or abstract clone() method, could be extended by another class. It seems the compiler will need to deduce that inheritance from C types newInstance() as an abstract method even though the base has an implementation, so as to insure that each subclass provides a unique implementation.
function f<this extends {x:number}>() { this.x = 3; this.y = 6; //error: y is not available on this }
Don't you mean implements instead of extends, since f is not allowed to add new member properties?
var o = {myMethod: f, y: "bob"}; o.myMethod(); //error: o does not match the shape of 'this' for 'myMethod'
I presume new o.myMethod() is legal.
Should methods now all implicitly gain a this type variable? This would prevent users from shooting themselves in the foot. For example:
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.
Google SoundScript's presentation says on page 24, "method extraction only allowed when this : any".
class C { f() { } } var c = new C(); var f = c.f; // Ideally of type <this extends C>() => void f(); // Should be an errorthe type of f currently is
() => void, but I assume it would now be<this extends C>() => void. That would ensurefcan't accidentally be called without a properthis.
That seems to be a nice generalization of the aforementioned plan for Google's SoundScript.
(3) What are the type compatibility rules for function types with this type arguments? For example:
var c = new C(); var f: () => void = c.f; // Is this allowed?If we treat this as 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.
That would break the aforementioned soundness expected by @DanielRosenwasser, which I see he pointed out.
class C { foo() { var x = this; x = new C(); } }
Why are you inferring the type of x: this instead of x: C?
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()); // 42Can 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.