TypeScript Array of Tuples
Asked Answered
D

5

9

So I have this simple example:

type SomeTuple = [string, number];

const foo = (options: SomeTuple[]) => console.log(options);

const options = [
    ['first', 1],
    ['second', 2],
];

const otherOptions: SomeTuple[] = [
    ['first', 1],
    ['second', 2],
];

foo(options); // Error

foo(otherOptions); // OK

Basically there no more much to tell, I just can't figure out why TS keeps showing me error in case where I'am not providing type for options explicitly;

Playground

Thank you for your time!

Delisle answered 19/2, 2020 at 11:11 Comment(1)
You need to explicitly narrow down the type, otherwise Typescript assumes you're dealing with arrays, not tuples.Holst
A
6
type TypeOfOptions = typeof options[number] // (string | number)[]
type IsTypeOfOptionsSubsetOfSomeTuple = 
  TypeOfOptions extends SomeTuple ? true : false // false is not assignable to SomeTuple

It meansTypeOfOptions is just more general type then SomeTuple and naturally cannot be used as SomeTuple, this is exactly why you do have the error. You want [string, number] you get by automatic inference (string | number)[], clearly not the same.

Why its more general because TS type inference works like that, we can change the behavior by assert our type to more specific by as or : or make value constructor which will do that.

Below few possible options how to change the initial inference:

// value constructor
const makeOptions = (...a: SomeTuple[]): SomeTuple[] => a
const options1 = makeOptions(
    ['first', 1],
    ['second', 2],
) // a is SomeTuple[]

// using :
const options2: SomeTuple[] = [
    ['first', 1],
    ['second', 2],
];

// using as
const options3 = [
    ['first', 1],
    ['second', 2],
] as SomeTuple[]

foo(options1) // ok
foo(options2) // ok
foo(options3) // ok

Additional note about question in the comment. Generally type SomeTuple is a specification of type TypeOfOptions, it means that if you have value which is TypeOfOptions it can be also SomeTuple but does not must be. Lets check if such relation is true:

type IsSomeTupleSubsetOfTypeOfOptions = SomeTuple extends TypeOfOptions ? true : false // true

So yes SomeTuple is specific variant of TypeOfOptions. It means we can assert type of TypeOfOptions into SomeTuple saying we are narrowing to this specific case. But if we do that - TS will validate the value to the narrowed type right away, if its match, type assertion is successful.

Argueta answered 19/2, 2020 at 12:0 Comment(4)
Thank you for your answer! One thing that still bothers me is that in this particular scenario type SomeTuple = [string, number] behaves the same way type SomeArray = Array<string | number> is, right ?.Delisle
@Delisle - No, [string, number] is not the same as Array<string | number>. [string, number] specifically requires a two-element array with a string in the first element and a number in the second.Rummer
@T.J.Crowder, ahh yes, thank you! I misunderstood type TypeOfOptions = typeof options[number]Delisle
@Delisle added some more info in the answer hope it helpsArgueta
R
2

TypeScript infers the type of options to be (string | number)[][] — an array of arrays containing either strings or numbers. It does not infer [string, number][].

This is a case of needing to specify the type, as in your otherOptions:

const otherOptions: SomeTuple[] = [
    ['first', 1],
    ['second', 2],
];

TypeScript inference is really good, but it doesn't handle this specific case for you. It would be too limiting on options for it to assume you won't do options.push([1, 2]) in other code.

In this case, even as const doesn't help, because then the array entries are too specific (you can't assign readonly ['first', 1] to a SomeTuple).

Rummer answered 19/2, 2020 at 11:16 Comment(0)
F
2

By default ['first', 1] is an array with a string or a number, or one can say it's type is (string|number)[]. Note that an array does not specify it's length, nor does it specify which element is a string or which is a number. You can push an item with 3 or more values in it and TypeScript will be just fine with that.

In your second example (['first', 1]) it's an [string, number][]. Note that this differs from (string|number)[]. Now it knows it's an array with two elements, the first being a string, the second being a number. To be able to help figure TypeScript this out, an explicit declaration is required.

If it were to infer [string, number][] from the start, you wouldn't be able to add [123, 'foobar'], [123] or [] anymore. This would lead to more confusing errors as you would have to specify the type for each of those cases instead (fixing one issue, having other issues return). And if it would infer based on the entire file or codebase, compilation would take years for a complex program.

Fiance answered 19/2, 2020 at 11:17 Comment(0)
S
0

It looks like Typescript thinks that options is of type (string | number)[] so if you change your type definition like below, you should not get a compile error for options or otherOptions. Hope this helps.

type SomeTuple = (string | number)[];
Seibert answered 19/2, 2020 at 11:39 Comment(5)
I am not sure why the down vote @downvoter. Please explain.Seibert
Someone seems to be downvoting all answers to this question.Rummer
Thanks @T.J. Crowder. I upvoted everyone's answer to hopefully remove the negative points.Seibert
That said, please note that your redefinition of SomeTuple is very different from the OP's definition of SomeTuple. Presumably if they were happy for the string and number to be in any order or duplicated, they wouldn't have defined SomeTuple the way they did.Rummer
Hopefully only the ones worth upvoting. It's not useful to upvote on spec. :-)Rummer
C
-1

It looks like Tuple declaration is inferred incorrectly, so you can declare explicitly your type:

foo = (options: SomeTuple[]) => console.log(options);

options: SomeTuple[] = [
    ['first', 1],
    ['second', 2],
];

otherOptions: SomeTuple[] = [
    ['first', 1],
    ['second', 2],
];

A work stackblitz example can be seen here.

Coolie answered 19/2, 2020 at 11:46 Comment(2)
@T.J.Crowder ooops, sorry, I've misunderstood the question. I've edited the reply. Thanks!Coolie
I am not @downvoter referred to above, but I am down-voting this, because it neither answers the question nor adds any new information. The OP said "If I do A, error, if I do B, no error. Why?" and all this answer says is "Do B twice - no error."Ximenes

© 2022 - 2024 — McMap. All rights reserved.