The polymorphic this
type is an implicit generic type parameter corresponding to the current context. Inside a method implementation in a class body, this
is constrained to the class instance type, but is otherwise unspecified. The compiler will treat it just like a generic type parameter inside a generic function, and not let you do something with it unless it's sure it would be applicable to all possible narrowings of that type. This improves type safety in the face of possible subclasses. On the other hand, in a method call, the this
type is specified with the type of the instance on which you call the method. So the compiler will allow you to do anything that works for that particular class and is not concerned about possible subclasses.
This is very similar to how generic functions behave:
function f<T extends string>(x: T): T {
return "x"; // error
}
const x: "x" = f("x"); // okay
There return "x"
is an error because T
can be any subtype of string
; it might not be "x"
. But f("x")
does produce the type "x"
when you call it. This makes sense because nothing stops someone from writing
const y: "y" = f("y"); // okay, but bad function impl
and it would be unfortunate if a value of type "y"
turned out to be "x"
at runtime.
For the same reason then, you can't assume this
inside the body of Foo
is exactly Foo
; it could be some subtype of Foo
. You might be thinking that's still okay because surely all subtypes will have a bar
property of type string
, right? And Partial<this>
should clear up any concerns about other properties, right?
Well, no... properties themselves can be narrowed. So bar
could be narrower than string
, as shown here:
class Baz extends Foo {
readonly bar = "oopsieDoodle";
}
And that means
new Baz("huh");
would end up passing { bar: "why" }
in a place that expects { bar: "oopsieDoodle" }
, and that's an error. See this question and its answer for the same general issue for explicitly generic functions.
Playground link to code