question-mark
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 of union with conditional type not working since 4.3

See original GitHub issue

Bug Report

🔎 Search Terms

type guard conditional type

🕗 Version & Regression Information

  • This changed between versions 4.2.3 and 4.3.2

⏯ Playground Link

Playground link with relevant code

💻 Code

interface A<S> {
    a: S
}
interface B<S> {
    b: S
}
interface C<S> {
    c: S
}

type U1<S> = A<S> | B<S> | C<S>
function f1<S>(u: U1<S>): u is B<S> | C<S> {
    return false
}
function test1<S>(x: U1<S>) {
    if (!f1(x)) {
        x.a //OK
    }
}

type Cond<S> = S extends number ? B<S> : C<S>
type U2<S> = A<S> | Cond<S>
function f2<S>(u: U2<S>): u is Cond<S> {
    return false
}
function test2<S>(x: U2<S>) {
    if (!f2(x)) {
        x.a //ERROR
    }
}

🙁 Actual behavior

The type guard is unable to narrow the type of x to A

🙂 Expected behavior

The type guard should narrow the type of x to A because it is not assignable to Cond<S>

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:3
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
andrewbranchcommented, Jul 23, 2021

This was caused by #43183, because the conditional type Cond<S> gets substituted for its constraint, B<S> | C<S>, in the type of x before it gets narrowed, so we are effectively trying to narrow from A<S> | B<S> | C<S>. Most of the time, that’s way more useful than trying to narrow with Cond<S>, because it means you can actually discriminate between the two branches of the conditional. Cond<S> just won’t relate with many useful types during narrowing. However, it will relate to itself, which is what your example is doing, but in 4.3 we can no longer tell that’s what’s happening since we substituted the constraint earlier on.

I don’t know if it would make sense for your real code @pedro-pedrosa, but a workaround would be to expand the conditional type to a union in your type predicate:

function f2<S>(u: A<S> | B<S> | C<S>): u is B<S> | C<S> {
    return false
}

The only way I can think to fix this without undoing the very useful improvements of #43183 would be to carry the original, non-substituted type through narrowing (in addition to the usually-more-narrowable constraint-substituted type), trying it if narrowing the constraint-substituted type had no effect. But that sounds like it could be expensive, for what feels like quite an edge case to me.

2reactions
RyanCavanaughcommented, Jun 17, 2021

This doesn’t meet our bar for a patch release, but I’ll move it to 4.4

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - Advanced Types - TypeScript
Type Guards and Differentiating Types. Union types are useful for modeling situations when values can overlap in the types they can take on....
Read more >
TypeScript 4.4 - Be on your guard - Instil Software
Celebrating TypeScript 4.4 by diving into type guards and control flow ... unions, type intersections, mapped types and conditional types.
Read more >
Type Guards and Control Flow Analysis in TypeScript 4.4
In this post, I'm going to delve into Type Unions since ... code would not work prior to TypeScript 4.4 since the guard...
Read more >
TypeScript Conditional Types and Type Guards - Stack Overflow
Typescript will not let you assign anything to a conditional type that still has free type parameter, it's just not supported.
Read more >
Announcing TypeScript 4.3 - Microsoft Developer Blogs
If you're not yet familiar with TypeScript, it's a language that builds on JavaScript by adding syntax for static types.
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 Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found