diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index 89538ca2a3..59a3988932 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -538,6 +538,13 @@ export type MethodCall = { loc: SourceLocation; }; +export type CallExpression = { + kind: "CallExpression"; + callee: Place; + args: Array; + loc: SourceLocation; +}; + /** * The value of a given instruction. Note that values are not recursive: complex * values such as objects or arrays are always defined by instructions to define @@ -605,12 +612,7 @@ export type InstructionValue = args: Array; loc: SourceLocation; } - | { - kind: "CallExpression"; - callee: Place; - args: Array; - loc: SourceLocation; - } + | CallExpression | MethodCall | { kind: "UnaryExpression"; diff --git a/compiler/forget/src/Inference/InferReferenceEffects.ts b/compiler/forget/src/Inference/InferReferenceEffects.ts index 0a9fbf7ae2..9ed2eca8c2 100644 --- a/compiler/forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/forget/src/Inference/InferReferenceEffects.ts @@ -11,6 +11,7 @@ import { Environment } from "../HIR"; import { BasicBlock, BlockId, + CallExpression, Effect, HIRFunction, IdentifierId, @@ -707,8 +708,6 @@ function inferBlock( continue; } case "CallExpression": { - valueKind = ValueKind.Mutable; - effectKind = Effect.Mutate; const hook = instrValue.callee.identifier.type.kind === "Hook" ? instrValue.callee.identifier.type.definition @@ -716,8 +715,42 @@ function inferBlock( if (hook !== null) { effectKind = hook.effectKind; valueKind = hook.valueKind; + break; } - break; + + const signature = getFunctionCallSignature( + env, + instrValue.callee.identifier.type + ); + + const effects = + signature !== null + ? getMethodCallEffects(instrValue, signature) + : null; + for (let i = 0; i < instrValue.args.length; i++) { + const arg = instrValue.args[i]; + const place = arg.kind === "Identifier" ? arg : arg.place; + if (effects !== null) { + // If effects are inferred for an argument, we should fail invalid + // mutating effects + state.referenceAndCheckError(place, effects[i]); + } else { + state.reference(place, Effect.Mutate); + } + } + if (signature !== null) { + state.referenceAndCheckError( + instrValue.callee, + signature.calleeEffect + ); + } else { + state.reference(instrValue.callee, Effect.Mutate); + } + + state.initialize(instrValue, ValueKind.Mutable); + state.define(instr.lvalue, instrValue); + instr.lvalue.effect = Effect.Mutate; + continue; } case "MethodCall": { invariant( @@ -1003,7 +1036,7 @@ function getFunctionCallSignature( * @returns Inferred effects of function arguments, or null if inference fails. */ function getMethodCallEffects( - fn: MethodCall, + fn: MethodCall | CallExpression, sig: FunctionSignature ): Array | null { const results = []; diff --git a/compiler/forget/src/__tests__/fixtures/compiler/globals-Boolean.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/globals-Boolean.expect.md index 642b273630..84b9eb6b94 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/globals-Boolean.expect.md +++ b/compiler/forget/src/__tests__/fixtures/compiler/globals-Boolean.expect.md @@ -17,25 +17,29 @@ import { unstable_useMemoCache as useMemoCache } from "react"; function Component(props) { const $ = useMemoCache(3); let t0; - let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x = {}; - t0 = Boolean(x); + t0 = {}; $[0] = t0; - $[1] = x; } else { t0 = $[0]; - x = $[1]; } - const y = t0; + const x = t0; let t1; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t1 = [x, y]; - $[2] = t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = Boolean(x); + $[1] = t1; } else { - t1 = $[2]; + t1 = $[1]; } - return t1; + const y = t1; + let t2; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t2 = [x, y]; + $[2] = t2; + } else { + t2 = $[2]; + } + return t2; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/compiler/globals-Number.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/globals-Number.expect.md index a8ce51aae2..7a29e07de6 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/globals-Number.expect.md +++ b/compiler/forget/src/__tests__/fixtures/compiler/globals-Number.expect.md @@ -17,25 +17,29 @@ import { unstable_useMemoCache as useMemoCache } from "react"; function Component(props) { const $ = useMemoCache(3); let t0; - let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x = {}; - t0 = Number(x); + t0 = {}; $[0] = t0; - $[1] = x; } else { t0 = $[0]; - x = $[1]; } - const y = t0; + const x = t0; let t1; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t1 = [x, y]; - $[2] = t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = Number(x); + $[1] = t1; } else { - t1 = $[2]; + t1 = $[1]; } - return t1; + const y = t1; + let t2; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t2 = [x, y]; + $[2] = t2; + } else { + t2 = $[2]; + } + return t2; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/compiler/globals-String.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/globals-String.expect.md index 8955b4b326..1efd363393 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/globals-String.expect.md +++ b/compiler/forget/src/__tests__/fixtures/compiler/globals-String.expect.md @@ -17,25 +17,29 @@ import { unstable_useMemoCache as useMemoCache } from "react"; function Component(props) { const $ = useMemoCache(3); let t0; - let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x = {}; - t0 = String(x); + t0 = {}; $[0] = t0; - $[1] = x; } else { t0 = $[0]; - x = $[1]; } - const y = t0; + const x = t0; let t1; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t1 = [x, y]; - $[2] = t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = String(x); + $[1] = t1; } else { - t1 = $[2]; + t1 = $[1]; } - return t1; + const y = t1; + let t2; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t2 = [x, y]; + $[2] = t2; + } else { + t2 = $[2]; + } + return t2; } ```