Skip to content

Compile {{this.#field}} via runtime accessor injection#21376

Open
NullVoxPopuli-ai-agent wants to merge 1 commit intoemberjs:nvp/fix-private-field-accessfrom
NullVoxPopuli-ai-agent:nvp/fix-private-field-access-impl
Open

Compile {{this.#field}} via runtime accessor injection#21376
NullVoxPopuli-ai-agent wants to merge 1 commit intoemberjs:nvp/fix-private-field-accessfrom
NullVoxPopuli-ai-agent:nvp/fix-private-field-access-impl

Conversation

@NullVoxPopuli-ai-agent
Copy link
Copy Markdown
Contributor

Summary

Closes the gap on #21375 — templates that read a class's private field
({{this.#foo}}) used to render empty because Glimmer's path walker
resolved the tail segment as a string property access (instance['#foo']),
which never reaches a real private slot.

This change introduces:

  1. A strict-mode AST plugin (transform-private-field-access) that
    rewrites every this.#field PathExpression into a call to a synthetic
    free variable — (__ember_priv_field_<name> this) — and records the
    discovered field name on meta.privateFields.
  2. A small extension to template()'s implicit-form evaluator that
    declares one (inst) => inst.#<name> accessor per recorded name
    inside the wrapper function it hands to the user's eval. Since the
    user's eval sits inside the class's static block, the #<name>
    syntax parses against the class's private slots; the returned
    closures retain that access for every later render.
  3. A lexicalScope short-circuit so the rewritten free variables are
    accepted in strict mode, and a clear error if a template uses
    this.#field without an eval option (no other form can reach
    private slots).

The path is strict-mode-only by design; resolution mode has no
lexical-scope plumbing for the synthetic accessor.

Test plan

  • Smoke test added in [BUGFIX] #private field access in class-based components #21375 (private field access > it works)
    passes in classic, embroiderWebpack, and embroiderVite scenarios.
  • New integration tests in
    runtime-template-compiler-implicit-test.ts:
    • Can read a \#private` field`
    • Can read multiple \#private` fields and pass them to a helper`
  • New unit test in template_test.ts asserting compile + private
    slot reachability.
  • Full local pnpm testem ci run — 9311 pass / 17 skip / 0 fail.
  • pnpm type-check and pnpm lint:eslint:fix clean.

🤖 Generated with Claude Code

Templates that reference a class's private field — `{{this.#foo}}` — used
to render empty because Glimmer's path walker resolved the tail segment as
a string property access (`instance['#foo']`), which never reaches a real
private slot.

This adds a strict-mode AST plugin that rewrites every
`this.#field` PathExpression into a call to a synthetic free variable
(`(__ember_priv_field_<name> this)`) and records the field name. The
implicit-form evaluator in `template()` then declares one
`(inst) => inst.#<name>` accessor per recorded name inside the wrapper
function. Because the wrapper source is evaluated by the user's `eval()`
— which sits inside the class's static block — the `#<name>` syntax
parses against the class's private slots, and the resulting closures
preserve that access for later renders.

Includes integration coverage in
runtime-template-compiler-implicit-test.ts (one field, multiple fields
piped through a helper) and a unit test in template_test.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants