TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Function overloading of generic functions allows for invalid code

Open cassiano opened this issue 1 year ago • 1 comments

🔎 Search Terms

function overloading generic functions invalid code

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about "function overloading generic"

⏯ Playground Link

https://tsplay.dev/N5Y35N

💻 Code

/* overload #1 */ function f0(fn: () => void): (() => void);
/* overload #2 */ function f0<P1 extends {}>(fn: (p1: P1) => void): ((p1: P1) => void);
/* overload #3 */ function f0<P1 extends {}, P2 extends {}>(fn: (p1: P1, p2: P2) => void): ((p1: P1, p2: P2) => void);

function f0(fn: (...args: unknown[]) => void): (...args: unknown[]) => void {
  fn(); // Creates a bug in overloads #2 and #3, given both p1 and p2 will be undefined, even though P1 and P2 do not allow it.
  fn(1);  // Should only be valid if P1 extends number; in addition p2 will be undefined, even though P2 does not allow it.
  fn(1, 2);  // Should only be valid if P1 and P2 both extend number.

  return () => {};
}

f0((a: string) => { console.log('a: ', a, 'typeof a: ', typeof a)}) // Prints: "a: ",  undefined,  "typeof a: ",  "undefined"

🙁 Actual behavior

TS compiles the code above as if it were correct, even though the following code correctly triggers an error for i and j:

const h: ((...args: unknown[]) => void) = (() => {})
const i: ((...args: unknown[]) => void) = ((p1: string) => {})
const j: ((...args: unknown[]) => void) = ((p1: string, p2: boolean) => {})

🙂 Expected behavior

TS should warn about the potencial error.

Notice how TS detects an error in a similar situation, when the functions are NOT generic:

/* overload #1 */ function f0(fn: () => void): (() => void);
/* overload #2 */ function f0(fn: (p1: string) => void): ((p1: string) => void);
/* overload #3 */ function f0(fn: (p1: string, p2: boolean) => void): ((p1: string, p2: boolean) => void);

But fail to detect it in the presence of generic functions.

Additional information about the issue

In the following similar code, but using a single tuple parameter, TS does detect the potencial error, triggering the "This overload signature is not compatible with its implementation signature" message:

function f1(fn: (a: []) => boolean): number;
function f1<P1>(fn: (a: [P1]) => boolean): string;
function f1<P1, P2>(fn: (a: [P1, P2]) => boolean): boolean;
function f1(fn: (a: unknown[]) => boolean): any {
  fn([1, 2, 3]);
}

It makes sense, since these expressions are all invalid:

const f: ((a: unknown[]) => boolean) = ((a: []) => true)
const g: ((a: unknown[]) => boolean) = ((a: [number]) => true)
const h: ((a: unknown[]) => boolean) = ((a: [number, string]) => true)

cassiano avatar Jun 28 '24 20:06 cassiano

Overloads are checked more loosely than they "should" be, see #13235 among others. You'll probably always be able to find some inconsistency in exactly what is allowed versus rejected.

jcalz avatar Jun 29 '24 23:06 jcalz

Joe and Andrew, I agree that it's virtually impossible for TS to cover all use cases. However, as TS already detects the error in the case below (Playground):

/* overload #1 */ function f0(fn: () => void): (() => void);
/* overload #2 */ function f0(fn: (p1: string) => void): ((p1: string) => void);
/* overload #3 */ function f0(fn: (p1: string, p2: boolean) => void): ((p1: string, p2: boolean) => void);

function f0(fn: (...args: unknown[]) => void): (...args: unknown[]) => void {
  // ...
}

I don't see why it fails to detect the same situation when generic types are present, like the similar code below, considering that string is (sort of) a "subset" (i.e. contained in) of the generic type P1 and boolean is a "subset" of P2 (Playground):

/* overload #1 */ function f0(fn: () => void): (() => void);
/* overload #2 */ function f0<P1>(fn: (p1: P1) => void): ((p1: P1) => void);
/* overload #3 */ function f0<P1, P2>(fn: (p1: P1, p2: P2) => void): ((p1: P1, p2: P2) => void);

cassiano avatar Jul 03 '24 20:07 cassiano

This issue has been marked as "Not a Defect" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

typescript-bot avatar Jul 12 '24 01:07 typescript-bot