Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Type guard affects type of variable in surprising way

See original GitHub issue

Bug 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:open
  • Created a year ago
  • Reactions:3
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

ahejlsbergcommented, Sep 27, 2022

Agreed about the footgun. We’ll continue to think about ways to improve this.

ahejlsbergcommented, Sep 23, 2022

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:

Note that one issue with favoring the asserted type is that CFA continues with that type after the conditional block. Though undesired, that’s an effect of how CFA works in the face of mutual subtypes. This behavior was already present for singleton types, but now also extends to union types.

So this is effectively a design limitation, but we’ll continue to think of ways in which to improve it.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Post

No results found

github_iconTop Related Hashnode Post

No results found