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

### 💻 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

1reaction
ahejlsbergcommented, Sep 27, 2022

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

1reaction
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.

#### 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...
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”: ...
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.
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:...
A type guard is a function that allows you to narrow the type of an object to a more specific one by performing...

#### Troubleshoot Live Code

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