Compile {{this.#field}} via runtime accessor injection#21376
Open
NullVoxPopuli-ai-agent wants to merge 1 commit intoemberjs:nvp/fix-private-field-accessfrom
Open
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the gap on #21375 — templates that read a class's private field
(
{{this.#foo}}) used to render empty because Glimmer's path walkerresolved the tail segment as a string property access (
instance['#foo']),which never reaches a real private slot.
This change introduces:
transform-private-field-access) thatrewrites every
this.#fieldPathExpression into a call to a syntheticfree variable —
(__ember_priv_field_<name> this)— and records thediscovered field name on
meta.privateFields.template()'s implicit-form evaluator thatdeclares one
(inst) => inst.#<name>accessor per recorded nameinside the wrapper function it hands to the user's
eval. Since theuser's
evalsits inside the class'sstaticblock, the#<name>syntax parses against the class's private slots; the returned
closures retain that access for every later render.
lexicalScopeshort-circuit so the rewritten free variables areaccepted in strict mode, and a clear error if a template uses
this.#fieldwithout anevaloption (no other form can reachprivate slots).
The path is strict-mode-only by design; resolution mode has no
lexical-scope plumbing for the synthetic accessor.
Test plan
private field access > it works)passes in classic, embroiderWebpack, and embroiderVite scenarios.
runtime-template-compiler-implicit-test.ts:Can read a \#private` field`Can read multiple \#private` fields and pass them to a helper`template_test.tsasserting compile + privateslot reachability.
pnpm testem cirun — 9311 pass / 17 skip / 0 fail.pnpm type-checkandpnpm lint:eslint:fixclean.🤖 Generated with Claude Code