Description
Search Terms
class static side interface extend
Problem
Currently, there's no way to set the static side interface of a class. This is something dating back to #1263 and has been a topic more recently in #17545.
Some suggest adding static properties on interfaces. That doesn't solve the problem in a reasonable manner, I think. Other suggestions propose defining the class with an expression, but that doesn't completely solve the problem either. Consider this:
interface StaticSide {
len: number
}
let myClass: StaticSide = class Foo {
static len = 42
}
I can enforce that Foo
has the static len
. But I want to extend that class too, and the derived classes should have this property as well. Currently, this can't happen:
// Type 'StaticSide' is not a constructor function type.
class Bar extends myClass {}
// Cannot find name 'Foo'.
class Bar2 extends Foo {}
You might suggest to simply put the len
property in the class itself and not use an interface in the first place:
class Foo {
static len = 42
}
// Type 'string' is not assignable to type 'number'.
class Bar extends Foo {
static len = 'bar'
}
In this case, that would work, but suppose we have a more advanced use case with type parameters:
interface StaticSide<T> {
len: T
}
let myClass: StaticSide<number> = class Foo {
static len = 42
}
Now, you are forced to use a class expression because defining the static property as earlier is not possible:
// Static members cannot reference class type parameters.
class Foo<T> {
static len: T
}
class Bar extends Foo<number> {
static len = 'bar'
}
This error makes sense because the len
in Foo
can be of any type, and its implementation probably expects a certain type. As said in #33870.
Suggestion
We need some sort of way to attach a static side interface to the class itself, not just the resulting variable of an expression. I believe this can be solved with the suggestion in #33892:
interface StaticSide<T> {
len: T
}
class Foo: StaticSide<number> {
static len = 42
}
class Bar extends Foo {
// Type 'string' is not assignable to type 'number'.
static len = 'bar'
}
class Bar2: StaticSide<string> extends Foo {
// Type 'number' is not assignable to type 'string'.
static len = 64
}
As demonstrated, the specified static type in Foo
now has a type parameter, but it is used as a value. Unlike the problem earlier, len
in Foo
now has a determined type, number
.
Derived classes of Foo
should have the static side of Foo
enforced on them, requiring len
of Bar
to be a number. However, if the derived class declares its own static side, it is merged on top of the parent's static side. That's why len
in Bar2
is expected to be string
.
Examples
Basically, to set the instance side of a class, you use implements
. To set the static side, you assign a type to its symbol:
interface StaticSide<T> {
len: T
}
interface InstanceSide<Y> {
name: Y
}
class Foo: StaticSide<number> implements InstanceSide<string> {
static len = 42
name = 'foo'
}
Use Cases
Essentially, this solves three issues:
- Implementing interfaces on the static side Introduce a way to enforce a static contract #1263 Why doesn't TypeScript provide a way to implement an interface on the static side? #17545
- Static type parameters Allow static members to reference class type parameters #24018 Allow for static members to reference class type parameters (Error: TS2302) #32211
- A way to specify static side interface without an expression Allow specifying interface implements clauses for the static side of classes #33892
- Type of
constructor
Suggestion: Improve type ofconstructor
on the instance type of a class #32452 (edit)
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.