Type guard affects type of variable in surprising way
See original GitHub issueBug Report
🔎 Search Terms
type guard / fall through / narrow / change / lazy / evaluate
🕗 Version & Regression Information
- This changed between versions v4.7.4 and v4.8.2
⏯ Playground Link
Playground link with relevant code
💻 Code
type Identity<T> = {[K in keyof T]: T[K]};
type Self<T> = T extends unknown ? Identity<T> : never;
function is<T>(value: T): value is Self<T> {
return true;
}
type Union = {a: number} | {b: number} | {c: number};
function example(x: Union) {
if (is(x)) {}
if (is(x)) {}
if (is(x)) {}
if (is(x)) {}
if (is(x)) {}
if (is(x)) {}
if (is(x)) {}
if (is(x)) {}
return x;
// ^?
}
🙁 Actual behavior
The type of x
is “narrowed” to Identity<Identity<Identity<Identity<Identity<Identity<Identity<Identity<{a: number;}>>>>>>>> | Identity<Identity<Identity<Identity<Identity<Identity<Identity<Identity<{b: number;}>>>>>>>> | Identity<Identity<Identity<Identity<Identity<Identity<Identity<Identity<{c: number;}>>>>>>>>
It’s as if a variable gets narrowed to the union of the types of both sides of the type predicate, e.g.
if (isA(aOrB)) {
// `aOrB` gets narrowed to `A`
} else {
// `aOrB` gets narrowed to `Exclude<typeof aOrB, A>`
}
// `aOrB` gets narrowed to `A | Exclude<typeof aOrB, A>` but it should just be left alone
🙂 Expected behavior
The type of x
doesn’t change.
Issue Analytics
- State:
- Created a year ago
- Reactions:3
- Comments:7 (4 by maintainers)
Top Results From Across the Web
Documentation - Narrowing - TypeScript
It looks at these special checks (called type guards) and assignments, and the process of refining types to more specific types than declared...
Read more >TypeScript — Make types “real”, the type guard functions
TypeScript provides 2 way to do Type Guards: by using instanceof and typeof JavaScript operators; by creating “User-Defined Type Guards”: ...
Read more >Expanded type guard suggestions · Issue #4868 - GitHub
typeof type guards are hard-coded because they originally were there only to support primitives, and primitives themselves are hard-coded.
Read more >Can I write a type guard that asserts multiple invariants?
Yes you can and you almost had it exactly right in pseudocode interface A { a?: number; b?: string; hasAandB(): this is {a:...
Read more >TypeScript: How Type Guards Can Help You Get Rid of 'as'
A type guard is a function that allows you to narrow the type of an object to a more specific one by performing...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Agreed about the footgun. We’ll continue to think about ways to improve this.
This is an effect of #50044. The issue here is that the argument type and the asserted type are subtypes of each other, and therefore appear interchangeable in control flow analysis. From the PR:
So this is effectively a design limitation, but we’ll continue to think of ways in which to improve it.