'How to have functions pass arguments with the same overloads?

I am trying to create a function passes its arguments to another function. Both of these functions need to have the same overloads.

function original (a: number): boolean;
function original (a: string, b: string): boolean;
function original (a: number | string, b?: string): boolean {
  return true;
}

function pass (a: number): boolean;
function pass (a: string, b: string): boolean;
function pass (a: number | string, b?: string): boolean {
  return original(a, b);
}

This does not work.

Argument of type 'string | number' is not assignable to parameter of type 'string'.

Type 'number' is not assignable to type 'string'.(2345) input.tsx(4, 10): The call would have succeeded against this implementation, but implementation signatures of overloads are not externally visible.

Playground



Solution 1:[1]

You can just declare your pass function as being of the type of the original function using the typeof operator, e.g.

function original (a: number): boolean;
function original (a: string, b: string): boolean;
function original (): boolean {
  return true;
}

let pass: typeof original =  () => {
  return true;
};

pass(5);         // all good
pass('a', 'b');  // all godd
pass(45, 'b');   // error here

Playground here

Solution 2:[2]

Add extra overload:

function original<
  A extends number | string,
  B extends A extends string ? string : undefined
>(a: A, b: B): boolean;

Now it looks like:

function original(a: number): boolean;
function original(a: string, b: string): boolean;

function original<
  A extends number | string,
  B extends A extends string ? string : undefined
>(a: A, b: B): boolean;

function original(a: number | string, b?: string): boolean {
  return true;
}

function pass(a: number): boolean;
function pass(a: string, b: string): boolean;
function pass(a: number | string, b?: string): boolean {
  return original(a, b); // Works
}

const t1 = original(1); // Works
const t2 = original("foo", "foo"); // Works
const t3 = original(1, "foo"); // Works: gives an error as expected
const t4 = original("foo"); // Works: gives an error as expected

You no more need the second overload function original(a: string, b: string): boolean but you may still leave it for better readability of the code.

Playgroun

Solution 3:[3]

Here's a solution which doesn't use type assertions and doesn't change or add any overload signatures to either function: use an IIFE to create both functions, where the functions have the weaker signature inside the IIFE's "private" scope, but the stronger signature outside.

interface OriginalSignature {
  (a: number): boolean;
  (a: string, b: string): boolean;
}

const [original, pass] = (function(): [OriginalSignature, OriginalSignature] {
  function original(a: number | string, b?: string): boolean {
    return true;
  }
  function pass(a: number | string, b?: string): boolean {
    return original(a, b);
  }
  return [original, pass];
})();

Tests:

// OK
original(1);
original('a', 'b');
pass(1);
pass('a', 'b');

// errors
original('a');
original(1, 'b');
pass('a');
pass(1, 'b');

Playground Link

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 dezfowler
Solution 2 Andrei Kovalev
Solution 3 kaya3