Skip to content

Issue with Type guard when the variable is typed array with specified lengths #8473

Closed

Description

TypeScript Version: 1.8.10

Code

let value: number | [number] | [number, number];
let array: [number, number];

if (!Array.isArray(value)) {
    array = [value, value];
}
else if (value.length === 1) {
    array = [value[0], value[0]];
}
else {
    array = value;
}

Expected behavior:
On line 8 (inside the else if block), the array variable should be of type [number], and on line 11 (inside the else block) the array variable should be of type [number, number].

Other option is: in both code blocks, array should be any of the two types - [number] | [number, number].
In this case there should be a compile error on line 11 with the following message:

[ts] Type '[number] | [number, number]' is not assignable to type '[number, number]'. Type '[number]' is not assignable to type '[number, number]'. Property '1' is missing in type '[number]'.

And the workaround will be to explicitly cast value:

array = <[number, number]>value;

Actual behavior:
In both code blocks array is [number], and on line 11 there is a compile error:

[ts] Type '[number]' is not assignable to type '[number, number]'. Property '1' is missing in type '[number]'.

In this case we can explicitly cast value as shown above, but the error message is wrong.
If we replace value.length === 1 with isSingleLength(value), the behavior is the same.

function isSingleLength<T>(value: T[]): value is [T] {
    return value.length === 1;
}

Regards,
Kristiyan

Activity

  1. malibuzios commented on May 5, 2016

    @malibuzios

    (I'm using version 1.9.0-dev.20160505)

    Despite the apparent correct behavior, I don't believe that

    if (value.length === 1)

    is actually used as a guard here (I'm not aware of length assertions being used to narrow between unions of tuple types). It seems like the compiler would have inferred the same type even if the first else if would be replaced by a plain else:

    let value: number | [number] | [number, number];
    
    if (!Array.isArray(value)) {
        value; // type of 'value' is 'number'
    }
    else {
        value; // type of 'value' is '[number]'
    }

    This may have (possibly unintentionally) uncovered a different issue though: that perhaps in the 'else' block value should get type [number] | [number, number] instead of just [number].

    I have verified the same erroneous inference would also happen in the case where the first if would not use negation in the condition (i.e. if (Array.isArray(value))).

  2. mhegazy commented on May 5, 2016

    @mhegazy
    Contributor

    length is not used to narrow types. tuples are not guaranteed to have a fixed length. Tubles are just arrays, with known types for the first n elements. consider:

    var x: [number, number] = [1,2,3];
    x.length===2; // false;
  3. 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

Assignees

No one assigned

    Labels

    By DesignDeprecated - use "Working as Intended" or "Design Limitation" instead

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions