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

Equivalent arguments typed with tuples aren't assignable to the same functions with overloads when the same function is recursively referenced #45466

Open
niieani opened this issue Aug 16, 2021 · 7 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@niieani
Copy link

niieani commented Aug 16, 2021

Bug Report

🔎 Search Terms

arguments, overloads, function, tuple

🕗 Version & Regression Information

All versions with support for tuple arguments (upto v4.5.0-dev.20210815).

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about function overloading (there is nothing in the FAQ about tuples)

⏯ Playground Link

Playground link with relevant code

💻 Code

type START = 0
type DATA = 1

type Args<In, Out> =
  | [t: START, other: FnWithArgs<Out, In>]
  | [t: DATA]

type FnWithArgs<In, Out> = (...args: Args<In, Out>) => void

interface FnWithOverloads<In, Out> {
  (t: START, other: FnWithOverloads<Out, In>): void;
  (t: DATA): void;
}

declare const withArgs: FnWithArgs<1, 2>

// these should be assignable to one another, but aren't:
const assertion: FnWithOverloads<1, 2> = withArgs

🙁 Actual behavior

Two types that should be functionally equivalent aren't assignable to one another:

Type 'FnWithArgs<1, 2>' is not assignable to type 'FnWithOverloads<1, 2>'.
  Types of parameters 'args' and 't' are incompatible.
    Type '[t: 0, other: FnWithOverloads<2, 1>]' is not assignable to type 'Args<1, 2>'.
      Type '[t: 0, other: FnWithOverloads<2, 1>]' is not assignable to type '[t: 0, other: FnWithArgs<2, 1>]'.
        Type at position 1 in source is not compatible with type at position 1 in target.
          Type 'FnWithOverloads<2, 1>' is not assignable to type 'FnWithArgs<2, 1>'.
            Types of parameters 't' and 'args' are incompatible.
              Type 'Args<2, 1>' is not assignable to type '[t: 0, other: FnWithOverloads<1, 2>]'.
                Type '[t: 1]' is not assignable to type '[t: 0, other: FnWithOverloads<1, 2>]'.
                  Source has 1 element(s) but target requires 2.

I've tried simplifying the example, and this issue doesn't seem to occur when the parameters of the function don't reference themselves in their signature.

🙂 Expected behavior

No error, types should be assignable to one another.

Related issues

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Aug 16, 2021
@RyanCavanaugh
Copy link
Member

The example as presented is undecidable, thus rejected. Demonstrated:

type START = 0
type DATA = 1

type Args<In, Out> =
  | [t: START, other: FnWithArgs<Out, In>]
  | [t: DATA]

type FnWithArgs<In, Out> = (...args: Args<In, Out>) => void

type FnWithOverloads<In, Out> = {
  (t: START, other: FnWithOverloads<Out, In>): void;
  (t: DATA): void;
}

declare const withArgs: FnWithArgs<1, 2>
// Demonstrated: legal call
withArgs(0, withArgs);

// Asserted: This should be legal (use 'as any')
const assertion: FnWithOverloads<1, 2> = withArgs as any;
// Illegal call: 'withArgs' is not a substitute for 'assertion'
assertion(0, withArgs);

The last line is legal if and only if withArgs is substitutable for assertion, but that's the proposition we're testing when determining whether or not that's the case.

@niieani
Copy link
Author

niieani commented Aug 17, 2021

I'm sorry @RyanCavanaugh, I'm having trouble comprehending your example. In my eyes, it further demonstrates that the issue I described exists, i.e. assertion(0, withArgs) is illegal.

I understand that resolving In and Out is undecidable (because it recurses infinitely), however, my claim here is that at least the tuple/overload assignment should be legal, because both functions may be called only with an equivalent set of arguments. In other words:

// this is ok:
assertion(0, assertion);

// this is also ok:
withArgs(0, withArgs);

// and this SHOULD be okay, but isn't:
withArgs(0, assertion);

// the other way SHOULD also be okay (your example):
assertion(0, withArgs)

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript and removed Working as Intended The behavior described is the intended behavior; this is not a bug labels Aug 17, 2021
@RyanCavanaugh
Copy link
Member

It seems I've overthought this. The simplest form doesn't work:

type Overloaded = {
  (n: number): void;
  (s: string): void;
}
type Tupled = {
  (...args: [number] | [string]): void;
}
let x: Overloaded = null as any as Tupled;
let y: Tupled = null as any as Overloaded;

@fatcerberus
Copy link

fatcerberus commented Aug 18, 2021

Are those actually equivalent? I ask because:

const stuff: [number] | [string] = [ 812 ] as any;
tupled(...stuff);  // okay
overloaded(...stuff);  // error!

TS Playground

The root of the issue seems to be that TS wants to be able to "choose" a specific overload (even though it's going to be the same underlying function being called at runtime), while this decision doesn't need to be made for the tuple case. So they are not equivalent, even though they are.

@RyanCavanaugh
Copy link
Member

The root of the issue seems to be that TS wants to be able to "choose" a specific overload

This is a correct analysis of what's going wrong, but also a frequent source of complaint. Barring the problems of combinatorial explosion, this "should" work but doesn't.

@niieani
Copy link
Author

niieani commented Aug 25, 2021

Thanks @RyanCavanaugh, this is exactly the issue I was referring to! I noticed the label "Awaiting More Feedback", may I ask what type of feedback is expected? Or does this refer to some internal process?

@andrewbranch
Copy link
Member

It means we’d like to see how many people 👍 this issue and comment with other solutions and ideas before considering it more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants