2

I have read this answer https://stackoverflow.com/a/45486495/1108891, which demonstrates tuple type inference. After some experimentation, I came across this scenario.


When our tuple function has T extends string[], the tuple has string literal types.

export const tuple_0 = <T extends string[]>(...args: T): T => args;

const ALL_SUITS_0 = tuple_0('hearts', 'diamonds', 'spades', 'clubs');

type T0 = typeof ALL_SUITS_0; // ["hearts", "diamonds", "spades", "clubs"]

On the other hand, with T extends any[], the tuple has string types (not literals).

export const tuple_1 = <T extends any[]>(...args: T) => args;

const ALL_SUITS_1 = tuple_1('hearts', 'diamonds', 'spades', 'clubs');

type T1 = typeof ALL_SUITS_1; // [string, string, string, string]

Why do we lose the literal types in the latter case?


I suspect this has something to do with how many specificity steps the type inferences allows itself to take. That is, any is one step away from string, and string is one step away from 'some-string-literal'. Does the type inference only allow itself to take one step?

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467

1 Answers1

3

This is not specific to tuples necesarily. Typescript will infer a literal type for a generic parameter if the type parameter has a constraint that can have literal types. This behavior was introduced by this PR

From the PR:

During type argument inference for a call expression the type inferred for a type parameter T is widened to its widened literal type if [...] T has no constraint or its constraint does not include primitive or literal types

In the example from your question, since string is primitive, we do not widen, and since any is not primitive, we do widen.

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • So it is that the `any` type can be a non-literal, such as a `Date`, and so TypeScript literal inference does not apply? – Shaun Luttin Dec 20 '18 at 02:53
  • When you write, " ...has a constraint that can have literal types," do you mean "... has a constraint that must have literal types"? I ask because it seems that `extends any` can have literal types. – Shaun Luttin Dec 20 '18 at 02:54
  • @ShaunLuttin Yes. `any` can be anything so `T` will be not be inferred to a literal type (or as the PR describes it, it will be widened to the non literal type). – Titian Cernicova-Dragomir Dec 20 '18 at 02:58
  • @ShaunLuttin I'm not sure how to phrase it exactly, to infer a lietrral type the constraint has to be `string`, `number` `boolean` or an enum. – Titian Cernicova-Dragomir Dec 20 '18 at 02:59
  • I see it also has something to do with mutability. For instance: `ALL_SUITS_0[0] = 'foo';` fails whereas `ALL_SUITS_1[0] = 'foo';` succeeds. – Shaun Luttin Dec 20 '18 at 03:10
  • 1
    @ShaunLuttin you can assign a value to `ALL_SUITS_0[0] = 'foo'` it just always has to be `'hearts'`. It's not immutable in a true sense, there is just one value you can assign – Titian Cernicova-Dragomir Dec 20 '18 at 03:14
  • @ShaunLuttin added the quote, feel free to add any other infor from the PR you find relevant :) – Titian Cernicova-Dragomir Dec 20 '18 at 03:26