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

Error when spreading a union of tuples in a call #42508

Open
sarfata opened this issue Jan 27, 2021 · 6 comments
Open

Error when spreading a union of tuples in a call #42508

sarfata opened this issue Jan 27, 2021 · 6 comments
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@sarfata
Copy link

sarfata commented Jan 27, 2021

Bug Report

🔎 Search Terms

destructure array typescript union

🕗 Version & Regression Information

I used the playground to test different versions and it looks like this has always occured.

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about "Type Systems behavior", "Type Guards", "Functions".

⏯ Playground Link

Playground link with relevant code

💻 Code

const t = [4,20] as const
const t2 = [4, 20, 30] as const
const t3 = [10, 10] as [number, number] | [number, number, number]

// Works
new Date().setHours(...t)
new Date().setHours(...t2)

// Error: Expected 1-4 arguments, but got 0 or more.
new Date().setHours(...t3)

🙁 Actual behavior

Error: Expected 1-4 arguments, but got 0 or more.

The array type passed as a length of 2 or 3 so the message "got 0 arguments" is wrong.

🙂 Expected behavior

I expect the compiler should be able to recognize that the array type can only have length 2 or 3 and would not throw an error.

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Jan 29, 2021
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Jan 29, 2021
@mdziekon
Copy link

I think I've seen either same, or very similar problem in my code once I've upgraded from TS 3.9 to TS 4.0 / TS 4.1 (both versions report the same issue with my code). This was not an issue before the upgrade, so the last known working version (for me at least) is 3.9.7.


My code looks something like this (it's a React app, the snippet comes from a wrapper component that attaches its own additional handler on the passed onChange handler):

type BaseComponentProps = React.ComponentProps<typeof BaseComponent>;

const handleClick = (...args: Parameters<NonNullable<BaseComponentProps['onChange']>>) => {
    onChangeAdditionalHandler(...args);

    onChange?.(...args);
};

With this code I get the following error on the onChangeAdditionalHandler call line:

Expected 1 arguments, but got 0 or more.  TS2556

even though the args is properly inferred as:

type Args = typeof args;
// -> [ event: React.ChangeEvent<HTMLInputElement>] | [ event: React.ChangeEvent<HTMLInputElement>]

(the union type comes from how React typings are created for the base component)


This can be simplified and reproduced with the following code:

const testFunction = (thisVal: () => void) => {
    console.log(thisVal);
};

const testCase = () => {
    type MyArgs =
        | [ handler: () => void ]
        | [ handler: () => void ];

    const args = [ () => undefined ] as MyArgs;

    testFunction(...args);
};

It looks like union types freak out the arguments spreading, even though the applied types are basically the same thing. Even the tuples have the same length, so it shouldn't be a problem.

@sandersn sandersn assigned sandersn and unassigned andrewbranch Apr 27, 2021
@sandersn sandersn changed the title Cannot destructure array to pass arguments when the array type is a union of array Error when spreading a union of tuples in a call Apr 27, 2021
@sandersn
Copy link
Member

Related to #42891; I'll look at both at the same time.

@typescript-bot typescript-bot added the Fix Available A PR has been opened for this issue label Apr 29, 2021
@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Jun 18, 2021
sandersn added a commit to sandersn/TypeScript that referenced this issue Jul 28, 2023
Previously, only calls with tuples were allowed, not unions of
tuples. For example, this already works:

```
f(string, [number, string], string) ==> f(string, number, string, string)
```

But this does not:

```
f(string, [string] | [number, number]) ==> (f, string, string | number, number | undefined)
```

This PR allows union types like these *as the last argument*. It's
possible to allow them anywhere, but quite a bit more complicated. And
the code is already complicated enough: getEffectiveCallArguments now
needs to return the minimum number of arguments as well as the argument
list (which is the maximum number of arguments).

Also, this transformation should not happen when the tuple union
argument is passed to a tuple union rest parameter: `[number] |
[string]` is assignable to `[number] | [string]` but the transformed
`number | string` is not. Checking for this requires passing
around even more information.

Bottom line: I'm not sure the complexity is worth the new code.
However, if this is a good idea, there are 3 things that need to be cleaned up:

1. More precise error messages--maybe. Right now error messages are
reported in terms of the maximum number of arguments:
2. Allow tuples with trailing `...T` types. Not done or tested, but
straightfoward.
3. Find a more elegant way to return the minimum number of arguments.

Fixes microsoft#42508
Supercedes microsoft#43882, but does less than half of that PR, in less than half
the code.
@sandersn
Copy link
Member

I tried again with a smaller fix, although I'm still not sure it's a good enough fix.

@sandersn
Copy link
Member

After discussing this fix in the design meeting, we decided to not take #55185 either. It's too ad-hoc for the complexity of its code.

If/when we try to reform overload resolution's interaction with type parameter inference and contextual typing, we should address this bug at the same time. Specifically, this example needs the ability to roll back inferences, same as overload type parameter inference. Then the fix here would be to make tuples generate multiple argument lists instead of trying to produce a single argument list. Each argument list can be applied against each overload (or a single signature if there's only one.)

@jason-ha
Copy link

I believe I have another case for this issue.
A fix is needed to truly support "perfect" forwarding when target function has rest that is union.

function target(arg1: string, ...numsOrStrs: number[] | string[]): void {}

function wrapper(...args: Parameters<typeof target>): ReturnType<typeof target> {
    return target(...args); // TS2556 A spread argument must either have a tuple type or be passed to a rest parameter.
}

In this case Parameters<...> is union of tuples: [arg1: string, ...numsOrStrs: number[]] | [arg1: string, ...numsOrStrs: string[]].

Either Parameters needs to produce tuple as original parameter type ([arg1: string, ...numsOrStrs: number[] | string[]]) or spread needs to recognize unions of tuples.

playground that has a little extra

@nikelborm
Copy link

Why not just use this?

const t3 = [10, 10] as [number, number, number?]
new Date().setHours(...t3)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
9 participants