[patch] Fix control flow bug in PropagateScopeDeps

A dependency D from either an instruction or scope is poisoned if there may be a 
(non-linear) jump instruction between it and the start of its immediate parent 
scope. Poisoned dependencies are added as conditional dependencies to their 
parent scope. 

(done: reduce false positives in scopes that begin after return/throw) (done: 
fix bugs in recording and joining exhaustive conditional deps) (done: flesh out 
commit message, clean up PR, add more fixtures) 

--- \## Bug details: 

Take a simple example: ```js target: {   instrA;   if (...) {     instrB;     
break target;   } else {     instrC;   }   instrD;   // ... } instrE; // ... ``` 

This diagram shows how we represent this program in the reactive IR. - Blocks 
are represented as a list of nodes. - Green nodes show instructions and value 
blocks (simplified as a single instruction). - Pink nodes show terminals, which 
transfer control to a subtree of nodes. <img width="450" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/930789f2-39cd-4ea8-b12a-530042807b46"> 

Prior to this PR, PropagateReactiveScopeDeps was incorrect because it assumed 
that a block's instructions are evaluated unconditionally (which is how HIR 
basic blocks work). E.g. if a reactive scope enclosed `block 1`, we assume that 
`instrA` and `instrD` both will evaluate unconditionally. 

This failed to account for `jump` instructions like break, continue, return, and 
throw. This may result in invalid hoisting of PropertyLoads (i.e. Forget output 
may throw when source does not throw). Note that other terminals (e.g. if and 
loops) are not affected as they are self contained subtrees that evaluate 
sequentially. 

With the changes in this PR, we mark `block 1` as poisoned upon encountering the 
`break` instruction. While `block 1` is active and poisoned, it will determine 
how visited dependencies are added. 

Here, added solid lines show unconditional dependencies, dashed lines show 
conditionally accessed dependencies: - dependencies from `instrB, instrC` are 
conditional because they are within conditional subtrees - dependencies from 
`instrD` are conditional because it is within a poisoned block within its parent 
scope. 

<img width="450" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/81980f68-7e65-4bd7-ba94-3f0c26550e5c"> 

--- Recapping an offline discussion with @josephsavona: this pass would really 
benefit from operating on HIR. The minimal work needed for this pass to run on 
HIR is to rewrite and reorder  `AlignReactiveScopesToBlockScopes` to operate on 
HIR. 

The following diagram shows what HIR blocks look like for the same code. 
Evaluating hoistable PropertyLoad dependencies for a scope enclosing 
`instr{A-D}` is much simpler:  just evaluate whether the PropertyLoad evaluates 
for every path between `bb0` and `bb4`. <img width="250" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/44b38939-defb-4b29-878d-4445ec6ccc06"> 

---
This commit is contained in:
Mofei Zhang
2024-03-27 20:26:18 -04:00
parent b98b569017
commit 5166869204
34 changed files with 1929 additions and 44 deletions
@@ -7,6 +7,8 @@
import { CompilerError } from "../CompilerError";
import {
BlockId,
GeneratedSource,
Identifier,
IdentifierId,
InstructionId,
@@ -135,9 +137,152 @@ class FindPromotedTemporaries extends ReactiveFunctionVisitor<TemporariesUsedOut
type DeclMap = Map<IdentifierId, Decl>;
type Decl = {
id: InstructionId;
scope: Stack<ReactiveScope>;
scope: Stack<ScopeTraversalState>;
};
/**
* TraversalState and PoisonState is used to track the poisoned state of a scope.
*
* A scope is poisoned when either of these conditions hold:
* - one of its own nested blocks is a jump target (for break/continues)
* - it is a outermost scope and contains a throw / return
*
* When a scope is poisoned, all dependencies (from instructions and inner scopes)
* are added as conditionally accessed.
*/
type ScopeTraversalState = {
value: ReactiveScope;
ownBlocks: Stack<BlockId>;
};
class PoisonState {
poisonedBlocks: Set<BlockId> = new Set();
poisonedScopes: Set<ScopeId> = new Set();
isPoisoned: boolean = false;
constructor(
poisonedBlocks: Set<BlockId>,
poisonedScopes: Set<ScopeId>,
isPoisoned: boolean
) {
this.poisonedBlocks = poisonedBlocks;
this.poisonedScopes = poisonedScopes;
this.isPoisoned = isPoisoned;
}
clone(): PoisonState {
return new PoisonState(
new Set(this.poisonedBlocks),
new Set(this.poisonedScopes),
this.isPoisoned
);
}
take(other: PoisonState): PoisonState {
const copy = new PoisonState(
this.poisonedBlocks,
this.poisonedScopes,
this.isPoisoned
);
this.poisonedBlocks = other.poisonedBlocks;
this.poisonedScopes = other.poisonedScopes;
this.isPoisoned = other.isPoisoned;
return copy;
}
merge(
others: Array<PoisonState>,
currentScope: ScopeTraversalState | null
): void {
for (const other of others) {
for (const id of other.poisonedBlocks) {
this.poisonedBlocks.add(id);
}
for (const id of other.poisonedScopes) {
this.poisonedScopes.add(id);
}
}
this.#invalidate(currentScope);
}
#invalidate(currentScope: ScopeTraversalState | null): void {
if (currentScope != null) {
if (this.poisonedScopes.has(currentScope.value.id)) {
this.isPoisoned = true;
return;
} else if (
currentScope.ownBlocks.find((blockId) =>
this.poisonedBlocks.has(blockId)
)
) {
this.isPoisoned = true;
return;
}
}
this.isPoisoned = false;
}
/**
* Mark a block or scope as poisoned and update the `isPoisoned` flag.
*
* @param targetBlock id of the block which ends non-linear control flow.
* For a break/continue instruction, this is the target block.
* Throw and return instructions have no target and will poison the earliest
* active scope
*/
addPoisonTarget(
target: BlockId | null,
activeScopes: Stack<ScopeTraversalState>
): void {
const currentScope = activeScopes.value;
if (target == null && currentScope != null) {
let cursor = activeScopes;
while (true) {
const next = cursor.pop();
if (next.value == null) {
const poisonedScope = cursor.value!.value.id;
this.poisonedScopes.add(poisonedScope);
if (poisonedScope === currentScope?.value.id) {
this.isPoisoned = true;
}
break;
} else {
cursor = next;
}
}
} else if (target != null) {
this.poisonedBlocks.add(target);
if (
!this.isPoisoned &&
currentScope?.ownBlocks.find((blockId) => blockId === target)
) {
this.isPoisoned = true;
}
}
}
/**
* Invoked during traversal when a poisoned scope becomes inactive
* @param id
* @param currentScope
*/
removeMaybePoisonedScope(
id: ScopeId,
currentScope: ScopeTraversalState | null
): void {
this.poisonedScopes.delete(id);
this.#invalidate(currentScope);
}
removeMaybePoisonedBlock(
id: BlockId,
currentScope: ScopeTraversalState | null
): void {
this.poisonedBlocks.delete(id);
this.#invalidate(currentScope);
}
}
class Context {
#temporariesUsedOutsideScope: Set<IdentifierId>;
#declarations: DeclMap = new Map();
@@ -163,7 +308,8 @@ class Context {
*/
#depsInCurrentConditional: ReactiveScopeDependencyTree =
new ReactiveScopeDependencyTree();
#scopes: Stack<ReactiveScope> = empty();
#scopes: Stack<ScopeTraversalState> = empty();
poisonState: PoisonState = new PoisonState(new Set(), new Set(), false);
constructor(temporariesUsedOutsideScope: Set<IdentifierId>) {
this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope;
@@ -173,6 +319,13 @@ class Context {
// Save context of previous scope
const prevInConditional = this.#inConditionalWithinScope;
const previousDependencies = this.#dependencies;
const prevDepsInConditional: ReactiveScopeDependencyTree | null = this
.isPoisoned
? this.#depsInCurrentConditional
: null;
if (prevDepsInConditional != null) {
this.#depsInCurrentConditional = new ReactiveScopeDependencyTree();
}
/*
* Set context for new scope
@@ -183,12 +336,18 @@ class Context {
const scopedDependencies = new ReactiveScopeDependencyTree();
this.#inConditionalWithinScope = false;
this.#dependencies = scopedDependencies;
this.#scopes = this.#scopes.push(scope);
this.#scopes = this.#scopes.push({
value: scope,
ownBlocks: empty(),
});
this.poisonState.isPoisoned = false;
fn();
// Restore context of previous scope
this.#scopes = this.#scopes.pop();
this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value);
this.#dependencies = previousDependencies;
this.#inConditionalWithinScope = prevInConditional;
@@ -204,9 +363,20 @@ class Context {
*/
this.#dependencies.addDepsFromInnerScope(
scopedDependencies,
this.#inConditionalWithinScope,
this.#inConditionalWithinScope || this.isPoisoned,
this.#checkValidDependency.bind(this)
);
if (prevDepsInConditional != null) {
// Outer scope is poisoned
prevDepsInConditional.addDepsFromInnerScope(
this.#depsInCurrentConditional,
true,
this.#checkValidDependency.bind(this)
);
this.#depsInCurrentConditional = prevDepsInConditional;
}
return minInnerScopeDependencies;
}
@@ -368,13 +538,13 @@ class Context {
const currentDeclaration =
this.#reassignments.get(identifier) ??
this.#declarations.get(identifier.id);
const currentScope = this.#scopes !== null ? this.#scopes.value : null;
const currentScope = this.currentScope.value?.value;
return (
currentScope != null &&
currentDeclaration !== undefined &&
currentDeclaration.id < currentScope.range.start &&
(currentDeclaration.scope == null ||
currentDeclaration.scope.value !== currentScope)
currentDeclaration.scope.value?.value !== currentScope)
);
}
@@ -382,13 +552,17 @@ class Context {
if (this.#scopes === null) {
return false;
}
return this.#scopes.contains(scope);
return this.#scopes.find((state) => state.value === scope);
}
get currentScope(): Stack<ReactiveScope> {
get currentScope(): Stack<ScopeTraversalState> {
return this.#scopes;
}
get isPoisoned(): boolean {
return this.poisonState.isPoisoned;
}
visitOperand(place: Place): void {
const resolved = this.resolveTemporary(place);
/*
@@ -436,22 +610,26 @@ class Context {
originalDeclaration.scope.value !== null
) {
originalDeclaration.scope.each((scope) => {
if (!this.#isScopeActive(scope)) {
scope.declarations.set(maybeDependency.identifier.id, {
if (!this.#isScopeActive(scope.value)) {
scope.value.declarations.set(maybeDependency.identifier.id, {
identifier: maybeDependency.identifier,
scope: originalDeclaration.scope.value!, // checked above
scope: originalDeclaration.scope.value!.value,
});
}
});
}
if (this.#checkValidDependency(maybeDependency)) {
this.#depsInCurrentConditional.add(maybeDependency, false);
const isPoisoned = this.isPoisoned;
this.#depsInCurrentConditional.add(maybeDependency, isPoisoned);
/*
* Add info about this dependency to the existing tree
* We do not try to join/reduce dependencies here due to missing info
*/
this.#dependencies.add(maybeDependency, this.#inConditionalWithinScope);
this.#dependencies.add(
maybeDependency,
this.#inConditionalWithinScope || isPoisoned
);
}
}
@@ -460,16 +638,37 @@ class Context {
* current one as a {@link ReactiveScope.reassignments}
*/
visitReassignment(place: Place): void {
const currentScope = this.currentScope.value?.value;
if (
this.currentScope.value != null &&
!Array.from(this.currentScope.value.reassignments).some(
currentScope != null &&
!Array.from(currentScope.reassignments).some(
(identifier) => identifier.id === place.identifier.id
) &&
this.#checkValidDependency({ identifier: place.identifier, path: [] })
) {
this.currentScope.value.reassignments.add(place.identifier);
currentScope.reassignments.add(place.identifier);
}
}
pushLabeledBlock(id: BlockId): void {
const currentScope = this.#scopes.value;
if (currentScope != null) {
currentScope.ownBlocks = currentScope.ownBlocks.push(id);
}
}
popLabeledBlock(id: BlockId): void {
const currentScope = this.#scopes.value;
if (currentScope != null) {
const last = currentScope.ownBlocks.value;
currentScope.ownBlocks = currentScope.ownBlocks.pop();
CompilerError.invariant(last != null && last === id, {
reason: "[PropagateScopeDependencies] Misformed block stack",
loc: GeneratedSource,
});
}
this.poisonState.removeMaybePoisonedBlock(id, currentScope);
}
}
class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
@@ -659,10 +858,38 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
}
}
enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void {
if (stmt.label != null) {
context.pushLabeledBlock(stmt.label.id);
}
const terminal = stmt.terminal;
switch (terminal.kind) {
case "continue":
case "break": {
context.poisonState.addPoisonTarget(
terminal.target,
context.currentScope
);
break;
}
case "throw":
case "return": {
context.poisonState.addPoisonTarget(null, context.currentScope);
break;
}
}
}
exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void {
if (stmt.label != null) {
context.popLabeledBlock(stmt.label.id);
}
}
override visitTerminal(
stmt: ReactiveTerminalStatement,
context: Context
): void {
this.enterTerminal(stmt, context);
const terminal = stmt.terminal;
switch (terminal.kind) {
case "break":
@@ -719,13 +946,23 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
case "if": {
context.visitOperand(terminal.test);
const { consequent, alternate } = terminal;
/*
* Consequent and alternate branches are mutually exclusive,
* so we save and restore the poison state here.
*/
const prevPoisonState = context.poisonState.clone();
const depsInIf = context.enterConditional(() => {
this.visitBlock(consequent, context);
});
if (alternate !== null) {
const ifPoisonState = context.poisonState.take(prevPoisonState);
const depsInElse = context.enterConditional(() => {
this.visitBlock(alternate, context);
});
context.poisonState.merge(
[ifPoisonState],
context.currentScope.value
);
context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]);
}
break;
@@ -743,6 +980,11 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
}
const depsInCases = [];
let foundDefault = false;
/**
* Switch branches are mutually exclusive
*/
const prevPoisonState = context.poisonState.clone();
const mutExPoisonStates: Array<PoisonState> = [];
/*
* This can underestimate unconditional accesses due to the current
* CFG representation for fallthrough. This is safe. It only
@@ -755,6 +997,9 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
foundDefault = true;
}
if (block !== undefined) {
mutExPoisonStates.push(
context.poisonState.take(prevPoisonState.clone())
);
depsInCases.push(
context.enterConditional(() => {
this.visitBlock(block, context);
@@ -765,6 +1010,10 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
if (foundDefault) {
context.promoteDepsFromExhaustiveConditionals(depsInCases);
}
context.poisonState.merge(
mutExPoisonStates,
context.currentScope.value
);
break;
}
case "label": {
@@ -783,5 +1032,6 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
);
}
}
this.exitTerminal(stmt, context);
}
}
@@ -25,10 +25,13 @@ interface StackInterface<T> {
pop(): StackInterface<T>;
contains(value: T): boolean;
find(fn: (value: T) => boolean): boolean;
each(fn: (value: T) => void): void;
get value(): T | null;
print(fn: (node: T) => string): string;
}
export function create<T>(value: T): Stack<T> {
@@ -56,6 +59,10 @@ class Node<T> implements StackInterface<T> {
return this.#next;
}
find(fn: (value: T) => boolean): boolean {
return fn(this.#value) ? true : this.#next.find(fn);
}
contains(value: T): boolean {
return (
value === this.#value ||
@@ -70,6 +77,10 @@ class Node<T> implements StackInterface<T> {
get value(): T {
return this.#value;
}
print(fn: (node: T) => string): string {
return fn(this.#value) + this.#next.print(fn);
}
}
class Empty<T> implements StackInterface<T> {
@@ -79,6 +90,10 @@ class Empty<T> implements StackInterface<T> {
pop(): Stack<T> {
return this;
}
find(_fn: (value: T) => boolean): boolean {
return false;
}
contains(_value: T): boolean {
return false;
}
@@ -88,6 +103,9 @@ class Empty<T> implements StackInterface<T> {
get value(): T | null {
return null;
}
print(_: (node: T) => string): string {
return "";
}
}
const EMPTY: Stack<void> = new Empty();
@@ -34,14 +34,9 @@ import { unstable_useMemoCache as useMemoCache } from "react";
* props.b *does* influence `a`
*/
function Component(props) {
const $ = useMemoCache(5);
const $ = useMemoCache(2);
let a;
if (
$[0] !== props.a ||
$[1] !== props.b ||
$[2] !== props.c ||
$[3] !== props.d
) {
if ($[0] !== props) {
a = [];
a.push(props.a);
bb1: {
@@ -53,13 +48,10 @@ function Component(props) {
}
a.push(props.d);
$[0] = props.a;
$[1] = props.b;
$[2] = props.c;
$[3] = props.d;
$[4] = a;
$[0] = props;
$[1] = a;
} else {
a = $[4];
a = $[1];
}
return a;
}
@@ -71,10 +71,10 @@ import { unstable_useMemoCache as useMemoCache } from "react";
* props.b does *not* influence `a`
*/
function ComponentA(props) {
const $ = useMemoCache(5);
const $ = useMemoCache(3);
let a_DEBUG;
let t0;
if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) {
if ($[0] !== props) {
t0 = Symbol.for("react.early_return_sentinel");
bb7: {
a_DEBUG = [];
@@ -86,14 +86,12 @@ function ComponentA(props) {
a_DEBUG.push(props.d);
}
$[0] = props.a;
$[1] = props.b;
$[2] = props.d;
$[3] = a_DEBUG;
$[4] = t0;
$[0] = props;
$[1] = a_DEBUG;
$[2] = t0;
} else {
a_DEBUG = $[3];
t0 = $[4];
a_DEBUG = $[1];
t0 = $[2];
}
if (t0 !== Symbol.for("react.early_return_sentinel")) {
return t0;
@@ -19,6 +19,10 @@ export const FIXTURE_ENTRYPOINT = {
sequentialRenders: [
{ obj: null, objIsNull: true },
{ obj: { a: 2 }, objIsNull: false },
// check we preserve nullthrows
{ obj: { a: undefined }, objIsNull: false },
{ obj: undefined, objIsNull: false },
{ obj: { a: undefined }, objIsNull: false },
],
};
@@ -32,7 +36,7 @@ function useFoo(t0) {
const $ = useMemoCache(3);
const { obj, objIsNull } = t0;
let x;
if ($[0] !== objIsNull || $[1] !== obj.a) {
if ($[0] !== objIsNull || $[1] !== obj) {
x = [];
bb1: {
if (objIsNull) {
@@ -42,7 +46,7 @@ function useFoo(t0) {
x.push(obj.a);
}
$[0] = objIsNull;
$[1] = obj.a;
$[1] = obj;
$[2] = x;
} else {
x = $[2];
@@ -56,8 +60,18 @@ export const FIXTURE_ENTRYPOINT = {
sequentialRenders: [
{ obj: null, objIsNull: true },
{ obj: { a: 2 }, objIsNull: false },
// check we preserve nullthrows
{ obj: { a: undefined }, objIsNull: false },
{ obj: undefined, objIsNull: false },
{ obj: { a: undefined }, objIsNull: false },
],
};
```
### Eval output
(kind: ok) []
[2]
[null]
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
[null]
@@ -15,5 +15,9 @@ export const FIXTURE_ENTRYPOINT = {
sequentialRenders: [
{ obj: null, objIsNull: true },
{ obj: { a: 2 }, objIsNull: false },
// check we preserve nullthrows
{ obj: { a: undefined }, objIsNull: false },
{ obj: undefined, objIsNull: false },
{ obj: { a: undefined }, objIsNull: false },
],
};
@@ -0,0 +1,94 @@
## Input
```javascript
import { identity } from "shared-runtime";
function useFoo({ input, cond }) {
const x = [];
label: {
if (cond) {
break label;
}
x.push(identity(input.a.b));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: { a: { b: 2 } }, cond: false },
// preserve nullthrows
{ input: null, cond: false },
{ input: null, cond: true },
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { identity } from "shared-runtime";
function useFoo(t0) {
const $ = useMemoCache(5);
const { input, cond } = t0;
let x;
if ($[0] !== cond || $[1] !== input) {
x = [];
bb1: {
if (cond) {
break bb1;
}
let t1;
if ($[3] !== input.a.b) {
t1 = identity(input.a.b);
$[3] = input.a.b;
$[4] = t1;
} else {
t1 = $[4];
}
x.push(t1);
}
$[0] = cond;
$[1] = input;
$[2] = x;
} else {
x = $[2];
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: { a: { b: 2 } }, cond: false },
// preserve nullthrows
{ input: null, cond: false },
{ input: null, cond: true },
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
### Eval output
(kind: ok) [2]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'a') ]]
[]
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
[null]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
[3]
@@ -0,0 +1,27 @@
import { identity } from "shared-runtime";
function useFoo({ input, cond }) {
const x = [];
label: {
if (cond) {
break label;
}
x.push(identity(input.a.b));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: { a: { b: 2 } }, cond: false },
// preserve nullthrows
{ input: null, cond: false },
{ input: null, cond: true },
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
@@ -0,0 +1,77 @@
## Input
```javascript
function useFoo({ obj, objIsNull }) {
const x = [];
for (let i = 0; i < 5; i++) {
if (objIsNull) {
continue;
}
x.push(obj.a);
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ obj: null, objIsNull: true }],
sequentialRenders: [
{ obj: null, objIsNull: true },
{ obj: { a: 2 }, objIsNull: false },
// check we preserve nullthrows
{ obj: { a: undefined }, objIsNull: false },
{ obj: undefined, objIsNull: false },
{ obj: { a: undefined }, objIsNull: false },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function useFoo(t0) {
const $ = useMemoCache(3);
const { obj, objIsNull } = t0;
let x;
if ($[0] !== objIsNull || $[1] !== obj) {
x = [];
for (let i = 0; i < 5; i++) {
if (objIsNull) {
continue;
}
x.push(obj.a);
}
$[0] = objIsNull;
$[1] = obj;
$[2] = x;
} else {
x = $[2];
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ obj: null, objIsNull: true }],
sequentialRenders: [
{ obj: null, objIsNull: true },
{ obj: { a: 2 }, objIsNull: false },
// check we preserve nullthrows
{ obj: { a: undefined }, objIsNull: false },
{ obj: undefined, objIsNull: false },
{ obj: { a: undefined }, objIsNull: false },
],
};
```
### Eval output
(kind: ok) []
[2,2,2,2,2]
[null,null,null,null,null]
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
[null,null,null,null,null]
@@ -0,0 +1,23 @@
function useFoo({ obj, objIsNull }) {
const x = [];
for (let i = 0; i < 5; i++) {
if (objIsNull) {
continue;
}
x.push(obj.a);
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ obj: null, objIsNull: true }],
sequentialRenders: [
{ obj: null, objIsNull: true },
{ obj: { a: 2 }, objIsNull: false },
// check we preserve nullthrows
{ obj: { a: undefined }, objIsNull: false },
{ obj: undefined, objIsNull: false },
{ obj: { a: undefined }, objIsNull: false },
],
};
@@ -0,0 +1,114 @@
## Input
```javascript
import { identity } from "shared-runtime";
function useFoo({ input, cond, hasAB }) {
const x = [];
if (cond) {
if (!hasAB) {
return null;
}
x.push(identity(input.a.b));
} else {
x.push(identity(input.a.b));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, cond: true, hasAB: false }],
sequentialRenders: [
{ input: { a: { b: 1 } }, cond: true, hasAB: true },
{ input: null, cond: true, hasAB: false },
// preserve nullthrows
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
{ input: { a: undefined }, cond: true, hasAB: true },
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
{ input: undefined, cond: true, hasAB: true },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { identity } from "shared-runtime";
function useFoo(t0) {
const $ = useMemoCache(9);
const { input, cond, hasAB } = t0;
let x;
let t1;
if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) {
t1 = Symbol.for("react.early_return_sentinel");
bb10: {
x = [];
if (cond) {
if (!hasAB) {
t1 = null;
break bb10;
}
let t2;
if ($[5] !== input.a.b) {
t2 = identity(input.a.b);
$[5] = input.a.b;
$[6] = t2;
} else {
t2 = $[6];
}
x.push(t2);
} else {
let t2;
if ($[7] !== input.a.b) {
t2 = identity(input.a.b);
$[7] = input.a.b;
$[8] = t2;
} else {
t2 = $[8];
}
x.push(t2);
}
}
$[0] = cond;
$[1] = hasAB;
$[2] = input;
$[3] = x;
$[4] = t1;
} else {
x = $[3];
t1 = $[4];
}
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, cond: true, hasAB: false }],
sequentialRenders: [
{ input: { a: { b: 1 } }, cond: true, hasAB: true },
{ input: null, cond: true, hasAB: false },
// preserve nullthrows
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
{ input: { a: undefined }, cond: true, hasAB: true },
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
{ input: undefined, cond: true, hasAB: true },
],
};
```
### Eval output
(kind: ok) [1]
null
[null]
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
[null]
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
@@ -0,0 +1,28 @@
import { identity } from "shared-runtime";
function useFoo({ input, cond, hasAB }) {
const x = [];
if (cond) {
if (!hasAB) {
return null;
}
x.push(identity(input.a.b));
} else {
x.push(identity(input.a.b));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, cond: true, hasAB: false }],
sequentialRenders: [
{ input: { a: { b: 1 } }, cond: true, hasAB: true },
{ input: null, cond: true, hasAB: false },
// preserve nullthrows
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
{ input: { a: undefined }, cond: true, hasAB: true },
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
{ input: undefined, cond: true, hasAB: true },
],
};
@@ -0,0 +1,123 @@
## Input
```javascript
import { identity } from "shared-runtime";
function useFoo({ input, cond, hasAB }) {
const x = [];
if (cond) {
if (!hasAB) {
return null;
} else {
x.push(identity(input.a.b));
}
x.push(identity(input.a.b));
} else {
x.push(identity(input.a.b));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, cond: true, hasAB: false }],
sequentialRenders: [
{ input: { a: { b: 1 } }, cond: true, hasAB: true },
{ input: null, cond: true, hasAB: false },
// preserve nullthrows
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
{ input: { a: null }, cond: true, hasAB: true },
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { identity } from "shared-runtime";
function useFoo(t0) {
const $ = useMemoCache(11);
const { input, cond, hasAB } = t0;
let x;
let t1;
if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) {
t1 = Symbol.for("react.early_return_sentinel");
bb11: {
x = [];
if (cond) {
if (!hasAB) {
t1 = null;
break bb11;
} else {
let t2;
if ($[5] !== input.a.b) {
t2 = identity(input.a.b);
$[5] = input.a.b;
$[6] = t2;
} else {
t2 = $[6];
}
x.push(t2);
}
let t2;
if ($[7] !== input.a.b) {
t2 = identity(input.a.b);
$[7] = input.a.b;
$[8] = t2;
} else {
t2 = $[8];
}
x.push(t2);
} else {
let t2;
if ($[9] !== input.a.b) {
t2 = identity(input.a.b);
$[9] = input.a.b;
$[10] = t2;
} else {
t2 = $[10];
}
x.push(t2);
}
}
$[0] = cond;
$[1] = hasAB;
$[2] = input;
$[3] = x;
$[4] = t1;
} else {
x = $[3];
t1 = $[4];
}
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, cond: true, hasAB: false }],
sequentialRenders: [
{ input: { a: { b: 1 } }, cond: true, hasAB: true },
{ input: null, cond: true, hasAB: false },
// preserve nullthrows
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
{ input: { a: null }, cond: true, hasAB: true },
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
],
};
```
### Eval output
(kind: ok) [1,1]
null
[null,null]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
[null,null]
@@ -0,0 +1,29 @@
import { identity } from "shared-runtime";
function useFoo({ input, cond, hasAB }) {
const x = [];
if (cond) {
if (!hasAB) {
return null;
} else {
x.push(identity(input.a.b));
}
x.push(identity(input.a.b));
} else {
x.push(identity(input.a.b));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, cond: true, hasAB: false }],
sequentialRenders: [
{ input: { a: { b: 1 } }, cond: true, hasAB: true },
{ input: null, cond: true, hasAB: false },
// preserve nullthrows
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
{ input: { a: null }, cond: true, hasAB: true },
{ input: { a: { b: undefined } }, cond: true, hasAB: true },
],
};
@@ -17,6 +17,10 @@ export const FIXTURE_ENTRYPOINT = {
sequentialRenders: [
{ obj: null, objIsNull: true },
{ obj: { a: 2 }, objIsNull: false },
// check we preserve nullthrows
{ obj: { a: undefined }, objIsNull: false },
{ obj: undefined, objIsNull: false },
{ obj: { a: undefined }, objIsNull: false },
],
};
@@ -31,7 +35,7 @@ function useFoo(t0) {
const { obj, objIsNull } = t0;
let x;
let t1;
if ($[0] !== objIsNull || $[1] !== obj.b) {
if ($[0] !== objIsNull || $[1] !== obj) {
t1 = Symbol.for("react.early_return_sentinel");
bb7: {
x = [];
@@ -43,7 +47,7 @@ function useFoo(t0) {
x.push(obj.b);
}
$[0] = objIsNull;
$[1] = obj.b;
$[1] = obj;
$[2] = x;
$[3] = t1;
} else {
@@ -62,8 +66,18 @@ export const FIXTURE_ENTRYPOINT = {
sequentialRenders: [
{ obj: null, objIsNull: true },
{ obj: { a: 2 }, objIsNull: false },
// check we preserve nullthrows
{ obj: { a: undefined }, objIsNull: false },
{ obj: undefined, objIsNull: false },
{ obj: { a: undefined }, objIsNull: false },
],
};
```
### Eval output
(kind: ok)
[null]
[null]
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
[null]
@@ -13,5 +13,9 @@ export const FIXTURE_ENTRYPOINT = {
sequentialRenders: [
{ obj: null, objIsNull: true },
{ obj: { a: 2 }, objIsNull: false },
// check we preserve nullthrows
{ obj: { a: undefined }, objIsNull: false },
{ obj: undefined, objIsNull: false },
{ obj: { a: undefined }, objIsNull: false },
],
};
@@ -0,0 +1,100 @@
## Input
```javascript
import { identity } from "shared-runtime";
function useFoo({ input, cond }) {
const x = [];
if (cond) {
return null;
}
x.push(identity(input.a.b));
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: { a: { b: 2 } }, cond: false },
// preserve nullthrows
{ input: null, cond: false },
{ input: null, cond: true },
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { identity } from "shared-runtime";
function useFoo(t0) {
const $ = useMemoCache(6);
const { input, cond } = t0;
let x;
let t1;
if ($[0] !== cond || $[1] !== input) {
t1 = Symbol.for("react.early_return_sentinel");
bb7: {
x = [];
if (cond) {
t1 = null;
break bb7;
}
let t2;
if ($[4] !== input.a.b) {
t2 = identity(input.a.b);
$[4] = input.a.b;
$[5] = t2;
} else {
t2 = $[5];
}
x.push(t2);
}
$[0] = cond;
$[1] = input;
$[2] = x;
$[3] = t1;
} else {
x = $[2];
t1 = $[3];
}
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: { a: { b: 2 } }, cond: false },
// preserve nullthrows
{ input: null, cond: false },
{ input: null, cond: true },
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
### Eval output
(kind: ok) [2]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'a') ]]
null
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
[null]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
[3]
@@ -0,0 +1,25 @@
import { identity } from "shared-runtime";
function useFoo({ input, cond }) {
const x = [];
if (cond) {
return null;
}
x.push(identity(input.a.b));
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: { a: { b: 2 } }, cond: false },
// preserve nullthrows
{ input: null, cond: false },
{ input: null, cond: true },
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
@@ -0,0 +1,94 @@
## Input
```javascript
import { identity } from "shared-runtime";
function useFoo({ input, cond }) {
const x = [];
label: {
if (cond) {
break label;
} else {
x.push(identity(input.a.b));
}
}
return x[0];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: null, cond: true },
{ input: { a: { b: 2 } }, cond: false },
{ input: null, cond: true },
// preserve nullthrows
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { identity } from "shared-runtime";
function useFoo(t0) {
const $ = useMemoCache(5);
const { input, cond } = t0;
let x;
if ($[0] !== cond || $[1] !== input) {
x = [];
bb1: if (cond) {
break bb1;
} else {
let t1;
if ($[3] !== input.a.b) {
t1 = identity(input.a.b);
$[3] = input.a.b;
$[4] = t1;
} else {
t1 = $[4];
}
x.push(t1);
}
$[0] = cond;
$[1] = input;
$[2] = x;
} else {
x = $[2];
}
return x[0];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: null, cond: true },
{ input: { a: { b: 2 } }, cond: false },
{ input: null, cond: true },
// preserve nullthrows
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
### Eval output
(kind: ok)
2
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
null
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
3
@@ -0,0 +1,28 @@
import { identity } from "shared-runtime";
function useFoo({ input, cond }) {
const x = [];
label: {
if (cond) {
break label;
} else {
x.push(identity(input.a.b));
}
}
return x[0];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: null, cond: true },
{ input: { a: { b: 2 } }, cond: false },
{ input: null, cond: true },
// preserve nullthrows
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
@@ -0,0 +1,81 @@
## Input
```javascript
function useFoo({ input, cond }) {
const x = [];
label: {
if (cond) {
break label;
}
}
x.push(input.a.b); // unconditional
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: { a: { b: 2 } }, cond: false },
// preserve nullthrows
{ input: null, cond: false },
{ input: null, cond: true },
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function useFoo(t0) {
const $ = useMemoCache(3);
const { input, cond } = t0;
let x;
if ($[0] !== cond || $[1] !== input.a.b) {
x = [];
bb1: if (cond) {
break bb1;
}
x.push(input.a.b);
$[0] = cond;
$[1] = input.a.b;
$[2] = x;
} else {
x = $[2];
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: { a: { b: 2 } }, cond: false },
// preserve nullthrows
{ input: null, cond: false },
{ input: null, cond: true },
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
### Eval output
(kind: ok) [2]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'a') ]]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'a') ]]
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
[null]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
[3]
@@ -0,0 +1,25 @@
function useFoo({ input, cond }) {
const x = [];
label: {
if (cond) {
break label;
}
}
x.push(input.a.b); // unconditional
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: { a: { b: 2 } }, cond: false },
// preserve nullthrows
{ input: null, cond: false },
{ input: null, cond: true },
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
@@ -0,0 +1,86 @@
## Input
```javascript
function useFoo({ input, max }) {
const x = [];
let i = 0;
while (true) {
i += 1;
if (i > max) {
break;
}
}
x.push(i);
x.push(input.a.b); // unconditional
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, max: 8 }],
sequentialRenders: [
{ input: { a: { b: 2 } }, max: 8 },
// preserve nullthrows
{ input: null, max: 8 },
{ input: {}, max: 8 },
{ input: { a: { b: null } }, max: 8 },
{ input: { a: null }, max: 8 },
{ input: { a: { b: 3 } }, max: 8 },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function useFoo(t0) {
const $ = useMemoCache(3);
const { input, max } = t0;
let x;
if ($[0] !== max || $[1] !== input.a.b) {
x = [];
let i = 0;
while (true) {
i = i + 1;
if (i > max) {
break;
}
}
x.push(i);
x.push(input.a.b);
$[0] = max;
$[1] = input.a.b;
$[2] = x;
} else {
x = $[2];
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, max: 8 }],
sequentialRenders: [
{ input: { a: { b: 2 } }, max: 8 },
// preserve nullthrows
{ input: null, max: 8 },
{ input: {}, max: 8 },
{ input: { a: { b: null } }, max: 8 },
{ input: { a: null }, max: 8 },
{ input: { a: { b: 3 } }, max: 8 },
],
};
```
### Eval output
(kind: ok) [9,2]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'a') ]]
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
[9,null]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
[9,3]
@@ -0,0 +1,27 @@
function useFoo({ input, max }) {
const x = [];
let i = 0;
while (true) {
i += 1;
if (i > max) {
break;
}
}
x.push(i);
x.push(input.a.b); // unconditional
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, max: 8 }],
sequentialRenders: [
{ input: { a: { b: 2 } }, max: 8 },
// preserve nullthrows
{ input: null, max: 8 },
{ input: {}, max: 8 },
{ input: { a: { b: null } }, max: 8 },
{ input: { a: null }, max: 8 },
{ input: { a: { b: 3 } }, max: 8 },
],
};
@@ -0,0 +1,91 @@
## Input
```javascript
import { identity } from "shared-runtime";
function useFoo({ input, hasAB, returnNull }) {
const x = [];
if (!hasAB) {
x.push(identity(input.a));
if (!returnNull) {
return null;
}
} else {
x.push(identity(input.a.b));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, hasAB: false, returnNull: false }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { identity } from "shared-runtime";
function useFoo(t0) {
const $ = useMemoCache(9);
const { input, hasAB, returnNull } = t0;
let x;
let t1;
if ($[0] !== hasAB || $[1] !== input.a || $[2] !== returnNull) {
t1 = Symbol.for("react.early_return_sentinel");
bb10: {
x = [];
if (!hasAB) {
let t2;
if ($[5] !== input.a) {
t2 = identity(input.a);
$[5] = input.a;
$[6] = t2;
} else {
t2 = $[6];
}
x.push(t2);
if (!returnNull) {
t1 = null;
break bb10;
}
} else {
let t2;
if ($[7] !== input.a.b) {
t2 = identity(input.a.b);
$[7] = input.a.b;
$[8] = t2;
} else {
t2 = $[8];
}
x.push(t2);
}
}
$[0] = hasAB;
$[1] = input.a;
$[2] = returnNull;
$[3] = x;
$[4] = t1;
} else {
x = $[3];
t1 = $[4];
}
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, hasAB: false, returnNull: false }],
};
```
### Eval output
(kind: ok) null
@@ -0,0 +1,19 @@
import { identity } from "shared-runtime";
function useFoo({ input, hasAB, returnNull }) {
const x = [];
if (!hasAB) {
x.push(identity(input.a));
if (!returnNull) {
return null;
}
} else {
x.push(identity(input.a.b));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, hasAB: false, returnNull: false }],
};
@@ -0,0 +1,123 @@
## Input
```javascript
import { identity } from "shared-runtime";
function useFoo({ input, cond2, cond1 }) {
const x = [];
if (cond1) {
if (!cond2) {
x.push(identity(input.a.b));
return null;
} else {
x.push(identity(input.a.b));
}
} else {
x.push(identity(input.a.b));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, cond1: true, cond2: false }],
sequentialRenders: [
{ input: { a: { b: 1 } }, cond1: true, cond2: true },
{ input: null, cond1: true, cond2: false },
// preserve nullthrows
{ input: { a: { b: undefined } }, cond1: true, cond2: true },
{ input: { a: null }, cond1: true, cond2: true },
{ input: { a: { b: undefined } }, cond1: true, cond2: true },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { identity } from "shared-runtime";
function useFoo(t0) {
const $ = useMemoCache(11);
const { input, cond2, cond1 } = t0;
let x;
let t1;
if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== input.a.b) {
t1 = Symbol.for("react.early_return_sentinel");
bb11: {
x = [];
if (cond1) {
if (!cond2) {
let t2;
if ($[5] !== input.a.b) {
t2 = identity(input.a.b);
$[5] = input.a.b;
$[6] = t2;
} else {
t2 = $[6];
}
x.push(t2);
t1 = null;
break bb11;
} else {
let t2;
if ($[7] !== input.a.b) {
t2 = identity(input.a.b);
$[7] = input.a.b;
$[8] = t2;
} else {
t2 = $[8];
}
x.push(t2);
}
} else {
let t2;
if ($[9] !== input.a.b) {
t2 = identity(input.a.b);
$[9] = input.a.b;
$[10] = t2;
} else {
t2 = $[10];
}
x.push(t2);
}
}
$[0] = cond1;
$[1] = cond2;
$[2] = input.a.b;
$[3] = x;
$[4] = t1;
} else {
x = $[3];
t1 = $[4];
}
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, cond1: true, cond2: false }],
sequentialRenders: [
{ input: { a: { b: 1 } }, cond1: true, cond2: true },
{ input: null, cond1: true, cond2: false },
// preserve nullthrows
{ input: { a: { b: undefined } }, cond1: true, cond2: true },
{ input: { a: null }, cond1: true, cond2: true },
{ input: { a: { b: undefined } }, cond1: true, cond2: true },
],
};
```
### Eval output
(kind: ok) [1]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'a') ]]
[null]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
[null]
@@ -0,0 +1,29 @@
import { identity } from "shared-runtime";
function useFoo({ input, cond2, cond1 }) {
const x = [];
if (cond1) {
if (!cond2) {
x.push(identity(input.a.b));
return null;
} else {
x.push(identity(input.a.b));
}
} else {
x.push(identity(input.a.b));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, cond1: true, cond2: false }],
sequentialRenders: [
{ input: { a: { b: 1 } }, cond1: true, cond2: true },
{ input: null, cond1: true, cond2: false },
// preserve nullthrows
{ input: { a: { b: undefined } }, cond1: true, cond2: true },
{ input: { a: null }, cond1: true, cond2: true },
{ input: { a: { b: undefined } }, cond1: true, cond2: true },
],
};
@@ -0,0 +1,90 @@
## Input
```javascript
import { arrayPush } from "shared-runtime";
function useFoo({ input, cond }) {
if (cond) {
return { result: "early return" };
}
// unconditional
const x = [];
arrayPush(x, input.a.b);
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: null, cond: true },
{ input: { a: { b: 2 } }, cond: false },
{ input: null, cond: true },
// preserve nullthrows
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { arrayPush } from "shared-runtime";
function useFoo(t0) {
const $ = useMemoCache(3);
const { input, cond } = t0;
if (cond) {
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { result: "early return" };
$[0] = t1;
} else {
t1 = $[0];
}
return t1;
}
let x;
if ($[1] !== input.a.b) {
x = [];
arrayPush(x, input.a.b);
$[1] = input.a.b;
$[2] = x;
} else {
x = $[2];
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: null, cond: true },
{ input: { a: { b: 2 } }, cond: false },
{ input: null, cond: true },
// preserve nullthrows
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
### Eval output
(kind: ok) {"result":"early return"}
[2]
{"result":"early return"}
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
[null]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
[3]
@@ -0,0 +1,27 @@
import { arrayPush } from "shared-runtime";
function useFoo({ input, cond }) {
if (cond) {
return { result: "early return" };
}
// unconditional
const x = [];
arrayPush(x, input.a.b);
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: null, cond: true },
{ input: { a: { b: 2 } }, cond: false },
{ input: null, cond: true },
// preserve nullthrows
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
@@ -0,0 +1,83 @@
## Input
```javascript
import { arrayPush } from "shared-runtime";
function useFoo({ input, cond }) {
if (cond) {
throw new Error("throw with error!");
}
// unconditional
const x = [];
arrayPush(x, input.a.b);
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: null, cond: true },
{ input: { a: { b: 2 } }, cond: false },
{ input: null, cond: true },
// preserve nullthrows
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { arrayPush } from "shared-runtime";
function useFoo(t0) {
const $ = useMemoCache(2);
const { input, cond } = t0;
if (cond) {
throw new Error("throw with error!");
}
let x;
if ($[0] !== input.a.b) {
x = [];
arrayPush(x, input.a.b);
$[0] = input.a.b;
$[1] = x;
} else {
x = $[1];
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: null, cond: true },
{ input: { a: { b: 2 } }, cond: false },
{ input: null, cond: true },
// preserve nullthrows
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
```
### Eval output
(kind: ok) [[ (exception in render) Error: throw with error! ]]
[[ (exception in render) Error: throw with error! ]]
[[ (exception in render) Error: throw with error! ]]
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
[null]
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
[3]
@@ -0,0 +1,27 @@
import { arrayPush } from "shared-runtime";
function useFoo({ input, cond }) {
if (cond) {
throw new Error("throw with error!");
}
// unconditional
const x = [];
arrayPush(x, input.a.b);
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { a: { b: 2 } }, cond: false }],
sequentialRenders: [
{ input: null, cond: true },
{ input: { a: { b: 2 } }, cond: false },
{ input: null, cond: true },
// preserve nullthrows
{ input: {}, cond: false },
{ input: { a: { b: null } }, cond: false },
{ input: { a: null }, cond: false },
{ input: { a: { b: 3 } }, cond: false },
],
};
@@ -0,0 +1,101 @@
## Input
```javascript
import { identity } from "shared-runtime";
function useFoo({ input, inputHasAB, inputHasABC }) {
const x = [];
if (!inputHasABC) {
x.push(identity(input.a));
if (!inputHasAB) {
return null;
}
x.push(identity(input.a.b));
} else {
x.push(identity(input.a.b.c));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, inputHasAB: false, inputHasABC: false }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { identity } from "shared-runtime";
function useFoo(t0) {
const $ = useMemoCache(11);
const { input, inputHasAB, inputHasABC } = t0;
let x;
let t1;
if ($[0] !== inputHasABC || $[1] !== input.a || $[2] !== inputHasAB) {
t1 = Symbol.for("react.early_return_sentinel");
bb10: {
x = [];
if (!inputHasABC) {
let t2;
if ($[5] !== input.a) {
t2 = identity(input.a);
$[5] = input.a;
$[6] = t2;
} else {
t2 = $[6];
}
x.push(t2);
if (!inputHasAB) {
t1 = null;
break bb10;
}
let t3;
if ($[7] !== input.a.b) {
t3 = identity(input.a.b);
$[7] = input.a.b;
$[8] = t3;
} else {
t3 = $[8];
}
x.push(t3);
} else {
let t2;
if ($[9] !== input.a.b.c) {
t2 = identity(input.a.b.c);
$[9] = input.a.b.c;
$[10] = t2;
} else {
t2 = $[10];
}
x.push(t2);
}
}
$[0] = inputHasABC;
$[1] = input.a;
$[2] = inputHasAB;
$[3] = x;
$[4] = t1;
} else {
x = $[3];
t1 = $[4];
}
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, inputHasAB: false, inputHasABC: false }],
};
```
### Eval output
(kind: ok) null
@@ -0,0 +1,20 @@
import { identity } from "shared-runtime";
function useFoo({ input, inputHasAB, inputHasABC }) {
const x = [];
if (!inputHasABC) {
x.push(identity(input.a));
if (!inputHasAB) {
return null;
}
x.push(identity(input.a.b));
} else {
x.push(identity(input.a.b.c));
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: { b: 1 }, inputHasAB: false, inputHasABC: false }],
};