Home > Enterprise >  How is type compatibility checked between call signatures?
How is type compatibility checked between call signatures?

Time:05-25

Consider the following snippet:

type Add = (a: number | string, b: number | string) => number | string;

function hof(cb: Add) {}

const addNum = (a: number, b: number): number => a   b;
const addStr = (a: string, b: string): string => a   b;

// @ts-expect-error
hof(addNum);
// @ts-expect-error
hof(addStr);

Why can't we pass addNum and addStr functions to hof, IMO their call signatures should be compatible with what hof expects, but it actually isn't.

And if their types are incompatible, then why doesn't the overload signatures in the following snippet complain?

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: number | string, b: number | string): number | string {
  return 1;
}

CodePudding user response:

You are assuming covariant types, but function arguments are contravariant. (see enter image description here

How can the return type be used?

Now let's see the ways in which hof can use the value returned by cb.

Because cb can return either a number or a string, it's hof's responsibility to use the return value wisely, meaning that hof should not assume that the returned value is always a number or a string, hof needs to put appropriate type guards before making any such assumptions.

function hof(cb: (a: number | string) => number | string) {
  // @ts-expect-error
  const fixed = cb(100).toFixed() // wrong assumption that the returned value is always a number
}

function hof(cb: (a: number | string) => number | string) {
  const ret = cb(100)
  if (typeof ret === "number") { // appropriate check to make sure that it's a number
    const fixed = ret.toFixed();
  }
}

And again notice the place where the error is thrown, it's thrown inside the function body indicating that there's a problem with the implementation of the function.

The callback passed to hof can also just return a number and TS would be absolutely fine with that.

function hof(cb: (a: number | string) => number | string) {}

function double(a: number | string): number {
  return 2 * Number(a)
}

hof(double)

So, the callbacks passed to hof should only return a subset of values that hof expects the callback to return.

enter image description here

Overload Signatures

The behavior with overload signatures is different, the purpose of overloading is to restrict the behavior of a function.

So, arguments and return values of overload signatures should always be a subset of the arguments and return values of the implementation signature.

  • Related