microsoft / microsoft/TypeScript
Allow overloading constructors with type parameters to instantiate the same class but with different generics
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 117
Description
Suggestion
🔍 Search Terms
constructor overload return type parameter annotation generic
✅ Viability 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, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
Previous Discussions & Related Issues
-
#10860
The closest to a duplicate of this proposal. Closed due to conflicts between generics on class and generics on constructor overloads. -
#11588
A feature request asking for TypeScript to allow setting any return type for a constructor. It was declined due to that pattern being considered bad practice. -
#27465
Quickly closed due to being a duplicate of the first issue, #10860, did not end up with much further discussion once it was closed, unfortunately. -
#27594
Asking again for TypeScript to allow setting the return type for a constructor. From what I can see, the issue does not specify whether the instance type should be assignable to the return type, or whether any return type should be allowed (IE, a straight rehash of #11588.) This issue is still open and has garnered a lot of discussion, but it's only tangentially related to this proposal.
⭐ Goals of this Proposal
1. Allow writing classes that generate the correct generics without requiring the consumer to provide any.
The ideal circumstances for class construction with generics is the following, where the consumer doesn't need to provide any type parameters:
// Ideal circumstances:
class Collection<T> {
constructor(iterable: Iterable<T>) {}
}
new Collection([1, 2, 3]) // Collection<number>
That, unfortunately, is not always possible. In the following example, for instance, the ideal return would use HTMLElementTagNameMap[TAG_NAME], but there's no way to do that:
class ElementWrapper<TELEMENT extends Element = Element> {
constructor(tagName: string) {}
}
new ElementWrapper("button") // ElementWrapper<Element>
new ElementWrapper<HTMLButtonElement>("button") // ElementWrapper<HTMLButtonElement>, but the consumer has to annotate it themself :(
2. Don't affect existing TypeScript code at all. Any new functionality should be layered over top what's already there.
When a developer isn't using any of the newly-allowed annotations, TypeScript should function exactly as it does now.
3. Don't introduce new syntax for providing type parameters to a constructor call.
4. Disallow constructors to be annotated as returning things not assignable to the class's type.
Returning anything is out of scope for this proposal.
🚀 Proposal
Constructor overload signatures can now have their own type parameters and provide a return type.
In order to allow this while still respecting the other stated goals above, this comes with the following restrictions:
-
When type parameters are specified, and the overload is called, the overload's type parameters are used instead of the class generics.
-
When a signature is using custom type parameters, it must, therefore, have a return type, otherwise there wouldn't be a way for TypeScript to determine the generics of new instances.
-
Any constructor return type must be assignable to the class's instance type. (As stated above, anything else is out of scope for this proposal.)
-
Again, only overloads may have type parameters or return types. The constructor implementation may not have type parameters or a return type, as it adds additional complexity to the proposal. The current errors will become
Type parameters can only appear on overloaded constructor declarations.andType annotations can only appear on overloaded constructor declarations.
Example
Using the above ElementWrapper example, we can now do the following:
class ElementWrapper<TELEMENT extends Element = Element> {
constructor<TTAG_NAME extends keyof HTMLElementTagNameMap>
(tagName: TTAG_NAME): ElementWrapper<HTMLElementTagNameMap[TTAG_NAME]>;
constructor(tagName: string) {}
}
Constructor overloads can use different type parameters from each other.
The functionality exactly mirrors function overloads, and allows for the following:
class ElementWrapper<TELEMENT extends Element = Element> {
constructor<TTAG_NAME extends keyof HTMLElementTagNameMap>
(tagName: TTAG_NAME): ElementWrapper<HTMLElementTagNameMap[TTAG_NAME]>;
constructor(tagName: string): ElementWrapper<Element>;
constructor(tagName: string) {}
}
Constructor overloads should not be handled any differently in TypeScript than they are now (yes, it already works!), using the complex newable interface syntax. (To note, while that syntax works for type definitions, if a bit awkwardly, trying to implement the class itself using those types is even more awkward.)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No implementation files, tests, or compiler entry points are named. Start by reviewing the proposal and related issues #10860, #11588, #27465, and #27594; done would require an agreed design and corresponding TypeScript behavior that supports generic constructor overloads without changing existing code.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100