type-fest icon indicating copy to clipboard operation
type-fest copied to clipboard

A type util to produce an alternation of function overloads.

Open unphased opened this issue 2 months ago • 1 comments

Example situation:

type F = {
    (thing: string): void;
    (things: string[]): void;
}

type Z = Parameters<F>[0]

Type Z here resolves to string[], not string | string[], because overloads just get the last declared one matched.

I am curious to know if this is possible to implement. As far as I can tell this also affects the standard function overload approach, e.g.:

function fun(thing: string): void; 
function fun(stuff: string[]): void;
// function fun(thing: string | string[]); // uncomment me to allow type Y to match `string`.
function fun(thing: string | string[]) { console.log(Array.isArray(thing) ? thing.join('; ') : thing); }

type F = {
    (thing: string): void;
    (things: string[]): void;
}

type Z = Parameters<F>[0]
type Y = Parameters<typeof fun>[0];

let zz:Z = 'abc';
let yy:Y = 'abc';

To be clear, what I'm asking for is a util e.g. OverloadParameters which in this case would produce for type OverloadParameters<F> a value of [string] | [string[]]. ~~I'm actually currently unsure of how to unwrap the parameter-array situation there but I'm sure it's manageable.~~ As expected, the type ([string] | [string[]])[0] is string | string[].

unphased avatar Apr 23 '24 06:04 unphased

GPT4 plus testing shows to me that It appears possible to do this via

type OverloadParams<T> = T extends {
  (...args: infer A): any;
  (...args: infer B): any;
} ? A | B : never;

A general solution working for N overloads seems elusive. In practical terms we only ever need this for up to say 5 overloads. So unrolling it seems practical. A recursive implementation would be nice, but may lead to longer compilation time.

This definitely seems to work for me:

type OverloadParams<T> = T extends {
  (...args: infer A);
  (...args: infer B);
  (...args: infer C);
  (...args: infer D);
  (...args: infer E);
} ? A | B | C | D | E : T extends {
  (...args: infer A);
  (...args: infer B);
  (...args: infer C);
  (...args: infer D);
} ? A | B | C | D : T extends {
  (...args: infer A);
  (...args: infer B);
  (...args: infer C);
} ? A | B | C : T extends {
  (...args: infer A);
  (...args: infer B);
} ? A | B : never;

unphased avatar Apr 23 '24 06:04 unphased