diff --git a/compiler/forget/src/HIR/ObjectShape.ts b/compiler/forget/src/HIR/ObjectShape.ts index f7bf3e3b29..43459357f0 100644 --- a/compiler/forget/src/HIR/ObjectShape.ts +++ b/compiler/forget/src/HIR/ObjectShape.ts @@ -159,7 +159,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [ positionalParams: [], restParam: Effect.Capture, returnType: PRIMITIVE_TYPE, - calleeEffect: Effect.Mutate, + calleeEffect: Effect.Store, }), ], // TODO: rest of Array properties diff --git a/compiler/forget/src/Inference/InferAliasForStores.ts b/compiler/forget/src/Inference/InferAliasForStores.ts index db8725dca1..11000e2a16 100644 --- a/compiler/forget/src/Inference/InferAliasForStores.ts +++ b/compiler/forget/src/Inference/InferAliasForStores.ts @@ -25,7 +25,15 @@ export function inferAliasForStores( for (const [_, block] of func.body.blocks) { for (const instr of block.instructions) { const { value, lvalue } = instr; - if (lvalue.effect !== Effect.Store) { + const isStore = + lvalue.effect === Effect.Store || + // Some typed functions annotate callees or arguments + // as Effect.Store. + ![...eachInstructionValueOperand(value)].every( + (operand) => operand.effect !== Effect.Store + ); + + if (!isStore) { continue; } for (const operand of eachInstructionLValue(instr)) { diff --git a/compiler/forget/src/Inference/InferReferenceEffects.ts b/compiler/forget/src/Inference/InferReferenceEffects.ts index 9704e67e9e..cce90565d9 100644 --- a/compiler/forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/forget/src/Inference/InferReferenceEffects.ts @@ -323,6 +323,20 @@ class InferenceState { break; } case Effect.Store: { + if ( + valueKind !== ValueKind.Mutable && + valueKind !== ValueKind.Context && + shouldError + ) { + CompilerError.invalidInput( + `InferReferenceEffects: inferred mutation of known immutable value`, + place.loc, + `Found mutation of ${printIdentifier(place.identifier)}${printType( + place.identifier.type + )} (${valueKind})` + ); + } + // TODO(gsn): This should be bailout once we add bailout infra. // // invariant( diff --git a/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.expect.md new file mode 100644 index 0000000000..c405f36edb --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.expect.md @@ -0,0 +1,51 @@ + +## Input + +```javascript +function Component() { + // a's mutable range should be the same as x's mutable range, + // since a is captured into x (which gets mutated later) + let a = someObj(); + + let x = []; + x.push(a); + + mutate(x); + return [x, a]; +} + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component() { + const $ = useMemoCache(3); + let x; + let a; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + a = someObj(); + + x = []; + x.push(a); + + mutate(x); + $[0] = x; + $[1] = a; + } else { + x = $[0]; + a = $[1]; + } + let t0; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x, a]; + $[2] = t0; + } else { + t0 = $[2]; + } + return t0; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.js b/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.js new file mode 100644 index 0000000000..13470813eb --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.js @@ -0,0 +1,11 @@ +function Component() { + // a's mutable range should be the same as x's mutable range, + // since a is captured into x (which gets mutated later) + let a = someObj(); + + let x = []; + x.push(a); + + mutate(x); + return [x, a]; +} diff --git a/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver.expect.md new file mode 100644 index 0000000000..96bc86b771 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver.expect.md @@ -0,0 +1,51 @@ + +## Input + +```javascript +function Component() { + // a's mutable range should be limited + // the following line + let a = someObj(); + + let x = []; + x.push(a); + + return [x, a]; +} + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component() { + const $ = useMemoCache(3); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = someObj(); + $[0] = t0; + } else { + t0 = $[0]; + } + const a = t0; + let x; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + x = []; + x.push(a); + $[1] = x; + } else { + x = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = [x, a]; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver.js b/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver.js new file mode 100644 index 0000000000..9b2dbbdd1b --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver.js @@ -0,0 +1,10 @@ +function Component() { + // a's mutable range should be limited + // the following line + let a = someObj(); + + let x = []; + x.push(a); + + return [x, a]; +}