You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constfoo=(x : object)=>x;constbar=(x : NonNullable<unknown>)=>foo(x);// No errorconstbaz=(x: Exclude<unknown,null|undefined>)=>foo(x);// Errorbar(3);// No errorfoo(3);// Errorconstfoo2=(x: object|null|undefined)=>x;constbar2=(x: NonNullable<unknown>|null|undefined)=>foo2(x);// No errorconstbar3=(x: unknown)=>foo2(x);// Errorbar2('abc');// No errorfoo2('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.
The text was updated successfully, but these errors were encountered:
This isn't unique to {} fwiw - you can also assign primitives to e.g. { toString(): string } which is assignable to object for obvious reasons. This is a known unsoundness with object iirc, because object types accept anything that is coercible to that object.
Exclude<unknown, null | undefined> does absolutely nothing and resolves to unknown. Exclude<> is used to remove types from a union type, but unknown is not a union type. To represent a type "unknown, except null and undefined" you'd need Negated Types: #4196
π Search Terms
object unknown null
inconsistent behavior around unknown and object
object accepts primitive types through unknown
π Version & Regression Information
β― Playground Link
https://www.typescriptlang.org/play?ts=5.3.0-dev.20231024#code/MYewdgzgLgBAZiEMC8MAUAPGAuGIBGAVgKbBQCUKAfDBgNwCwAUKJLPgIYBOK6WuAOXACArgBsxHfGOIAeEWADWYEAHcwVSshoIQmcoxbhoMTgC9emXAFEMwMSIAmchcrVgANDDDixMAD4wCs5wAJZgxI6a1PCI+obMnFxoAMwGzLqp6UzMrCa6AEyWGLgEJGQB3r6VwcRhEY5aNPS5xuzcRahWMEJgohJSMvJKKuo0gT4SNWAh4ZFNsSAF8a1sptwpxbiuo2ALhSs5TEnLAORSwKfZB+f4l9lAA
π» Code
π Actual behavior
Only some of the cases in the code sample error, but
bar
andbar2
don't, despite passing a primitive tofoo
which expects anobject
.This is because
object
acceptsunknown
values once they're checked for null/undefined (i.e. it accepts{}
/NonNullable<unknown>
), and{}
accepts primitives, so primitives can be indirectly accepted asobject
.Relatedly, in the case of
unknown
,NonNullable<T>
ends up behaving differently thanExclude<T, null | undefined>
.π Expected behavior
All the cases in the code sample should error, including
bar
andbar2
when given primitive values.Generally,
object
is the type of all non-primitive values, so passing primitives to it should result in an error. SinceNotNullable<unknown>
(i.e.{}
) includes primitives, passing it asobject
should result in an error.Further,
NotNullable<Type>
is defined as "excluding null and undefined from Type", soExclude<unknown, null | undefined>
should behave the same asNonNullable<unknown>
(and converselyNonNullable<unknown> | null | undefined
should behave the same asunknown
).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.: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.The text was updated successfully, but these errors were encountered: