Mateus Pires
Mateus Pires

Reputation: 983

Why TS can't recognize the type of a second parameter based on the first one

I could see that TS can't recognize the type of a param if it's derived from another by using ternary and extends.

Simplifying what I'm trying to achieve:

enum Types {
  A = "a",
  B = "b",
}

type A = { a: string };

type B = {};

function fn<T extends Types.A | Types.B>(
  first: T,
  second: T extends Types.A ? A : B
) {
  if (first === Types.A) {
    second.a;
    // Property 'a' does not exist on type 'A | B'.
    //   Property 'a' does not exist on type 'B'.ts(2339)
  }
}

TS should have recognized that second has type A inside the if block. Since first is Types.A, second has type A, accordingly to the parameter type declared at the function signature.

I thought that the extends could be the problem here but I could check that TS can recognize the derived type correctly in the following case:

function fnWithMultipleTypes<T extends Types.A | Types.B>(arg: T) {}

function fn<T extends Types.A>(arg: T) {
    fnWithMultipleTypes(arg)
}

The arg from fn matches the type T from fnWithMultipleTypes.

Am I missing something?

Upvotes: 1

Views: 280

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250336

The main problem is that first === Types.A narrows first not the type parameter T. In fact there is no way to narrow a type parameter.

What you could do is use a union of tuple types, and destructure the parameters in the parameter list. In newer versions of TypeScript the compiler will be able to follow that the destructured parameters are related:

enum Types {
  A = "a",
  B = "b",
}

type A = { a: string };

type B = {};

function fn(
    ...[first, second]: 
            | [first: Types.A, second: A]
            | [first: Types.B, second: B]
) {
  if (first === Types.A) {
    second.a;
  }
}

Playground Link

Upvotes: 1

Related Questions