Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function overloading of generic functions allows for invalid code #59069

Closed
cassiano opened this issue Jun 28, 2024 · 3 comments
Closed

Function overloading of generic functions allows for invalid code #59069

cassiano opened this issue Jun 28, 2024 · 3 comments
Labels
Not a Defect This behavior is one of several equally-correct options

Comments

@cassiano
Copy link

cassiano commented Jun 28, 2024

πŸ”Ž 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)
@jcalz
Copy link
Contributor

jcalz commented Jun 29, 2024

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.

@cassiano
Copy link
Author

cassiano commented Jul 3, 2024

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);

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Jul 9, 2024
@typescript-bot
Copy link
Collaborator

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 typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Jul 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not a Defect This behavior is one of several equally-correct options
Projects
None yet
Development

No branches or pull requests

4 participants