Skip to content

Implicit type conversion of tuple [...T[]] to array T[] #29311

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

Closed
egormkn opened this issue Jan 8, 2019 · 4 comments · Fixed by #52617
Closed

Implicit type conversion of tuple [...T[]] to array T[] #29311

egormkn opened this issue Jan 8, 2019 · 4 comments · Fixed by #52617
Labels
Bug A bug in TypeScript Domain: Contextual Types The issue relates to contextual types
Milestone

Comments

@egormkn
Copy link

egormkn commented Jan 8, 2019

TypeScript Version: 3.3.0-dev.20190108

Search Terms: spread, rest, type conversion, tuple, array length

Code

type test1 = [...number[]]           // number[]     <-- Is it a feature?
type test2 = [number, ...number[]]   // [number, ...number[]]
type test3 = [number?, ...number[]]  // [number?, ...number[]]

type fixed1 = test1 & { length: 2 }  // number[] & { length: 2 }
type fixed2 = test2 & { length: 2 }  // [number, ...number[]] & { length: 2 }
type fixed3 = test3 & { length: 2 }  // [number?, ...number[]] & { length: 2 }

let var1: fixed1 = [0, 0]
/*  error TS2322: Type 'number[]' is not assignable to type 'fixed1'.
    Type 'number[]' is not assignable to type '{ length: 2; }'.
        Types of property 'length' are incompatible.
        Type 'number' is not assignable to type '2'. */
let var2: fixed2 = [0, 0] // OK
let var3: fixed3 = [0, 0] // OK

Expected behaviour:
Expected [...number[]] and [number, ...number[]] to have the same behaviour except that the latter requires at least one element in tuple.

Actual behavior:
[...number[]] is implicitly converted to number[] with the length: number field, that is incompatible with length: 2

Error TS2322: Type 'number[]' is not assignable to type 'fixed1'.
    Type 'number[]' is not assignable to type '{ length: 2; }'.
        Types of property 'length' are incompatible.
        Type 'number' is not assignable to type '2'.

Playground Link: 🔗

Related Issues: Haven't found anything about the same problem.

@weswigham weswigham added Bug A bug in TypeScript Domain: Contextual Types The issue relates to contextual types labels Jan 8, 2019
@weswigham
Copy link
Member

In theory they're the same, but in practice the simplification we do deletes the fact that the type should be a tuple at all, which causes the error. So, because of that, it might be best not to perform the inlining so as to preserve the tupleness of the type.

@ahejlsberg
Copy link
Member

We definitely want to keep the simplification of [...T[]] to T[]. To fix the issue we should instead consider types that are array-like and have a length property of a numeric literal type to be tuple-like. That would cause the contextual type to be tuple-like so we'd infer a tuple type for [0, 0].

@weswigham
Copy link
Member

weswigham commented Jan 11, 2019

To fix the issue we should instead consider types that are array-like and have a length property of a numeric literal type to be tuple-like

And/or having numeric-named members (eg, 0: Thing), as is our internal tuplish criteria?

@Andarist
Copy link
Contributor

Andarist commented Feb 4, 2023

The simplification at hand makes it hard to create a tuple constraint:

declare function test<T extends [...any[]]>(a: T): T

const res1 = test([1, 2]) 
//        ^? actual: number[], expected: [number, number]

There are different ways of dealing with this:

  • the old weird trick of T[] | [T]
  • [T, ...T[]] - it doesn't allow an empty tuple
  • [T?, ...T[]] - it might look OK at first glance but when passing T[K] through a mapped type to some other type, it will assume that it might be undefined too, so this solution has some unintended caveats
  • const type params in 5.0+ - this implies constness which isn't always desirable
  • using the spread at argument position: declare function test<T extends any[]>(a: [...T]): T. This works exactly as expected but requires moving the "hint" to the argument position

It seems that the most "complete" way of defining a tuple constraint using tuple types is... [T, ...T[]] | []. It's quite cumbersome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Domain: Contextual Types The issue relates to contextual types
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants