Repository: facebook/react
Pull request: [compiler] Repro of missing memoization due to capturing w/o mutation

Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at bottom):
* #30790
* #30789
* #30785
* #30784
* __->__ #30783
* #30771
* #30766
* #30764

If you have a function expression which _captures_ a mutable value (but does not mutate it), and that function is invoked during render, we infer the invocation as a mutation of the captured value. But in some circumstances we can prove that the captured value cannot have been mutated, and could in theory avoid inferring a mutation.

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.expect.md
patch: @@ -0,0 +1,54 @@
+
+## Input
+
+```javascript
+// @flow @validatePreserveExistingMemoizationGuarantees
+import {useMemo} from 'react';
+import {logValue, useFragment, useHook, typedLog} from 'shared-runtime';
+
+component Component() {
+  const data = useFragment();
+
+  const getIsEnabled = () => {
+    if (data != null) {
+      return true;
+    } else {
+      return false;
+    }
+  };
+
+  // We infer that getIsEnabled returns a mutable value, such that
+  // isEnabled is mutable
+  const isEnabled = useMemo(() => getIsEnabled(), [getIsEnabled]);
+
+  // We then infer getLoggingData as capturing that mutable value,
+  // so any calls to this function are then inferred as extending
+  // the mutable range of isEnabled
+  const getLoggingData = () => {
+    return {
+      isEnabled,
+    };
+  };
+
+  // The call here is then inferred as an indirect mutation of isEnabled
+  useHook(getLoggingData());
+
+  return <div onClick={() => typedLog(getLoggingData())} />;
+}
+
+```
+
+
+## Error
+
+```
+  16 |   // We infer that getIsEnabled returns a mutable value, such that
+  17 |   // isEnabled is mutable
+> 18 |   const isEnabled = useMemo(() => getIsEnabled(), [getIsEnabled]);
+     |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. (18:18)
+  19 |
+  20 |   // We then infer getLoggingData as capturing that mutable value,
+  21 |   // so any calls to this function are then inferred as extending
+```
+          
+      
\ No newline at end of file

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.js
patch: @@ -0,0 +1,33 @@
+// @flow @validatePreserveExistingMemoizationGuarantees
+import {useMemo} from 'react';
+import {logValue, useFragment, useHook, typedLog} from 'shared-runtime';
+
+component Component() {
+  const data = useFragment();
+
+  const getIsEnabled = () => {
+    if (data != null) {
+      return true;
+    } else {
+      return false;
+    }
+  };
+
+  // We infer that getIsEnabled returns a mutable value, such that
+  // isEnabled is mutable
+  const isEnabled = useMemo(() => getIsEnabled(), [getIsEnabled]);
+
+  // We then infer getLoggingData as capturing that mutable value,
+  // so any calls to this function are then inferred as extending
+  // the mutable range of isEnabled
+  const getLoggingData = () => {
+    return {
+      isEnabled,
+    };
+  };
+
+  // The call here is then inferred as an indirect mutation of isEnabled
+  useHook(getLoggingData());
+
+  return <div onClick={() => typedLog(getLoggingData())} />;
+}