@@ -45,6 +45,7 @@ import {
4545 printPlace ,
4646 printSourceLocation ,
4747} from '../HIR/PrintHIR' ;
48+ import { FunctionSignature } from '../HIR/ObjectShape' ;
4849
4950export function inferMutationAliasingEffects (
5051 fn : HIRFunction ,
@@ -233,6 +234,18 @@ function applySignature(
233234 instruction : Instruction ,
234235 effectInstructionValueCache : Map < AliasingEffect , InstructionValue > ,
235236) : Array < AliasingEffect > | null {
237+ /*
238+ * TODO: this should look for FunctionExpression instructions, and check for any
239+ * obviously invalid effects. for example, a known mutation of props is always
240+ * invalid even if the function might not be called
241+ */
242+
243+ /*
244+ * Track which values we've already aliased once, so that we can switch to
245+ * appendAlias() for subsequent aliases into the same value
246+ */
247+ const aliased = new Set < IdentifierId > ( ) ;
248+
236249 const effects : Array < AliasingEffect > = [ ] ;
237250 for ( const effect of signature . effects ) {
238251 switch ( effect . kind ) {
@@ -407,7 +420,12 @@ function applySignature(
407420 break ;
408421 }
409422 default : {
410- state . alias ( effect . into , effect . from ) ;
423+ if ( aliased . has ( effect . into . identifier . id ) ) {
424+ state . appendAlias ( effect . into , effect . from ) ;
425+ } else {
426+ aliased . add ( effect . into . identifier . id ) ;
427+ state . alias ( effect . into , effect . from ) ;
428+ }
411429 effects . push ( effect ) ;
412430 break ;
413431 }
@@ -564,6 +582,21 @@ class InferenceState {
564582 this . #variables. set ( place . identifier . id , new Set ( values ) ) ;
565583 }
566584
585+ appendAlias ( place : Place , value : Place ) : void {
586+ const values = this . #variables. get ( value . identifier . id ) ;
587+ CompilerError . invariant ( values != null , {
588+ reason : `[InferMutationAliasingEffects] Expected value for identifier to be initialized` ,
589+ description : `${ printIdentifier ( value . identifier ) } ` ,
590+ loc : value . loc ,
591+ suggestions : null ,
592+ } ) ;
593+ const prevValues = this . values ( place ) ;
594+ this . #variables. set (
595+ place . identifier . id ,
596+ new Set ( [ ...prevValues , ...values ] ) ,
597+ ) ;
598+ }
599+
567600 // Defines (initializing or updating) a variable with a specific kind of value.
568601 define ( place : Place , value : InstructionValue ) : void {
569602 CompilerError . invariant ( this . #values. has ( value ) , {
@@ -650,10 +683,13 @@ class InferenceState {
650683 case 'MutateTransitive' : {
651684 switch ( kind ) {
652685 case ValueKind . Mutable :
653- case ValueKind . Primitive :
654686 case ValueKind . Context : {
655687 return true ;
656688 }
689+ case ValueKind . Primitive : {
690+ // technically an error, but it's not React specific
691+ return false ;
692+ }
657693 default : {
658694 // TODO this is an error!
659695 return false ;
@@ -952,6 +988,15 @@ function computeSignatureForInstruction(
952988 : null ;
953989 if ( signatureEffects != null && signature ?. aliasing != null ) {
954990 effects . push ( ...signatureEffects ) ;
991+ } else if ( signature != null ) {
992+ effects . push (
993+ ...computeEffectsForLegacySignature (
994+ signature ,
995+ lvalue ,
996+ receiver ,
997+ value . args ,
998+ ) ,
999+ ) ;
9551000 } else {
9561001 effects . push ( { kind : 'Create' , into : lvalue , value : ValueKind . Mutable } ) ;
9571002 /**
@@ -1054,10 +1099,13 @@ function computeSignatureForInstruction(
10541099 *
10551100 * ```
10561101 * const a = {};
1057- * // We don't want to consider a as mutating here either, this just declares the function
1102+ *
1103+ * // We don't want to consider a as mutating here, this just declares the function
10581104 * const f = () => { maybeMutate(a) };
1105+ *
10591106 * // We don't want to consider a as mutating here either, it can't possibly call f yet
10601107 * const x = [f];
1108+ *
10611109 * // Here we have to assume that f can be called (transitively), and have to consider a
10621110 * // as mutating
10631111 * callAllFunctionInArray(x);
@@ -1068,6 +1116,12 @@ function computeSignatureForInstruction(
10681116 * that those operands are also considered mutated. If the function is never called,
10691117 * they won't be!
10701118 *
1119+ * This relies on the rule that:
1120+ * Capture a -> b and MutateTransitive(b) => Mutate(a)
1121+ *
1122+ * Substituting:
1123+ * Capture contextvar -> function and MutateTransitive(function) => Mutate(contextvar)
1124+ *
10711125 * Note that if the type of the context variables are frozen, global, or primitive, the
10721126 * Capture will either get pruned or downgraded to an ImmutableCapture.
10731127 */
@@ -1259,6 +1313,122 @@ function computeSignatureForInstruction(
12591313 } ;
12601314}
12611315
1316+ /**
1317+ * Creates a set of aliasing effects given a legacy FunctionSignature. This makes all of the
1318+ * old implicit behaviors from the signatures and InferReferenceEffects explicit, see comments
1319+ * in the body for details.
1320+ *
1321+ * The goal of this method is to make it easier to migrate incrementally to the new system,
1322+ * so we don't have to immediately write new signatures for all the methods to get expected
1323+ * compilation output.
1324+ */
1325+ function computeEffectsForLegacySignature (
1326+ signature : FunctionSignature ,
1327+ lvalue : Place ,
1328+ receiver : Place ,
1329+ args : Array < Place | SpreadPattern > ,
1330+ ) : Array < AliasingEffect > {
1331+ const effects : Array < AliasingEffect > = [ ] ;
1332+ effects . push ( {
1333+ kind : 'Create' ,
1334+ into : lvalue ,
1335+ value : signature . returnValueKind ,
1336+ } ) ;
1337+ /*
1338+ * InferReferenceEffects and FunctionSignature have an implicit assumption that the receiver
1339+ * is captured into the return value. Consider for example the signature for Array.proto.pop:
1340+ * the calleeEffect is Store, since it's a known mutation but non-transitive. But the return
1341+ * of the pop() captures from the receiver! This isn't specified explicitly. So we add this
1342+ * here, and rely on applySignature() to downgrade this to ImmutableCapture (or prune) if
1343+ * the type doesn't actually need to be captured based on the input and return type.
1344+ */
1345+ effects . push ( {
1346+ kind : 'Capture' ,
1347+ from : receiver ,
1348+ into : lvalue ,
1349+ } ) ;
1350+ const stores : Array < Place > = [ ] ;
1351+ const captures : Array < Place > = [ ] ;
1352+ const returnValueReason = signature . returnValueReason ?? ValueReason . Other ;
1353+ function visit ( place : Place , effect : Effect ) : void {
1354+ switch ( effect ) {
1355+ case Effect . Store : {
1356+ effects . push ( {
1357+ kind : 'Mutate' ,
1358+ value : place ,
1359+ } ) ;
1360+ stores . push ( place ) ;
1361+ break ;
1362+ }
1363+ case Effect . Capture : {
1364+ captures . push ( place ) ;
1365+ break ;
1366+ }
1367+ case Effect . ConditionallyMutate : {
1368+ effects . push ( {
1369+ kind : 'MutateTransitiveConditionally' ,
1370+ value : place ,
1371+ } ) ;
1372+ break ;
1373+ }
1374+ case Effect . ConditionallyMutateIterator : {
1375+ // We don't currently use this effect in any signatures
1376+ CompilerError . throwTodo ( {
1377+ reason : `Unexpected ${ effect } effect in a legacy function signature` ,
1378+ loc : place . loc ,
1379+ } ) ;
1380+ }
1381+ case Effect . Freeze : {
1382+ effects . push ( {
1383+ kind : 'Freeze' ,
1384+ value : place ,
1385+ reason : returnValueReason ,
1386+ } ) ;
1387+ break ;
1388+ }
1389+ case Effect . Mutate : {
1390+ effects . push ( { kind : 'MutateTransitive' , value : place } ) ;
1391+ break ;
1392+ }
1393+ case Effect . Read : {
1394+ effects . push ( {
1395+ kind : 'ImmutableCapture' ,
1396+ from : place ,
1397+ into : lvalue ,
1398+ } ) ;
1399+ break ;
1400+ }
1401+ }
1402+ }
1403+
1404+ visit ( receiver , signature . calleeEffect ) ;
1405+ for ( let i = 0 ; i < args . length ; i ++ ) {
1406+ const arg = args [ i ] ;
1407+ const place = arg . kind === 'Identifier' ? arg : arg . place ;
1408+ const effect =
1409+ arg . kind === 'Identifier' && i < signature . positionalParams . length
1410+ ? signature . positionalParams [ i ] !
1411+ : ( signature . restParam ?? Effect . ConditionallyMutate ) ;
1412+ visit ( place , effect ) ;
1413+ }
1414+ if ( captures . length !== 0 ) {
1415+ if ( stores . length === 0 ) {
1416+ // If no stores, then capture into the return value
1417+ for ( const capture of captures ) {
1418+ effects . push ( { kind : 'Capture' , from : capture , into : lvalue } ) ;
1419+ }
1420+ } else {
1421+ // Else capture into the stores
1422+ for ( const capture of captures ) {
1423+ for ( const store of stores ) {
1424+ effects . push ( { kind : 'Capture' , from : capture , into : store } ) ;
1425+ }
1426+ }
1427+ }
1428+ }
1429+ return effects ;
1430+ }
1431+
12621432function computeEffectsForSignature (
12631433 signature : AliasingSignature ,
12641434 lvalue : Place ,
0 commit comments