I wonder why TypeScript resolves conditional type to never
if I add a constraint that should guaranty desire type.
Example
Let's say we have written such code
type PrimitiveDataType = string | number | bigint | boolean | symbol | undefined | null;
type ConditionalType<T> = T extends PrimitiveDataType
? (v: T) => void
: never;
abstract class AbstractClass<T> {
abstract value: T;
protected conditionalFunctions: Map<ConditionalType<T>, number | undefined> = new Map();
}
class SomeClass<T extends PrimitiveDataType> extends AbstractClass<T> {
value: T;
constructor(value: T) {
super();
this.value = value;
}
someMethod() {
for (const someFn of this.conditionalFunctions.keys()) {
someFn(this.value);
}
}
}
In the above code I have created a PrimitiveDataType
which is a union of all primitive data types in JavaScript.
Then I have created a ConditionalType<T>
that will resolve to some callback only if T
is one of PrimitiveDataType
.
Then I have created an abstract generic class that have a field which type(ConditionalType<T>
) depends on generic value of this class.
In the end, I have crated a SomeClass
that extends AbstractClass<T>
and add constraints that generic parameter T
have to extend PrimitiveDataType
and I get this error:
TS2345: Argument of type 'PrimitiveDataType' is not assignable to parameter of type 'never'.
Type 'undefined' is not assignable to type 'never'.
Conclusion
I thought if in SomeClass
T
have a constraint that it have to be one on PrimitiveDataType
then TypeScript will resolve conditionalFunctions
field to be a type of Map<(v: T) => void, number | undefined>
. To my surprise TypeScript resolves this type to Map<(v: never) => void, number | undefined>
what is not clear for me and I do not know where is a mistake in the way how do I think about this?
Can you explain to me why this work like that, or maybe it is bug in TypeScript compiler?
Observations
If I leave only one, type in PrimitiveDataType
then everything works okay but for more than one I am getting an error
CodePudding user response:
Your generic type ConditionalType<T>
produces a distributive type when given a union.
For example when given the union string | number
it results in the following type
type Foo = ConditionalType<string | number>
// ((v: string) => void) | ((v: number) => void)
Wrap either side of the condition in square brackets to avoid this
type ConditionalType<T> = [T] extends [PrimitiveDataType]
? (v: T) => void
: never;
The above now generates the following, non-distributive, type which should work for your abstract class
type Foo = ConditionalType<string | number>
// (v: string | number) => void