Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b370db084e |
@@ -27,7 +27,6 @@ import {
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {retainWhere} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
* Validates that a function does not access a ref value during render. This includes a partial check
|
||||
@@ -280,10 +279,9 @@ function validateNoRefAccessInRenderImpl(
|
||||
for (let i = 0; (i == 0 || env.hasChanged()) && i < 10; i++) {
|
||||
env.resetChanged();
|
||||
returnValues = [];
|
||||
const safeBlocks: Array<{block: BlockId; ref: RefId}> = [];
|
||||
const safeBlocks = new Map<BlockId, RefId>();
|
||||
const errors = new CompilerError();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
retainWhere(safeBlocks, entry => entry.block !== block.id);
|
||||
for (const phi of block.phis) {
|
||||
env.set(
|
||||
phi.place.identifier.id,
|
||||
@@ -434,12 +432,7 @@ function validateNoRefAccessInRenderImpl(
|
||||
* By default we check that function call operands are not refs,
|
||||
* ref values, or functions that can access refs.
|
||||
*/
|
||||
if (
|
||||
isRefLValue ||
|
||||
(hookKind != null &&
|
||||
hookKind !== 'useState' &&
|
||||
hookKind !== 'useReducer')
|
||||
) {
|
||||
if (isRefLValue || hookKind != null) {
|
||||
/**
|
||||
* Special cases:
|
||||
*
|
||||
@@ -510,17 +503,15 @@ function validateNoRefAccessInRenderImpl(
|
||||
case 'PropertyStore':
|
||||
case 'ComputedDelete':
|
||||
case 'ComputedStore': {
|
||||
const safe = safeBlocks.get(block.id);
|
||||
const target = env.get(instr.value.object.identifier.id);
|
||||
let safe: (typeof safeBlocks)['0'] | null | undefined = null;
|
||||
if (
|
||||
instr.value.kind === 'PropertyStore' &&
|
||||
target != null &&
|
||||
target.kind === 'Ref'
|
||||
safe != null &&
|
||||
target?.kind === 'Ref' &&
|
||||
target.refId === safe
|
||||
) {
|
||||
safe = safeBlocks.find(entry => entry.ref === target.refId);
|
||||
}
|
||||
if (safe != null) {
|
||||
retainWhere(safeBlocks, entry => entry !== safe);
|
||||
safeBlocks.delete(block.id);
|
||||
} else {
|
||||
validateNoRefUpdate(errors, env, instr.value.object, instr.loc);
|
||||
}
|
||||
@@ -608,11 +599,8 @@ function validateNoRefAccessInRenderImpl(
|
||||
|
||||
if (block.terminal.kind === 'if') {
|
||||
const test = env.get(block.terminal.test.identifier.id);
|
||||
if (
|
||||
test?.kind === 'Guard' &&
|
||||
safeBlocks.find(entry => entry.ref === test.refId) == null
|
||||
) {
|
||||
safeBlocks.push({block: block.terminal.fallthrough, ref: test.refId});
|
||||
if (test?.kind === 'Guard') {
|
||||
safeBlocks.set(block.terminal.consequent, test.refId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender
|
||||
|
||||
import {useRef} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const ref = useRef(null);
|
||||
if (ref.current == null) {
|
||||
// the logical means the ref write is in a different block
|
||||
// from the if consequent. this tests that the "safe" blocks
|
||||
// extend up to the if's fallthrough
|
||||
ref.current = props.unknownKey ?? props.value;
|
||||
}
|
||||
return <Child ref={ref} />;
|
||||
}
|
||||
|
||||
function Child({ref}) {
|
||||
'use no memo';
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRender
|
||||
|
||||
import { useRef } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(1);
|
||||
const ref = useRef(null);
|
||||
if (ref.current == null) {
|
||||
ref.current = props.unknownKey ?? props.value;
|
||||
}
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <Child ref={ref} />;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
function Child({ ref }) {
|
||||
"use no memo";
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 42 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) 42
|
||||
@@ -1,24 +0,0 @@
|
||||
// @validateRefAccessDuringRender
|
||||
|
||||
import {useRef} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const ref = useRef(null);
|
||||
if (ref.current == null) {
|
||||
// the logical means the ref write is in a different block
|
||||
// from the if consequent. this tests that the "safe" blocks
|
||||
// extend up to the if's fallthrough
|
||||
ref.current = props.unknownKey ?? props.value;
|
||||
}
|
||||
return <Child ref={ref} />;
|
||||
}
|
||||
|
||||
function Child({ref}) {
|
||||
'use no memo';
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useReducer, useRef} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const ref = useRef(props.value);
|
||||
const [state] = useReducer(
|
||||
(state, action) => state + action,
|
||||
0,
|
||||
init => ref.current
|
||||
);
|
||||
|
||||
return <Stringify state={state} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
|
||||
|
||||
error.invalid-access-ref-in-reducer-init.ts:8:4
|
||||
6 | (state, action) => state + action,
|
||||
7 | 0,
|
||||
> 8 | init => ref.current
|
||||
| ^^^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
|
||||
9 | );
|
||||
10 |
|
||||
11 | return <Stringify state={state} />;
|
||||
```
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import {useReducer, useRef} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const ref = useRef(props.value);
|
||||
const [state] = useReducer(
|
||||
(state, action) => state + action,
|
||||
0,
|
||||
init => ref.current
|
||||
);
|
||||
|
||||
return <Stringify state={state} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useReducer, useRef} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const ref = useRef(props.value);
|
||||
const [state] = useReducer(() => ref.current, null);
|
||||
|
||||
return <Stringify state={state} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
|
||||
|
||||
error.invalid-access-ref-in-reducer.ts:5:29
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef(props.value);
|
||||
> 5 | const [state] = useReducer(() => ref.current, null);
|
||||
| ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
|
||||
6 |
|
||||
7 | return <Stringify state={state} />;
|
||||
8 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import {useReducer, useRef} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const ref = useRef(props.value);
|
||||
const [state] = useReducer(() => ref.current, null);
|
||||
|
||||
return <Stringify state={state} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useRef, useState} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const ref = useRef(props.value);
|
||||
const [state] = useState(() => ref.current);
|
||||
|
||||
return <Stringify state={state} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
|
||||
|
||||
error.invalid-access-ref-in-state-initializer.ts:5:27
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef(props.value);
|
||||
> 5 | const [state] = useState(() => ref.current);
|
||||
| ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
|
||||
6 |
|
||||
7 | return <Stringify state={state} />;
|
||||
8 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import {useRef, useState} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const ref = useRef(props.value);
|
||||
const [state] = useState(() => ref.current);
|
||||
|
||||
return <Stringify state={state} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
};
|
||||
Reference in New Issue
Block a user