[compiler] Repro of missing memoization due to capturing w/o mutation
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. ghstack-source-id: 47664e48ce8c51a6edf4d714d1acd1ec4781df80 Pull Request resolved: https://github.com/facebook/react/pull/30783
Joe Savona committed
Aug 21, 2024 at 17:31 UTC
8a20fc3b19b600a3b8666203f1877230c62becf9
2 files changed
+87
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
new
+54
@@ -0,0 +1,54 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @flow @validatePreserveExistingMemoizationGuarantees
6
+import {useMemo} from 'react';
7
+import {logValue, useFragment, useHook, typedLog} from 'shared-runtime';
8
+
9
+component Component() {
10
+ const data = useFragment();
11
+
12
+ const getIsEnabled = () => {
13
+ if (data != null) {
14
+ return true;
15
+ } else {
16
+ return false;
17
+ }
18
+ };
19
+
20
+ // We infer that getIsEnabled returns a mutable value, such that
21
+ // isEnabled is mutable
22
+ const isEnabled = useMemo(() => getIsEnabled(), [getIsEnabled]);
23
+
24
+ // We then infer getLoggingData as capturing that mutable value,
25
+ // so any calls to this function are then inferred as extending
26
+ // the mutable range of isEnabled
27
+ const getLoggingData = () => {
28
+ return {
29
+ isEnabled,
30
+ };
31
+ };
32
+
33
+ // The call here is then inferred as an indirect mutation of isEnabled
34
+ useHook(getLoggingData());
35
+
36
+ return <div onClick={() => typedLog(getLoggingData())} />;
37
+}
38
+
39
+```
40
+
41
+
42
+## Error
43
+
44
+```
45
+ 16 | // We infer that getIsEnabled returns a mutable value, such that
46
+ 17 | // isEnabled is mutable
47
+> 18 | const isEnabled = useMemo(() => getIsEnabled(), [getIsEnabled]);
48
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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)
49
+ 19 |
50
+ 20 | // We then infer getLoggingData as capturing that mutable value,
51
+ 21 | // so any calls to this function are then inferred as extending
52
+```
53
+
54
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.js
new
+33
@@ -0,0 +1,33 @@
1
+// @flow @validatePreserveExistingMemoizationGuarantees
2
+import {useMemo} from 'react';
3
+import {logValue, useFragment, useHook, typedLog} from 'shared-runtime';
4
+
5
+component Component() {
6
+ const data = useFragment();
7
+
8
+ const getIsEnabled = () => {
9
+ if (data != null) {
10
+ return true;
11
+ } else {
12
+ return false;
13
+ }
14
+ };
15
+
16
+ // We infer that getIsEnabled returns a mutable value, such that
17
+ // isEnabled is mutable
18
+ const isEnabled = useMemo(() => getIsEnabled(), [getIsEnabled]);
19
+
20
+ // We then infer getLoggingData as capturing that mutable value,
21
+ // so any calls to this function are then inferred as extending
22
+ // the mutable range of isEnabled
23
+ const getLoggingData = () => {
24
+ return {
25
+ isEnabled,
26
+ };
27
+ };
28
+
29
+ // The call here is then inferred as an indirect mutation of isEnabled
30
+ useHook(getLoggingData());
31
+
32
+ return <div onClick={() => typedLog(getLoggingData())} />;
33
+}