Home > other >  Can I use T extends Contravariant<any> to safely constrain generic type to a Contravariant uni
Can I use T extends Contravariant<any> to safely constrain generic type to a Contravariant uni

Time:07-18

Let's start with some definitions:

type Contravariant<T> = (t: T) => void;
declare let cNum: Contravariant<number>;
declare let cStr: Contravariant<string>;

Now, say I want to make a function which I can pass in an array of Contravariant union type. First, I used a simple approach:

declare function f1<T>(a: T[]): void;
f1([cNum, cStr]); // T inferred as 'Contravariant<number> | Contravariant<string>'

Looks good! However, the array can take any type currently, I'd like to limit this to only Contravariant type, so let's make T extend Contravariant.

declare function f2<T extends Contravariant<any>>(a: T[]): void;
f2([cNum, cStr]); // T inferred as 'Contravariant<number> | Contravariant<string>'

Seems to work good. However, eslint is now complaining about using any. unknown obviously doesn't work since it's contravariant.

What are the potential downsides to using any in this case? Can I safely use any? Are there any ways to do this without using any?

Some other things I tried:

declare function f3<T extends Contravariant<U>, U>(a: T[]): void;
f3([cNum, cStr]);
// Type 'Contravariant<number>' is not assignable to type 'Contravariant<unknown>'.
//  Type 'unknown' is not assignable to type 'number'.
declare function f3a<U>(a: Contravariant<U>[]): void;
f3a([cNum, cStr]);
// Type 'Contravariant<string>' is not assignable to type 'Contravariant<number>'.
//   Type 'number' is not assignable to type 'string'.
declare function f4<T extends Contravariant<T extends Contravariant<infer I> ? I : never>>(a: T[]): void;
f4([cNum, cStr]);
// Type 'Contravariant<string>' is not assignable to type 'Contravariant<string | number>'.
//   Type 'string | number' is not assignable to type 'string'.
//     Type 'number' is not assignable to type 'string'.

Why do all these (f3, f3a, f4) result in a different type inferred? They look like they all do the same thing to me.

CodePudding user response:

I don't see any downside of using any here. After all, it's just used for a constraint. But if you really need a solution without any, you may be interested in this:

declare function f1<
  T extends unknown[]
>(a: [...{ [K in keyof T]: Contravariant<T[K]> }]): void;

f1([cNum, cStr]);

T will be inferred as [number, string] in this case.

Playground

  • Related