Search code examples
javascripttypescriptoverloading

How to use the same parameters with two overloaded functions in TypeScript?


I tried to call another overloaded function within an overloaded function in typescript. Since the type Func2 is identical to the type Func1, it is certain that the arguements passed onto func1 from func2 will be typed correctly. However, typescript seems to be unable to pick that up, and throwing an error.

type Func1 = {
  (a: string, b: string): void
  (a: undefined, b: undefined): void
}
const func1: Func1 = (a, b) => {
  console.log(a, b)
}

type Func2 = {
  (a: string, b: string): void
  (a: undefined, b: undefined): void
}
const func2: Func2 = (a, b) => {
  func1(a, b)
  console.log(a, b)
}

/*
No overload matches this call.
  Overload 1 of 2, '(a: string, b: string): void', gave the following error.
    Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
      Type 'undefined' is not assignable to type 'string'.
  Overload 2 of 2, '(a: undefined, b: undefined): void', gave the following error.
    Argument of type 'string | undefined' is not assignable to parameter of type 'undefined'.
      Type 'string' is not assignable to type 'undefined'.
*/

How can I resolve this?

EDIT: Here's the actual implementation that I was attempting:

type ParseRandomArgs = {
  (a: undefined, b: undefined): [number, number]
  (a: number, b: undefined): [number, number]
  (a: number, b: number): [number, number]
  (a: [number, number], b: undefined): [number, number]
  (a: [number], b: undefined): [number, number]
}

type RandomNumber = {
  (a: undefined, b: undefined): number
  (a: number, b: undefined): number
  (a: number, b: number): number
  (a: [number, number], b: undefined): number
  (a: [number], b: undefined): number
}

const isNullish = (value: any) => value === undefined || value === null

const parseRandomArgs: ParseRandomArgs = (a, b) => {
  if (Array.isArray(a)) {
    if (a.length === 2) return a
    return [0, a[0]]
  }
  else if (isNullish(b)) return [0, isNullish(a) ? 1 : a as number]
  else return [a as number, b as number]
}

const randomFloat: RandomNumber = (a, b) => {
  let [min, max] = parseRandomArgs(a, b) // [min, max]
  return Math.random() * (max - min) + min
}

const randomInt: RandomNumber = (a, b) => {
  let [min, max] = parseRandomArgs(a, b) // [min, max]
  min = Math.ceil(min)
  max = Math.floor(max)
  return Math.floor(Math.random() * (max - min) + min)
}

Solution

  • As @Mike mentioned, making undefined arguments optional makes overloading simpler to work out. This should work:

    type RandomGenerator = {
        (start: number, end?: number): number
        (range: [number, number]): number;
        (range: [number]): number;
    }
    
    const randomFloat: RandomGenerator = (start, end?) => {
        const [min, max] = parseRandomArgs(start, end as number | undefined);
        return computeRandom(min, max);
    }
    
    const randomInt: RandomGenerator = (start, end?) => {
        const [min, max] = parseRandomArgs(start, end as number | undefined);
        return Math.floor(computeRandom(min, max));
    }
    
    const computeRandom = (min: number, max: number): number => {
        return Math.random() * (max - min) + min;
    }
    
    const parseRandomArgs = (first: number | [number, number] | [number], second: number | undefined): [number, number] => {
        let args: [number, number];
        if (Array.isArray(first) && first.length === 1) {
            args = [0, first[0]];
        } else if (Array.isArray(first) && first.length === 2) {
            args = first;
        } else {
            args = Number.isFinite(second) ? [first, second as number] : [0, first];
        }
        const [min, max] = args;
        return [Math.ceil(min), Math.floor(max)];
    }