Closed
Description
Bug Report
π Search Terms
type narrowing, generics
π Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about type narrowing
β― Playground Link
π» Code
enum Enum {
A = 1,
B = 2
}
type EnumPair = {
[Enum.A]: {
x: number;
};
[Enum.B]: {
y: number;
};
}
const makePair = <T extends Enum>(first: T, second: EnumPair[T]) => {
if (first === Enum.A) {
console.log(second.x)
}
}
π Actual behavior
Property 'x' does not exist on type '{ x: number; } | { y: number; }'.
Property 'x' does not exist on type '{ y: number; }'.(2339)
π Expected behavior
I expected typescript to narrow the type of T to the specific enum being used (due to the equality operator), and therefore the enum pair too.
Activity
jcalz commentedon Jul 18, 2023
#24085, #27808, etc
nmain commentedon Jul 18, 2023
But if you called it like this, your proposed narrowing would be invalidated:
iscekic commentedon Jul 18, 2023
Calling it appropriately narrows down the type correctly, however I also expected the narrowing to happen inside the if equality block in the function itself.
Since this seems to be a long-standing issue, as well as a duplicate, feel free to close it.
fatcerberus commentedon Jul 18, 2023
You're assuming
makePair
is always going to be called directly with a literal1. Lots of people make this assumption when they write things likeT extends Enum
orT extends keyof U
, but the compiler won't cooperate, because it knows that this can happen:...and there's no way, at the type level, to prevent that from happening. The lack of narrowing inside the function is therefore intentional--it wouldn't be sound to do so. Hence #27808 which would give you a way to explicitly tell the compiler that
T
can't be a union of severalEnum
values and make the above construction invalid.Footnotes
This is why I dislike using explicit type arguments as a counterexample, as in general the assumption people tend to make is that the unsound cases can never be inferred--which isn't true. β©
fatcerberus commentedon Jul 18, 2023
See also #30581 and related issues, which are fundamentally about a way to communicate to TS that
T
andEnumPair[T]
are correlated whenT
is a union type.iscekic commentedon Jul 18, 2023
I understand why this is the case for the function body in general, but I don't get how this is true within the strict equality branch.
fatcerberus commentedon Jul 18, 2023
See my code example above. Having
first === Enum.A
doesnβt guarantee you have the correct object forsecond
; the type system isnβt currently powerful enough to make this guarantee.iscekic commentedon Jul 18, 2023
I get it - since
first
isEnum.A | Enum.B
and second isEnumPair[Enum.A] | EnumPair[Enum.B]
, just because I narrowedfirst
doesn't meansecond
is narrowed too (the co-dependency between the two wasn't communicated to the compiler π).I worked around the issue by casting
second
.Thank you for the explanations everyone! I'm going to close the ticket, since it seems like a duplicate to me.
craigphicks commentedon Jul 19, 2023
An object argument in a template function will have the members appropriately correlated -
but as you can see the extra args in the function call are not detected as errors.
Using function overloads will detect the extra args in the function call -
playground