Description
Search Terms
default type, default type parameters
Suggestion
This is related to #32211 and especially, #24018. I would have commented on the latter, but it's limited to collaborators. @Andy-MS said:
Without inheritance that wouldn't make much sense:
class Super<T> { static m(x: T): void; } Super.m(); // What's `T`?I see what you mean though, when a subclass is introduced:
class Sub extends Super<number> {} Sub.m(); // T is number
My proposal is to allow type parameters in static members only if those parameters have default values. For example:
class Super<T> {
// Error: Static members cannot reference class type parameters.
// Proposition: Static members can only reference class type parameters with default type.
static value: T
}
let val = Super.value // What's T
But if you specify a default value, everything makes sense:
class Super<T = number> {
static value: T
}
let val = Super.value // number
class Sub extends Super { }
class Sub2 extends Super<string> {}
let subval = Sub.value // number
let subval2 = Sub2.value // string
Use Cases
In my personal use case, I have an instance property options
that merges input with data from a static defaults
property:
class Component<O> {
static defaults: O // Error: Static members cannot reference class type parameters.
options: O
constructor (settings: Partial<O>) {
let ctor = this.constructor as typeof Component
this.options = { ...ctor.defaults, ...settings }
}
}
If I then extend the class, I get no type-checking for defaults
:
interface SubOptions {
foo: string
}
class SubComponent extends Component<SubOptions> {
static defaults = {
foo: 42 // not type-checked
}
}
Examples
With my proposal, you could define the base class like this:
interface DefaultOptions {
element: HTMLElement,
value: number
}
class Component<O = DefaultOptions> {
static defaults: O
options: O
constructor (settings: Partial<O>) {
let ctor = this.constructor as typeof Component
this.options = { ...ctor.defaults, ...settings }
}
}
The descendant like this:
interface SubOptions {
foo: string
}
class SubComponent extends Component<SubOptions> {
static defaults = {
foo: 42 // Error: Type '42' is not assignable to type 'string'.
}
}
And the final result would be:
let def = Component.defaults // DefaultOptions
let foodef = SubComponent.defaults // SubOptions
Everything is explicitly defined and typed.
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript codeThis wouldn't change the runtime behavior of existing JavaScript codeThis could be implemented without emitting different JS based on the types of the expressionsThis 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.
Activity
RyanCavanaugh commentedon Oct 8, 2019
This would be a massive soundness hole. Nothing about any particular instantiation of e.g.
O
is at all constrained by the default; literally any type might come out of that spread expression with no relation to the instantiated type.