-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Description
π Search Terms
empty object inferred as {} instead of object
π Version & Regression Information
This is the behavior in every version I tried, and I reviewed the FAQ for entries about { } Does Not Refer to Objects With No Properties.
β― Playground Link
https://www.typescriptlang.org/play/?#code/DYUwLgBAhhC8EG8C+BuAsAKBvArCiA9AdMMAPYDuIAJkA
π» Code
let a = {};
a = 5; // allowedπ Actual behavior
The empty object literal infers as {}.
Since {} is assignable from all non-nullish values, this assignment changes the fundamental runtime category of the variable.
This is surprising given the initializer. From let a = {}, we know that:
- the value is an object
- it has no known properties
However, the inferred type {} does not preserve the "object-ness" of the initializer and instead allows primitives.
π Expected behavior
object seems like a more faithful inferred type than {}.
If a were inferred as object, then:
let a = {};
a = 5; // errorwhich appears more consistent with the runtime category of the initializer.
This also matches how other initializers behave:
let b = 5;
b = {}; // errorA numeric initializer does not widen to a type that also accepts objects. By contrast, {} currently widens to a type that also accepts primitives.
Additional information about the issue
This affects generic utilities and conditional types that reason about broad categories of values.
For example, because {} includes non-nullish primitives, utilities that inspect or classify types can get surprising results when fed the inferred type of {}.
More broadly, it seems odd that an object literal can infer to a type that later permits assignment of primitive values, unless that widening was explicitly requested via annotation.
Cosnidering {} has long-standing semantics in TypeScript and that changing inference here would likely be breaking, I am not proposing that this must change unconditionally.
However, it seems reasonable to ask whether:
- the current inference is truly the intended design, or just a consequence of existing
{}semantics objectwould be a better inference target for the specific case of the empty object literal initializer- this could make sense behind a strictness flag, e.g. something like
strictEmptyObjectInference
If someone wants the broader behavior, they can always write it explicitly:
let a: {} = {};
a = 5; // allowedSo inferring object from {} would not remove access to the current behavior; it would only stop opting into it implicitly.
Is inferring {} from let a = {} considered the intended design?
If so, what is the rationale for preferring {} over object?
And if not, would an opt-in stricter mode be worth considering?