mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
HIR-based MergeOverlappingScopes
ghstack-source-id: 93ac68114683044d7e6332fe7898b0dcc5dc6685 Pull Request resolved: https://github.com/facebook/react-forget/pull/2828
This commit is contained in:
@@ -264,14 +264,14 @@ function* runWithEnvironment(
|
||||
name: "AlignReactiveScopesToBlockScopes",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
}
|
||||
|
||||
mergeOverlappingReactiveScopes(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
name: "MergeOverlappingReactiveScopes",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
mergeOverlappingReactiveScopes(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
name: "MergeOverlappingReactiveScopes",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
}
|
||||
|
||||
buildReactiveBlocks(reactiveFunction);
|
||||
yield log({
|
||||
|
||||
+257
-68
@@ -10,6 +10,7 @@ import {
|
||||
BlockId,
|
||||
HIRFunction,
|
||||
InstructionId,
|
||||
MutableRange,
|
||||
Place,
|
||||
ReactiveScope,
|
||||
makeInstructionId,
|
||||
@@ -21,7 +22,9 @@ import {
|
||||
mapTerminalSuccessors,
|
||||
terminalFallthrough,
|
||||
} from "../HIR/visitors";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import { retainWhere } from "../Utils/utils";
|
||||
import { getPlaceScope } from "./BuildReactiveBlocks";
|
||||
|
||||
/*
|
||||
* Note: this is the 2nd of 4 passes that determine how to break a function into discrete
|
||||
@@ -64,119 +67,149 @@ import { retainWhere } from "../Utils/utils";
|
||||
* finds the first instruction after the scope's mutable range in that same block scope (which
|
||||
* will be the updated end for that scope).
|
||||
*/
|
||||
|
||||
export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
type BlockContext =
|
||||
| { kind: "block"; block: BlockId; scopes: Array<ReactiveScope> }
|
||||
| {
|
||||
kind: "value";
|
||||
start: InstructionId;
|
||||
end: InstructionId;
|
||||
scopes: Array<ReactiveScope>;
|
||||
};
|
||||
const blockContexts = new Map<BlockId, BlockContext>();
|
||||
const blockNodes = new Map<BlockId, BlockNode>();
|
||||
const rootNode: BlockNode = {
|
||||
kind: "node",
|
||||
valueRange: null,
|
||||
children: [],
|
||||
id: makeInstructionId(0),
|
||||
};
|
||||
blockNodes.set(fn.body.entry, rootNode);
|
||||
const seen = new Set<ReactiveScope>();
|
||||
const placeScopes = new Map<Place, ReactiveScope>();
|
||||
|
||||
function recordPlace(place: Place, context: BlockContext): void {
|
||||
const scope = place.identifier.scope;
|
||||
function recordPlace(id: InstructionId, place: Place, node: BlockNode): void {
|
||||
if (place.identifier.scope !== null) {
|
||||
placeScopes.set(place, place.identifier.scope);
|
||||
}
|
||||
|
||||
const scope = getPlaceScope(id, place);
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
node.children.push({ kind: "scope", scope, id });
|
||||
|
||||
if (seen.has(scope)) {
|
||||
return;
|
||||
}
|
||||
if (context.kind === "value") {
|
||||
seen.add(scope);
|
||||
if (node.valueRange !== null) {
|
||||
scope.range.start = makeInstructionId(
|
||||
Math.min(context.start, scope.range.start)
|
||||
Math.min(node.valueRange.start, scope.range.start)
|
||||
);
|
||||
scope.range.end = makeInstructionId(
|
||||
Math.max(context.end, scope.range.end)
|
||||
Math.max(node.valueRange.end, scope.range.end)
|
||||
);
|
||||
}
|
||||
seen.add(scope);
|
||||
context.scopes.push(scope);
|
||||
}
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
const { instructions, terminal } = block;
|
||||
let context = blockContexts.get(block.id);
|
||||
if (context === undefined) {
|
||||
if (block.kind === "block" || block.kind === "catch") {
|
||||
context = { kind: "block", block: block.id, scopes: [] };
|
||||
} else {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected a context to be initialized for value block`,
|
||||
loc: instructions[0]?.loc ?? terminal.loc,
|
||||
description: `No value for block bb${block.id}`,
|
||||
});
|
||||
}
|
||||
} else if (block.kind === "block" && context.kind !== "block") {
|
||||
const node = blockNodes.get(block.id);
|
||||
if (node === undefined) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected a block context for block`,
|
||||
reason: `Expected a node to be initialized for block`,
|
||||
loc: instructions[0]?.loc ?? terminal.loc,
|
||||
description: `Got value block for bb${block.id}`,
|
||||
description: `No node for block bb${block.id} (${block.kind})`,
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Any scopes that carried over across a terminal->fallback need their range extended
|
||||
* to at least the first instruction of the fallback
|
||||
*/
|
||||
const startId = instructions.at(0)?.id ?? terminal.id;
|
||||
for (const scope of context.scopes) {
|
||||
scope.range.end = makeInstructionId(Math.max(scope.range.end, startId));
|
||||
}
|
||||
|
||||
/*
|
||||
* Visit instructions, pruning scopes that end and recording new scopes that appear
|
||||
* on operands
|
||||
*/
|
||||
for (const instr of instructions) {
|
||||
retainWhere(context.scopes, (scope) => scope.range.end > instr.id);
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
recordPlace(lvalue, context);
|
||||
recordPlace(instr.id, lvalue, node);
|
||||
}
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
recordPlace(operand, context);
|
||||
recordPlace(instr.id, operand, node);
|
||||
}
|
||||
}
|
||||
|
||||
// Close scopes that complete at the terminal, and visit scopes of operands
|
||||
retainWhere(context.scopes, (scope) => scope.range.end > terminal.id);
|
||||
for (const operand of eachTerminalOperand(terminal)) {
|
||||
recordPlace(operand, context);
|
||||
recordPlace(terminal.id, operand, node);
|
||||
}
|
||||
|
||||
// Save the current context for the fallback block, where this block scope continues
|
||||
// Save the current node for the fallback block, where this block scope continues
|
||||
const fallthrough = terminalFallthrough(terminal);
|
||||
if (fallthrough !== null && !blockContexts.has(fallthrough)) {
|
||||
blockContexts.set(fallthrough, context);
|
||||
if (fallthrough !== null && !blockNodes.has(fallthrough)) {
|
||||
/*
|
||||
* Any scopes that carried over across a terminal->fallback need their range extended
|
||||
* to at least the first instruction of the fallback
|
||||
*
|
||||
* Note that it's possible for a terminal such as an if or switch to have a null fallback,
|
||||
* indicating that all control-flow paths diverge instead of reaching the fallthrough.
|
||||
* In this case there isn't an instruction id in the program that we can point to for the
|
||||
* updated range. Since the output is correct in this case we leave it, but it would be
|
||||
* more correct to find the maximum instuction id in the whole program and set the range.end
|
||||
* to one greater. Alternatively, we could leave in an unreachable fallthrough (with a new
|
||||
* "unreachable" terminal variant, perhaps) and use that instruction id.
|
||||
*/
|
||||
const fallthroughBlock = fn.body.blocks.get(fallthrough)!;
|
||||
const nextId =
|
||||
fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
|
||||
for (const child of node.children) {
|
||||
if (child.kind !== "scope") {
|
||||
continue;
|
||||
}
|
||||
const scope = child.scope;
|
||||
if (scope.range.end > terminal.id) {
|
||||
scope.range.end = makeInstructionId(
|
||||
Math.max(scope.range.end, nextId)
|
||||
);
|
||||
}
|
||||
}
|
||||
blockNodes.set(fallthrough, node);
|
||||
}
|
||||
|
||||
/*
|
||||
* Visit all successors (not just direct successors for control-flow ordering) to
|
||||
* set a value block context where necessary to align the value block start/end
|
||||
* set a value block node where necessary to align the value block start/end
|
||||
* back to the outer block scope.
|
||||
*
|
||||
* TODO: add a variant of eachTerminalSuccessor() that visits _all_ successors, not
|
||||
* just those that are direct successors for normal control-flow ordering.
|
||||
*/
|
||||
mapTerminalSuccessors(terminal, (successor) => {
|
||||
if (blockNodes.has(successor)) {
|
||||
return successor;
|
||||
}
|
||||
|
||||
const successorBlock = fn.body.blocks.get(successor)!;
|
||||
/*
|
||||
* we need the block kind check here because the do..while terminal's successor
|
||||
* is a block, and try's successor is a catch block
|
||||
*/
|
||||
if (
|
||||
!blockContexts.has(successor) &&
|
||||
successorBlock.kind !== "block" &&
|
||||
successorBlock.kind !== "catch"
|
||||
if (successorBlock.kind === "block" || successorBlock.kind === "catch") {
|
||||
const childNode: BlockNode = {
|
||||
kind: "node",
|
||||
id: terminal.id,
|
||||
children: [],
|
||||
valueRange: null,
|
||||
};
|
||||
node.children.push(childNode);
|
||||
blockNodes.set(successor, childNode);
|
||||
} else if (
|
||||
node.valueRange === null ||
|
||||
terminal.kind === "ternary" ||
|
||||
terminal.kind === "logical" ||
|
||||
terminal.kind === "optional"
|
||||
) {
|
||||
let valueContext: BlockContext;
|
||||
if (context!.kind === "value") {
|
||||
valueContext = context!;
|
||||
} else {
|
||||
/**
|
||||
* Create a new scope node whenever we transition from block scope -> value scope.
|
||||
*
|
||||
* For compatibility with the previous ReactiveFunction-based scope merging logic,
|
||||
* we also create new scope nodes for ternary, logical, and optional terminals.
|
||||
* However, inside value blocks we always store a range (valueRange) that is the
|
||||
* start/end instruction ids at the nearest parent block scope level, so that
|
||||
* scopes inside the value blocks can be extended to align with block scope
|
||||
* instructions.
|
||||
*/
|
||||
const childNode = {
|
||||
kind: "node",
|
||||
id: terminal.id,
|
||||
children: [],
|
||||
valueRange: null,
|
||||
} as BlockNode;
|
||||
if (node.valueRange === null) {
|
||||
// Transition from block->value scope, derive the outer block scope range
|
||||
CompilerError.invariant(fallthrough !== null, {
|
||||
reason: `Expected a fallthrough for value block`,
|
||||
loc: terminal.loc,
|
||||
@@ -185,16 +218,172 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
const nextId =
|
||||
fallthroughBlock.instructions[0]?.id ??
|
||||
fallthroughBlock.terminal.id;
|
||||
valueContext = {
|
||||
kind: "value",
|
||||
childNode.valueRange = {
|
||||
start: terminal.id,
|
||||
end: nextId,
|
||||
scopes: [],
|
||||
} as BlockContext;
|
||||
};
|
||||
} else {
|
||||
// else value->value transition, reuse the range
|
||||
childNode.valueRange = node.valueRange;
|
||||
}
|
||||
blockContexts.set(successor, valueContext);
|
||||
node.children.push(childNode);
|
||||
blockNodes.set(successor, childNode);
|
||||
} else {
|
||||
// this is a value -> value block transition, reuse the node
|
||||
blockNodes.set(successor, node);
|
||||
}
|
||||
return successor;
|
||||
});
|
||||
}
|
||||
|
||||
// console.log(_debug(rootNode));
|
||||
|
||||
const joinedScopes: DisjointSet<ReactiveScope> =
|
||||
mergeOverlappingScopes(rootNode);
|
||||
|
||||
joinedScopes.forEach((scope, groupScope) => {
|
||||
if (scope !== groupScope) {
|
||||
groupScope.range.start = makeInstructionId(
|
||||
Math.min(groupScope.range.start, scope.range.start)
|
||||
);
|
||||
groupScope.range.end = makeInstructionId(
|
||||
Math.max(groupScope.range.end, scope.range.end)
|
||||
);
|
||||
}
|
||||
});
|
||||
for (const [place, originalScope] of placeScopes) {
|
||||
const nextScope = joinedScopes.find(originalScope);
|
||||
if (nextScope !== null && nextScope !== originalScope) {
|
||||
place.identifier.scope = nextScope;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type BlockNode = {
|
||||
kind: "node";
|
||||
id: InstructionId;
|
||||
valueRange: MutableRange | null;
|
||||
children: Array<BlockNode | ReactiveScopeNode>;
|
||||
};
|
||||
type ReactiveScopeNode = {
|
||||
kind: "scope";
|
||||
id: InstructionId;
|
||||
scope: ReactiveScope;
|
||||
};
|
||||
|
||||
function _debug(node: BlockNode): string {
|
||||
const buf: Array<string> = [];
|
||||
_printNode(node, buf, 0);
|
||||
return buf.join("\n");
|
||||
}
|
||||
function _printNode(
|
||||
node: BlockNode | ReactiveScopeNode,
|
||||
out: Array<string>,
|
||||
depth: number = 0
|
||||
): void {
|
||||
let prefix = " ".repeat(depth);
|
||||
if (node.kind === "scope") {
|
||||
out.push(
|
||||
`${prefix}[${node.id}] @${node.scope.id} [${node.scope.range.start}:${node.scope.range.end}]`
|
||||
);
|
||||
} else {
|
||||
let range =
|
||||
node.valueRange !== null
|
||||
? ` [${node.valueRange.start}:${node.valueRange.end}]`
|
||||
: "";
|
||||
out.push(`${prefix}[${node.id}] node${range} [`);
|
||||
for (const child of node.children) {
|
||||
_printNode(child, out, depth + 1);
|
||||
}
|
||||
out.push(`${prefix}]`);
|
||||
}
|
||||
}
|
||||
|
||||
type ScopeItem = {
|
||||
scope: ReactiveScope;
|
||||
shadowedBy: ReactiveScope | null;
|
||||
};
|
||||
class BlockItem {
|
||||
seen: Set<ReactiveScope> = new Set();
|
||||
scopes: Array<ScopeItem> = [];
|
||||
}
|
||||
|
||||
function mergeOverlappingScopes(root: BlockNode): DisjointSet<ReactiveScope> {
|
||||
const seen = new Set<ReactiveScope>();
|
||||
const joined = new DisjointSet<ReactiveScope>();
|
||||
|
||||
function visit(node: BlockNode, stack: Array<BlockItem>): void {
|
||||
const currentBlock = stack.at(-1)!;
|
||||
child: for (const child of node.children) {
|
||||
retainWhere(currentBlock.scopes, (item) => {
|
||||
if (item.scope.range.end > child.id) {
|
||||
return true;
|
||||
} else {
|
||||
currentBlock.seen.delete(item.scope);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (child.kind === "node") {
|
||||
visit(child, [...stack, new BlockItem()]);
|
||||
} else {
|
||||
const scope = child.scope;
|
||||
if (!seen.has(scope)) {
|
||||
seen.add(scope);
|
||||
currentBlock.seen.add(scope);
|
||||
currentBlock.scopes.push({ shadowedBy: null, scope });
|
||||
continue;
|
||||
}
|
||||
|
||||
let index = stack.length - 1;
|
||||
let nextBlock = currentBlock;
|
||||
while (!nextBlock.seen.has(scope)) {
|
||||
joined.union([scope, ...nextBlock.scopes.map((s) => s.scope)]);
|
||||
index--;
|
||||
if (index < 0) {
|
||||
currentBlock.seen.add(scope);
|
||||
currentBlock.scopes.push({ shadowedBy: null, scope });
|
||||
continue child;
|
||||
}
|
||||
nextBlock = stack[index]!;
|
||||
}
|
||||
|
||||
// Handle interleaving within a given block scope
|
||||
let found = false;
|
||||
for (let i = 0; i < nextBlock.scopes.length; i++) {
|
||||
const current = nextBlock.scopes[i]!;
|
||||
if (current.scope.id === scope.id) {
|
||||
found = true;
|
||||
if (current.shadowedBy !== null) {
|
||||
joined.union([current.shadowedBy, current.scope]);
|
||||
}
|
||||
} else if (found && current.shadowedBy === null) {
|
||||
// `scope` is shadowing `current` and may interleave
|
||||
current.shadowedBy = scope;
|
||||
if (current.scope.range.end > scope.range.end) {
|
||||
/*
|
||||
* Current is shadowed by `scope`, and we know that `current` will mutate
|
||||
* again (per its range), so the scopes are already known to interleave.
|
||||
*
|
||||
* Eagerly extend the ranges of the scopes so that we don't prematurely end
|
||||
* a scope relative to its eventual post-merge mutable range
|
||||
*/
|
||||
const end = makeInstructionId(
|
||||
Math.max(current.scope.range.end, scope.range.end)
|
||||
);
|
||||
current.scope.range.end = end;
|
||||
scope.range.end = end;
|
||||
joined.union([current.scope, scope]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!currentBlock.seen.has(scope)) {
|
||||
currentBlock.seen.add(scope);
|
||||
currentBlock.scopes.push({ shadowedBy: null, scope });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visit(root, [new BlockItem()]);
|
||||
return joined;
|
||||
}
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { makeObject_Primitives } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const object = makeObject_Primitives();
|
||||
if (props.cond) {
|
||||
object.value = 1;
|
||||
return object;
|
||||
} else {
|
||||
object.value = props.value;
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false, value: [0, 1, 2] }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { makeObject_Primitives } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(2);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb9: {
|
||||
const object = makeObject_Primitives();
|
||||
if (props.cond) {
|
||||
object.value = 1;
|
||||
t0 = object;
|
||||
break bb9;
|
||||
} else {
|
||||
object.value = props.value;
|
||||
t0 = object;
|
||||
break bb9;
|
||||
}
|
||||
}
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false, value: [0, 1, 2] }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"a":0,"b":"value1","c":true,"value":[0,1,2]}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { makeObject_Primitives } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const object = makeObject_Primitives();
|
||||
if (props.cond) {
|
||||
object.value = 1;
|
||||
return object;
|
||||
} else {
|
||||
object.value = props.value;
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false, value: [0, 1, 2] }],
|
||||
};
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { useState } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const items = props.items ? props.items.slice() : [];
|
||||
const [state] = useState("");
|
||||
return props.cond ? (
|
||||
<div>{state}</div>
|
||||
) : (
|
||||
<div>
|
||||
{items.map((item) => (
|
||||
<div key={item.id}>{item.name}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false, items: [{ id: 0, name: "Alice" }] }],
|
||||
sequentialRenders: [
|
||||
{ cond: false, items: [{ id: 0, name: "Alice" }] },
|
||||
{
|
||||
cond: false,
|
||||
items: [
|
||||
{ id: 0, name: "Alice" },
|
||||
{ id: 1, name: "Bob" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cond: true,
|
||||
items: [
|
||||
{ id: 0, name: "Alice" },
|
||||
{ id: 1, name: "Bob" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cond: false,
|
||||
items: [
|
||||
{ id: 1, name: "Bob" },
|
||||
{ id: 2, name: "Claire" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { useState } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const items = props.items ? props.items.slice() : [];
|
||||
const [state] = useState("");
|
||||
return props.cond ? (
|
||||
<div>{state}</div>
|
||||
) : (
|
||||
<div>
|
||||
{items.map((item) => (
|
||||
<div key={item.id}>{item.name}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false, items: [{ id: 0, name: "Alice" }] }],
|
||||
sequentialRenders: [
|
||||
{ cond: false, items: [{ id: 0, name: "Alice" }] },
|
||||
{
|
||||
cond: false,
|
||||
items: [
|
||||
{ id: 0, name: "Alice" },
|
||||
{ id: 1, name: "Bob" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cond: true,
|
||||
items: [
|
||||
{ id: 0, name: "Alice" },
|
||||
{ id: 1, name: "Bob" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cond: false,
|
||||
items: [
|
||||
{ id: 1, name: "Bob" },
|
||||
{ id: 2, name: "Claire" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div><div>Alice</div></div>
|
||||
<div><div>Alice</div><div>Bob</div></div>
|
||||
<div></div>
|
||||
<div><div>Bob</div><div>Claire</div></div>
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { useState } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const items = props.items ? props.items.slice() : [];
|
||||
const [state] = useState("");
|
||||
return props.cond ? (
|
||||
<div>{state}</div>
|
||||
) : (
|
||||
<div>
|
||||
{items.map((item) => (
|
||||
<div key={item.id}>{item.name}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false, items: [{ id: 0, name: "Alice" }] }],
|
||||
sequentialRenders: [
|
||||
{ cond: false, items: [{ id: 0, name: "Alice" }] },
|
||||
{
|
||||
cond: false,
|
||||
items: [
|
||||
{ id: 0, name: "Alice" },
|
||||
{ id: 1, name: "Bob" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cond: true,
|
||||
items: [
|
||||
{ id: 0, name: "Alice" },
|
||||
{ id: 1, name: "Bob" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cond: false,
|
||||
items: [
|
||||
{ id: 1, name: "Bob" },
|
||||
{ id: 2, name: "Claire" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
const DISPLAY = true;
|
||||
function Component({ cond = false, id }) {
|
||||
return (
|
||||
<>
|
||||
<div className={identity(styles.a, id !== null ? styles.b : {})}></div>
|
||||
|
||||
{cond === false && (
|
||||
<div className={identity(styles.c, DISPLAY ? styles.d : {})} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false, id: 42 }],
|
||||
sequentialRenders: [
|
||||
{ cond: false, id: 4 },
|
||||
{ cond: true, id: 4 },
|
||||
{ cond: true, id: 42 },
|
||||
],
|
||||
};
|
||||
|
||||
const styles = {
|
||||
a: "a",
|
||||
b: "b",
|
||||
c: "c",
|
||||
d: "d",
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
const DISPLAY = true;
|
||||
function Component(t0) {
|
||||
const $ = useMemoCache(9);
|
||||
const { cond: t1, id } = t0;
|
||||
const cond = t1 === undefined ? false : t1;
|
||||
let t2;
|
||||
if ($[0] !== id) {
|
||||
t2 = identity(styles.a, id !== null ? styles.b : {});
|
||||
$[0] = id;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
}
|
||||
let t3;
|
||||
if ($[2] !== t2) {
|
||||
t3 = <div className={t2} />;
|
||||
$[2] = t2;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
let t4;
|
||||
if ($[4] !== cond) {
|
||||
t4 = cond === false && (
|
||||
<div className={identity(styles.c, DISPLAY ? styles.d : {})} />
|
||||
);
|
||||
$[4] = cond;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
let t5;
|
||||
if ($[6] !== t3 || $[7] !== t4) {
|
||||
t5 = (
|
||||
<>
|
||||
{t3}
|
||||
{t4}
|
||||
</>
|
||||
);
|
||||
$[6] = t3;
|
||||
$[7] = t4;
|
||||
$[8] = t5;
|
||||
} else {
|
||||
t5 = $[8];
|
||||
}
|
||||
return t5;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false, id: 42 }],
|
||||
sequentialRenders: [
|
||||
{ cond: false, id: 4 },
|
||||
{ cond: true, id: 4 },
|
||||
{ cond: true, id: 42 },
|
||||
],
|
||||
};
|
||||
|
||||
const styles = {
|
||||
a: "a",
|
||||
b: "b",
|
||||
c: "c",
|
||||
d: "d",
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div class="a"></div><div class="c"></div>
|
||||
<div class="a"></div>
|
||||
<div class="a"></div>
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
const DISPLAY = true;
|
||||
function Component({ cond = false, id }) {
|
||||
return (
|
||||
<>
|
||||
<div className={identity(styles.a, id !== null ? styles.b : {})}></div>
|
||||
|
||||
{cond === false && (
|
||||
<div className={identity(styles.c, DISPLAY ? styles.d : {})} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false, id: 42 }],
|
||||
sequentialRenders: [
|
||||
{ cond: false, id: 4 },
|
||||
{ cond: true, id: 4 },
|
||||
{ cond: true, id: 42 },
|
||||
],
|
||||
};
|
||||
|
||||
const styles = {
|
||||
a: "a",
|
||||
b: "b",
|
||||
c: "c",
|
||||
d: "d",
|
||||
};
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import fbt from "fbt";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const label = fbt(
|
||||
fbt.plural("bar", props.value.length, {
|
||||
many: "bars",
|
||||
showCount: "yes",
|
||||
}),
|
||||
"The label text"
|
||||
);
|
||||
return props.cond ? (
|
||||
<Stringify
|
||||
description={<fbt desc="Some text">Text here</fbt>}
|
||||
label={label.toString()}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: true, value: [0, 1, 2] }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
import fbt from "fbt";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(3);
|
||||
let t0;
|
||||
if ($[0] !== props.value.length || $[1] !== props.cond) {
|
||||
const label = fbt._(
|
||||
{ "*": "{number} bars", _1: "1 bar" },
|
||||
[fbt._plural(props.value.length, "number")],
|
||||
{ hk: "4mUen7" }
|
||||
);
|
||||
|
||||
t0 = props.cond ? (
|
||||
<Stringify
|
||||
description={fbt._("Text here", null, { hk: "21YpZs" })}
|
||||
label={label.toString()}
|
||||
/>
|
||||
) : null;
|
||||
$[0] = props.value.length;
|
||||
$[1] = props.cond;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: true, value: [0, 1, 2] }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"description":"Text here","label":"3 bars"}</div>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import fbt from "fbt";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const label = fbt(
|
||||
fbt.plural("bar", props.value.length, {
|
||||
many: "bars",
|
||||
showCount: "yes",
|
||||
}),
|
||||
"The label text"
|
||||
);
|
||||
return props.cond ? (
|
||||
<Stringify
|
||||
description={<fbt desc="Some text">Text here</fbt>}
|
||||
label={label.toString()}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: true, value: [0, 1, 2] }],
|
||||
};
|
||||
Reference in New Issue
Block a user