Skip to content

Inference to unions containing void behave differently from 4.2 on #42786

Closed

Description

Bug Report

🔎 Search Terms

void, union, user-defined type guard

🕗 Version & Regression Information

v4.2.0-dev.20210207. It stayed this way in 4.3.0-dev.20210211

  • This changed between versions 4.1 and 4.2 RC
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about void

⏯ Playground Link

Playground link with relevant code

💻 Code

function isDefined<T>(value: T | undefined | null | void): value is T {
  return value !== undefined && value !== null;
}

declare const foo: string | undefined;

if (isDefined(foo)) {
  // 4.2: foo is string | undefined
  // 4.1: foo is string
  console.log(foo.toUpperCase()); 
}

🙁 Actual behavior

Compile-time error. foo is string | undefined

🙂 Expected behavior

No compile-time error. foo is string

Activity

  1. added
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    and removed
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    on Feb 13, 2021
  2. changed the title [-]Unions containing `void` behave differently from 4.2 on[/-] [+]Inference to unions containing `void` behave differently from 4.2 on[/+] on Feb 13, 2021
  3. DanielRosenwasser commented on Feb 13, 2021

    @DanielRosenwasser
    Member

    Likely involves the changes from #42353, and I have theories of what we probably do, but Anders Hejlsberg (@ahejlsberg) will surely have a better idea.

  4. ahejlsberg commented on Feb 15, 2021

    @ahejlsberg
    Member

    Yeah, this one is an effect of us now reducing void | undefined to just void. With that reduction, the type of the value parameter becomes T | null | void, but then the value !== undefined test in the body doesn't remove void because we sometimes think of void possibly being any type. So, we're trying to have our cake and eat it too, and it isn't working.

    We've had many discussions about our strange void semantics, but we have yet to come up with something better that doesn't break the world. However, in the spirit of at least not breaking things, we could dial back and only do the void | undefined to void reduction in cases where we perform subtype reduction. That was our previous behavior.

  5. DanielRosenwasser commented on Feb 17, 2021

    @DanielRosenwasser
    Member

    Anders Hejlsberg (@ahejlsberg) is that an option? Is it just a matter of special-casing how we invoke literal type reduction? That might be worth it.

  6. ahejlsberg commented on Feb 17, 2021

    @ahejlsberg
    Member

    is that an option? Is it just a matter of special-casing how we invoke literal type reduction?

    Yes, it's an option and it isn't particularly hard to implement. We'd just make the void | undefined to void reduction in the literal type reduction pass conditional on whether we are also (in a subsequent pass) performing subtype reduction.

  7. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions