Test cases for reactive (control) dependencies

Adds test cases for all the cases of control dependencies that I can think of. 
We don't currently handle control dependencies correctly in any of these cases. 

There's also another test case which demonstrates why reactive dependency 
inference needs to be fixpoint, even for non-control dependencies.
This commit is contained in:
Joe Savona
2023-10-17 09:48:23 +01:00
parent 4fc392e628
commit 84da8994bd
25 changed files with 1011 additions and 0 deletions
@@ -0,0 +1,64 @@
## Input
```javascript
function Component(props) {
let x;
let i = 0;
do {
if (i > 10) {
x = 10;
} else {
x = 1;
}
i++;
} while (i < props.test);
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose value is affected by
// `props.test` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: 12 }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let i = 0;
do {
let x = undefined;
if (i > 10) {
x = 10;
} else {
x = 1;
}
i++;
} while (i < props.test);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: 12 }],
};
```
@@ -0,0 +1,22 @@
function Component(props) {
let x;
let i = 0;
do {
if (i > 10) {
x = 10;
} else {
x = 1;
}
i++;
} while (i < props.test);
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose value is affected by
// `props.test` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: 12 }],
};
@@ -0,0 +1,61 @@
## Input
```javascript
function Component(props) {
let x;
for (let i = props.init; i < 10; i++) {
if (i === 0) {
x = 0;
break;
} else {
x = 1;
break;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose initial value `props.init` is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ init: 0 }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x;
for (const i = props.init; i < 10; ) {
if (i === 0) {
x = 0;
break;
} else {
x = 1;
break;
}
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ init: 0 }],
};
```
@@ -0,0 +1,21 @@
function Component(props) {
let x;
for (let i = props.init; i < 10; i++) {
if (i === 0) {
x = 0;
break;
} else {
x = 1;
break;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose initial value `props.init` is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ init: 0 }],
};
@@ -0,0 +1,58 @@
## Input
```javascript
function Component(props) {
let x;
for (let i = 0; i < props.test; i++) {
if (i > 10) {
x = 10;
} else {
x = 1;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose value is capped by
// `props.test` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: 12 }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x;
for (let i = 0; i < props.test; i++) {
if (i > 10) {
x = 10;
} else {
x = 1;
}
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: 12 }],
};
```
@@ -0,0 +1,20 @@
function Component(props) {
let x;
for (let i = 0; i < props.test; i++) {
if (i > 10) {
x = 10;
} else {
x = 1;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose value is capped by
// `props.test` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: 12 }],
};
@@ -0,0 +1,58 @@
## Input
```javascript
function Component(props) {
let x;
for (let i = 0; i < 10; i += props.update) {
if (i > 0 && i % 2 === 0) {
x = 2;
} else {
x = 1;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose possible values are
// affected by `props.update` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ update: 2 }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x;
for (let i = 0; i < 10; i = i + props.update, i) {
if (i > 0 && i % 2 === 0) {
x = 2;
} else {
x = 1;
}
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ update: 2 }],
};
```
@@ -0,0 +1,20 @@
function Component(props) {
let x;
for (let i = 0; i < 10; i += props.update) {
if (i > 0 && i % 2 === 0) {
x = 2;
} else {
x = 1;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose possible values are
// affected by `props.update` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ update: 2 }],
};
@@ -0,0 +1,60 @@
## Input
```javascript
function Component(props) {
let x;
for (const key in props.values) {
const i = parseInt(key, 10);
if (i > 10) {
x = 10;
} else {
x = 1;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose value is derived from
// `props.values` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ values: { "12": true } }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x;
for (const key in props.values) {
const i = parseInt(key, 10);
if (i > 10) {
x = 10;
} else {
x = 1;
}
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ values: { "12": true } }],
};
```
@@ -0,0 +1,21 @@
function Component(props) {
let x;
for (const key in props.values) {
const i = parseInt(key, 10);
if (i > 10) {
x = 10;
} else {
x = 1;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose value is derived from
// `props.values` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ values: { "12": true } }],
};
@@ -0,0 +1,58 @@
## Input
```javascript
function Component(props) {
let x;
for (const i of props.values) {
if (i > 10) {
x = 10;
} else {
x = 1;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose value is derived from
// `props.values` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ values: [12] }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x;
for (const i of props.values) {
if (i > 10) {
x = 10;
} else {
x = 1;
}
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ values: [12] }],
};
```
@@ -0,0 +1,20 @@
function Component(props) {
let x;
for (const i of props.values) {
if (i > 10) {
x = 10;
} else {
x = 1;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose value is derived from
// `props.values` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ values: [12] }],
};
@@ -0,0 +1,53 @@
## Input
```javascript
function Component(props) {
let x;
if (props.cond) {
x = 1;
} else {
x = 2;
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" value `props.cond` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x = undefined;
if (props.cond) {
x = 1;
} else {
x = 2;
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
};
```
@@ -0,0 +1,17 @@
function Component(props) {
let x;
if (props.cond) {
x = 1;
} else {
x = 2;
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" value `props.cond` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
};
@@ -0,0 +1,78 @@
## Input
```javascript
function Component(props) {
let x = 0;
let value = null;
loop: for (let i = 0; i < 10; i++) {
switch (value) {
case true: {
x = 1;
break loop;
}
case false: {
x = 2;
break loop;
}
}
value = props.value;
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `value` used as the switch test
// condition. That variable is initially null on the first iteration
// of the loop, but is later set to `props.value` which is reactive.
// Therefore x should be treated as reactive.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x = 0;
let value = null;
for (let i = 0; i < 10; i++) {
switch (value) {
case true: {
x = 1;
break;
}
case false: {
x = 2;
break;
}
}
value = props.value;
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
};
```
@@ -0,0 +1,31 @@
function Component(props) {
let x = 0;
let value = null;
loop: for (let i = 0; i < 10; i++) {
switch (value) {
case true: {
x = 1;
break loop;
}
case false: {
x = 2;
break loop;
}
}
value = props.value;
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `value` used as the switch test
// condition. That variable is initially null on the first iteration
// of the loop, but is later set to `props.value` which is reactive.
// Therefore x should be treated as reactive.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
};
@@ -0,0 +1,69 @@
## Input
```javascript
function Component(props) {
let x;
switch (props.cond) {
case true: {
x = 1;
break;
}
case false: {
x = 2;
break;
}
default: {
x = 3;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" value `props.cond` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x = undefined;
bb1: switch (props.cond) {
case true: {
x = 1;
break bb1;
}
case false: {
x = 2;
break bb1;
}
default: {
x = 3;
}
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
};
```
@@ -0,0 +1,25 @@
function Component(props) {
let x;
switch (props.cond) {
case true: {
x = 1;
break;
}
case false: {
x = 2;
break;
}
default: {
x = 3;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" value `props.cond` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
};
@@ -0,0 +1,68 @@
## Input
```javascript
const GLOBAL = 42;
function Component({ value }) {
let x;
switch (GLOBAL) {
case value: {
x = 1;
break;
}
default: {
x = 2;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" value `props.value` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: GLOBAL }],
// TODO: test executing the sequence {value: GLOBAL}, {value: null}, {value: GLOBAL}
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
const GLOBAL = 42;
function Component(t14) {
const $ = useMemoCache(1);
const { value } = t14;
let x = undefined;
bb1: switch (GLOBAL) {
case value: {
x = 1;
break bb1;
}
default: {
x = 2;
}
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: GLOBAL }],
// TODO: test executing the sequence {value: GLOBAL}, {value: null}, {value: GLOBAL}
};
```
@@ -0,0 +1,24 @@
const GLOBAL = 42;
function Component({ value }) {
let x;
switch (GLOBAL) {
case value: {
x = 1;
break;
}
default: {
x = 2;
}
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" value `props.value` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: GLOBAL }],
// TODO: test executing the sequence {value: GLOBAL}, {value: null}, {value: GLOBAL}
};
@@ -0,0 +1,63 @@
## Input
```javascript
function Component(props) {
let x;
let i = 0;
while (i < props.test) {
if (i > 10) {
x = 10;
} else {
x = 1;
}
i++;
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose value is affected by
// `props.test` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: 12 }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x;
let i = 0;
while (i < props.test) {
if (i > 10) {
x = 10;
} else {
x = 1;
}
i++;
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: 12 }],
};
```
@@ -0,0 +1,22 @@
function Component(props) {
let x;
let i = 0;
while (i < props.test) {
if (i > 10) {
x = 10;
} else {
x = 1;
}
i++;
}
// The values assigned to `x` are non-reactive, but the value of `x`
// depends on the "control" variable `i`, whose value is affected by
// `props.test` which is reactive.
// Therefore x should be treated as reactive too.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: 12 }],
};
@@ -0,0 +1,56 @@
## Input
```javascript
function Component(props) {
let x = 0;
let y = 0;
while (x === 0) {
x = y;
y = props.value;
}
// x and y initially start out with non-reactive values,
// but after an iteration of the loop y becomes reactive,
// and this reactive value then flows into x on the next
// loop iteration, making x reactive.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x = 0;
let y = 0;
while (x === 0) {
x = y;
y = props.value;
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [x];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
};
```
@@ -0,0 +1,20 @@
function Component(props) {
let x = 0;
let y = 0;
while (x === 0) {
x = y;
y = props.value;
}
// x and y initially start out with non-reactive values,
// but after an iteration of the loop y becomes reactive,
// and this reactive value then flows into x on the next
// loop iteration, making x reactive.
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
};
@@ -416,6 +416,8 @@ const skipFilter = new Set([
"readonly-object-method-calls",
"readonly-object-method-calls-mutable-lambda",
"bug.reactive-control-dependency-do-while-test",
// TODO: 🌲
"forest-basic",