diff --git a/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts index 79ebcb449c..daeaed5969 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts @@ -21,6 +21,7 @@ import { MethodCall, Phi, Place, + SpreadPattern, Type, ValueKind, ValueReason, @@ -133,26 +134,48 @@ export default function inferReferenceEffects( kind: ValueKind.Frozen, reason: new Set([ValueReason.ReactiveFunctionArgument]), }; - for (const param of fn.params) { + + const isComponent = isComponentName(fn.id); + if (isComponent) { + CompilerError.invariant(fn.params.length <= 2, { + reason: + "Expected React component to have not more than two parameters: one for props and for ref", + description: null, + loc: fn.loc, + suggestions: null, + }); + const [props, ref] = fn.params; let value: InstructionValue; let place: Place; - if (param.kind === "Identifier") { - place = param; - value = { - kind: "Primitive", - loc: param.loc, - value: undefined, - }; - } else { - place = param.place; - value = { - kind: "Primitive", - loc: param.place.loc, - value: undefined, - }; + if (props) { + inferParam(props, initialState, paramKind); + } + if (ref) { + if (ref.kind === "Identifier") { + place = ref; + value = { + kind: "ObjectExpression", + properties: [], + loc: ref.loc, + }; + } else { + place = ref.place; + value = { + kind: "ObjectExpression", + properties: [], + loc: ref.place.loc, + }; + } + initialState.initialize(value, { + kind: ValueKind.Mutable, + reason: new Set([ValueReason.Other]), + }); + initialState.define(place, value); + } + } else { + for (const param of fn.params) { + inferParam(param, initialState, paramKind); } - initialState.initialize(value, paramKind); - initialState.define(place, value); } // Map of blocks to the last (merged) incoming state that was processed @@ -596,6 +619,36 @@ class InferenceState { } } +function isComponentName(name: string | null): boolean { + return name !== null && /^[A-Z]/.test(name); +} + +function inferParam( + param: Place | SpreadPattern, + initialState: InferenceState, + paramKind: AbstractValue +): void { + let value: InstructionValue; + let place: Place; + if (param.kind === "Identifier") { + place = param; + value = { + kind: "Primitive", + loc: param.loc, + value: undefined, + }; + } else { + place = param.place; + value = { + kind: "Primitive", + loc: param.place.loc, + value: undefined, + }; + } + initialState.initialize(value, paramKind); + initialState.define(place, value); +} + /* * Joins two values using the following rules: * == Effect Transitions == diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md new file mode 100644 index 0000000000..a3822fea87 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -0,0 +1,67 @@ + +## Input + +```javascript +import { useEffect } from "react"; + +function Foo(props, ref) { + useEffect(() => { + ref.current = 2; + }, []); + return
{props.bar}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { useEffect, unstable_useMemoCache as useMemoCache } from "react"; + +function Foo(props, ref) { + const $ = useMemoCache(5); + let t0; + if ($[0] !== ref.current) { + t0 = () => { + ref.current = 2; + }; + $[0] = ref.current; + $[1] = t0; + } else { + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; + } + useEffect(t0, t1); + let t2; + if ($[3] !== props.bar) { + t2 =
{props.bar}
; + $[3] = props.bar; + $[4] = t2; + } else { + t2 = $[4]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }], + isComponent: true, +}; + +``` + +### Eval output +(kind: ok)
foo
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js new file mode 100644 index 0000000000..adc1c9ce89 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js @@ -0,0 +1,14 @@ +import { useEffect } from "react"; + +function Foo(props, ref) { + useEffect(() => { + ref.current = 2; + }, []); + return
{props.bar}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }], + isComponent: true, +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo.validate-mutate-ref-arg-in-render.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo.validate-mutate-ref-arg-in-render.expect.md new file mode 100644 index 0000000000..8416b01394 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo.validate-mutate-ref-arg-in-render.expect.md @@ -0,0 +1,46 @@ + +## Input + +```javascript +// @validateRefAccessDuringRender: true +function Foo(props, ref) { + ref.current = 2; + return
{props.bar}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; // @validateRefAccessDuringRender: true +function Foo(props, ref) { + const $ = useMemoCache(2); + ref.current = 2; + let t0; + if ($[0] !== props.bar) { + t0 =
{props.bar}
; + $[0] = props.bar; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }], + isComponent: true, +}; + +``` + +### Eval output +(kind: ok)
foo
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo.validate-mutate-ref-arg-in-render.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo.validate-mutate-ref-arg-in-render.js new file mode 100644 index 0000000000..6163a7b007 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo.validate-mutate-ref-arg-in-render.js @@ -0,0 +1,11 @@ +// @validateRefAccessDuringRender: true +function Foo(props, ref) { + ref.current = 2; + return
{props.bar}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }], + isComponent: true, +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-1.expect.md similarity index 76% rename from compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter.expect.md rename to compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-1.expect.md index f37e101de6..793cdea9b4 100644 --- a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter.expect.md +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-1.expect.md @@ -2,8 +2,7 @@ ## Input ```javascript -// @debug -function Component(a, [b], { c }) { +function Component({ a: a, b: [b], c: { c } }) { let d = a++; let e = ++a; let f = b--; @@ -15,7 +14,7 @@ function Component(a, [b], { c }) { export const FIXTURE_ENTRYPOINT = { fn: Component, - params: [2, [3], { c: 4 }], + params: [{ a: 2, b: [3], c: { c: 4 } }], isComponent: false, }; @@ -24,11 +23,12 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { unstable_useMemoCache as useMemoCache } from "react"; // @debug -function Component(a, t37, t38) { +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(t38) { const $ = useMemoCache(10); - let [b] = t37; - let { c } = t38; + let { a, b: t40, c: t41 } = t38; + let [b] = t40; + let { c } = t41; const d = a++; const e = ++a; const f = b--; @@ -66,7 +66,7 @@ function Component(a, t37, t38) { export const FIXTURE_ENTRYPOINT = { fn: Component, - params: [2, [3], { c: 4 }], + params: [{ a: 2, b: [3], c: { c: 4 } }], isComponent: false, }; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-1.js similarity index 69% rename from compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter.js rename to compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-1.js index ec5c6d56f0..bbed4b7f86 100644 --- a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter.js +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-1.js @@ -1,5 +1,4 @@ -// @debug -function Component(a, [b], { c }) { +function Component({ a: a, b: [b], c: { c } }) { let d = a++; let e = ++a; let f = b--; @@ -11,6 +10,6 @@ function Component(a, [b], { c }) { export const FIXTURE_ENTRYPOINT = { fn: Component, - params: [2, [3], { c: 4 }], + params: [{ a: 2, b: [3], c: { c: 4 } }], isComponent: false, }; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-2.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-2.expect.md new file mode 100644 index 0000000000..e07c7bf161 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-2.expect.md @@ -0,0 +1,49 @@ + +## Input + +```javascript +function Component(a) { + let d = a++; + let e = ++a; + return [a, d, e]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [2], + isComponent: false, +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(a) { + const $ = useMemoCache(4); + const d = a++; + const e = ++a; + let t0; + if ($[0] !== a || $[1] !== d || $[2] !== e) { + t0 = [a, d, e]; + $[0] = a; + $[1] = d; + $[2] = e; + $[3] = t0; + } else { + t0 = $[3]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [2], + isComponent: false, +}; + +``` + +### Eval output +(kind: ok) [4,2,4] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-2.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-2.js new file mode 100644 index 0000000000..b86519477c --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-2.js @@ -0,0 +1,11 @@ +function Component(a) { + let d = a++; + let e = ++a; + return [a, d, e]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [2], + isComponent: false, +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-3.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-3.expect.md new file mode 100644 index 0000000000..04905404f8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-3.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +function Component({ c }) { + let h = c++; + let i = --c; + return [c, h, i]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ c: 4 }], + isComponent: false, +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(t14) { + const $ = useMemoCache(4); + let { c } = t14; + const h = c++; + const i = --c; + let t0; + if ($[0] !== c || $[1] !== h || $[2] !== i) { + t0 = [c, h, i]; + $[0] = c; + $[1] = h; + $[2] = i; + $[3] = t0; + } else { + t0 = $[3]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ c: 4 }], + isComponent: false, +}; + +``` + +### Eval output +(kind: ok) [4,4,4] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-3.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-3.js new file mode 100644 index 0000000000..488cc93d49 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-3.js @@ -0,0 +1,11 @@ +function Component({ c }) { + let h = c++; + let i = --c; + return [c, h, i]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ c: 4 }], + isComponent: false, +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-4.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-4.expect.md new file mode 100644 index 0000000000..4ec48b36ab --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-4.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +function Component([b]) { + let f = b--; + let g = --b; + return [b, f, g]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [[3]], + isComponent: false, +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(t14) { + const $ = useMemoCache(4); + let [b] = t14; + const f = b--; + const g = --b; + let t0; + if ($[0] !== b || $[1] !== f || $[2] !== g) { + t0 = [b, f, g]; + $[0] = b; + $[1] = f; + $[2] = g; + $[3] = t0; + } else { + t0 = $[3]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [[3]], + isComponent: false, +}; + +``` + +### Eval output +(kind: ok) [1,3,1] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-4.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-4.js new file mode 100644 index 0000000000..c4c75e460d --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter-4.js @@ -0,0 +1,11 @@ +function Component([b]) { + let f = b--; + let g = --b; + return [b, f, g]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [[3]], + isComponent: false, +};