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

Viewed 63

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?

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]

Related