Home > Blockchain >  why TS gets type never if I add constraint that should resolve to given type?
why TS gets type never if I add constraint that should resolve to given type?

Time:12-28

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

TypeScript playground link

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

Playground

  • Related