Skip to content

Read unassigned class fields as undefined during construction - #803

Merged
ctate merged 1 commit into
mainfrom
ctate/read-unassigned-class-48a2a0
Oct 9, 2026
Merged

ctate merged 1 commit into
mainfrom
ctate/read-unassigned-class-48a2a0

Conversation

@ctate

@ctate ctate commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Class-typed fields that construction may read before assigning them now read as undefined, as in Node, instead of an empty slot.

  • Analysis (construction-escapes.ts): walks field initializers and the constructor in evaluation order, and stops at the first point that can hand the instance to other code: a method call, an argument, a callback, or a base constructor that exposes this. Private helpers that only assign fields are followed. Fields assigned before that point keep their plain representation, and only unproven fields take the deferred form.
  • Deferred fields: the slot starts empty and reads check it inline, so assigned reads stay borrowed and only an empty slot takes the slow path. Equality, typeof, truthiness, == null, optional chaining, as casts, destinations that admit undefined, and console output all see undefined itself. Member reads, method calls, updates and writes through an unassigned field throw Node's TypeErrors in Node's evaluation order.
  • Fields with a definite-assignment assertion (!) that private helpers assign no longer need the deferred form.
  • Tests: corpus 4471-unassigned-field-reads.ts covers inheritance, callbacks, initializer order, private helpers and member updates. Unit tests cover the analysis and check that the emitted check is inline, with no union box.

Validation: full plain corpus 2172/2172; filtered sanitized lane 448/448; package, diagnostics, coverage, surface-manifest and runtime-tree-shaking tests pass; Effect 125/125, effect4 180/180, npm-static and library-mode pass apart from known environment-only cases; self-hosting suites 54/54, and the compiler compiles itself; TypeScript preflight/order baselines complete; lint and format clean.

Class-typed fields that construction may read before assigning them now
observe undefined instead of reading an empty slot. A small analysis walks
field initializers and the constructor in evaluation order and stops at the
first point that can hand the instance to other code (a method call, an
argument, a callback, a base constructor that does); private helpers that
only assign fields are followed. Fields not assigned by then take the
deferred-init representation:

- the slot starts empty and reads check it inline, so assigned reads stay
  borrowed and only an empty slot reaches the throw;
- equality, `typeof`, truthiness, `== null`, optional chaining, `as`
  casts, undefined-admitting destinations and console output observe the
  undefined itself;
- member reads, method calls, updates and writes through an unassigned
  field throw Node's TypeErrors in Node's evaluation order.

Fields assigned before any exposure keep their plain representation.
Definite-assignment-asserted fields assigned through such private helpers
no longer need the deferred form either.

Corpus 4471 pins inheritance, callbacks, initializer order, private helpers
and member updates; unit tests cover the analysis and the emitted checks.
@vercel

vercel Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
scriptc Ready Ready Preview, v0 Oct 9, 2026 7:28pm UTC

@ctate
ctate merged commit 1254caa into main Oct 9, 2026
75 of 78 checks passed

This branch was successfully deployed

1 active deployment
Preview — dd6b8d4a Deployed Oct 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant