mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[hir] todo tests for lambda capture
---
(I'm not sure if these are already known issues. I found them while playing
around with lambda captures. They are also reproducible on main / stable)
I have some limited understanding of lambda captures after reading Sathya's
posts -- please correct if/where this is incorrect
```
function Component() {
// instr1
// instr2
const func3 = function(...) {
// func3instr1
}
}
```
We currently determine effects of captured references in `AnalyzeFunctions`,
before InferReferenceEffects.
- i.e. for some function
1. dependencies of all functions (func3.deps)
2. prefix traversal of all instructions (e.g. instr1, instr2, func3.deps,
func3instr1, ...)
- is this just an implementation decision? i.e. what is stopping us from postfix
traversal in InferReferenceEffects (e.g. instr1, instr2, func3instr1,
func3.deps)
As such, for each captured reference, `AnalyzeFunctions` needs to assign a
reference effect. We currently check `MutableRange`, which seems to miss a few
cases
- We do not model assignments to primitives correctly, since primitives do not
have a mutable range.
- We're not able to model captured (but not mutated) values correctly.
Would it be possible to consolidate `AnalyzeFunctions` into
InferReferenceEffects, using some post-order traversal (iterating over a
function's instructions to collect its dependencies + associated capture
effects)? I definitely don't understand lambdas completely, so please tell me
what I'm missing
This commit is contained in:
+77
@@ -0,0 +1,77 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// Here, element should not be memoized independently of aliasedElement, since
|
||||
// it is captured by fn.
|
||||
// AnalyzeFunctions currently does not find captured objects.
|
||||
// - mutated context refs are declared as `Capture` effect in `FunctionExpression.deps`
|
||||
// - all other context refs are left as Unknown. InferReferenceEffects currently demotes
|
||||
// them to reads
|
||||
function CaptureNotMutate(props) {
|
||||
const idx = foo(props.x);
|
||||
const element = bar(props.el);
|
||||
|
||||
const fn = function () {
|
||||
const arr = { element };
|
||||
return arr[idx];
|
||||
};
|
||||
const aliasedElement = fn();
|
||||
mutate(aliasedElement);
|
||||
return aliasedElement;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// Here, element should not be memoized independently of aliasedElement, since
|
||||
// it is captured by fn.
|
||||
// AnalyzeFunctions currently does not find captured objects.
|
||||
// - mutated context refs are declared as `Capture` effect in `FunctionExpression.deps`
|
||||
// - all other context refs are left as Unknown. InferReferenceEffects currently demotes
|
||||
// them to reads
|
||||
function CaptureNotMutate(props) {
|
||||
const $ = React.unstable_useMemoCache(7);
|
||||
const c_0 = $[0] !== props.x;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
t0 = foo(props.x);
|
||||
$[0] = props.x;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const idx = t0;
|
||||
const c_2 = $[2] !== props.el;
|
||||
let t1;
|
||||
if (c_2) {
|
||||
t1 = bar(props.el);
|
||||
$[2] = props.el;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const element = t1;
|
||||
const c_4 = $[4] !== element;
|
||||
const c_5 = $[5] !== idx;
|
||||
let aliasedElement;
|
||||
if (c_4 || c_5) {
|
||||
const fn = function () {
|
||||
const arr = { element };
|
||||
return arr[idx];
|
||||
};
|
||||
aliasedElement = fn();
|
||||
mutate(aliasedElement);
|
||||
$[4] = element;
|
||||
$[5] = idx;
|
||||
$[6] = aliasedElement;
|
||||
} else {
|
||||
aliasedElement = $[6];
|
||||
}
|
||||
return aliasedElement;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Here, element should not be memoized independently of aliasedElement, since
|
||||
// it is captured by fn.
|
||||
// AnalyzeFunctions currently does not find captured objects.
|
||||
// - mutated context refs are declared as `Capture` effect in `FunctionExpression.deps`
|
||||
// - all other context refs are left as Unknown. InferReferenceEffects currently demotes
|
||||
// them to reads
|
||||
function CaptureNotMutate(props) {
|
||||
const idx = foo(props.x);
|
||||
const element = bar(props.el);
|
||||
|
||||
const fn = function () {
|
||||
const arr = { element };
|
||||
return arr[idx];
|
||||
};
|
||||
const aliasedElement = fn();
|
||||
mutate(aliasedElement);
|
||||
return aliasedElement;
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const x = {};
|
||||
{
|
||||
const x = [];
|
||||
const fn = function () {
|
||||
mutate(x);
|
||||
};
|
||||
fn();
|
||||
}
|
||||
return x; // should return {}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const $ = React.unstable_useMemoCache(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = {};
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const x = t0;
|
||||
|
||||
const x_0 = [];
|
||||
const fn = function () {
|
||||
mutate(x);
|
||||
};
|
||||
fn();
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
function Component() {
|
||||
const x = {};
|
||||
{
|
||||
const x = [];
|
||||
const fn = function () {
|
||||
mutate(x);
|
||||
};
|
||||
fn();
|
||||
}
|
||||
return x; // should return {}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// writing to primitives is not a 'mutate' or 'store' to context references,
|
||||
// under current analysis in AnalyzeFunctions.
|
||||
// <unknown> $23:TFunction = Function @deps[<unknown>
|
||||
// $21:TPrimitive,<unknown> $22:TPrimitive]:
|
||||
|
||||
function Component() {
|
||||
let x = 40;
|
||||
|
||||
const fn = function () {
|
||||
x = x + 1;
|
||||
};
|
||||
fn();
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// writing to primitives is not a 'mutate' or 'store' to context references,
|
||||
// under current analysis in AnalyzeFunctions.
|
||||
// <unknown> $23:TFunction = Function @deps[<unknown>
|
||||
// $21:TPrimitive,<unknown> $22:TPrimitive]:
|
||||
|
||||
function Component() {
|
||||
const fn = function () {
|
||||
x = x + 1;
|
||||
};
|
||||
fn();
|
||||
return 40;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// writing to primitives is not a 'mutate' or 'store' to context references,
|
||||
// under current analysis in AnalyzeFunctions.
|
||||
// <unknown> $23:TFunction = Function @deps[<unknown>
|
||||
// $21:TPrimitive,<unknown> $22:TPrimitive]:
|
||||
|
||||
function Component() {
|
||||
let x = 40;
|
||||
|
||||
const fn = function () {
|
||||
x = x + 1;
|
||||
};
|
||||
fn();
|
||||
return x;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const x = {};
|
||||
{
|
||||
let x = 56;
|
||||
const fn = function () {
|
||||
x = 42;
|
||||
};
|
||||
fn();
|
||||
}
|
||||
return x; // should return {}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const $ = React.unstable_useMemoCache(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = {};
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const x = t0;
|
||||
|
||||
const fn = function () {
|
||||
x = 42;
|
||||
};
|
||||
fn();
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
function Component() {
|
||||
const x = {};
|
||||
{
|
||||
let x = 56;
|
||||
const fn = function () {
|
||||
x = 42;
|
||||
};
|
||||
fn();
|
||||
}
|
||||
return x; // should return {}
|
||||
}
|
||||
Reference in New Issue
Block a user