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,
+};