marongKim
marongKim

Reputation: 35

Typescript doesn't infer correct type with Generic extending string union type?

type Test = 'a' | 'b' | 'c';

function foo<T extends Test>(arg: T) {
  if (arg === 'a') {
    console.log(arg);
  }
}

like this.. I expect arg inferred 'a' in if block.

but ts infer as just T.

why this situation?

I think that extends keyword has something...

Upvotes: 1

Views: 656

Answers (1)

Maciej Sikora
Maciej Sikora

Reputation: 20162

The thing is that your type T is not Test but is a subset, we can say every type which can be used instead of T, and every for union means a type which has the same or less union members. Consider example type which extends Test:

type Test = 'a' | 'b' | 'c';
type SubTest = 'b' | 'c'

type IsSubTest = SubTest extends Test ? true : false // evaluates to true

As you can see SubTest doesn't have a member but it is assignable to Test, therefore we can use foo function with such a type.

const arg: SubTest = 'b'
foo(arg)

Everything good, no error. But that means that your condition arg === 'a' will never be met, as there is no member a in SubTest, that is why TS cannot assume that inside the condition we work with a, as in this situation this branch is totally not reachable and inside it will be type never. We can even write such function and check:

function foo(arg: SubTest) {
  if (arg === 'a') { // compilation error, condition will be always false
    console.log(arg);
  }
}

Ok but even though why TS is not narrowing to a, as the condition is clear anything which will pass it will be a, no doubt here!

Lets try reproduce manually the same type guard which exists in the original condition, and the condition checks if the left value say x (which extends from Test), equals to the right value say y(which extends Test)

function isTestMember<X extends Test, Y extends Test>(x: X, y: Y): x is Y { 
  return x == y  // error we cannot compare member of X with member of Y
}

As you can see such typeguard cannot be even written. As X and Y can not overlap, therefor the condition x === y is not valid for all members of X and Y

In summary TS cannot consider conditions on types which can not overlap as type guards, therefor type is not narrowed.

Upvotes: 1

Related Questions