Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript UnpackArrayType for inferred never[] and any[] works incorrectly

I was trying to use (infer R)[] for never and any types to unpack arrays.

type UnpackArrayType<T> = [T] extends [(infer R)[]] ? R : false;

But the result was unexpectable: Typescript neither cast false from "else" (non-array condition) nor detect inferred type R.

demo link

type UnpackArrayType<T> = [T] extends [(infer R)[]] ? R : false; // false type used as non-array-marker
type UA<T> = UnpackArrayType<T>; // just alias

type TypeTesting = {
    "never": UA<never>,     // unknown  - incorrect
    "never[]": UA<never[]>, // never - correct

    "any": UA<any>,         // unknown  - incorrect
    "any[]": UA<any[]>,     // any - correct

    "unknown": UA<unknown>, // false - correct
    "unknown[]": UA<unknown[]>, // unknown - correct

    "null": UA<null>,       // false - correct
    "null[]": UA<null[]>,   // null - correct

    "undefined": UA<undefined>, // false - correct
    "undefined[]": UA<undefined[]>, // undefined - correct

    "number": UA<number>,   // false - correct
    "number[]": UA<number[]>, // number - correct
}

Why any and never types unpacking as unknown?

like image 575
mont Avatar asked Jan 19 '26 14:01

mont


1 Answers

extends tests relationships between types and then attempts to extract the inferred location.

any and never both have the property that they are subtypes of any other type (any is even stranger as it is also a base type of any other type). So asking the question never extends T or any extends T will return true for any type T.

After the extends question is answered then can we consider the inference question and since there is no good way to extract R in [(infer R)[]] from any or never you end up with unknown.

You could special case these two universal types subtype:

type UnpackArrayType<T> = 
    [T] extends [never]  ? false: // nothing extends never except never itslef
    [T & 1] extends [T & 2]? false : // T & 1 extends T & 2 will only be true if T is any and the & 1 and & 2 get absorbed into any
    [T] extends [(infer R)[]] ? R : false; // false type used as non-array-marker
type UA<T> = UnpackArrayType<T>; // just alias

type TypeTesting = {
    "never": UA<never>,     // false  - correct
    "never[]": UA<never[]>, // never - correct

    "any": UA<any>,         // false  - correct
    "any[]": UA<any[]>,     // any - correct

    "unknown": UA<unknown>, // false - correct
    "unknown[]": UA<unknown[]>, // unknown - correct

    "null": UA<null>,       // false - correct
    "null[]": UA<null[]>,   // null - correct

    "undefined": UA<undefined>, // false - correct
    "undefined[]": UA<undefined[]>, // undefined - correct

    "number": UA<number>,   // false - correct
    "number[]": UA<number[]>, // number - correct
}

Playground Link

Note: To extract the element type from a type known to be an array you can also use T[number]

like image 191
Titian Cernicova-Dragomir Avatar answered Jan 23 '26 19:01

Titian Cernicova-Dragomir



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!