Description
Agenda
- Process: publish notes on github
- Design update on this types for classes and interfaces (Supporting 'this' type #3694)
Notes
Publishing Notes on github
- Publish notes on github a la C#
Conclusion: Start publishing this meeting as github issues
Design update on this types for classes and interfaces (#3694)
Overview
This update is only limited to declaring, inferring and propagating this types in classes and interfaces. function declarations will be handled separately. With this in place, fluent APIs can be correctly modeled in TypeScript, e.g.
class Base {
foo() {
return this;
}
}
class Derived extends Base {
bar() {
return this;
}
}
var d = new Derived();
d.foo().bar(); // where the call to foo returns Derived and not Base.
Design overview
Consider every class/interface as a generic type with an implicit this
type arguments. The this
type parameter is constrained to the type, i.e. A<this extends A<A>>
. The type of the value this
inside a class or an interface is the generic type parameter this
. Every reference to class/interface A outside the class is a type reference to A<this: A>
. assignment compatibility flows normally like other generic type parameters, e.g.:
class C {
doSomething(other: this) {
var c: C;
c = other; // OK
other = c; // Error, other is not guaranteed to be a c
}
}
Performance concerns
With this
type treated as a generic type parameter, every type is generic and every reference to a type is a generic type reference that has to be instantiated. Huge perf and memory cost..
Alternatives
- Make
this
type opt-in and not inferred automatically, pros: no perf penalty for existing code, cons: unintuitive, not clear when you should declare it or not, it is not clear what would be a notation on classes/interfaces to signify they need to capture this type. - Possibly add support for
sealed
class as a way to opt-out of the newthis
Possible optimizations
interfaces: if the interface does not reference this
anywhere explicitly in the declaration no need to instantiate. one caviet, base interfaces may reference this
classes: do not try to do this for classes, all classes would have a reference to this
in a body of a method, doing the analysis to figure out if the this type "escapes" the class body is not cheep.
do the check on property/method level, e.g. if the property type is a primitive type, number,sting,etc.. no need to instantiate for this
with these optimizations in place, perf penalty can be reduced to 5% for class-heavy code bases.
Conclusion:
this
types adds new expressiveness to the language and enables for modeling common APIs; the feature provides enough value to offset the perf degradation.Action item: follow up on plans to offset the perf loses by looking for other optimizations in the system
Discussion
this
vs. typeof this
Two options, either use this
in a type position to indicate the this
type, or use typeof this
instead:
typeof this
is more consistent with other places where typeof is used, and indicates difference between value and type spaces.this
is shorter to write- in an interface the value
this
does not exist - in a fluent API, you will be typing
typeof this
a lot, so saving the 7 additional characters matter - it is easier to restrict a new type
this
and expand it later to include functions declarations than to restrict the use oftypeof this
.
Conclusion: use
this
to indicate type of this, in the future expandtypeof
to allowthis
Where is the type of this allowed
- Only in method bodies, property declaration/signature, method declaration/signature within a class or an interface
- Not allowed in static properties or methods.
static this
A common pattern is a create method on a class, that returns the type of a subtype instead of of the type. e.g.:
class C {
static create() {
return new this();
}
}
class D extends C {}
D.create(); // expect it to be D
On the other hand, it is confusing to have the same notation to mean two different things, e.g:
class C {
static x : this; // this is the constructor
y: this; // this is the instance this
}
Conclusion: do not allow this in static members
this
and intersection types
Intersecting two types there is no effect on the value of this
, e.g:
interface A {
foo();
}
interface B {
bar(): this;
}
var x: A & B;
x.bar() // returns B and not A&B
The reason is this
is a generic parameter of the type that is instantiated by the reference, (which is needed to support property declaration using type this), and not passed at the call site. This does not match the JS call semantics. but can be done along with function declaration this type support.
Function declarations
Function declarations needs to be handled separately. An explicit this
type parameter would be specified, and checked for assignment compatibility. e.g.:
function compare<this extends A>(other: this) {
}
Also, add a new flag --noImplicitThis
to make it an error to reference this in function declarations without declaring its type.