[compiler][cleanup] Remove unused enableReactiveScopesInHIR flag

Reactive scopes in HIR has been stable for over 3 months now and is the future direction of react compiler, removing this flag to reduce implementation forks.

ghstack-source-id: 65cdf63cf76029fa22d40fd85aba0ac976dcfc08
Pull Request resolved: https://github.com/facebook/react/pull/30891
This commit is contained in:
Mofei Zhang 2024-09-05 20:14:34 -04:00 committed by Mofei Zhang
parent a03254bc60
commit 43264a61d0
24 changed files with 47 additions and 1355 deletions

View File

@ -46,18 +46,13 @@ import {instructionReordering} from '../Optimization/InstructionReordering';
import {
CodegenFunction,
alignObjectMethodScopes,
alignReactiveScopesToBlockScopes,
assertScopeInstructionsWithinScopes,
assertWellFormedBreakTargets,
buildReactiveBlocks,
buildReactiveFunction,
codegenFunction,
extractScopeDeclarationsFromDestructuring,
flattenReactiveLoops,
flattenScopesWithHooksOrUse,
inferReactiveScopeVariables,
memoizeFbtAndMacroOperandsInSameScope,
mergeOverlappingReactiveScopes,
mergeReactiveScopesThatInvalidateTogether,
promoteUsedTemporaries,
propagateEarlyReturns,
@ -300,54 +295,52 @@ function* runWithEnvironment(
value: hir,
});
if (env.config.enableReactiveScopesInHIR) {
pruneUnusedLabelsHIR(hir);
yield log({
kind: 'hir',
name: 'PruneUnusedLabelsHIR',
value: hir,
});
pruneUnusedLabelsHIR(hir);
yield log({
kind: 'hir',
name: 'PruneUnusedLabelsHIR',
value: hir,
});
alignReactiveScopesToBlockScopesHIR(hir);
yield log({
kind: 'hir',
name: 'AlignReactiveScopesToBlockScopesHIR',
value: hir,
});
alignReactiveScopesToBlockScopesHIR(hir);
yield log({
kind: 'hir',
name: 'AlignReactiveScopesToBlockScopesHIR',
value: hir,
});
mergeOverlappingReactiveScopesHIR(hir);
yield log({
kind: 'hir',
name: 'MergeOverlappingReactiveScopesHIR',
value: hir,
});
assertValidBlockNesting(hir);
mergeOverlappingReactiveScopesHIR(hir);
yield log({
kind: 'hir',
name: 'MergeOverlappingReactiveScopesHIR',
value: hir,
});
assertValidBlockNesting(hir);
buildReactiveScopeTerminalsHIR(hir);
yield log({
kind: 'hir',
name: 'BuildReactiveScopeTerminalsHIR',
value: hir,
});
buildReactiveScopeTerminalsHIR(hir);
yield log({
kind: 'hir',
name: 'BuildReactiveScopeTerminalsHIR',
value: hir,
});
assertValidBlockNesting(hir);
assertValidBlockNesting(hir);
flattenReactiveLoopsHIR(hir);
yield log({
kind: 'hir',
name: 'FlattenReactiveLoopsHIR',
value: hir,
});
flattenReactiveLoopsHIR(hir);
yield log({
kind: 'hir',
name: 'FlattenReactiveLoopsHIR',
value: hir,
});
flattenScopesWithHooksOrUseHIR(hir);
yield log({
kind: 'hir',
name: 'FlattenScopesWithHooksOrUseHIR',
value: hir,
});
assertTerminalSuccessorsExist(hir);
assertTerminalPredsExist(hir);
}
flattenScopesWithHooksOrUseHIR(hir);
yield log({
kind: 'hir',
name: 'FlattenScopesWithHooksOrUseHIR',
value: hir,
});
assertTerminalSuccessorsExist(hir);
assertTerminalPredsExist(hir);
const reactiveFunction = buildReactiveFunction(hir);
yield log({
@ -364,44 +357,6 @@ function* runWithEnvironment(
name: 'PruneUnusedLabels',
value: reactiveFunction,
});
if (!env.config.enableReactiveScopesInHIR) {
alignReactiveScopesToBlockScopes(reactiveFunction);
yield log({
kind: 'reactive',
name: 'AlignReactiveScopesToBlockScopes',
value: reactiveFunction,
});
mergeOverlappingReactiveScopes(reactiveFunction);
yield log({
kind: 'reactive',
name: 'MergeOverlappingReactiveScopes',
value: reactiveFunction,
});
buildReactiveBlocks(reactiveFunction);
yield log({
kind: 'reactive',
name: 'BuildReactiveBlocks',
value: reactiveFunction,
});
flattenReactiveLoops(reactiveFunction);
yield log({
kind: 'reactive',
name: 'FlattenReactiveLoops',
value: reactiveFunction,
});
flattenScopesWithHooksOrUse(reactiveFunction);
yield log({
kind: 'reactive',
name: 'FlattenScopesWithHooks',
value: reactiveFunction,
});
}
assertScopeInstructionsWithinScopes(reactiveFunction);
propagateScopeDependencies(reactiveFunction);

View File

@ -222,8 +222,6 @@ const EnvironmentConfigSchema = z.object({
*/
enableUseTypeAnnotations: z.boolean().default(false),
enableReactiveScopesInHIR: z.boolean().default(true),
/**
* Enables inference of optional dependency chains. Without this flag
* a property chain such as `props?.items?.foo` will infer as a dep on

View File

@ -2,8 +2,6 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {Stringify, identity, makeArray, mutate} from 'shared-runtime';
/**
@ -37,8 +35,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
import { c as _c } from "react/compiler-runtime";
import { Stringify, identity, makeArray, mutate } from "shared-runtime";
/**
@ -52,11 +49,12 @@ import { Stringify, identity, makeArray, mutate } from "shared-runtime";
* handles this correctly.
*/
function Foo(t0) {
const $ = _c(4);
const $ = _c(3);
const { cond1, cond2 } = t0;
const arr = makeArray({ a: 2 }, 2, []);
let t1;
if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== arr) {
if ($[0] !== cond1 || $[1] !== cond2) {
const arr = makeArray({ a: 2 }, 2, []);
t1 = cond1 ? (
<>
<div>{identity("foo")}</div>
@ -65,10 +63,9 @@ function Foo(t0) {
) : null;
$[0] = cond1;
$[1] = cond2;
$[2] = arr;
$[3] = t1;
$[2] = t1;
} else {
t1 = $[3];
t1 = $[2];
}
return t1;
}

View File

@ -1,5 +1,3 @@
// @enableReactiveScopesInHIR:false
import {Stringify, identity, makeArray, mutate} from 'shared-runtime';
/**

View File

@ -1,61 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
/**
* This is a weird case as data has type `BuiltInMixedReadonly`.
* The only scoped value we currently infer in this program is the
* PropertyLoad `data?.toString`.
*/
import {useFragment} from 'shared-runtime';
function Foo() {
const data = useFragment();
return [data?.toString() || ''];
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
/**
* This is a weird case as data has type `BuiltInMixedReadonly`.
* The only scoped value we currently infer in this program is the
* PropertyLoad `data?.toString`.
*/
import { useFragment } from "shared-runtime";
function Foo() {
const $ = _c(2);
const data = useFragment();
const t0 = data?.toString() || "";
let t1;
if ($[0] !== t0) {
t1 = [t0];
$[0] = t0;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
### Eval output
(kind: ok) ["[object Object]"]

View File

@ -1,18 +0,0 @@
// @enableReactiveScopesInHIR:false
/**
* This is a weird case as data has type `BuiltInMixedReadonly`.
* The only scoped value we currently infer in this program is the
* PropertyLoad `data?.toString`.
*/
import {useFragment} from 'shared-runtime';
function Foo() {
const data = useFragment();
return [data?.toString() || ''];
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};

View File

@ -1,96 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {StaticText1, Stringify, identity, useHook} from 'shared-runtime';
/**
* `button` and `dispatcher` must end up in the same memo block. It would be
* invalid for `button` to take a dependency on `dispatcher` as dispatcher
* is created later.
*
* Sprout error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) "[[ function params=1 ]]"
* Forget:
* (kind: exception) Cannot access 'dispatcher' before initialization
*/
function useFoo({onClose}) {
const button = StaticText1 ?? (
<Stringify
primary={{
label: identity('label'),
onPress: onClose,
}}
secondary={{
onPress: () => {
dispatcher.go('route2');
},
}}
/>
);
const dispatcher = useHook();
return button;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{onClose: identity()}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
import { StaticText1, Stringify, identity, useHook } from "shared-runtime";
/**
* `button` and `dispatcher` must end up in the same memo block. It would be
* invalid for `button` to take a dependency on `dispatcher` as dispatcher
* is created later.
*
* Sprout error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) "[[ function params=1 ]]"
* Forget:
* (kind: exception) Cannot access 'dispatcher' before initialization
*/
function useFoo(t0) {
const $ = _c(3);
const { onClose } = t0;
let t1;
if ($[0] !== onClose || $[1] !== dispatcher) {
t1 = StaticText1 ?? (
<Stringify
primary={{ label: identity("label"), onPress: onClose }}
secondary={{
onPress: () => {
dispatcher.go("route2");
},
}}
/>
);
$[0] = onClose;
$[1] = dispatcher;
$[2] = t1;
} else {
t1 = $[2];
}
const button = t1;
const dispatcher = useHook();
return button;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ onClose: identity() }],
};
```

View File

@ -1,38 +0,0 @@
// @enableReactiveScopesInHIR:false
import {StaticText1, Stringify, identity, useHook} from 'shared-runtime';
/**
* `button` and `dispatcher` must end up in the same memo block. It would be
* invalid for `button` to take a dependency on `dispatcher` as dispatcher
* is created later.
*
* Sprout error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) "[[ function params=1 ]]"
* Forget:
* (kind: exception) Cannot access 'dispatcher' before initialization
*/
function useFoo({onClose}) {
const button = StaticText1 ?? (
<Stringify
primary={{
label: identity('label'),
onPress: onClose,
}}
secondary={{
onPress: () => {
dispatcher.go('route2');
},
}}
/>
);
const dispatcher = useHook();
return button;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{onClose: identity()}],
};

View File

@ -1,137 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {identity, mutate} from 'shared-runtime';
/**
* The root cause of this bug is in `InferReactiveScopeVariables`. Currently,
* InferReactiveScopeVariables do not ensure that maybe-aliased values get
* assigned the same reactive scope. This is safe only when an already-
* constructed value is captured, e.g.
* ```js
* const x = makeObj(); ⌝ mutable range of x
* mutate(x); ⌟
* <-- after this point, we can produce a canonical version
* of x for all following aliases
* const y = [];
* y.push(x); <-- y captures x
* ```
*
* However, if a value is captured/aliased during its mutable range and the
* capturing container is separately memoized, it becomes difficult to guarantee
* that all aliases refer to the same value.
*
* Sprout error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [{"wat0":"joe"},3]
* [{"wat0":"joe"},3]
* Forget:
* (kind: ok) [{"wat0":"joe"},3]
* [[ (exception in render) Error: oh no! ]]
*
*/
function useFoo({a, b}) {
const x = {a};
const y = {};
mutate(x);
const z = [identity(y), b];
mutate(y);
if (z[0] !== y) {
throw new Error('oh no!');
}
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 4, b: 3},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
import { identity, mutate } from "shared-runtime";
/**
* The root cause of this bug is in `InferReactiveScopeVariables`. Currently,
* InferReactiveScopeVariables do not ensure that maybe-aliased values get
* assigned the same reactive scope. This is safe only when an already-
* constructed value is captured, e.g.
* ```js
* const x = makeObj(); ⌝ mutable range of x
* mutate(x); ⌟
* <-- after this point, we can produce a canonical version
* of x for all following aliases
* const y = [];
* y.push(x); <-- y captures x
* ```
*
* However, if a value is captured/aliased during its mutable range and the
* capturing container is separately memoized, it becomes difficult to guarantee
* that all aliases refer to the same value.
*
* Sprout error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [{"wat0":"joe"},3]
* [{"wat0":"joe"},3]
* Forget:
* (kind: ok) [{"wat0":"joe"},3]
* [[ (exception in render) Error: oh no! ]]
*
*/
function useFoo(t0) {
const $ = _c(6);
const { a, b } = t0;
let z;
let y;
if ($[0] !== a || $[1] !== b) {
const x = { a };
y = {};
mutate(x);
let t1;
if ($[4] !== b) {
t1 = [identity(y), b];
$[4] = b;
$[5] = t1;
} else {
t1 = $[5];
}
z = t1;
mutate(y);
$[0] = a;
$[1] = b;
$[2] = z;
$[3] = y;
} else {
z = $[2];
y = $[3];
}
if (z[0] !== y) {
throw new Error("oh no!");
}
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ a: 2, b: 3 }],
sequentialRenders: [
{ a: 2, b: 3 },
{ a: 4, b: 3 },
],
};
```

View File

@ -1,52 +0,0 @@
// @enableReactiveScopesInHIR:false
import {identity, mutate} from 'shared-runtime';
/**
* The root cause of this bug is in `InferReactiveScopeVariables`. Currently,
* InferReactiveScopeVariables do not ensure that maybe-aliased values get
* assigned the same reactive scope. This is safe only when an already-
* constructed value is captured, e.g.
* ```js
* const x = makeObj(); mutable range of x
* mutate(x);
* <-- after this point, we can produce a canonical version
* of x for all following aliases
* const y = [];
* y.push(x); <-- y captures x
* ```
*
* However, if a value is captured/aliased during its mutable range and the
* capturing container is separately memoized, it becomes difficult to guarantee
* that all aliases refer to the same value.
*
* Sprout error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [{"wat0":"joe"},3]
* [{"wat0":"joe"},3]
* Forget:
* (kind: ok) [{"wat0":"joe"},3]
* [[ (exception in render) Error: oh no! ]]
*
*/
function useFoo({a, b}) {
const x = {a};
const y = {};
mutate(x);
const z = [identity(y), b];
mutate(y);
if (z[0] !== y) {
throw new Error('oh no!');
}
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 4, b: 3},
],
};

View File

@ -1,51 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {useRef} from 'react';
import {addOne} from 'shared-runtime';
function useKeyCommand() {
const currentPosition = useRef(0);
const handleKey = direction => () => {
const position = currentPosition.current;
const nextPosition = direction === 'left' ? addOne(position) : position;
currentPosition.current = nextPosition;
};
const moveLeft = {
handler: handleKey('left'),
};
const moveRight = {
handler: handleKey('right'),
};
return [moveLeft, moveRight];
}
export const FIXTURE_ENTRYPOINT = {
fn: useKeyCommand,
params: [],
};
```
## Error
```
11 | };
12 | const moveLeft = {
> 13 | handler: handleKey('left'),
| ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (13:13)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (13:13)
InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (16:16)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (16:16)
14 | };
15 | const moveRight = {
16 | handler: handleKey('right'),
```

View File

@ -1,24 +0,0 @@
// @enableReactiveScopesInHIR:false
import {useRef} from 'react';
import {addOne} from 'shared-runtime';
function useKeyCommand() {
const currentPosition = useRef(0);
const handleKey = direction => () => {
const position = currentPosition.current;
const nextPosition = direction === 'left' ? addOne(position) : position;
currentPosition.current = nextPosition;
};
const moveLeft = {
handler: handleKey('left'),
};
const moveRight = {
handler: handleKey('right'),
};
return [moveLeft, moveRight];
}
export const FIXTURE_ENTRYPOINT = {
fn: useKeyCommand,
params: [],
};

View File

@ -1,84 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {Stringify, identity, makeArray, mutate} from 'shared-runtime';
/**
* Here, identity('foo') is an immutable allocating instruction.
* `arr` is a mutable value whose mutable range ends at `arr.map`.
*
* The previous (reactive function) version of alignScopesToBlocks set the range of
* both scopes to end at value blocks within the <></> expression.
* However, both scope ranges should be aligned to the outer value block
* (e.g. `cond1 ? <>: null`). The HIR version of alignScopesToBlocks
* handles this correctly.
*/
function Foo({cond1, cond2}) {
const arr = makeArray<any>({a: 2}, 2, []);
return cond1 ? (
<>
<div>{identity('foo')}</div>
<Stringify value={cond2 ? arr.map(mutate) : null} />
</>
) : null;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond1: true, cond2: true}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
import { Stringify, identity, makeArray, mutate } from "shared-runtime";
/**
* Here, identity('foo') is an immutable allocating instruction.
* `arr` is a mutable value whose mutable range ends at `arr.map`.
*
* The previous (reactive function) version of alignScopesToBlocks set the range of
* both scopes to end at value blocks within the <></> expression.
* However, both scope ranges should be aligned to the outer value block
* (e.g. `cond1 ? <>: null`). The HIR version of alignScopesToBlocks
* handles this correctly.
*/
function Foo(t0) {
const $ = _c(4);
const { cond1, cond2 } = t0;
const arr = makeArray({ a: 2 }, 2, []);
let t1;
if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== arr) {
t1 = cond1 ? (
<>
<div>{identity("foo")}</div>
<Stringify value={cond2 ? arr.map(mutate) : null} />
</>
) : null;
$[0] = cond1;
$[1] = cond2;
$[2] = arr;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ cond1: true, cond2: true }],
};
```
### Eval output
(kind: ok) <div>foo</div><div>{"value":[null,null,null]}</div>

View File

@ -1,29 +0,0 @@
// @enableReactiveScopesInHIR:false
import {Stringify, identity, makeArray, mutate} from 'shared-runtime';
/**
* Here, identity('foo') is an immutable allocating instruction.
* `arr` is a mutable value whose mutable range ends at `arr.map`.
*
* The previous (reactive function) version of alignScopesToBlocks set the range of
* both scopes to end at value blocks within the <></> expression.
* However, both scope ranges should be aligned to the outer value block
* (e.g. `cond1 ? <>: null`). The HIR version of alignScopesToBlocks
* handles this correctly.
*/
function Foo({cond1, cond2}) {
const arr = makeArray<any>({a: 2}, 2, []);
return cond1 ? (
<>
<div>{identity('foo')}</div>
<Stringify value={cond2 ? arr.map(mutate) : null} />
</>
) : null;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond1: true, cond2: true}],
};

View File

@ -1,101 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {CONST_TRUE, identity, shallowCopy} from 'shared-runtime';
/**
* There are three values with their own scopes in this fixture.
* - arr, whose mutable range extends to the `mutate(...)` call
* - cond, which has a mutable range of exactly 1 (e.g. created but not
* mutated)
* - { val: CONST_TRUE }, which is also not mutated after creation. However,
* its scope range becomes extended to the value block.
*
* After AlignScopesToBlockScopes, our scopes look roughly like this
* ```js
* [1] arr = shallowCopy() ⌝@0
* [2] cond = identity() <- @1 |
* [3] $0 = Ternary test=cond ⌝@2 |
* [4] {val : CONST_TRUE} | |
* [5] mutate(arr) | |
* [6] return $0 ⌟ ⌟
* ```
*
* Observe that instruction 5 mutates scope 0, which means that scopes 0 and 2
* should be merged.
*/
function useFoo({input}) {
const arr = shallowCopy(input);
const cond = identity(false);
return cond ? {val: CONST_TRUE} : mutate(arr);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{input: 3}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
import { CONST_TRUE, identity, shallowCopy } from "shared-runtime";
/**
* There are three values with their own scopes in this fixture.
* - arr, whose mutable range extends to the `mutate(...)` call
* - cond, which has a mutable range of exactly 1 (e.g. created but not
* mutated)
* - { val: CONST_TRUE }, which is also not mutated after creation. However,
* its scope range becomes extended to the value block.
*
* After AlignScopesToBlockScopes, our scopes look roughly like this
* ```js
* [1] arr = shallowCopy() ⌝@0
* [2] cond = identity() <- @1 |
* [3] $0 = Ternary test=cond ⌝@2 |
* [4] {val : CONST_TRUE} | |
* [5] mutate(arr) | |
* [6] return $0 ⌟ ⌟
* ```
*
* Observe that instruction 5 mutates scope 0, which means that scopes 0 and 2
* should be merged.
*/
function useFoo(t0) {
const $ = _c(3);
const { input } = t0;
const arr = shallowCopy(input);
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = identity(false);
$[0] = t1;
} else {
t1 = $[0];
}
const cond = t1;
let t2;
if ($[1] !== arr) {
t2 = cond ? { val: CONST_TRUE } : mutate(arr);
$[1] = arr;
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ input: 3 }],
};
```
### Eval output
(kind: exception) mutate is not defined

View File

@ -1,35 +0,0 @@
// @enableReactiveScopesInHIR:false
import {CONST_TRUE, identity, shallowCopy} from 'shared-runtime';
/**
* There are three values with their own scopes in this fixture.
* - arr, whose mutable range extends to the `mutate(...)` call
* - cond, which has a mutable range of exactly 1 (e.g. created but not
* mutated)
* - { val: CONST_TRUE }, which is also not mutated after creation. However,
* its scope range becomes extended to the value block.
*
* After AlignScopesToBlockScopes, our scopes look roughly like this
* ```js
* [1] arr = shallowCopy() @0
* [2] cond = identity() <- @1 |
* [3] $0 = Ternary test=cond @2 |
* [4] {val : CONST_TRUE} | |
* [5] mutate(arr) | |
* [6] return $0
* ```
*
* Observe that instruction 5 mutates scope 0, which means that scopes 0 and 2
* should be merged.
*/
function useFoo({input}) {
const arr = shallowCopy(input);
const cond = identity(false);
return cond ? {val: CONST_TRUE} : mutate(arr);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{input: 3}],
};

View File

@ -1,92 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {mutate} from 'shared-runtime';
/**
* This test fixture is similar to mutation-within-jsx. The only difference
* is that there is no `freeze` effect here, which means that `z` may be
* mutated after its memo block through mutating `y`.
*
* While this is technically correct (as `z` is a nested memo block), it
* is an edge case as we believe that values are not mutated after their
* memo blocks (which may lead to 'tearing', i.e. mutating one render's
* values in a subsequent render.
*/
function useFoo({a, b}) {
// x and y's scopes start here
const x = {a};
const y = [b];
mutate(x);
// z captures the result of `mutate(y)`, which may be aliased to `y`.
const z = [mutate(y)];
// the following line may also mutate z
mutate(y);
// and end here
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{a: 2, b: 3}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
import { mutate } from "shared-runtime";
/**
* This test fixture is similar to mutation-within-jsx. The only difference
* is that there is no `freeze` effect here, which means that `z` may be
* mutated after its memo block through mutating `y`.
*
* While this is technically correct (as `z` is a nested memo block), it
* is an edge case as we believe that values are not mutated after their
* memo blocks (which may lead to 'tearing', i.e. mutating one render's
* values in a subsequent render.
*/
function useFoo(t0) {
const $ = _c(5);
const { a, b } = t0;
let z;
if ($[0] !== a || $[1] !== b) {
const x = { a };
const y = [b];
mutate(x);
const t1 = mutate(y);
let t2;
if ($[3] !== t1) {
t2 = [t1];
$[3] = t1;
$[4] = t2;
} else {
t2 = $[4];
}
z = t2;
mutate(y);
$[0] = a;
$[1] = b;
$[2] = z;
} else {
z = $[2];
}
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ a: 2, b: 3 }],
};
```
### Eval output
(kind: ok) [null]

View File

@ -1,30 +0,0 @@
// @enableReactiveScopesInHIR:false
import {mutate} from 'shared-runtime';
/**
* This test fixture is similar to mutation-within-jsx. The only difference
* is that there is no `freeze` effect here, which means that `z` may be
* mutated after its memo block through mutating `y`.
*
* While this is technically correct (as `z` is a nested memo block), it
* is an edge case as we believe that values are not mutated after their
* memo blocks (which may lead to 'tearing', i.e. mutating one render's
* values in a subsequent render.
*/
function useFoo({a, b}) {
// x and y's scopes start here
const x = {a};
const y = [b];
mutate(x);
// z captures the result of `mutate(y)`, which may be aliased to `y`.
const z = [mutate(y)];
// the following line may also mutate z
mutate(y);
// and end here
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{a: 2, b: 3}],
};

View File

@ -1,99 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {
Stringify,
makeObject_Primitives,
mutate,
mutateAndReturn,
} from 'shared-runtime';
function useFoo({data}) {
let obj = null;
let myDiv = null;
label: {
if (data.cond) {
obj = makeObject_Primitives();
if (data.cond1) {
myDiv = <Stringify value={mutateAndReturn(obj)} />;
break label;
}
mutate(obj);
}
}
return myDiv;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{data: {cond: true, cond1: true}}],
sequentialRenders: [
{data: {cond: true, cond1: true}},
{data: {cond: true, cond1: true}},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
import {
Stringify,
makeObject_Primitives,
mutate,
mutateAndReturn,
} from "shared-runtime";
function useFoo(t0) {
const $ = _c(5);
const { data } = t0;
let obj;
let myDiv = null;
bb0: if (data.cond) {
if ($[0] !== data.cond1) {
obj = makeObject_Primitives();
if (data.cond1) {
const t1 = mutateAndReturn(obj);
let t2;
if ($[3] !== t1) {
t2 = <Stringify value={t1} />;
$[3] = t1;
$[4] = t2;
} else {
t2 = $[4];
}
myDiv = t2;
break bb0;
}
mutate(obj);
$[0] = data.cond1;
$[1] = obj;
$[2] = myDiv;
} else {
obj = $[1];
myDiv = $[2];
}
}
return myDiv;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ data: { cond: true, cond1: true } }],
sequentialRenders: [
{ data: { cond: true, cond1: true } },
{ data: { cond: true, cond1: true } },
],
};
```
### Eval output
(kind: ok) <div>{"value":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
<div>{"value":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>

View File

@ -1,33 +0,0 @@
// @enableReactiveScopesInHIR:false
import {
Stringify,
makeObject_Primitives,
mutate,
mutateAndReturn,
} from 'shared-runtime';
function useFoo({data}) {
let obj = null;
let myDiv = null;
label: {
if (data.cond) {
obj = makeObject_Primitives();
if (data.cond1) {
myDiv = <Stringify value={mutateAndReturn(obj)} />;
break label;
}
mutate(obj);
}
}
return myDiv;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{data: {cond: true, cond1: true}}],
sequentialRenders: [
{data: {cond: true, cond1: true}},
{data: {cond: true, cond1: true}},
],
};

View File

@ -1,141 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {
Stringify,
makeObject_Primitives,
mutateAndReturn,
} from 'shared-runtime';
/**
* In this example, the `<Stringify ... />` JSX block mutates then captures obj.
* As JSX expressions freeze their values, we know that `obj` and `myDiv` cannot
* be mutated past this.
* This set of mutable range + scopes is an edge case because the JSX expression
* references values in two scopes.
* - (freeze) the result of `mutateAndReturn`
* this is a mutable value with a mutable range starting at `makeObject()`
* - (mutate) the lvalue storing the result of `<Stringify .../>`
* this is a immutable value and so gets assigned a different scope
*
* obj@0 = makeObj(); ⌝ scope@0
* if (cond) { |
* $1@0 = mutate(obj@0); |
* myDiv@1 = JSX $1@0 <- scope@1 |
* } ⌟
*
* Coincidentally, the range of `obj` is extended by alignScopesToBlocks to *past*
* the end of the JSX instruction. As we currently alias identifier mutableRanges to
* scope ranges, this `freeze` reference is perceived as occurring during the mutable
* range of `obj` (even though it is after the last mutating reference).
*
* This case is technically safe as `myDiv` correctly takes `obj` as a dependency. As
* a result, developers can never observe myDiv can aliasing a different value generation
* than `obj` (e.g. the invariant `myDiv.props.value === obj` always holds).
*/
function useFoo({data}) {
let obj = null;
let myDiv = null;
if (data.cond) {
obj = makeObject_Primitives();
if (data.cond1) {
myDiv = <Stringify value={mutateAndReturn(obj)} />;
}
}
return myDiv;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{data: {cond: true, cond1: true}}],
sequentialRenders: [
{data: {cond: true, cond1: true}},
{data: {cond: true, cond1: true}},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
import {
Stringify,
makeObject_Primitives,
mutateAndReturn,
} from "shared-runtime";
/**
* In this example, the `<Stringify ... />` JSX block mutates then captures obj.
* As JSX expressions freeze their values, we know that `obj` and `myDiv` cannot
* be mutated past this.
* This set of mutable range + scopes is an edge case because the JSX expression
* references values in two scopes.
* - (freeze) the result of `mutateAndReturn`
* this is a mutable value with a mutable range starting at `makeObject()`
* - (mutate) the lvalue storing the result of `<Stringify .../>`
* this is a immutable value and so gets assigned a different scope
*
* obj@0 = makeObj(); ⌝ scope@0
* if (cond) { |
* $1@0 = mutate(obj@0); |
* myDiv@1 = JSX $1@0 <- scope@1 |
* } ⌟
*
* Coincidentally, the range of `obj` is extended by alignScopesToBlocks to *past*
* the end of the JSX instruction. As we currently alias identifier mutableRanges to
* scope ranges, this `freeze` reference is perceived as occurring during the mutable
* range of `obj` (even though it is after the last mutating reference).
*
* This case is technically safe as `myDiv` correctly takes `obj` as a dependency. As
* a result, developers can never observe myDiv can aliasing a different value generation
* than `obj` (e.g. the invariant `myDiv.props.value === obj` always holds).
*/
function useFoo(t0) {
const $ = _c(5);
const { data } = t0;
let obj;
let myDiv = null;
if (data.cond) {
if ($[0] !== data.cond1) {
obj = makeObject_Primitives();
if (data.cond1) {
const t1 = mutateAndReturn(obj);
let t2;
if ($[3] !== t1) {
t2 = <Stringify value={t1} />;
$[3] = t1;
$[4] = t2;
} else {
t2 = $[4];
}
myDiv = t2;
}
$[0] = data.cond1;
$[1] = obj;
$[2] = myDiv;
} else {
obj = $[1];
myDiv = $[2];
}
}
return myDiv;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ data: { cond: true, cond1: true } }],
sequentialRenders: [
{ data: { cond: true, cond1: true } },
{ data: { cond: true, cond1: true } },
],
};
```
### Eval output
(kind: ok) <div>{"value":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
<div>{"value":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>

View File

@ -1,53 +0,0 @@
// @enableReactiveScopesInHIR:false
import {
Stringify,
makeObject_Primitives,
mutateAndReturn,
} from 'shared-runtime';
/**
* In this example, the `<Stringify ... />` JSX block mutates then captures obj.
* As JSX expressions freeze their values, we know that `obj` and `myDiv` cannot
* be mutated past this.
* This set of mutable range + scopes is an edge case because the JSX expression
* references values in two scopes.
* - (freeze) the result of `mutateAndReturn`
* this is a mutable value with a mutable range starting at `makeObject()`
* - (mutate) the lvalue storing the result of `<Stringify .../>`
* this is a immutable value and so gets assigned a different scope
*
* obj@0 = makeObj(); scope@0
* if (cond) { |
* $1@0 = mutate(obj@0); |
* myDiv@1 = JSX $1@0 <- scope@1 |
* }
*
* Coincidentally, the range of `obj` is extended by alignScopesToBlocks to *past*
* the end of the JSX instruction. As we currently alias identifier mutableRanges to
* scope ranges, this `freeze` reference is perceived as occurring during the mutable
* range of `obj` (even though it is after the last mutating reference).
*
* This case is technically safe as `myDiv` correctly takes `obj` as a dependency. As
* a result, developers can never observe myDiv can aliasing a different value generation
* than `obj` (e.g. the invariant `myDiv.props.value === obj` always holds).
*/
function useFoo({data}) {
let obj = null;
let myDiv = null;
if (data.cond) {
obj = makeObject_Primitives();
if (data.cond1) {
myDiv = <Stringify value={mutateAndReturn(obj)} />;
}
}
return myDiv;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{data: {cond: true, cond1: true}}],
sequentialRenders: [
{data: {cond: true, cond1: true}},
{data: {cond: true, cond1: true}},
],
};

View File

@ -1,61 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {identity, makeObject_Primitives} from 'shared-runtime';
function useTest({cond}) {
const val = makeObject_Primitives();
useHook();
/**
* We don't technically need a reactive scope for this ternary as
* it cannot produce newly allocated values.
* While identity(...) may allocate, we can teach the compiler that
* its result is only used as as a test condition
*/
const result = identity(cond) ? val : null;
return result;
}
export const FIXTURE_ENTRYPOINT = {
fn: useTest,
params: [{cond: true}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
import { identity, makeObject_Primitives } from "shared-runtime";
function useTest(t0) {
const $ = _c(1);
const { cond } = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = makeObject_Primitives();
$[0] = t1;
} else {
t1 = $[0];
}
const val = t1;
useHook();
const result = identity(cond) ? val : null;
return result;
}
export const FIXTURE_ENTRYPOINT = {
fn: useTest,
params: [{ cond: true }],
};
```
### Eval output
(kind: exception) useHook is not defined

View File

@ -1,21 +0,0 @@
// @enableReactiveScopesInHIR:false
import {identity, makeObject_Primitives} from 'shared-runtime';
function useTest({cond}) {
const val = makeObject_Primitives();
useHook();
/**
* We don't technically need a reactive scope for this ternary as
* it cannot produce newly allocated values.
* While identity(...) may allocate, we can teach the compiler that
* its result is only used as as a test condition
*/
const result = identity(cond) ? val : null;
return result;
}
export const FIXTURE_ENTRYPOINT = {
fn: useTest,
params: [{cond: true}],
};