Anita
Anita

Reputation: 3146

Narrowing types in typescript array

I have an array of type MyArray.

type Maybe<T> = T | null;
type MyArray =  Maybe<Materials>[] | Maybe<Seasons>[] | Seasons[] | Materials[] | null | undefined;
type Value = Seasons | Materials;

Example code:


   if (!myArray?.length) {
      return;
    }

    const isValid = myArray.includes(value); // typescript is complaining about value

The issue is when I highlight value:

Argument of type Seasons | Materials is not assignable to parameter of type 'never'.

How can I get rid of this error?

Upvotes: 0

Views: 1011

Answers (1)

Benoit
Benoit

Reputation: 791

This error is actually legit, there is a mismatch because:

  • On one hand we have a Seasons | Materials (the value)
  • On the other hand we have (the array):
    • Either a box of Seasons[] that won't accept the value because it could be a Materials
    • Or a box of Materials[] that won't accept the value either because it could be a Seasons

We can try to fix it by using an intermediate includes function, such as:

type ElementOf<A extends any[], B> = B extends A[number] ? B : never

function includes<A extends any[], B>(list: A, element: ElementOf<A, B>): boolean {
    return list.includes(element)
}

This function ensures that:

  • Semantically, we keep either a box of Seasons[], or a box of Materials[], but not a box of (Seasons | Materials)[]
  • We have to use an array for the first argument
  • We have to use a value whose type may be contained in the array from the first argument
const isValid = includes(myArray, value)

You can check on this playground.


Previous answer:

I'm pretty sure we can fix this issue by simplifying the definition of the MyArray type:

type MyArray = Maybe<Array<Maybe<Materials> | Maybe<Seasons>>> | undefined
// or: type MyArray = Array<Maybe<Materials> | Maybe<Seasons>> | null | undefined
function foo(myArray: MyArray, value: Value): void {
    // myArray: Maybe<Array<Value | null>> | undefined (or, equivalent -> myArray: Array<Value | null> | null | undefined)
    if (!myArray?.length) {
        return
    }
    // myArray: Array<Value | null>
    const isValid = myArray.includes(value)
    console.log(isValid)
}

Somehow, when the type of myArray is a union type of arrays:

type MyArray =
  | Maybe<Materials>[]
  | Maybe<Seasons>[]
  | null
  | undefined

Instead of an array of a union type:

type MyArray = Array<Maybe<Materials> | Maybe<Seasons>> | null | undefined

Then the type inference when using the includes function is broken, so we end up with the type never instead of Maybe<Value> (or Value | null, same thing).

I don't know if this behavior is intended or a bug, it might be interesting to look for an open issue on the TypeScript repository mentioning this, or create a new one.

(playground)

Upvotes: 2

Related Questions