Skip to content

Equivalence between tuple containing union type and union type of tuples #39357

Closed
@ruizb

Description

@ruizb

Search Terms

tuple, union type, equivalence, distributivity, infer, 2322

Suggestion

I'd like TypeScript to make this possible:

  • [any, 'a' | 'b'] equivalent as [any, 'a'] | [any, 'b']

Use Cases

I'm writing a union type of tuples, which are containing values which types are also union types.

The thing is, some of these values are mutually exclusive, so I can't write a single tuple ['a' | 'b', 'c' | 'd'], since I don't want the ['a', 'd'] pair to exist (for example).

I have to manually write the possible pairs, e.g. ['a', 'c'], ['b', 'c'] and ['b', 'd'], which is fine. However, when building a value for this type, by making some checks with conditional branching to make sure the first element is either 'a' or 'b', the type guard applied doesn't allow me to build a value of this type.

This seems to work well with objects (so it's a workaround I guess?), but I'd like to avoid the struggle of finding names - let's be honest, the hardest part of our job - for each property. Since objects and tuples are isomorphic, tuples should behave the same and not throw an error (cf. example).

I'm assuming such feature could have a significant impact regarding TSC performance: if our tuples contain union types with tens of possible values, then finding all the possible tuples for all these union types could be quite heavy.

Example

TypeScript version: 3.9.2.

type A = [number, 'yes'] | [number, 'no'] | [string, 'maybe']
type B = 'yes' | 'no' | 'maybe'

declare const b: B
const a: A = (b === 'yes' || b === 'no') ? [12, b] : ['12', 'maybe']

Here we have a TS2322 error on a:

Type '[number, "yes" | "no"] | [string, "maybe"]' is not assignable to type 'A'.
  Type '[number, "yes" | "no"]' is not assignable to type 'A'.
    Type '[number, "yes" | "no"]' is not assignable to type '[number, "yes"]'.
      Type '"yes" | "no"' is not assignable to type '"yes"'.
        Type '"no"' is not assignable to type '"yes"'.

However, when doing the same with an object:

type C = { value: number, tag: 'yes' } | { value: number, tag: 'no' } | { value: string, tag: 'maybe' }

const c: C = (b === 'yes' || b === 'no') ? { value: 12, tag: b } : { value: '12', tag: 'maybe' }

There's no error on c, which is the behavior I'm looking for, but for a.

(Playground)

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptFixedA PR has been merged for this issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions