Description
The language currently only allows dotted identifiers in extends
clauses, e.g. class Foo extends X.Y.Bar { }
. The identifier must resolve to a class in both the value and type namespaces.
With the advent of class expressions (#497), we'll presumably need to start allowing arbitrary expressions, or at least loosen up the restriction that the named parent be an actual class.
var B: /* ... */;
class A extends B { }
The question is, given the type of B
, how do we determine the type of A
? Any rule should ideally end up at the same result as the current type system when B
is a class.
Construct signatures?
Rule
The simplest rule would be that the return type of the construct signature of B
is the prototypal parent of A
. The constructor of A
must invoke that construct signature (via super
) according to the same rules as usual.
Issues
Problematically, B
can actually have any number of construct signatures, with no restriction:
interface Evil {
new(n: string): Horse;
new(n: number): Airplane;
}
var B: Evil;
// What is A??
class A extends B { }
We cannot use A
's super
call to resolve the ambiguity via overload resolution because A
might be an ambient class, in which case we won't know which super
overload was selected.
Prototype?
Rule
TypeScript defines the prototype
property of a class constructor function to be the same as the instance shape of the class. This is the prototypal parent of A
, and A
's constructor must call one of B
's construct signatures via super
.
Issues
The prototype
property replaces all references to A
's type parameters with any
, erasing the genericness of the type.
Decision points
Questions to consider at this point:
- Do base class expressions really need to have a
prototype
property? Probably not. - Are types with random construct signatures realistic base types? Probably not.
Compromise
Rule
A class extending some type B
has a prototypal parent of the best common type of the return types of B
's construct signatures. This best common type must exist (not {}
), and all of B
's construct signatures must have same number of generic type parameters. The extending class must specify that same number of generic type arguments when referencing B
in the extends
clause, which are then applied to the BCT of B
's construct signature. Otherwise, it is an error to attempt to extend from B
.
If a super
call is required, the extending type's constructor may invoke any construct signature of the base type.
TBD
- How does static inheritance work here?