Repository: TypeScript
Issue: Allow overloading constructors with type parameters to instantiate the same class but with different generics
Labels: Suggestion,Awaiting More Feedback

# Suggestion

## 🔍 Search Terms
constructor overload return type parameter annotation generic

## ✅ Viability Checklist

My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).

## Previous Discussions & Related Issues
1. #10860 
The closest to a duplicate of this proposal. Closed due to [conflicts between generics on class and generics on constructor overloads.](https://github.com/microsoft/TypeScript/issues/10860#issuecomment-246456721)

2. #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.](https://github.com/microsoft/TypeScript/issues/11588#issuecomment-253602163)

3. #27465 
Quickly closed due to being a duplicate of the first issue, [#10860](https://github.com/microsoft/TypeScript/issues/10860), did not end up with much further discussion once it was closed, unfortunately.

4. #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](https://github.com/microsoft/TypeScript/issues/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:
```ts
// 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:
```ts
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.` and `Type annotations can only appear on overloaded constructor declarations.`

### Example
Using the above `ElementWrapper` example, we can now do the following:
```ts
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:
```ts
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](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgKIBsIFsLgOpRwAOR0APACqoAyqAsqgHIXIQAekIAJgM5qY5wyALz9suMAD5kAbwBQASAgCJALmRVaDZgG45AXzlzQkWIhQZx+QiWgBhAPYgeYKAFcEYB1Flzk-5BAIAHdkSgoAQQBxAH1GCIZWDlxeZABrCABPBxhkAAkKOloVcAo4AHNGOBw6YkkACjAKqpx1CkjY+IYASnVLQTACYlIoMgKi-okyyuqIWqIAbXbouITUAF1JHWQ-AKDgxubZ9RcoUHLesQGh2yg9Qy4IBHQ4KBQAN1eriRuRvpLBjYRo5nK4PF47kZ9t9rMNoPUAERcYDvBHdZAAegx+UK1AAIijJuA5NCiYC4VBEVg4KA0ZjseNilYwHIgA). <sub>(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](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgKIBsIFsLgOpRwAOR0APACqoAyqAsqgHIXIQAekIAJgM5qY5wyALz9suMAD5kAbwBQASAgCJALmRVaDZgG45AXzlzQkWIhQZx+QiWgBhAPYgeYKAFcEYB1Flzk-5BAIAHdkSgoAQQBxAH1GCIZWDlxeZABrCABPBxhkAAkKOloVcAo4AHNGOBw6YkkACjAKqpx1CkjY+IYASnVLQTACYlIoMgKi-okyyuqIWqIAbXbouITUAF1JHWQ-AKDgxubZ9RcoUHLesQGh2yg9QzkEdDgePknrYegASSwidEoaPQmCx2JxUu8wCIrhJpMBfiUwG8ETcRgCtMDpPJFEQ3AAjdDABDIKAQOBcJzoTKsBFtQHaCh6bF4glEhBOU4eLxQQ4zVrIU7nbq+BQKMAAC2APAAdMorJDROSEG4BlKECS4JAITyWhAhS9kG4QGkQA5giBkPrNEDdIpDA82c5IRCUdAoc6bCMfn8LUi5S6oI5He5PN5GftoR9bvUAERcYAAN2jQoA9Mn8oVqAARBMQuTh92fbnRrBwUBJ5Cp9MTBFGIA).)</sub>