Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89535e5d18 | ||
|
|
997df89565 | ||
|
|
1022b06eb7 | ||
|
|
cca2c4beda | ||
|
|
13f6508c21 | ||
|
|
ae3c5a707f | ||
|
|
cb021861a5 | ||
|
|
7505808cd4 | ||
|
|
a974753ddf | ||
|
|
2c4a3b9587 | ||
|
|
8428a7ec5f |
@@ -1,7 +1,3 @@
|
||||
## Pending
|
||||
|
||||
* Improve impurity and ref validation, reducing false positives [#35298](https://github.com/facebook/react/pull/35298) by [@josephsavona](https://github.com/josephsavona)
|
||||
|
||||
## 19.1.0-rc.2 (May 14, 2025)
|
||||
|
||||
## babel-plugin-react-compiler
|
||||
|
||||
@@ -19,16 +19,10 @@ This document contains knowledge about the React Compiler gathered during develo
|
||||
# Run all tests
|
||||
yarn snap
|
||||
|
||||
# Run tests matching a pattern
|
||||
# Example: yarn snap -p 'error.*'
|
||||
# Run specific test by pattern
|
||||
yarn snap -p <pattern>
|
||||
|
||||
# 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)
|
||||
# Update fixture outputs
|
||||
yarn snap -u
|
||||
```
|
||||
|
||||
|
||||
@@ -132,6 +132,12 @@ 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;
|
||||
}
|
||||
|
||||
@@ -626,7 +626,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
// TODO: rest of Global objects
|
||||
];
|
||||
|
||||
const createRenderHookAliasing: (
|
||||
const RenderHookAliasing: (
|
||||
reason: ValueReason,
|
||||
) => AliasingSignatureConfig = reason => ({
|
||||
receiver: '@receiver',
|
||||
@@ -745,12 +745,12 @@ const useEffectEvent = addHook(
|
||||
returns: '@return',
|
||||
temporaries: [],
|
||||
effects: [
|
||||
{kind: 'Assign', from: '@value', into: '@return'},
|
||||
{
|
||||
kind: 'Freeze',
|
||||
value: '@value',
|
||||
reason: ValueReason.HookCaptured,
|
||||
},
|
||||
{kind: 'Assign', from: '@value', into: '@return'},
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -769,7 +769,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useContext',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.Context,
|
||||
aliasing: createRenderHookAliasing(ValueReason.Context),
|
||||
aliasing: RenderHookAliasing(ValueReason.Context),
|
||||
},
|
||||
BuiltInUseContextHookId,
|
||||
),
|
||||
@@ -784,7 +784,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useState',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.State,
|
||||
aliasing: createRenderHookAliasing(ValueReason.State),
|
||||
aliasing: RenderHookAliasing(ValueReason.State),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -797,7 +797,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useActionState',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.State,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -810,7 +810,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useReducer',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.ReducerState,
|
||||
aliasing: createRenderHookAliasing(ValueReason.ReducerState),
|
||||
aliasing: RenderHookAliasing(ValueReason.ReducerState),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -860,7 +860,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useMemo',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -877,7 +877,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useCallback',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -937,7 +937,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useTransition',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -950,7 +950,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useOptimistic',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.State,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -964,7 +964,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
returnType: {kind: 'Poly'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
aliasing: RenderHookAliasing(ValueReason.HookCaptured),
|
||||
},
|
||||
BuiltInUseOperatorId,
|
||||
),
|
||||
|
||||
@@ -983,6 +983,14 @@ 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})`;
|
||||
}
|
||||
case 'Freeze': {
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
isArrayType,
|
||||
isJsxOrJsxUnionType,
|
||||
isMapType,
|
||||
isMutableEffect,
|
||||
isPrimitiveType,
|
||||
isRefOrRefValue,
|
||||
isSetType,
|
||||
|
||||
@@ -581,7 +581,9 @@ export function inferMutationAliasingRanges(
|
||||
|
||||
if (
|
||||
errors.hasAnyErrors() &&
|
||||
(!isFunctionExpression || isJsxOrJsxUnionType(fn.returns.identifier.type))
|
||||
(fn.fnType === 'Component' ||
|
||||
isJsxOrJsxUnionType(fn.returns.identifier.type) ||
|
||||
!isFunctionExpression)
|
||||
) {
|
||||
return Err(errors);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerDiagnostic, CompilerError} from '..';
|
||||
import {CompilerDiagnostic, CompilerError, Effect} from '..';
|
||||
import {
|
||||
areEqualSourceLocations,
|
||||
HIRFunction,
|
||||
@@ -19,6 +19,7 @@ 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'}>;
|
||||
@@ -94,6 +95,9 @@ function processEffects(
|
||||
!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;
|
||||
}
|
||||
@@ -107,6 +111,9 @@ function processEffects(
|
||||
) {
|
||||
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;
|
||||
}
|
||||
@@ -122,15 +129,15 @@ function processEffects(
|
||||
if (
|
||||
functionEffect != null &&
|
||||
!impureFunctions.has(effect.into.identifier.id)
|
||||
/*
|
||||
* TODO: check if the function signature has changed (should be rare)
|
||||
* ||
|
||||
* !areEqualFunctionSignatures(
|
||||
* impureFunctions.get(effect.into.identifier.id)!.effects,
|
||||
* functionEffect.effects,
|
||||
* )
|
||||
*/
|
||||
// ||
|
||||
// !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;
|
||||
}
|
||||
@@ -139,6 +146,7 @@ function processEffects(
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -162,6 +170,7 @@ function processEffects(
|
||||
previousResult == null ||
|
||||
!areEqualFunctionSignatures(result.effects, previousResult.effects)
|
||||
) {
|
||||
// console.log(`Function $${effect.into.identifier.id}`);
|
||||
impureFunctions.set(effect.into.identifier.id, result);
|
||||
hasChanges = true;
|
||||
}
|
||||
@@ -224,6 +233,10 @@ function inferImpureValues(
|
||||
}
|
||||
|
||||
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
|
||||
@@ -255,30 +268,8 @@ function inferImpureValues(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: consider propagating impurity for assignments/mutations that
|
||||
* are controlled by an impure value.
|
||||
*
|
||||
* ```
|
||||
* const controlPlace = getBlockControl(block.id);
|
||||
* const controlImpureEffect =
|
||||
* controlPlace != null ? impure.get(controlPlace.identifier.id) : null;
|
||||
* ```
|
||||
*
|
||||
* Example
|
||||
*
|
||||
* This should error since we know the semantics of array.push, it's a definite
|
||||
* Mutate and definite Capture, not maybemutate+maybecapture:
|
||||
*
|
||||
* ```
|
||||
* let x = [];
|
||||
* if (Date.now() < START_DATE) {
|
||||
* x.push(1);
|
||||
* }
|
||||
* return <Foo x={x} />
|
||||
* ```
|
||||
*/
|
||||
for (const instr of block.instructions) {
|
||||
const _impure = new Set(impure.keys());
|
||||
hasChanges =
|
||||
processEffects(
|
||||
instr.id,
|
||||
|
||||
@@ -177,20 +177,16 @@ function validateFunction(
|
||||
if (block.terminal.kind === 'if') {
|
||||
const guard = guards.get(block.terminal.test.identifier.id);
|
||||
if (guard != null) {
|
||||
/*
|
||||
* For equality checks (==, ===), consequent is safe (condition true = ref is null)
|
||||
* For inequality checks (!=, !==), alternate is safe (condition false = ref is null)
|
||||
*/
|
||||
// For equality checks (==, ===), consequent is safe (condition true = ref is null)
|
||||
// For inequality checks (!=, !==), alternate is safe (condition false = ref is null)
|
||||
const safeBlock = guard.isEquality
|
||||
? block.terminal.consequent
|
||||
: block.terminal.alternate;
|
||||
const fallthrough = block.terminal.fallthrough;
|
||||
|
||||
/*
|
||||
* Propagate safety through control flow using a queue
|
||||
* Stop when we reach the fallthrough (end of the guarded region)
|
||||
*/
|
||||
const queue: Array<BlockId> = [safeBlock];
|
||||
// Propagate safety through control flow using a queue
|
||||
// Stop when we reach the fallthrough (end of the guarded region)
|
||||
const queue: BlockId[] = [safeBlock];
|
||||
const visited = new Set<BlockId>();
|
||||
while (queue.length > 0) {
|
||||
const blockId = queue.shift()!;
|
||||
@@ -392,10 +388,8 @@ function processInstruction(
|
||||
|
||||
case 'FunctionExpression':
|
||||
case 'ObjectMethod': {
|
||||
/*
|
||||
* Recursively validate function expressions
|
||||
* Pass isTopLevel=false since these are nested functions
|
||||
*/
|
||||
// Recursively validate function expressions
|
||||
// Pass isTopLevel=false since these are nested functions
|
||||
const mutation = validateFunction(
|
||||
value.loweredFunc.func,
|
||||
refs,
|
||||
|
||||
@@ -27,7 +27,7 @@ testRule(
|
||||
return value;
|
||||
}
|
||||
`,
|
||||
errors: [makeTestCaseError('Cannot access ref value during render')],
|
||||
errors: [makeTestCaseError('Cannot access refs during render')],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user