Skip to content

Unions of objects/dictionaries/maps and tuples generate type errors when numeric keys are present #13179

Closed

Description

@jameswilddev

TypeScript Version: 2.1.4

Code

type BoxOfBoolPairs = { [name: string]: BoxOfBoolPairs } | [boolean, boolean]

// Good.
const boxA : BoxOfBoolPairs = {}

// Good.
const boxB : BoxOfBoolPairs = { hey: [true, false], world: [false, true] }

// Good.
const boxC : BoxOfBoolPairs = { hey: [true, false], world: { earth: [true, true], notEarth: [false, false] } }

// Good.
const boxD : BoxOfBoolPairs = { "hey": [true, false], "world": [false, true] }

// Type error.
const boxE : BoxOfBoolPairs = { hey: [true, false], world: [false, true], "0": [false, true] }

Expected behavior:
boxE is seen as an object containing tuples "hey", "world" and "0".

Actual behavior:
[ts]
Type '{ "0": boolean[]; hey: [true, false]; world: [false, true]; }' is not assignable to type 'BoxOfBoolPairs'.
Type '{ "0": boolean[]; hey: [true, false]; world: [false, true]; }' is not assignable to type '[boolean, boolean]'.
Types of property '0' are incompatible.
Type 'boolean[]' is not assignable to type 'boolean'.

Activity

  1. sandersn commented on Apr 24, 2017

    @sandersn
    Member

    The error message is not the best here, which is a problem we have with unions from time to time. The compiler thinks things that have a "0" property are tuples, so it has compared boxE to the [boolean, boolean] branch of BoxOfBoolPairs, not the string indexer branch.

    That's because tuples are really just aliases for a family of types that look kind of like this:

    type Tuple2<T,U> = { '0': T, '1': U, [n: number]: T | U }
    type Tuple3<T,U,V> = { '0': T, '1': U, '2': V, [n: number]: T | U | V }
    // etc ...

    So what happens is that the compiler sees the '0', tries to infer T from [false, true], tries to infer U from the non-existent '1' property and creates a Tuple<boolean[], {}>. This fails to match Tuple<boolean, boolean> and creates an error.

    I'll go see why the compiler doesn't explore the string indexer branch and find a match.

  2. sandersn commented on Apr 25, 2017

    @sandersn
    Member

    Looks like contextual type applies to hey to give the type [true, false] but 0 has the contextual type boolean[], which is then not assignable to either side of BoxOfBoolPairs.

    The type of '0' is taken from the contextual type BoxOfBoolPairs, which finds a property '0': boolean in the [boolean, boolean] side of the union.

    The type of 'hey' is taken from the contextual type BoxOfBookPairs, which does not find a matching property hey, so falls back on the string indexer type.

    I need to think about a fix; finding a specific property name 0 should definitely outweigh the type of the string indexer. But it does the wrong thing in this case.

  3. sandersn commented on Apr 25, 2017

    @sandersn
    Member

    After talking to Ryan Cavanaugh (@RyanCavanaugh), I don't think there is a way to create values of type BoxOfBoolPairs in Typescript. Because of the way tuples and arrays are defined in Javascript as objects with number-string keys, it's not possible to distinguish the two sides of the union when given a value.

    The correct fix is to use the ADT pattern and add a discriminant field, something like kind, to distinguish between leaf and non-leaf instances of the type.

  4. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScript

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions