mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[hir] Re-implement mergeOverlappingReactiveScopes (+ bugfix)
ghstack-source-id: 06d49edffe5ae3c31eb6ef642078752c056c617c Pull Request resolved: https://github.com/facebook/react-forget/pull/2852
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
assertValidMutableRanges,
|
||||
lower,
|
||||
mergeConsecutiveBlocks,
|
||||
mergeOverlappingReactiveScopesHIR,
|
||||
pruneUnusedLabelsHIR,
|
||||
} from "../HIR";
|
||||
import {
|
||||
@@ -252,6 +253,13 @@ function* runWithEnvironment(
|
||||
value: hir,
|
||||
});
|
||||
|
||||
mergeOverlappingReactiveScopesHIR(hir);
|
||||
yield log({
|
||||
kind: "hir",
|
||||
name: "MergeOverlappingReactiveScopesHIR",
|
||||
value: hir,
|
||||
});
|
||||
|
||||
assertValidBlockNesting(hir);
|
||||
}
|
||||
|
||||
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
import {
|
||||
HIRFunction,
|
||||
InstructionId,
|
||||
Place,
|
||||
ReactiveScope,
|
||||
makeInstructionId,
|
||||
} from ".";
|
||||
import { getPlaceScope } from "../ReactiveScopes/BuildReactiveBlocks";
|
||||
import { isMutable } from "../ReactiveScopes/InferReactiveScopeVariables";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import { getOrInsertDefault } from "../Utils/utils";
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionOperand,
|
||||
eachTerminalOperand,
|
||||
} from "./visitors";
|
||||
|
||||
/**
|
||||
* While previous passes ensure that reactive scopes span valid sets of program
|
||||
* blocks, pairs of reactive scopes may still be inconsistent with respect to
|
||||
* each other.
|
||||
*
|
||||
* (a) Reactive scopes ranges must form valid blocks in the resulting javascript
|
||||
* program. Any two scopes must either be entirely disjoint or one scope must be
|
||||
* nested within the other.
|
||||
* ```js
|
||||
* // Scopes 1:3 and 3:5 are valid because they contain no common instructions
|
||||
* [1] ⌝
|
||||
* [2] ⌟
|
||||
* [3] ⌝
|
||||
* [4] ⌟
|
||||
* // Scopes 1:3 and 1:5 are valid because the former is nested within the other
|
||||
* [1] ⌝ ⌝
|
||||
* [2] ⌟ |
|
||||
* [3] |
|
||||
* [4] ⌟
|
||||
* // Scopes 1:4 and 2:5 are invalid because we cannot produce if-else memo
|
||||
* // blocks representing these scopes in the output program.
|
||||
* [1] ⌝
|
||||
* [2] | ⌝
|
||||
* [3] ⌟ |
|
||||
* [4] ⌟
|
||||
* ```
|
||||
*
|
||||
* (b) A scope's own instructions may only mutate that scope.
|
||||
* For each reactive scope, we currently produce exactly one if-block which
|
||||
* spans the instruction range of the scope. In this simple example, instr [2]
|
||||
* does not mutate any values but is included within scope @0.
|
||||
* ```js
|
||||
* // IR instructions
|
||||
* [1] (writes to scope @0's values)
|
||||
* [2] (does not mutate anything)
|
||||
* [3] (writes to scope @0's values)
|
||||
*
|
||||
* // javascript output
|
||||
* if (( scope @0's dependencies changed )) {
|
||||
* [1]
|
||||
* [2]
|
||||
* [3]
|
||||
* }
|
||||
* ```
|
||||
* Nested scopes may be modeled as a tree in which child scopes are contained
|
||||
* within parent scopes. This corresponds to nested if-else memo blocks in the
|
||||
* output program). An instruction may only mutate its own "active" scope.
|
||||
* ```js
|
||||
* // Active scopes for a simple program
|
||||
* scope @0 {
|
||||
* [0] (active scope=@0)
|
||||
* scope @1 {
|
||||
* [1] (active scope=@1)
|
||||
* [2] (active scope=@1)
|
||||
* }
|
||||
* [3] (active scope=@0)
|
||||
* }
|
||||
* [4] (no active scope)
|
||||
*
|
||||
* // In this example, scopes @0 and @1 must be merged because instr [2]'s
|
||||
* // active scope is scope@1 but it mutates scope@0.
|
||||
* scope @0, produces x {
|
||||
* [0] x = []
|
||||
* scope @1, produces y {
|
||||
* [1] y = []
|
||||
* [2] x.push(2)
|
||||
* [3] y.push(3)
|
||||
* }
|
||||
* [3] x.push(1)
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* As mentioned, these constraints arise entirely from the current design of
|
||||
* compiler output.
|
||||
* - instruction ordering is preserved (otherwise, disjoint ranges for scopes
|
||||
* may be produced by reordering their mutating instructions)
|
||||
* - exactly one if-else block per scope, which does not allow the composition
|
||||
* of a reactive scope from disconnected instruction ranges.
|
||||
*/
|
||||
|
||||
export function mergeOverlappingReactiveScopesHIR(fn: HIRFunction): void {
|
||||
/**
|
||||
* Collect all scopes eagerly because some scopes begin before the first
|
||||
* instruction that references them (due to alignReactiveScopesToBlocks)
|
||||
*/
|
||||
const scopesInfo = collectScopeInfo(fn);
|
||||
|
||||
/**
|
||||
* Iterate through scopes and instructions to find which should be merged
|
||||
*/
|
||||
const joinedScopes = getOverlappingReactiveScopes(fn, scopesInfo);
|
||||
|
||||
/**
|
||||
* Merge scopes and rewrite all references
|
||||
*/
|
||||
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 scopesInfo.placeScopes) {
|
||||
const nextScope = joinedScopes.find(originalScope);
|
||||
if (nextScope !== null && nextScope !== originalScope) {
|
||||
place.identifier.scope = nextScope;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ScopeInfo = {
|
||||
scopeStarts: Array<{ id: InstructionId; scopes: Set<ReactiveScope> }>;
|
||||
scopeEnds: Array<{ id: InstructionId; scopes: Set<ReactiveScope> }>;
|
||||
placeScopes: Map<Place, ReactiveScope>;
|
||||
};
|
||||
|
||||
type TraversalState = {
|
||||
joined: DisjointSet<ReactiveScope>;
|
||||
activeScopes: Array<ReactiveScope>;
|
||||
};
|
||||
|
||||
function collectScopeInfo(fn: HIRFunction): ScopeInfo {
|
||||
const scopeStarts: Map<InstructionId, Set<ReactiveScope>> = new Map();
|
||||
const scopeEnds: Map<InstructionId, Set<ReactiveScope>> = new Map();
|
||||
const placeScopes: Map<Place, ReactiveScope> = new Map();
|
||||
|
||||
function collectPlaceScope(place: Place): void {
|
||||
const scope = place.identifier.scope;
|
||||
if (scope != null) {
|
||||
placeScopes.set(place, scope);
|
||||
if (scope.range.start !== scope.range.end) {
|
||||
getOrInsertDefault(scopeStarts, scope.range.start, new Set()).add(
|
||||
scope
|
||||
);
|
||||
getOrInsertDefault(scopeEnds, scope.range.end, new Set()).add(scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
for (const operand of eachInstructionLValue(instr)) {
|
||||
collectPlaceScope(operand);
|
||||
}
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
collectPlaceScope(operand);
|
||||
}
|
||||
}
|
||||
for (const operand of eachTerminalOperand(block.terminal)) {
|
||||
collectPlaceScope(operand);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
scopeStarts: [...scopeStarts.entries()]
|
||||
.map(([id, scopes]) => ({ id, scopes }))
|
||||
.sort((a, b) => b.id - a.id),
|
||||
scopeEnds: [...scopeEnds.entries()]
|
||||
.map(([id, scopes]) => ({ id, scopes }))
|
||||
.sort((a, b) => b.id - a.id),
|
||||
placeScopes,
|
||||
};
|
||||
}
|
||||
|
||||
function visitInstructionId(
|
||||
id: InstructionId,
|
||||
{ scopeEnds, scopeStarts }: ScopeInfo,
|
||||
{ activeScopes, joined }: TraversalState
|
||||
): void {
|
||||
/**
|
||||
* Handle all scopes that end at this instruction.
|
||||
*/
|
||||
const scopeEndTop = scopeEnds.at(-1);
|
||||
if (scopeEndTop != null && scopeEndTop.id <= id) {
|
||||
scopeEnds.pop();
|
||||
|
||||
/**
|
||||
* Match scopes that end at this instruction with our stack of active
|
||||
* scopes (from traversal state). We need to sort these in descending
|
||||
* order of start IDs because the scopes stack is ordered as such
|
||||
*/
|
||||
const scopesSortedStartDescending = [...scopeEndTop.scopes].sort(
|
||||
(a, b) => b.range.start - a.range.start
|
||||
);
|
||||
for (const scope of scopesSortedStartDescending) {
|
||||
const idx = activeScopes.indexOf(scope);
|
||||
if (idx !== -1) {
|
||||
/**
|
||||
* Detect and merge all overlapping scopes. `activeScopes` is ordered
|
||||
* by scope start, so every active scope between a completed scope s
|
||||
* and the top of the stack (1) started later than s and (2) completes after s.
|
||||
*/
|
||||
if (idx !== activeScopes.length - 1) {
|
||||
joined.union([scope, ...activeScopes.slice(idx + 1)]);
|
||||
}
|
||||
activeScopes.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all scopes that begin at this instruction by adding them
|
||||
* to the scopes stack
|
||||
*/
|
||||
const scopeStartTop = scopeStarts.at(-1);
|
||||
if (scopeStartTop != null && scopeStartTop.id <= id) {
|
||||
scopeStarts.pop();
|
||||
|
||||
const scopesSortedEndDescending = [...scopeStartTop.scopes].sort(
|
||||
(a, b) => b.range.end - a.range.end
|
||||
);
|
||||
activeScopes.push(...scopesSortedEndDescending);
|
||||
/**
|
||||
* Merge all identical scopes (ones with the same start and end),
|
||||
* as they end up with the same reactive block
|
||||
*/
|
||||
for (let i = 1; i < scopesSortedEndDescending.length; i++) {
|
||||
const prev = scopesSortedEndDescending[i - 1];
|
||||
const curr = scopesSortedEndDescending[i];
|
||||
if (prev.range.end === curr.range.end) {
|
||||
joined.union([prev, curr]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function visitPlace(
|
||||
id: InstructionId,
|
||||
place: Place,
|
||||
{ activeScopes, joined }: TraversalState
|
||||
): void {
|
||||
/**
|
||||
* If an instruction mutates an outer scope, flatten all scopes from the top
|
||||
* of the stack to the mutated outer scope.
|
||||
*/
|
||||
const placeScope = getPlaceScope(id, place);
|
||||
if (placeScope != null && isMutable({ id } as any, place)) {
|
||||
const placeScopeIdx = activeScopes.indexOf(placeScope);
|
||||
if (placeScopeIdx !== -1 && placeScopeIdx !== activeScopes.length - 1) {
|
||||
joined.union([placeScope, ...activeScopes.slice(placeScopeIdx + 1)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getOverlappingReactiveScopes(
|
||||
fn: HIRFunction,
|
||||
context: ScopeInfo
|
||||
): DisjointSet<ReactiveScope> {
|
||||
const state: TraversalState = {
|
||||
joined: new DisjointSet<ReactiveScope>(),
|
||||
activeScopes: [],
|
||||
};
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
visitInstructionId(instr.id, context, state);
|
||||
for (const place of eachInstructionOperand(instr)) {
|
||||
visitPlace(instr.id, place, state);
|
||||
}
|
||||
for (const place of eachInstructionLValue(instr)) {
|
||||
visitPlace(instr.id, place, state);
|
||||
}
|
||||
}
|
||||
visitInstructionId(block.terminal.id, context, state);
|
||||
for (const place of eachTerminalOperand(block.terminal)) {
|
||||
visitPlace(block.terminal.id, place, state);
|
||||
}
|
||||
}
|
||||
|
||||
return state.joined;
|
||||
}
|
||||
@@ -27,5 +27,6 @@ export {
|
||||
reversePostorderBlocks,
|
||||
} from "./HIRBuilder";
|
||||
export { mergeConsecutiveBlocks } from "./MergeConsecutiveBlocks";
|
||||
export { mergeOverlappingReactiveScopesHIR } from "./MergeOverlappingReactiveScopesHIR";
|
||||
export { printFunction, printHIR } from "./PrintHIR";
|
||||
export { pruneUnusedLabelsHIR } from "./PruneUnusedLabelsHIR";
|
||||
|
||||
-131
@@ -22,8 +22,6 @@ import {
|
||||
mapTerminalSuccessors,
|
||||
terminalFallthrough,
|
||||
} from "../HIR/visitors";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import { retainWhere } from "../Utils/utils";
|
||||
import { getPlaceScope } from "./BuildReactiveBlocks";
|
||||
|
||||
/*
|
||||
@@ -237,46 +235,6 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
// console.log(_debug(rootNode));
|
||||
|
||||
const joinedScopes: DisjointSet<ReactiveScope> =
|
||||
mergeOverlappingScopes(rootNode);
|
||||
|
||||
/**
|
||||
* Join scopes that begin and end at the same instructions
|
||||
*/
|
||||
{
|
||||
const allScopes = [...new Set(placeScopes.values())].sort(
|
||||
(a, b) => a.range.start - b.range.start
|
||||
);
|
||||
for (let i = 1; i < allScopes.length; i++) {
|
||||
const prev = allScopes[i - 1];
|
||||
const curr = allScopes[i];
|
||||
if (
|
||||
prev.range.start === curr.range.start &&
|
||||
prev.range.end === curr.range.end
|
||||
) {
|
||||
joinedScopes.union([prev, curr]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = {
|
||||
@@ -318,92 +276,3 @@ function _printNode(
|
||||
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 { identity, mutate } from "shared-runtime";
|
||||
|
||||
function Foo({ cond }) {
|
||||
const x = identity(identity(cond)) ? { a: 2 } : { b: 2 };
|
||||
|
||||
mutate(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ cond: false }],
|
||||
sequentialRenders: [
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { identity, mutate } from "shared-runtime";
|
||||
|
||||
function Foo(t0) {
|
||||
const $ = useMemoCache(2);
|
||||
const { cond } = t0;
|
||||
let x;
|
||||
if ($[0] !== cond) {
|
||||
x = identity(identity(cond)) ? { a: 2 } : { b: 2 };
|
||||
|
||||
mutate(x);
|
||||
$[0] = cond;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ cond: false }],
|
||||
sequentialRenders: [
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"b":2,"wat0":"joe"}
|
||||
{"b":2,"wat0":"joe"}
|
||||
{"a":2,"wat0":"joe"}
|
||||
{"a":2,"wat0":"joe"}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { identity, mutate } from "shared-runtime";
|
||||
|
||||
function Foo({ cond }) {
|
||||
const x = identity(identity(cond)) ? { a: 2 } : { b: 2 };
|
||||
|
||||
mutate(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ cond: false }],
|
||||
sequentialRenders: [
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {
|
||||
CONST_TRUE,
|
||||
identity,
|
||||
makeObject_Primitives,
|
||||
mutateAndReturn,
|
||||
useHook,
|
||||
} from "shared-runtime";
|
||||
|
||||
/**
|
||||
* value and `mutateAndReturn(value)` should end up in the same reactive scope.
|
||||
* (1) `value = makeObject` and `(temporary) = mutateAndReturn(value)` should be assigned
|
||||
* the same scope id (on their identifiers)
|
||||
* (2) alignScopesToBlockScopes should expand the scopes of both `(temporary) = identity(1)`
|
||||
* and `(temporary) = mutateAndReturn(value)` to the outermost value block boundaries
|
||||
* (3) mergeOverlappingScopes should merge the scopes of the above two instructions
|
||||
*/
|
||||
function Component({}) {
|
||||
const value = makeObject_Primitives();
|
||||
useHook();
|
||||
const mutatedValue =
|
||||
identity(1) && CONST_TRUE ? mutateAndReturn(value) : null;
|
||||
const result = [];
|
||||
useHook();
|
||||
result.push(value, mutatedValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}, {}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import {
|
||||
CONST_TRUE,
|
||||
identity,
|
||||
makeObject_Primitives,
|
||||
mutateAndReturn,
|
||||
useHook,
|
||||
} from "shared-runtime";
|
||||
|
||||
/**
|
||||
* value and `mutateAndReturn(value)` should end up in the same reactive scope.
|
||||
* (1) `value = makeObject` and `(temporary) = mutateAndReturn(value)` should be assigned
|
||||
* the same scope id (on their identifiers)
|
||||
* (2) alignScopesToBlockScopes should expand the scopes of both `(temporary) = identity(1)`
|
||||
* and `(temporary) = mutateAndReturn(value)` to the outermost value block boundaries
|
||||
* (3) mergeOverlappingScopes should merge the scopes of the above two instructions
|
||||
*/
|
||||
function Component(t0) {
|
||||
const value = makeObject_Primitives();
|
||||
useHook();
|
||||
const mutatedValue =
|
||||
identity(1) && CONST_TRUE ? mutateAndReturn(value) : null;
|
||||
const result = [];
|
||||
useHook();
|
||||
result.push(value, mutatedValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}, {}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [{"a":0,"b":"value1","c":true,"wat0":"joe"},"[[ cyclic ref *1 ]]"]
|
||||
[{"a":0,"b":"value1","c":true,"wat0":"joe"},"[[ cyclic ref *1 ]]"]
|
||||
[{"a":0,"b":"value1","c":true,"wat0":"joe"},"[[ cyclic ref *1 ]]"]
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
CONST_TRUE,
|
||||
identity,
|
||||
makeObject_Primitives,
|
||||
mutateAndReturn,
|
||||
useHook,
|
||||
} from "shared-runtime";
|
||||
|
||||
/**
|
||||
* value and `mutateAndReturn(value)` should end up in the same reactive scope.
|
||||
* (1) `value = makeObject` and `(temporary) = mutateAndReturn(value)` should be assigned
|
||||
* the same scope id (on their identifiers)
|
||||
* (2) alignScopesToBlockScopes should expand the scopes of both `(temporary) = identity(1)`
|
||||
* and `(temporary) = mutateAndReturn(value)` to the outermost value block boundaries
|
||||
* (3) mergeOverlappingScopes should merge the scopes of the above two instructions
|
||||
*/
|
||||
function Component({}) {
|
||||
const value = makeObject_Primitives();
|
||||
useHook();
|
||||
const mutatedValue =
|
||||
identity(1) && CONST_TRUE ? mutateAndReturn(value) : null;
|
||||
const result = [];
|
||||
useHook();
|
||||
result.push(value, mutatedValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}, {}],
|
||||
};
|
||||
+31
-14
@@ -2,39 +2,42 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { arrayPush } from "shared-runtime";
|
||||
function foo(props) {
|
||||
let x = [];
|
||||
x.push(props.bar);
|
||||
props.cond
|
||||
? ((x = {}), (x = []), x.push(props.foo))
|
||||
: ((x = []), (x = []), x.push(props.bar));
|
||||
mut(x);
|
||||
arrayPush(x, 4);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: foo,
|
||||
params: [{ cond: false, foo: 2, bar: 55 }],
|
||||
sequentialRenders: [
|
||||
{ cond: false, foo: 2, bar: 55 },
|
||||
{ cond: false, foo: 3, bar: 55 },
|
||||
{ cond: true, foo: 3, bar: 55 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { arrayPush } from "shared-runtime";
|
||||
function foo(props) {
|
||||
const $ = useMemoCache(5);
|
||||
const $ = useMemoCache(2);
|
||||
let x;
|
||||
if ($[0] !== props) {
|
||||
x = [];
|
||||
x.push(props.bar);
|
||||
if ($[2] !== props || $[3] !== x) {
|
||||
props.cond
|
||||
? ((x = []), x.push(props.foo))
|
||||
: ((x = []), x.push(props.bar));
|
||||
mut(x);
|
||||
$[2] = props;
|
||||
$[3] = x;
|
||||
$[4] = x;
|
||||
} else {
|
||||
x = $[4];
|
||||
}
|
||||
props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar));
|
||||
arrayPush(x, 4);
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
} else {
|
||||
@@ -43,5 +46,19 @@ function foo(props) {
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: foo,
|
||||
params: [{ cond: false, foo: 2, bar: 55 }],
|
||||
sequentialRenders: [
|
||||
{ cond: false, foo: 2, bar: 55 },
|
||||
{ cond: false, foo: 3, bar: 55 },
|
||||
{ cond: true, foo: 3, bar: 55 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [55,4]
|
||||
[55,4]
|
||||
[3,4]
|
||||
+12
-1
@@ -1,9 +1,20 @@
|
||||
import { arrayPush } from "shared-runtime";
|
||||
function foo(props) {
|
||||
let x = [];
|
||||
x.push(props.bar);
|
||||
props.cond
|
||||
? ((x = {}), (x = []), x.push(props.foo))
|
||||
: ((x = []), (x = []), x.push(props.bar));
|
||||
mut(x);
|
||||
arrayPush(x, 4);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: foo,
|
||||
params: [{ cond: false, foo: 2, bar: 55 }],
|
||||
sequentialRenders: [
|
||||
{ cond: false, foo: 2, bar: 55 },
|
||||
{ cond: false, foo: 3, bar: 55 },
|
||||
{ cond: true, foo: 3, bar: 55 },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -334,7 +334,6 @@ const skipFilter = new Set([
|
||||
"ssa-property-alias-mutate-inside-if",
|
||||
"ssa-renaming-ternary-destruction-with-mutation",
|
||||
"ssa-renaming-ternary-with-mutation",
|
||||
"ssa-renaming-unconditional-ternary-with-mutation",
|
||||
"ssa-renaming-unconditional-with-mutation",
|
||||
"ssa-renaming-via-destructuring-with-mutation",
|
||||
"ssa-renaming-with-mutation",
|
||||
|
||||
Reference in New Issue
Block a user