diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-break-labeled.expect.md new file mode 100644 index 0000000000000..76648c251a101 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-break-labeled.expect.md @@ -0,0 +1,65 @@ + +## Input + +```javascript +/** + * props.b *does* influence `a` + */ +function Component(props) { + const a = []; + a.push(props.a); + label: { + if (props.b) { + break label; + } + a.push(props.c); + } + a.push(props.d); + return a; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: ['TodoAdd'], + isComponent: 'TodoAdd', +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; /** + * props.b *does* influence `a` + */ +function Component(props) { + const $ = _c(2); + let a; + if ($[0] !== props) { + a = []; + a.push(props.a); + bb0: { + if (props.b) { + break bb0; + } + + a.push(props.c); + } + + a.push(props.d); + $[0] = props; + $[1] = a; + } else { + a = $[1]; + } + return a; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: ["TodoAdd"], + isComponent: "TodoAdd", +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-break-labeled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-break-labeled.js new file mode 100644 index 0000000000000..ccf983189cf71 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-break-labeled.js @@ -0,0 +1,21 @@ +/** + * props.b *does* influence `a` + */ +function Component(props) { + const a = []; + a.push(props.a); + label: { + if (props.b) { + break label; + } + a.push(props.c); + } + a.push(props.d); + return a; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: ['TodoAdd'], + isComponent: 'TodoAdd', +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-early-return.expect.md new file mode 100644 index 0000000000000..82537902bfa19 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-early-return.expect.md @@ -0,0 +1,197 @@ + +## Input + +```javascript +/** + * props.b does *not* influence `a` + */ +function ComponentA(props) { + const a_DEBUG = []; + a_DEBUG.push(props.a); + if (props.b) { + return null; + } + a_DEBUG.push(props.d); + return a_DEBUG; +} + +/** + * props.b *does* influence `a` + */ +function ComponentB(props) { + const a = []; + a.push(props.a); + if (props.b) { + a.push(props.c); + } + a.push(props.d); + return a; +} + +/** + * props.b *does* influence `a`, but only in a way that is never observable + */ +function ComponentC(props) { + const a = []; + a.push(props.a); + if (props.b) { + a.push(props.c); + return null; + } + a.push(props.d); + return a; +} + +/** + * props.b *does* influence `a` + */ +function ComponentD(props) { + const a = []; + a.push(props.a); + if (props.b) { + a.push(props.c); + return a; + } + a.push(props.d); + return a; +} + +export const FIXTURE_ENTRYPOINT = { + fn: ComponentA, + params: [{a: 1, b: false, d: 3}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; /** + * props.b does *not* influence `a` + */ +function ComponentA(props) { + const $ = _c(3); + let a_DEBUG; + let t0; + if ($[0] !== props) { + t0 = Symbol.for("react.early_return_sentinel"); + bb0: { + a_DEBUG = []; + a_DEBUG.push(props.a); + if (props.b) { + t0 = null; + break bb0; + } + + a_DEBUG.push(props.d); + } + $[0] = props; + $[1] = a_DEBUG; + $[2] = t0; + } else { + a_DEBUG = $[1]; + t0 = $[2]; + } + if (t0 !== Symbol.for("react.early_return_sentinel")) { + return t0; + } + return a_DEBUG; +} + +/** + * props.b *does* influence `a` + */ +function ComponentB(props) { + const $ = _c(2); + let a; + if ($[0] !== props) { + a = []; + a.push(props.a); + if (props.b) { + a.push(props.c); + } + + a.push(props.d); + $[0] = props; + $[1] = a; + } else { + a = $[1]; + } + return a; +} + +/** + * props.b *does* influence `a`, but only in a way that is never observable + */ +function ComponentC(props) { + const $ = _c(3); + let a; + let t0; + if ($[0] !== props) { + t0 = Symbol.for("react.early_return_sentinel"); + bb0: { + a = []; + a.push(props.a); + if (props.b) { + a.push(props.c); + t0 = null; + break bb0; + } + + a.push(props.d); + } + $[0] = props; + $[1] = a; + $[2] = t0; + } else { + a = $[1]; + t0 = $[2]; + } + if (t0 !== Symbol.for("react.early_return_sentinel")) { + return t0; + } + return a; +} + +/** + * props.b *does* influence `a` + */ +function ComponentD(props) { + const $ = _c(3); + let a; + let t0; + if ($[0] !== props) { + t0 = Symbol.for("react.early_return_sentinel"); + bb0: { + a = []; + a.push(props.a); + if (props.b) { + a.push(props.c); + t0 = a; + break bb0; + } + + a.push(props.d); + } + $[0] = props; + $[1] = a; + $[2] = t0; + } else { + a = $[1]; + t0 = $[2]; + } + if (t0 !== Symbol.for("react.early_return_sentinel")) { + return t0; + } + return a; +} + +export const FIXTURE_ENTRYPOINT = { + fn: ComponentA, + params: [{ a: 1, b: false, d: 3 }], +}; + +``` + +### Eval output +(kind: ok) [1,3] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-early-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-early-return.js new file mode 100644 index 0000000000000..e0ed101409252 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-early-return.js @@ -0,0 +1,58 @@ +/** + * props.b does *not* influence `a` + */ +function ComponentA(props) { + const a_DEBUG = []; + a_DEBUG.push(props.a); + if (props.b) { + return null; + } + a_DEBUG.push(props.d); + return a_DEBUG; +} + +/** + * props.b *does* influence `a` + */ +function ComponentB(props) { + const a = []; + a.push(props.a); + if (props.b) { + a.push(props.c); + } + a.push(props.d); + return a; +} + +/** + * props.b *does* influence `a`, but only in a way that is never observable + */ +function ComponentC(props) { + const a = []; + a.push(props.a); + if (props.b) { + a.push(props.c); + return null; + } + a.push(props.d); + return a; +} + +/** + * props.b *does* influence `a` + */ +function ComponentD(props) { + const a = []; + a.push(props.a); + if (props.b) { + a.push(props.c); + return a; + } + a.push(props.d); + return a; +} + +export const FIXTURE_ENTRYPOINT = { + fn: ComponentA, + params: [{a: 1, b: false, d: 3}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-on-mutable.expect.md new file mode 100644 index 0000000000000..24c565b088d2f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-on-mutable.expect.md @@ -0,0 +1,88 @@ + +## Input + +```javascript +function ComponentA(props) { + const a = []; + const b = []; + if (b) { + a.push(props.p0); + } + if (props.p1) { + b.push(props.p2); + } + return ; +} + +function ComponentB(props) { + const a = []; + const b = []; + if (mayMutate(b)) { + a.push(props.p0); + } + if (props.p1) { + b.push(props.p2); + } + return ; +} + +function Foo() {} +function mayMutate() {} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function ComponentA(props) { + const $ = _c(2); + let t0; + if ($[0] !== props) { + const a = []; + const b = []; + if (b) { + a.push(props.p0); + } + if (props.p1) { + b.push(props.p2); + } + + t0 = ; + $[0] = props; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +function ComponentB(props) { + const $ = _c(2); + let t0; + if ($[0] !== props) { + const a = []; + const b = []; + if (mayMutate(b)) { + a.push(props.p0); + } + if (props.p1) { + b.push(props.p2); + } + + t0 = ; + $[0] = props; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +function Foo() {} +function mayMutate() {} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-on-mutable.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-on-mutable.js new file mode 100644 index 0000000000000..0378ab655c1ef --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/conditional-on-mutable.js @@ -0,0 +1,26 @@ +function ComponentA(props) { + const a = []; + const b = []; + if (b) { + a.push(props.p0); + } + if (props.p1) { + b.push(props.p2); + } + return ; +} + +function ComponentB(props) { + const a = []; + const b = []; + if (mayMutate(b)) { + a.push(props.p0); + } + if (props.p1) { + b.push(props.p2); + } + return ; +} + +function Foo() {} +function mayMutate() {} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md new file mode 100644 index 0000000000000..2d33981f73fd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md @@ -0,0 +1,89 @@ + +## Input + +```javascript +function Component(props) { + let x = []; + if (props.cond) { + x.push(props.a); + if (props.b) { + const y = [props.b]; + x.push(y); + // oops no memo! + return x; + } + // oops no memo! + return x; + } else { + return foo(); + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true, a: 42, b: 3.14}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(props) { + const $ = _c(5); + let t0; + if ($[0] !== props) { + t0 = Symbol.for("react.early_return_sentinel"); + bb0: { + const x = []; + if (props.cond) { + x.push(props.a); + if (props.b) { + let t1; + if ($[2] !== props.b) { + t1 = [props.b]; + $[2] = props.b; + $[3] = t1; + } else { + t1 = $[3]; + } + const y = t1; + x.push(y); + t0 = x; + break bb0; + } + + t0 = x; + break bb0; + } else { + let t1; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t1 = foo(); + $[4] = t1; + } else { + t1 = $[4]; + } + t0 = t1; + break bb0; + } + } + $[0] = props; + $[1] = t0; + } else { + t0 = $[1]; + } + if (t0 !== Symbol.for("react.early_return_sentinel")) { + return t0; + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true, a: 42, b: 3.14 }], +}; + +``` + +### Eval output +(kind: ok) [42,[3.14]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.js new file mode 100644 index 0000000000000..53eb06bc591e4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.js @@ -0,0 +1,21 @@ +function Component(props) { + let x = []; + if (props.cond) { + x.push(props.a); + if (props.b) { + const y = [props.b]; + x.push(y); + // oops no memo! + return x; + } + // oops no memo! + return x; + } else { + return foo(); + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true, a: 42, b: 3.14}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md new file mode 100644 index 0000000000000..6c3525e9e77eb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md @@ -0,0 +1,112 @@ + +## Input + +```javascript +import {makeArray} from 'shared-runtime'; + +function Component(props) { + let x = []; + if (props.cond) { + x.push(props.a); + // oops no memo! + return x; + } else { + return makeArray(props.b); + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + sequentialRenders: [ + // pattern 1 + {cond: true, a: 42}, + {cond: true, a: 42}, + // pattern 2 + {cond: false, b: 3.14}, + {cond: false, b: 3.14}, + // pattern 1 + {cond: true, a: 42}, + // pattern 2 + {cond: false, b: 3.14}, + // pattern 1 + {cond: true, a: 42}, + // pattern 2 + {cond: false, b: 3.14}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { makeArray } from "shared-runtime"; + +function Component(props) { + const $ = _c(4); + let t0; + if ($[0] !== props) { + t0 = Symbol.for("react.early_return_sentinel"); + bb0: { + const x = []; + if (props.cond) { + x.push(props.a); + t0 = x; + break bb0; + } else { + let t1; + if ($[2] !== props.b) { + t1 = makeArray(props.b); + $[2] = props.b; + $[3] = t1; + } else { + t1 = $[3]; + } + t0 = t1; + break bb0; + } + } + $[0] = props; + $[1] = t0; + } else { + t0 = $[1]; + } + if (t0 !== Symbol.for("react.early_return_sentinel")) { + return t0; + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + sequentialRenders: [ + // pattern 1 + { cond: true, a: 42 }, + { cond: true, a: 42 }, + // pattern 2 + { cond: false, b: 3.14 }, + { cond: false, b: 3.14 }, + // pattern 1 + { cond: true, a: 42 }, + // pattern 2 + { cond: false, b: 3.14 }, + // pattern 1 + { cond: true, a: 42 }, + // pattern 2 + { cond: false, b: 3.14 }, + ], +}; + +``` + +### Eval output +(kind: ok) [42] +[42] +[3.14] +[3.14] +[42] +[3.14] +[42] +[3.14] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.js new file mode 100644 index 0000000000000..7446d76fb320c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.js @@ -0,0 +1,33 @@ +import {makeArray} from 'shared-runtime'; + +function Component(props) { + let x = []; + if (props.cond) { + x.push(props.a); + // oops no memo! + return x; + } else { + return makeArray(props.b); + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + sequentialRenders: [ + // pattern 1 + {cond: true, a: 42}, + {cond: true, a: 42}, + // pattern 2 + {cond: false, b: 3.14}, + {cond: false, b: 3.14}, + // pattern 1 + {cond: true, a: 42}, + // pattern 2 + {cond: false, b: 3.14}, + // pattern 1 + {cond: true, a: 42}, + // pattern 2 + {cond: false, b: 3.14}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-call-chain-in-optional.expect.md new file mode 100644 index 0000000000000..75c5d61d407f0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-call-chain-in-optional.expect.md @@ -0,0 +1,34 @@ + +## Input + +```javascript +function useFoo(props: {value: {x: string; y: string} | null}) { + const value = props.value; + return createArray(value?.x, value?.y)?.join(', '); +} + +function createArray(...args: Array): Array { + return args; +} + +export const FIXTURE_ENTRYPONT = { + fn: useFoo, + props: [{value: null}], +}; + +``` + + +## Error + +``` + 1 | function useFoo(props: {value: {x: string; y: string} | null}) { + 2 | const value = props.value; +> 3 | return createArray(value?.x, value?.y)?.join(', '); + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + 4 | } + 5 | + 6 | function createArray(...args: Array): Array { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-call-chain-in-optional.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-call-chain-in-optional.ts new file mode 100644 index 0000000000000..7ee50e0380288 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-call-chain-in-optional.ts @@ -0,0 +1,13 @@ +function useFoo(props: {value: {x: string; y: string} | null}) { + const value = props.value; + return createArray(value?.x, value?.y)?.join(', '); +} + +function createArray(...args: Array): Array { + return args; +} + +export const FIXTURE_ENTRYPONT = { + fn: useFoo, + props: [{value: null}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md new file mode 100644 index 0000000000000..bed1c329f0d10 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md @@ -0,0 +1,57 @@ + +## Input + +```javascript +function Component(props) { + const items = (() => { + if (props.cond) { + return []; + } else { + return null; + } + })(); + items?.push(props.a); + return items; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(props) { + const $ = _c(2); + let items; + if ($[0] !== props) { + let t0; + if (props.cond) { + t0 = []; + } else { + t0 = null; + } + items = t0; + + items?.push(props.a); + $[0] = props; + $[1] = items; + } else { + items = $[1]; + } + return items; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: {} }], +}; + +``` + +### Eval output +(kind: ok) null \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.js new file mode 100644 index 0000000000000..f4f953d294e6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.js @@ -0,0 +1,16 @@ +function Component(props) { + const items = (() => { + if (props.cond) { + return []; + } else { + return null; + } + })(); + items?.push(props.a); + return items; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {}}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/object-mutated-in-consequent-alternate-both-return.expect.md new file mode 100644 index 0000000000000..8a20f9186b447 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/object-mutated-in-consequent-alternate-both-return.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +import {makeObject_Primitives} from 'shared-runtime'; + +function Component(props) { + const object = makeObject_Primitives(); + if (props.cond) { + object.value = 1; + return object; + } else { + object.value = props.value; + return object; + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: false, value: [0, 1, 2]}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { makeObject_Primitives } from "shared-runtime"; + +function Component(props) { + const $ = _c(2); + let t0; + if ($[0] !== props) { + t0 = Symbol.for("react.early_return_sentinel"); + bb0: { + const object = makeObject_Primitives(); + if (props.cond) { + object.value = 1; + t0 = object; + break bb0; + } else { + object.value = props.value; + t0 = object; + break bb0; + } + } + $[0] = props; + $[1] = t0; + } else { + t0 = $[1]; + } + if (t0 !== Symbol.for("react.early_return_sentinel")) { + return t0; + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: false, value: [0, 1, 2] }], +}; + +``` + +### Eval output +(kind: ok) {"a":0,"b":"value1","c":true,"value":[0,1,2]} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/object-mutated-in-consequent-alternate-both-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/object-mutated-in-consequent-alternate-both-return.js new file mode 100644 index 0000000000000..94a142d033708 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/object-mutated-in-consequent-alternate-both-return.js @@ -0,0 +1,17 @@ +import {makeObject_Primitives} from 'shared-runtime'; + +function Component(props) { + const object = makeObject_Primitives(); + if (props.cond) { + object.value = 1; + return object; + } else { + object.value = props.value; + return object; + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: false, value: [0, 1, 2]}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md new file mode 100644 index 0000000000000..398161f0c6a24 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md @@ -0,0 +1,80 @@ + +## Input + +```javascript +function Component(props) { + let x = []; + let y = null; + if (props.cond) { + x.push(props.a); + // oops no memo! + return x; + } else { + y = foo(); + if (props.b) { + return; + } + } + return y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true, a: 42}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(props) { + const $ = _c(4); + let y; + let t0; + if ($[0] !== props) { + t0 = Symbol.for("react.early_return_sentinel"); + bb0: { + const x = []; + if (props.cond) { + x.push(props.a); + t0 = x; + break bb0; + } else { + let t1; + if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + t1 = foo(); + $[3] = t1; + } else { + t1 = $[3]; + } + y = t1; + if (props.b) { + t0 = undefined; + break bb0; + } + } + } + $[0] = props; + $[1] = y; + $[2] = t0; + } else { + y = $[1]; + t0 = $[2]; + } + if (t0 !== Symbol.for("react.early_return_sentinel")) { + return t0; + } + return y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true, a: 42 }], +}; + +``` + +### Eval output +(kind: ok) [42] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.js new file mode 100644 index 0000000000000..66d68242b8bb6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.js @@ -0,0 +1,20 @@ +function Component(props) { + let x = []; + let y = null; + if (props.cond) { + x.push(props.a); + // oops no memo! + return x; + } else { + y = foo(); + if (props.b) { + return; + } + } + return y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true, a: 42}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push-consecutive-phis.expect.md new file mode 100644 index 0000000000000..f17bcc92cb7ce --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push-consecutive-phis.expect.md @@ -0,0 +1,110 @@ + +## Input + +```javascript +import {makeArray} from 'shared-runtime'; + +function Component(props) { + const x = {}; + let y; + if (props.cond) { + if (props.cond2) { + y = [props.value]; + } else { + y = [props.value2]; + } + } else { + y = []; + } + // This should be inferred as ` y` s.t. `x` can still + // be independently memoized. *But* this also must properly + // extend the mutable range of the array literals in the + // if/else branches + y.push(x); + + return [x, y]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true, cond2: true, value: 42}], + sequentialRenders: [ + {cond: true, cond2: true, value: 3.14}, + {cond: true, cond2: true, value: 42}, + {cond: true, cond2: true, value: 3.14}, + {cond: true, cond2: false, value2: 3.14}, + {cond: true, cond2: false, value2: 42}, + {cond: true, cond2: false, value2: 3.14}, + {cond: false}, + {cond: false}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { makeArray } from "shared-runtime"; + +function Component(props) { + const $ = _c(3); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = {}; + $[0] = t0; + } else { + t0 = $[0]; + } + const x = t0; + let t1; + if ($[1] !== props) { + let y; + if (props.cond) { + if (props.cond2) { + y = [props.value]; + } else { + y = [props.value2]; + } + } else { + y = []; + } + + y.push(x); + + t1 = [x, y]; + $[1] = props; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true, cond2: true, value: 42 }], + sequentialRenders: [ + { cond: true, cond2: true, value: 3.14 }, + { cond: true, cond2: true, value: 42 }, + { cond: true, cond2: true, value: 3.14 }, + { cond: true, cond2: false, value2: 3.14 }, + { cond: true, cond2: false, value2: 42 }, + { cond: true, cond2: false, value2: 3.14 }, + { cond: false }, + { cond: false }, + ], +}; + +``` + +### Eval output +(kind: ok) [{},[3.14,"[[ cyclic ref *1 ]]"]] +[{},[42,"[[ cyclic ref *1 ]]"]] +[{},[3.14,"[[ cyclic ref *1 ]]"]] +[{},[3.14,"[[ cyclic ref *1 ]]"]] +[{},[42,"[[ cyclic ref *1 ]]"]] +[{},[3.14,"[[ cyclic ref *1 ]]"]] +[{},["[[ cyclic ref *1 ]]"]] +[{},["[[ cyclic ref *1 ]]"]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push-consecutive-phis.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push-consecutive-phis.js new file mode 100644 index 0000000000000..0a9aa39defea4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push-consecutive-phis.js @@ -0,0 +1,37 @@ +import {makeArray} from 'shared-runtime'; + +function Component(props) { + const x = {}; + let y; + if (props.cond) { + if (props.cond2) { + y = [props.value]; + } else { + y = [props.value2]; + } + } else { + y = []; + } + // This should be inferred as ` y` s.t. `x` can still + // be independently memoized. *But* this also must properly + // extend the mutable range of the array literals in the + // if/else branches + y.push(x); + + return [x, y]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true, cond2: true, value: 42}], + sequentialRenders: [ + {cond: true, cond2: true, value: 3.14}, + {cond: true, cond2: true, value: 42}, + {cond: true, cond2: true, value: 3.14}, + {cond: true, cond2: false, value2: 3.14}, + {cond: true, cond2: false, value2: 42}, + {cond: true, cond2: false, value2: 3.14}, + {cond: false}, + {cond: false}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push.expect.md new file mode 100644 index 0000000000000..f58eed10fda5a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push.expect.md @@ -0,0 +1,83 @@ + +## Input + +```javascript +function Component(props) { + const x = {}; + let y; + if (props.cond) { + y = [props.value]; + } else { + y = []; + } + // This should be inferred as ` y` s.t. `x` can still + // be independently memoized. *But* this also must properly + // extend the mutable range of the array literals in the + // if/else branches + y.push(x); + + return [x, y]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true, value: 42}], + sequentialRenders: [ + {cond: true, value: 3.14}, + {cond: false, value: 3.14}, + {cond: true, value: 42}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(props) { + const $ = _c(3); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = {}; + $[0] = t0; + } else { + t0 = $[0]; + } + const x = t0; + let t1; + if ($[1] !== props) { + let y; + if (props.cond) { + y = [props.value]; + } else { + y = []; + } + + y.push(x); + + t1 = [x, y]; + $[1] = props; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true, value: 42 }], + sequentialRenders: [ + { cond: true, value: 3.14 }, + { cond: false, value: 3.14 }, + { cond: true, value: 42 }, + ], +}; + +``` + +### Eval output +(kind: ok) [{},[3.14,"[[ cyclic ref *1 ]]"]] +[{},["[[ cyclic ref *1 ]]"]] +[{},[42,"[[ cyclic ref *1 ]]"]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push.js new file mode 100644 index 0000000000000..732a1524cba2d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-array-push.js @@ -0,0 +1,26 @@ +function Component(props) { + const x = {}; + let y; + if (props.cond) { + y = [props.value]; + } else { + y = []; + } + // This should be inferred as ` y` s.t. `x` can still + // be independently memoized. *But* this also must properly + // extend the mutable range of the array literals in the + // if/else branches + y.push(x); + + return [x, y]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true, value: 42}], + sequentialRenders: [ + {cond: true, value: 3.14}, + {cond: false, value: 3.14}, + {cond: true, value: 42}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md new file mode 100644 index 0000000000000..70551c8e9d7b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +// @debug +function Component(props) { + const x = {}; + let y; + if (props.cond) { + y = {}; + } else { + y = {a: props.a}; + } + // This should be inferred as ` y` s.t. `x` can still + // be independently memoized. *But* this also must properly + // extend the mutable range of the object literals in the + // if/else branches + y.x = x; + + return [x, y]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: false, a: 'a!'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @debug +function Component(props) { + const $ = _c(3); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = {}; + $[0] = t0; + } else { + t0 = $[0]; + } + const x = t0; + let t1; + if ($[1] !== props) { + let y; + if (props.cond) { + y = {}; + } else { + y = { a: props.a }; + } + + y.x = x; + + t1 = [x, y]; + $[1] = props; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: false, a: "a!" }], +}; + +``` + +### Eval output +(kind: ok) [{},{"a":"a!","x":"[[ cyclic ref *1 ]]"}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.js new file mode 100644 index 0000000000000..ba7133ff808df --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.js @@ -0,0 +1,22 @@ +// @debug +function Component(props) { + const x = {}; + let y; + if (props.cond) { + y = {}; + } else { + y = {a: props.a}; + } + // This should be inferred as ` y` s.t. `x` can still + // be independently memoized. *But* this also must properly + // extend the mutable range of the object literals in the + // if/else branches + y.x = x; + + return [x, y]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: false, a: 'a!'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md new file mode 100644 index 0000000000000..6574bd1966ed9 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -0,0 +1,31 @@ + +## Input + +```javascript +function Component(props) { + return props.post.feedback.comments?.edges?.map(render); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(props) { + const $ = _c(2); + let t0; + if ($[0] !== props.post.feedback.comments) { + t0 = props.post.feedback.comments?.edges?.map(render); + $[0] = props.post.feedback.comments; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reactive-dependencies-non-optional-properties-inside-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reactive-dependencies-non-optional-properties-inside-optional-chain.js new file mode 100644 index 0000000000000..e8e0da392e9e7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reactive-dependencies-non-optional-properties-inside-optional-chain.js @@ -0,0 +1,3 @@ +function Component(props) { + return props.post.feedback.comments?.edges?.map(render); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-with-mutation.expect.md new file mode 100644 index 0000000000000..f4689e5795552 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-with-mutation.expect.md @@ -0,0 +1,79 @@ + +## Input + +```javascript +import {mutate} from 'shared-runtime'; + +function useFoo(props) { + let x = []; + x.push(props.bar); + if (props.cond) { + x = {}; + x = []; + x.push(props.foo); + } else { + x = []; + x = []; + x.push(props.bar); + } + mutate(x); + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{bar: 'bar', foo: 'foo', cond: true}], + sequentialRenders: [ + {bar: 'bar', foo: 'foo', cond: true}, + {bar: 'bar', foo: 'foo', cond: true}, + {bar: 'bar', foo: 'foo', cond: false}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { mutate } from "shared-runtime"; + +function useFoo(props) { + const $ = _c(2); + let x; + if ($[0] !== props) { + x = []; + x.push(props.bar); + if (props.cond) { + x = []; + x.push(props.foo); + } else { + x = []; + x.push(props.bar); + } + + mutate(x); + $[0] = props; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ bar: "bar", foo: "foo", cond: true }], + sequentialRenders: [ + { bar: "bar", foo: "foo", cond: true }, + { bar: "bar", foo: "foo", cond: true }, + { bar: "bar", foo: "foo", cond: false }, + ], +}; + +``` + +### Eval output +(kind: ok) ["foo","joe"] +["foo","joe"] +["bar","joe"] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-with-mutation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-with-mutation.js new file mode 100644 index 0000000000000..3e7078cfc7999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-with-mutation.js @@ -0,0 +1,27 @@ +import {mutate} from 'shared-runtime'; + +function useFoo(props) { + let x = []; + x.push(props.bar); + if (props.cond) { + x = {}; + x = []; + x.push(props.foo); + } else { + x = []; + x = []; + x.push(props.bar); + } + mutate(x); + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{bar: 'bar', foo: 'foo', cond: true}], + sequentialRenders: [ + {bar: 'bar', foo: 'foo', cond: true}, + {bar: 'bar', foo: 'foo', cond: true}, + {bar: 'bar', foo: 'foo', cond: false}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 7d7476dc74739..7140cad2f74bc 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -50,6 +50,7 @@ const skipFilter = new Set([ 'component', 'cond-deps-conditional-member-expr', 'conditional-break-labeled', + 'propagate-scope-deps-hir-fork/conditional-break-labeled', 'conditional-set-state-in-render', 'constant-computed', 'constant-propagation-phi',