From 0b9385354e08045e0f82dffda7b6be37b1b53811 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:15:14 -0400 Subject: [PATCH 1/2] Remove -with-dynamic-vars and -get-dynamic-var support Removes the template-facing dynamic vars feature: - the -get-dynamic-var (call/append) and -with-dynamic-vars (block) keywords, the GetDynamicVar/WithDynamicVars MIR nodes, their encoding and strict-mode passes - wire format opcodes WithDynamicVars (45) and GetDynamicVar (53) and their types (numbers left as gaps, not renumbered) - VM opcodes GetDynamicVar (111) and BindDynamicScope (58), their append-opcode handlers, VM#bindDynamicScope, and debug metadata - the associated integration test suites Dynamic scope itself stays: Ember's outlet machinery reads and writes outletState on the dynamic scope directly from JS, and component invocation still pushes/pops dynamic scope. Only the template syntax for reading/writing arbitrary dynamic vars is removed. Note: this deletes the intimate API liquid-fire uses to intercept outletState in templates. Co-Authored-By: Claude Fable 5 --- .../glimmer/tests/integration/outlet-test.js | 42 ---------- .../tests/integration/refinements-test.js | 12 +-- .../syntax/with-dynamic-var-test.js | 35 -------- .../integration-tests/lib/suites.ts | 1 - .../lib/suites/entry-point.ts | 15 ---- .../lib/suites/with-dynamic-vars.ts | 84 ------------------- .../test/ember-component-test.ts | 18 ---- .../integration-tests/test/jit-suites-test.ts | 2 - .../passes/1-normalization/keywords/append.ts | 2 - .../passes/1-normalization/keywords/block.ts | 26 ------ .../passes/1-normalization/keywords/call.ts | 2 - .../keywords/utils/dynamic-vars.ts | 55 ------------ .../1-normalization/visitors/strict-mode.ts | 8 -- .../compiler/lib/passes/2-encoding/content.ts | 6 -- .../lib/passes/2-encoding/expressions.ts | 6 -- .../compiler/lib/passes/2-encoding/mir.ts | 11 --- .../compiler/lib/wire-format-debug.ts | 6 -- .../@glimmer/constants/lib/syscall-ops.ts | 4 - .../@glimmer/debug/lib/opcode-metadata.ts | 8 -- .../lib/compile/wire-format/api.d.ts | 12 --- .../lib/compile/wire-format/opcodes.d.ts | 2 - .../@glimmer/interfaces/lib/vm-opcodes.d.ts | 4 - .../lib/opcode-builder/helpers/vm.ts | 20 +---- .../opcode-compiler/lib/syntax/expressions.ts | 6 -- .../opcode-compiler/lib/syntax/statements.ts | 14 ---- .../lib/compiled/opcodes/expressions.ts | 14 ---- .../runtime/lib/compiled/opcodes/vm.ts | 6 -- packages/@glimmer/runtime/lib/vm/append.ts | 18 ---- packages/@glimmer/wire-format/lib/opcodes.ts | 4 - 29 files changed, 4 insertions(+), 439 deletions(-) delete mode 100644 packages/@ember/-internals/glimmer/tests/integration/syntax/with-dynamic-var-test.js delete mode 100644 packages/@glimmer-workspace/integration-tests/lib/suites/with-dynamic-vars.ts delete mode 100644 packages/@glimmer/compiler/lib/passes/1-normalization/keywords/utils/dynamic-vars.ts diff --git a/packages/@ember/-internals/glimmer/tests/integration/outlet-test.js b/packages/@ember/-internals/glimmer/tests/integration/outlet-test.js index fc17ee2730e..b0c2612acb5 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/outlet-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/outlet-test.js @@ -117,47 +117,5 @@ moduleFor( this.assertText('HIBYE'); } - - ['@test outletState can pass through user code (liquid-fire initimate API) ']() { - this.registerTemplate( - 'outer', - 'A{{#-with-dynamic-vars outletState=(identity (-get-dynamic-var "outletState"))}}B{{outlet}}D{{/-with-dynamic-vars}}E' - ); - this.registerTemplate('inner', 'C'); - - // This looks like it doesn't do anything, but its presence - // guarantees that the outletState gets converted from a reference - // to a value and then back to a reference. That is what we're - // testing here. - this.registerHelper('identity', ([a]) => a); - - let outletState = { - render: { - owner: this.owner, - name: 'outer', - controller: {}, - template: this.owner.lookup('template:outer')(this.owner), - }, - outlets: { - main: { - render: { - owner: this.owner, - name: 'inner', - controller: {}, - template: this.owner.lookup('template:inner')(this.owner), - }, - outlets: Object.create(null), - }, - }, - }; - - runTask(() => this.component.setOutletState(outletState)); - - runAppend(this.component); - - this.assertText('ABCDE'); - - this.assertStableRerender(); - } } ); diff --git a/packages/@ember/-internals/glimmer/tests/integration/refinements-test.js b/packages/@ember/-internals/glimmer/tests/integration/refinements-test.js index 9ea183396f0..3e284efd947 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/refinements-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/refinements-test.js @@ -40,27 +40,21 @@ moduleFor( --- - {{#let this.var as |-with-dynamic-vars|}} - {{-with-dynamic-vars}} - {{/let}} - - --- - {{#let this.var as |-in-element|}} {{-in-element}} {{/let}}`, { var: 'var' } ); - this.assertText('var---var---var---var---var---var---var'); + this.assertText('var---var---var---var---var---var'); runTask(() => set(this.context, 'var', 'RARRR!!!')); - this.assertText('RARRR!!!---RARRR!!!---RARRR!!!---RARRR!!!---RARRR!!!---RARRR!!!---RARRR!!!'); + this.assertText('RARRR!!!---RARRR!!!---RARRR!!!---RARRR!!!---RARRR!!!---RARRR!!!'); runTask(() => set(this.context, 'var', 'var')); - this.assertText('var---var---var---var---var---var---var'); + this.assertText('var---var---var---var---var---var'); } } ); diff --git a/packages/@ember/-internals/glimmer/tests/integration/syntax/with-dynamic-var-test.js b/packages/@ember/-internals/glimmer/tests/integration/syntax/with-dynamic-var-test.js deleted file mode 100644 index 7c61bc39e9f..00000000000 --- a/packages/@ember/-internals/glimmer/tests/integration/syntax/with-dynamic-var-test.js +++ /dev/null @@ -1,35 +0,0 @@ -import { moduleFor, RenderingTestCase, strip } from 'internal-test-helpers'; - -moduleFor( - '{{-with-dynamic-var}}', - class extends RenderingTestCase { - ['@test does not allow setting values other than outletState']() { - expectAssertion(() => { - this.render(strip` - {{#-with-dynamic-vars foo="bar"}} - {{-get-dynamic-var 'foo'}} - {{/-with-dynamic-vars}} - `); - }, /Using `-with-dynamic-scope` is only supported for `outletState` \(you used `foo`\)./); - } - - ['@test allows setting/getting outletState']() { - // this is simply asserting that we can write and read outletState - // the actual value being used here is not what is used in real life - // feel free to change the value being set and asserted as needed - this.render(strip` - {{#-with-dynamic-vars outletState="bar"}} - {{-get-dynamic-var 'outletState'}} - {{/-with-dynamic-vars}} - `); - - this.assertText('bar'); - } - - ['@test does not allow getting values other than outletState']() { - expectAssertion(() => { - this.render(`{{-get-dynamic-var 'foo'}}`); - }, /Using `-get-dynamic-scope` is only supported for `outletState` \(you used `foo`\)./); - } - } -); diff --git a/packages/@glimmer-workspace/integration-tests/lib/suites.ts b/packages/@glimmer-workspace/integration-tests/lib/suites.ts index a3ecce2e83c..b25c0e80d97 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/suites.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/suites.ts @@ -11,5 +11,4 @@ export * from './suites/initial-render'; export * from './suites/scope'; export * from './suites/shadowing'; export * from './suites/ssr'; -export * from './suites/with-dynamic-vars'; export * from './suites/yield'; diff --git a/packages/@glimmer-workspace/integration-tests/lib/suites/entry-point.ts b/packages/@glimmer-workspace/integration-tests/lib/suites/entry-point.ts index 0710b2fa384..4167901267d 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/suites/entry-point.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/suites/entry-point.ts @@ -1,6 +1,5 @@ import { castToBrowser } from '@glimmer/debug-util'; import { createPrimitiveRef } from '@glimmer/reference'; -import { DynamicScopeImpl } from '@glimmer/runtime'; import type { ComponentKind } from '../components/types'; @@ -69,18 +68,4 @@ export class EntryPointTest extends RenderTest { delegate.renderComponent(Body, { body }, element); QUnit.assert.strictEqual(castToBrowser(element, 'HTML').innerHTML, '

body text

'); } - - @test - 'supports passing in an initial dynamic context'() { - let delegate = new JitRenderDelegate(); - let Locale = defineComponent({}, `{{-get-dynamic-var "locale"}}`); - - let element = delegate.getInitialElement(); - let dynamicScope = new DynamicScopeImpl({ - locale: createPrimitiveRef('en_US'), - }); - delegate.renderComponent(Locale, {}, element, dynamicScope); - - QUnit.assert.strictEqual(castToBrowser(element, 'HTML').innerHTML, 'en_US'); - } } diff --git a/packages/@glimmer-workspace/integration-tests/lib/suites/with-dynamic-vars.ts b/packages/@glimmer-workspace/integration-tests/lib/suites/with-dynamic-vars.ts deleted file mode 100644 index 7608fcddf8a..00000000000 --- a/packages/@glimmer-workspace/integration-tests/lib/suites/with-dynamic-vars.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { RenderTest } from '../render-test'; -import { test } from '../test-decorator'; - -export class WithDynamicVarsSuite extends RenderTest { - static suiteName = '-with-dynamic-vars and -get-dynamic-var'; - @test - 'Can get and set dynamic variable'() { - this.render( - { - layout: '{{#-with-dynamic-vars myKeyword=@value}}{{yield}}{{/-with-dynamic-vars}}', - template: '{{-get-dynamic-var "myKeyword"}}', - args: { value: 'this.value' }, - }, - { value: 'hello' } - ); - - this.assertComponent('hello'); - this.assertStableRerender(); - - this.rerender({ value: 'goodbye' }); - this.assertComponent('goodbye'); - this.assertStableNodes(); - - this.rerender({ value: 'hello' }); - this.assertComponent('hello'); - this.assertStableNodes(); - } - - @test - 'Can get and set dynamic variable with bound names'() { - this.render( - { - layout: - '{{#-with-dynamic-vars myKeyword=@value1 secondKeyword=@value2}}{{yield}}{{/-with-dynamic-vars}}', - template: '{{this.keyword}}-{{-get-dynamic-var this.keyword}}', - args: { value1: 'this.value1', value2: 'this.value2' }, - }, - { value1: 'hello', value2: 'goodbye', keyword: 'myKeyword' } - ); - - this.assertComponent('myKeyword-hello'); - this.assertStableRerender(); - - this.rerender({ keyword: 'secondKeyword' }); - this.assertComponent('secondKeyword-goodbye'); - this.assertStableNodes(); - - this.rerender({ value2: 'goodbye!' }); - this.assertComponent('secondKeyword-goodbye!'); - this.assertStableNodes(); - - this.rerender({ value1: 'hello', value2: 'goodbye', keyword: 'myKeyword' }); - this.assertComponent('myKeyword-hello'); - this.assertStableNodes(); - } - - @test - 'Can shadow existing dynamic variable'() { - this.render( - { - layout: - '{{#-with-dynamic-vars myKeyword=@outer}}
{{-get-dynamic-var "myKeyword"}}
{{#-with-dynamic-vars myKeyword=@inner}}{{yield}}{{/-with-dynamic-vars}}
{{-get-dynamic-var "myKeyword"}}
{{/-with-dynamic-vars}}', - template: '
{{-get-dynamic-var "myKeyword"}}
', - args: { outer: 'this.outer', inner: 'this.inner' }, - }, - { outer: 'original', inner: 'shadowed' } - ); - - this.assertComponent('
original
shadowed
original
'); - this.assertStableRerender(); - - this.rerender({ outer: 'original2', inner: 'shadowed' }); - this.assertComponent('
original2
shadowed
original2
'); - this.assertStableNodes(); - - this.rerender({ outer: 'original2', inner: 'shadowed2' }); - this.assertComponent('
original2
shadowed2
original2
'); - this.assertStableNodes(); - - this.rerender({ outer: 'original', inner: 'shadowed' }); - this.assertComponent('
original
shadowed
original
'); - this.assertStableNodes(); - } -} diff --git a/packages/@glimmer-workspace/integration-tests/test/ember-component-test.ts b/packages/@glimmer-workspace/integration-tests/test/ember-component-test.ts index 5f1c5f4c509..2ce5c73560e 100644 --- a/packages/@glimmer-workspace/integration-tests/test/ember-component-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/ember-component-test.ts @@ -718,23 +718,6 @@ class CurlyScopeTest extends CurlyTest { } } -class CurlyDynamicScopeSmokeTest extends CurlyTest { - static suiteName = '[curly components] dynamicScope access smoke test'; - - @test - 'component has access to dynamic scope'() { - class SampleComponent extends EmberishCurlyComponent { - static fromDynamicScope = ['theme']; - } - - this.registerComponent('Curly', 'sample-component', '{{this.theme}}', SampleComponent); - - this.render('{{#-with-dynamic-vars theme="light"}}{{sample-component}}{{/-with-dynamic-vars}}'); - - this.assertEmberishElement('div', 'light'); - } -} - class CurlyPositionalArgsTest extends CurlyTest { static suiteName = '[curly components] positional arguments'; @@ -2229,7 +2212,6 @@ jitSuite(CurlyDynamicComponentTest); jitSuite(CurlyDynamicCustomizationTest); jitSuite(CurlyArgsTest); jitSuite(CurlyScopeTest); -jitSuite(CurlyDynamicScopeSmokeTest); jitSuite(CurlyPositionalArgsTest); jitSuite(CurlyClosureComponentsTest); jitSuite(CurlyIdsTest); diff --git a/packages/@glimmer-workspace/integration-tests/test/jit-suites-test.ts b/packages/@glimmer-workspace/integration-tests/test/jit-suites-test.ts index 69afb5a202c..a46289c269f 100644 --- a/packages/@glimmer-workspace/integration-tests/test/jit-suites-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/jit-suites-test.ts @@ -11,7 +11,6 @@ import { ScopeSuite, ShadowingSuite, TemplateOnlyComponents, - WithDynamicVarsSuite, YieldSuite, } from '@glimmer-workspace/integration-tests'; @@ -26,5 +25,4 @@ jitComponentSuite(HasBlockSuite); jitComponentSuite(HasBlockParamsHelperSuite); jitComponentSuite(ScopeSuite); jitComponentSuite(ShadowingSuite); -jitComponentSuite(WithDynamicVarsSuite); jitComponentSuite(YieldSuite); diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/append.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/append.ts index 5c3b33ea21a..728a5f86150 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/append.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/append.ts @@ -11,7 +11,6 @@ import { VISIT_EXPRS } from '../visitors/expressions'; import { keywords } from './impl'; import { toAppend } from './utils/call-to-append'; import { assertCurryKeyword } from './utils/curry'; -import { getDynamicVarKeyword } from './utils/dynamic-vars'; import { hasBlockKeyword } from './utils/has-block'; import { ifUnlessInlineKeyword } from './utils/if-unless'; import { logKeyword } from './utils/log'; @@ -19,7 +18,6 @@ import { logKeyword } from './utils/log'; export const APPEND_KEYWORDS = keywords('Append') .kw('has-block', toAppend(hasBlockKeyword('has-block'))) .kw('has-block-params', toAppend(hasBlockKeyword('has-block-params'))) - .kw('-get-dynamic-var', toAppend(getDynamicVarKeyword)) .kw('log', toAppend(logKeyword)) .kw('if', toAppend(ifUnlessInlineKeyword('if'))) .kw('unless', toAppend(ifUnlessInlineKeyword('unless'))) diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/block.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/block.ts index 7f0c1b5c12d..5ba107f818e 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/block.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/block.ts @@ -335,32 +335,6 @@ export const BLOCK_KEYWORDS = keywords('Block') ); }, }) - .kw('-with-dynamic-vars', { - assert(node: ASTv2.InvokeBlock): Result<{ - named: ASTv2.NamedArguments; - }> { - return Ok({ named: node.args.named }); - }, - - translate( - { node, state }: { node: ASTv2.InvokeBlock; state: NormalizationState }, - { named }: { named: ASTv2.NamedArguments } - ): Result { - let block = node.blocks.get('default'); - - let namedResult = VISIT_EXPRS.NamedArguments(named, state); - let blockResult = VISIT_STMTS.NamedBlock(block, state); - - return Result.all(namedResult, blockResult).mapOk( - ([named, block]) => - new mir.WithDynamicVars({ - loc: node.loc, - named, - block, - }) - ); - }, - }) .kw('component', { assert: assertCurryKeyword(CURRIED_COMPONENT), diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/call.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/call.ts index 8202b7d98da..1a428df6ff0 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/call.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/call.ts @@ -6,7 +6,6 @@ import { import { keywords } from './impl'; import { curryKeyword } from './utils/curry'; -import { getDynamicVarKeyword } from './utils/dynamic-vars'; import { hasBlockKeyword } from './utils/has-block'; import { ifUnlessInlineKeyword } from './utils/if-unless'; import { logKeyword } from './utils/log'; @@ -14,7 +13,6 @@ import { logKeyword } from './utils/log'; export const CALL_KEYWORDS = keywords('Call') .kw('has-block', hasBlockKeyword('has-block')) .kw('has-block-params', hasBlockKeyword('has-block-params')) - .kw('-get-dynamic-var', getDynamicVarKeyword) .kw('log', logKeyword) .kw('if', ifUnlessInlineKeyword('if')) .kw('unless', ifUnlessInlineKeyword('unless')) diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/utils/dynamic-vars.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/utils/dynamic-vars.ts deleted file mode 100644 index 375e25c4bf3..00000000000 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/utils/dynamic-vars.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type * as ASTv2 from '@glimmer/syntax/lib/v2/api'; -import { generateSyntaxError } from '@glimmer/syntax/lib/syntax-error'; - -import type { Result } from '../../../../shared/result'; -import type { NormalizationState } from '../../context'; -import type { GenericKeywordNode, KeywordDelegate } from '../impl'; - -import { Err, Ok } from '../../../../shared/result'; -import * as mir from '../../../2-encoding/mir'; -import { VISIT_EXPRS } from '../../visitors/expressions'; - -function assertGetDynamicVarKeyword(node: GenericKeywordNode): Result { - let call = node.type === 'AppendContent' ? node.value : node; - - let named = call.type === 'Call' ? call.args.named : null; - let positionals = call.type === 'Call' ? call.args.positional : null; - - if (named && !named.isEmpty()) { - return Err( - generateSyntaxError(`(-get-dynamic-vars) does not take any named arguments`, node.loc) - ); - } - - let varName = positionals?.nth(0); - - if (!varName) { - return Err(generateSyntaxError(`(-get-dynamic-vars) requires a var name to get`, node.loc)); - } - - if (positionals && positionals.size > 1) { - return Err( - generateSyntaxError(`(-get-dynamic-vars) only receives one positional arg`, node.loc) - ); - } - - return Ok(varName); -} - -function translateGetDynamicVarKeyword( - { node, state }: { node: GenericKeywordNode; state: NormalizationState }, - name: ASTv2.ExpressionNode -): Result { - return VISIT_EXPRS.visit(name, state).mapOk( - (name) => new mir.GetDynamicVar({ name, loc: node.loc }) - ); -} - -export const getDynamicVarKeyword: KeywordDelegate< - GenericKeywordNode, - ASTv2.ExpressionNode, - mir.GetDynamicVar -> = { - assert: assertGetDynamicVarKeyword, - translate: translateGetDynamicVarKeyword, -}; diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/strict-mode.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/strict-mode.ts index 2d30ed042c6..6f8887af0e4 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/strict-mode.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/strict-mode.ts @@ -96,9 +96,6 @@ export default class StrictModeValidationPass { case 'Let': return this.Let(statement); - case 'WithDynamicVars': - return this.WithDynamicVars(statement); - case 'InvokeComponent': return this.InvokeComponent(statement); } @@ -128,7 +125,6 @@ export default class StrictModeValidationPass { case 'Local': case 'HasBlock': case 'HasBlockParams': - case 'GetDynamicVar': return Ok(null); case 'PathExpression': @@ -310,10 +306,6 @@ export default class StrictModeValidationPass { return this.Positional(statement.positional).andThen(() => this.NamedBlock(statement.block)); } - WithDynamicVars(statement: mir.WithDynamicVars): Result { - return this.NamedArguments(statement.named).andThen(() => this.NamedBlock(statement.block)); - } - InvokeComponent(statement: mir.InvokeComponent): Result { return this.Expression(statement.definition, statement, COMPONENT_RESOLUTION) .andThen(() => this.Args(statement.args)) diff --git a/packages/@glimmer/compiler/lib/passes/2-encoding/content.ts b/packages/@glimmer/compiler/lib/passes/2-encoding/content.ts index cdd4c6ad0d1..24adba8a2bd 100644 --- a/packages/@glimmer/compiler/lib/passes/2-encoding/content.ts +++ b/packages/@glimmer/compiler/lib/passes/2-encoding/content.ts @@ -79,8 +79,6 @@ export class ContentEncoder { return this.Each(stmt); case 'Let': return this.Let(stmt); - case 'WithDynamicVars': - return this.WithDynamicVars(stmt); case 'InvokeComponent': return this.InvokeComponent(stmt); default: @@ -215,10 +213,6 @@ export class ContentEncoder { return [SexpOpcodes.Let, EXPR.Positional(positional), CONTENT.NamedBlock(block)[1]]; } - WithDynamicVars({ named, block }: mir.WithDynamicVars): WireFormat.Statements.WithDynamicVars { - return [SexpOpcodes.WithDynamicVars, EXPR.NamedArguments(named), CONTENT.NamedBlock(block)[1]]; - } - InvokeComponent({ definition, args, diff --git a/packages/@glimmer/compiler/lib/passes/2-encoding/expressions.ts b/packages/@glimmer/compiler/lib/passes/2-encoding/expressions.ts index 220ce54d059..2365711ab6e 100644 --- a/packages/@glimmer/compiler/lib/passes/2-encoding/expressions.ts +++ b/packages/@glimmer/compiler/lib/passes/2-encoding/expressions.ts @@ -45,8 +45,6 @@ export class ExpressionEncoder { return this.IfInline(expr); case 'InterpolateExpression': return this.InterpolateExpression(expr); - case 'GetDynamicVar': - return this.GetDynamicVar(expr); case 'Log': return this.Log(expr); } @@ -163,10 +161,6 @@ export class ExpressionEncoder { return expr as WireFormat.Expressions.IfInline; } - GetDynamicVar({ name }: mir.GetDynamicVar): WireFormat.Expressions.GetDynamicVar { - return [SexpOpcodes.GetDynamicVar, EXPR.expr(name)]; - } - Log({ positional }: mir.Log): WireFormat.Expressions.Log { return [SexpOpcodes.Log, this.Positional(positional)]; } diff --git a/packages/@glimmer/compiler/lib/passes/2-encoding/mir.ts b/packages/@glimmer/compiler/lib/passes/2-encoding/mir.ts index d1d9cf69ce7..96676d509c2 100644 --- a/packages/@glimmer/compiler/lib/passes/2-encoding/mir.ts +++ b/packages/@glimmer/compiler/lib/passes/2-encoding/mir.ts @@ -48,15 +48,6 @@ export class Let extends node('Let').fields<{ block: NamedBlock; }>() {} -export class WithDynamicVars extends node('WithDynamicVars').fields<{ - named: NamedArguments; - block: NamedBlock; -}>() {} - -export class GetDynamicVar extends node('GetDynamicVar').fields<{ - name: ExpressionNode; -}>() {} - export class Log extends node('Log').fields<{ positional: Positional; }>() {} @@ -191,7 +182,6 @@ export type ExpressionNode = | HasBlock | HasBlockParams | Curry - | GetDynamicVar | Log; export type ElementParameter = StaticAttr | DynamicAttr | Modifier | SplatAttr; @@ -219,5 +209,4 @@ export type Statement = | If | Each | Let - | WithDynamicVars | InvokeComponent; diff --git a/packages/@glimmer/compiler/lib/wire-format-debug.ts b/packages/@glimmer/compiler/lib/wire-format-debug.ts index 61be45299e1..576a28505ac 100644 --- a/packages/@glimmer/compiler/lib/wire-format-debug.ts +++ b/packages/@glimmer/compiler/lib/wire-format-debug.ts @@ -230,12 +230,6 @@ export default class WireFormatDebugger { case Op.Log: return ['log', this.formatParams(opcode[1])]; - case Op.WithDynamicVars: - return ['-with-dynamic-vars', this.formatHash(opcode[1]), this.formatBlock(opcode[2])]; - - case Op.GetDynamicVar: - return ['-get-dynamic-vars', this.formatOpcode(opcode[1])]; - case Op.InvokeComponent: return [ 'component', diff --git a/packages/@glimmer/constants/lib/syscall-ops.ts b/packages/@glimmer/constants/lib/syscall-ops.ts index 22e49360b86..93252b7f037 100644 --- a/packages/@glimmer/constants/lib/syscall-ops.ts +++ b/packages/@glimmer/constants/lib/syscall-ops.ts @@ -6,7 +6,6 @@ import type { VmAppendText, VmAssertSame, VmBeginComponentTransaction, - VmBindDynamicScope, VmCaptureArgs, VmChildScope, VmCloseElement, @@ -38,7 +37,6 @@ import type { VmGetComponentLayout, VmGetComponentSelf, VmGetComponentTagName, - VmGetDynamicVar, VmGetProperty, VmGetVariable, VmHasBlock, @@ -137,7 +135,6 @@ export const VM_FLUSH_ELEMENT_OP = 54 satisfies VmFlushElement; export const VM_CLOSE_ELEMENT_OP = 55 satisfies VmCloseElement; export const VM_POP_REMOTE_ELEMENT_OP = 56 satisfies VmPopRemoteElement; export const VM_MODIFIER_OP = 57 satisfies VmModifier; -export const VM_BIND_DYNAMIC_SCOPE_OP = 58 satisfies VmBindDynamicScope; export const VM_PUSH_DYNAMIC_SCOPE_OP = 59 satisfies VmPushDynamicScope; export const VM_POP_DYNAMIC_SCOPE_OP = 60 satisfies VmPopDynamicScope; export const VM_COMPILE_BLOCK_OP = 61 satisfies VmCompileBlock; @@ -185,7 +182,6 @@ export const VM_DYNAMIC_HELPER_OP = 107 satisfies VmDynamicHelper; export const VM_DYNAMIC_MODIFIER_OP = 108 satisfies VmDynamicModifier; export const VM_IF_INLINE_OP = 109 satisfies VmIfInline; export const VM_NOT_OP = 110 satisfies VmNot; -export const VM_GET_DYNAMIC_VAR_OP = 111 satisfies VmGetDynamicVar; export const VM_LOG_OP = 112 satisfies VmLog; export const VM_SYSCALL_SIZE = 113 satisfies VmSize; diff --git a/packages/@glimmer/debug/lib/opcode-metadata.ts b/packages/@glimmer/debug/lib/opcode-metadata.ts index b710b81ce6e..1d227d51a55 100644 --- a/packages/@glimmer/debug/lib/opcode-metadata.ts +++ b/packages/@glimmer/debug/lib/opcode-metadata.ts @@ -20,7 +20,6 @@ import { VM_APPEND_TEXT_OP, VM_ASSERT_SAME_OP, VM_BEGIN_COMPONENT_TRANSACTION_OP, - VM_BIND_DYNAMIC_SCOPE_OP, VM_CAPTURE_ARGS_OP, VM_CHILD_SCOPE_OP, VM_CLOSE_ELEMENT_OP, @@ -463,13 +462,6 @@ if (LOCAL_DEBUG) { ops: ['helper:handle'], }; - METADATA[VM_BIND_DYNAMIC_SCOPE_OP] = { - name: 'BindDynamicScope', - mnemonic: 'setdynscope', - stackChange: null, - ops: ['names:const/str[]'], - }; - METADATA[VM_PUSH_DYNAMIC_SCOPE_OP] = { name: 'PushDynamicScope', mnemonic: 'dynscopepush', diff --git a/packages/@glimmer/interfaces/lib/compile/wire-format/api.d.ts b/packages/@glimmer/interfaces/lib/compile/wire-format/api.d.ts index f001eb4cf87..da9c632cf14 100644 --- a/packages/@glimmer/interfaces/lib/compile/wire-format/api.d.ts +++ b/packages/@glimmer/interfaces/lib/compile/wire-format/api.d.ts @@ -18,7 +18,6 @@ import type { DynamicAttrOpcode, EachOpcode, FlushElementOpcode, - GetDynamicVarOpcode, GetFreeAsComponentHeadOpcode, GetFreeAsComponentOrHelperHeadOpcode, GetFreeAsHelperHeadOpcode, @@ -45,7 +44,6 @@ import type { TrustingComponentAttrOpcode, TrustingDynamicAttrOpcode, UndefinedOpcode, - WithDynamicVarsOpcode, YieldOpcode, } from './opcodes.js'; @@ -139,7 +137,6 @@ export namespace Expressions { export type TupleExpression = | Get - | GetDynamicVar | Concat | HasBlock | HasBlockParams @@ -169,8 +166,6 @@ export namespace Expressions { export type Not = [op: NotOpcode, value: Expression]; - export type GetDynamicVar = [op: GetDynamicVarOpcode, value: Expression]; - export type Log = [op: LogOpcode, positional: Params]; } @@ -286,12 +281,6 @@ export namespace Statements { export type Let = [op: LetOpcode, positional: Core.Params, block: SerializedInlineBlock]; - export type WithDynamicVars = [ - op: WithDynamicVarsOpcode, - args: Core.Hash, - block: SerializedInlineBlock, - ]; - export type InvokeComponent = [ op: InvokeComponentOpcode, definition: Expression, @@ -324,7 +313,6 @@ export namespace Statements { | If | Each | Let - | WithDynamicVars | InvokeComponent; export type Attribute = diff --git a/packages/@glimmer/interfaces/lib/compile/wire-format/opcodes.d.ts b/packages/@glimmer/interfaces/lib/compile/wire-format/opcodes.d.ts index 56781bbfffb..8925bf25924 100644 --- a/packages/@glimmer/interfaces/lib/compile/wire-format/opcodes.d.ts +++ b/packages/@glimmer/interfaces/lib/compile/wire-format/opcodes.d.ts @@ -53,7 +53,6 @@ export type InElementOpcode = 40; export type IfOpcode = 41; export type EachOpcode = 42; export type LetOpcode = 44; -export type WithDynamicVarsOpcode = 45; export type InvokeComponentOpcode = 46; // Keyword Expressions @@ -62,7 +61,6 @@ export type HasBlockParamsOpcode = 49; export type CurryOpcode = 50; export type NotOpcode = 51; export type IfInlineOpcode = 52; -export type GetDynamicVarOpcode = 53; export type LogOpcode = 54; export type GetStartOpcode = GetSymbolOpcode; diff --git a/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts b/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts index 53058efcddb..58b32867280 100644 --- a/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts +++ b/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts @@ -61,7 +61,6 @@ export type VmFlushElement = 54; export type VmCloseElement = 55; export type VmPopRemoteElement = 56; export type VmModifier = 57; -export type VmBindDynamicScope = 58; export type VmPushDynamicScope = 59; export type VmPopDynamicScope = 60; export type VmCompileBlock = 61; @@ -109,7 +108,6 @@ export type VmDynamicHelper = 107; export type VmDynamicModifier = 108; export type VmIfInline = 109; export type VmNot = 110; -export type VmGetDynamicVar = 111; export type VmLog = 112; export type VmSize = 113; @@ -156,7 +154,6 @@ export type VmOp = | VmCloseElement | VmPopRemoteElement | VmModifier - | VmBindDynamicScope | VmPushDynamicScope | VmPopDynamicScope | VmCompileBlock @@ -205,7 +202,6 @@ export type VmOp = | VmDynamicModifier | VmIfInline | VmNot - | VmGetDynamicVar | VmLog; export type SomeVmOp = VmOp | VmMachineOp; diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/vm.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/vm.ts index f2ef561eda3..8785b618eb8 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/vm.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/vm.ts @@ -1,23 +1,20 @@ import type { CurriedType, NonSmallIntOperand, Nullable, WireFormat } from '@glimmer/interfaces'; import { encodeImmediate, isSmallInt } from '@glimmer/constants/lib/immediate'; import { - VM_BIND_DYNAMIC_SCOPE_OP, VM_CAPTURE_ARGS_OP, VM_CURRY_OP, VM_DUP_OP, VM_DYNAMIC_HELPER_OP, VM_FETCH_OP, VM_HELPER_OP, - VM_POP_DYNAMIC_SCOPE_OP, VM_POP_OP, VM_PRIMITIVE_OP, VM_PRIMITIVE_REFERENCE_OP, - VM_PUSH_DYNAMIC_SCOPE_OP, } from '@glimmer/constants/lib/syscall-ops'; import { VM_POP_FRAME_OP, VM_PUSH_FRAME_OP } from '@glimmer/constants/lib/vm-ops'; import { $fp, $v0 } from '@glimmer/vm/lib/registers'; -import type { PushExpressionOp, PushStatementOp } from '../../syntax/compilers'; +import type { PushExpressionOp } from '../../syntax/compilers'; import { isStrictMode, nonSmallIntOperand } from '../operands'; import { expr } from './expr'; @@ -105,21 +102,6 @@ export function CallDynamic( } } -/** - * Evaluate statements in the context of new dynamic scope entries. Move entries from the - * stack into named entries in the dynamic scope, then evaluate the statements, then pop - * the dynamic scope - * - * @param names a list of dynamic scope names - * @param block a function that returns a list of statements to evaluate - */ -export function DynamicScope(op: PushStatementOp, names: string[], block: () => void): void { - op(VM_PUSH_DYNAMIC_SCOPE_OP); - op(VM_BIND_DYNAMIC_SCOPE_OP, names); - block(); - op(VM_POP_DYNAMIC_SCOPE_OP); -} - export function Curry( op: PushExpressionOp, type: CurriedType, diff --git a/packages/@glimmer/opcode-compiler/lib/syntax/expressions.ts b/packages/@glimmer/opcode-compiler/lib/syntax/expressions.ts index 1815ea34494..b3336ec1211 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax/expressions.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax/expressions.ts @@ -4,7 +4,6 @@ import { VM_CONCAT_OP, VM_CONSTANT_REFERENCE_OP, VM_FETCH_OP, - VM_GET_DYNAMIC_VAR_OP, VM_GET_PROPERTY_OP, VM_GET_VARIABLE_OP, VM_HAS_BLOCK_OP, @@ -114,11 +113,6 @@ EXPRESSIONS.add(SexpOpcodes.Not, (op, [, value]) => { op(VM_NOT_OP); }); -EXPRESSIONS.add(SexpOpcodes.GetDynamicVar, (op, [, expression]) => { - expr(op, expression); - op(VM_GET_DYNAMIC_VAR_OP); -}); - EXPRESSIONS.add(SexpOpcodes.Log, (op, [, positional]) => { op(VM_PUSH_FRAME_OP); SimpleArgs(op, positional, null, false); diff --git a/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts b/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts index cfafe8dd329..30b4cd27df5 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts @@ -67,7 +67,6 @@ import { CompilePositional, SimpleArgs } from '../opcode-builder/helpers/shared' import { Call, CallDynamic, - DynamicScope, PushPrimitiveReference, } from '../opcode-builder/helpers/vm'; import { HighLevelBuilderOpcodes, HighLevelResolutionOpcodes } from '../opcode-builder/opcodes'; @@ -363,19 +362,6 @@ STATEMENTS.add(SexpOpcodes.Let, (op, [, positional, block]) => { InvokeStaticBlockWithStack(op, block, count); }); -STATEMENTS.add(SexpOpcodes.WithDynamicVars, (op, [, named, block]) => { - if (named) { - let [names, expressions] = named; - - CompilePositional(op, expressions); - DynamicScope(op, names, () => { - InvokeStaticBlock(op, block); - }); - } else { - InvokeStaticBlock(op, block); - } -}); - STATEMENTS.add(SexpOpcodes.InvokeComponent, (op, [, expr, positional, named, blocks]) => { if (isGetFreeComponent(expr)) { op(HighLevelResolutionOpcodes.Component, expr, (component: CompileTimeComponent) => { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts index 0e99c73c52f..56584123abb 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts @@ -15,7 +15,6 @@ import { VM_CURRY_OP, VM_DYNAMIC_HELPER_OP, VM_GET_BLOCK_OP, - VM_GET_DYNAMIC_VAR_OP, VM_GET_PROPERTY_OP, VM_GET_VARIABLE_OP, VM_HAS_BLOCK_OP, @@ -307,19 +306,6 @@ APPEND_OPCODES.add(VM_NOT_OP, (vm) => { ); }); -APPEND_OPCODES.add(VM_GET_DYNAMIC_VAR_OP, (vm) => { - let scope = vm.dynamicScope(); - let stack = vm.stack; - let nameRef = check(stack.pop(), CheckReference); - - stack.push( - createComputeRef(() => { - let name = String(valueForRef(nameRef)); - return valueForRef(scope.get(name)); - }) - ); -}); - APPEND_OPCODES.add(VM_LOG_OP, (vm) => { let { positional } = check(vm.stack.pop(), CheckArguments).capture(); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index 8f918623bf6..a22780aa900 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -5,7 +5,6 @@ import type { Tag } from '@glimmer/interfaces'; import { decodeHandle, decodeImmediate, isHandle } from '@glimmer/constants/lib/immediate'; import { VM_ASSERT_SAME_OP, - VM_BIND_DYNAMIC_SCOPE_OP, VM_CHILD_SCOPE_OP, VM_COMPILE_BLOCK_OP, VM_CONSTANT_OP, @@ -130,11 +129,6 @@ APPEND_OPCODES.add(VM_FETCH_OP, (vm, { op1: register }) => { vm.fetch(check(register, CheckSyscallRegister)); }); -APPEND_OPCODES.add(VM_BIND_DYNAMIC_SCOPE_OP, (vm, { op1: _names }) => { - let names = vm.constants.getArray(_names); - vm.bindDynamicScope(names); -}); - APPEND_OPCODES.add(VM_ENTER_OP, (vm, { op1: args }) => { vm.enter(args); }); diff --git a/packages/@glimmer/runtime/lib/vm/append.ts b/packages/@glimmer/runtime/lib/vm/append.ts index d6f1613e89f..29bc50f0ab1 100644 --- a/packages/@glimmer/runtime/lib/vm/append.ts +++ b/packages/@glimmer/runtime/lib/vm/append.ts @@ -30,7 +30,6 @@ import { assertGlobalContextWasSet } from '@glimmer/global-context'; import { LOCAL_DEBUG, LOCAL_TRACE_LOGGING } from '@glimmer/local-debug-flags'; import { createIteratorItemRef } from '@glimmer/reference/lib/iterable'; import { UNDEFINED_REFERENCE } from '@glimmer/reference/lib/reference'; -import { reverse } from '@glimmer/util/lib/array-utils'; import { StackImpl as Stack } from '@glimmer/util/lib/collections'; import { LOCAL_LOGGER } from '@glimmer/util'; import { beginTrackFrame, endTrackFrame, resetTracking } from '@glimmer/validator/lib/tracking'; @@ -610,23 +609,6 @@ export class VM { return child; } - /** - * ## Opcodes - * - * - Append: `BindDynamicScope` - * - * ## State changes: - * - * [!] pop Dynamic Scope Stack `names.length` times - */ - bindDynamicScope(names: string[]) { - let scope = this.dynamicScope(); - - for (const name of reverse(names)) { - scope.set(name, this.stack.pop()); - } - } - /** * ## State changes * diff --git a/packages/@glimmer/wire-format/lib/opcodes.ts b/packages/@glimmer/wire-format/lib/opcodes.ts index 824348c763e..8175c57807d 100644 --- a/packages/@glimmer/wire-format/lib/opcodes.ts +++ b/packages/@glimmer/wire-format/lib/opcodes.ts @@ -14,7 +14,6 @@ import type { DynamicAttrOpcode, EachOpcode, FlushElementOpcode, - GetDynamicVarOpcode, GetFreeAsComponentHeadOpcode, GetFreeAsComponentOrHelperHeadOpcode, GetFreeAsHelperHeadOpcode, @@ -43,7 +42,6 @@ import type { TrustingComponentAttrOpcode, TrustingDynamicAttrOpcode, UndefinedOpcode, - WithDynamicVarsOpcode, YieldOpcode, } from '@glimmer/interfaces'; @@ -85,13 +83,11 @@ export const opcodes = { If: 41 satisfies IfOpcode, Each: 42 satisfies EachOpcode, Let: 44 satisfies LetOpcode, - WithDynamicVars: 45 satisfies WithDynamicVarsOpcode, InvokeComponent: 46 satisfies InvokeComponentOpcode, HasBlock: 48 satisfies HasBlockOpcode, HasBlockParams: 49 satisfies HasBlockParamsOpcode, Curry: 50 satisfies CurryOpcode, Not: 51 satisfies NotOpcode, IfInline: 52 satisfies IfInlineOpcode, - GetDynamicVar: 53 satisfies GetDynamicVarOpcode, Log: 54 satisfies LogOpcode, } as const; From 6d1c6c33470f672cb48675b84b4a395ce46ebb7e Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:26:26 -0400 Subject: [PATCH 2/2] Replace DynamicScope with a render scope tracker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the VM's DynamicScope concept (interface, impl, per-closure capture, manager/helper parameters) and replaces it with a minimal render scope tracker: a parent-linked node tree where components with the renamed renderScope capability provide ambient values on their own node and descendants read the nearest provided value. Because provides land on the provider's own node, sibling isolation is free, and custom-manager components no longer need a scope frame at all (they only had dynamicScope: true to prevent set() leaking to siblings). Update-path correctness — previously handled by capturing dynamicScope in every block closure — is handled by paired Enter/ExitRenderScopeOpcode updating opcodes that re-establish the current node as the updating VM descends. Ember's two ambient values migrate to private symbol keys in utils/render-scope.ts: the outlet component manager provides OUTLET_STATE and the {{outlet}} helper reads the nearest one; curly and root managers provide/read VIEW for the classic parentView chain. Co-Authored-By: Claude Fable 5 --- .../glimmer/lib/component-managers/curly.ts | 16 +-- .../glimmer/lib/component-managers/mount.ts | 2 +- .../glimmer/lib/component-managers/outlet.ts | 11 +- .../glimmer/lib/component-managers/root.ts | 10 +- .../lib/component-managers/route-template.ts | 2 +- .../glimmer/lib/components/internal.ts | 4 +- .../@ember/-internals/glimmer/lib/renderer.ts | 36 +------ .../-internals/glimmer/lib/syntax/outlet.ts | 8 +- .../glimmer/lib/utils/render-scope.ts | 36 +++++++ .../lib/components/emberish-curly.ts | 14 +-- .../lib/modes/jit/delegate.ts | 6 +- .../integration-tests/lib/render-delegate.ts | 4 +- .../integration-tests/lib/render-test.ts | 14 +-- .../integration-tests/test/owner-test.ts | 2 +- .../@glimmer/constants/lib/syscall-ops.ts | 8 +- .../@glimmer/debug/lib/opcode-metadata.ts | 16 +-- .../lib/managers/internal/component.d.ts | 10 +- .../interfaces/lib/runtime/environment.d.ts | 2 + .../interfaces/lib/runtime/helper.d.ts | 4 +- .../interfaces/lib/runtime/local-debug.d.ts | 3 +- .../interfaces/lib/runtime/scope.d.ts | 25 ++++- .../@glimmer/interfaces/lib/vm-opcodes.d.ts | 8 +- .../@glimmer/manager/lib/public/component.ts | 2 +- .../@glimmer/manager/lib/util/capabilities.ts | 6 +- .../manager/test/capabilities-test.ts | 10 +- .../@glimmer/manager/test/managers-test.ts | 2 +- .../lib/opcode-builder/delegate.ts | 4 +- .../lib/opcode-builder/helpers/components.ts | 16 +-- packages/@glimmer/runtime/index.ts | 9 +- .../runtime/lib/compiled/opcodes/component.ts | 16 +-- .../lib/compiled/opcodes/expressions.ts | 2 +- .../runtime/lib/compiled/opcodes/vm.ts | 16 ++- .../runtime/lib/component/template-only.ts | 2 +- packages/@glimmer/runtime/lib/environment.ts | 4 + packages/@glimmer/runtime/lib/render-scope.ts | 100 ++++++++++++++++++ packages/@glimmer/runtime/lib/render.ts | 11 +- packages/@glimmer/runtime/lib/scope.ts | 36 +------ packages/@glimmer/runtime/lib/vm/append.ts | 55 +--------- packages/@glimmer/runtime/lib/vm/update.ts | 2 - packages/@glimmer/vm/lib/flags.ts | 4 +- 40 files changed, 278 insertions(+), 260 deletions(-) create mode 100644 packages/@ember/-internals/glimmer/lib/utils/render-scope.ts create mode 100644 packages/@glimmer/runtime/lib/render-scope.ts diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts index a79ef3392af..bfcf113eacd 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts @@ -48,7 +48,8 @@ import { } from '@glimmer/validator/lib/tracking'; import { validateTag, valueForTag } from '@glimmer/validator/lib/validators'; import type Component from '../component'; -import type { DynamicScope } from '../renderer'; +import type { View } from '../renderer'; +import { provideView, readParentView } from '../utils/render-scope'; import type RuntimeResolver from '../resolver'; import { isTemplateFactory } from '../template'; import { @@ -262,13 +263,14 @@ export default class CurlyComponentManager owner: Owner, ComponentClass: ComponentFactory, args: VMArguments, - { isInteractive }: Environment, - dynamicScope: DynamicScope, + env: Environment, callerSelfRef: Reference ): ComponentStateBucket { + let { isInteractive } = env; + // Get the nearest concrete component instance from the scope. "Virtual" // components will be skipped. - let parentView = dynamicScope.view; + let parentView = (readParentView(env.renderScope.current) ?? null) as View | null; // Capture the arguments, which tells Glimmer to give us our own, stable // copy of the Arguments object that is safe to hold on to between renders. @@ -309,8 +311,8 @@ export default class CurlyComponentManager let finalizer = _instrumentStart('render.component', initialRenderInstrumentDetails, component); // We become the new parentView for downstream components, so save our - // component off on the dynamic scope. - dynamicScope.view = component; + // component off on the render scope. + provideView(env, component); // Unless we're the root component, we need to add ourselves to our parent // component's childViews array. @@ -554,7 +556,7 @@ const CURLY_CAPABILITIES: InternalComponentCapabilities = { attributeHook: true, elementHook: true, createCaller: true, - dynamicScope: true, + renderScope: true, updateHook: true, createInstance: true, wrapped: true, diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/mount.ts b/packages/@ember/-internals/glimmer/lib/component-managers/mount.ts index 538377dcea2..fa96090782a 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/mount.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/mount.ts @@ -43,7 +43,7 @@ const CAPABILITIES = { attributeHook: false, elementHook: false, createCaller: true, - dynamicScope: true, + renderScope: true, updateHook: true, createInstance: true, wrapped: false, diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/outlet.ts b/packages/@ember/-internals/glimmer/lib/component-managers/outlet.ts index aba8e2e979d..830bc644220 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/outlet.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/outlet.ts @@ -21,8 +21,8 @@ import { UNDEFINED_REFERENCE, valueForRef } from '@glimmer/reference/lib/referen import { EMPTY_ARGS } from '@glimmer/runtime/lib/vm/arguments'; import { unwrapTemplate } from './unwrap-template'; -import type { DynamicScope } from '../renderer'; import type { OutletState } from '../utils/outlet'; +import { provideOutletState, readOutletState } from '../utils/render-scope'; import type OutletView from '../views/outlet'; function instrumentationPayload(def: OutletDefinitionState) { @@ -53,7 +53,7 @@ const CAPABILITIES: InternalComponentCapabilities = { attributeHook: false, elementHook: false, createCaller: false, - dynamicScope: true, + renderScope: true, updateHook: false, createInstance: true, wrapped: false, @@ -72,17 +72,16 @@ class OutletComponentManager _owner: InternalOwner, definition: OutletDefinitionState, _args: VMArguments, - env: Environment, - dynamicScope: DynamicScope + env: Environment ): OutletInstanceState { - let parentStateRef = dynamicScope.get('outletState'); + let parentStateRef = readOutletState(env.renderScope.current); let currentStateRef = definition.ref; // This is the actual primary responsibility of the outlet component – // it represents the switching from one route component/template into // the next. The rest only exists to support the debug render tree and // the old-school (and unreliable) instrumentation. - dynamicScope.set('outletState', currentStateRef); + provideOutletState(env, currentStateRef); let state: OutletInstanceState = { finalize: _instrumentStart('render.outlet', instrumentationPayload, definition), diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/root.ts b/packages/@ember/-internals/glimmer/lib/component-managers/root.ts index 2ca09c5e158..45448be19da 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/root.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/root.ts @@ -14,13 +14,13 @@ import { capabilityFlagsFrom } from '@glimmer/manager/lib/util/capabilities'; import { CONSTANT_TAG } from '@glimmer/validator/lib/validators'; import { consumeTag } from '@glimmer/validator/lib/tracking'; import type Component from '../component'; -import type { DynamicScope } from '../renderer'; import ComponentStateBucket from '../utils/curly-component-state-bucket'; import CurlyComponentManager, { DIRTY_TAG, initialRenderInstrumentDetails, processComponentInitializationAssertions, } from './curly'; +import { provideView } from '../utils/render-scope'; class RootComponentManager extends CurlyComponentManager { component: Component; @@ -34,14 +34,14 @@ class RootComponentManager extends CurlyComponentManager { _owner: Owner, _state: unknown, _args: Nullable, - { isInteractive }: Environment, - dynamicScope: DynamicScope + env: Environment ) { + let { isInteractive } = env; let component = this.component; let finalizer = _instrumentStart('render.component', initialRenderInstrumentDetails, component); - dynamicScope.view = component; + provideView(env, component); let hasWrappedElement = component.tagName !== ''; @@ -87,7 +87,7 @@ const ROOT_CAPABILITIES: InternalComponentCapabilities = { attributeHook: true, elementHook: true, createCaller: true, - dynamicScope: true, + renderScope: true, updateHook: true, createInstance: true, wrapped: true, diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/route-template.ts b/packages/@ember/-internals/glimmer/lib/component-managers/route-template.ts index c1cba0428ef..f5c5ab6135a 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/route-template.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/route-template.ts @@ -38,7 +38,7 @@ const CAPABILITIES: InternalComponentCapabilities = { attributeHook: false, elementHook: false, createCaller: false, - dynamicScope: false, + renderScope: false, updateHook: false, createInstance: true, wrapped: false, diff --git a/packages/@ember/-internals/glimmer/lib/components/internal.ts b/packages/@ember/-internals/glimmer/lib/components/internal.ts index cd5571b35dc..64ea748e96d 100644 --- a/packages/@ember/-internals/glimmer/lib/components/internal.ts +++ b/packages/@ember/-internals/glimmer/lib/components/internal.ts @@ -5,7 +5,6 @@ import { assert } from '@ember/debug'; import type { CapturedArguments, Destroyable, - DynamicScope, Environment, InternalComponentCapabilities, InternalComponentManager, @@ -164,7 +163,7 @@ const CAPABILITIES: InternalComponentCapabilities = { attributeHook: false, elementHook: false, createCaller: true, - dynamicScope: false, + renderScope: false, updateHook: false, createInstance: true, wrapped: false, @@ -186,7 +185,6 @@ class InternalManager definition: OpaqueInternalComponentConstructor, args: VMArguments, _env: Environment, - _dynamicScope: DynamicScope, caller: Reference ): InternalComponent { assert('caller must be const', isConstRef(caller)); diff --git a/packages/@ember/-internals/glimmer/lib/renderer.ts b/packages/@ember/-internals/glimmer/lib/renderer.ts index e533c4042ba..c7fc5127a2d 100644 --- a/packages/@ember/-internals/glimmer/lib/renderer.ts +++ b/packages/@ember/-internals/glimmer/lib/renderer.ts @@ -14,7 +14,6 @@ import type { Bounds, Environment, RenderResult as GlimmerRenderResult, - DynamicScope as GlimmerDynamicScope, Template, TemplateFactory, EvaluationContext, @@ -39,7 +38,6 @@ import { BOUNDS } from './component-managers/curly'; import { createRootOutlet } from './component-managers/outlet'; import { RootComponentDefinition } from './component-managers/root'; import RouterResolver from './router-resolver'; -import type { OutletState } from './utils/outlet'; import OutletView from './views/outlet'; import { makeRouteTemplate } from './component-managers/route-template'; import type { IBuilder, RendererRoot } from './base-renderer'; @@ -67,34 +65,6 @@ export interface View { [BOUNDS]: Bounds | null; } -export class DynamicScope implements GlimmerDynamicScope { - constructor( - public view: View | null, - public outletState: Reference - ) {} - - child() { - return new DynamicScope(this.view, this.outletState); - } - - get(key: 'outletState'): Reference { - assert( - `Using \`-get-dynamic-scope\` is only supported for \`outletState\` (you used \`${key}\`).`, - key === 'outletState' - ); - return this.outletState; - } - - set(key: 'outletState', value: Reference) { - assert( - `Using \`-with-dynamic-scope\` is only supported for \`outletState\` (you used \`${key}\`).`, - key === 'outletState' - ); - this.outletState = value; - return value; - } -} - class ClassicRootState implements RendererRoot { readonly type = 'classic'; public id: string; @@ -110,7 +80,6 @@ class ClassicRootState implements RendererRoot { template: Template, self: Reference, parentElement: SimpleElement, - dynamicScope: DynamicScope, builder: IBuilder ) { assert( @@ -131,8 +100,7 @@ class ClassicRootState implements RendererRoot { owner, self, builder(context.env, { element: parentElement, nextSibling: null }), - layout, - dynamicScope + layout ); let result = (this.result = iterator.sync()); @@ -268,7 +236,6 @@ export class Renderer extends BaseRenderer { target: SimpleElement ): void { let self = createConstRef(definition, 'this'); - let dynamicScope = new DynamicScope(null, UNDEFINED_REFERENCE); let rootState = new ClassicRootState( root, this.state.context, @@ -276,7 +243,6 @@ export class Renderer extends BaseRenderer { this._rootTemplate, self, target, - dynamicScope, this.state.builder ); this.state.renderRoot(rootState, this); diff --git a/packages/@ember/-internals/glimmer/lib/syntax/outlet.ts b/packages/@ember/-internals/glimmer/lib/syntax/outlet.ts index f39637e320b..dd8581c850f 100644 --- a/packages/@ember/-internals/glimmer/lib/syntax/outlet.ts +++ b/packages/@ember/-internals/glimmer/lib/syntax/outlet.ts @@ -4,7 +4,7 @@ import { DEBUG } from '@glimmer/env'; import type { CapturedArguments, CurriedComponent, - DynamicScope, + RenderScopeNode, Template, } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference/lib/reference'; @@ -24,6 +24,7 @@ import { OutletComponent, type OutletDefinitionState } from '../component-manage import { makeRouteTemplate } from '../component-managers/route-template'; import { internalHelper } from '../helpers/internal-helper'; import type { OutletState } from '../utils/outlet'; +import { readOutletState } from '../utils/render-scope'; /** The `{{outlet}}` helper lets you specify where a child route will render in @@ -57,15 +58,16 @@ import type { OutletState } from '../utils/outlet'; @public */ export const outletHelper = /*@__PURE__*/ internalHelper( - (_args: CapturedArguments, owner?: InternalOwner, scope?: DynamicScope) => { + (_args: CapturedArguments, owner?: InternalOwner, scope?: RenderScopeNode) => { assert('Expected owner to be present, {{outlet}} requires an owner', owner); assert( 'Expected dynamic scope to be present. You may have attempted to use the {{outlet}} keyword dynamically. This keyword cannot be used dynamically.', scope ); + let outletStateRef = readOutletState(scope); let outletRef = createComputeRef(() => { - let state = valueForRef(scope.get('outletState') as Reference); + let state = valueForRef(outletStateRef); return state?.outlets?.main; }); diff --git a/packages/@ember/-internals/glimmer/lib/utils/render-scope.ts b/packages/@ember/-internals/glimmer/lib/utils/render-scope.ts new file mode 100644 index 00000000000..032d0d24877 --- /dev/null +++ b/packages/@ember/-internals/glimmer/lib/utils/render-scope.ts @@ -0,0 +1,36 @@ +import type { Environment, Nullable, RenderScopeNode } from '@glimmer/interfaces'; +import type { Reference } from '@glimmer/reference/lib/reference'; +import { UNDEFINED_REFERENCE } from '@glimmer/reference/lib/reference'; +import { provideRenderScopeValue, readRenderScopeValue } from '@glimmer/runtime/lib/render-scope'; +import type { OutletState } from './outlet'; + +// Ember's two ambient render-scope values. `OUTLET_STATE` is how each +// `{{outlet}}` hands the current route's outlet state to descendant outlets; +// `VIEW` is the classic component `parentView` chain. +const OUTLET_STATE = Symbol('OUTLET_STATE'); +const VIEW = Symbol('VIEW'); + +export function provideOutletState( + env: Environment, + state: Reference +): void { + provideRenderScopeValue(env.renderScope, OUTLET_STATE, state); +} + +export function readOutletState( + scope: Nullable +): Reference { + return ( + (readRenderScopeValue(scope, OUTLET_STATE) as + | Reference + | undefined) ?? UNDEFINED_REFERENCE + ); +} + +export function provideView(env: Environment, view: object): void { + provideRenderScopeValue(env.renderScope, VIEW, view); +} + +export function readParentView(scope: Nullable): object | undefined { + return readRenderScopeValue(scope, VIEW) as object | undefined; +} diff --git a/packages/@glimmer-workspace/integration-tests/lib/components/emberish-curly.ts b/packages/@glimmer-workspace/integration-tests/lib/components/emberish-curly.ts index 61560bb3931..4fa9ef78d7c 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/components/emberish-curly.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/components/emberish-curly.ts @@ -4,7 +4,6 @@ import type { CompilableProgram, Destroyable, Dict, - DynamicScope, ElementOperations, Environment, InternalComponentCapabilities, @@ -40,7 +39,6 @@ export type Attrs = Dict; export type AttrsDiff = { oldAttrs: Nullable; newAttrs: Attrs }; export interface EmberishCurlyComponentFactory extends TestComponentConstructor { - fromDynamicScope?: string[]; positionalParams: string | string[]; create(options: { attrs: Attrs; targetObject: any }): EmberishCurlyComponent; new (...args: unknown[]): this; @@ -124,7 +122,7 @@ const EMBERISH_CURLY_CAPABILITIES: InternalComponentCapabilities = { createArgs: true, attributeHook: true, elementHook: true, - dynamicScope: true, + renderScope: true, createCaller: true, updateHook: true, createInstance: true, @@ -205,7 +203,6 @@ export class EmberishCurlyComponentManager definition: EmberishCurlyComponentFactory, _args: VMArguments, _env: Environment, - dynamicScope: DynamicScope, callerSelf: Reference, hasDefaultBlock: boolean ): EmberishCurlyComponentState { @@ -225,15 +222,6 @@ export class EmberishCurlyComponentManager component.args = args; - let dyn: Nullable = klass.fromDynamicScope || null; - - if (dyn) { - for (let i = 0; i < dyn.length; i++) { - let name = dyn[i] as string; - component.set(name, valueForRef(dynamicScope.get(name))); - } - } - consumeTag(component.dirtinessTag); component.didInitAttrs({ attrs }); diff --git a/packages/@glimmer-workspace/integration-tests/lib/modes/jit/delegate.ts b/packages/@glimmer-workspace/integration-tests/lib/modes/jit/delegate.ts index a4fae43a8bb..0c000ba9318 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/modes/jit/delegate.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/modes/jit/delegate.ts @@ -2,7 +2,6 @@ import type { CapturedRenderNode, Cursor, Dict, - DynamicScope, ElementNamespace, Environment, EvaluationContext, @@ -221,13 +220,12 @@ export class JitRenderDelegate implements RenderDelegate { renderComponent( component: object, args: Record, - element: SimpleElement, - dynamicScope?: DynamicScope + element: SimpleElement ): RenderResult { let cursor = { element, nextSibling: null }; let { env } = this.context; let builder = this.getElementBuilder(env, cursor); - let iterator = renderComponent(this.context, builder, {}, component, args, dynamicScope); + let iterator = renderComponent(this.context, builder, {}, component, args); return renderSync(env, iterator); } diff --git a/packages/@glimmer-workspace/integration-tests/lib/render-delegate.ts b/packages/@glimmer-workspace/integration-tests/lib/render-delegate.ts index 08126efb3f0..953caec9e6a 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/render-delegate.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/render-delegate.ts @@ -1,7 +1,6 @@ import type { Cursor, Dict, - DynamicScope, ElementNamespace, Environment, Helper, @@ -53,8 +52,7 @@ export default interface RenderDelegate { renderComponent?( component: object, args: Record, - element: SimpleElement, - dynamicScope?: DynamicScope + element: SimpleElement ): RenderResult; getElementBuilder(env: Environment, cursor: Cursor): TreeBuilder; getSelf(env: Environment, context: unknown): Reference; diff --git a/packages/@glimmer-workspace/integration-tests/lib/render-test.ts b/packages/@glimmer-workspace/integration-tests/lib/render-test.ts index 3bb2198f2a0..8689cc3f18b 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/render-test.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/render-test.ts @@ -1,7 +1,6 @@ import type { ComponentDefinitionState, Dict, - DynamicScope, Helper, Maybe, Nullable, @@ -389,11 +388,7 @@ export class RenderTest implements IRenderTest { }); } - renderComponent( - component: ComponentDefinitionState, - args: Dict = {}, - dynamicScope?: DynamicScope - ): void { + renderComponent(component: ComponentDefinitionState, args: Dict = {}): void { try { // eslint-disable-next-line @typescript-eslint/no-base-to-string QUnit.assert.ok(true, `Rendering ${String(component)} with ${JSON.stringify(args)}`); @@ -407,12 +402,7 @@ export class RenderTest implements IRenderTest { ); run(() => { - this.renderResult = this.delegate.renderComponent!( - component, - args, - this.element, - dynamicScope - ); + this.renderResult = this.delegate.renderComponent!(component, args, this.element); }); } diff --git a/packages/@glimmer-workspace/integration-tests/test/owner-test.ts b/packages/@glimmer-workspace/integration-tests/test/owner-test.ts index b80180164f5..20886a254e8 100644 --- a/packages/@glimmer-workspace/integration-tests/test/owner-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/owner-test.ts @@ -46,7 +46,7 @@ const CAPABILITIES = { attributeHook: false, elementHook: false, createCaller: false, - dynamicScope: false, + renderScope: false, updateHook: false, createInstance: true, wrapped: false, diff --git a/packages/@glimmer/constants/lib/syscall-ops.ts b/packages/@glimmer/constants/lib/syscall-ops.ts index 93252b7f037..8c70be2a662 100644 --- a/packages/@glimmer/constants/lib/syscall-ops.ts +++ b/packages/@glimmer/constants/lib/syscall-ops.ts @@ -59,7 +59,7 @@ import type { VmOpenElement, VmPop, VmPopArgs, - VmPopDynamicScope, + VmPopRenderScope, VmPopRemoteElement, VmPopScope, VmPopulateLayout, @@ -70,7 +70,7 @@ import type { VmPushBlockScope, VmPushComponentDefinition, VmPushDynamicComponentInstance, - VmPushDynamicScope, + VmPushRenderScope, VmPushEmptyArgs, VmPushRemoteElement, VmPushSymbolTable, @@ -135,8 +135,8 @@ export const VM_FLUSH_ELEMENT_OP = 54 satisfies VmFlushElement; export const VM_CLOSE_ELEMENT_OP = 55 satisfies VmCloseElement; export const VM_POP_REMOTE_ELEMENT_OP = 56 satisfies VmPopRemoteElement; export const VM_MODIFIER_OP = 57 satisfies VmModifier; -export const VM_PUSH_DYNAMIC_SCOPE_OP = 59 satisfies VmPushDynamicScope; -export const VM_POP_DYNAMIC_SCOPE_OP = 60 satisfies VmPopDynamicScope; +export const VM_PUSH_RENDER_SCOPE_OP = 59 satisfies VmPushRenderScope; +export const VM_POP_RENDER_SCOPE_OP = 60 satisfies VmPopRenderScope; export const VM_COMPILE_BLOCK_OP = 61 satisfies VmCompileBlock; export const VM_PUSH_BLOCK_SCOPE_OP = 62 satisfies VmPushBlockScope; export const VM_PUSH_SYMBOL_TABLE_OP = 63 satisfies VmPushSymbolTable; diff --git a/packages/@glimmer/debug/lib/opcode-metadata.ts b/packages/@glimmer/debug/lib/opcode-metadata.ts index 1d227d51a55..ba80bbddb30 100644 --- a/packages/@glimmer/debug/lib/opcode-metadata.ts +++ b/packages/@glimmer/debug/lib/opcode-metadata.ts @@ -69,7 +69,7 @@ import { VM_OPEN_DYNAMIC_ELEMENT_OP, VM_OPEN_ELEMENT_OP, VM_POP_ARGS_OP, - VM_POP_DYNAMIC_SCOPE_OP, + VM_POP_RENDER_SCOPE_OP, VM_POP_OP, VM_POP_REMOTE_ELEMENT_OP, VM_POP_SCOPE_OP, @@ -81,7 +81,7 @@ import { VM_PUSH_BLOCK_SCOPE_OP, VM_PUSH_COMPONENT_DEFINITION_OP, VM_PUSH_DYNAMIC_COMPONENT_INSTANCE_OP, - VM_PUSH_DYNAMIC_SCOPE_OP, + VM_PUSH_RENDER_SCOPE_OP, VM_PUSH_EMPTY_ARGS_OP, VM_PUSH_REMOTE_ELEMENT_OP, VM_PUSH_SYMBOL_TABLE_OP, @@ -462,15 +462,15 @@ if (LOCAL_DEBUG) { ops: ['helper:handle'], }; - METADATA[VM_PUSH_DYNAMIC_SCOPE_OP] = { - name: 'PushDynamicScope', - mnemonic: 'dynscopepush', + METADATA[VM_PUSH_RENDER_SCOPE_OP] = { + name: 'PushRenderScope', + mnemonic: 'rscopepush', stackChange: 0, }; - METADATA[VM_POP_DYNAMIC_SCOPE_OP] = { - name: 'PopDynamicScope', - mnemonic: 'dynscopepop', + METADATA[VM_POP_RENDER_SCOPE_OP] = { + name: 'PopRenderScope', + mnemonic: 'rscopepop', stackChange: 0, }; diff --git a/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts b/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts index 0aaf6814e93..7b2f755d804 100644 --- a/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts +++ b/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts @@ -9,7 +9,6 @@ import type { CapturedArguments, VMArguments } from '../../runtime/arguments.js' import type { RenderNode } from '../../runtime/debug-render-tree.js'; import type { ElementOperations } from '../../runtime/element.js'; import type { Environment } from '../../runtime/environment.js'; -import type { DynamicScope } from '../../runtime/scope.js'; import type { CompilableProgram } from '../../template.js'; import type { ProgramSymbolTable } from '../../tier1/symbol-table.js'; @@ -87,7 +86,7 @@ export interface InternalComponentCapabilities { /** * Whether the component needs an additional dynamic scope frame. */ - dynamicScope: boolean; + renderScope: boolean; /** * Whether there is a component instance to create. If this is false, @@ -119,7 +118,7 @@ export type PrepareArgsCapability = 0b0000000000100; export type CreateArgsCapability = 0b0000000001000; export type AttributeHookCapability = 0b0000000010000; export type ElementHookCapability = 0b0000000100000; -export type DynamicScopeCapability = 0b0000001000000; +export type RenderScopeCapability = 0b0000001000000; export type CreateCallerCapability = 0b0000010000000; export type UpdateHookCapability = 0b0000100000000; export type CreateInstanceCapability = 0b0001000000000; @@ -135,7 +134,7 @@ export type InternalComponentCapability = | CreateArgsCapability | AttributeHookCapability | ElementHookCapability - | DynamicScopeCapability + | RenderScopeCapability | CreateCallerCapability | UpdateHookCapability | CreateInstanceCapability @@ -203,7 +202,6 @@ export interface WithCreateInstance< state: ComponentDefinitionState, args: Nullable, env: Environment, - dynamicScope: Nullable, caller: Nullable, hasDefaultBlock: boolean ): ComponentInstanceState; @@ -234,7 +232,7 @@ export interface WithUpdateHook< > extends InternalComponentManager { // When the component's tag has invalidated, the manager's `update` hook is // called. - update(state: ComponentInstanceState, dynamicScope: Nullable): void; + update(state: ComponentInstanceState): void; } export interface WithDynamicLayout< diff --git a/packages/@glimmer/interfaces/lib/runtime/environment.d.ts b/packages/@glimmer/interfaces/lib/runtime/environment.d.ts index c47f1c98abb..9e8722e2086 100644 --- a/packages/@glimmer/interfaces/lib/runtime/environment.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/environment.d.ts @@ -1,3 +1,4 @@ +import type { RenderScopeStack } from './scope.js'; import type { SimpleDocument } from '@simple-dom/interface'; import type { @@ -46,6 +47,7 @@ export interface Environment { getAppendOperations(): GlimmerTreeConstruction; isInteractive: boolean; + renderScope: RenderScopeStack; debugRenderTree?: DebugRenderTree | undefined; // eslint-disable-next-line @typescript-eslint/no-explicit-any isArgumentCaptureError?: ((error: any) => boolean) | undefined; diff --git a/packages/@glimmer/interfaces/lib/runtime/helper.d.ts b/packages/@glimmer/interfaces/lib/runtime/helper.d.ts index e0993e90fe6..4bb214399f8 100644 --- a/packages/@glimmer/interfaces/lib/runtime/helper.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/helper.d.ts @@ -1,10 +1,10 @@ import type { Reference } from '../references.js'; import type { CapturedArguments } from './arguments.js'; import type { Owner } from './owner.js'; -import type { DynamicScope } from './scope.js'; +import type { RenderScopeNode } from './scope.js'; export type HelperDefinitionState = object; export interface Helper { - (args: CapturedArguments, owner: O | undefined, dynamicScope?: DynamicScope): Reference; + (args: CapturedArguments, owner: O | undefined, renderScope?: RenderScopeNode): Reference; } diff --git a/packages/@glimmer/interfaces/lib/runtime/local-debug.d.ts b/packages/@glimmer/interfaces/lib/runtime/local-debug.d.ts index 8fbbd599212..4cb96f180ea 100644 --- a/packages/@glimmer/interfaces/lib/runtime/local-debug.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/local-debug.d.ts @@ -5,7 +5,7 @@ import type { AppendingBlock } from '../dom/attributes.js'; import type { Cursor } from '../dom/bounds.js'; import type { EvaluationContext } from '../program.js'; import type { BlockMetadata } from '../template.js'; -import type { DynamicScope, Scope, ScopeSlot } from './scope.js'; +import type { Scope, ScopeSlot } from './scope.js'; import type { UpdatingBlockOpcode, UpdatingOpcode } from './vm.js'; export type MachineRegisters = [$pc: number, $ra: number, $fp: number, $sp: number]; @@ -67,7 +67,6 @@ export interface DebugVmSnapshot { export interface DebugStacks { scope: Scope[]; - dynamicScope: DynamicScope[]; updating: UpdatingOpcode[][]; cache: UpdatingOpcode[]; list: UpdatingBlockOpcode[]; diff --git a/packages/@glimmer/interfaces/lib/runtime/scope.d.ts b/packages/@glimmer/interfaces/lib/runtime/scope.d.ts index 05ab4783270..51c7eb5234a 100644 --- a/packages/@glimmer/interfaces/lib/runtime/scope.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/scope.d.ts @@ -32,8 +32,25 @@ export interface Scope { child(): Scope; } -export interface DynamicScope { - get(key: string): Reference; - set(key: string, reference: Reference): Reference; - child(): DynamicScope; +/** + * A node in the render scope tree. Each component with the `renderScope` + * capability gets a node, linked to the nearest enclosing node. Values + * provided at a node are visible to that node's subtree via `readRenderScopeValue`. + */ +export interface RenderScopeNode { + parent: Nullable; + values: Nullable>; +} + +/** + * Tracks the current render scope node for the environment's active render. + * `push` creates a node during initial render; `enter`/`exit` re-establish + * an existing node while the updating VM descends the tree. + */ +export interface RenderScopeStack { + readonly current: Nullable; + begin(): void; + push(): RenderScopeNode; + enter(node: RenderScopeNode): void; + exit(): void; } diff --git a/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts b/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts index 58b32867280..4d1ba120e6d 100644 --- a/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts +++ b/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts @@ -61,8 +61,8 @@ export type VmFlushElement = 54; export type VmCloseElement = 55; export type VmPopRemoteElement = 56; export type VmModifier = 57; -export type VmPushDynamicScope = 59; -export type VmPopDynamicScope = 60; +export type VmPushRenderScope = 59; +export type VmPopRenderScope = 60; export type VmCompileBlock = 61; export type VmPushBlockScope = 62; export type VmPushSymbolTable = 63; @@ -154,8 +154,8 @@ export type VmOp = | VmCloseElement | VmPopRemoteElement | VmModifier - | VmPushDynamicScope - | VmPopDynamicScope + | VmPushRenderScope + | VmPopRenderScope | VmCompileBlock | VmPushBlockScope | VmPushSymbolTable diff --git a/packages/@glimmer/manager/lib/public/component.ts b/packages/@glimmer/manager/lib/public/component.ts index 9ab0635c3dc..fea778c81fd 100644 --- a/packages/@glimmer/manager/lib/public/component.ts +++ b/packages/@glimmer/manager/lib/public/component.ts @@ -33,7 +33,7 @@ const CAPABILITIES = { attributeHook: false, elementHook: false, createCaller: false, - dynamicScope: true, + renderScope: false, updateHook: true, createInstance: true, wrapped: false, diff --git a/packages/@glimmer/manager/lib/util/capabilities.ts b/packages/@glimmer/manager/lib/util/capabilities.ts index 9ec370a32df..59f330c42ad 100644 --- a/packages/@glimmer/manager/lib/util/capabilities.ts +++ b/packages/@glimmer/manager/lib/util/capabilities.ts @@ -7,7 +7,7 @@ import type { CreateCallerCapability, CreateInstanceCapability, DynamicLayoutCapability, - DynamicScopeCapability, + RenderScopeCapability, DynamicTagCapability, ElementHookCapability, Expand, @@ -56,7 +56,7 @@ export function capabilityFlagsFrom(capabilities: CapabilityOptions): Capability capability(capabilities, 'createArgs') | capability(capabilities, 'attributeHook') | capability(capabilities, 'elementHook') | - capability(capabilities, 'dynamicScope') | + capability(capabilities, 'renderScope') | capability(capabilities, 'createCaller') | capability(capabilities, 'updateHook') | capability(capabilities, 'createInstance') | @@ -85,7 +85,7 @@ export type InternalComponentCapabilityFor { createArgs: false, attributeHook: false, elementHook: false, - dynamicScope: false, + renderScope: false, createCaller: false, updateHook: false, createInstance: false, @@ -33,7 +33,7 @@ QUnit.test('encodes a capabilities object into a bitmap', (assert) => { createArgs: true, attributeHook: true, elementHook: true, - dynamicScope: true, + renderScope: true, createCaller: true, updateHook: true, createInstance: true, @@ -53,7 +53,7 @@ QUnit.test('encodes a capabilities object into a bitmap', (assert) => { createArgs: false, attributeHook: false, elementHook: true, - dynamicScope: false, + renderScope: false, createCaller: false, updateHook: true, createInstance: false, @@ -74,7 +74,7 @@ QUnit.test('allows querying bitmap for a capability', (assert) => { createArgs: false, attributeHook: false, elementHook: true, - dynamicScope: true, + renderScope: true, createCaller: false, updateHook: true, createInstance: false, @@ -129,7 +129,7 @@ QUnit.test('allows querying bitmap for a capability', (assert) => { managerHasCapability( {} as InternalComponentManager, capabilities, - InternalComponentCapabilities.dynamicScope + InternalComponentCapabilities.renderScope ) ); assert.false( diff --git a/packages/@glimmer/manager/test/managers-test.ts b/packages/@glimmer/manager/test/managers-test.ts index 2d28b602635..8eeb3b6fdeb 100644 --- a/packages/@glimmer/manager/test/managers-test.ts +++ b/packages/@glimmer/manager/test/managers-test.ts @@ -71,7 +71,7 @@ module('Managers', () => { createArgs: false, attributeHook: false, elementHook: false, - dynamicScope: false, + renderScope: false, createCaller: false, updateHook: false, createInstance: false, diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/delegate.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/delegate.ts index fb83e191533..00ee90ee063 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/delegate.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/delegate.ts @@ -11,7 +11,7 @@ export const DEFAULT_CAPABILITIES: InternalComponentCapabilities = { createArgs: true, attributeHook: false, elementHook: false, - dynamicScope: true, + renderScope: true, createCaller: false, updateHook: true, createInstance: true, @@ -27,7 +27,7 @@ export const MINIMAL_CAPABILITIES: InternalComponentCapabilities = { createArgs: false, attributeHook: false, elementHook: false, - dynamicScope: false, + renderScope: false, createCaller: false, updateHook: false, createInstance: false, diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/components.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/components.ts index 4bbfe09b09d..b16ead64be6 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/components.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/components.ts @@ -27,7 +27,7 @@ import { VM_JUMP_UNLESS_OP, VM_LOAD_OP, VM_OPEN_DYNAMIC_ELEMENT_OP, - VM_POP_DYNAMIC_SCOPE_OP, + VM_POP_RENDER_SCOPE_OP, VM_POP_OP, VM_POP_SCOPE_OP, VM_POPULATE_LAYOUT_OP, @@ -36,7 +36,7 @@ import { VM_PUSH_ARGS_OP, VM_PUSH_COMPONENT_DEFINITION_OP, VM_PUSH_DYNAMIC_COMPONENT_INSTANCE_OP, - VM_PUSH_DYNAMIC_SCOPE_OP, + VM_PUSH_RENDER_SCOPE_OP, VM_PUSH_EMPTY_ARGS_OP, VM_PUSH_SYMBOL_TABLE_OP, VM_PUT_COMPONENT_OPERATIONS_OP, @@ -310,8 +310,8 @@ function InvokeStaticComponent( op(VM_BEGIN_COMPONENT_TRANSACTION_OP, $s0); - if (hasCapability(capabilities, InternalComponentCapabilities.dynamicScope)) { - op(VM_PUSH_DYNAMIC_SCOPE_OP); + if (hasCapability(capabilities, InternalComponentCapabilities.renderScope)) { + op(VM_PUSH_RENDER_SCOPE_OP); } if (hasCapability(capabilities, InternalComponentCapabilities.createInstance)) { @@ -367,8 +367,8 @@ function InvokeStaticComponent( op(VM_POP_FRAME_OP); op(VM_POP_SCOPE_OP); - if (hasCapability(capabilities, InternalComponentCapabilities.dynamicScope)) { - op(VM_POP_DYNAMIC_SCOPE_OP); + if (hasCapability(capabilities, InternalComponentCapabilities.renderScope)) { + op(VM_POP_RENDER_SCOPE_OP); } op(VM_COMMIT_COMPONENT_TRANSACTION_OP); @@ -446,7 +446,7 @@ export function invokePreparedComponent( populateLayout: Nullable<() => void> = null ): void { op(VM_BEGIN_COMPONENT_TRANSACTION_OP, $s0); - op(VM_PUSH_DYNAMIC_SCOPE_OP); + op(VM_PUSH_RENDER_SCOPE_OP); // eslint-disable-next-line @typescript-eslint/no-explicit-any op(VM_CREATE_COMPONENT_OP, (hasBlock as any) | 0); @@ -474,7 +474,7 @@ export function invokePreparedComponent( op(VM_POP_FRAME_OP); op(VM_POP_SCOPE_OP); - op(VM_POP_DYNAMIC_SCOPE_OP); + op(VM_POP_RENDER_SCOPE_OP); op(VM_COMMIT_COMPONENT_TRANSACTION_OP); } diff --git a/packages/@glimmer/runtime/index.ts b/packages/@glimmer/runtime/index.ts index 72e95d58c30..39585f9b291 100644 --- a/packages/@glimmer/runtime/index.ts +++ b/packages/@glimmer/runtime/index.ts @@ -43,7 +43,14 @@ export { not } from './lib/helpers/not'; export { or } from './lib/helpers/or'; export { on } from './lib/modifiers/on'; export { renderComponent, renderMain, renderSync } from './lib/render'; -export { DynamicScopeImpl, ScopeImpl } from './lib/scope'; +export { + EnterRenderScopeOpcode, + ExitRenderScopeOpcode, + provideRenderScopeValue, + readRenderScopeValue, + RenderScopeStackImpl, +} from './lib/render-scope'; +export { ScopeImpl } from './lib/scope'; export type { SafeString } from './lib/upsert'; export { UpdatingVM, type VM } from './lib/vm'; export { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index ec5b2c206d1..1f78affef14 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -10,7 +10,6 @@ import type { ComponentInstanceState, ComponentInstanceWithCreate, Dict, - DynamicScope, ElementOperations, InternalComponentManager, ModifierInstance, @@ -394,11 +393,6 @@ APPEND_OPCODES.add(VM_CREATE_COMPONENT_OP, (vm, { op1: flags }) => { return; } - let dynamicScope: Nullable = null; - if (managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicScope)) { - dynamicScope = vm.dynamicScope(); - } - let hasDefaultBlock = flags & 1; let args: Nullable = null; @@ -416,7 +410,6 @@ APPEND_OPCODES.add(VM_CREATE_COMPONENT_OP, (vm, { op1: flags }) => { definition.state, args, vm.env, - dynamicScope, self, !!hasDefaultBlock ); @@ -426,7 +419,7 @@ APPEND_OPCODES.add(VM_CREATE_COMPONENT_OP, (vm, { op1: flags }) => { instance.state = state; if (managerHasCapability(manager, capabilities, InternalComponentCapabilities.updateHook)) { - vm.updateWith(new UpdateComponentOpcode(state, manager, dynamicScope)); + vm.updateWith(new UpdateComponentOpcode(state, manager)); } }); @@ -925,14 +918,13 @@ APPEND_OPCODES.add(VM_COMMIT_COMPONENT_TRANSACTION_OP, (vm) => { export class UpdateComponentOpcode implements UpdatingOpcode { constructor( private component: ComponentInstanceState, - private manager: WithUpdateHook, - private dynamicScope: Nullable + private manager: WithUpdateHook ) {} evaluate(_vm: UpdatingVM) { - let { component, manager, dynamicScope } = this; + let { component, manager } = this; - manager.update(component, dynamicScope); + manager.update(component); } } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts index 56584123abb..a69a1b30af8 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts @@ -175,7 +175,7 @@ APPEND_OPCODES.add(VM_HELPER_OP, (vm, { op1: handle }) => { let stack = vm.stack; let helper = check(vm.constants.getValue(handle), CheckHelper); let args = check(stack.pop(), CheckArguments); - let value = helper(args.capture(), vm.getOwner(), vm.dynamicScope()); + let value = helper(args.capture(), vm.getOwner(), vm.env.renderScope.current ?? undefined); if (_hasDestroyableChildren(value)) { vm.associateDestroyable(value); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index a22780aa900..1ea6e8794ae 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -18,13 +18,13 @@ import { VM_JUMP_IF_OP, VM_JUMP_UNLESS_OP, VM_LOAD_OP, - VM_POP_DYNAMIC_SCOPE_OP, + VM_POP_RENDER_SCOPE_OP, VM_POP_OP, VM_POP_SCOPE_OP, VM_PRIMITIVE_OP, VM_PRIMITIVE_REFERENCE_OP, VM_PUSH_BLOCK_SCOPE_OP, - VM_PUSH_DYNAMIC_SCOPE_OP, + VM_PUSH_RENDER_SCOPE_OP, VM_PUSH_SYMBOL_TABLE_OP, VM_TO_BOOLEAN_OP, } from '@glimmer/constants/lib/syscall-ops'; @@ -56,6 +56,7 @@ import { import { beginTrackFrame, consumeTag, endTrackFrame } from '@glimmer/validator/lib/tracking'; import { CONSTANT_TAG, INITIAL, validateTag, valueForTag } from '@glimmer/validator/lib/validators'; +import { EnterRenderScopeOpcode, ExitRenderScopeOpcode } from '../../render-scope'; import type { UpdatingVM } from '../../vm'; import type { VM } from '../../vm/append'; @@ -67,9 +68,16 @@ APPEND_OPCODES.add(VM_CHILD_SCOPE_OP, (vm) => vm.pushChildScope()); APPEND_OPCODES.add(VM_POP_SCOPE_OP, (vm) => vm.popScope()); -APPEND_OPCODES.add(VM_PUSH_DYNAMIC_SCOPE_OP, (vm) => vm.pushDynamicScope()); +APPEND_OPCODES.add(VM_PUSH_RENDER_SCOPE_OP, (vm) => { + let scope = vm.env.renderScope; + vm.updateWith(new EnterRenderScopeOpcode(scope.push(), scope)); +}); -APPEND_OPCODES.add(VM_POP_DYNAMIC_SCOPE_OP, (vm) => vm.popDynamicScope()); +APPEND_OPCODES.add(VM_POP_RENDER_SCOPE_OP, (vm) => { + let scope = vm.env.renderScope; + scope.exit(); + vm.updateWith(new ExitRenderScopeOpcode(scope)); +}); APPEND_OPCODES.add(VM_CONSTANT_OP, (vm, { op1: other }) => { vm.stack.push(vm.constants.getValue(decodeHandle(other))); diff --git a/packages/@glimmer/runtime/lib/component/template-only.ts b/packages/@glimmer/runtime/lib/component/template-only.ts index 6871d658d27..1524e9fd499 100644 --- a/packages/@glimmer/runtime/lib/component/template-only.ts +++ b/packages/@glimmer/runtime/lib/component/template-only.ts @@ -11,7 +11,7 @@ const CAPABILITIES: InternalComponentCapabilities = { attributeHook: false, elementHook: false, createCaller: false, - dynamicScope: false, + renderScope: false, updateHook: false, createInstance: false, wrapped: false, diff --git a/packages/@glimmer/runtime/lib/environment.ts b/packages/@glimmer/runtime/lib/environment.ts index bafbefe2f82..3fccfe4972d 100644 --- a/packages/@glimmer/runtime/lib/environment.ts +++ b/packages/@glimmer/runtime/lib/environment.ts @@ -19,6 +19,7 @@ import { ProgramImpl } from '@glimmer/program/lib/program'; import { track } from '@glimmer/validator/lib/tracking'; import { UPDATE_TAG as updateTag } from '@glimmer/validator/lib/validators'; +import { RenderScopeStackImpl } from './render-scope'; import DebugRenderTree from './debug-render-tree'; import { DOMChangesImpl, DOMTreeConstruction } from './dom/helper'; import { isArgumentError } from './vm/arguments'; @@ -99,6 +100,8 @@ class TransactionImpl implements Transaction { export class EnvironmentImpl implements Environment { [TRANSACTION]: Nullable = null; + readonly renderScope = new RenderScopeStackImpl(); + declare protected appendOperations: GlimmerTreeConstruction; protected updateOperations?: GlimmerTreeChanges | undefined; @@ -144,6 +147,7 @@ export class EnvironmentImpl implements Environment { 'A glimmer transaction was begun, but one already exists. You may have a nested transaction, possibly caused by an earlier runtime exception while rendering. Please check your console for the stack trace of any prior exceptions.' ); + this.renderScope.begin(); this.debugRenderTree?.begin(); this[TRANSACTION] = new TransactionImpl(); diff --git a/packages/@glimmer/runtime/lib/render-scope.ts b/packages/@glimmer/runtime/lib/render-scope.ts new file mode 100644 index 00000000000..4b8be61b498 --- /dev/null +++ b/packages/@glimmer/runtime/lib/render-scope.ts @@ -0,0 +1,100 @@ +import type { + Nullable, + RenderScopeNode, + RenderScopeStack, + UpdatingOpcode, + UpdatingVM, +} from '@glimmer/interfaces'; + +/** + * Tracks the render scope tree: one node per component with the `renderScope` + * capability, linked to the nearest enclosing node. A component provides + * values (e.g. Ember's `outletState` and `view`) on its own node, and + * descendants read the nearest provided value by walking up the parent chain. + * + * During initial render, `push` creates nodes as the append VM encounters + * components. During updates, `EnterRenderScopeOpcode`/`ExitRenderScopeOpcode` + * re-establish existing nodes as the updating VM descends, so any content + * (re-)rendered mid-update — e.g. a `TryOpcode` re-rendering after a state + * change — sees the same scope it would have on initial render. + */ +export class RenderScopeStackImpl implements RenderScopeStack { + private stack: RenderScopeNode[] = []; + + get current(): Nullable { + return this.stack.length === 0 ? null : (this.stack[this.stack.length - 1] as RenderScopeNode); + } + + // Drop any nodes left behind by a render that errored mid-flight. + begin(): void { + this.stack.length = 0; + } + + push(): RenderScopeNode { + let node: RenderScopeNode = { parent: this.current, values: null }; + this.stack.push(node); + return node; + } + + enter(node: RenderScopeNode): void { + this.stack.push(node); + } + + exit(): void { + this.stack.pop(); + } +} + +/** + * Provide `value` for `key` at the current render scope node — visible to the + * providing component's subtree. May only be called while that component is + * rendering (in practice: from a component manager's `create`). + */ +export function provideRenderScopeValue( + scope: RenderScopeStack, + key: PropertyKey, + value: unknown +): void { + let node = scope.current; + + if (node === null) { + throw new Error( + 'Attempted to provide a render scope value outside of rendering a component with the `renderScope` capability' + ); + } + + (node.values ??= new Map()).set(key, value); +} + +/** + * Read the nearest provided value for `key`, starting at `node` and walking up + * the render scope tree. Returns `undefined` if no ancestor provides `key`. + */ +export function readRenderScopeValue(node: Nullable, key: PropertyKey): unknown { + for (let current = node; current !== null; current = current.parent) { + if (current.values?.has(key)) { + return current.values.get(key); + } + } + + return undefined; +} + +export class EnterRenderScopeOpcode implements UpdatingOpcode { + constructor( + private node: RenderScopeNode, + private scope: RenderScopeStack + ) {} + + evaluate(_vm: UpdatingVM) { + this.scope.enter(this.node); + } +} + +export class ExitRenderScopeOpcode implements UpdatingOpcode { + constructor(private scope: RenderScopeStack) {} + + evaluate(_vm: UpdatingVM) { + this.scope.exit(); + } +} diff --git a/packages/@glimmer/runtime/lib/render.ts b/packages/@glimmer/runtime/lib/render.ts index 7061473ebe7..9ca73ae1507 100644 --- a/packages/@glimmer/runtime/lib/render.ts +++ b/packages/@glimmer/runtime/lib/render.ts @@ -2,7 +2,6 @@ import { DEBUG } from '@glimmer/env'; import type { CompilableProgram, ComponentDefinitionState, - DynamicScope, Environment, EvaluationContext, Owner, @@ -19,7 +18,6 @@ import { childRefFor, createConstRef } from '@glimmer/reference/lib/reference'; import { debug } from '@glimmer/validator/lib/debug'; import { inTransaction } from './environment'; -import { DynamicScopeImpl } from './scope'; import { VM } from './vm/append'; class TemplateIteratorImpl implements TemplateIterator { @@ -52,8 +50,7 @@ export function renderMain( owner: Owner, self: Reference, tree: TreeBuilder, - layout: CompilableProgram, - dynamicScope: DynamicScope = new DynamicScopeImpl() + layout: CompilableProgram ): TemplateIterator { let handle = unwrapHandle(layout.compile(context)); let numSymbols = layout.symbolTable.symbols.length; @@ -63,7 +60,6 @@ export function renderMain( self, size: numSymbols, }, - dynamicScope, tree, handle, owner, @@ -130,10 +126,9 @@ export function renderComponent( tree: TreeBuilder, owner: Owner, definition: ComponentDefinitionState, - args: Record = {}, - dynamicScope: DynamicScope = new DynamicScopeImpl() + args: Record = {} ): TemplateIterator { - let vm = VM.initial(context, { tree, handle: context.stdlib.main, dynamicScope, owner }); + let vm = VM.initial(context, { tree, handle: context.stdlib.main, owner }); return renderInvocation(vm, context, owner, definition, recordToReference(args)); } diff --git a/packages/@glimmer/runtime/lib/scope.ts b/packages/@glimmer/runtime/lib/scope.ts index 035f950f742..5e81c39c632 100644 --- a/packages/@glimmer/runtime/lib/scope.ts +++ b/packages/@glimmer/runtime/lib/scope.ts @@ -1,40 +1,6 @@ -import type { - Dict, - DynamicScope, - Nullable, - Owner, - Scope, - ScopeBlock, - ScopeSlot, -} from '@glimmer/interfaces'; +import type { Nullable, Owner, Scope, ScopeBlock, ScopeSlot } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference/lib/reference'; -import { unwrap } from '@glimmer/debug-util/lib/platform-utils'; import { UNDEFINED_REFERENCE } from '@glimmer/reference/lib/reference'; -import { assign } from '@glimmer/util/lib/object-utils'; - -export class DynamicScopeImpl implements DynamicScope { - private bucket: Dict; - - constructor(bucket?: Dict) { - if (bucket) { - this.bucket = assign({}, bucket); - } else { - this.bucket = {}; - } - } - - get(key: string): Reference { - return unwrap(this.bucket[key]); - } - - set(key: string, reference: Reference): Reference { - return (this.bucket[key] = reference); - } - - child(): DynamicScopeImpl { - return new DynamicScopeImpl(this.bucket); - } -} export function isScopeReference(s: ScopeSlot): s is Reference { if (s === null || Array.isArray(s)) return false; diff --git a/packages/@glimmer/runtime/lib/vm/append.ts b/packages/@glimmer/runtime/lib/vm/append.ts index 29bc50f0ab1..dff9d54b051 100644 --- a/packages/@glimmer/runtime/lib/vm/append.ts +++ b/packages/@glimmer/runtime/lib/vm/append.ts @@ -7,7 +7,6 @@ import type { DebugVmSnapshot, DebugVmTrace, Destroyable, - DynamicScope, Environment, EvaluationContext, Owner, @@ -58,22 +57,19 @@ class Stacks { readonly drop: object = {}; readonly scope = new Stack(); - readonly dynamicScope = new Stack(); readonly updating = new Stack(); readonly cache = new Stack(); readonly list = new Stack(); readonly destroyable = new Stack(); - constructor(scope: Scope, dynamicScope: DynamicScope) { + constructor(scope: Scope) { this.scope.push(scope); - this.dynamicScope.push(dynamicScope); this.destroyable.push(this.drop); if (LOCAL_DEBUG) { this.debug = (): DebugStacks => { return { scope: this.scope.snapshot(), - dynamicScope: this.dynamicScope.snapshot(), updating: this.updating.snapshot(), cache: this.cache.snapshot(), list: this.list.snapshot(), @@ -223,7 +219,7 @@ export class VM { readonly context: EvaluationContext; constructor( - { scope, dynamicScope, stack, pc }: ClosureState, + { scope, stack, pc }: ClosureState, context: EvaluationContext, tree: TreeBuilder ) { @@ -237,7 +233,7 @@ export class VM { this.#tree = tree; this.context = context; - this.#stacks = new Stacks(scope, dynamicScope); + this.#stacks = new Stacks(scope); this.args = new VMArgumentsImpl(); this.lowlevel = new LowLevelVM(evalStack, context, externs(this), evalStack.registers); @@ -279,11 +275,7 @@ export class VM { options.scope ?? { self: UNDEFINED_REFERENCE, size: 0 } ); - const state = closureState( - context.program.heap.getaddr(options.handle), - scope, - options.dynamicScope - ); + const state = closureState(context.program.heap.getaddr(options.handle), scope); return new VM(state, context, options.tree); } @@ -314,7 +306,6 @@ export class VM { return { pc, scope: this.scope(), - dynamicScope: this.dynamicScope(), stack: this.stack.capture(args), }; } @@ -594,21 +585,6 @@ export class VM { this.#stacks.scope.pop(); } - /** - * ## Opcodes - * - * - Append: `PushDynamicScope` - * - * ## State changes: - * - * [!] push Dynamic Scope Stack <- child of current Dynamic Scope - */ - pushDynamicScope(): DynamicScope { - let child = this.dynamicScope().child(); - this.#stacks.dynamicScope.push(child); - return child; - } - /** * ## State changes * @@ -679,20 +655,6 @@ export class VM { return expect(this.#stacks.scope.current, 'expected scope on the scope stack'); } - /** - * Get current Dynamic Scope - */ - dynamicScope(): DynamicScope { - return expect( - this.#stacks.dynamicScope.current, - 'expected dynamic scope on the dynamic scope stack' - ); - } - - popDynamicScope() { - this.#stacks.dynamicScope.pop(); - } - /// SCOPE HELPERS getOwner(): Owner { @@ -780,11 +742,10 @@ export class VM { } } -function closureState(pc: number, scope: Scope, dynamicScope: DynamicScope): ClosureState { +function closureState(pc: number, scope: Scope): ClosureState { return { pc, scope, - dynamicScope, stack: [], }; } @@ -806,7 +767,6 @@ export interface InitialVmState { * */ tree: TreeBuilder; - dynamicScope: DynamicScope; owner: Owner; } @@ -822,11 +782,6 @@ export interface ClosureState { */ readonly scope: Scope; - /** - * The current value of the VM's dynamic scope - */ - readonly dynamicScope: DynamicScope; - /** * A number of stack elements captured during the initial evaluation, and which should be restored * to the stack when the block is re-evaluated. diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index 92981cc0531..3e9591cc35d 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -2,7 +2,6 @@ import { DEBUG } from '@glimmer/env'; import type { AppendingBlock, Bounds, - DynamicScope, Environment, EvaluationContext, ExceptionHandler, @@ -106,7 +105,6 @@ export class UpdatingVM implements IUpdatingVM { export interface VMState { readonly pc: number; readonly scope: Scope; - readonly dynamicScope: DynamicScope; readonly stack: unknown[]; } diff --git a/packages/@glimmer/vm/lib/flags.ts b/packages/@glimmer/vm/lib/flags.ts index 7ca9922c12f..e66bd64733b 100644 --- a/packages/@glimmer/vm/lib/flags.ts +++ b/packages/@glimmer/vm/lib/flags.ts @@ -5,7 +5,7 @@ import type { CreateCallerCapability, CreateInstanceCapability, DynamicLayoutCapability, - DynamicScopeCapability, + RenderScopeCapability, DynamicTagCapability, ElementHookCapability, EmptyCapability, @@ -29,7 +29,7 @@ export const InternalComponentCapabilities = { createArgs: 0b0000000001000 satisfies CreateArgsCapability, attributeHook: 0b0000000010000 satisfies AttributeHookCapability, elementHook: 0b0000000100000 satisfies ElementHookCapability, - dynamicScope: 0b0000001000000 satisfies DynamicScopeCapability, + renderScope: 0b0000001000000 satisfies RenderScopeCapability, createCaller: 0b0000010000000 satisfies CreateCallerCapability, updateHook: 0b0000100000000 satisfies UpdateHookCapability, createInstance: 0b0001000000000 satisfies CreateInstanceCapability,