Defaults for generic type parameters (#13487)
- Ron Buckton (@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?
interfaceI{x: T}interfaceI<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.
- Current implementation seems to always perform inference, even if type arguments are entirely supplied.
- 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 and catch in Promise 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 its this.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 be any. :(
- DR: But then we could have
setState's parameter typed as this["state"]! 👍
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.
interfaceFoo<T>extendsT{// ...}- 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.
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> and Readonly<T>!
Difficulty: intersections previously never adjusted the this type of intersected members.
interfaceA{x: this;}interfaceB{y: this;}// Expected to work, previously an error.declareletfoo: 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.
// Something constructable.typeConstructor<T>=new(...args: any[])=>T;// Strip out signatures from types.typeProps<T>={[PinkeyofT]: T[P]};declarefunctionMixin<B,BC,M,MC>(base: Constructor<B>&BC,mixin: Constructor<M>&MC): Constructor<B&C>&Props<BC&MC>;[[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.
functionidentifiable<Sextendsnew(...args: any[])=>any)>(superClass: S){returnclassextendssuperClass{_id: string;staticsuperStatic: 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.
Defaults for generic type parameters (#13487)
{}, use the default type.x: Trefers to the type parameter.thenandcatchinPromisewhich just don't work in subtle ways.React.Componentwhich doesn't use itsthis.statestatecan still be overridden in the subclass.setStatewill still beany. :(setState's parameter typed asthis["state"]! 👍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.
extendstoday 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.&operator), and the behavior is well-defined for conflicting members (just recursively intersect).New changes
These new changes allow users to reconcile difficulties when mixing and matching interfaces which extend, and type aliases which intersect.
Partial<T>andReadonly<T>!Difficulty: intersections previously never adjusted the
thistype of intersected members.thistype with the intersection itself.thistype can be carried onward.[[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.
argsthat type-checks? What construct signatures are produced on the new class?