Skip to content

let a = {} infers {} instead of object, which seems too wide for the initializerΒ #63308

@5cover

Description

@5cover

πŸ”Ž 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; // error

which appears more consistent with the runtime category of the initializer.

This also matches how other initializers behave:

let b = 5;
b = {}; // error

A 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
  • object would 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; // allowed

So 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions