mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Mutation within a reactively controlled block propagates reactivity
In InferReactivePlaces, we already account for reactively controlled values:
where a value is never assigned a non-reactive value, but _which_ value is
assigned is based on a reactive condition (the test conditions of an if, switch,
loop, etc).
This PR extends that reactively-controlled inference to mutation that is
conditioned upon a reactive value. From the test case:
```javascript
let x = [];
if (props.cond) {
// This mutation has no reactive inputs.
// *But* the mutation conditionally occurs based on props.cond which is reactive
x.push(1);
}
let y = false;
if (x[0]) { // therefore the value observed here is reactive
y = true;
}
// so the value of y here is reactive via the reactive control dependency x[0]
return [y];
```
This commit is contained in:
@@ -97,10 +97,48 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
includeThrowsAsExitNode: false,
|
||||
});
|
||||
const postDominatorFrontierCache = new Map<BlockId, Set<BlockId>>();
|
||||
|
||||
function isReactiveControlledBlock(id: BlockId): boolean {
|
||||
let controlBlocks = postDominatorFrontierCache.get(id);
|
||||
if (controlBlocks === undefined) {
|
||||
controlBlocks = postDominatorFrontier(fn, postDominators, id);
|
||||
postDominatorFrontierCache.set(id, controlBlocks);
|
||||
}
|
||||
for (const blockId of controlBlocks) {
|
||||
const controlBlock = fn.body.blocks.get(blockId)!;
|
||||
switch (controlBlock.terminal.kind) {
|
||||
case "if":
|
||||
case "branch": {
|
||||
if (reactiveIdentifiers.isReactive(controlBlock.terminal.test)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "switch": {
|
||||
if (reactiveIdentifiers.isReactive(controlBlock.terminal.test)) {
|
||||
return true;
|
||||
}
|
||||
for (const case_ of controlBlock.terminal.cases) {
|
||||
if (
|
||||
case_.test !== null &&
|
||||
reactiveIdentifiers.isReactive(case_.test)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasLoop = hasBackEdge(fn);
|
||||
do {
|
||||
const identifierMapping = new Map<Identifier, Identifier>();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
let hasReactiveControl = isReactiveControlledBlock(block.id);
|
||||
|
||||
for (const phi of block.phis) {
|
||||
if (reactiveIdentifiers.isReactiveIdentifier(phi.id)) {
|
||||
// Already marked reactive on a previous pass
|
||||
@@ -116,48 +154,10 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
if (isPhiReactive) {
|
||||
reactiveIdentifiers.markReactiveIdentifier(phi.id);
|
||||
} else {
|
||||
// check to see if it has a reactive control dependency
|
||||
for (const [pred, _operand] of phi.operands) {
|
||||
let controlBlocks = postDominatorFrontierCache.get(pred);
|
||||
if (controlBlocks === undefined) {
|
||||
controlBlocks = postDominatorFrontier(fn, postDominators, pred);
|
||||
postDominatorFrontierCache.set(pred, controlBlocks);
|
||||
}
|
||||
control: for (const blockId of controlBlocks) {
|
||||
const controlBlock = fn.body.blocks.get(blockId)!;
|
||||
switch (controlBlock.terminal.kind) {
|
||||
case "if":
|
||||
case "branch": {
|
||||
if (
|
||||
reactiveIdentifiers.isReactive(controlBlock.terminal.test)
|
||||
) {
|
||||
// control dependency is reactive
|
||||
reactiveIdentifiers.markReactiveIdentifier(phi.id);
|
||||
break control;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "switch": {
|
||||
if (
|
||||
reactiveIdentifiers.isReactive(controlBlock.terminal.test)
|
||||
) {
|
||||
// control dependency is reactive
|
||||
reactiveIdentifiers.markReactiveIdentifier(phi.id);
|
||||
break control;
|
||||
}
|
||||
for (const case_ of controlBlock.terminal.cases) {
|
||||
if (
|
||||
case_.test !== null &&
|
||||
reactiveIdentifiers.isReactive(case_.test)
|
||||
) {
|
||||
// control dependency is reactive
|
||||
reactiveIdentifiers.markReactiveIdentifier(phi.id);
|
||||
break control;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const [pred] of phi.operands) {
|
||||
if (isReactiveControlledBlock(pred)) {
|
||||
reactiveIdentifiers.markReactiveIdentifier(phi.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +197,8 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
}
|
||||
reactiveIdentifiers.markReactive(lvalue);
|
||||
}
|
||||
|
||||
}
|
||||
if (hasReactiveInput || hasReactiveControl) {
|
||||
for (const operand of eachInstructionValueOperand(value)) {
|
||||
switch (operand.effect) {
|
||||
case Effect.Capture:
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
// x is mutated conditionally based on a reactive value,
|
||||
// so it needs to be considered reactive
|
||||
let x = [];
|
||||
if (props.cond) {
|
||||
x.push(1);
|
||||
}
|
||||
// Since x is reactive, y is now reactively controlled too:
|
||||
let y = false;
|
||||
if (x[0]) {
|
||||
y = true;
|
||||
}
|
||||
// Thus this value should be reactive on `y`:
|
||||
return [y];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: true }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(2);
|
||||
|
||||
const x = [];
|
||||
if (props.cond) {
|
||||
x.push(1);
|
||||
}
|
||||
|
||||
let y = false;
|
||||
if (x[0]) {
|
||||
y = true;
|
||||
}
|
||||
let t0;
|
||||
if ($[0] !== y) {
|
||||
t0 = [y];
|
||||
$[0] = y;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: true }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [true]
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
function Component(props) {
|
||||
// x is mutated conditionally based on a reactive value,
|
||||
// so it needs to be considered reactive
|
||||
let x = [];
|
||||
if (props.cond) {
|
||||
x.push(1);
|
||||
}
|
||||
// Since x is reactive, y is now reactively controlled too:
|
||||
let y = false;
|
||||
if (x[0]) {
|
||||
y = true;
|
||||
}
|
||||
// Thus this value should be reactive on `y`:
|
||||
return [y];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: true }],
|
||||
};
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
// x is mutated conditionally based on a reactive value,
|
||||
// so it needs to be considered reactive
|
||||
let x = [];
|
||||
if (props.cond) {
|
||||
x.push(1);
|
||||
}
|
||||
// Since x is reactive, y is now reactively controlled too:
|
||||
let y = false;
|
||||
switch (x[0]) {
|
||||
case 1: {
|
||||
y = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Thus this value should be reactive on `y`:
|
||||
return [y];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: true }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(2);
|
||||
|
||||
const x = [];
|
||||
if (props.cond) {
|
||||
x.push(1);
|
||||
}
|
||||
|
||||
let y = false;
|
||||
switch (x[0]) {
|
||||
case 1: {
|
||||
y = true;
|
||||
}
|
||||
}
|
||||
let t0;
|
||||
if ($[0] !== y) {
|
||||
t0 = [y];
|
||||
$[0] = y;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: true }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [true]
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
function Component(props) {
|
||||
// x is mutated conditionally based on a reactive value,
|
||||
// so it needs to be considered reactive
|
||||
let x = [];
|
||||
if (props.cond) {
|
||||
x.push(1);
|
||||
}
|
||||
// Since x is reactive, y is now reactively controlled too:
|
||||
let y = false;
|
||||
switch (x[0]) {
|
||||
case 1: {
|
||||
y = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Thus this value should be reactive on `y`:
|
||||
return [y];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: true }],
|
||||
};
|
||||
Reference in New Issue
Block a user