Fix destructuring with mixed local/scope declarations

Fixes T176436488. The logic for rewriting Destructure instructions was correct, 
but the visitor implementation was accidentally dropping subsequent Destructure 
instructions within a block after encountering one that needed a rewrite. 
Switching to use the transform infra (added after this pass was written) fixes 
it.
This commit is contained in:
Joe Savona
2024-02-05 22:29:53 -08:00
parent 0277bc4d54
commit af6e837b60
5 changed files with 352 additions and 34 deletions
@@ -11,13 +11,17 @@ import {
IdentifierId,
InstructionKind,
Place,
ReactiveBlock,
ReactiveFunction,
ReactiveInstruction,
ReactiveScopeBlock,
ReactiveStatement,
} from "../HIR";
import { eachPatternOperand, mapPatternOperands } from "../HIR/visitors";
import { ReactiveFunctionTransform, visitReactiveFunction } from "./visitors";
import {
ReactiveFunctionTransform,
Transformed,
visitReactiveFunction,
} from "./visitors";
/*
* Destructuring statements may sometimes define some variables which are declared by the scope,
@@ -92,41 +96,29 @@ class Visitor extends ReactiveFunctionTransform<State> {
this.traverseScope(scope, state);
}
override visitBlock(block: ReactiveBlock, state: State): void {
// Traverse first to transform inner items
this.traverseBlock(block, state);
override transformInstruction(
instruction: ReactiveInstruction,
state: State
): Transformed<ReactiveStatement> {
this.visitInstruction(instruction, state);
// Then transform any mixed destructuring instructions
let nextBlock: ReactiveBlock | null = null;
for (let i = 0; i < block.length; i++) {
const instr = block[i];
if (
instr.kind === "instruction" &&
instr.instruction.value.kind === "Destructure"
) {
const transformed = transformDestructuring(
state,
instr.instruction,
instr.instruction.value
);
if (transformed) {
nextBlock ??= block.slice(0, i);
transformed.forEach((instruction) => {
nextBlock?.push({
kind: "instruction",
instruction,
});
});
continue;
}
} else if (nextBlock !== null) {
nextBlock.push(instr);
if (instruction.value.kind === "Destructure") {
const transformed = transformDestructuring(
state,
instruction,
instruction.value
);
if (transformed) {
return {
kind: "replace-many",
value: transformed.map((instruction) => ({
kind: "instruction",
instruction,
})),
};
}
}
if (nextBlock !== null) {
block.length = 0;
block.push(...nextBlock);
}
return { kind: "keep" };
}
}
@@ -0,0 +1,129 @@
## Input
```javascript
import { identity } from "shared-runtime";
function Component(statusName) {
const { status, text } = foo(statusName);
const { bg, color } = getStyles(status);
return (
<div className={identity(bg)}>
<span className={identity(color)}>{[text]}</span>
</div>
);
}
function foo(name) {
return {
status: `<status>`,
text: `${name}!`,
};
}
function getStyles(status) {
return {
bg: "#eee8d5",
color: "#657b83",
};
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: ["Mofei"],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { identity } from "shared-runtime";
function Component(statusName) {
const $ = useMemoCache(12);
let text;
let t0;
let t1;
if ($[0] !== statusName) {
const { status, text: t47 } = foo(statusName);
text = t47;
const { bg, color } = getStyles(status);
t1 = identity(bg);
t0 = identity(color);
$[0] = statusName;
$[1] = text;
$[2] = t0;
$[3] = t1;
} else {
text = $[1];
t0 = $[2];
t1 = $[3];
}
let t2;
if ($[4] !== text) {
t2 = [text];
$[4] = text;
$[5] = t2;
} else {
t2 = $[5];
}
let t3;
if ($[6] !== t0 || $[7] !== t2) {
t3 = <span className={t0}>{t2}</span>;
$[6] = t0;
$[7] = t2;
$[8] = t3;
} else {
t3 = $[8];
}
let t4;
if ($[9] !== t1 || $[10] !== t3) {
t4 = <div className={t1}>{t3}</div>;
$[9] = t1;
$[10] = t3;
$[11] = t4;
} else {
t4 = $[11];
}
return t4;
}
function foo(name) {
const $ = useMemoCache(2);
const t0 = `${name}!`;
let t1;
if ($[0] !== t0) {
t1 = { status: `<status>`, text: t0 };
$[0] = t0;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
function getStyles(status) {
const $ = useMemoCache(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = { bg: "#eee8d5", color: "#657b83" };
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: ["Mofei"],
};
```
### Eval output
(kind: ok) <div class="#eee8d5"><span class="#657b83">Mofei!</span></div>
@@ -0,0 +1,30 @@
import { identity } from "shared-runtime";
function Component(statusName) {
const { status, text } = foo(statusName);
const { bg, color } = getStyles(status);
return (
<div className={identity(bg)}>
<span className={identity(color)}>{[text]}</span>
</div>
);
}
function foo(name) {
return {
status: `<status>`,
text: `${name}!`,
};
}
function getStyles(status) {
return {
bg: "#eee8d5",
color: "#657b83",
};
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: ["Mofei"],
};
@@ -0,0 +1,134 @@
## Input
```javascript
import { identity } from "shared-runtime";
function Component(statusName) {
// status is local, text is a scope declaration
const { status, text } = foo(statusName);
// color is local, font is a scope declaration
const { color, font } = getStyles(status);
// bg is a declaration
const bg = identity(color);
return (
<div className={bg}>
<span className={font}>{[text]}</span>
</div>
);
}
function foo(name) {
return {
status: `<status>`,
text: `${name}!`,
};
}
function getStyles(status) {
return {
font: "comic-sans",
color: "#657b83",
};
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: ["Sathya"],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { identity } from "shared-runtime";
function Component(statusName) {
const $ = useMemoCache(12);
let t0;
let text;
let font;
if ($[0] !== statusName) {
const { status, text: t49 } = foo(statusName);
text = t49;
const { color, font: t50 } = getStyles(status);
font = t50;
t0 = identity(color);
$[0] = statusName;
$[1] = t0;
$[2] = text;
$[3] = font;
} else {
t0 = $[1];
text = $[2];
font = $[3];
}
const bg = t0;
let t1;
if ($[4] !== text) {
t1 = [text];
$[4] = text;
$[5] = t1;
} else {
t1 = $[5];
}
let t2;
if ($[6] !== font || $[7] !== t1) {
t2 = <span className={font}>{t1}</span>;
$[6] = font;
$[7] = t1;
$[8] = t2;
} else {
t2 = $[8];
}
let t3;
if ($[9] !== bg || $[10] !== t2) {
t3 = <div className={bg}>{t2}</div>;
$[9] = bg;
$[10] = t2;
$[11] = t3;
} else {
t3 = $[11];
}
return t3;
}
function foo(name) {
const $ = useMemoCache(2);
const t0 = `${name}!`;
let t1;
if ($[0] !== t0) {
t1 = { status: `<status>`, text: t0 };
$[0] = t0;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
function getStyles(status) {
const $ = useMemoCache(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = { font: "comic-sans", color: "#657b83" };
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: ["Sathya"],
};
```
### Eval output
(kind: ok) <div class="#657b83"><span class="comic-sans">Sathya!</span></div>
@@ -0,0 +1,33 @@
import { identity } from "shared-runtime";
function Component(statusName) {
// status is local, text is a scope declaration
const { status, text } = foo(statusName);
// color is local, font is a scope declaration
const { color, font } = getStyles(status);
// bg is a declaration
const bg = identity(color);
return (
<div className={bg}>
<span className={font}>{[text]}</span>
</div>
);
}
function foo(name) {
return {
status: `<status>`,
text: `${name}!`,
};
}
function getStyles(status) {
return {
font: "comic-sans",
color: "#657b83",
};
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: ["Sathya"],
};