mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[compiler][rewrite] Patch logic for aligning scopes to non-value blocks
Our previous logic for aligning scopes to block scopes constructs a tree of block and scope nodes. We ensured that blocks always mapped to the same node as their fallthroughs. e.g.
```js
// source
a();
if (...) {
b();
}
c();
// HIR
bb0:
a()
if test=... consequent=bb1 fallthrough=bb2
bb1:
b()
goto bb2
bb2:
c()
// AlignReactiveScopesToBlockScopesHIR nodes
Root node (maps to both bb0 and bb2)
|- bb1
|- ...
```
There are two issues with the existing implementation:
1. Only scopes that overlap with the beginning of a block are aligned correctly. This is because the traversal does not store information about the block-fallthrough pair for scopes that begin *within* the block-fallthrough range.
```
\# This case gets handled correctly
┌──────────────┐
│ │
block start block end
scope start scope end
│ │
└───────────────┘
\# But not this one!
┌──────────────┐
│ │
block start block end
scope start scope end
│ │
└───────────────┘
```
2. Only scopes that are directly used by a block is considered. See the `align-scopes-nested-block-structure` fixture for details.
ghstack-source-id: 327dec5019
Pull Request resolved: https://github.com/facebook/react/pull/29891
This commit is contained in:
+85
-77
@@ -22,8 +22,10 @@ import {
|
||||
mapTerminalSuccessors,
|
||||
terminalFallthrough,
|
||||
} from "../HIR/visitors";
|
||||
import { retainWhere_Set } from "../Utils/utils";
|
||||
import { getPlaceScope } from "./BuildReactiveBlocks";
|
||||
|
||||
type InstructionRange = MutableRange;
|
||||
/*
|
||||
* Note: this is the 2nd of 4 passes that determine how to break a function into discrete
|
||||
* reactive scopes (independently memoizeable units of code):
|
||||
@@ -66,18 +68,20 @@ import { getPlaceScope } from "./BuildReactiveBlocks";
|
||||
* will be the updated end for that scope).
|
||||
*/
|
||||
export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
const blockNodes = new Map<BlockId, BlockNode>();
|
||||
const rootNode: BlockNode = {
|
||||
kind: "node",
|
||||
valueRange: null,
|
||||
children: [],
|
||||
id: makeInstructionId(0),
|
||||
};
|
||||
blockNodes.set(fn.body.entry, rootNode);
|
||||
const activeBlockFallthroughRanges: Array<{
|
||||
range: InstructionRange;
|
||||
fallthrough: BlockId;
|
||||
}> = [];
|
||||
const activeScopes = new Set<ReactiveScope>();
|
||||
const seen = new Set<ReactiveScope>();
|
||||
const valueBlockNodes = new Map<BlockId, ValueBlockNode>();
|
||||
const placeScopes = new Map<Place, ReactiveScope>();
|
||||
|
||||
function recordPlace(id: InstructionId, place: Place, node: BlockNode): void {
|
||||
function recordPlace(
|
||||
id: InstructionId,
|
||||
place: Place,
|
||||
node: ValueBlockNode | null
|
||||
): void {
|
||||
if (place.identifier.scope !== null) {
|
||||
placeScopes.set(place, place.identifier.scope);
|
||||
}
|
||||
@@ -86,13 +90,14 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
node.children.push({ kind: "scope", scope, id });
|
||||
activeScopes.add(scope);
|
||||
node?.children.push({ kind: "scope", scope, id });
|
||||
|
||||
if (seen.has(scope)) {
|
||||
return;
|
||||
}
|
||||
seen.add(scope);
|
||||
if (node.valueRange !== null) {
|
||||
if (node != null && node.valueRange !== null) {
|
||||
scope.range.start = makeInstructionId(
|
||||
Math.min(node.valueRange.start, scope.range.start)
|
||||
);
|
||||
@@ -103,16 +108,25 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
const { instructions, terminal } = block;
|
||||
const node = blockNodes.get(block.id);
|
||||
if (node === undefined) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected a node to be initialized for block`,
|
||||
loc: instructions[0]?.loc ?? terminal.loc,
|
||||
description: `No node for block bb${block.id} (${block.kind})`,
|
||||
});
|
||||
const startingId = block.instructions[0]?.id ?? block.terminal.id;
|
||||
retainWhere_Set(activeScopes, (scope) => scope.range.end > startingId);
|
||||
const top = activeBlockFallthroughRanges.at(-1);
|
||||
if (top?.fallthrough === block.id) {
|
||||
activeBlockFallthroughRanges.pop();
|
||||
/*
|
||||
* All active scopes must have either started before or within the last
|
||||
* block-fallthrough range. In either case, they overlap this block-
|
||||
* fallthrough range and can have their ranges extended.
|
||||
*/
|
||||
for (const scope of activeScopes) {
|
||||
scope.range.start = makeInstructionId(
|
||||
Math.min(scope.range.start, top.range.start)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { instructions, terminal } = block;
|
||||
const node = valueBlockNodes.get(block.id) ?? null;
|
||||
for (const instr of instructions) {
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
recordPlace(instr.id, lvalue, node);
|
||||
@@ -125,36 +139,42 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
recordPlace(terminal.id, operand, node);
|
||||
}
|
||||
|
||||
// Save the current node for the fallback block, where this block scope continues
|
||||
const fallthrough = terminalFallthrough(terminal);
|
||||
if (fallthrough !== null && !blockNodes.has(fallthrough)) {
|
||||
if (fallthrough !== null) {
|
||||
/*
|
||||
* 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.
|
||||
* Any currently active scopes that overlaps the block-fallthrough range
|
||||
* need their range extended to at least the first instruction of the
|
||||
* fallthrough
|
||||
*/
|
||||
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;
|
||||
for (const scope of activeScopes) {
|
||||
if (scope.range.end > terminal.id) {
|
||||
scope.range.end = makeInstructionId(
|
||||
Math.max(scope.range.end, nextId)
|
||||
);
|
||||
}
|
||||
}
|
||||
blockNodes.set(fallthrough, node);
|
||||
/**
|
||||
* We also record the block-fallthrough range for future scopes that begin
|
||||
* within the range (and overlap with the range end).
|
||||
*/
|
||||
activeBlockFallthroughRanges.push({
|
||||
fallthrough,
|
||||
range: {
|
||||
start: terminal.id,
|
||||
end: nextId,
|
||||
},
|
||||
});
|
||||
|
||||
CompilerError.invariant(!valueBlockNodes.has(fallthrough), {
|
||||
reason: "Expect hir blocks to have unique fallthroughs",
|
||||
loc: terminal.loc,
|
||||
});
|
||||
if (node != null) {
|
||||
valueBlockNodes.set(fallthrough, node);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -166,48 +186,35 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
* just those that are direct successors for normal control-flow ordering.
|
||||
*/
|
||||
mapTerminalSuccessors(terminal, (successor) => {
|
||||
if (blockNodes.has(successor)) {
|
||||
if (valueBlockNodes.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 (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);
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
} else if (
|
||||
node.valueRange === null ||
|
||||
node == null ||
|
||||
terminal.kind === "ternary" ||
|
||||
terminal.kind === "logical" ||
|
||||
terminal.kind === "optional"
|
||||
) {
|
||||
/**
|
||||
* Create a new scope node whenever we transition from block scope -> value scope.
|
||||
* Create a new node whenever we transition from non-value -> value block.
|
||||
*
|
||||
* 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
|
||||
* 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
|
||||
let valueRange: MutableRange;
|
||||
if (node == null) {
|
||||
// Transition from block->value block, derive the outer block range
|
||||
CompilerError.invariant(fallthrough !== null, {
|
||||
reason: `Expected a fallthrough for value block`,
|
||||
loc: terminal.loc,
|
||||
@@ -216,32 +223,36 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
const nextId =
|
||||
fallthroughBlock.instructions[0]?.id ??
|
||||
fallthroughBlock.terminal.id;
|
||||
childNode.valueRange = {
|
||||
valueRange = {
|
||||
start: terminal.id,
|
||||
end: nextId,
|
||||
};
|
||||
} else {
|
||||
// else value->value transition, reuse the range
|
||||
childNode.valueRange = node.valueRange;
|
||||
valueRange = node.valueRange;
|
||||
}
|
||||
node.children.push(childNode);
|
||||
blockNodes.set(successor, childNode);
|
||||
const childNode: ValueBlockNode = {
|
||||
kind: "node",
|
||||
id: terminal.id,
|
||||
children: [],
|
||||
valueRange,
|
||||
};
|
||||
node?.children.push(childNode);
|
||||
valueBlockNodes.set(successor, childNode);
|
||||
} else {
|
||||
// this is a value -> value block transition, reuse the node
|
||||
blockNodes.set(successor, node);
|
||||
valueBlockNodes.set(successor, node);
|
||||
}
|
||||
return successor;
|
||||
});
|
||||
}
|
||||
|
||||
// console.log(_debug(rootNode));
|
||||
}
|
||||
|
||||
type BlockNode = {
|
||||
type ValueBlockNode = {
|
||||
kind: "node";
|
||||
id: InstructionId;
|
||||
valueRange: MutableRange | null;
|
||||
children: Array<BlockNode | ReactiveScopeNode>;
|
||||
valueRange: MutableRange;
|
||||
children: Array<ValueBlockNode | ReactiveScopeNode>;
|
||||
};
|
||||
type ReactiveScopeNode = {
|
||||
kind: "scope";
|
||||
@@ -249,13 +260,13 @@ type ReactiveScopeNode = {
|
||||
scope: ReactiveScope;
|
||||
};
|
||||
|
||||
function _debug(node: BlockNode): string {
|
||||
function _debug(node: ValueBlockNode): string {
|
||||
const buf: Array<string> = [];
|
||||
_printNode(node, buf, 0);
|
||||
return buf.join("\n");
|
||||
}
|
||||
function _printNode(
|
||||
node: BlockNode | ReactiveScopeNode,
|
||||
node: ValueBlockNode | ReactiveScopeNode,
|
||||
out: Array<string>,
|
||||
depth: number = 0
|
||||
): void {
|
||||
@@ -265,10 +276,7 @@ function _printNode(
|
||||
`${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}]`
|
||||
: "";
|
||||
let range = ` (range=[${node.valueRange.start}:${node.valueRange.end}])`;
|
||||
out.push(`${prefix}[${node.id}] node${range} [`);
|
||||
for (const child of node.children) {
|
||||
_printNode(child, out, depth + 1);
|
||||
|
||||
@@ -45,6 +45,17 @@ export function retainWhere<T>(
|
||||
array.length = writeIndex;
|
||||
}
|
||||
|
||||
export function retainWhere_Set<T>(
|
||||
items: Set<T>,
|
||||
predicate: (item: T) => boolean
|
||||
): void {
|
||||
for (const item of items) {
|
||||
if (!predicate(item)) {
|
||||
items.delete(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getOrInsertWith<U, V>(
|
||||
m: Map<U, V>,
|
||||
key: U,
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { mutate } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Similar fixture to `align-scopes-nested-block-structure`, but
|
||||
* a simpler case.
|
||||
*/
|
||||
function useFoo(cond) {
|
||||
let s = null;
|
||||
if (cond) {
|
||||
s = {};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
mutate(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [true],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { mutate } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Similar fixture to `align-scopes-nested-block-structure`, but
|
||||
* a simpler case.
|
||||
*/
|
||||
function useFoo(cond) {
|
||||
const $ = _c(3);
|
||||
let s;
|
||||
let t0;
|
||||
if ($[0] !== cond) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
if (cond) {
|
||||
s = {};
|
||||
} else {
|
||||
t0 = null;
|
||||
break bb0;
|
||||
}
|
||||
|
||||
mutate(s);
|
||||
}
|
||||
$[0] = cond;
|
||||
$[1] = t0;
|
||||
$[2] = s;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
s = $[2];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [true],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"wat0":"joe"}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { mutate } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Similar fixture to `align-scopes-nested-block-structure`, but
|
||||
* a simpler case.
|
||||
*/
|
||||
function useFoo(cond) {
|
||||
let s = null;
|
||||
if (cond) {
|
||||
s = {};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
mutate(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [true],
|
||||
};
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { getNull } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const items = (() => {
|
||||
return getNull() ?? [];
|
||||
})();
|
||||
items.push(props.a);
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ a: {} }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { getNull } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
let items;
|
||||
if ($[0] !== props.a) {
|
||||
t0 = getNull() ?? [];
|
||||
items = t0;
|
||||
|
||||
items.push(props.a);
|
||||
$[0] = props.a;
|
||||
$[1] = items;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
items = $[1];
|
||||
t0 = $[2];
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ a: {} }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [{}]
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { mutate } from "shared-runtime";
|
||||
/**
|
||||
* Fixture showing that it's not sufficient to only align direct scoped
|
||||
* accesses of a block-fallthrough pair.
|
||||
* Below is a simplified view of HIR blocks in this fixture.
|
||||
* Note that here, s is mutated in both bb1 and bb4. However, neither
|
||||
* bb1 nor bb4 have terminal fallthroughs or are fallthroughs themselves.
|
||||
*
|
||||
* This means that we need to recursively visit all scopes accessed between
|
||||
* a block and its fallthrough and extend the range of those scopes which overlap
|
||||
* with an active block/fallthrough pair,
|
||||
*
|
||||
* bb0
|
||||
* ┌──────────────┐
|
||||
* │let s = null │
|
||||
* │test cond1 │
|
||||
* │ <fallthr=bb3>│
|
||||
* └┬─────────────┘
|
||||
* │ bb1
|
||||
* ├─►┌───────┐
|
||||
* │ │s = {} ├────┐
|
||||
* │ └───────┘ │
|
||||
* │ bb2 │
|
||||
* └─►┌───────┐ │
|
||||
* │return;│ │
|
||||
* └───────┘ │
|
||||
* bb3 │
|
||||
* ┌──────────────┐◄┘
|
||||
* │test cond2 │
|
||||
* │ <fallthr=bb5>│
|
||||
* └┬─────────────┘
|
||||
* │ bb4
|
||||
* ├─►┌─────────┐
|
||||
* │ │mutate(s)├─┐
|
||||
* ▼ └─────────┘ │
|
||||
* bb5 │
|
||||
* ┌───────────┐ │
|
||||
* │return s; │◄──┘
|
||||
* └───────────┘
|
||||
*/
|
||||
function useFoo({ cond1, cond2 }) {
|
||||
let s = null;
|
||||
if (cond1) {
|
||||
s = {};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cond2) {
|
||||
mutate(s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ cond1: true, cond2: false }],
|
||||
sequentialRenders: [
|
||||
{ cond1: true, cond2: false },
|
||||
{ cond1: true, cond2: false },
|
||||
{ cond1: true, cond2: true },
|
||||
{ cond1: true, cond2: true },
|
||||
{ cond1: false, cond2: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { mutate } from "shared-runtime";
|
||||
/**
|
||||
* Fixture showing that it's not sufficient to only align direct scoped
|
||||
* accesses of a block-fallthrough pair.
|
||||
* Below is a simplified view of HIR blocks in this fixture.
|
||||
* Note that here, s is mutated in both bb1 and bb4. However, neither
|
||||
* bb1 nor bb4 have terminal fallthroughs or are fallthroughs themselves.
|
||||
*
|
||||
* This means that we need to recursively visit all scopes accessed between
|
||||
* a block and its fallthrough and extend the range of those scopes which overlap
|
||||
* with an active block/fallthrough pair,
|
||||
*
|
||||
* bb0
|
||||
* ┌──────────────┐
|
||||
* │let s = null │
|
||||
* │test cond1 │
|
||||
* │ <fallthr=bb3>│
|
||||
* └┬─────────────┘
|
||||
* │ bb1
|
||||
* ├─►┌───────┐
|
||||
* │ │s = {} ├────┐
|
||||
* │ └───────┘ │
|
||||
* │ bb2 │
|
||||
* └─►┌───────┐ │
|
||||
* │return;│ │
|
||||
* └───────┘ │
|
||||
* bb3 │
|
||||
* ┌──────────────┐◄┘
|
||||
* │test cond2 │
|
||||
* │ <fallthr=bb5>│
|
||||
* └┬─────────────┘
|
||||
* │ bb4
|
||||
* ├─►┌─────────┐
|
||||
* │ │mutate(s)├─┐
|
||||
* ▼ └─────────┘ │
|
||||
* bb5 │
|
||||
* ┌───────────┐ │
|
||||
* │return s; │◄──┘
|
||||
* └───────────┘
|
||||
*/
|
||||
function useFoo(t0) {
|
||||
const $ = _c(4);
|
||||
const { cond1, cond2 } = t0;
|
||||
let s;
|
||||
let t1;
|
||||
if ($[0] !== cond1 || $[1] !== cond2) {
|
||||
t1 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
if (cond1) {
|
||||
s = {};
|
||||
} else {
|
||||
t1 = null;
|
||||
break bb0;
|
||||
}
|
||||
if (cond2) {
|
||||
mutate(s);
|
||||
}
|
||||
}
|
||||
$[0] = cond1;
|
||||
$[1] = cond2;
|
||||
$[2] = t1;
|
||||
$[3] = s;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
s = $[3];
|
||||
}
|
||||
if (t1 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t1;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ cond1: true, cond2: false }],
|
||||
sequentialRenders: [
|
||||
{ cond1: true, cond2: false },
|
||||
{ cond1: true, cond2: false },
|
||||
{ cond1: true, cond2: true },
|
||||
{ cond1: true, cond2: true },
|
||||
{ cond1: false, cond2: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {}
|
||||
{}
|
||||
{"wat0":"joe"}
|
||||
{"wat0":"joe"}
|
||||
null
|
||||
+13
-15
@@ -1,7 +1,4 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { mutate } from "shared-runtime";
|
||||
/**
|
||||
* Fixture showing that it's not sufficient to only align direct scoped
|
||||
* accesses of a block-fallthrough pair.
|
||||
@@ -41,7 +38,7 @@
|
||||
* │return s; │◄──┘
|
||||
* └───────────┘
|
||||
*/
|
||||
function useFoo(cond1, cond2) {
|
||||
function useFoo({ cond1, cond2 }) {
|
||||
let s = null;
|
||||
if (cond1) {
|
||||
s = {};
|
||||
@@ -56,13 +53,14 @@ function useFoo(cond1, cond2) {
|
||||
return s;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 4:10(5:15)
|
||||
```
|
||||
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ cond1: true, cond2: false }],
|
||||
sequentialRenders: [
|
||||
{ cond1: true, cond2: false },
|
||||
{ cond1: true, cond2: false },
|
||||
{ cond1: true, cond2: true },
|
||||
{ cond1: true, cond2: true },
|
||||
{ cond1: false, cond2: true },
|
||||
],
|
||||
};
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function useFoo({ cond }) {
|
||||
let items: any = {};
|
||||
b0: {
|
||||
if (cond) {
|
||||
// Mutable range of `items` begins here, but its reactive scope block
|
||||
// should be aligned to above the if-branch
|
||||
items = [];
|
||||
} else {
|
||||
break b0;
|
||||
}
|
||||
items.push(2);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ cond: true }],
|
||||
sequentialRenders: [
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function useFoo(t0) {
|
||||
const $ = _c(3);
|
||||
const { cond } = t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = {};
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
let items = t1;
|
||||
bb0: if ($[1] !== cond) {
|
||||
if (cond) {
|
||||
items = [];
|
||||
} else {
|
||||
break bb0;
|
||||
}
|
||||
|
||||
items.push(2);
|
||||
$[1] = cond;
|
||||
$[2] = items;
|
||||
} else {
|
||||
items = $[2];
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ cond: true }],
|
||||
sequentialRenders: [
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [2]
|
||||
[2]
|
||||
{}
|
||||
{}
|
||||
[2]
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
function useFoo({ cond, value }) {
|
||||
let items;
|
||||
label: {
|
||||
items = [];
|
||||
// Mutable range of `items` begins here, but its reactive scope block
|
||||
// should be aligned to above the label-block
|
||||
if (cond) break label;
|
||||
arrayPush(items, value);
|
||||
}
|
||||
arrayPush(items, value);
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ cond: true, value: 2 }],
|
||||
sequentialRenders: [
|
||||
{ cond: true, value: 2 },
|
||||
{ cond: true, value: 2 },
|
||||
{ cond: true, value: 3 },
|
||||
{ cond: false, value: 3 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(3);
|
||||
const { cond, value } = t0;
|
||||
let items;
|
||||
if ($[0] !== cond || $[1] !== value) {
|
||||
bb0: {
|
||||
items = [];
|
||||
if (cond) {
|
||||
break bb0;
|
||||
}
|
||||
arrayPush(items, value);
|
||||
}
|
||||
|
||||
arrayPush(items, value);
|
||||
$[0] = cond;
|
||||
$[1] = value;
|
||||
$[2] = items;
|
||||
} else {
|
||||
items = $[2];
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ cond: true, value: 2 }],
|
||||
sequentialRenders: [
|
||||
{ cond: true, value: 2 },
|
||||
{ cond: true, value: 2 },
|
||||
{ cond: true, value: 3 },
|
||||
{ cond: false, value: 3 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [2]
|
||||
[2]
|
||||
[3]
|
||||
[3,3]
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { arrayPush, mutate } from "shared-runtime";
|
||||
|
||||
function useFoo({ value }) {
|
||||
let items = null;
|
||||
try {
|
||||
// Mutable range of `items` begins here, but its reactive scope block
|
||||
// should be aligned to above the try-block
|
||||
items = [];
|
||||
arrayPush(items, value);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
mutate(items);
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ value: 2 }],
|
||||
sequentialRenders: [{ value: 2 }, { value: 2 }, { value: 3 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { arrayPush, mutate } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(2);
|
||||
const { value } = t0;
|
||||
let items;
|
||||
if ($[0] !== value) {
|
||||
try {
|
||||
items = [];
|
||||
arrayPush(items, value);
|
||||
} catch {}
|
||||
|
||||
mutate(items);
|
||||
$[0] = value;
|
||||
$[1] = items;
|
||||
} else {
|
||||
items = $[1];
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ value: 2 }],
|
||||
sequentialRenders: [{ value: 2 }, { value: 2 }, { value: 3 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [2]
|
||||
[2]
|
||||
[3]
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { arrayPush } from "shared-runtime";
|
||||
import { arrayPush, mutate } from "shared-runtime";
|
||||
|
||||
function useFoo({ value }) {
|
||||
let items = null;
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { CONST_TRUE, makeObject_Primitives } from "shared-runtime";
|
||||
|
||||
function Foo() {
|
||||
try {
|
||||
let thing = null;
|
||||
if (cond) {
|
||||
thing = makeObject_Primitives();
|
||||
}
|
||||
if (CONST_TRUE) {
|
||||
mutate(thing);
|
||||
}
|
||||
return thing;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { CONST_TRUE, makeObject_Primitives } from "shared-runtime";
|
||||
|
||||
function Foo() {
|
||||
const $ = _c(1);
|
||||
try {
|
||||
let thing;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
thing = null;
|
||||
if (cond) {
|
||||
thing = makeObject_Primitives();
|
||||
}
|
||||
if (CONST_TRUE) {
|
||||
mutate(thing);
|
||||
}
|
||||
$[0] = thing;
|
||||
} else {
|
||||
thing = $[0];
|
||||
}
|
||||
return thing;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { CONST_TRUE, makeObject_Primitives } from "shared-runtime";
|
||||
|
||||
function Foo() {
|
||||
try {
|
||||
let thing = null;
|
||||
if (cond) {
|
||||
thing = makeObject_Primitives();
|
||||
}
|
||||
if (CONST_TRUE) {
|
||||
mutate(thing);
|
||||
}
|
||||
return thing;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Foo() {
|
||||
try {
|
||||
let thing = null;
|
||||
if (cond) {
|
||||
thing = makeObject();
|
||||
}
|
||||
if (otherCond) {
|
||||
mutate(thing);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 2:24(18:26)
|
||||
```
|
||||
|
||||
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
function Foo() {
|
||||
try {
|
||||
let thing = null;
|
||||
if (cond) {
|
||||
thing = makeObject();
|
||||
}
|
||||
if (otherCond) {
|
||||
mutate(thing);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Foo(props, ref) {
|
||||
const value = {};
|
||||
if (cond1) {
|
||||
mutate(value);
|
||||
return <Child ref={ref} />;
|
||||
}
|
||||
mutate(value);
|
||||
if (cond2) {
|
||||
return <Child ref={identity(ref)} />;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 1:21(16:23)
|
||||
```
|
||||
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
function Foo(props, ref) {
|
||||
const value = {};
|
||||
if (cond1) {
|
||||
mutate(value);
|
||||
return <Child ref={ref} />;
|
||||
}
|
||||
mutate(value);
|
||||
if (cond2) {
|
||||
return <Child ref={identity(ref)} />;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Similar fixture to `error.todo-align-scopes-nested-block-structure`, but
|
||||
* a simpler case.
|
||||
*/
|
||||
function useFoo(cond) {
|
||||
let s = null;
|
||||
if (cond) {
|
||||
s = {};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
mutate(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 4:10(5:13)
|
||||
```
|
||||
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Similar fixture to `error.todo-align-scopes-nested-block-structure`, but
|
||||
* a simpler case.
|
||||
*/
|
||||
function useFoo(cond) {
|
||||
let s = null;
|
||||
if (cond) {
|
||||
s = {};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
mutate(s);
|
||||
return s;
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Fixture showing that it's not sufficient to only align direct scoped
|
||||
* accesses of a block-fallthrough pair.
|
||||
* Below is a simplified view of HIR blocks in this fixture.
|
||||
* Note that here, s is mutated in both bb1 and bb4. However, neither
|
||||
* bb1 nor bb4 have terminal fallthroughs or are fallthroughs themselves.
|
||||
*
|
||||
* This means that we need to recursively visit all scopes accessed between
|
||||
* a block and its fallthrough and extend the range of those scopes which overlap
|
||||
* with an active block/fallthrough pair,
|
||||
*
|
||||
* bb0
|
||||
* ┌──────────────┐
|
||||
* │let s = null │
|
||||
* │test cond1 │
|
||||
* │ <fallthr=bb3>│
|
||||
* └┬─────────────┘
|
||||
* │ bb1
|
||||
* ├─►┌───────┐
|
||||
* │ │s = {} ├────┐
|
||||
* │ └───────┘ │
|
||||
* │ bb2 │
|
||||
* └─►┌───────┐ │
|
||||
* │return;│ │
|
||||
* └───────┘ │
|
||||
* bb3 │
|
||||
* ┌──────────────┐◄┘
|
||||
* │test cond2 │
|
||||
* │ <fallthr=bb5>│
|
||||
* └┬─────────────┘
|
||||
* │ bb4
|
||||
* ├─►┌─────────┐
|
||||
* │ │mutate(s)├─┐
|
||||
* ▼ └─────────┘ │
|
||||
* bb5 │
|
||||
* ┌───────────┐ │
|
||||
* │return s; │◄──┘
|
||||
* └───────────┘
|
||||
*/
|
||||
function useFoo(cond1, cond2) {
|
||||
let s = null;
|
||||
if (cond1) {
|
||||
s = {};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cond2) {
|
||||
mutate(s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { getNull } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const items = (() => {
|
||||
return getNull() ?? [];
|
||||
})();
|
||||
items.push(props.a);
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ a: {} }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 2:15(3:21)
|
||||
```
|
||||
|
||||
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function useFoo({ cond }) {
|
||||
let items: any = {};
|
||||
b0: {
|
||||
if (cond) {
|
||||
// Mutable range of `items` begins here, but its reactive scope block
|
||||
// should be aligned to above the if-branch
|
||||
items = [];
|
||||
} else {
|
||||
break b0;
|
||||
}
|
||||
items.push(2);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ cond: true }],
|
||||
sequentialRenders: [
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 6:11(7:15)
|
||||
```
|
||||
|
||||
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
function useFoo({ cond, value }) {
|
||||
let items;
|
||||
label: {
|
||||
items = [];
|
||||
// Mutable range of `items` begins here, but its reactive scope block
|
||||
// should be aligned to above the label-block
|
||||
if (cond) break label;
|
||||
arrayPush(items, value);
|
||||
}
|
||||
arrayPush(items, value);
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ cond: true, value: 2 }],
|
||||
sequentialRenders: [
|
||||
{ cond: true, value: 2 },
|
||||
{ cond: true, value: 2 },
|
||||
{ cond: true, value: 3 },
|
||||
{ cond: false, value: 3 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 3:14(4:18)
|
||||
```
|
||||
|
||||
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
function useFoo({ value }) {
|
||||
let items = null;
|
||||
try {
|
||||
// Mutable range of `items` begins here, but its reactive scope block
|
||||
// should be aligned to above the try-block
|
||||
items = [];
|
||||
arrayPush(items, value);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
mutate(items);
|
||||
return items;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ value: 2 }],
|
||||
sequentialRenders: [{ value: 2 }, { value: 2 }, { value: 3 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 4:19(5:22)
|
||||
```
|
||||
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { Stringify, identity, mutate, CONST_TRUE } from "shared-runtime";
|
||||
|
||||
function Foo(props, ref) {
|
||||
const value = {};
|
||||
if (CONST_TRUE) {
|
||||
mutate(value);
|
||||
return <Stringify ref={ref} />;
|
||||
}
|
||||
mutate(value);
|
||||
if (CONST_TRUE) {
|
||||
return <Stringify ref={identity(ref)} />;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}, { current: "fake-ref-object" }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify, identity, mutate, CONST_TRUE } from "shared-runtime";
|
||||
|
||||
function Foo(props, ref) {
|
||||
const $ = _c(5);
|
||||
let value;
|
||||
let t0;
|
||||
if ($[0] !== ref) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
value = {};
|
||||
if (CONST_TRUE) {
|
||||
mutate(value);
|
||||
t0 = <Stringify ref={ref} />;
|
||||
break bb0;
|
||||
}
|
||||
|
||||
mutate(value);
|
||||
if (CONST_TRUE) {
|
||||
const t1 = identity(ref);
|
||||
let t2;
|
||||
if ($[3] !== t1) {
|
||||
t2 = <Stringify ref={t1} />;
|
||||
$[3] = t1;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
t0 = t2;
|
||||
break bb0;
|
||||
}
|
||||
}
|
||||
$[0] = ref;
|
||||
$[1] = value;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
value = $[1];
|
||||
t0 = $[2];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}, { current: "fake-ref-object" }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"ref":{"current":"fake-ref-object"}}</div>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { Stringify, identity, mutate, CONST_TRUE } from "shared-runtime";
|
||||
|
||||
function Foo(props, ref) {
|
||||
const value = {};
|
||||
if (CONST_TRUE) {
|
||||
mutate(value);
|
||||
return <Stringify ref={ref} />;
|
||||
}
|
||||
mutate(value);
|
||||
if (CONST_TRUE) {
|
||||
return <Stringify ref={identity(ref)} />;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}, { current: "fake-ref-object" }],
|
||||
};
|
||||
Reference in New Issue
Block a user