diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 6b3ba6f94c8ae..a8efe8a4713eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1445,6 +1445,7 @@ export enum ValueKind { Primitive = 'primitive', Global = 'global', Mutable = 'mutable', + ShallowMutable = 'shallowmutable', Context = 'context', } @@ -1454,6 +1455,7 @@ export const ValueKindSchema = z.enum([ ValueKind.Primitive, ValueKind.Global, ValueKind.Mutable, + ValueKind.ShallowMutable, ValueKind.Context, ]); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index 8ef78aa196428..a9cd774e54574 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -591,8 +591,14 @@ function applyEffect( }; context.effectInstructionValueCache.set(effect, value); } + + const outputKind = + fromValue.kind === ValueKind.ShallowMutable + ? ValueKind.Frozen + : fromValue.kind; + state.initialize(value, { - kind: fromValue.kind, + kind: outputKind, reason: new Set(fromValue.reason), }); state.define(effect.into, value); @@ -607,10 +613,11 @@ function applyEffect( }); break; } + case ValueKind.ShallowMutable: case ValueKind.Frozen: { effects.push({ kind: 'Create', - value: fromValue.kind, + value: outputKind, into: effect.into, reason: [...fromValue.reason][0] ?? ValueReason.Other, }); @@ -720,11 +727,14 @@ function applyEffect( * copy-on-write semantics, then we can prune the effect */ const intoKind = state.kind(effect.into).kind; + const fromKind = state.kind(effect.from).kind; + let isMutableDesination: boolean; switch (intoKind) { case ValueKind.Context: case ValueKind.Mutable: - case ValueKind.MaybeFrozen: { + case ValueKind.MaybeFrozen: + case ValueKind.ShallowMutable: { isMutableDesination = true; break; } @@ -733,7 +743,6 @@ function applyEffect( break; } } - const fromKind = state.kind(effect.from).kind; let isMutableReferenceType: boolean; switch (fromKind) { case ValueKind.Global: @@ -741,6 +750,7 @@ function applyEffect( isMutableReferenceType = false; break; } + case ValueKind.ShallowMutable: case ValueKind.Frozen: { isMutableReferenceType = false; applyEffect( @@ -781,6 +791,7 @@ function applyEffect( const fromValue = state.kind(effect.from); const fromKind = fromValue.kind; switch (fromKind) { + case ValueKind.ShallowMutable: case ValueKind.Frozen: { applyEffect( context, @@ -1267,6 +1278,7 @@ class InferenceState { switch (value.kind) { case ValueKind.Context: case ValueKind.Mutable: + case ValueKind.ShallowMutable: case ValueKind.MaybeFrozen: { const values = this.values(place); for (const instrValue of values) { @@ -1315,13 +1327,30 @@ class InferenceState { if (isRefOrRefValue(place.identifier)) { return 'mutate-ref'; } - const kind = this.kind(place).kind; + const abstractValue = this.kind(place); + const kind = abstractValue.kind; + + // Downgrade ShallowMutable to Mutable when mutated + if (kind === ValueKind.ShallowMutable) { + const values = this.values(place); + for (const value of values) { + const valueInfo = this.#values.get(value); + if (valueInfo && valueInfo.kind === ValueKind.ShallowMutable) { + this.#values.set(value, { + kind: ValueKind.Mutable, + reason: valueInfo.reason, + }); + } + } + } + switch (variant) { case 'MutateConditionally': case 'MutateTransitiveConditionally': { switch (kind) { case ValueKind.Mutable: - case ValueKind.Context: { + case ValueKind.Context: + case ValueKind.ShallowMutable: { return 'mutate'; } default: { @@ -1333,7 +1362,8 @@ class InferenceState { case 'MutateTransitive': { switch (kind) { case ValueKind.Mutable: - case ValueKind.Context: { + case ValueKind.Context: + case ValueKind.ShallowMutable: { return 'mutate'; } case ValueKind.Primitive: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/array-spread-from-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/array-spread-from-hook.expect.md new file mode 100644 index 0000000000000..4aa3346e24dfd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/array-spread-from-hook.expect.md @@ -0,0 +1,91 @@ + +## Input + +```javascript +import {useMemo} from 'react'; +import {ValidateMemoization} from 'shared-runtime'; + +function useData() { + return ['a', 'b', 'c']; +} + +function Component() { + const [first, ...rest] = useData(); + + const result = useMemo(() => { + return rest.join('-'); + }, [rest]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useMemo } from "react"; +import { ValidateMemoization } from "shared-runtime"; + +function useData() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ["a", "b", "c"]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +function Component() { + const $ = _c(7); + const t0 = useData(); + let rest; + if ($[0] !== t0) { + [, ...rest] = t0; + $[0] = t0; + $[1] = rest; + } else { + rest = $[1]; + } + + const result = rest.join("-"); + let t1; + if ($[2] !== rest) { + t1 = [rest]; + $[2] = rest; + $[3] = t1; + } else { + t1 = $[3]; + } + let t2; + if ($[4] !== result || $[5] !== t1) { + t2 = ; + $[4] = result; + $[5] = t1; + $[6] = t2; + } else { + t2 = $[6]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[["b","c"]],"output":"b-c"}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/array-spread-from-hook.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/array-spread-from-hook.tsx new file mode 100644 index 0000000000000..d1b0b40cda176 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/array-spread-from-hook.tsx @@ -0,0 +1,22 @@ +import {useMemo} from 'react'; +import {ValidateMemoization} from 'shared-runtime'; + +function useData() { + return ['a', 'b', 'c']; +} + +function Component() { + const [first, ...rest] = useData(); + + const result = useMemo(() => { + return rest.join('-'); + }, [rest]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/destructured-with-rest-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/destructured-with-rest-props.expect.md new file mode 100644 index 0000000000000..0720d98cddcf4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/destructured-with-rest-props.expect.md @@ -0,0 +1,120 @@ + +## Input + +```javascript +import {useMemo} from 'react'; + +function useTheme() { + return {primary: '#blue', secondary: '#green'}; +} + +function computeStyles( + specialProp: string | undefined, + restProps: any, + theme: any, +) { + return { + color: specialProp ? theme.primary : theme.secondary, + ...restProps.style, + }; +} + +export function SpecialButton({ + specialProp, + ...restProps +}: { + specialProp?: string; + style?: Record; + onClick?: () => void; +}) { + const theme = useTheme(); + + const styles = useMemo( + () => computeStyles(specialProp, restProps, theme), + [specialProp, restProps, theme], + ); + + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: SpecialButton, + params: [{specialProp: 'test', style: {fontSize: '16px'}, onClick: () => {}}], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useMemo } from "react"; + +function useTheme() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = { primary: "#blue", secondary: "#green" }; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +function computeStyles(specialProp, restProps, theme) { + const $ = _c(3); + + const t0 = specialProp ? theme.primary : theme.secondary; + let t1; + if ($[0] !== restProps.style || $[1] !== t0) { + t1 = { color: t0, ...restProps.style }; + $[0] = restProps.style; + $[1] = t0; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export function SpecialButton(t0) { + const $ = _c(3); + const { specialProp, ...restProps } = t0; + + const theme = useTheme(); + + const styles = computeStyles(specialProp, restProps, theme); + let t1; + if ($[0] !== restProps.onClick || $[1] !== styles) { + t1 = ( + + ); + $[0] = restProps.onClick; + $[1] = styles; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: SpecialButton, + params: [ + { specialProp: "test", style: { fontSize: "16px" }, onClick: () => {} }, + ], + isComponent: true, +}; + +``` + +### Eval output +(kind: ok) \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/destructured-with-rest-props.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/destructured-with-rest-props.tsx new file mode 100644 index 0000000000000..c0804c824b83f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/destructured-with-rest-props.tsx @@ -0,0 +1,44 @@ +import {useMemo} from 'react'; + +function useTheme() { + return {primary: '#blue', secondary: '#green'}; +} + +function computeStyles( + specialProp: string | undefined, + restProps: any, + theme: any, +) { + return { + color: specialProp ? theme.primary : theme.secondary, + ...restProps.style, + }; +} + +export function SpecialButton({ + specialProp, + ...restProps +}: { + specialProp?: string; + style?: Record; + onClick?: () => void; +}) { + const theme = useTheme(); + + const styles = useMemo( + () => computeStyles(specialProp, restProps, theme), + [specialProp, restProps, theme], + ); + + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: SpecialButton, + params: [{specialProp: 'test', style: {fontSize: '16px'}, onClick: () => {}}], + isComponent: true, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/function-arg-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/function-arg-spread.expect.md new file mode 100644 index 0000000000000..7162bc1fdf9f0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/function-arg-spread.expect.md @@ -0,0 +1,85 @@ + +## Input + +```javascript +import {useMemo} from 'react'; +import {ValidateMemoization} from 'shared-runtime'; + +function Component({...data}: {x: number; y: number}) { + const result = useMemo(() => { + return data.x + data.y; + }, [data.x, data.y]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: 10, y: 20}], + sequentialRenders: [ + {x: 10, y: 20}, + {x: 10, y: 20}, + {x: 15, y: 25}, + ], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useMemo } from "react"; +import { ValidateMemoization } from "shared-runtime"; + +function Component(t0) { + const $ = _c(8); + let data; + if ($[0] !== t0) { + ({ ...data } = t0); + $[0] = t0; + $[1] = data; + } else { + data = $[1]; + } + const result = data.x + data.y; + let t1; + if ($[2] !== data.x || $[3] !== data.y) { + t1 = [data.x, data.y]; + $[2] = data.x; + $[3] = data.y; + $[4] = t1; + } else { + t1 = $[4]; + } + let t2; + if ($[5] !== result || $[6] !== t1) { + t2 = ; + $[5] = result; + $[6] = t1; + $[7] = t2; + } else { + t2 = $[7]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ x: 10, y: 20 }], + sequentialRenders: [ + { x: 10, y: 20 }, + { x: 10, y: 20 }, + { x: 15, y: 25 }, + ], + + isComponent: true, +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[10,20],"output":30}
+
{"inputs":[10,20],"output":30}
+
{"inputs":[15,25],"output":40}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/function-arg-spread.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/function-arg-spread.tsx new file mode 100644 index 0000000000000..517f75f0e7ec2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/function-arg-spread.tsx @@ -0,0 +1,21 @@ +import {useMemo} from 'react'; +import {ValidateMemoization} from 'shared-runtime'; + +function Component({...data}: {x: number; y: number}) { + const result = useMemo(() => { + return data.x + data.y; + }, [data.x, data.y]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: 10, y: 20}], + sequentialRenders: [ + {x: 10, y: 20}, + {x: 10, y: 20}, + {x: 15, y: 25}, + ], + isComponent: true, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/object-spread-hook-returns.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/object-spread-hook-returns.expect.md new file mode 100644 index 0000000000000..9e6b273460b37 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/object-spread-hook-returns.expect.md @@ -0,0 +1,98 @@ + +## Input + +```javascript +import {useMemo} from 'react'; +import {ValidateMemoization} from 'shared-runtime'; + +function useConfig() { + return {a: 1, b: 2, c: 3}; +} + +function Component() { + const {...spread} = useConfig(); + + const result = useMemo(() => { + return spread.a + spread.b + spread.c; + }, [spread.a, spread.b, spread.c]); + + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useMemo } from "react"; +import { ValidateMemoization } from "shared-runtime"; + +function useConfig() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = { a: 1, b: 2, c: 3 }; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +function Component() { + const $ = _c(9); + const t0 = useConfig(); + let spread; + if ($[0] !== t0) { + ({ ...spread } = t0); + $[0] = t0; + $[1] = spread; + } else { + spread = $[1]; + } + + const result = spread.a + spread.b + spread.c; + let t1; + if ($[2] !== spread.a || $[3] !== spread.b || $[4] !== spread.c) { + t1 = [spread.a, spread.b, spread.c]; + $[2] = spread.a; + $[3] = spread.b; + $[4] = spread.c; + $[5] = t1; + } else { + t1 = $[5]; + } + let t2; + if ($[6] !== result || $[7] !== t1) { + t2 = ; + $[6] = result; + $[7] = t1; + $[8] = t2; + } else { + t2 = $[8]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[1,2,3],"output":6}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/object-spread-hook-returns.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/object-spread-hook-returns.tsx new file mode 100644 index 0000000000000..1136d2ff303c2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/object-spread-hook-returns.tsx @@ -0,0 +1,27 @@ +import {useMemo} from 'react'; +import {ValidateMemoization} from 'shared-runtime'; + +function useConfig() { + return {a: 1, b: 2, c: 3}; +} + +function Component() { + const {...spread} = useConfig(); + + const result = useMemo(() => { + return spread.a + spread.b + spread.c; + }, [spread.a, spread.b, spread.c]); + + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/regular-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/regular-props.expect.md new file mode 100644 index 0000000000000..caa60991a8243 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/regular-props.expect.md @@ -0,0 +1,95 @@ + +## Input + +```javascript +import {useMemo} from 'react'; + +function useSession() { + return {user: {userCode: 'ABC123'}}; +} + +function getDefaultFromValue( + defaultValues: string | undefined, + userCode: string, +) { + return defaultValues ? `${defaultValues}-${userCode}` : userCode; +} + +export function UpSertField(props: {defaultValues?: string}) { + const { + user: {userCode}, + } = useSession(); + + const defaultValues = useMemo( + () => getDefaultFromValue(props.defaultValues, userCode), + [props.defaultValues, userCode], + ); + + return
{defaultValues}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: UpSertField, + params: [{defaultValues: 'test'}], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useMemo } from "react"; + +function useSession() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = { user: { userCode: "ABC123" } }; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +function getDefaultFromValue(defaultValues, userCode) { + return defaultValues ? `${defaultValues}-${userCode}` : userCode; +} + +export function UpSertField(props) { + const $ = _c(5); + const { user: t0 } = useSession(); + const { userCode } = t0; + let t1; + if ($[0] !== props.defaultValues || $[1] !== userCode) { + t1 = getDefaultFromValue(props.defaultValues, userCode); + $[0] = props.defaultValues; + $[1] = userCode; + $[2] = t1; + } else { + t1 = $[2]; + } + const defaultValues = t1; + let t2; + if ($[3] !== defaultValues) { + t2 =
{defaultValues}
; + $[3] = defaultValues; + $[4] = t2; + } else { + t2 = $[4]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: UpSertField, + params: [{ defaultValues: "test" }], + isComponent: true, +}; + +``` + +### Eval output +(kind: ok)
test-ABC123
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/regular-props.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/regular-props.tsx new file mode 100644 index 0000000000000..693d09484266d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/regular-props.tsx @@ -0,0 +1,31 @@ +import {useMemo} from 'react'; + +function useSession() { + return {user: {userCode: 'ABC123'}}; +} + +function getDefaultFromValue( + defaultValues: string | undefined, + userCode: string, +) { + return defaultValues ? `${defaultValues}-${userCode}` : userCode; +} + +export function UpSertField(props: {defaultValues?: string}) { + const { + user: {userCode}, + } = useSession(); + + const defaultValues = useMemo( + () => getDefaultFromValue(props.defaultValues, userCode), + [props.defaultValues, userCode], + ); + + return
{defaultValues}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: UpSertField, + params: [{defaultValues: 'test'}], + isComponent: true, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/shallow-mutable-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/shallow-mutable-mutation.expect.md new file mode 100644 index 0000000000000..7728ab7a6ba4d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/shallow-mutable-mutation.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +function Component({...props}: {value: string}) { + const obj = {}; + props.newProp = obj; + obj.mutated = true; + + return
{props.value}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 'test'}], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(4); + let props; + if ($[0] !== t0) { + ({ ...props } = t0); + const obj = {}; + props.newProp = obj; + obj.mutated = true; + $[0] = t0; + $[1] = props; + } else { + props = $[1]; + } + let t1; + if ($[2] !== props.value) { + t1 =
{props.value}
; + $[2] = props.value; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: "test" }], + isComponent: true, +}; + +``` + +### Eval output +(kind: ok)
test
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/shallow-mutable-mutation.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/shallow-mutable-mutation.tsx new file mode 100644 index 0000000000000..f329d9da959b4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/shallow-mutable-mutation.tsx @@ -0,0 +1,13 @@ +function Component({...props}: {value: string}) { + const obj = {}; + props.newProp = obj; + obj.mutated = true; + + return
{props.value}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 'test'}], + isComponent: true, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/spread-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/spread-props.expect.md new file mode 100644 index 0000000000000..b1bd8d542c51d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/spread-props.expect.md @@ -0,0 +1,84 @@ + +## Input + +```javascript +import {useMemo} from 'react'; +import {ValidateMemoization} from 'shared-runtime'; + +function Component({...props}: {value: string}) { + const result = useMemo(() => { + return props.value.toUpperCase(); + }, [props.value]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 'test'}], + sequentialRenders: [{value: 'test'}, {value: 'test'}, {value: 'changed'}], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useMemo } from "react"; +import { ValidateMemoization } from "shared-runtime"; + +function Component(t0) { + const $ = _c(8); + let props; + let t1; + if ($[0] !== t0) { + ({ ...props } = t0); + + t1 = props.value.toUpperCase(); + $[0] = t0; + $[1] = props; + $[2] = t1; + } else { + props = $[1]; + t1 = $[2]; + } + const result = t1; + let t2; + if ($[3] !== props.value) { + t2 = [props.value]; + $[3] = props.value; + $[4] = t2; + } else { + t2 = $[4]; + } + let t3; + if ($[5] !== result || $[6] !== t2) { + t3 = ; + $[5] = result; + $[6] = t2; + $[7] = t3; + } else { + t3 = $[7]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: "test" }], + sequentialRenders: [ + { value: "test" }, + { value: "test" }, + { value: "changed" }, + ], + isComponent: true, +}; + +``` + +### Eval output +(kind: ok)
{"inputs":["test"],"output":"TEST"}
+
{"inputs":["test"],"output":"TEST"}
+
{"inputs":["changed"],"output":"CHANGED"}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/spread-props.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/spread-props.tsx new file mode 100644 index 0000000000000..7b7f831fe3e10 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/spread-props.tsx @@ -0,0 +1,17 @@ +import {useMemo} from 'react'; +import {ValidateMemoization} from 'shared-runtime'; + +function Component({...props}: {value: string}) { + const result = useMemo(() => { + return props.value.toUpperCase(); + }, [props.value]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 'test'}], + sequentialRenders: [{value: 'test'}, {value: 'test'}, {value: 'changed'}], + isComponent: true, +};