microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.73k stars 12.45k forks source link

Union tuple type in rest argument can't accpect less argument. #48663

Open beanbean97 opened 2 years ago

beanbean97 commented 2 years ago

Bug Report

🔎 Search Terms

source has but target allows only

🕗 Version & Regression Information

⏯ Playground Link

Playground link with relevant code

💻 Code

const func: (...args: [number, string] | [string, number]) => void = (item) => { }

🙁 Actual behavior

throw error

Type '(item: string | number) => void' is not assignable to type '(...args: [number, string] | [string, number]) => void'.
  Types of parameters 'item' and 'args' are incompatible.
    Type '[number, string] | [string, number]' is not assignable to type '[item: string | number]'.
      Type '[number, string]' is not assignable to type '[item: string | number]'.
        Source has 2 element(s) but target allows only 1.

🙂 Expected behavior

no error

MartinJohns commented 2 years ago

Duplicate of #45972. Used search terms: `source has but target allows only"

jcalz commented 2 years ago

I'm... not sure this is a duplicate of that, since there are no leading rest elements here to make the analysis weirder. Maybe the same underlying issue is happening in both places?

Anyway this one feels more like a bug to me, since the following is fine:

const f1: (x: string | number) => void = x => { }; 
const f2: (x: string | number, y: string | number) => void = f1; // okay
const f3: (...args: [number, string] | [string, number]) => void = f2; // okay

And while transitivity of assignability isn't the be-all and end-all for TypeScript, it's nice to be able to reason about things this way when possible.

graphemecluster commented 2 years ago

Sorry to copy my https://github.com/microsoft/TypeScript/pull/49218#issuecomment-1140416519 here:

I think the easiest way to solve all the related issues is to wrap all the arguments into a tuple with rest syntax, i.e. item => {} should become (...[item]) => {} and ([first, ...rest], foo, ...args) => {} should work just like (...[[first, ...rest], foo, ...args]) => {}.

cdauth commented 3 months ago

Not exactly the same issue, but probably the same root cause:

const func: (arg1: string, arg2?: string) => void = (...args: [string] | [string, string]) => undefined; // Error

Also:

type Func = (...args: [string] | [string, string]) => void;
type Test = Func extends (arg1: string, ...args: any[]) => any ? true : false; // false

// Workaround:
type Test = Func extends (...args: infer Args) => any ? (Args extends [string, ...any[]] ? true : false) : false; // true