8000 "object" can be made to accept primitives due to handling of "{}" · Issue #56205 · microsoft/TypeScript · GitHub
[go: up one dir, main page]

Skip to content
"object" can be made to accept primitives due to handling of "{}" #56205
@tom-wild-moose

Description

@tom-wild-moose

🔎 Search Terms

object unknown null
inconsistent behavior around unknown and object
object accepts primitive types through unknown

🕗 Version & Regression Information

  • This changed between versions 4.7 and 4.8

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.3.0-dev.20231024#code/MYewdgzgLgBAZiEMC8MAUAPGAuGIBGAVgKbBQCUKAfDBgNwCwAUKJLPgIYBOK6WuAOXACArgBsxHfGOIAeEWADWYEAHcwVSshoIQmcoxbhoMTgC9emXAFEMwMSIAmchcrVgANDDDixMAD4wCs5wAJZgxI6a1PCI+obMnFxoAMwGzLqp6UzMrCa6AEyWGLgEJGQB3r6VwcRhEY5aNPS5xuzcRahWMEJgohJSMvJKKuo0gT4SNWAh4ZFNsSAF8a1sptwpxbiuo2ALhSs5TEnLAORSwKfZB+f4l9lAA

💻 Code

const foo = (x : object) => x;
const bar = (x : NonNullable<unknown>) => foo(x); // No error
const baz = (x: Exclude<unknown, null | undefined>) => foo(x); // Error

bar(3); // No error
foo(3); // Error

const foo2 = (x: object | null | undefined) => x;
const bar2 = (x: NonNullable<unknown> | null | undefined) => foo2(x); // No error
const bar3 = (x: unknown) => foo2(x); // Error

bar2('abc'); // No error
foo2('abc'); // Error

🙁 Actual behavior

Only some of the cases in the code sample error, but bar and bar2 don't, despite passing a primitive to foo which expects an object.

This is because object accepts unknown values once they're checked for null/undefined (i.e. it accepts {} / NonNullable<unknown>), and {} accepts primitives, so primitives can be indirectly accepted as object.

Relatedly, in the case of unknown, NonNullable<T> ends up behaving differently than Exclude<T, null | undefined>.

🙂 Expected behavior

All the cases in the code sample should error, including bar and bar2 when given primitive values.

Generally, object is the type of all non-primitive values, so passing primitives to it should result in an error. Since NotNullable<unknown> (i.e. {}) includes primitives, passing it as object should result in an error.

Further, NotNullable<Type> is defined as "excluding null and undefined from Type", so Exclude<unknown, null | undefined> should behave the same as NonNullable<unknown> (and conversely NonNullable<unknown> | null | undefined should behave the same as unknown).

Additional information about the issue

This inconsistent behavior was introduced in TS 4.8.

Note that while the examples above may seem kind of esoteric, this also appears in the much more common case where {} is implicitly inferred thanks to a null check, e.g.:

const foo = (x : object) => x;
function bar(x : unknown) {
  if (x != null) foo(x); // No error since v4.8
}

If I had to guess, I suppose it might be due to conflating {}'s actual meaning (which appears to be "any non-nullish value" - that is, including primitives) vs. what it visually looks like, which is an object.

Metadata

Metadata

Assignees

No one assigned

    Labels

    UnactionableThere isn't something we can do with this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0