-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Description
🔎 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
💻 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.