From fbfe37ee4010d36dad795d9144bb88fbeb8e1419 Mon Sep 17 00:00:00 2001
From: mofeiZ <34200447+mofeiZ@users.noreply.github.com>
Date: Thu, 10 Oct 2024 12:23:44 -0400
Subject: [PATCH] [compiler] Test fixture: non-reactive phi creates 'dangling
ref' scope (#31103)
---
.../bug-invalid-phi-as-dependency.expect.md | 91 +++++++++++++++++++
.../bug-invalid-phi-as-dependency.tsx | 30 ++++++
.../packages/snap/src/SproutTodoFilter.ts | 1 +
3 files changed, 122 insertions(+)
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md
new file mode 100644
index 0000000000000..32e2c9fd6483e
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md
@@ -0,0 +1,91 @@
+
+## Input
+
+```javascript
+import {CONST_TRUE, Stringify, mutate, useIdentity} from 'shared-runtime';
+
+/**
+ * Fixture showing an edge case for ReactiveScope variable propagation.
+ *
+ * Found differences in evaluator results
+ * Non-forget (expected):
+ *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * {"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * Forget:
+ * {"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * [[ (exception in render) Error: invariant broken ]]
+ *
+ */
+function Component() {
+ const obj = CONST_TRUE ? {inner: {value: 'hello'}} : null;
+ const boxedInner = [obj?.inner];
+ useIdentity(null);
+ mutate(obj);
+ if (boxedInner[0] !== obj?.inner) {
+ throw new Error('invariant broken');
+ }
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{arg: 0}],
+ sequentialRenders: [{arg: 0}, {arg: 1}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { CONST_TRUE, Stringify, mutate, useIdentity } from "shared-runtime";
+
+/**
+ * Fixture showing an edge case for ReactiveScope variable propagation.
+ *
+ * Found differences in evaluator results
+ * Non-forget (expected):
+ * {"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * {"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * Forget:
+ * {"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * [[ (exception in render) Error: invariant broken ]]
+ *
+ */
+function Component() {
+ const $ = _c(4);
+ const obj = CONST_TRUE ? { inner: { value: "hello" } } : null;
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = [obj?.inner];
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ const boxedInner = t0;
+ useIdentity(null);
+ mutate(obj);
+ if (boxedInner[0] !== obj?.inner) {
+ throw new Error("invariant broken");
+ }
+ let t1;
+ if ($[1] !== obj || $[2] !== boxedInner) {
+ t1 = ;
+ $[1] = obj;
+ $[2] = boxedInner;
+ $[3] = t1;
+ } else {
+ t1 = $[3];
+ }
+ return t1;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ arg: 0 }],
+ sequentialRenders: [{ arg: 0 }, { arg: 1 }],
+};
+
+```
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx
new file mode 100644
index 0000000000000..a1a78bfa7e6b1
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx
@@ -0,0 +1,30 @@
+import {CONST_TRUE, Stringify, mutate, useIdentity} from 'shared-runtime';
+
+/**
+ * Fixture showing an edge case for ReactiveScope variable propagation.
+ *
+ * Found differences in evaluator results
+ * Non-forget (expected):
+ * {"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * {"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * Forget:
+ * {"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * [[ (exception in render) Error: invariant broken ]]
+ *
+ */
+function Component() {
+ const obj = CONST_TRUE ? {inner: {value: 'hello'}} : null;
+ const boxedInner = [obj?.inner];
+ useIdentity(null);
+ mutate(obj);
+ if (boxedInner[0] !== obj?.inner) {
+ throw new Error('invariant broken');
+ }
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{arg: 0}],
+ sequentialRenders: [{arg: 0}, {arg: 1}],
+};
diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts
index dc90c9e29e2e7..351f242e40820 100644
--- a/compiler/packages/snap/src/SproutTodoFilter.ts
+++ b/compiler/packages/snap/src/SproutTodoFilter.ts
@@ -481,6 +481,7 @@ const skipFilter = new Set([
'bug-invalid-hoisting-functionexpr',
'bug-try-catch-maybe-null-dependency',
'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted',
+ 'bug-invalid-phi-as-dependency',
'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond',
'original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block',
'original-reactive-scopes-fork/bug-hoisted-declaration-with-scope',