1

I'm trying to create a generic function that takes in two parameters, the first of which is an array of functions. The second parameter’s type should be the union of the return types of the first parameter's elements (all of which are functions).

Here is what I've tried:

type Fn<R> = () => R;
const a: Fn<true> = () => true;
const b: Fn<"somen"> = () => "somen";

interface C<ABUnion, ABReturnUnion> {
  fns: ABUnion[];
  returns: ABReturnUnion;
}

function getX<R>(fns: Array<Fn<R>>, returns: R) {
  type FnsUnion = typeof fns[number];
  const c: C<FnsUnion, R> = {
    fns,
    returns,
  };
  return c;
}

getX([b, a], "true");

The language service underlines b in the getX call, and displays the following error:

  Type '"somen"' is not assignable to type 'true'.

Anyone know of a solution? Thank you!

Harry Solovay
  • 483
  • 3
  • 14

1 Answers1

2

When the compiler sees a heterogenous array literal there it uses some heuristics to determine whether to synthesize a union for the element type, or to emit an error on the first element that doesn't match the inferred type. It's a tradeoff because sometimes you want such an error. For example, the following error is generally considered desirable, as mentioned in a related question:

function same<T>(x: T, y: T) { };
same(0, ""); // error!
// ---> ~~
// argument of type "" is not assignable to number

That function can be turned into an array example like this:

function sameTuple<T>(x: [T, T]) { };
sameTuple([0, ""]); // error!
// ---------> ~~
// Type 'string' is not assignable to type 'number'.

And you can see the dilemma... sometimes people want T to be the union, other times people want to restrict calls to homogeneous arrays.


In this case, I'd suggest changing the type of fns to something the compiler is willing to infer a wider generic type for. For example:

function getX<F extends Array<() => any>>(fns: F, returns: ReturnType<F[number]>) {
  const c: C<F[number], ReturnType<F[number]>> = {
    fns,
    returns,
  };
  return c;
}

Here the generic type F should be anything matching an array of no-arg functions. That is the most straightforward thing to infer from the type of fns. Once that succeeds, you can synthesize your own type for returns as ReturnType<F[number]>.

Now it should work as you expect, I think:

getX([b, a], "somen"); // okay
getX([b, a], "true"); // error!
// --------> ~~~~~~
// "true" is not assignable to true | "somen".

Okay, hope that helps; good luck!

Link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360