Closed dblachut-adb closed 2 years ago
https://github.com/microsoft/TypeScript/wiki/FAQ#why-are-all-types-assignable-to-empty-interfaces
More information to clear your confusion: https://mariusschulz.com/blog/the-object-type-in-typescript
Okay so {}
is an empty interface and that is the root of all evil. :wink:
Makes sense, but can also be a source of errors, for example:
const X = {} // X: {}
const variableWithSameTypeAsX: typeof X = 1;
// typescript allows comparison of "number" and "object" which does not make sense
if (variableWithSameTypeAsX == X) {
}
In other words: IMHO primitives shall not be assignable to empty interface.
It's intentional. You basically shouldn't be using {}
, unless you understood it and have a good reason for it. It means "any type, except undefined
and null
."
IMHO primitives shall not be assigned to empty interface.
You're entitled to your own opinion, but it's working as intended. :-) See #44874, specifically https://github.com/microsoft/TypeScript/issues/44874#issuecomment-874892210.
Well, I think it does not make sense because primitives can't have methods and properties, so maybe they shouldn't be assignable to any interface. Thanks for the link and explanation though!
(the length
in string comes from autoboxing feature and you can't add any property to a primitive)
TypeScript doesn't really make the distinction between a primitive and its boxed value. string
is a subtype of String
, so if String
extends {}
, so will string
.
Maybe such distinction should exist for the reasons I mentioned - you cannot have any properties added to a primitive.
So the only possible assignment of primitive to an interface (except empty one which accepts everything) is when this interface have properties defined in it's boxed prototype.
Boxed prototypes are well-defined so maybe there is a way to restrict primitive to interface assignment while still allowing such code:
interface L { length: number }
const X: L = "str";
I'm sure the TypeScript team has thought about this the numerous times this issue came up. Like Ryan mentioned, it has implications on subtype reduction.
But I fail to see the general issue this causes. Can you provide w real example? Your earlier example where you mentioned the comparison between number and object is fine, because your variable typed {}
can either store a number or an object.
The whole argument is about whether number
and other primitives should be stored in variable typed {}
(empty interface).
Here is similar example:
const X = 1;
const Y = {};
const Z = {
prop: true,
};
if (X == Y) { // ok
}
if (X == Z) { // error
}
Typescript in general disallows comparing between two different types as it does not make sense and can be erroneous, but it is allowed when the object is empty. In general inconsistency and confusion should be avoided when possible. As you can see this case can be confusing not only to me which could be an argument to make a change in the current behavior.
Unfortunately I don't have any real example except the one provided (that I have came up with) and TBH it looks like I have been misusing {}
as a shortcut to object
type since it looks like object literal.
So in general I don't insist on changing it but IMHO if confusion and inconsistency can be avoided it definitely should.
I don't know if {}
being different from all other object types is a design decision or an oversight, but it's more or less a feature now: people use it to represent any non-nullish value. There's no other type that represents the same thing.
Yeah I guess you are right, it cannot be simply changed.
I don't see how it's being different. There only seems to be the wrong assuming that the type {}
means it's an object. Neither does interface
or type
mean you're dealing with an object.
Here's a proof of why string
is { }
x
has a property k
if x.k
is a legal expression"hello world".length
is a legal expression
string
has a length
propertylength
property are subtypes of { length: number }
string
is a subtype of { length: number }
{ a: A, b: B }
and { a: A }
, the former is a subtype of the latter
{ a: A }
is a subtype of { }
string
is a subtype of { }
You can break this proof at any single step, but it introduces really bad assymmetries without fixing any other problem than "some people misapprehended that { }
is a synonym of object
", and it's impossible to remove all misapprehensions from a system.
Question related: can you have a type in typescript which represents object without any properties? So that it would be possible to assign only empty object to that type.
@dblachut-adb No, currently you can't define such a type in TypeScript. This would require #12936.
Bug Report
🔎 Search Terms
"object" | "{}"
🕗 Version & Regression Information
⏯ Playground Link
Playground link with relevant code
💻 Code
🙁 Actual behavior
I have found at least few issues with
{}
type, all presented in playground and code samples above:{}
type assumed while it should rather beObject
/object
.new Object
has a{}
type assumed while it should rather beObject
/object
.{}
type can be assigned to more narrowobject
type, which should not be possible.🙂 Expected behavior
{}
type is documented, cannot be assigned to more narrowobject
type and is not assumed when used withtypeof <some_object>
.