Search code examples
arraystypescriptnarrowing

type narrowing in ternary


In myFunction below, I accept a parameter which is either a string or an array of strings, and I normalize it to an array in the function body:

export const myFunction = <T extends string | string[]>(
  myParam: T
) => {
  let myStringArray = (Array.isArray(myParam) ? myParam : [myParam])
}

I expected Array.isArray to narrow the type in the branches of the ternary operator to either string or string[] so that the final type of myStringArray would be string[]. Instead, the final type is more complicated: (T & any[]) | T[].

TypeScript Playground

I noticed, however, that if I restrucure the code to use an if-else instead, the type-narrowing works perfectly:

export const myFunction = <T extends string | string[]>(
  myParam: T
) => {
  let myStringArray;
  if (Array.isArray(myParam)) {
    myStringArray = myParam;
  } else {
    myStringArray = [myParam];
  } 
  console.log(myStringArray);
}

The type of myStringArray on the last line is string[], as expected.

TypeScript Playground

Is it possible to get the type-narrowing to work with the ternary operator? Are the two expressions not equivalent?


Solution

  • The TS compiler does not evaluate the logic of ternary expressions when producing a return type.

    For a simpler example:

    const x = true ? 1 : 0 // TS says 1 | 0
    

    Despite the 0 value being impossible, TS does not discard it.

    The only analysis TS performs on ternary expressions is type refinement within them:

    enter image description here enter image description here enter image description here

    This has been previously reported as marked as working as intended: https://github.com/microsoft/TypeScript/issues/39550