@@ -276,6 +276,8 @@ class InferenceState {
276276 */
277277 #variables: Map < IdentifierId , Set < InstructionValue > > ;
278278
279+ #functionEffects: Map < InstructionValue , Array < FunctionEffect > > = new Map ( ) ;
280+
279281 constructor (
280282 env : Environment ,
281283 values : Map < InstructionValue , AbstractValue > ,
@@ -302,6 +304,19 @@ class InferenceState {
302304 this . #values. set ( value , kind ) ;
303305 }
304306
307+ setFunctionEffects ( value : InstructionValue , effect : Array < FunctionEffect > ) : void {
308+ CompilerError . invariant ( value . kind !== 'LoadLocal' , {
309+ reason :
310+ 'Expected all top-level identifiers to be defined as variables, not values' ,
311+ description : null ,
312+ loc : value . loc ,
313+ suggestions : null ,
314+ } ) ;
315+ const curEffects = this . #functionEffects. get ( value ) ?? [ ] ;
316+ curEffects . push ( ...effect ) ;
317+ this . #functionEffects. set ( value , curEffects ) ;
318+ }
319+
305320 values ( place : Place ) : Array < InstructionValue > {
306321 const values = this . #variables. get ( place . identifier . id ) ;
307322 CompilerError . invariant ( values != null , {
@@ -400,50 +415,57 @@ class InferenceState {
400415 }
401416
402417 // Propagate effects of function expressions to the outer (ie current) effect context
418+ const dependentEffects = [ ] ;
403419 for ( const value of values ) {
404420 if (
405421 ( value . kind === 'FunctionExpression' ||
406- value . kind === 'ObjectMethod' ) &&
407- value . loweredFunc . func . effects != null
422+ value . kind === 'ObjectMethod' )
408423 ) {
409- for ( const effect of value . loweredFunc . func . effects ) {
410- if (
411- effect . kind === 'GlobalMutation' ||
412- effect . kind === 'ReactMutation'
413- ) {
414- // Known effects are always propagated upwards
415- functionEffects . push ( effect ) ;
416- } else {
417- /**
418- * Contextual effects need to be replayed against the current inference
419- * state, which may know more about the value to which the effect applied.
420- * The main cases are:
421- * 1. The mutated context value is _still_ a context value in the current scope,
422- * so we have to continue propagating the original context mutation.
423- * 2. The mutated context value is a mutable value in the current scope,
424- * so the context mutation was fine and we can skip propagating the effect.
425- * 3. The mutated context value is an immutable value in the current scope,
426- * resulting in a non-ContextMutation FunctionEffect. We propagate that new,
427- * more detailed effect to the current function context.
428- */
429- for ( const place of effect . places ) {
430- if ( this . isDefined ( place ) ) {
431- const replayedEffect = this . reference (
432- { ...place , loc : effect . loc } ,
433- effect . effect ,
434- reason ,
435- ) ;
436- if ( replayedEffect != null ) {
437- if ( replayedEffect . kind === 'ContextMutation' ) {
438- // Case 1, still a context value so propagate the original effect
439- functionEffects . push ( effect ) ;
440- } else {
441- // Case 3, immutable value so propagate the more precise effect
442- functionEffects . push ( replayedEffect ) ;
443- }
444- } // else case 2, local mutable value so this effect was fine
424+ dependentEffects . push ( ...value . loweredFunc . func . effects ?? [ ] ) ;
425+ }
426+ const knownEffects = this . #functionEffects. get ( value ) ;
427+ if ( knownEffects != null ) {
428+ dependentEffects . push ( ...knownEffects )
429+ }
430+ }
431+
432+
433+ for ( const effect of dependentEffects ) {
434+ if (
435+ effect . kind === 'GlobalMutation' ||
436+ effect . kind === 'ReactMutation'
437+ ) {
438+ // Known effects are always propagated upwards
439+ functionEffects . push ( effect ) ;
440+ } else {
441+ /**
442+ * Contextual effects need to be replayed against the current inference
443+ * state, which may know more about the value to which the effect applied.
444+ * The main cases are:
445+ * 1. The mutated context value is _still_ a context value in the current scope,
446+ * so we have to continue propagating the original context mutation.
447+ * 2. The mutated context value is a mutable value in the current scope,
448+ * so the context mutation was fine and we can skip propagating the effect.
449+ * 3. The mutated context value is an immutable value in the current scope,
450+ * resulting in a non-ContextMutation FunctionEffect. We propagate that new,
451+ * more detailed effect to the current function context.
452+ */
453+ for ( const place of effect . places ) {
454+ if ( this . isDefined ( place ) ) {
455+ const replayedEffect = this . reference (
456+ { ...place , loc : effect . loc } ,
457+ effect . effect ,
458+ reason ,
459+ ) ;
460+ if ( replayedEffect != null ) {
461+ if ( replayedEffect . kind === 'ContextMutation' ) {
462+ // Case 1, still a context value so propagate the original effect
463+ functionEffects . push ( effect ) ;
464+ } else {
465+ // Case 3, immutable value so propagate the more precise effect
466+ functionEffects . push ( replayedEffect ) ;
445467 }
446- }
468+ } // else case 2, local mutable value so this effect was fine
447469 }
448470 }
449471 }
@@ -995,8 +1017,22 @@ function inferBlock(
9951017 context : new Set ( ) ,
9961018 } ;
9971019 effect = { kind : Effect . Capture , reason : ValueReason . Other } ;
998- lvalueEffect = Effect . Store ;
999- break ;
1020+
1021+ const arrEffects : Array < FunctionEffect > = [ ] ;
1022+ for ( const operand of eachInstructionOperand ( instr ) ) {
1023+ state . referenceAndRecordEffects (
1024+ operand ,
1025+ effect . kind ,
1026+ effect . reason ,
1027+ arrEffects ,
1028+ ) ;
1029+ }
1030+
1031+ state . initialize ( instrValue , valueKind ) ;
1032+ state . define ( instr . lvalue , instrValue ) ;
1033+ instr . lvalue . effect = Effect . Store ;
1034+ state . setFunctionEffects ( instrValue , arrEffects ) ;
1035+ continue ;
10001036 }
10011037 case 'NewExpression' : {
10021038 /**
@@ -1039,6 +1075,7 @@ function inferBlock(
10391075 continue ;
10401076 }
10411077 case 'ObjectExpression' : {
1078+ const objEffects : Array < FunctionEffect > = [ ] ;
10421079 valueKind = hasContextRefOperand ( state , instrValue )
10431080 ? {
10441081 kind : ValueKind . Context ,
@@ -1060,15 +1097,15 @@ function inferBlock(
10601097 property . key . name ,
10611098 Effect . Freeze ,
10621099 ValueReason . Other ,
1063- functionEffects ,
1100+ objEffects ,
10641101 ) ;
10651102 }
10661103 // Object construction captures but does not modify the key/property values
10671104 state . referenceAndRecordEffects (
10681105 property . place ,
10691106 Effect . Capture ,
10701107 ValueReason . Other ,
1071- functionEffects ,
1108+ objEffects ,
10721109 ) ;
10731110 break ;
10741111 }
@@ -1078,7 +1115,7 @@ function inferBlock(
10781115 property . place ,
10791116 Effect . Capture ,
10801117 ValueReason . Other ,
1081- functionEffects ,
1118+ objEffects ,
10821119 ) ;
10831120 break ;
10841121 }
@@ -1092,6 +1129,7 @@ function inferBlock(
10921129 }
10931130
10941131 state . initialize ( instrValue , valueKind ) ;
1132+ state . setFunctionEffects ( instrValue , objEffects ) ;
10951133 state . define ( instr . lvalue , instrValue ) ;
10961134 instr . lvalue . effect = Effect . Store ;
10971135 continue ;
@@ -1547,14 +1585,16 @@ function inferBlock(
15471585 break ;
15481586 }
15491587 case 'PropertyLoad' : {
1588+ const loadEffects : Array < FunctionEffect > = [ ] ;
15501589 state . referenceAndRecordEffects (
15511590 instrValue . object ,
15521591 Effect . Read ,
15531592 ValueReason . Other ,
1554- functionEffects ,
1593+ loadEffects ,
15551594 ) ;
15561595 const lvalue = instr . lvalue ;
15571596 lvalue . effect = Effect . ConditionallyMutate ;
1597+ state . setFunctionEffects ( instrValue , loadEffects ) ;
15581598 state . initialize ( instrValue , state . kind ( instrValue . object ) ) ;
15591599 state . define ( lvalue , instrValue ) ;
15601600 continue ;
@@ -1611,22 +1651,24 @@ function inferBlock(
16111651 continue ;
16121652 }
16131653 case 'ComputedLoad' : {
1654+ const loadEffects : Array < FunctionEffect > = [ ] ;
16141655 state . referenceAndRecordEffects (
16151656 instrValue . object ,
16161657 Effect . Read ,
16171658 ValueReason . Other ,
1618- functionEffects ,
1659+ loadEffects ,
16191660 ) ;
16201661 state . referenceAndRecordEffects (
16211662 instrValue . property ,
16221663 Effect . Read ,
16231664 ValueReason . Other ,
1624- functionEffects ,
1665+ loadEffects ,
16251666 ) ;
16261667 const lvalue = instr . lvalue ;
16271668 lvalue . effect = Effect . ConditionallyMutate ;
16281669 state . initialize ( instrValue , state . kind ( instrValue . object ) ) ;
16291670 state . define ( lvalue , instrValue ) ;
1671+ state . setFunctionEffects ( instrValue , loadEffects ) ;
16301672 continue ;
16311673 }
16321674 case 'Await' : {
0 commit comments