@@ -39,13 +39,14 @@ import {
3939} from '@glimmer/debug' ;
4040import { debugToString , localAssert } from '@glimmer/debug-util' ;
4141import { _hasDestroyableChildren , associateDestroyableChild , destroy } from '@glimmer/destroyable' ;
42- import { debugAssert , setProp , toBool } from '@glimmer/global-context' ;
42+ import { debugAssert , toBool } from '@glimmer/global-context' ;
4343import {
4444 getInternalComponentManager ,
4545 getInternalHelperManager ,
4646 getInternalModifierManager ,
4747} from '@glimmer/manager' ;
4848import type { Reference } from '@glimmer/reference' ;
49+ import type { ChildRefTransform } from '@glimmer/reference' ;
4950import {
5051 childRefFor ,
5152 createComputeRef ,
@@ -54,7 +55,7 @@ import {
5455 UNDEFINED_REFERENCE ,
5556 valueForRef ,
5657} from '@glimmer/reference' ;
57- import { assign , isDict , isIndexable } from '@glimmer/util' ;
58+ import { assign , isIndexable } from '@glimmer/util' ;
5859import { $v0 } from '@glimmer/vm' ;
5960
6061import { isCurriedType , resolveCurriedValue } from '../../curried-value' ;
@@ -75,58 +76,43 @@ import {
7576
7677// Returns true if the value has an associated component or modifier manager —
7778// meaning it is a definition object that must not be bound. Helper managers are
78- // excluded because ALL functions have a default helper manager (RFC #756), and
79- // binding a helper function is harmless.
79+ // excluded because ALL functions have a default helper manager (RFC #756).
8080function hasDefinitionManager ( value : object ) : boolean {
8181 return (
8282 getInternalModifierManager ( value , true ) !== null ||
8383 getInternalComponentManager ( value , true ) !== null
8484 ) ;
8585}
8686
87- // Like childRefFor, but for function values returns a stable wrapper that
88- // defers the property read to invocation time, calling it with the parent
89- // as `this`. Non-function values pass through unchanged.
90- //
91- // Uses childRefFor internally for tracking — the property is only tracked
92- // the same way it would be without binding.
93- function boundChildRefFor ( parentRef : Reference , path : string ) : Reference {
94- const innerRef = childRefFor ( parentRef , path ) ;
95-
96- let cachedParent : object | null = null ;
97- let wrapper : CallableFunction | null = null ;
98-
99- return createComputeRef (
100- ( ) => {
101- const value = valueForRef ( innerRef ) ;
102-
103- if ( typeof value !== 'function' || hasDefinitionManager ( value as object ) ) {
104- return value ;
105- }
87+ // Per-parent cache of wrapper functions so that the same (parent, path) pair
88+ // returns a stable callable identity across revalidations.
89+ const BOUND_FN_CACHE : WeakMap < object , Map < string , CallableFunction > > = new WeakMap ( ) ;
90+
91+ // Transform for childRefFor that wraps function values in a stable callable
92+ // preserving `this`. The wrapper defers the actual property lookup to
93+ // invocation time — `parent[path]` is evaluated when the function is called,
94+ // not during render.
95+ const bindTransform : ChildRefTransform = ( parent , path , value ) => {
96+ if ( typeof value !== 'function' || hasDefinitionManager ( value as object ) ) {
97+ return value ;
98+ }
10699
107- const parent = valueForRef ( parentRef ) ;
108-
109- // Return a stable wrapper per parent identity. The wrapper defers
110- // the property read to call time — `parent[path]` is evaluated
111- // only when the function is actually invoked, not during render.
112- if ( parent !== cachedParent ) {
113- cachedParent = parent ;
114- wrapper = function ( this : unknown , ...args : unknown [ ] ) {
115- const fn = ( parent as Record < string , CallableFunction > ) [ path ] ;
116- return fn ?. call ( parent , ...args ) ;
117- } ;
118- }
119- return wrapper ;
120- } ,
121- ( val ) => {
122- const parent = valueForRef ( parentRef ) ;
100+ let cache = BOUND_FN_CACHE . get ( parent ) ;
101+ if ( cache === undefined ) {
102+ cache = new Map ( ) ;
103+ BOUND_FN_CACHE . set ( parent , cache ) ;
104+ }
123105
124- if ( isDict ( parent ) ) {
125- return setProp ( parent , path , val ) ;
126- }
127- }
128- ) ;
129- }
106+ let wrapper = cache . get ( path ) ;
107+ if ( wrapper === undefined ) {
108+ wrapper = function ( this : unknown , ...args : unknown [ ] ) {
109+ const fn = ( parent as Record < string , CallableFunction > ) [ path ] ;
110+ return fn ?. call ( parent , ...args ) ;
111+ } ;
112+ cache . set ( path , wrapper ) ;
113+ }
114+ return wrapper ;
115+ } ;
130116
131117APPEND_OPCODES . add ( VM_CURRY_OP , ( vm , { op1 : type , op2 : _isStrict } ) => {
132118 let stack = vm . stack ;
@@ -275,7 +261,7 @@ APPEND_OPCODES.add(VM_GET_PROPERTY_OP, (vm, { op1: _key }) => {
275261APPEND_OPCODES . add ( VM_GET_PROPERTY_BOUND_OP , ( vm , { op1 : _key } ) => {
276262 let key = vm . constants . getValue < string > ( _key ) ;
277263 let expr = check ( vm . stack . pop ( ) , CheckReference ) ;
278- vm . stack . push ( boundChildRefFor ( expr , key ) ) ;
264+ vm . stack . push ( childRefFor ( expr , key , bindTransform ) ) ;
279265} ) ;
280266
281267APPEND_OPCODES . add ( VM_GET_BLOCK_OP , ( vm , { op1 : _block } ) => {
0 commit comments