@samitouri / QOS-React / commits / 8a20fc3b19

[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 +}