Joel
Joel

Reputation: 1833

use typescript generics to infer type of param function

function validate<K>(validationFunc: (...args: (K extends Array<infer T> ? T : K)[]) => boolean, validationArgs: K[]): boolean {
  let res: boolean;
  for (const validationArg of validationArgs) {
    if (Array.isArray(validationArg)) {
      res = validationFunc(...validationArg);
    } else {
      // res = validationFunc(validationArg);
      res = (validationFunc as (args: K) => boolean)(validationArg);
    }
    if(!res) 
      return false;
  }
  return true
}

The commented line throws an Error at the argument: the Argument of type 'K' is not assignable to parameter of type 'K extends (infer T)[] ? T : K'.ts(2345), whereas the casted version works, and does not throw any errors. As seen in this playground.

Why is typescript not able to infer, that on this line, K cannot be of type Array<any> and thus is allowed to be passed to the validation function?

Semantically: If the second argument is of type K[], the function needs to accept K as a single parameter. If the second argument is of type K[][], the function needs to accept multiple arguments of K.

E.g.

validate((x: number) => x%2, [1, 2, 3]) should be ok

validate((a: string, b: string) => a === b, [['a', 'a'], ['b', 'b']]) should be ok

validate((x: number) => x%2, ['a', 'b']) should throw an error

validate((x: number) => x%2, [['a', 'a'], ['b', 'b']]) should throw an error

EDIT: validate((x: number) => x % 2 === 0, [[1, 2, 3]]) should also throw an error, since validate would destructure the number[][] once, and try to call (x: number) => boolean with number[]

Upvotes: 0

Views: 475

Answers (1)

Grabofus
Grabofus

Reputation: 2034

I don't think you need to infer the type of the param function. Your validation arguments can be just K[] | K[][] as you explained it.

I also did a small change when you call the validate function x % 2 should be wrapped in Boolean() otherwise the return value would be incorrect.

function validate<T extends (...args: any) => boolean, P extends Parameters<T>>(validationFunc: T, validationArgs: P[]): boolean {
  let res: boolean;

  for (const validationArg of validationArgs) {
    res = validationFunc(validationArg);
    if(!res) 
      return false;
  }
  return true
}

function simplified<T extends (...args: any) => boolean, P extends Parameters<T>>(validationFunc: T, validationArgs: P[]): boolean {
  return validationArgs.every((args) => validationFunc(args));
}

validate((x: number) => Boolean(x % 2), [[1], [2], [3]]) // should be ok

validate((a: string, b: string) => a === b, [['a', 'a'], ['b', 'b']]) // should be ok

validate((x: number) => Boolean(x % 2), [['a'], ['b']]) // should throw an error

validate((x: number) => Boolean(x % 2), [['a', 'a'], ['b', 'b']]) // should throw an error

validate((x: number) => x % 2 === 0, [[1, 2, 3]]); // should throw an error

Playground Link

Upvotes: 1

Related Questions