Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Narrow the type of the first array element based on the length of the array?

I’m working with an array for which the type of the first element is known, if the array has length one, and which could be one of two types, otherwise; i.e. something like the following:

type NarrowableArray<TKnown, TGenereal> = [TKnown] | [TGeneral, TGeneral, ...TGeneral[]]

type Foo = number | number[]

let a: NarrowableArray<number, Foo> = Math.random() > 0.5 ? [3] : [3,4,5]

if (a.length === 1) {
    a 
    console.log(a[0] + 2) //Error > TS can't tell `a[0]` has type 'number'. 
}

Is this expected? Is there a work around?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

The problem is that the length property of an open-ended tuple type is number, as described in microsoft/TypeScript#24897; TypeScript doesn’t have accurate range types (as requested in microsoft/TypeScript#15480) to express "any number except 1" or {length: WholeNumber & GreaterThan<1>}.

So unfortunately just checking the length property for 1 won’t be able to discriminate the union, since both union members have a length to which 1 is assignable.

There are other narrowing methods that work directly with tuples more naturally, but if you want to perform a length check yourself you might need to refactor to a user-defined type guard function of the form:

function hasLengthOne(x: any[]): x is [any] {
    return x.length === 1;
}

And then call that instead of directly checking the length:

function aFunction(a: NarrowableArray<number, Foo>) {
    if (hasLengthOne(a)) {
        a // (parameter) a: [number]
        console.log(a[0] + 2); // okay
    } else {
        a // (parameter) a: [Foo, Foo, ...Foo[]]
    }
}

That works because the compiler is able to filter NarrowableArray<number, Foo> by [any]; we’re circumventing the length issue entirely.

Playground link to code

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading