Compare commits

..

1 Commits

Author SHA1 Message Date
Joe Savona
8d6d9e4b0f [compiler] update fixtures 2025-06-18 15:09:57 -07:00
8 changed files with 90 additions and 364 deletions

View File

@@ -8,7 +8,6 @@
import {CompilerErrorDetailOptions} from '../CompilerError';
import {
FunctionExpression,
GeneratedSource,
Hole,
IdentifierId,
ObjectMethod,
@@ -19,7 +18,6 @@ import {
ValueReason,
} from '../HIR';
import {FunctionSignature} from '../HIR/ObjectShape';
import {printSourceLocation} from '../HIR/PrintHIR';
/**
* `AliasingEffect` describes a set of "effects" that an instruction/terminal has on one or
@@ -202,19 +200,10 @@ export function hashEffect(effect: AliasingEffect): string {
return [effect.kind, effect.value.identifier.id, effect.reason].join(':');
}
case 'Impure':
case 'Render': {
return [effect.kind, effect.place.identifier.id].join(':');
}
case 'Render':
case 'MutateFrozen':
case 'MutateGlobal': {
return [
effect.kind,
effect.place.identifier.id,
effect.error.severity,
effect.error.reason,
effect.error.description,
printSourceLocation(effect.error.loc ?? GeneratedSource),
].join(':');
return [effect.kind, effect.place.identifier.id].join(':');
}
case 'Mutate':
case 'MutateConditionally':

View File

@@ -38,7 +38,6 @@ import {
import {
eachInstructionValueLValue,
eachInstructionValueOperand,
eachTerminalOperand,
eachTerminalSuccessor,
} from '../HIR/visitors';
import {Ok, Result} from '../Utils/Result';
@@ -50,14 +49,12 @@ import {
} from './InferReferenceEffects';
import {
assertExhaustive,
getOrInsertDefault,
getOrInsertWith,
Set_isSuperset,
} from '../Utils/utils';
import {
printAliasingEffect,
printAliasingSignature,
printFunction,
printIdentifier,
printInstruction,
printInstructionValue,
@@ -197,15 +194,12 @@ export function inferMutationAliasingEffects(
let count = 0;
while (queuedStates.size !== 0) {
count++;
if (count > 100) {
if (count > 1000) {
console.log(
'oops infinite loop',
fn.id,
typeof fn.loc !== 'symbol' ? fn.loc?.filename : null,
);
if (DEBUG) {
console.log(printFunction(fn));
}
throw new Error('infinite loop');
}
for (const [blockId, block] of fn.body.blocks) {
@@ -217,11 +211,6 @@ export function inferMutationAliasingEffects(
statesByBlock.set(blockId, incomingState);
const state = incomingState.clone();
if (DEBUG) {
console.log('*************');
console.log(`bb${block.id}`);
console.log('*************');
}
inferBlock(context, state, block);
for (const nextBlockId of eachTerminalSuccessor(block.terminal)) {
@@ -232,19 +221,8 @@ export function inferMutationAliasingEffects(
return Ok(undefined);
}
function findHoistedContextDeclarations(
fn: HIRFunction,
): Map<DeclarationId, Place | null> {
const hoisted = new Map<DeclarationId, Place | null>();
function visit(place: Place): void {
if (
hoisted.has(place.identifier.declarationId) &&
hoisted.get(place.identifier.declarationId) == null
) {
// If this is the first load of the value, store the location
hoisted.set(place.identifier.declarationId, place);
}
}
function findHoistedContextDeclarations(fn: HIRFunction): Set<DeclarationId> {
const hoisted = new Set<DeclarationId>();
for (const block of fn.body.blocks.values()) {
for (const instr of block.instructions) {
if (instr.value.kind === 'DeclareContext') {
@@ -254,17 +232,10 @@ function findHoistedContextDeclarations(
kind == InstructionKind.HoistedFunction ||
kind == InstructionKind.HoistedLet
) {
hoisted.set(instr.value.lvalue.place.identifier.declarationId, null);
}
} else {
for (const operand of eachInstructionValueOperand(instr.value)) {
visit(operand);
hoisted.add(instr.value.lvalue.place.identifier.declarationId);
}
}
}
for (const operand of eachTerminalOperand(block.terminal)) {
visit(operand);
}
}
return hoisted;
}
@@ -274,40 +245,21 @@ class Context {
instructionSignatureCache: Map<Instruction, InstructionSignature> = new Map();
effectInstructionValueCache: Map<AliasingEffect, InstructionValue> =
new Map();
applySignatureCache: Map<
AliasingSignature,
Map<AliasingEffect, Array<AliasingEffect> | null>
> = new Map();
catchHandlers: Map<BlockId, Place> = new Map();
functionSignatureCache: Map<FunctionExpression, AliasingSignature> =
new Map();
isFuctionExpression: boolean;
fn: HIRFunction;
hoistedContextDeclarations: Map<DeclarationId, Place | null>;
hoistedContextDeclarations: Set<DeclarationId>;
constructor(
isFunctionExpression: boolean,
fn: HIRFunction,
hoistedContextDeclarations: Map<DeclarationId, Place | null>,
hoistedContextDeclarations: Set<DeclarationId>,
) {
this.isFuctionExpression = isFunctionExpression;
this.fn = fn;
this.hoistedContextDeclarations = hoistedContextDeclarations;
}
cacheApplySignature(
signature: AliasingSignature,
effect: Extract<AliasingEffect, {kind: 'Apply'}>,
f: () => Array<AliasingEffect> | null,
): Array<AliasingEffect> | null {
const inner = getOrInsertDefault(
this.applySignatureCache,
signature,
new Map(),
);
return getOrInsertWith(inner, effect, f);
}
internEffect(effect: AliasingEffect): AliasingEffect {
const hash = hashEffect(effect);
let interned = this.internedEffects.get(hash);
@@ -381,13 +333,11 @@ function inferBlock(
state.appendAlias(handlerParam, instr.lvalue);
const kind = state.kind(instr.lvalue).kind;
if (kind === ValueKind.Mutable || kind == ValueKind.Context) {
effects.push(
context.internEffect({
kind: 'Alias',
from: instr.lvalue,
into: handlerParam,
}),
);
effects.push({
kind: 'Alias',
from: instr.lvalue,
into: handlerParam,
});
}
}
}
@@ -396,11 +346,11 @@ function inferBlock(
} else if (terminal.kind === 'return') {
if (!context.isFuctionExpression) {
terminal.effects = [
context.internEffect({
{
kind: 'Freeze',
value: terminal.value,
reason: ValueReason.JsxCaptured,
}),
},
];
}
}
@@ -577,21 +527,20 @@ function applyEffect(
break;
}
case ValueKind.Frozen: {
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
aliased,
effects,
);
effects.push({
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
});
break;
}
default: {
effects.push(effect);
effects.push({
// OK: recording information flow
kind: 'CreateFrom', // prev Alias
from: effect.from,
into: effect.into,
});
}
}
break;
@@ -690,17 +639,11 @@ function applyEffect(
}
case ValueKind.Frozen: {
isMutableReferenceType = false;
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
aliased,
effects,
);
effects.push({
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
});
break;
}
default: {
@@ -722,17 +665,11 @@ function applyEffect(
const fromKind = fromValue.kind;
switch (fromKind) {
case ValueKind.Frozen: {
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
aliased,
effects,
);
effects.push({
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
});
let value = context.effectInstructionValueCache.get(effect);
if (value == null) {
value = {
@@ -790,33 +727,23 @@ function applyEffect(
* We're calling a locally declared function, we already know it's effects!
* We just have to substitute in the args for the params
*/
const functionExpr = functionValues[0];
let signature = context.functionSignatureCache.get(functionExpr);
if (signature == null) {
signature = buildSignatureFromFunctionExpression(
state.env,
functionExpr,
);
context.functionSignatureCache.set(functionExpr, signature);
}
const signature = buildSignatureFromFunctionExpression(
state.env,
functionValues[0],
);
if (DEBUG) {
console.log(
`constructed alias signature:\n${printAliasingSignature(signature)}`,
);
}
const signatureEffects = context.cacheApplySignature(
const signatureEffects = computeEffectsForSignature(
state.env,
signature,
effect,
() =>
computeEffectsForSignature(
state.env,
signature,
effect.into,
effect.receiver,
effect.args,
functionExpr.loweredFunc.func.context,
effect.loc,
),
effect.into,
effect.receiver,
effect.args,
functionValues[0].loweredFunc.func.context,
effect.loc,
);
if (signatureEffects != null) {
if (DEBUG) {
@@ -835,24 +762,18 @@ function applyEffect(
break;
}
}
let signatureEffects = null;
if (effect.signature?.aliasing != null) {
const signature = effect.signature.aliasing;
signatureEffects = context.cacheApplySignature(
effect.signature.aliasing,
effect,
() =>
computeEffectsForSignature(
const signatureEffects =
effect.signature?.aliasing != null
? computeEffectsForSignature(
state.env,
signature,
effect.signature.aliasing,
effect.into,
effect.receiver,
effect.args,
[],
effect.loc,
),
);
}
)
: null;
if (signatureEffects != null) {
if (DEBUG) {
console.log('apply aliasing signature effects');
@@ -980,89 +901,48 @@ function applyEffect(
console.log(prettyFormat(state.debugAbstractValue(value)));
}
let reason: string;
let description: string | null = null;
if (
mutationKind === 'mutate-frozen' &&
context.hoistedContextDeclarations.has(
effect.value.identifier.declarationId,
)
) {
const description =
reason = `This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time`;
if (
effect.value.identifier.name !== null &&
effect.value.identifier.name.kind === 'named'
? `Variable \`${effect.value.identifier.name.value}\` is accessed before it is declared`
: null;
const hoistedAccess = context.hoistedContextDeclarations.get(
effect.value.identifier.declarationId,
);
if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) {
applyEffect(
context,
state,
{
kind: 'MutateFrozen',
place: effect.value,
error: {
severity: ErrorSeverity.InvalidReact,
reason: `This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time`,
description,
loc: hoistedAccess.loc,
suggestions: null,
},
},
aliased,
effects,
);
) {
description = `Move the declaration of \`${effect.value.identifier.name.value}\` to before it is first referenced`;
}
applyEffect(
context,
state,
{
kind: 'MutateFrozen',
place: effect.value,
error: {
severity: ErrorSeverity.InvalidReact,
reason: `This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time`,
description,
loc: effect.value.loc,
suggestions: null,
},
},
aliased,
effects,
);
} else {
const reason = getWriteErrorReason({
reason = getWriteErrorReason({
kind: value.kind,
reason: value.reason,
context: new Set(),
});
const description =
if (
effect.value.identifier.name !== null &&
effect.value.identifier.name.kind === 'named'
? `Found mutation of \`${effect.value.identifier.name.value}\``
: null;
applyEffect(
context,
state,
{
kind:
value.kind === ValueKind.Frozen
? 'MutateFrozen'
: 'MutateGlobal',
place: effect.value,
error: {
severity: ErrorSeverity.InvalidReact,
reason,
description,
loc: effect.value.loc,
suggestions: null,
},
},
aliased,
effects,
);
) {
description = `Found mutation of \`${effect.value.identifier.name.value}\``;
}
}
effects.push({
kind:
value.kind === ValueKind.Frozen ? 'MutateFrozen' : 'MutateGlobal',
place: effect.value,
error: {
severity: ErrorSeverity.InvalidReact,
reason,
description,
loc: effect.value.loc,
suggestions: null,
},
});
}
break;
}

View File

@@ -38,15 +38,13 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
17 | * $2 = Function context=setState
18 | */
> 19 | useEffect(() => setState(2), []);
| ^^^^^^^^ InvalidReact: This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time. Variable `setState` is accessed before it is declared (19:19)
InvalidReact: This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. Variable `setState` is accessed before it is declared (21:21)
19 | useEffect(() => setState(2), []);
20 |
21 | const [state, setState] = useState(0);
> 21 | const [state, setState] = useState(0);
| ^^^^^^^^ InvalidReact: This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. Move the declaration of `setState` to before it is first referenced (21:21)
22 | return <Stringify state={state} />;
23 | }
24 |
```

View File

@@ -31,15 +31,13 @@ function Component({content, refetch}) {
## Error
```
9 | // TDZ violation!
10 | const onRefetch = useCallback(() => {
> 11 | refetch(data);
| ^^^^ InvalidReact: This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time. Variable `data` is accessed before it is declared (11:11)
InvalidReact: This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. Variable `data` is accessed before it is declared (19:19)
12 | }, [refetch]);
13 |
14 | // The context variable gets frozen here since it's passed to a hook
17 | // This has to error: onRefetch needs to memoize with `content` as a
18 | // dependency, but the dependency comes later
> 19 | const {data = null} = content;
| ^^^^^^^^^^^ InvalidReact: This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. Move the declaration of `data` to before it is first referenced (19:19)
20 |
21 | return <Foo data={data} onSubmit={onSubmit} />;
22 | }
```

View File

@@ -1,54 +0,0 @@
## Input
```javascript
// @flow @enableNewMutationAliasingModel
import fbt from 'fbt';
component Component() {
const sections = Object.keys(items);
for (let i = 0; i < sections.length; i += 3) {
chunks.push(
sections.slice(i, i + 3).map(section => {
return <Child />;
})
);
}
return <Child />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import fbt from "fbt";
function Component() {
const $ = _c(1);
const sections = Object.keys(items);
for (let i = 0; i < sections.length; i = i + 3, i) {
chunks.push(sections.slice(i, i + 3).map(_temp));
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <Child />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
function _temp(section) {
return <Child />;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,17 +0,0 @@
// @flow @enableNewMutationAliasingModel
import fbt from 'fbt';
component Component() {
const sections = Object.keys(items);
for (let i = 0; i < sections.length; i += 3) {
chunks.push(
sections.slice(i, i + 3).map(section => {
return <Child />;
})
);
}
return <Child />;
}

View File

@@ -1,53 +0,0 @@
## Input
```javascript
// @flow @enableNewMutationAliasingModel
import {identity, Stringify, useFragment} from 'shared-runtime';
component Example() {
const data = useFragment();
const {a, b} = identity(data);
const el = <Stringify tooltip={b} />;
identity(a.at(0));
return <Stringify icon={el} />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { identity, Stringify, useFragment } from "shared-runtime";
function Example() {
const $ = _c(2);
const data = useFragment();
let t0;
if ($[0] !== data) {
const { a, b } = identity(data);
const el = <Stringify tooltip={b} />;
identity(a.at(0));
t0 = <Stringify icon={el} />;
$[0] = data;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,15 +0,0 @@
// @flow @enableNewMutationAliasingModel
import {identity, Stringify, useFragment} from 'shared-runtime';
component Example() {
const data = useFragment();
const {a, b} = identity(data);
const el = <Stringify tooltip={b} />;
identity(a.at(0));
return <Stringify icon={el} />;
}