Skip to content

Commit

Permalink
[compiler] Repro of missing memoization due to capturing w/o mutation
Browse files Browse the repository at this point in the history
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-poisoned]
  • Loading branch information
josephsavona committed Aug 22, 2024
1 parent f04fbf1 commit a9752f9
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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
```
Original file line number Diff line number Diff line change
@@ -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())} />;
}

0 comments on commit a9752f9

Please sign in to comment.