-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow overloading constructors with type parameters to instantiate the same class but with different generics #54157
Comments
How would you specify the type of the constructor argument when invoking? |
As stated in the proposal:
Here's some examples to maybe make it a bit more clear:
|
That makes sense. My brain must have filtered that part when reading the issue. |
Since the type of the rest of the class depends on the return type of the constructor and not the type parameters used to construct it, this feels like it will make things very difficult (as in, having to |
Would you mind giving an example? Iβm not sure I'm following. All the fields and methods in a class would use the class generics as it works now, they wouldn't have to know about any constructor return types. This stuff is purely for consumers constructing a class. |
I wasn't talking about the methods, but the constructor. Initializing the class would involve the same challenges as a function with a complex generic return type. See for example #33912 |
(note that conditional types can sneak in where you don't expect them - |
Hmm, I don't think I included enough description on this topic in the proposal. The idea in this case is that the constructor body would function exactly the same as it does now -- it would act as though it's executing as the most generic form of the class, regardless of any inputs or the specified return type. Iβm not even arguing for any sort of additional or modified return type checking in the constructor implementation. (I don't like the returning from constructor pattern.) This proposal is solely syntactic sugar for the callable constructor signatures, not for anything on the implementation side. |
Okay so concretely, if you write a constructor signature that goes something like |
Okay here's a concrete example, say I'm making a wrapper class for an enum value, but the constructor accepts enum keys: enum AnimalType {
Unknown,
Dog,
Cat,
Bird,
}
class Animal<TAnimal extends AnimalType> {
public readonly type: TAnimal;
public constructor<TAnimalName extends string>(name?: TAnimalName): Animal<TAnimalName extends keyof typeof AnimalType ? (typeof AnimalType)[TAnimalName] : AnimalType.Unknown> {
// `this` here resolves to `Animal<AnimalType>`, ie, as generic as possible
// I have access to the following types:
// - TAnimal (which resolves to `AnimalType`)
// - TAnimalName (which resolves to `string`)
this.type = AnimalType[name as keyof typeof AnimalType] ?? AnimalType.Unknown;
}
} And here's an alternate version where I have overloads, too: class Animal<TAnimal extends AnimalType> {
public readonly type: TAnimal;
public constructor<TAnimalName extends string>(name?: TAnimalName): Animal<TAnimalName extends keyof typeof AnimalType ? (typeof AnimalType)[TAnimalName] : AnimalType.Unknown>;
public constructor<TAnimalType extends AnimalType = AnimalType.Unknown>(type?: TAnimalType): Animal<TAnimalType>;
public constructor(typeOrName: string | AnimalType) {
// `this` here resolves to `Animal<AnimalType>`, ie, as generic as possible
// I have access to the following types:
// - TAnimal (which resolves to `AnimalType`)
if (typeof typeOrName === "string") {
this.type = AnimalType[name as keyof typeof AnimalType] ?? AnimalType.Unknown;
} else if (typeof typeOrName === "number") {
this.type = typeOrName;
} else {
this.type = AnimalType.Unknown;
}
}
} In these examples I don't include |
If it's too confusing that the return type is not used for checks in the implementation, perhaps we could require that only overloads can have type parameters and return types? For example, in the initial example I gave (copied below) the constructor would error with tweaked versions of the current errors: class ElementWrapper<TELEMENT extends Element = Element> {
constructor<TTAG_NAME extends keyof HTMLElementTagNameMap>
(tagName: TTAG_NAME): ElementWrapper<HTMLElementTagNameMap[TTAG_NAME]>
{}
} Whereas this following one would work fine: class ElementWrapper<TELEMENT extends Element = Element> {
constructor<TTAG_NAME extends keyof HTMLElementTagNameMap>
(tagName: TTAG_NAME): ElementWrapper<HTMLElementTagNameMap[TTAG_NAME]>;
constructor(tagName: string) {}
} EDIT: I've updated the proposal to only allow type parameters and return types on overloads to resolve this issue. |
This would be very useful even without overloaded type parameters.
Edit: It is possible to achieve the desired behaviour from my above example today:
|
I would love to see @robbiespeed's example stripped down even further, like this: class Variable<T> {
value: T
constructor(value: number)
constructor(value: string)
constructor(value: T) {
this.value = value;
}
}
const x = new Variable(3); In the code above I would expect |
@meszaros-lajos-gyorgy I've updated my above example, as it is possible today. Your example can be achieved just by removing the overloads. Though more complex examples are also possible today like:
|
@robbiespeed thank you very much for the info, this class Variable<T> {
value: T
constructor(value: T extends number ? T : never)
constructor(value: T extends string ? T : never)
constructor(value: T) {
this.value = value;
}
}
const x = new Variable(3); // x = Variable<3> instead of Variable<number>
x.value = 4 // Error: Type '4' is not assignable to type '3' Is there a way to enforce the type |
@meszaros-lajos-gyorgy yes there is, the type condition needs to be reversed from |
Suggestion
π Search Terms
constructor overload return type parameter annotation generic
β Viability Checklist
My suggestion meets these guidelines:
Previous Discussions & Related Issues
TS1092: Type parameters cannot appear on a constructor declarationΒ #10860
The closest to a duplicate of this proposal. Closed due to conflicts between generics on class and generics on constructor overloads.
TS1093: Can't specify return types on constructorsΒ #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.
Feature Request: Declared constructor return typesΒ #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.
Allow to specify return type of constructorΒ #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:
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: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:Constructor overloads can use different type parameters from each other.
The functionality exactly mirrors function overloads, and allows for the following:
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.)
The text was updated successfully, but these errors were encountered: