Skip to content

[bug] switch (true) { case {}: } passes type-check when case is variable #57253

Closed

Description

@niedzielski

🔎 Search Terms

"switch(true)" case object type

🕗 Version & Regression Information

  • This is a crash
  • This changed between versions ______ and _______
  • This changed in commit or PR _______
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about switch and {}
  • I was unable to test this on prior versions because _______

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.4.0-dev.20240131#code/M4dwlgLgxgFgBACggJwK4FMCUcDeAoOOAeiLgAEJgBadADwAd0oIblkB7ZAuKAQ2HS4AvgC5uJcpRoMmLdG07c+AuAHJeAIyiqxQvHijsAdsAhx2GgFZwAvMIPHTcU8ltrN2-aEixEKDNj4hBIU1HSMzKwcXITKghaWYsGkodIRcgoxPPyCLrp4QA

💻 Code

const obj = {}
const str = 'abc'

switch (true) {
  // @ts-expect-error
  case obj: // No good, this is not an error.
  // @ts-expect-error
  case str: // OK, this is an error.
}

🙁 Actual behavior

When a case is an object ({}) variable, the type-check passes. true !== {} so it will never execute.

🙂 Expected behavior

The type-check fails on the always false true === {} comparison.

Additional information about the issue

This appeared to be distinct from:

Activity

  1. DanielRosenwasser commented on Jan 31, 2024

    @DanielRosenwasser
    Member

    Same is true of any other operations that use comparability.

    Playground Link

    const obj = {}
    const str = 'abc'
    
    obj === str // works too

    The issue is that you can always write

    let x: {} = "hello!";

    because {} just describes any value other than null or undefined, and requires no members to be present. It doesn't exclude primitives. The way you could write this instead would be to use the non-primitive type called object.

    const obj: object = {}
    const str = 'abc'
    
    obj === str // does not work
  2. niedzielski commented on Jan 31, 2024

    @niedzielski
    Author

    Thank you, Daniel Rosenwasser (@DanielRosenwasser). In my real code, {} is actually intended to be the Record<never, never> pattern encouraged by the Deno linter. From your docs it sounds like this is always a better alternative for a record intended to have no entries?

  3. fatcerberus commented on Feb 1, 2024

    @fatcerberus

    You should probably be careful about bringing up linters banning {} around here, the maintainers have some strong opinions about that one :)

    a record intended to have no entries

    This kind of thing is really an antipattern in general in TS. You can't prevent { a: string } (or any other object type) from having additional properties (see #12936), so IMO trying to represent "guaranteed-to-be-empty object" in the type system is largely a fool's errand.

    btw, from the linked Deno page:

    Finally, Object and {} means "any non-nullish value"

    And technically, so does { toString(): string }, due to the structural typing machinery. 😉 This is a pretty weak argument against {}, especially if Object is being dragged into it.

    ...hmm, it seems I may have some opinions about this myself... 😜

  4. RyanCavanaugh commented on Feb 1, 2024

    @RyanCavanaugh
    Member

    The Deno linter is wrong to ban {}. There, I said it 🥰

  5. niedzielski commented on Feb 2, 2024

    @niedzielski
    Author

    Thanks all <3

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions