Closed
Description
Defaults for generic type parameters (#13487)
- @rbuckton decided to revisit this.
- Allows defaults in type parameters in classes, interfaces, function signatures.
- Rules
- Required type parameters cannot not follow optional type parameters.
- Type parameters can't have defaults which circularly reference themselves.
- Defaults must satisfy their constraints.
- A class or interface may introduce a default for an existing type parameter - the analogous entities merge.
- When performing type argument inference, and a type argument with a default lacks any candidates, instead of
{}
, use the default type.
- What about the following?
interface I { x: T } interface I<T = number> { // ... }
- Currently means that
x: T
refers to the type parameter. - Seems absolutely wrong that that should even occur.
- Maybe let's just make that an error because it's ambiguous.
- Currently means that
- Current implementation seems to always perform inference, even if type arguments are entirely supplied.
- Fix that.
- Why even perform inference if you have a type parameter default?
- Because inference can do better.
- What are some motivating use-cases?
- The overloads for
then
andcatch
inPromise
which just don't work in subtle ways.- We keep trying to patch them up, default type parameters seem to fix this.
- A
React.Component
which doesn't use itsthis.state
- DR: Is it a hazard if people don't know about type parameter defaults, but do want a typed state?
- RC:
state
can still be overridden in the subclass.- RC: But then parameter on
setState
will still beany
. :(- DR: But then we could have
setState
's parameter typed asthis["state"]
! 👍
- DR: But then we could have
- RC: But then parameter on
- RC:
- DR: Is it a hazard if people don't know about type parameter defaults, but do want a typed state?
- The overloads for
Resolution: Sounds good so far, let's take a look at the PR!
Mixins and Extending generic type parameters (#4890)
-
People have often asked for the ability to extend a type parameter.
interface Foo<T> extends T { // ... }
- Problem:
extends
today currently implies that TypeScript will perform some type-checking to see if members in the current type are compatible with those in the derived type. - If we can't give errors up-front, we can't give certain guarantees, which would not be good.
- Currently, the work done does not allow you to extend from type-parameters.
- However, we allow you to intersect the types (i.e. using the
&
operator), and the behavior is well-defined for conflicting members (just recursively intersect).- This is mostly fine, but we don't allow users to inherit from constructors that return intersection types.
- Problem:
-
New changes
- Allow clases to inherit from things that construct object types or intersection types.
- Allow interfaces to extend from both object types and intersection types.
- Allow classes to implement from both object types and intersection types.
-
These new changes allow users to reconcile difficulties when mixing and matching interfaces which extend, and type aliases which intersect.
- Great! Less friction.
- Means you can inherit from
Partial<T>
andReadonly<T>
!
-
Difficulty: intersections previously never adjusted the
this
type of intersected members.interface A { x: this; } interface B { y: this; } // Expected to work, previously an error. declare let foo: A & B; a.x.y; x.y.x
- We now instantiate the
this
type with the intersection itself.- This is great - mixins can actually be modeled more easily because the
this
type can be carried onward.
- This is great - mixins can actually be modeled more easily because the
// Something constructable. type Constructor<T> = new (...args: any[]) => T; // Strip out signatures from types. type Props<T> = { [P in keyof T]: T[P] }; declare function Mixin<B, BC, M, MC>(base: Constructor<B> & BC, mixin: Constructor<M> & MC): Constructor<B & C> & Props<BC & MC>;
- We now instantiate the
-
[[Too many samples to effectively jot notes...]]
-
So mixins could be sort of modeled, but there's still some weird stuff that needs to be handled with construct signatures.
- Effectively would be magic from the perspective of the user.
- Basically want to inherit the construct list from the base, and potentially tack on any required parameters in the derived constructor.
function identifiable<S extends new (...args: any[]) => any)>(superClass: S) { return class extends superClass { _id: string; static superStatic: string; constructor(id, ...args) { super(...args); this._id = id; } } }
- Basically rephrased: what is the type of
args
that type-checks? What construct signatures are produced on the new class? - We can think of this down the line.