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/@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/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/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-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/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..8c70be2a662 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, @@ -61,7 +59,7 @@ import type { VmOpenElement, VmPop, VmPopArgs, - VmPopDynamicScope, + VmPopRenderScope, VmPopRemoteElement, VmPopScope, VmPopulateLayout, @@ -72,7 +70,7 @@ import type { VmPushBlockScope, VmPushComponentDefinition, VmPushDynamicComponentInstance, - VmPushDynamicScope, + VmPushRenderScope, VmPushEmptyArgs, VmPushRemoteElement, VmPushSymbolTable, @@ -137,9 +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_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_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; @@ -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..ba80bbddb30 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, @@ -70,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, @@ -82,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, @@ -463,22 +462,15 @@ 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', + 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/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/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 53058efcddb..4d1ba120e6d 100644 --- a/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts +++ b/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts @@ -61,9 +61,8 @@ 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 VmPushRenderScope = 59; +export type VmPopRenderScope = 60; export type VmCompileBlock = 61; export type VmPushBlockScope = 62; export type VmPushSymbolTable = 63; @@ -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,9 +154,8 @@ export type VmOp = | VmCloseElement | VmPopRemoteElement | VmModifier - | VmBindDynamicScope - | VmPushDynamicScope - | VmPopDynamicScope + | VmPushRenderScope + | VmPopRenderScope | VmCompileBlock | VmPushBlockScope | VmPushSymbolTable @@ -205,7 +202,6 @@ export type VmOp = | VmDynamicModifier | VmIfInline | VmNot - | VmGetDynamicVar | VmLog; export type SomeVmOp = VmOp | VmMachineOp; 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/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/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 0e99c73c52f..a69a1b30af8 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, @@ -176,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); @@ -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..1ea6e8794ae 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, @@ -19,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'; @@ -57,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'; @@ -68,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))); @@ -130,11 +137,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/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 d6f1613e89f..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, @@ -30,7 +29,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'; @@ -59,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(), @@ -224,7 +219,7 @@ export class VM { readonly context: EvaluationContext; constructor( - { scope, dynamicScope, stack, pc }: ClosureState, + { scope, stack, pc }: ClosureState, context: EvaluationContext, tree: TreeBuilder ) { @@ -238,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); @@ -280,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); } @@ -315,7 +306,6 @@ export class VM { return { pc, scope: this.scope(), - dynamicScope: this.dynamicScope(), stack: this.stack.capture(args), }; } @@ -595,38 +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; - } - - /** - * ## 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 * @@ -697,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 { @@ -798,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: [], }; } @@ -824,7 +767,6 @@ export interface InitialVmState { * */ tree: TreeBuilder; - dynamicScope: DynamicScope; owner: Owner; } @@ -840,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, 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;