Skip to content

Commit db9b410

Browse files
committed
[compiler] Don't validate when effect cleanup function depends on effect localized setState state derived values
1 parent f84fda3 commit db9b410

File tree

5 files changed

+215
-2
lines changed

5 files changed

+215
-2
lines changed

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,14 @@ function recordInstructionDerivations(
358358
context.effects.add(effectFunction.loweredFunc.func);
359359
}
360360
} else if (isUseStateType(lvalue.identifier) && value.args.length > 0) {
361-
isSource = true;
362-
typeOfValue = joinValue(typeOfValue, 'fromState');
361+
typeOfValue = 'fromState';
362+
context.derivationCache.addDerivationEntry(
363+
lvalue,
364+
new Set(),
365+
typeOfValue,
366+
true,
367+
);
368+
return;
363369
}
364370
}
365371

@@ -568,6 +574,23 @@ function renderTree(
568574
return result;
569575
}
570576

577+
function getFnGlobalDeps(
578+
fn: FunctionExpression | undefined,
579+
deps: Set<IdentifierId>,
580+
): void {
581+
if (!fn) {
582+
return;
583+
}
584+
585+
for (const [, block] of fn.loweredFunc.func.body.blocks) {
586+
for (const instr of block.instructions) {
587+
if (instr.value.kind === 'LoadLocal') {
588+
deps.add(instr.value.place.identifier.id);
589+
}
590+
}
591+
}
592+
}
593+
571594
function validateEffect(
572595
effectFunction: HIRFunction,
573596
context: ValidationContext,
@@ -586,8 +609,24 @@ function validateEffect(
586609
Set<SourceLocation>
587610
> = new Map();
588611

612+
const cleanUpFunctionDeps: Set<IdentifierId> = new Set();
613+
589614
const globals: Set<IdentifierId> = new Set();
590615
for (const block of effectFunction.body.blocks.values()) {
616+
/*
617+
* if the block is in an effect and is of type return then its an effect's cleanup function
618+
* if the cleanup function depends on a value from which effect-set state is derived then
619+
* we can't validate
620+
*/
621+
if (
622+
block.terminal.kind === 'return' &&
623+
block.terminal.returnVariant === 'Explicit'
624+
) {
625+
getFnGlobalDeps(
626+
context.functions.get(block.terminal.value.identifier.id),
627+
cleanUpFunctionDeps,
628+
);
629+
}
591630
for (const pred of block.preds) {
592631
if (!seenBlocks.has(pred)) {
593632
// skip if block has a back edge
@@ -698,6 +737,12 @@ function validateEffect(
698737
),
699738
);
700739

740+
for (const dep of derivedSetStateCall.sourceIds) {
741+
if (cleanUpFunctionDeps.has(dep)) {
742+
return;
743+
}
744+
}
745+
701746
const propsArr = Array.from(propsSet);
702747
const stateArr = Array.from(stateSet);
703748

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
6+
7+
import {useEffect, useState} from 'react';
8+
9+
function Component(file: File) {
10+
const [imageUrl, setImageUrl] = useState(null);
11+
12+
/*
13+
* Cleaning up the variable or a source of the variable used to setState
14+
* inside the effect communicates that we always need to clean up something
15+
* which is a valid use case for useEffect. In which case we want to
16+
* avoid an throwing
17+
*/
18+
useEffect(() => {
19+
const imageUrlPrepared = URL.createObjectURL(file);
20+
setImageUrl(imageUrlPrepared);
21+
return () => URL.revokeObjectURL(imageUrlPrepared);
22+
}, [file]);
23+
24+
return <Image src={imageUrl} xstyle={styles.imageSizeLimits} />;
25+
}
26+
27+
```
28+
29+
## Code
30+
31+
```javascript
32+
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
33+
34+
import { useEffect, useState } from "react";
35+
36+
function Component(file) {
37+
const $ = _c(5);
38+
const [imageUrl, setImageUrl] = useState(null);
39+
let t0;
40+
let t1;
41+
if ($[0] !== file) {
42+
t0 = () => {
43+
const imageUrlPrepared = URL.createObjectURL(file);
44+
setImageUrl(imageUrlPrepared);
45+
return () => URL.revokeObjectURL(imageUrlPrepared);
46+
};
47+
t1 = [file];
48+
$[0] = file;
49+
$[1] = t0;
50+
$[2] = t1;
51+
} else {
52+
t0 = $[1];
53+
t1 = $[2];
54+
}
55+
useEffect(t0, t1);
56+
let t2;
57+
if ($[3] !== imageUrl) {
58+
t2 = <Image src={imageUrl} xstyle={styles.imageSizeLimits} />;
59+
$[3] = imageUrl;
60+
$[4] = t2;
61+
} else {
62+
t2 = $[4];
63+
}
64+
return t2;
65+
}
66+
67+
```
68+
69+
## Logs
70+
71+
```
72+
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":108},"end":{"line":21,"column":1,"index":700},"filename":"effect-with-cleanup-function-depending-on-derived-computation-value.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
73+
```
74+
75+
### Eval output
76+
(kind: exception) Fixture not implemented
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
2+
3+
import {useEffect, useState} from 'react';
4+
5+
function Component(file: File) {
6+
const [imageUrl, setImageUrl] = useState(null);
7+
8+
/*
9+
* Cleaning up the variable or a source of the variable used to setState
10+
* inside the effect communicates that we always need to clean up something
11+
* which is a valid use case for useEffect. In which case we want to
12+
* avoid an throwing
13+
*/
14+
useEffect(() => {
15+
const imageUrlPrepared = URL.createObjectURL(file);
16+
setImageUrl(imageUrlPrepared);
17+
return () => URL.revokeObjectURL(imageUrlPrepared);
18+
}, [file]);
19+
20+
return <Image src={imageUrl} xstyle={styles.imageSizeLimits} />;
21+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @validateNoDerivedComputationsInEffects_exp
6+
7+
function Component({ prop }) {
8+
const [s, setS] = useState(prop)
9+
const [second, setSecond] = useState(prop)
10+
11+
useEffect(() => {
12+
setS(second)
13+
}, [second])
14+
15+
return <div>{s}</div>
16+
}
17+
18+
```
19+
20+
## Code
21+
22+
```javascript
23+
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp
24+
25+
function Component(t0) {
26+
const $ = _c(5);
27+
const { prop } = t0;
28+
const [s, setS] = useState(prop);
29+
const [second] = useState(prop);
30+
let t1;
31+
let t2;
32+
if ($[0] !== second) {
33+
t1 = () => {
34+
setS(second);
35+
};
36+
t2 = [second];
37+
$[0] = second;
38+
$[1] = t1;
39+
$[2] = t2;
40+
} else {
41+
t1 = $[1];
42+
t2 = $[2];
43+
}
44+
useEffect(t1, t2);
45+
let t3;
46+
if ($[3] !== s) {
47+
t3 = <div>{s}</div>;
48+
$[3] = s;
49+
$[4] = t3;
50+
} else {
51+
t3 = $[4];
52+
}
53+
return t3;
54+
}
55+
56+
```
57+
58+
### Eval output
59+
(kind: exception) Fixture not implemented
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @validateNoDerivedComputationsInEffects_exp
2+
3+
function Component({ prop }) {
4+
const [s, setS] = useState(prop)
5+
const [second, setSecond] = useState(prop)
6+
7+
useEffect(() => {
8+
setS(second)
9+
}, [second])
10+
11+
return <div>{s}</div>
12+
}

0 commit comments

Comments
 (0)