Compare commits

..

2 Commits

Author SHA1 Message Date
Joe Savona
f49ce79b26 [compiler] Improve snap usability
A whole bunch of changes to snap aimed at making it more usable for humans and agents. Here's the new CLI interface:

```
node dist/main.js --help
Options:
      --version         Show version number                            [boolean]
      --sync            Run compiler in main thread (instead of using worker
                        threads or subprocesses). Defaults to false.
                                                      [boolean] [default: false]
      --worker-threads  Run compiler in worker threads (instead of
                        subprocesses). Defaults to true.
                                                       [boolean] [default: true]
      --help            Show help                                      [boolean]
  -w, --watch           Run compiler in watch mode, re-running after changes
                                                                       [boolean]
  -u, --update          Update fixtures                                [boolean]
  -p, --pattern         Optional glob pattern to filter fixtures (e.g.,
                        "error.*", "use-memo")                          [string]
  -d, --debug           Enable debug logging to print HIR for each pass[boolean]
```

Key changes:
* Added abbreviations for common arguments
* No more testfilter.txt! Filtering/debugging works more like Jest, see below.
* The `--debug` flag (`-d`) controls whether to emit debug information. In watch mode, this flag sets the initial debug value, and it can be toggled by pressing the 'd' key while watching.
* The `--pattern` flag (`-p`) sets a filter pattern. In watch mode, this flag sets the initial filter. It can be changed by pressing 'p' and typing a new pattern, or pressing 'a' to switch to running all tests.
* As before, we only actually enable debugging if debug mode is enabled _and_ there is only one test selected.
2026-01-22 14:34:50 -08:00
Joe Savona
9eb8b04a87 [compiler] Claude file/settings
Initializes CLAUDE.md and a settings file for the compiler/ directory to help use claude with the compiler. Note that some of the commands here depend on changes to snap from the next PR.
2026-01-22 14:34:47 -08:00
115 changed files with 1952 additions and 3283 deletions

View File

@@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(yarn snap:*)",
"Bash(yarn snap:build)",
"Bash(node scripts/enable-feature-flag.js:*)"
],
"deny": [],
"ask": []
}
}

View File

@@ -1,20 +0,0 @@
{
"permissions": {
"allow": [
"Bash(node scripts/enable-feature-flag.js:*)",
"Bash(yarn snap:*)",
"Bash(for test in \"error.invalid-access-ref-during-render\" \"error.invalid-ref-in-callback-invoked-during-render\" \"error.invalid-impure-functions-in-render-via-render-helper\")",
"Bash(do)",
"Bash(echo:*)",
"Bash(done)",
"Bash(cat:*)",
"Bash(sl revert:*)",
"Bash(yarn workspace snap run build:*)",
"Bash(yarn tsc:*)",
"Bash(yarn snap:build)",
"Bash(timeout 30 yarn snap:*)"
],
"deny": [],
"ask": []
}
}

2
compiler/.gitignore vendored
View File

@@ -8,7 +8,9 @@ dist
.vscode
!packages/playground/.vscode
testfilter.txt
.claude/settings.local.json
# forgive
*.vsix
.vscode-test

View File

@@ -10,7 +10,8 @@ This document contains knowledge about the React Compiler gathered during develo
- `src/Validation/` - Validation passes that check for errors
- `src/Entrypoint/Pipeline.ts` - Main compilation pipeline with pass ordering
- `src/__tests__/fixtures/compiler/` - Test fixtures
- `error.*.js` - Fixtures that should produce compilation errors
- `error.todo-*.js` - Unsupported feature, correctly throws Todo error (graceful bailout)
- `error.bug-*.js` - Known bug, throws wrong error type or incorrect behavior
- `*.expect.md` - Expected output for each fixture
## Running Tests
@@ -19,21 +20,30 @@ This document contains knowledge about the React Compiler gathered during develo
# Run all tests
yarn snap
# Run specific test by pattern
# Run tests matching a pattern
# Example: yarn snap -p 'error.*'
yarn snap -p <pattern>
# Update fixture outputs
# Run a single fixture in debug mode. Use the path relative to the __tests__/fixtures/compiler directory
# For each step of compilation, outputs the step name and state of the compiled program
# Example: yarn snap -p simple.js -d
yarn snap -p <file-basename> -d
# Update fixture outputs (also works with -p)
yarn snap -u
```
## Version Control
This repository uses Sapling (`sl`) for version control. Unlike git, Sapling does not require explicitly adding files to the staging area.
This repository uses Sapling (`sl`) for version control. Sapling is similar to Mercurial: there is not staging area, but new/deleted files must be explicitlyu added/removed.
```bash
# Check status
sl status
# Add new files, remove deleted files
sl addremove
# Commit all changes
sl commit -m "Your commit message"
@@ -173,69 +183,17 @@ const UseEffectEventHook = addHook(
**Key insight:** If a hook is missing an `aliasing` config, it falls back to `DefaultNonmutatingHook` which includes a `Render` effect on all arguments. This can cause false positives for hooks like `useEffectEvent` whose callbacks are not called during render.
### Effect Inference Pipeline
## Feature Flags
Effects are populated by `InferMutationAliasingEffects` (runs before validation):
Feature flags are configured in `src/HIR/Environment.ts`, for example `enableJsxOutlining`. Test fixtures can override the active feature flags used for that fixture via a comment pragma on the first line of the fixture input, for example:
1. For `Date.now()`, `Math.random()` etc. - adds `Impure` effect (controlled by `validateNoImpureFunctionsInRender` config)
2. For `ref.current` access - adds `Impure` effect (controlled by `validateRefAccessDuringRender` config)
3. For return terminals - adds `Alias` from return value to `fn.returns`
4. For component/JSX returns - adds `Render` effect
5. For function expressions - adds `CreateFunction` effect with captures
```javascript
// enableJsxOutlining @enableChangeVariableCodegen:false
### Validation: validateNoImpureValuesInRender
...code...
```
Located at `src/Validation/ValidateNoImpureValuesInRender.ts`
**Purpose:** Detect when impure values (refs, Date.now results, etc.) flow into render context.
**Algorithm:**
1. Track impure values in a Map<IdentifierId, ImpureEffect>
2. Track functions with impure returns separately (they're not impure values themselves)
3. Fixed-point iteration over all blocks:
- Process phi nodes (any impure operand makes result impure)
- Process instruction effects
- Process terminal effects
- Backwards propagation for mutated LoadLocal values
4. Validate: check all Render effects against impure values
**Key patterns:**
- `Impure` effect marks the target as impure
- `Capture/Alias/etc` propagates impurity from source to target
- `Apply` propagates impurity from args/receiver to result
- `CreateFunction` propagates impurity from captured values (but NOT from body effects)
- If a value has both `Render` and `Capture` in same instruction, only error on Render (don't cascade)
**Tracking functions with impure returns:**
- Separate from the `impure` map (function values aren't impure, just their returns)
- Populated when analyzing FunctionExpression bodies
- Used when:
1. Calling the function - mark call result as impure
2. Capturing the function - mark target as impure (for object.foo = impureFunc cases)
**Backwards propagation:**
- When `$x = LoadLocal y` and `$x` is mutated with impure content, mark `y` as impure
- This handles: `const arr = []; arr.push(impure); render(arr)`
## Known Issues / Edge Cases
### Function Outlining
After `OutlineFunctions` pass, inner functions are replaced with `LoadGlobal(_temp)`. The validation runs BEFORE outlining, so it sees the original FunctionExpression. But be aware that test output shows post-outlining HIR.
### SSA and LoadLocal
In SSA form, each `LoadLocal` creates a new identifier. When a loaded value is mutated:
- `$x = LoadLocal y`
- `mutate($x, impure)`
- `$z = LoadLocal y` (different from $x!)
- `render($z)`
The impurity in $x must propagate back to y, then forward to $z. This requires backwards propagation in the fixed-point loop.
## Configuration Flags
In `Environment.ts` / test directives:
- `validateNoImpureFunctionsInRender` - Enable impure function validation (Date.now, Math.random, etc.)
- `validateRefAccessDuringRender` - Enable ref access validation
Would enable the `enableJsxOutlining` feature and disable the `enableChangeVariableCodegen` feature.
## Debugging Tips
@@ -243,3 +201,21 @@ In `Environment.ts` / test directives:
2. Look for `@aliasingEffects=` on FunctionExpressions
3. Look for `Impure`, `Render`, `Capture` effects on instructions
4. Check the pass ordering in Pipeline.ts to understand when effects are populated vs validated
## Error Handling for Unsupported Features
When the compiler encounters an unsupported but known pattern, use `CompilerError.throwTodo()` instead of `CompilerError.invariant()`. Todo errors cause graceful bailouts in production; Invariant errors are hard failures indicating unexpected/invalid states.
```typescript
// Unsupported but expected pattern - graceful bailout
CompilerError.throwTodo({
reason: `Support [description of unsupported feature]`,
loc: terminal.loc,
});
// Invariant is for truly unexpected/invalid states - hard failure
CompilerError.invariant(false, {
reason: `Unexpected [thing]`,
loc: terminal.loc,
});
```

View File

@@ -132,12 +132,6 @@ export class CompilerDiagnostic {
return new CompilerDiagnostic({...options, details: []});
}
clone(): CompilerDiagnostic {
const cloned = CompilerDiagnostic.create({...this.options});
cloned.options.details = [...this.options.details];
return cloned;
}
get reason(): CompilerDiagnosticOptions['reason'] {
return this.options.reason;
}

View File

@@ -96,6 +96,7 @@ import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHI
import {outlineJSX} from '../Optimization/OutlineJsx';
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
import {transformFire} from '../Transform';
import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender';
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
@@ -106,7 +107,6 @@ import {nameAnonymousFunctions} from '../Transform/NameAnonymousFunctions';
import {optimizeForSSR} from '../Optimization/OptimizeForSSR';
import {validateExhaustiveDependencies} from '../Validation/ValidateExhaustiveDependencies';
import {validateSourceLocations} from '../Validation/ValidateSourceLocations';
import {validateNoImpureValuesInRender} from '../Validation/ValidateNoImpureValuesInRender';
export type CompilerPipelineValue =
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -271,6 +271,10 @@ function runWithEnvironment(
assertValidMutableRanges(hir);
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir).unwrap();
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir).unwrap();
}
@@ -292,15 +296,8 @@ function runWithEnvironment(
env.logErrors(validateNoJSXInTryStatement(hir));
}
if (
env.config.validateNoImpureFunctionsInRender ||
env.config.validateRefAccessDuringRender
) {
validateNoImpureValuesInRender(hir).unwrap();
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir).unwrap();
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir).unwrap();
}
validateNoFreezingKnownMutableFunctions(hir).unwrap();

View File

@@ -38,7 +38,7 @@ import {
addObject,
} from './ObjectShape';
import {BuiltInType, ObjectType, PolyType} from './Types';
import {AliasingSignatureConfig, TypeConfig} from './TypeSchema';
import {TypeConfig} from './TypeSchema';
import {assertExhaustive} from '../Utils/utils';
import {isHookName} from './Environment';
import {CompilerError, SourceLocation} from '..';
@@ -626,136 +626,11 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
// TODO: rest of Global objects
];
const RenderHookAliasing: (
reason: ValueReason,
) => AliasingSignatureConfig = reason => ({
receiver: '@receiver',
params: [],
rest: '@rest',
returns: '@returns',
temporaries: [],
effects: [
// Freeze the arguments
{
kind: 'Freeze',
value: '@rest',
reason: ValueReason.HookCaptured,
},
// Render the arguments
{
kind: 'Render',
place: '@rest',
},
// Returns a frozen value
{
kind: 'Create',
into: '@returns',
value: ValueKind.Frozen,
reason,
},
// May alias any arguments into the return
{
kind: 'Alias',
from: '@rest',
into: '@returns',
},
],
});
const EffectHookAliasing: AliasingSignatureConfig = {
receiver: '@receiver',
params: ['@fn', '@deps'],
rest: '@rest',
returns: '@returns',
temporaries: ['@effect'],
effects: [
// Freezes the function and deps
{
kind: 'Freeze',
value: '@rest',
reason: ValueReason.Effect,
},
{
kind: 'Freeze',
value: '@fn',
reason: ValueReason.Effect,
},
{
kind: 'Freeze',
value: '@deps',
reason: ValueReason.Effect,
},
// Deps are accessed during render
{
kind: 'Render',
place: '@deps',
},
// Internally creates an effect object that captures the function and deps
{
kind: 'Create',
into: '@effect',
value: ValueKind.Frozen,
reason: ValueReason.KnownReturnSignature,
},
// The effect stores the function and dependencies
{
kind: 'Capture',
from: '@rest',
into: '@effect',
},
{
kind: 'Capture',
from: '@fn',
into: '@effect',
},
// Returns undefined
{
kind: 'Create',
into: '@returns',
value: ValueKind.Primitive,
reason: ValueReason.KnownReturnSignature,
},
],
};
/*
* TODO(mofeiZ): We currently only store rest param effects for hooks.
* now that FeatureFlag `enableTreatHooksAsFunctions` is removed we can
* use positional params too (?)
*/
const useEffectEvent = addHook(
DEFAULT_SHAPES,
{
positionalParams: [],
restParam: Effect.Freeze,
returnType: {
kind: 'Function',
return: {kind: 'Poly'},
shapeId: BuiltInEffectEventId,
isConstructor: false,
},
calleeEffect: Effect.Read,
hookKind: 'useEffectEvent',
// Frozen because it should not mutate any locally-bound values
returnValueKind: ValueKind.Frozen,
aliasing: {
receiver: '@receiver',
params: ['@value'],
rest: null,
returns: '@return',
temporaries: [],
effects: [
{kind: 'Assign', from: '@value', into: '@return'},
{
kind: 'Freeze',
value: '@value',
reason: ValueReason.HookCaptured,
},
],
},
},
BuiltInUseEffectEventId,
);
const REACT_APIS: Array<[string, BuiltInType]> = [
[
'useContext',
@@ -769,7 +644,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
hookKind: 'useContext',
returnValueKind: ValueKind.Frozen,
returnValueReason: ValueReason.Context,
aliasing: RenderHookAliasing(ValueReason.Context),
},
BuiltInUseContextHookId,
),
@@ -784,7 +658,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
hookKind: 'useState',
returnValueKind: ValueKind.Frozen,
returnValueReason: ValueReason.State,
aliasing: RenderHookAliasing(ValueReason.State),
}),
],
[
@@ -797,7 +670,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
hookKind: 'useActionState',
returnValueKind: ValueKind.Frozen,
returnValueReason: ValueReason.State,
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
}),
],
[
@@ -810,7 +682,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
hookKind: 'useReducer',
returnValueKind: ValueKind.Frozen,
returnValueReason: ValueReason.ReducerState,
aliasing: RenderHookAliasing(ValueReason.ReducerState),
}),
],
[
@@ -822,22 +693,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
calleeEffect: Effect.Read,
hookKind: 'useRef',
returnValueKind: ValueKind.Mutable,
aliasing: {
receiver: '@receiver',
params: [],
rest: '@rest',
returns: '@return',
temporaries: [],
effects: [
{
kind: 'Create',
into: '@return',
value: ValueKind.Mutable,
reason: ValueReason.KnownReturnSignature,
},
{kind: 'Capture', from: '@rest', into: '@return'},
],
},
}),
],
[
@@ -860,7 +715,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
calleeEffect: Effect.Read,
hookKind: 'useMemo',
returnValueKind: ValueKind.Frozen,
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
}),
],
[
@@ -868,16 +722,10 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
addHook(DEFAULT_SHAPES, {
positionalParams: [],
restParam: Effect.Freeze,
returnType: {
kind: 'Function',
isConstructor: false,
return: {kind: 'Poly'},
shapeId: null,
},
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
hookKind: 'useCallback',
returnValueKind: ValueKind.Frozen,
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
}),
],
[
@@ -891,7 +739,41 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
calleeEffect: Effect.Read,
hookKind: 'useEffect',
returnValueKind: ValueKind.Frozen,
aliasing: EffectHookAliasing,
aliasing: {
receiver: '@receiver',
params: [],
rest: '@rest',
returns: '@returns',
temporaries: ['@effect'],
effects: [
// Freezes the function and deps
{
kind: 'Freeze',
value: '@rest',
reason: ValueReason.Effect,
},
// Internally creates an effect object that captures the function and deps
{
kind: 'Create',
into: '@effect',
value: ValueKind.Frozen,
reason: ValueReason.KnownReturnSignature,
},
// The effect stores the function and dependencies
{
kind: 'Capture',
from: '@rest',
into: '@effect',
},
// Returns undefined
{
kind: 'Create',
into: '@returns',
value: ValueKind.Primitive,
reason: ValueReason.KnownReturnSignature,
},
],
},
},
BuiltInUseEffectHookId,
),
@@ -907,7 +789,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
calleeEffect: Effect.Read,
hookKind: 'useLayoutEffect',
returnValueKind: ValueKind.Frozen,
aliasing: EffectHookAliasing,
},
BuiltInUseLayoutEffectHookId,
),
@@ -923,7 +804,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
calleeEffect: Effect.Read,
hookKind: 'useInsertionEffect',
returnValueKind: ValueKind.Frozen,
aliasing: EffectHookAliasing,
},
BuiltInUseInsertionEffectHookId,
),
@@ -937,7 +817,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
calleeEffect: Effect.Read,
hookKind: 'useTransition',
returnValueKind: ValueKind.Frozen,
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
}),
],
[
@@ -950,7 +829,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
hookKind: 'useOptimistic',
returnValueKind: ValueKind.Frozen,
returnValueReason: ValueReason.State,
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
}),
],
[
@@ -964,7 +842,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
},
BuiltInUseOperatorId,
),
@@ -989,8 +866,27 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
BuiltInFireId,
),
],
['useEffectEvent', useEffectEvent],
['experimental_useEffectEvent', useEffectEvent],
[
'useEffectEvent',
addHook(
DEFAULT_SHAPES,
{
positionalParams: [],
restParam: Effect.Freeze,
returnType: {
kind: 'Function',
return: {kind: 'Poly'},
shapeId: BuiltInEffectEventId,
isConstructor: false,
},
calleeEffect: Effect.Read,
hookKind: 'useEffectEvent',
// Frozen because it should not mutate any locally-bound values
returnValueKind: ValueKind.Frozen,
},
BuiltInUseEffectEventId,
),
],
['AUTODEPS', addObject(DEFAULT_SHAPES, BuiltInAutodepsId, [])],
];

View File

@@ -1879,15 +1879,7 @@ export function isRefValueType(id: Identifier): boolean {
}
export function isUseRefType(id: Identifier): boolean {
return isUseRefType_(id.type);
}
export function isUseRefType_(type: Type): boolean {
return (
(type.kind === 'Object' && type.shapeId === 'BuiltInUseRefId') ||
(type.kind === 'Phi' &&
type.operands.some(operand => isUseRefType_(operand)))
);
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseRefId';
}
export function isUseStateType(id: Identifier): boolean {
@@ -1898,13 +1890,6 @@ export function isJsxType(type: Type): boolean {
return type.kind === 'Object' && type.shapeId === 'BuiltInJsx';
}
export function isJsxOrJsxUnionType(type: Type): boolean {
return (
(type.kind === 'Object' && type.shapeId === 'BuiltInJsx') ||
(type.kind === 'Phi' && type.operands.some(op => isJsxOrJsxUnionType(op)))
);
}
export function isRefOrRefValue(id: Identifier): boolean {
return isUseRefType(id) || isRefValueType(id);
}
@@ -2073,23 +2058,4 @@ export function getHookKindForType(
return null;
}
export function areEqualSourceLocations(
loc1: SourceLocation,
loc2: SourceLocation,
): boolean {
if (typeof loc1 === 'symbol' || typeof loc2 === 'symbol') {
return false;
}
return (
loc1.filename === loc2.filename &&
loc1.identifierName === loc2.identifierName &&
loc1.start.line === loc2.start.line &&
loc1.start.column === loc2.start.column &&
loc1.start.index === loc2.start.index &&
loc1.end.line === loc2.end.line &&
loc1.end.column === loc2.end.column &&
loc1.end.index === loc2.end.index
);
}
export * from './Types';

View File

@@ -988,7 +988,7 @@ export function createTemporaryPlace(
identifier: makeTemporaryIdentifier(env.nextIdentifierId, loc),
reactive: false,
effect: Effect.Unknown,
loc,
loc: GeneratedSource,
};
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorCategory} from '../CompilerError';
import {CompilerError} from '../CompilerError';
import {AliasingEffect, AliasingSignature} from '../Inference/AliasingEffects';
import {assertExhaustive} from '../Utils/utils';
import {
@@ -190,22 +190,14 @@ function parseAliasingSignatureConfig(
};
}
case 'Impure': {
const into = lookup(effect.into);
return {
kind: 'Impure',
into,
category: ErrorCategory.Purity,
description: effect.description,
reason: effect.reason,
sourceMessage: effect.sourceMessage,
usageMessage: effect.usageMessage,
};
}
case 'Render': {
const place = lookup(effect.place);
return {
kind: 'Render',
kind: 'Impure',
place,
error: CompilerError.throwTodo({
reason: 'Support impure effect declarations',
loc: GeneratedSource,
}),
};
}
case 'Apply': {
@@ -1521,11 +1513,6 @@ export const DefaultNonmutatingHook = addHook(
value: '@rest',
reason: ValueReason.HookCaptured,
},
// Render the arguments
{
kind: 'Render',
place: '@rest',
},
// Returns a frozen value
{
kind: 'Create',

View File

@@ -983,15 +983,15 @@ export function printAliasingEffect(effect: AliasingEffect): string {
return `...${printPlaceForAliasEffect(arg.place)}`;
})
.join(', ');
// let signature = '';
// if (effect.signature != null) {
// if (effect.signature.aliasing != null) {
// signature = printAliasingSignature(effect.signature.aliasing);
// } else {
// signature = JSON.stringify(effect.signature, null, 2);
// }
// }
return `Apply ${printPlaceForAliasEffect(effect.into)} = ${receiverCallee}(${args})`;
let signature = '';
if (effect.signature != null) {
if (effect.signature.aliasing != null) {
signature = printAliasingSignature(effect.signature.aliasing);
} else {
signature = JSON.stringify(effect.signature, null, 2);
}
}
return `Apply ${printPlaceForAliasEffect(effect.into)} = ${receiverCallee}(${args})${signature != '' ? '\n ' : ''}${signature}`;
}
case 'Freeze': {
return `Freeze ${printPlaceForAliasEffect(effect.value)} ${effect.reason}`;
@@ -1009,7 +1009,7 @@ export function printAliasingEffect(effect: AliasingEffect): string {
return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;
}
case 'Impure': {
return `Impure ${printPlaceForAliasEffect(effect.into)} reason=${effect.reason} description=${effect.description}`;
return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;
}
case 'Render': {
return `Render ${printPlaceForAliasEffect(effect.place)}`;

View File

@@ -185,29 +185,11 @@ export const ApplyEffectSchema: z.ZodType<ApplyEffectConfig> = z.object({
export type ImpureEffectConfig = {
kind: 'Impure';
into: string;
reason: string;
description: string;
sourceMessage: string;
usageMessage: string;
place: string;
};
export const ImpureEffectSchema: z.ZodType<ImpureEffectConfig> = z.object({
kind: z.literal('Impure'),
into: LifetimeIdSchema,
reason: z.string(),
description: z.string(),
sourceMessage: z.string(),
usageMessage: z.string(),
});
export type RenderEffectConfig = {
kind: 'Render';
place: string;
};
export const RenderEffectSchema: z.ZodType<RenderEffectConfig> = z.object({
kind: z.literal('Render'),
place: LifetimeIdSchema,
});
@@ -222,8 +204,7 @@ export type AliasingEffectConfig =
| ImpureEffectConfig
| MutateEffectConfig
| MutateTransitiveConditionallyConfig
| ApplyEffectConfig
| RenderEffectConfig;
| ApplyEffectConfig;
export const AliasingEffectSchema: z.ZodType<AliasingEffectConfig> = z.union([
FreezeEffectSchema,

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerDiagnostic, ErrorCategory} from '../CompilerError';
import {CompilerDiagnostic} from '../CompilerError';
import {
FunctionExpression,
GeneratedSource,
@@ -162,15 +162,7 @@ export type AliasingEffect =
/**
* Indicates a side-effect that is not safe during render
*/
| {
kind: 'Impure';
into: Place;
category: ErrorCategory;
reason: string;
description: string;
usageMessage: string;
sourceMessage: string;
}
| {kind: 'Impure'; place: Place; error: CompilerDiagnostic}
/**
* Indicates that a given place is accessed during render. Used to distingush
* hook arguments that are known to be called immediately vs those used for
@@ -230,14 +222,6 @@ export function hashEffect(effect: AliasingEffect): string {
return [effect.kind, effect.value.identifier.id, effect.reason].join(':');
}
case 'Impure':
return [
effect.kind,
effect.into.identifier.id,
effect.reason,
effect.description,
effect.usageMessage,
effect.sourceMessage,
].join(':');
case 'Render': {
return [effect.kind, effect.place.identifier.id].join(':');
}

View File

@@ -8,7 +8,7 @@
import {BlockId, computePostDominatorTree, HIRFunction, Place} from '../HIR';
import {PostDominator} from '../HIR/Dominator';
export type ControlDominators = (id: BlockId) => Place | null;
export type ControlDominators = (id: BlockId) => boolean;
/**
* Returns an object that lazily calculates whether particular blocks are controlled
@@ -23,7 +23,7 @@ export function createControlDominators(
});
const postDominatorFrontierCache = new Map<BlockId, Set<BlockId>>();
function isControlledBlock(id: BlockId): Place | null {
function isControlledBlock(id: BlockId): boolean {
let controlBlocks = postDominatorFrontierCache.get(id);
if (controlBlocks === undefined) {
controlBlocks = postDominatorFrontier(fn, postDominators, id);
@@ -35,24 +35,24 @@ export function createControlDominators(
case 'if':
case 'branch': {
if (isControlVariable(controlBlock.terminal.test)) {
return controlBlock.terminal.test;
return true;
}
break;
}
case 'switch': {
if (isControlVariable(controlBlock.terminal.test)) {
return controlBlock.terminal.test;
return true;
}
for (const case_ of controlBlock.terminal.cases) {
if (case_.test !== null && isControlVariable(case_.test)) {
return case_.test;
return true;
}
}
break;
}
}
}
return null;
return false;
}
return isControlledBlock;

View File

@@ -27,13 +27,11 @@ import {
InstructionKind,
InstructionValue,
isArrayType,
isJsxOrJsxUnionType,
isJsxType,
isMapType,
isMutableEffect,
isPrimitiveType,
isRefOrRefValue,
isSetType,
isUseRefType,
makeIdentifierId,
Phi,
Place,
@@ -72,7 +70,6 @@ import {
MutationReason,
} from './AliasingEffects';
import {ErrorCategory} from '../CompilerError';
import {REF_ERROR_DESCRIPTION} from '../Validation/ValidateNoRefAccessInRender';
const DEBUG = false;
@@ -572,32 +569,14 @@ function inferBlock(
terminal.effects = effects.length !== 0 ? effects : null;
}
} else if (terminal.kind === 'return') {
terminal.effects = [
context.internEffect({
kind: 'Alias',
from: terminal.value,
into: context.fn.returns,
}),
];
if (!context.isFuctionExpression) {
terminal.effects.push(
terminal.effects = [
context.internEffect({
kind: 'Freeze',
value: terminal.value,
reason: ValueReason.JsxCaptured,
}),
);
}
if (
context.fn.fnType === 'Component' ||
isJsxOrJsxUnionType(context.fn.returns.identifier.type)
) {
terminal.effects.push(
context.internEffect({
kind: 'Render',
place: terminal.value,
}),
);
];
}
}
}
@@ -770,7 +749,17 @@ function applyEffect(
break;
}
case 'ImmutableCapture': {
effects.push(effect);
const kind = state.kind(effect.from).kind;
switch (kind) {
case ValueKind.Global:
case ValueKind.Primitive: {
// no-op: we don't need to track data flow for copy types
break;
}
default: {
effects.push(effect);
}
}
break;
}
case 'CreateFrom': {
@@ -1072,17 +1061,6 @@ function applyEffect(
reason: new Set(fromValue.reason),
});
state.define(effect.into, value);
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
initialized,
effects,
);
break;
}
default: {
@@ -1094,8 +1072,6 @@ function applyEffect(
break;
}
case 'Apply': {
effects.push(effect);
const functionValues = state.values(effect.function);
if (
functionValues.length === 1 &&
@@ -1990,11 +1966,6 @@ function computeSignatureForInstruction(
value: ValueKind.Primitive,
reason: ValueReason.Other,
});
effects.push({
kind: 'ImmutableCapture',
from: value.object,
into: lvalue,
});
} else {
effects.push({
kind: 'CreateFrom',
@@ -2002,20 +1973,6 @@ function computeSignatureForInstruction(
into: lvalue,
});
}
if (
env.config.validateRefAccessDuringRender &&
isUseRefType(value.object.identifier)
) {
effects.push({
kind: 'Impure',
into: lvalue,
category: ErrorCategory.Refs,
reason: `Cannot access ref value during render`,
description: REF_ERROR_DESCRIPTION,
sourceMessage: `Ref is initially accessed`,
usageMessage: `Ref value is used during render`,
});
}
break;
}
case 'PropertyStore':
@@ -2180,15 +2137,6 @@ function computeSignatureForInstruction(
into: lvalue,
});
}
if (value.children != null) {
// Children are typically called during render, not used as an event/effect callback
for (const child of value.children) {
effects.push({
kind: 'Render',
place: child,
});
}
}
if (value.kind === 'JsxExpression') {
if (value.tag.kind === 'Identifier') {
// Tags are render function, by definition they're called during render
@@ -2197,17 +2145,29 @@ function computeSignatureForInstruction(
place: value.tag,
});
}
for (const prop of value.props) {
const place =
prop.kind === 'JsxAttribute' ? prop.place : prop.argument;
if (isUseRefType(place.identifier)) {
continue;
}
if (place.identifier.type.kind !== 'Function') {
// Functions are checked independently
if (value.children != null) {
// Children are typically called during render, not used as an event/effect callback
for (const child of value.children) {
effects.push({
kind: 'Render',
place,
place: child,
});
}
}
for (const prop of value.props) {
if (
prop.kind === 'JsxAttribute' &&
prop.place.identifier.type.kind === 'Function' &&
(isJsxType(prop.place.identifier.type.return) ||
(prop.place.identifier.type.return.kind === 'Phi' &&
prop.place.identifier.type.return.operands.some(operand =>
isJsxType(operand),
)))
) {
// Any props which return jsx are assumed to be called during render
effects.push({
kind: 'Render',
place: prop.place,
});
}
}
@@ -2243,11 +2203,6 @@ function computeSignatureForInstruction(
value: ValueKind.Primitive,
reason: ValueReason.Other,
});
effects.push({
kind: 'ImmutableCapture',
from: value.value,
into: place,
});
} else if (patternItem.kind === 'Identifier') {
effects.push({
kind: 'CreateFrom',
@@ -2429,46 +2384,15 @@ function computeSignatureForInstruction(
});
break;
}
case 'BinaryExpression': {
effects.push({
kind: 'Create',
into: lvalue,
value: ValueKind.Primitive,
reason: ValueReason.Other,
});
effects.push({
kind: 'ImmutableCapture',
into: lvalue,
from: value.left,
});
effects.push({
kind: 'ImmutableCapture',
into: lvalue,
from: value.right,
});
break;
}
case 'UnaryExpression': {
effects.push({
kind: 'Create',
into: lvalue,
value: ValueKind.Primitive,
reason: ValueReason.Other,
});
effects.push({
kind: 'ImmutableCapture',
into: lvalue,
from: value.value,
});
break;
}
case 'TaggedTemplateExpression':
case 'BinaryExpression':
case 'Debugger':
case 'JSXText':
case 'MetaProperty':
case 'Primitive':
case 'RegExpLiteral':
case 'TemplateLiteral':
case 'UnaryExpression':
case 'UnsupportedNode': {
effects.push({
kind: 'Create',
@@ -2499,7 +2423,7 @@ function computeEffectsForLegacySignature(
lvalue: Place,
receiver: Place,
args: Array<Place | SpreadPattern | Hole>,
_loc: SourceLocation,
loc: SourceLocation,
): Array<AliasingEffect> {
const returnValueReason = signature.returnValueReason ?? ValueReason.Other;
const effects: Array<AliasingEffect> = [];
@@ -2512,18 +2436,20 @@ function computeEffectsForLegacySignature(
if (signature.impure && state.env.config.validateNoImpureFunctionsInRender) {
effects.push({
kind: 'Impure',
into: lvalue,
category: ErrorCategory.Purity,
reason: 'Cannot access impure value during render',
description:
'Calling an impure function can produce unstable results that update ' +
'unpredictably when the component happens to re-render. ' +
'(https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
sourceMessage:
signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function.`
: 'This function is impure',
usageMessage: 'Cannot access impure value during render',
place: receiver,
error: CompilerDiagnostic.create({
category: ErrorCategory.Purity,
reason: 'Cannot call impure function during render',
description:
(signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function. `
: '') +
'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
}).withDetails({
kind: 'error',
loc,
message: 'Cannot call impure function',
}),
});
}
if (signature.knownIncompatible != null && state.env.enableValidations) {
@@ -2822,23 +2748,7 @@ function computeEffectsForSignature(
}
break;
}
case 'Impure': {
if (env.config.validateNoImpureFunctionsInRender) {
const values = substitutions.get(effect.into.identifier.id) ?? [];
for (const value of values) {
effects.push({
kind: effect.kind,
into: value,
category: effect.category,
reason: effect.reason,
description: effect.description,
sourceMessage: effect.sourceMessage,
usageMessage: effect.usageMessage,
});
}
}
break;
}
case 'Impure':
case 'MutateFrozen':
case 'MutateGlobal': {
const values = substitutions.get(effect.place.identifier.id) ?? [];

View File

@@ -19,8 +19,6 @@ import {
ValueReason,
Place,
isPrimitiveType,
isUseRefType,
isJsxOrJsxUnionType,
} from '../HIR/HIR';
import {
eachInstructionLValue,
@@ -30,9 +28,6 @@ import {
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
import {Err, Ok, Result} from '../Utils/Result';
import {AliasingEffect, MutationReason} from './AliasingEffects';
import {printIdentifier, printType} from '../HIR/PrintHIR';
const DEBUG = false;
/**
* This pass builds an abstract model of the heap and interprets the effects of the
@@ -109,6 +104,7 @@ export function inferMutationAliasingRanges(
reason: MutationReason | null;
}> = [];
const renders: Array<{index: number; place: Place}> = [];
let index = 0;
const errors = new CompilerError();
@@ -201,12 +197,14 @@ export function inferMutationAliasingRanges(
});
} else if (
effect.kind === 'MutateFrozen' ||
effect.kind === 'MutateGlobal'
effect.kind === 'MutateGlobal' ||
effect.kind === 'Impure'
) {
errors.pushDiagnostic(effect.error);
functionEffects.push(effect);
} else if (effect.kind === 'Render') {
renders.push({index: index++, place: effect.place});
functionEffects.push(effect);
}
}
}
@@ -216,6 +214,10 @@ export function inferMutationAliasingRanges(
state.assign(index, from, into);
}
}
if (block.terminal.kind === 'return') {
state.assign(index++, block.terminal.value, fn.returns);
}
if (
(block.terminal.kind === 'maybe-throw' ||
block.terminal.kind === 'return') &&
@@ -225,31 +227,23 @@ export function inferMutationAliasingRanges(
if (effect.kind === 'Alias') {
state.assign(index++, effect.from, effect.into);
} else {
CompilerError.invariant(
effect.kind === 'Freeze' || effect.kind === 'Render',
{
reason: `Unexpected '${effect.kind}' effect for MaybeThrow terminal`,
description: null,
details: [
{
kind: 'error',
loc: block.terminal.loc,
message: null,
},
],
},
);
CompilerError.invariant(effect.kind === 'Freeze', {
reason: `Unexpected '${effect.kind}' effect for MaybeThrow terminal`,
description: null,
details: [
{
kind: 'error',
loc: block.terminal.loc,
message: null,
},
],
});
}
}
}
}
for (const mutation of mutations) {
if (DEBUG) {
console.log(
`[${mutation.index}] mutate ${printIdentifier(mutation.place.identifier)}`,
);
}
state.mutate(
mutation.index,
mutation.place.identifier,
@@ -261,16 +255,8 @@ export function inferMutationAliasingRanges(
errors,
);
}
if (DEBUG) {
console.log(state.debug());
}
for (const render of renders) {
if (DEBUG) {
console.log(
`[${render.index}] render ${printIdentifier(render.place.identifier)}`,
);
}
state.render(render.index, render.place, errors);
state.render(render.index, render.place.identifier, errors);
}
for (const param of [...fn.context, ...fn.params]) {
const place = param.kind === 'Identifier' ? param : param.place;
@@ -397,7 +383,17 @@ export function inferMutationAliasingRanges(
break;
}
case 'Apply': {
break;
CompilerError.invariant(false, {
reason: `[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects`,
description: null,
details: [
{
kind: 'error',
loc: effect.function.loc,
message: null,
},
],
});
}
case 'MutateTransitive':
case 'MutateConditionally':
@@ -519,13 +515,6 @@ export function inferMutationAliasingRanges(
const ignoredErrors = new CompilerError();
for (const param of [...fn.params, ...fn.context, fn.returns]) {
const place = param.kind === 'Identifier' ? param : param.place;
const node = state.nodes.get(place.identifier);
if (node != null && node.render != null) {
functionEffects.push({
kind: 'Render',
place: place,
});
}
tracked.push(place);
}
for (const into of tracked) {
@@ -579,12 +568,7 @@ export function inferMutationAliasingRanges(
}
}
if (
errors.hasAnyErrors() &&
(fn.fnType === 'Component' ||
isJsxOrJsxUnionType(fn.returns.identifier.type) ||
!isFunctionExpression)
) {
if (errors.hasAnyErrors() && !isFunctionExpression) {
return Err(errors);
}
return Ok(functionEffects);
@@ -593,6 +577,7 @@ export function inferMutationAliasingRanges(
function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
for (const effect of fn.aliasingEffects ?? []) {
switch (effect.kind) {
case 'Impure':
case 'MutateFrozen':
case 'MutateGlobal': {
errors.pushDiagnostic(effect.error);
@@ -627,74 +612,10 @@ type Node = {
| {kind: 'Object'}
| {kind: 'Phi'}
| {kind: 'Function'; function: HIRFunction};
render: Place | null;
};
function _printNode(node: Node): string {
const out: Array<string> = [];
debugNode(out, node);
return out.join('\n');
}
function debugNode(out: Array<string>, node: Node): void {
out.push(
printIdentifier(node.id) +
printType(node.id.type) +
` lastMutated=[${node.lastMutated}]`,
);
if (node.transitive != null) {
out.push(` transitive=${node.transitive.kind}`);
}
if (node.local != null) {
out.push(` local=${node.local.kind}`);
}
if (node.mutationReason != null) {
out.push(` mutationReason=${node.mutationReason?.kind}`);
}
const edges: Array<{
index: number;
direction: '<=' | '=>';
kind: string;
id: Identifier;
}> = [];
for (const [alias, index] of node.createdFrom) {
edges.push({index, direction: '<=', kind: 'createFrom', id: alias});
}
for (const [alias, index] of node.aliases) {
edges.push({index, direction: '<=', kind: 'alias', id: alias});
}
for (const [alias, index] of node.maybeAliases) {
edges.push({index, direction: '<=', kind: 'alias?', id: alias});
}
for (const [alias, index] of node.captures) {
edges.push({index, direction: '<=', kind: 'capture', id: alias});
}
for (const edge of node.edges) {
edges.push({
index: edge.index,
direction: '=>',
kind: edge.kind,
id: edge.node,
});
}
edges.sort((a, b) => a.index - b.index);
for (const edge of edges) {
out.push(
` [${edge.index}] ${edge.direction} ${edge.kind} ${printIdentifier(edge.id)}`,
);
}
}
class AliasingState {
nodes: Map<Identifier, Node> = new Map();
debug(): string {
const items: Array<string> = [];
for (const [_id, node] of this.nodes) {
debugNode(items, node);
}
return items.join('\n');
}
create(place: Place, value: Node['value']): void {
this.nodes.set(place.identifier, {
id: place.identifier,
@@ -708,7 +629,6 @@ class AliasingState {
lastMutated: 0,
mutationReason: null,
value,
render: null,
});
}
@@ -761,9 +681,9 @@ class AliasingState {
}
}
render(index: number, start: Place, errors: CompilerError): void {
render(index: number, start: Identifier, errors: CompilerError): void {
const seen = new Set<Identifier>();
const queue: Array<Identifier> = [start.identifier];
const queue: Array<Identifier> = [start];
while (queue.length !== 0) {
const current = queue.pop()!;
if (seen.has(current)) {
@@ -771,34 +691,11 @@ class AliasingState {
}
seen.add(current);
const node = this.nodes.get(current);
if (node == null || isUseRefType(node.id)) {
if (DEBUG) {
console.log(` render ${printIdentifier(current)}: skip mutated/ref`);
}
if (node == null || node.transitive != null || node.local != null) {
continue;
}
if (
node.local == null &&
node.transitive == null &&
node.value.kind === 'Function'
) {
const returns = node.value.function.returns;
if (
isJsxType(returns.identifier.type) ||
(returns.identifier.type.kind === 'Phi' &&
returns.identifier.type.operands.some(operand =>
isJsxType(operand),
))
) {
appendFunctionErrors(errors, node.value.function);
}
if (DEBUG) {
console.log(` render ${printIdentifier(current)}: skip function`);
}
continue;
}
if (node.render == null) {
node.render = start;
if (node.value.kind === 'Function') {
appendFunctionErrors(errors, node.value.function);
}
for (const [alias, when] of node.createdFrom) {
if (when >= index) {
@@ -812,12 +709,6 @@ class AliasingState {
}
queue.push(alias);
}
for (const [alias, when] of node.maybeAliases) {
if (when >= index) {
continue;
}
queue.push(alias);
}
for (const [capture, when] of node.captures) {
if (when >= index) {
continue;

View File

@@ -167,14 +167,6 @@ export function Set_filter<T>(
return result;
}
export function Set_subtract<T>(
source: ReadonlySet<T>,
other: Iterable<T>,
): Set<T> {
const otherSet = other instanceof Set ? other : new Set(other);
return Set_filter(source, item => !otherSet.has(item));
}
export function hasNode<T>(
input: NodePath<T | null | undefined>,
): input is NodePath<NonNullable<T>> {

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CompilerDiagnostic, CompilerError} from '..';
import {ErrorCategory} from '../CompilerError';
import {HIRFunction} from '../HIR';
import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffects';
import {Result} from '../Utils/Result';
/**
* Checks that known-impure functions are not called during render. Examples of invalid functions to
* call during render are `Math.random()` and `Date.now()`. Users may extend this set of
* impure functions via a module type provider and specifying functions with `impure: true`.
*
* TODO: add best-effort analysis of functions which are called during render. We have variations of
* this in several of our validation passes and should unify those analyses into a reusable helper
* and use it here.
*/
export function validateNoImpureFunctionsInRender(
fn: HIRFunction,
): Result<void, CompilerError> {
const errors = new CompilerError();
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
const value = instr.value;
if (value.kind === 'MethodCall' || value.kind == 'CallExpression') {
const callee =
value.kind === 'MethodCall' ? value.property : value.callee;
const signature = getFunctionCallSignature(
fn.env,
callee.identifier.type,
);
if (signature != null && signature.impure === true) {
errors.pushDiagnostic(
CompilerDiagnostic.create({
category: ErrorCategory.Purity,
reason: 'Cannot call impure function during render',
description:
(signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function. `
: '') +
'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
suggestions: null,
}).withDetails({
kind: 'error',
loc: callee.loc,
message: 'Cannot call impure function',
}),
);
}
}
}
}
return errors.asResult();
}

View File

@@ -1,387 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CompilerDiagnostic, CompilerError, Effect} from '..';
import {
areEqualSourceLocations,
HIRFunction,
IdentifierId,
InstructionId,
isJsxType,
isUseRefType,
} from '../HIR';
import {AliasingEffect, hashEffect} from '../Inference/AliasingEffects';
import {createControlDominators} from '../Inference/ControlDominators';
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
import {Err, Ok, Result} from '../Utils/Result';
import {getOrInsertWith} from '../Utils/utils';
import {printFunction} from '../HIR/PrintHIR';
type ImpureEffect = Extract<AliasingEffect, {kind: 'Impure'}>;
type RenderEffect = Extract<AliasingEffect, {kind: 'Render'}>;
type FunctionCache = Map<HIRFunction, Map<string, ImpuritySignature>>;
type ImpuritySignature = {
effects: Map<IdentifierId, ImpureEffect>;
error: CompilerError;
returns: IdentifierId;
};
export function validateNoImpureValuesInRender(
fn: HIRFunction,
): Result<void, CompilerError> {
const impure = new Map<IdentifierId, ImpureEffect>();
const impureFunctions = new Map<IdentifierId, ImpuritySignature>();
const result = inferImpureValues(fn, impure, impureFunctions, new Map());
if (result.error.hasAnyErrors()) {
return Err(result.error);
}
return Ok(undefined);
}
function inferFunctionExpressionMemo(
fn: HIRFunction,
impure: Map<IdentifierId, ImpureEffect>,
impureFunctions: Map<IdentifierId, ImpuritySignature>,
cache: FunctionCache,
): ImpuritySignature {
const key = fn.context
.map(
place =>
`${place.identifier.id}:${impure.has(place.identifier.id)}:${Array.from(
impureFunctions.get(place.identifier.id)?.effects ?? new Map(),
)
.map(([id, effect]) => `${id}=>${effect.into.identifier.id}`)
.join(',')}`,
)
.join(',');
return getOrInsertWith(
getOrInsertWith(cache, fn, () => new Map()),
key,
() => inferImpureValues(fn, impure, impureFunctions, cache),
);
}
function processEffects(
id: InstructionId,
effects: Array<AliasingEffect>,
impure: Map<IdentifierId, ImpureEffect>,
impureFunctions: Map<IdentifierId, ImpuritySignature>,
cache: FunctionCache,
): boolean {
let hasChanges = false;
const rendered: Set<IdentifierId> = new Set();
for (const effect of effects) {
if (effect.kind === 'Render') {
rendered.add(effect.place.identifier.id);
}
}
for (const effect of effects) {
switch (effect.kind) {
case 'Alias':
case 'Assign':
case 'Capture':
case 'CreateFrom':
case 'ImmutableCapture': {
const sourceEffect = impure.get(effect.from.identifier.id);
if (
sourceEffect != null &&
!impure.has(effect.into.identifier.id) &&
!rendered.has(effect.from.identifier.id) &&
!isUseRefType(effect.into.identifier) &&
!isJsxType(effect.into.identifier.type)
) {
// console.log(
// `${effect.kind} $${effect.into.identifier.id} <= $${effect.from.identifier.id} ($${sourceEffect.into.identifier.id} forward)`,
// );
impure.set(effect.into.identifier.id, sourceEffect);
hasChanges = true;
}
if (
sourceEffect == null &&
(effect.kind === 'Assign' || effect.kind === 'Capture') &&
!impure.has(effect.from.identifier.id) &&
!rendered.has(effect.from.identifier.id) &&
!isUseRefType(effect.from.identifier) &&
isMutable({id}, effect.into)
) {
const destinationEffect = impure.get(effect.into.identifier.id);
if (destinationEffect != null) {
// console.log(
// `${effect.kind} $${effect.into.identifier.id} => $${effect.from.identifier.id} ($${destinationEffect.into.identifier.id} backward)`,
// );
impure.set(effect.from.identifier.id, destinationEffect);
hasChanges = true;
}
}
if (
(effect.kind === 'Alias' ||
effect.kind === 'Assign' ||
effect.kind === 'ImmutableCapture') &&
!rendered.has(effect.into.identifier.id) &&
!isJsxType(effect.into.identifier.type)
) {
const functionEffect = impureFunctions.get(effect.from.identifier.id);
if (
functionEffect != null &&
!impureFunctions.has(effect.into.identifier.id)
// ||
// !areEqualFunctionSignatures(
// impureFunctions.get(effect.into.identifier.id)!.effects,
// functionEffect.effects,
// )
) {
// console.log(
// `${effect.kind} $${effect.into.identifier.id} <= $${effect.from.identifier.id} (function)`,
// );
impureFunctions.set(effect.into.identifier.id, functionEffect);
hasChanges = true;
}
}
break;
}
case 'Impure': {
if (!impure.has(effect.into.identifier.id)) {
// console.log(`Impure $${effect.into.identifier.id}`);
impure.set(effect.into.identifier.id, effect);
hasChanges = true;
}
break;
}
case 'Render': {
break;
}
case 'CreateFunction': {
const result = inferFunctionExpressionMemo(
effect.function.loweredFunc.func,
impure,
impureFunctions,
cache,
);
if (result.error.hasAnyErrors()) {
break;
}
const previousResult = impureFunctions.get(effect.into.identifier.id);
if (
previousResult == null ||
!areEqualFunctionSignatures(result.effects, previousResult.effects)
) {
// console.log(`Function $${effect.into.identifier.id}`);
impureFunctions.set(effect.into.identifier.id, result);
hasChanges = true;
}
break;
}
case 'Apply': {
const functionSignature = impureFunctions.get(
effect.function.identifier.id,
);
if (functionSignature != null) {
for (const [id, functionEffect] of functionSignature.effects) {
if (!impure.has(id)) {
impure.set(id, functionEffect);
hasChanges = true;
}
if (
id === functionSignature.returns &&
!impure.has(effect.into.identifier.id)
) {
impure.set(effect.into.identifier.id, functionEffect);
hasChanges = true;
}
}
}
break;
}
case 'MaybeAlias':
case 'Create':
case 'Freeze':
case 'Mutate':
case 'MutateConditionally':
case 'MutateFrozen':
case 'MutateGlobal':
case 'MutateTransitive':
case 'MutateTransitiveConditionally': {
break;
}
}
}
return hasChanges;
}
function inferImpureValues(
fn: HIRFunction,
impure: Map<IdentifierId, ImpureEffect>,
impureFunctions: Map<IdentifierId, ImpuritySignature>,
cache: FunctionCache,
): ImpuritySignature {
const getBlockControl = createControlDominators(fn, place => {
return impure.has(place.identifier.id);
});
let hasChanges = false;
let iterations = 0;
do {
hasChanges = false;
if (iterations++ > 100) {
throw new Error('too many iterations');
}
for (const block of fn.body.blocks.values()) {
const controlPlace = getBlockControl(block.id);
const controlImpureEffect =
controlPlace != null ? impure.get(controlPlace.identifier.id) : null;
for (const phi of block.phis) {
if (impure.has(phi.place.identifier.id)) {
// Already marked impure on a previous pass
continue;
}
let impureEffect = null;
for (const [, operand] of phi.operands) {
const operandEffect = impure.get(operand.identifier.id);
if (operandEffect != null) {
impureEffect = operandEffect;
break;
}
}
if (impureEffect != null) {
impure.set(phi.place.identifier.id, impureEffect);
hasChanges = true;
} else {
for (const [pred] of phi.operands) {
const predControl = getBlockControl(pred);
if (predControl != null) {
const predEffect = impure.get(predControl.identifier.id);
if (predEffect != null) {
impure.set(phi.place.identifier.id, predEffect);
hasChanges = true;
break;
}
}
}
}
}
for (const instr of block.instructions) {
const _impure = new Set(impure.keys());
hasChanges =
processEffects(
instr.id,
instr.effects ?? [],
impure,
impureFunctions,
cache,
) || hasChanges;
}
if (block.terminal.kind === 'return' && block.terminal.effects != null) {
hasChanges =
processEffects(
block.terminal.id,
block.terminal.effects,
impure,
impureFunctions,
cache,
) || hasChanges;
}
}
} while (hasChanges);
fn.env.logger?.debugLogIRs?.({
kind: 'debug',
name: 'ValidateNoImpureValuesInRender',
value: JSON.stringify(Array.from(impure.keys()).sort(), null, 2),
});
fn.env.logger?.debugLogIRs?.({
kind: 'debug',
name: 'ValidateNoImpureValuesInRender (function)',
value: JSON.stringify(Array.from(impureFunctions.keys()).sort(), null, 2),
});
const error = new CompilerError();
function validateRenderEffect(effect: RenderEffect): void {
let impureEffect = impure.get(effect.place.identifier.id);
if (impureEffect == null) {
const functionSignature = impureFunctions.get(effect.place.identifier.id);
impureEffect = functionSignature?.effects.get(functionSignature.returns);
}
if (impureEffect == null) {
return;
}
const diagnostic = CompilerDiagnostic.create({
category: impureEffect.category,
reason: impureEffect.reason,
description: impureEffect.description,
}).withDetails({
kind: 'error',
loc: effect.place.loc,
message: impureEffect.usageMessage,
});
if (!areEqualSourceLocations(effect.place.loc, impureEffect.into.loc)) {
diagnostic.withDetails({
kind: 'error',
loc: impureEffect.into.loc,
message: impureEffect.sourceMessage,
});
}
error.pushDiagnostic(diagnostic);
}
for (const block of fn.body.blocks.values()) {
for (const instr of block.instructions) {
const value = instr.value;
if (
value.kind === 'FunctionExpression' ||
value.kind === 'ObjectMethod'
) {
const result = inferFunctionExpressionMemo(
value.loweredFunc.func,
impure,
impureFunctions,
cache,
);
if (result.error.hasAnyErrors()) {
error.merge(result.error);
}
}
for (const effect of instr.effects ?? []) {
if (effect.kind === 'Render') {
validateRenderEffect(effect);
}
}
}
if (block.terminal.kind === 'return' && block.terminal.effects != null) {
for (const effect of block.terminal.effects) {
if (effect.kind === 'Render') {
validateRenderEffect(effect);
}
}
}
}
const impureEffects: Map<IdentifierId, ImpureEffect> = new Map();
for (const param of [...fn.context, ...fn.params, fn.returns]) {
const place = param.kind === 'Identifier' ? param : param.place;
const impureEffect = impure.get(place.identifier.id);
if (impureEffect != null) {
impureEffects.set(place.identifier.id, impureEffect);
}
}
return {effects: impureEffects, error, returns: fn.returns.identifier.id};
}
function areEqualFunctionSignatures(
sig1: Map<IdentifierId, ImpureEffect>,
sig2: Map<IdentifierId, ImpureEffect>,
): boolean {
return (
sig1.size === sig2.size &&
Array.from(sig1).every(
([id, effect]) =>
sig2.has(id) && hashEffect(effect) === hashEffect(sig2.get(id)!),
)
);
}

View File

@@ -1,137 +0,0 @@
# ValidateNoRefAccessInRender
This document summarizes the design and key learnings for the ref mutation validation pass.
## Purpose
Validates that a function does not mutate a ref value during render. This ensures React components follow the rules of React by not writing to `ref.current` during the render phase.
## Key Concepts
### Ref vs RefValue
- **Ref**: The ref object itself (e.g., `useRef()` return value). Has type `React.RefObject<T>`.
- **RefValue**: The `.current` property of a ref. This is the mutable value that should not be accessed during render.
The validation tracks both using a `RefInfo` type with a `refId` that correlates refs with their `.current` values.
### What Constitutes a Mutation
A mutation is any `PropertyStore` or `ComputedStore` instruction where:
1. The target object is a known ref (tracked in the `refs` map)
2. OR the target object has a ref type (`isUseRefType`)
### Allowed Patterns
1. **Event handlers and effect callbacks**: Functions that are not called at the top level during render can mutate refs freely.
2. **Null-guard initialization**: The pattern `if (ref.current == null) { ref.current = value; }` is allowed because it's a common lazy initialization pattern that only runs once.
## Algorithm: Single Forward Data-Flow Pass
The validation uses a single forward pass over all blocks:
### Phase 1: Track Refs
- Initialize refs from function params and context (captured variables)
- Process phi nodes to propagate ref info through control flow joins
- Track refs through LoadLocal, StoreLocal, PropertyLoad operations
### Phase 2: Detect Null Guards
- Track nullable values (null literals, undefined)
- Track binary comparisons of `ref.current` to null (`==`, `===`, `!=`, `!==`)
- Mark blocks as "safe" for specific refs when inside null-guard branches
- Propagate safety through control flow until fallthrough
### Phase 3: Validate Mutations
- For PropertyStore/ComputedStore on refs:
- If inside a null-guard for this ref: allow (but track for duplicate detection)
- If at top level: error immediately
- If in nested function: track for later (error if function is called)
### Phase 4: Track Ref-Mutating Functions
- When a FunctionExpression mutates a ref, track it in `refMutatingFunctions`
- When such a function is called at top level, report the error at the mutation site
## Key Data Structures
```typescript
// Correlates refs with their .current values
type RefInfo = {
kind: 'Ref' | 'RefValue';
refId: number;
};
// Tracks null-guard conditions
type GuardInfo = {
refId: number;
isEquality: boolean; // true for ==, ===; false for !=, !==
};
// Information about a mutation (for error reporting)
type MutationInfo = {
loc: SourceLocation;
isCurrentProperty: boolean;
};
```
## Error Reporting
### Error Location
Errors highlight the **entire instruction** (e.g., `ref.current = value`), not just the ref identifier. This is achieved by using `instr.loc` instead of `value.object.loc`.
### Duplicate Initialization
When a ref is initialized more than once inside a null-guard:
1. Primary error: Points to the second initialization
2. Secondary error: Points to the first initialization with "Ref was first initialized here"
### Transitive Mutations
When a function that mutates refs is called during render:
- The error points to the mutation site inside the function
- Not the call site (the call site is what triggers the check)
## Edge Cases and Patterns
### Unary NOT on Guards
The `!` operator inverts guard polarity:
```javascript
if (!ref.current) { ... } // Same as: if (ref.current == null)
```
### Nested Functions
Functions defined during render but not called are allowed to mutate refs:
```javascript
// OK - onClick is not called during render
const onClick = () => { ref.current = value; };
return <button onClick={onClick} />;
```
### Props with Ref Type
Refs can come from props. The validation handles `props.ref` by checking type information.
## Limitations / Known Gaps
The following patterns are NOT currently validated by this pass:
1. **Impure values in render**: `Date.now()`, `Math.random()` flowing into render context (handled by `ValidateNoImpureValuesInRender`)
2. **useState/useReducer callbacks**: These hooks call their initializer functions during render, so ref access inside them should error. This requires special hook semantics.
3. **Ref reads during render**: This pass focuses on mutations. Ref reads are handled separately.
## Testing
Test fixtures use naming conventions:
- `error.*.ts` - Fixtures expected to produce compilation errors
- Regular names - Fixtures expected to compile successfully
Run tests with:
```bash
yarn snap -p <pattern> --nodebug # Run specific tests
yarn snap -p <pattern> --nodebug -u # Update expected output
```

View File

@@ -202,10 +202,10 @@ function getSetStateCall(
);
};
const isRefControlledBlock: (id: BlockId) => Place | null =
const isRefControlledBlock: (id: BlockId) => boolean =
enableAllowSetStateFromRefsInEffects
? createControlDominators(fn, place => isDerivedFromRef(place))
: (): Place | null => null;
: (): boolean => false;
for (const [, block] of fn.body.blocks) {
if (enableAllowSetStateFromRefsInEffects) {
@@ -224,7 +224,7 @@ function getSetStateCall(
refDerivedValues.add(phi.place.identifier.id);
} else {
for (const [pred] of phi.operands) {
if (isRefControlledBlock(pred) != null) {
if (isRefControlledBlock(pred)) {
refDerivedValues.add(phi.place.identifier.id);
break;
}
@@ -337,7 +337,7 @@ function getSetStateCall(
* be needed when initial layout measurements from refs need to be stored in state.
*/
return null;
} else if (isRefControlledBlock(block.id) != null) {
} else if (isRefControlledBlock(block.id)) {
continue;
}
}

View File

@@ -1,51 +0,0 @@
## Input
```javascript
import {useRef} from 'react';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function Component() {
const ref = useRef(null);
const object = {};
object.foo = () => ref.current;
const refValue = object.foo();
return <div>{refValue}</div>;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function Component() {
const $ = _c(2);
const ref = useRef(null);
const object = {};
object.foo = () => ref.current;
const refValue = object.foo();
let t0;
if ($[0] !== refValue) {
t0 = <div>{refValue}</div>;
$[0] = refValue;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -5,10 +5,6 @@
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function Component(props) {
const ref = useRef(props.value);
const object = {};
@@ -30,10 +26,6 @@ import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
import { Stringify } from "shared-runtime";
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function Component(props) {
const $ = _c(1);
const ref = useRef(props.value);

View File

@@ -1,10 +1,6 @@
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function Component(props) {
const ref = useRef(props.value);
const object = {};

View File

@@ -1,74 +0,0 @@
## Input
```javascript
import {useRef} from 'react';
import {addOne} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
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: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
import { addOne } from "shared-runtime";
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function useKeyCommand() {
const $ = _c(1);
const currentPosition = useRef(0);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
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")() };
t0 = [moveLeft, moveRight];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: useKeyCommand,
params: [],
};
```
### Eval output
(kind: ok) [{},{}]

View File

@@ -5,7 +5,6 @@
function Component() {
const Foo = () => {
someGlobal = true;
return <div />;
};
return <Foo />;
}
@@ -27,9 +26,9 @@ error.assign-global-in-component-tag-function.ts:3:4
2 | const Foo = () => {
> 3 | someGlobal = true;
| ^^^^^^^^^^ `someGlobal` cannot be reassigned
4 | return <div />;
5 | };
6 | return <Foo />;
4 | };
5 | return <Foo />;
6 | }
```

View File

@@ -1,7 +1,6 @@
function Component() {
const Foo = () => {
someGlobal = true;
return <div />;
};
return <Foo />;
}

View File

@@ -5,7 +5,6 @@
function Component() {
const foo = () => {
someGlobal = true;
return <div />;
};
// Children are generally access/called during render, so
// modifying a global in a children function is almost
@@ -30,9 +29,9 @@ error.assign-global-in-jsx-children.ts:3:4
2 | const foo = () => {
> 3 | someGlobal = true;
| ^^^^^^^^^^ `someGlobal` cannot be reassigned
4 | return <div />;
5 | };
6 | // Children are generally access/called during render, so
4 | };
5 | // Children are generally access/called during render, so
6 | // modifying a global in a children function is almost
```

View File

@@ -1,7 +1,6 @@
function Component() {
const foo = () => {
someGlobal = true;
return <div />;
};
// Children are generally access/called during render, so
// modifying a global in a children function is almost

View File

@@ -0,0 +1,64 @@
## Input
```javascript
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
```
Found 2 errors:
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.capture-ref-for-mutation.ts:12:13
10 | };
11 | const moveLeft = {
> 12 | handler: handleKey('left')(),
| ^^^^^^^^^^^^^^^^^ This function accesses a ref value
13 | };
14 | const moveRight = {
15 | handler: handleKey('right')(),
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.capture-ref-for-mutation.ts:15:13
13 | };
14 | const moveRight = {
> 15 | handler: handleKey('right')(),
| ^^^^^^^^^^^^^^^^^^ This function accesses a ref value
16 | };
17 | return [moveLeft, moveRight];
18 | }
```

View File

@@ -1,10 +1,6 @@
import {useRef} from 'react';
import {addOne} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function useKeyCommand() {
const currentPosition = useRef(0);
const handleKey = direction => () => {

View File

@@ -20,26 +20,30 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot access ref value during render
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.hook-ref-value.ts:5:22
3 | function Component(props) {
4 | const ref = useRef();
> 5 | useEffect(() => {}, [ref.current]);
| ^^^^^^^^^^^^^ Ref value is used during render
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {
error.hook-ref-value.ts:5:23
3 | function Component(props) {
4 | const ref = useRef();
> 5 | useEffect(() => {}, [ref.current]);
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^ Cannot access ref value during render
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {
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.hook-ref-value.ts:5:23
3 | function Component(props) {
4 | const ref = useRef();
> 5 | useEffect(() => {}, [ref.current]);
| ^^^^^^^^^^^ Cannot access ref value during render
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {

View File

@@ -17,23 +17,15 @@ function Component(props) {
```
Found 1 error:
Error: Cannot access ref value during render
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-during-render.ts:5:9
3 | const ref = useRef(null);
4 | const value = ref.current;
> 5 | return value;
| ^^^^^ Ref value is used during render
6 | }
7 |
error.invalid-access-ref-during-render.ts:4:16
2 | function Component(props) {
3 | const ref = useRef(null);
> 4 | const value = ref.current;
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^ Cannot access ref value during render
5 | return value;
6 | }
7 |

View File

@@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Error: Cannot access ref value during render
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).
@@ -36,16 +36,7 @@ error.invalid-access-ref-in-reducer-init.ts:8:4
6 | (state, action) => state + action,
7 | 0,
> 8 | init => ref.current
| ^^^^^^^^^^^^^^^^^^^ Ref value is used during render
9 | );
10 |
11 | return <Stringify state={state} />;
error.invalid-access-ref-in-reducer-init.ts:8:12
6 | (state, action) => state + action,
7 | 0,
> 8 | init => ref.current
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
9 | );
10 |
11 | return <Stringify state={state} />;

View File

@@ -22,9 +22,9 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 2 errors:
Found 1 error:
Error: Cannot access ref value during render
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).
@@ -32,38 +32,7 @@ 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);
| ^^^^^^^^^^^^^^^^^ Ref value is used during render
6 |
7 | return <Stringify state={state} />;
8 | }
error.invalid-access-ref-in-reducer.ts:5:35
3 | function Component(props) {
4 | const ref = useRef(props.value);
> 5 | const [state] = useReducer(() => ref.current, null);
| ^^^^^^^^^^^ Ref is initially accessed
6 |
7 | return <Stringify state={state} />;
8 | }
Error: Cannot access ref value 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:7:27
5 | const [state] = useReducer(() => ref.current, null);
6 |
> 7 | return <Stringify state={state} />;
| ^^^^^ Ref value is used during render
8 | }
9 |
10 | export const FIXTURE_ENTRYPOINT = {
error.invalid-access-ref-in-reducer.ts:5:35
3 | function Component(props) {
4 | const ref = useRef(props.value);
> 5 | const [state] = useReducer(() => ref.current, null);
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
6 |
7 | return <Stringify state={state} />;
8 | }

View File

@@ -0,0 +1,37 @@
## Input
```javascript
import {useRef} from 'react';
function Component() {
const ref = useRef(null);
const object = {};
object.foo = () => ref.current;
const refValue = object.foo();
return <div>{refValue}</div>;
}
```
## 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-render-mutate-object-with-ref-function.ts:7:19
5 | const object = {};
6 | object.foo = () => ref.current;
> 7 | const refValue = object.foo();
| ^^^^^^^^^^ This function accesses a ref value
8 | return <div>{refValue}</div>;
9 | }
10 |
```

View File

@@ -1,9 +1,5 @@
import {useRef} from 'react';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function Component() {
const ref = useRef(null);
const object = {};

View File

@@ -22,9 +22,9 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 2 errors:
Found 1 error:
Error: Cannot access ref value during render
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).
@@ -32,38 +32,7 @@ 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);
| ^^^^^^^^^^^^^^^^^ Ref value is used during render
6 |
7 | return <Stringify state={state} />;
8 | }
error.invalid-access-ref-in-state-initializer.ts:5:33
3 | function Component(props) {
4 | const ref = useRef(props.value);
> 5 | const [state] = useState(() => ref.current);
| ^^^^^^^^^^^ Ref is initially accessed
6 |
7 | return <Stringify state={state} />;
8 | }
Error: Cannot access ref value 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:7:27
5 | const [state] = useState(() => ref.current);
6 |
> 7 | return <Stringify state={state} />;
| ^^^^^ Ref value is used during render
8 | }
9 |
10 | export const FIXTURE_ENTRYPOINT = {
error.invalid-access-ref-in-state-initializer.ts:5:33
3 | function Component(props) {
4 | const ref = useRef(props.value);
> 5 | const [state] = useState(() => ref.current);
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
6 |
7 | return <Stringify state={state} />;
8 | }

View File

@@ -21,27 +21,17 @@ function Component(props) {
```
Found 1 error:
Error: Cannot access ref value during render
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-aliased-ref-in-callback-invoked-during-render-.ts:7:37
5 | const aliasedRef = ref;
6 | const current = aliasedRef.current;
> 7 | return <Foo item={item} current={current} />;
| ^^^^^^^ Ref value is used during render
error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:9:33
7 | return <Foo item={item} current={current} />;
8 | };
9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
> 9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
| ^^^^^^^^^^^^^^^^^^^^^^^^ Cannot access ref value during render
10 | }
error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:6:20
4 | const renderItem = item => {
5 | const aliasedRef = ref;
> 6 | const current = aliasedRef.current;
| ^^^^^^^^^^^^^^^^^^ Ref is initially accessed
7 | return <Foo item={item} current={current} />;
8 | };
9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
11 |
```

View File

@@ -27,14 +27,14 @@ 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-disallow-mutating-refs-in-render-transitive.ts:6:4
4 |
5 | const setRef = () => {
> 6 | ref.current = false;
| ^^^^^^^^^^^ Cannot update ref during render
7 | };
8 | const changeRef = setRef;
9 | changeRef();
error.invalid-disallow-mutating-refs-in-render-transitive.ts:9:2
7 | };
8 | const changeRef = setRef;
> 9 | changeRef();
| ^^^^^^^^^ This function accesses a ref value
10 |
11 | return <button ref={ref} />;
12 | }
```

View File

@@ -1,52 +0,0 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
import {arrayPush, identity, makeArray} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render. The impurity is lost
* when passed through external function calls.
*/
function Component() {
const getDate = () => Date.now();
const now = getDate();
const array = [];
arrayPush(array, now);
return <Foo hasDate={array} />;
}
```
## Error
```
Found 1 error:
Error: Cannot access impure value during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render-indirect-via-mutation.ts:15:23
13 | const array = [];
14 | arrayPush(array, now);
> 15 | return <Foo hasDate={array} />;
| ^^^^^ Cannot access impure value during render
16 | }
17 |
error.invalid-impure-functions-in-render-indirect-via-mutation.ts:11:24
9 | */
10 | function Component() {
> 11 | const getDate = () => Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
12 | const now = getDate();
13 | const array = [];
14 | arrayPush(array, now);
```

View File

@@ -1,16 +0,0 @@
// @validateNoImpureFunctionsInRender
import {arrayPush, identity, makeArray} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render. The impurity is lost
* when passed through external function calls.
*/
function Component() {
const getDate = () => Date.now();
const now = getDate();
const array = [];
arrayPush(array, now);
return <Foo hasDate={array} />;
}

View File

@@ -1,50 +0,0 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
import {identity, makeArray} from 'shared-runtime';
function Component() {
const now = Date.now();
const f = () => {
const array = makeArray(now);
const hasDate = identity(array);
return hasDate;
};
const hasDate = f();
return <Foo hasDate={hasDate} />;
}
```
## Error
```
Found 1 error:
Error: Cannot access impure value during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render-via-function-call.ts:13:23
11 | };
12 | const hasDate = f();
> 13 | return <Foo hasDate={hasDate} />;
| ^^^^^^^ Cannot access impure value during render
14 | }
15 |
error.invalid-impure-functions-in-render-via-function-call.ts:6:14
4 |
5 | function Component() {
> 6 | const now = Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
7 | const f = () => {
8 | const array = makeArray(now);
9 | const hasDate = identity(array);
```

View File

@@ -1,14 +0,0 @@
// @validateNoImpureFunctionsInRender
import {identity, makeArray} from 'shared-runtime';
function Component() {
const now = Date.now();
const f = () => {
const array = makeArray(now);
const hasDate = identity(array);
return hasDate;
};
const hasDate = f();
return <Foo hasDate={hasDate} />;
}

View File

@@ -1,51 +0,0 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
import {typedArrayPush, typedIdentity} from 'shared-runtime';
function Component() {
const now = () => Date.now();
const renderItem = () => {
const array = [];
typedArrayPush(array, now());
const hasDate = typedIdentity(array);
return <Bar hasDate={hasDate} />;
};
return <Foo renderItem={renderItem} />;
}
```
## Error
```
Found 1 error:
Error: Cannot access impure value during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render-via-render-helper-typed.ts:11:25
9 | typedArrayPush(array, now());
10 | const hasDate = typedIdentity(array);
> 11 | return <Bar hasDate={hasDate} />;
| ^^^^^^^ Cannot access impure value during render
12 | };
13 | return <Foo renderItem={renderItem} />;
14 | }
error.invalid-impure-functions-in-render-via-render-helper-typed.ts:6:20
4 |
5 | function Component() {
> 6 | const now = () => Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
7 | const renderItem = () => {
8 | const array = [];
9 | typedArrayPush(array, now());
```

View File

@@ -1,14 +0,0 @@
// @validateNoImpureFunctionsInRender
import {typedArrayPush, typedIdentity} from 'shared-runtime';
function Component() {
const now = () => Date.now();
const renderItem = () => {
const array = [];
typedArrayPush(array, now());
const hasDate = typedIdentity(array);
return <Bar hasDate={hasDate} />;
};
return <Foo renderItem={renderItem} />;
}

View File

@@ -19,65 +19,41 @@ function Component() {
```
Found 3 errors:
Error: Cannot access impure value during render
Error: Cannot call impure function during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:7:20
5 | const now = performance.now();
6 | const rand = Math.random();
> 7 | return <Foo date={date} now={now} rand={rand} />;
| ^^^^ Cannot access impure value during render
8 | }
9 |
`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:4:15
2 |
3 | function Component() {
> 4 | const date = Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
| ^^^^^^^^^^ Cannot call impure function
5 | const now = performance.now();
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
Error: Cannot access impure value during render
Error: Cannot call impure function during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:7:31
5 | const now = performance.now();
6 | const rand = Math.random();
> 7 | return <Foo date={date} now={now} rand={rand} />;
| ^^^ Cannot access impure value during render
8 | }
9 |
`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:5:14
3 | function Component() {
4 | const date = Date.now();
> 5 | const now = performance.now();
| ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function.
| ^^^^^^^^^^^^^^^^^ Cannot call impure function
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
Error: Cannot access impure value during render
Error: Cannot call impure function during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:7:42
5 | const now = performance.now();
6 | const rand = Math.random();
> 7 | return <Foo date={date} now={now} rand={rand} />;
| ^^^^ Cannot access impure value during render
8 | }
9 |
`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:6:15
4 | const date = Date.now();
5 | const now = performance.now();
> 6 | const rand = Math.random();
| ^^^^^^^^^^^^^ `Math.random` is an impure function.
| ^^^^^^^^^^^^^ Cannot call impure function
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
9 |

View File

@@ -1,66 +0,0 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
function Component() {
const now = () => Date.now();
const render = () => {
return <div>{now()}</div>;
};
return <div>{render()}</div>;
}
```
## Error
```
Found 2 errors:
Error: Cannot access impure value during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-value-in-render-helper.ts:5:17
3 | const now = () => Date.now();
4 | const render = () => {
> 5 | return <div>{now()}</div>;
| ^^^^^ Cannot access impure value during render
6 | };
7 | return <div>{render()}</div>;
8 | }
error.invalid-impure-value-in-render-helper.ts:3:20
1 | // @validateNoImpureFunctionsInRender
2 | function Component() {
> 3 | const now = () => Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
4 | const render = () => {
5 | return <div>{now()}</div>;
6 | };
Error: Cannot access impure value during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-value-in-render-helper.ts:7:15
5 | return <div>{now()}</div>;
6 | };
> 7 | return <div>{render()}</div>;
| ^^^^^^^^ Cannot access impure value during render
8 | }
9 |
error.invalid-impure-value-in-render-helper.ts:3:20
1 | // @validateNoImpureFunctionsInRender
2 | function Component() {
> 3 | const now = () => Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
4 | const render = () => {
5 | return <div>{now()}</div>;
6 | };
```

View File

@@ -1,8 +0,0 @@
// @validateNoImpureFunctionsInRender
function Component() {
const now = () => Date.now();
const render = () => {
return <div>{now()}</div>;
};
return <div>{render()}</div>;
}

View File

@@ -0,0 +1,34 @@
## Input
```javascript
// @validateRefAccessDuringRender
function Component(props) {
const ref = useRef(null);
const x = foo(ref);
return x.current;
}
```
## 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-pass-ref-to-function.ts:4:16
2 | function Component(props) {
3 | const ref = useRef(null);
> 4 | const x = foo(ref);
| ^^^ Passing a ref to a function may read its value during render
5 | return x.current;
6 | }
7 |
```

View File

@@ -0,0 +1,6 @@
// @validateRefAccessDuringRender
function Component(props) {
const ref = useRef(null);
const x = foo(ref);
return x.current;
}

View File

@@ -16,23 +16,15 @@ function Component({ref}) {
```
Found 1 error:
Error: Cannot access ref value during render
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-read-ref-prop-in-render-destructure.ts:4:15
2 | function Component({ref}) {
3 | const value = ref.current;
> 4 | return <div>{value}</div>;
| ^^^^^ Ref value is used during render
5 | }
6 |
error.invalid-read-ref-prop-in-render-destructure.ts:3:16
1 | // @validateRefAccessDuringRender @compilationMode:"infer"
2 | function Component({ref}) {
> 3 | const value = ref.current;
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^ Cannot access ref value during render
4 | return <div>{value}</div>;
5 | }
6 |

View File

@@ -16,23 +16,15 @@ function Component(props) {
```
Found 1 error:
Error: Cannot access ref value during render
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-read-ref-prop-in-render-property-load.ts:4:15
2 | function Component(props) {
3 | const value = props.ref.current;
> 4 | return <div>{value}</div>;
| ^^^^^ Ref value is used during render
5 | }
6 |
error.invalid-read-ref-prop-in-render-property-load.ts:3:16
1 | // @validateRefAccessDuringRender @compilationMode:"infer"
2 | function Component(props) {
> 3 | const value = props.ref.current;
| ^^^^^^^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^^^^^^^ Cannot access ref value during render
4 | return <div>{value}</div>;
5 | }
6 |

View File

@@ -22,27 +22,57 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Found 4 errors:
Error: Cannot access ref value during render
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).
4 | component C() {
5 | const r = useRef(null);
> 6 | const current = !r.current;
| ^^^^^^^^^ Cannot access ref value during render
7 | return <div>{current}</div>;
8 | }
9 |
To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`
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).
4 | component C() {
5 | const r = useRef(null);
> 6 | const current = !r.current;
| ^^^^^^^^^^ Cannot access ref value during render
7 | return <div>{current}</div>;
8 | }
9 |
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).
5 | const r = useRef(null);
6 | const current = !r.current;
> 7 | return <div>{current}</div>;
| ^^^^^^^ Ref value is used during render
| ^^^^^^^ Cannot access ref value during render
8 | }
9 |
10 | export const FIXTURE_ENTRYPOINT = {
4 | component C() {
5 | const r = useRef(null);
> 6 | const current = !r.current;
| ^^^^^^^^^ Ref is initially accessed
7 | return <div>{current}</div>;
8 | }
9 |
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).
5 | const r = useRef(null);
6 | const current = !r.current;
> 7 | return <div>{current}</div>;
| ^^^^^^^ Cannot access ref value during render
8 | }
9 |
10 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -20,27 +20,17 @@ function Component(props) {
```
Found 1 error:
Error: Cannot access ref value during render
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-ref-in-callback-invoked-during-render.ts:6:37
4 | const renderItem = item => {
5 | const current = ref.current;
> 6 | return <Foo item={item} current={current} />;
| ^^^^^^^ Ref value is used during render
7 | };
8 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
9 | }
error.invalid-ref-in-callback-invoked-during-render.ts:5:20
3 | const ref = useRef(null);
4 | const renderItem = item => {
> 5 | const current = ref.current;
| ^^^^^^^^^^^ Ref is initially accessed
6 | return <Foo item={item} current={current} />;
7 | };
8 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
error.invalid-ref-in-callback-invoked-during-render.ts:8:33
6 | return <Foo item={item} current={current} />;
7 | };
> 8 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
| ^^^^^^^^^^^^^^^^^^^^^^^^ Cannot access ref value during render
9 | }
10 |
```

View File

@@ -29,13 +29,15 @@ 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).
5 | const r = useRef(null);
6 | if (!r.current) {
> 7 | r.current = 1;
| ^^^^^^^^^ Cannot update ref during render
8 | }
9 | }
10 |
4 | component C() {
5 | const r = useRef(null);
> 6 | if (!r.current) {
| ^^^^^^^^^ Cannot access ref value during render
7 | r.current = 1;
8 | }
9 | }
To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`
```

View File

@@ -16,7 +16,7 @@ function Component(props) {
```
Found 1 error:
Error: Cannot access ref value during render
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).
@@ -24,7 +24,7 @@ error.invalid-ref-value-as-props.ts:4:19
2 | function Component(props) {
3 | const ref = useRef(null);
> 4 | return <Foo ref={ref.current} />;
| ^^^^^^^^^^^ Ref value is used during render
| ^^^^^^^^^^^ Cannot access ref value during render
5 | }
6 |
```

View File

@@ -15,9 +15,22 @@ function Component(props) {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot access ref value during render
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-set-and-read-ref-during-render.ts:4:2
2 | function Component(props) {
3 | const ref = useRef(null);
> 4 | ref.current = props.value;
| ^^^^^^^^^^^ Cannot update ref during render
5 | return ref.current;
6 | }
7 |
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).
@@ -25,7 +38,7 @@ error.invalid-set-and-read-ref-during-render.ts:5:9
3 | const ref = useRef(null);
4 | ref.current = props.value;
> 5 | return ref.current;
| ^^^^^^^^^^^ Ref value is used during render
| ^^^^^^^^^^^ Cannot access ref value during render
6 | }
7 |
```

View File

@@ -15,9 +15,22 @@ function Component(props) {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot access ref value during render
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-set-and-read-ref-nested-property-during-render.ts:4:2
2 | function Component(props) {
3 | const ref = useRef({inner: null});
> 4 | ref.current.inner = props.value;
| ^^^^^^^^^^^ Cannot update ref during render
5 | return ref.current.inner;
6 | }
7 |
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).
@@ -25,15 +38,7 @@ error.invalid-set-and-read-ref-nested-property-during-render.ts:5:9
3 | const ref = useRef({inner: null});
4 | ref.current.inner = props.value;
> 5 | return ref.current.inner;
| ^^^^^^^^^^^^^^^^^ Ref value is used during render
6 | }
7 |
error.invalid-set-and-read-ref-nested-property-during-render.ts:5:9
3 | const ref = useRef({inner: null});
4 | ref.current.inner = props.value;
> 5 | return ref.current.inner;
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^^^^^^^ Cannot access ref value during render
6 | }
7 |
```

View File

@@ -0,0 +1,53 @@
## Input
```javascript
// @validateRefAccessDuringRender
function Foo({a}) {
const ref = useRef();
// type information is lost here as we don't track types of fields
const val = {ref};
// without type info, we don't know that val.ref.current is a ref value so we
// *would* end up depending on val.ref.current
// however, this is an instance of accessing a ref during render and is disallowed
// under React's rules, so we reject this input
const x = {a, val: val.ref.current};
return <VideoList videos={x} />;
}
```
## Error
```
Found 2 errors:
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-use-ref-added-to-dep-without-type-info.ts:10:21
8 | // however, this is an instance of accessing a ref during render and is disallowed
9 | // under React's rules, so we reject this input
> 10 | const x = {a, val: val.ref.current};
| ^^^^^^^^^^^^^^^ Cannot access ref value during render
11 |
12 | return <VideoList videos={x} />;
13 | }
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-use-ref-added-to-dep-without-type-info.ts:12:28
10 | const x = {a, val: val.ref.current};
11 |
> 12 | return <VideoList videos={x} />;
| ^ Cannot access ref value during render
13 | }
14 |
```

View File

@@ -0,0 +1,13 @@
// @validateRefAccessDuringRender
function Foo({a}) {
const ref = useRef();
// type information is lost here as we don't track types of fields
const val = {ref};
// without type info, we don't know that val.ref.current is a ref value so we
// *would* end up depending on val.ref.current
// however, this is an instance of accessing a ref during render and is disallowed
// under React's rules, so we reject this input
const x = {a, val: val.ref.current};
return <VideoList videos={x} />;
}

View File

@@ -25,7 +25,19 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Found 2 errors:
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).
6 | component C() {
7 | const r = useRef(DEFAULT_VALUE);
> 8 | if (r.current == DEFAULT_VALUE) {
| ^^^^^^^^^ Cannot access ref value during render
9 | r.current = 1;
10 | }
11 | }
Error: Cannot access refs during render

View File

@@ -0,0 +1,41 @@
## Input
```javascript
//@flow
import {useRef} from 'react';
component C() {
const r = useRef(null);
if (r.current == null) {
f(r);
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
## 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).
5 | const r = useRef(null);
6 | if (r.current == null) {
> 7 | f(r);
| ^ Passing a ref to a function may read its value during render
8 | }
9 | }
10 |
```

View File

@@ -1,10 +1,6 @@
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {

View File

@@ -0,0 +1,41 @@
## Input
```javascript
//@flow
import {useRef} from 'react';
component C() {
const r = useRef(null);
if (r.current == null) {
f(r.current);
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
## 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).
5 | const r = useRef(null);
6 | if (r.current == null) {
> 7 | f(r.current);
| ^^^^^^^^^ Passing a ref to a function may read its value during render
8 | }
9 | }
10 |
```

View File

@@ -1,10 +1,6 @@
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {

View File

@@ -33,18 +33,10 @@ React refs are values that are not needed for rendering. Refs should only be acc
6 | if (r.current == null) {
7 | r.current = 42;
> 8 | r.current = 42;
| ^^^^^^^^^ Ref is initialized more than once during render
| ^^^^^^^^^ Cannot update ref during render
9 | }
10 | }
11 |
5 | const r = useRef(null);
6 | if (r.current == null) {
> 7 | r.current = 42;
| ^^^^^^^^^ Ref was first initialized here
8 | r.current = 42;
9 | }
10 | }
```

View File

@@ -24,19 +24,31 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Found 2 errors:
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).
4 | component C() {
5 | const r = useRef(null);
> 6 | const guard = r.current == null;
| ^^^^^^^^^^^^^^^^^ Cannot access ref value during render
7 | if (guard) {
8 | r.current = 1;
9 | }
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).
5 | const r = useRef(null);
6 | const guard = r.current == null;
7 | if (guard) {
> 8 | r.current = 1;
| ^^^^^^^^^ Cannot update ref during render
> 7 | if (guard) {
| ^^^^^ Cannot access ref value during render
8 | r.current = 1;
9 | }
10 | }
11 |
```

View File

@@ -0,0 +1,42 @@
## Input
```javascript
//@flow
import {useRef} from 'react';
component C() {
const r = useRef(null);
if (r.current == null) {
r.current = 1;
}
f(r.current);
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
## 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).
7 | r.current = 1;
8 | }
> 9 | f(r.current);
| ^^^^^^^^^ Passing a ref to a function may read its value during render
10 | }
11 |
12 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -1,10 +1,6 @@
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {

View File

@@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Error: Cannot access ref value during render
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).
@@ -30,7 +30,7 @@ error.ref-optional.ts:5:9
3 | function Component(props) {
4 | const ref = useRef();
> 5 | return ref?.current;
| ^^^^^^^^^^^^ Ref value is used during render
| ^^^^^^^^^^^^ Cannot access ref value during render
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {

View File

@@ -0,0 +1,69 @@
## Input
```javascript
// @enableInferEventHandlers
import {useRef} from 'react';
// Simulates a custom component wrapper
function CustomForm({onSubmit, children}: any) {
return <form onSubmit={onSubmit}>{children}</form>;
}
// Simulates react-hook-form's handleSubmit
function handleSubmit<T>(callback: (data: T) => void) {
return (event: any) => {
event.preventDefault();
callback({} as T);
};
}
function Component() {
const ref = useRef<HTMLInputElement>(null);
const onSubmit = (data: any) => {
// This should error: passing function with ref access to custom component
// event handler, even though it would be safe on a native <form>
if (ref.current !== null) {
console.log(ref.current.value);
}
};
return (
<>
<input ref={ref} />
<CustomForm onSubmit={handleSubmit(onSubmit)}>
<button type="submit">Submit</button>
</CustomForm>
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## 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.ref-value-in-custom-component-event-handler-wrapper.ts:31:41
29 | <>
30 | <input ref={ref} />
> 31 | <CustomForm onSubmit={handleSubmit(onSubmit)}>
| ^^^^^^^^ Passing a ref to a function may read its value during render
32 | <button type="submit">Submit</button>
33 | </CustomForm>
34 | </>
```

View File

@@ -18,8 +18,8 @@ function Component() {
const ref = useRef<HTMLInputElement>(null);
const onSubmit = (data: any) => {
// Allowed: we aren't sure that the ref.current value flows into the render
// output, so we optimistically assume it's safe
// This should error: passing function with ref access to custom component
// event handler, even though it would be safe on a native <form>
if (ref.current !== null) {
console.log(ref.current.value);
}

View File

@@ -0,0 +1,55 @@
## Input
```javascript
// @enableInferEventHandlers
import {useRef} from 'react';
// Simulates a handler wrapper
function handleClick(value: any) {
return () => {
console.log(value);
};
}
function Component() {
const ref = useRef(null);
// This should still error: passing ref.current directly to a wrapper
// The ref value is accessed during render, not in the event handler
return (
<>
<input ref={ref} />
<button onClick={handleClick(ref.current)}>Click</button>
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## 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.ref-value-in-event-handler-wrapper.ts:19:35
17 | <>
18 | <input ref={ref} />
> 19 | <button onClick={handleClick(ref.current)}>Click</button>
| ^^^^^^^^^^^ Cannot access ref value during render
20 | </>
21 | );
22 | }
```

View File

@@ -11,8 +11,8 @@ function handleClick(value: any) {
function Component() {
const ref = useRef(null);
// Allowed: we aren't sure that the ref.current value flows into the render
// output, so we optimistically assume it's safe
// This should still error: passing ref.current directly to a wrapper
// The ref value is accessed during render, not in the event handler
return (
<>
<input ref={ref} />

View File

@@ -41,7 +41,7 @@ error.todo-useCallback-set-ref-nested-property-ref-modified-later-preserve-memoi
12 |
13 | // The ref is modified later, extending its range and preventing memoization of onChange
> 14 | ref.current.inner = null;
| ^^^^^^^^^^^^^^^^^ Cannot update ref during render
| ^^^^^^^^^^^ Cannot update ref during render
15 |
16 | return <input onChange={onChange} />;
17 | }

View File

@@ -40,14 +40,14 @@ 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.useCallback-accesses-ref-mutated-later-via-function-preserve-memoization.ts:15:4
13 | // The ref is modified later, extending its range and preventing memoization of onChange
14 | const reset = () => {
> 15 | ref.current.inner = null;
| ^^^^^^^^^^^^^^^^^ Cannot update ref during render
error.useCallback-accesses-ref-mutated-later-via-function-preserve-memoization.ts:17:2
15 | ref.current.inner = null;
16 | };
17 | reset();
> 17 | reset();
| ^^^^^ This function accesses a ref value
18 |
19 | return <input onChange={onChange} />;
20 | }
```

View File

@@ -40,7 +40,7 @@ error.useCallback-set-ref-nested-property-dont-preserve-memoization.ts:13:2
11 | });
12 |
> 13 | ref.current.inner = null;
| ^^^^^^^^^^^^^^^^^ Cannot update ref during render
| ^^^^^^^^^^^ Cannot update ref during render
14 |
15 | return <input onChange={onChange} />;
16 | }

View File

@@ -0,0 +1,39 @@
## Input
```javascript
// @validateRefAccessDuringRender:true
function Foo(props, ref) {
console.log(ref.current);
return <div>{props.bar}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{bar: 'foo'}, {ref: {cuurrent: 1}}],
isComponent: true,
};
```
## 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.validate-mutate-ref-arg-in-render.ts:3:14
1 | // @validateRefAccessDuringRender:true
2 | function Foo(props, ref) {
> 3 | console.log(ref.current);
| ^^^^^^^^^^^ Passing a ref to a function may read its value during render
4 | return <div>{props.bar}</div>;
5 | }
6 |
```

View File

@@ -1,7 +1,5 @@
// @validateRefAccessDuringRender:true
function Foo(props, ref) {
// Allowed: the value is not guaranteed to flow into something that's rendered
console.log(ref.current);
return <div>{props.bar}</div>;
}

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies @validateRefAccessDuringRender:false
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {Stringify} from 'shared-runtime';

View File

@@ -1,4 +1,4 @@
// @validateExhaustiveMemoizationDependencies @validateRefAccessDuringRender:false
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {Stringify} from 'shared-runtime';

View File

@@ -1,56 +0,0 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
import {identity, makeArray} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render. The impurity is lost
* when passed through external function calls.
*/
function Component() {
const getDate = () => Date.now();
const array = makeArray(getDate());
const hasDate = identity(array);
return <Foo hasDate={hasDate} />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateNoImpureFunctionsInRender
import { identity, makeArray } from "shared-runtime";
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render. The impurity is lost
* when passed through external function calls.
*/
function Component() {
const $ = _c(1);
const getDate = _temp;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const array = makeArray(getDate());
const hasDate = identity(array);
t0 = <Foo hasDate={hasDate} />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
function _temp() {
return Date.now();
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,15 +0,0 @@
// @validateNoImpureFunctionsInRender
import {identity, makeArray} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render. The impurity is lost
* when passed through external function calls.
*/
function Component() {
const getDate = () => Date.now();
const array = makeArray(getDate());
const hasDate = identity(array);
return <Foo hasDate={hasDate} />;
}

View File

@@ -1,64 +0,0 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
import {identity, makeArray} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render. The impurity is lost
* when passed through external function calls.
*/
function Component() {
const now = () => Date.now();
const f = () => {
const array = makeArray(now());
const hasDate = identity(array);
return hasDate;
};
const hasDate = f();
return <Foo hasDate={hasDate} />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateNoImpureFunctionsInRender
import { identity, makeArray } from "shared-runtime";
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render. The impurity is lost
* when passed through external function calls.
*/
function Component() {
const $ = _c(1);
const now = _temp;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const f = () => {
const array = makeArray(now());
const hasDate = identity(array);
return hasDate;
};
const hasDate_0 = f();
t0 = <Foo hasDate={hasDate_0} />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
function _temp() {
return Date.now();
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,19 +0,0 @@
// @validateNoImpureFunctionsInRender
import {identity, makeArray} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render. The impurity is lost
* when passed through external function calls.
*/
function Component() {
const now = () => Date.now();
const f = () => {
const array = makeArray(now());
const hasDate = identity(array);
return hasDate;
};
const hasDate = f();
return <Foo hasDate={hasDate} />;
}

View File

@@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"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)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":20,"index":307},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}}
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"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)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":237},"end":{"line":8,"column":50,"index":285},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":259},"end":{"line":8,"column":30,"index":265},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -19,65 +19,41 @@ function Component() {
```
Found 3 errors:
Error: Cannot access impure value during render
Error: Cannot call impure function during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:7:20
5 | const now = performance.now();
6 | const rand = Math.random();
> 7 | return <Foo date={date} now={now} rand={rand} />;
| ^^^^ Cannot access impure value during render
8 | }
9 |
`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:4:15
2 |
3 | function Component() {
> 4 | const date = Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
| ^^^^^^^^^^ Cannot call impure function
5 | const now = performance.now();
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
Error: Cannot access impure value during render
Error: Cannot call impure function during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:7:31
5 | const now = performance.now();
6 | const rand = Math.random();
> 7 | return <Foo date={date} now={now} rand={rand} />;
| ^^^ Cannot access impure value during render
8 | }
9 |
`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:5:14
3 | function Component() {
4 | const date = Date.now();
> 5 | const now = performance.now();
| ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function.
| ^^^^^^^^^^^^^^^^^ Cannot call impure function
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
Error: Cannot access impure value during render
Error: Cannot call impure function during render
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:7:42
5 | const now = performance.now();
6 | const rand = Math.random();
> 7 | return <Foo date={date} now={now} rand={rand} />;
| ^^^^ Cannot access impure value during render
8 | }
9 |
`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:6:15
4 | const date = Date.now();
5 | const now = performance.now();
> 6 | const rand = Math.random();
| ^^^^^^^^^^^^^ `Math.random` is an impure function.
| ^^^^^^^^^^^^^ Cannot call impure function
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
9 |

View File

@@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"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)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":20,"index":339},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}}
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"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)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":269},"end":{"line":8,"column":50,"index":317},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":291},"end":{"line":8,"column":30,"index":297},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -1,45 +0,0 @@
## Input
```javascript
// @validateRefAccessDuringRender
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses a ref during render. The return type of foo() is unknown.
*/
function Component(props) {
const ref = useRef(null);
const x = foo(ref);
return x.current;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRender
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses a ref during render. The return type of foo() is unknown.
*/
function Component(props) {
const $ = _c(1);
const ref = useRef(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = foo(ref);
$[0] = t0;
} else {
t0 = $[0];
}
const x = t0;
return x.current;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,11 +0,0 @@
// @validateRefAccessDuringRender
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses a ref during render. The return type of foo() is unknown.
*/
function Component(props) {
const ref = useRef(null);
const x = foo(ref);
return x.current;
}

View File

@@ -0,0 +1,42 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees:true
import {useRef, useMemo} from 'react';
import {makeArray} from 'shared-runtime';
function useFoo() {
const r = useRef();
return useMemo(() => makeArray(r), []);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## 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.maybe-mutable-ref-not-preserved.ts:8:33
6 | function useFoo() {
7 | const r = useRef();
> 8 | return useMemo(() => makeArray(r), []);
| ^ Passing a ref to a function may read its value during render
9 | }
10 |
11 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -3,10 +3,6 @@
import {useRef, useMemo} from 'react';
import {makeArray} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function useFoo() {
const r = useRef();
return useMemo(() => makeArray(r), []);

View File

@@ -0,0 +1,37 @@
## Input
```javascript
// @flow @validatePreserveExistingMemoizationGuarantees
import {identity} from 'shared-runtime';
component Component(disableLocalRef, ref) {
const localRef = useFooRef();
const mergedRef = useMemo(() => {
return disableLocalRef ? ref : identity(ref, localRef);
}, [disableLocalRef, ref, localRef]);
return <div ref={mergedRef} />;
}
```
## 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).
5 | const localRef = useFooRef();
6 | const mergedRef = useMemo(() => {
> 7 | return disableLocalRef ? ref : identity(ref, localRef);
| ^^^ Passing a ref to a function may read its value during render
8 | }, [disableLocalRef, ref, localRef]);
9 | return <div ref={mergedRef} />;
10 | }
```

View File

@@ -1,59 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees:true
import {useRef, useMemo} from 'react';
import {makeArray} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function useFoo() {
const r = useRef();
return useMemo(() => makeArray(r), []);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees:true
import { useRef, useMemo } from "react";
import { makeArray } from "shared-runtime";
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function useFoo() {
const $ = _c(1);
const r = useRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = makeArray(r);
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
### Eval output
(kind: ok) [{}]

View File

@@ -1,52 +0,0 @@
## Input
```javascript
// @flow @validatePreserveExistingMemoizationGuarantees
import {identity} from 'shared-runtime';
component Component(disableLocalRef, ref) {
const localRef = useFooRef();
const mergedRef = useMemo(() => {
return disableLocalRef ? ref : identity(ref, localRef);
}, [disableLocalRef, ref, localRef]);
return <div ref={mergedRef} />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { identity } from "shared-runtime";
const Component = React.forwardRef(Component_withRef);
function Component_withRef(t0, ref) {
const $ = _c(6);
const { disableLocalRef } = t0;
const localRef = useFooRef();
let t1;
if ($[0] !== disableLocalRef || $[1] !== localRef || $[2] !== ref) {
t1 = disableLocalRef ? ref : identity(ref, localRef);
$[0] = disableLocalRef;
$[1] = localRef;
$[2] = ref;
$[3] = t1;
} else {
t1 = $[3];
}
const mergedRef = t1;
let t2;
if ($[4] !== mergedRef) {
t2 = <div ref={mergedRef} />;
$[4] = mergedRef;
$[5] = t2;
} else {
t2 = $[5];
}
return t2;
}
```

View File

@@ -1,46 +0,0 @@
## Input
```javascript
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {
f(r);
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
## Code
```javascript
import { useRef } from "react";
function C() {
const r = useRef(null);
if (r.current == null) {
f(r);
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
### Eval output
(kind: exception) f is not defined

View File

@@ -1,46 +0,0 @@
## Input
```javascript
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {
f(r.current);
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
## Code
```javascript
import { useRef } from "react";
function C() {
const r = useRef(null);
if (r.current == null) {
f(r.current);
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
### Eval output
(kind: exception) f is not defined

Some files were not shown because too many files have changed in this diff Show More