From 8deecf508559f064f9c1d1bf2b82ec0d90c9b4d0 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Fri, 14 Nov 2025 11:23:42 -0800 Subject: [PATCH] [compiler] Repro for false positive mutation of a value derived from props Repro from the compiler WG (Thanks Cody!) of a case where the compiler incorrectly thinks a value is mutable. --- ...ure-from-prop-with-default-value.expect.md | 43 +++++++++++++++++++ ...estructure-from-prop-with-default-value.js | 15 +++++++ 2 files changed, 58 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-destructure-from-prop-with-default-value.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-destructure-from-prop-with-default-value.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-destructure-from-prop-with-default-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-destructure-from-prop-with-default-value.expect.md new file mode 100644 index 0000000000000..9bd31999f6216 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-destructure-from-prop-with-default-value.expect.md @@ -0,0 +1,43 @@ + +## Input + +```javascript +export function useFormatRelativeTime(opts = {}) { + const {timeZone, minimal} = opts; + const format = useCallback(function formatWithUnit() {}, [minimal]); + // We record `{timeZone}` as capturing timeZone into the object, + // then assume that dateTimeFormat() mutates that object, + // which in turn can mutate timeZone and the object it came from, + // which means that the value `minimal` is derived from can change. + // + // The bug is that we shouldn't be recording a Capture effect given + // that `opts` is known maybe-frozen. If we correctly recorded + // an ImmutableCapture, this wouldn't be mistaken as mutating + // opts/minimal + dateTimeFormat({timeZone}); + return format; +} + +``` + + +## Error + +``` +Found 1 error: + +Compilation Skipped: Existing memoization could not be preserved + +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly. + +error.todo-repro-destructure-from-prop-with-default-value.ts:3:60 + 1 | export function useFormatRelativeTime(opts = {}) { + 2 | const {timeZone, minimal} = opts; +> 3 | const format = useCallback(function formatWithUnit() {}, [minimal]); + | ^^^^^^^ This dependency may be modified later + 4 | // We record `{timeZone}` as capturing timeZone into the object, + 5 | // then assume that dateTimeFormat() mutates that object, + 6 | // which in turn can mutate timeZone and the object it came from, +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-destructure-from-prop-with-default-value.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-destructure-from-prop-with-default-value.js new file mode 100644 index 0000000000000..bcbd85231f599 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-destructure-from-prop-with-default-value.js @@ -0,0 +1,15 @@ +export function useFormatRelativeTime(opts = {}) { + const {timeZone, minimal} = opts; + const format = useCallback(function formatWithUnit() {}, [minimal]); + // We record `{timeZone}` as capturing timeZone into the object, + // then assume that dateTimeFormat() mutates that object, + // which in turn can mutate timeZone and the object it came from, + // which means that the value `minimal` is derived from can change. + // + // The bug is that we shouldn't be recording a Capture effect given + // that `opts` is known maybe-frozen. If we correctly recorded + // an ImmutableCapture, this wouldn't be mistaken as mutating + // opts/minimal + dateTimeFormat({timeZone}); + return format; +}