Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb174a5230 | ||
|
|
70ae844c65 | ||
|
|
2bf8fa8e91 |
@@ -51,20 +51,20 @@ Add error accumulation to the `Environment` class so that any pass can record er
|
||||
|
||||
Change `runWithEnvironment` to run all passes and check for errors at the end instead of letting exceptions propagate.
|
||||
|
||||
- [ ] **2.1 Change `runWithEnvironment` return type** (`src/Entrypoint/Pipeline.ts`)
|
||||
- [x] **2.1 Change `runWithEnvironment` return type** (`src/Entrypoint/Pipeline.ts`)
|
||||
- Change return type from `CodegenFunction` to `Result<CodegenFunction, CompilerError>`
|
||||
- At the end of the pipeline, check `env.hasErrors()`:
|
||||
- If no errors: return `Ok(ast)`
|
||||
- If errors: return `Err(env.aggregateErrors())`
|
||||
|
||||
- [ ] **2.2 Update `compileFn` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
|
||||
- [x] **2.2 Update `compileFn` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
|
||||
- Change `compileFn` return type from `CodegenFunction` to `Result<CodegenFunction, CompilerError>`
|
||||
- Propagate the Result from `runWithEnvironment`
|
||||
|
||||
- [ ] **2.3 Update `run` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
|
||||
- [x] **2.3 Update `run` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
|
||||
- Same change for the internal `run` function
|
||||
|
||||
- [ ] **2.4 Update callers in Program.ts** (`src/Entrypoint/Program.ts`)
|
||||
- [x] **2.4 Update callers in Program.ts** (`src/Entrypoint/Program.ts`)
|
||||
- In `tryCompileFunction`, change from try/catch around `compileFn` to handling the `Result`:
|
||||
- If `Ok(codegenFn)`: return the compiled function
|
||||
- If `Err(compilerError)`: return `{kind: 'error', error: compilerError}`
|
||||
@@ -127,64 +127,64 @@ All validation passes need to record errors on the environment instead of return
|
||||
|
||||
These passes already accumulate errors internally and return `Result<void, CompilerError>`. The change is: instead of returning the Result, record errors on `env` and return void. Remove the `.unwrap()` call in Pipeline.ts.
|
||||
|
||||
- [ ] **4.1 `validateHooksUsage`** (`src/Validation/ValidateHooksUsage.ts`)
|
||||
- [x] **4.1 `validateHooksUsage`** (`src/Validation/ValidateHooksUsage.ts`)
|
||||
- Change signature from `(fn: HIRFunction): Result<void, CompilerError>` to `(fn: HIRFunction): void`
|
||||
- Record errors on `fn.env` instead of returning `errors.asResult()`
|
||||
- Update Pipeline.ts call site (line 211): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.2 `validateNoCapitalizedCalls`** (`src/Validation/ValidateNoCapitalizedCalls.ts`)
|
||||
- [x] **4.2 `validateNoCapitalizedCalls`** (`src/Validation/ValidateNoCapitalizedCalls.ts`)
|
||||
- Change signature to return void
|
||||
- Fix the hybrid pattern: the direct `CallExpression` path currently throws via `CompilerError.throwInvalidReact()` — change to record on env
|
||||
- The `MethodCall` path already accumulates — change to record on env
|
||||
- Update Pipeline.ts call site (line 214): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.3 `validateUseMemo`** (`src/Validation/ValidateUseMemo.ts`)
|
||||
- [x] **4.3 `validateUseMemo`** (`src/Validation/ValidateUseMemo.ts`)
|
||||
- Change signature to return void
|
||||
- Record hard errors on env instead of returning `errors.asResult()`
|
||||
- The soft `voidMemoErrors` path already uses `env.logErrors()` — keep as-is or also record
|
||||
- Update Pipeline.ts call site (line 170): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.4 `dropManualMemoization`** (`src/Inference/DropManualMemoization.ts`)
|
||||
- [x] **4.4 `dropManualMemoization`** (`src/Inference/DropManualMemoization.ts`)
|
||||
- Change signature to return void
|
||||
- Record errors on env instead of returning `errors.asResult()`
|
||||
- Update Pipeline.ts call site (line 178): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.5 `validateNoRefAccessInRender`** (`src/Validation/ValidateNoRefAccessInRender.ts`)
|
||||
- [x] **4.5 `validateNoRefAccessInRender`** (`src/Validation/ValidateNoRefAccessInRender.ts`)
|
||||
- Change signature to return void
|
||||
- Record errors on env instead of returning Result
|
||||
- Update Pipeline.ts call site (line 275): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.6 `validateNoSetStateInRender`** (`src/Validation/ValidateNoSetStateInRender.ts`)
|
||||
- [x] **4.6 `validateNoSetStateInRender`** (`src/Validation/ValidateNoSetStateInRender.ts`)
|
||||
- Change signature to return void
|
||||
- Record errors on env
|
||||
- Update Pipeline.ts call site (line 279): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.7 `validateNoImpureFunctionsInRender`** (`src/Validation/ValidateNoImpureFunctionsInRender.ts`)
|
||||
- [x] **4.7 `validateNoImpureFunctionsInRender`** (`src/Validation/ValidateNoImpureFunctionsInRender.ts`)
|
||||
- Change signature to return void
|
||||
- Record errors on env
|
||||
- Update Pipeline.ts call site (line 300): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.8 `validateNoFreezingKnownMutableFunctions`** (`src/Validation/ValidateNoFreezingKnownMutableFunctions.ts`)
|
||||
- [x] **4.8 `validateNoFreezingKnownMutableFunctions`** (`src/Validation/ValidateNoFreezingKnownMutableFunctions.ts`)
|
||||
- Change signature to return void
|
||||
- Record errors on env
|
||||
- Update Pipeline.ts call site (line 303): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.9 `validateExhaustiveDependencies`** (`src/Validation/ValidateExhaustiveDependencies.ts`)
|
||||
- [x] **4.9 `validateExhaustiveDependencies`** (`src/Validation/ValidateExhaustiveDependencies.ts`)
|
||||
- Change signature to return void
|
||||
- Record errors on env
|
||||
- Update Pipeline.ts call site (line 315): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.10 `validateMemoizedEffectDependencies`** (`src/Validation/ValidateMemoizedEffectDependencies.ts`)
|
||||
- [x] **4.10 `validateMemoizedEffectDependencies`** (`src/Validation/ValidateMemoizedEffectDependencies.ts`)
|
||||
- Change signature to return void (note: operates on `ReactiveFunction`)
|
||||
- Record errors on the function's env
|
||||
- Update Pipeline.ts call site (line 565): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.11 `validatePreservedManualMemoization`** (`src/Validation/ValidatePreservedManualMemoization.ts`)
|
||||
- [x] **4.11 `validatePreservedManualMemoization`** (`src/Validation/ValidatePreservedManualMemoization.ts`)
|
||||
- Change signature to return void (note: operates on `ReactiveFunction`)
|
||||
- Record errors on the function's env
|
||||
- Update Pipeline.ts call site (line 572): remove `.unwrap()`
|
||||
|
||||
- [ ] **4.12 `validateSourceLocations`** (`src/Validation/ValidateSourceLocations.ts`)
|
||||
- [x] **4.12 `validateSourceLocations`** (`src/Validation/ValidateSourceLocations.ts`)
|
||||
- Change signature to return void
|
||||
- Record errors on env
|
||||
- Update Pipeline.ts call site (line 585): remove `.unwrap()`
|
||||
@@ -202,16 +202,16 @@ These already use a soft-logging pattern and don't block compilation. They can b
|
||||
|
||||
These throw `CompilerError` directly (not via Result). They need the most work.
|
||||
|
||||
- [ ] **4.17 `validateContextVariableLValues`** (`src/Validation/ValidateContextVariableLValues.ts`)
|
||||
- [x] **4.17 `validateContextVariableLValues`** (`src/Validation/ValidateContextVariableLValues.ts`)
|
||||
- Currently throws via `CompilerError.throwTodo()` and `CompilerError.invariant()`
|
||||
- Change to record Todo errors on env and continue
|
||||
- Keep invariant throws (those indicate internal bugs)
|
||||
|
||||
- [ ] **4.18 `validateLocalsNotReassignedAfterRender`** (`src/Validation/ValidateLocalsNotReassignedAfterRender.ts`)
|
||||
- [x] **4.18 `validateLocalsNotReassignedAfterRender`** (`src/Validation/ValidateLocalsNotReassignedAfterRender.ts`)
|
||||
- Currently constructs a `CompilerError` and `throw`s it directly
|
||||
- Change to record errors on env
|
||||
|
||||
- [ ] **4.19 `validateNoDerivedComputationsInEffects`** (`src/Validation/ValidateNoDerivedComputationsInEffects.ts`)
|
||||
- [x] **4.19 `validateNoDerivedComputationsInEffects`** (`src/Validation/ValidateNoDerivedComputationsInEffects.ts`)
|
||||
- Currently throws directly
|
||||
- Change to record errors on env
|
||||
|
||||
@@ -219,14 +219,14 @@ These throw `CompilerError` directly (not via Result). They need the most work.
|
||||
|
||||
The inference passes are the most critical to handle correctly because they produce side effects (populating effects on instructions, computing mutable ranges) that downstream passes depend on. They must continue producing valid (even if imprecise) output when errors are encountered.
|
||||
|
||||
- [ ] **5.1 `inferMutationAliasingEffects`** (`src/Inference/InferMutationAliasingEffects.ts`)
|
||||
- [x] **5.1 `inferMutationAliasingEffects`** (`src/Inference/InferMutationAliasingEffects.ts`)
|
||||
- Currently returns `Result<void, CompilerError>` — errors are about mutation of frozen/global values
|
||||
- Change to record errors on `fn.env` instead of accumulating internally
|
||||
- **Key recovery strategy**: When a mutation of a frozen value is detected, record the error but treat the operation as a non-mutating read. This way downstream passes see a consistent (if conservative) view
|
||||
- When a mutation of a global is detected, record the error but continue with the global unchanged
|
||||
- Update Pipeline.ts (lines 233-239): remove the conditional `.isErr()` / throw pattern
|
||||
|
||||
- [ ] **5.2 `inferMutationAliasingRanges`** (`src/Inference/InferMutationAliasingRanges.ts`)
|
||||
- [x] **5.2 `inferMutationAliasingRanges`** (`src/Inference/InferMutationAliasingRanges.ts`)
|
||||
- Currently returns `Result<Array<AliasingEffect>, CompilerError>`
|
||||
- This pass has a meaningful success value (the function's external aliasing effects)
|
||||
- Change to: always produce a best-effort effects array, record errors on env
|
||||
@@ -235,7 +235,7 @@ The inference passes are the most critical to handle correctly because they prod
|
||||
|
||||
### Phase 6: Update Codegen
|
||||
|
||||
- [ ] **6.1 `codegenFunction`** (`src/ReactiveScopes/CodegenReactiveFunction.ts`)
|
||||
- [x] **6.1 `codegenFunction`** (`src/ReactiveScopes/CodegenReactiveFunction.ts`)
|
||||
- Currently returns `Result<CodegenFunction, CompilerError>`
|
||||
- Change to: always produce a `CodegenFunction`, record errors on env
|
||||
- If codegen encounters an error (e.g., an instruction it can't generate code for), it should:
|
||||
@@ -248,31 +248,31 @@ The inference passes are the most critical to handle correctly because they prod
|
||||
|
||||
Walk through `runWithEnvironment` and wrap each pass call site. This is the integration work tying Phases 3-6 together.
|
||||
|
||||
- [ ] **7.1 Wrap `lower()` call** (line 163)
|
||||
- [x] **7.1 Wrap `lower()` call** (line 163)
|
||||
- Change from `lower(func, env).unwrap()` to `lower(func, env)` (direct return after Phase 3.1)
|
||||
|
||||
- [ ] **7.2 Wrap validation calls that use `.unwrap()`** (lines 169-303)
|
||||
- [x] **7.2 Wrap validation calls that use `.unwrap()`** (lines 169-303)
|
||||
- Remove `.unwrap()` from all validation calls after they're updated in Phase 4
|
||||
- For validations guarded by `env.enableValidations`, keep the guard but remove the `.unwrap()`
|
||||
|
||||
- [ ] **7.3 Wrap inference calls** (lines 233-267)
|
||||
- [x] **7.3 Wrap inference calls** (lines 233-267)
|
||||
- After Phase 5, `inferMutationAliasingEffects` and `inferMutationAliasingRanges` record errors directly
|
||||
- Remove the `mutabilityAliasingErrors` / `mutabilityAliasingRangeErrors` variables and their conditional throw logic
|
||||
|
||||
- [ ] **7.4 Wrap `env.logErrors()` calls** (lines 286-331)
|
||||
- [x] **7.4 Wrap `env.logErrors()` calls** (lines 286-331)
|
||||
- After Phase 4.13-4.16, these passes record on env directly
|
||||
- Remove the `env.logErrors()` wrapper calls
|
||||
|
||||
- [ ] **7.5 Wrap codegen** (lines 575-578)
|
||||
- [x] **7.5 Wrap codegen** (lines 575-578)
|
||||
- After Phase 6.1, `codegenFunction` returns directly
|
||||
- Remove the `.unwrap()`
|
||||
|
||||
- [ ] **7.6 Add final error check** (end of `runWithEnvironment`)
|
||||
- [x] **7.6 Add final error check** (end of `runWithEnvironment`)
|
||||
- After all passes complete, check `env.hasErrors()`
|
||||
- If no errors: return `Ok(ast)`
|
||||
- If errors: return `Err(env.aggregateErrors())`
|
||||
|
||||
- [ ] **7.7 Consider wrapping each pass in `env.tryRecord()`** as a safety net
|
||||
- [x] **7.7 Consider wrapping each pass in `env.tryRecord()`** as a safety net
|
||||
- Even after individual passes are updated, wrapping each pass call in `env.tryRecord()` provides defense-in-depth
|
||||
- If a pass unexpectedly throws a CompilerError (e.g., from a code path we missed), it gets caught and recorded rather than aborting the pipeline
|
||||
- Non-CompilerError exceptions and invariants still propagate immediately
|
||||
@@ -318,3 +318,10 @@ Walk through `runWithEnvironment` and wrap each pass call site. This is the inte
|
||||
- The `assertConsistentIdentifiers`, `assertTerminalSuccessorsExist`, `assertTerminalPredsExist`, `assertValidBlockNesting`, `assertValidMutableRanges`, `assertWellFormedBreakTargets`, `assertScopeInstructionsWithinScopes` assertion functions should continue to throw — they are invariant checks on internal data structure consistency
|
||||
- The `panicThreshold` mechanism in Program.ts should continue to work — it now operates on the aggregated error from the Result rather than a caught exception, but the behavior is the same
|
||||
|
||||
## Key Learnings
|
||||
|
||||
* **Phase 2+7 (Pipeline tryRecord wrapping) was sufficient for basic fault tolerance.** Wrapping all passes in `env.tryRecord()` immediately enabled the compiler to continue past errors that previously threw. This caused 52 test fixtures to produce additional errors that were previously masked by the first error bailing out. For example, `error.todo-reassign-const` previously reported only "Support destructuring of context variables" but now also reports the immutability violation.
|
||||
* **Lint-only passes (Pattern B: `env.logErrors()`) should not use `tryRecord()`/`recordError()`** because those errors are intentionally non-blocking. They are reported via the logger only and should not cause the pipeline to return `Err`. The `logErrors` pattern was kept for `validateNoDerivedComputationsInEffects_exp`, `validateNoSetStateInEffects`, `validateNoJSXInTryStatement`, and `validateStaticComponents`.
|
||||
* **Inference passes that return `Result` with validation errors** (`inferMutationAliasingEffects`, `inferMutationAliasingRanges`) were changed to record errors via `env.recordErrors()` instead of throwing, allowing subsequent passes to proceed.
|
||||
* **Value-producing passes** (`memoizeFbtAndMacroOperandsInSameScope`, `renameVariables`, `buildReactiveFunction`) need safe default values when wrapped in `tryRecord()` since the callback can't return values. We initialize with empty defaults (e.g., `new Set()`) before the `tryRecord()` call.
|
||||
|
||||
|
||||
@@ -9,8 +9,11 @@ import {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import prettyFormat from 'pretty-format';
|
||||
import {CompilerOutputMode, Logger, ProgramContext} from '.';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
ReactiveFunction,
|
||||
assertConsistentIdentifiers,
|
||||
assertTerminalPredsExist,
|
||||
@@ -118,7 +121,7 @@ function run(
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
): CodegenFunction {
|
||||
): Result<CodegenFunction, CompilerError> {
|
||||
const contextIdentifiers = findContextIdentifiers(func);
|
||||
const env = new Environment(
|
||||
func.scope,
|
||||
@@ -149,93 +152,109 @@ function runWithEnvironment(
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
env: Environment,
|
||||
): CodegenFunction {
|
||||
): Result<CodegenFunction, CompilerError> {
|
||||
const log = (value: CompilerPipelineValue): void => {
|
||||
env.logger?.debugLogIRs?.(value);
|
||||
};
|
||||
const hir = lower(func, env).unwrap();
|
||||
log({kind: 'hir', name: 'HIR', value: hir});
|
||||
|
||||
pruneMaybeThrows(hir);
|
||||
env.tryRecord(() => {
|
||||
pruneMaybeThrows(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
|
||||
validateContextVariableLValues(hir);
|
||||
validateUseMemo(hir).unwrap();
|
||||
validateUseMemo(hir);
|
||||
|
||||
if (env.enableDropManualMemoization) {
|
||||
dropManualMemoization(hir).unwrap();
|
||||
dropManualMemoization(hir);
|
||||
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
|
||||
}
|
||||
|
||||
inlineImmediatelyInvokedFunctionExpressions(hir);
|
||||
env.tryRecord(() => {
|
||||
inlineImmediatelyInvokedFunctionExpressions(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'InlineImmediatelyInvokedFunctionExpressions',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
mergeConsecutiveBlocks(hir);
|
||||
env.tryRecord(() => {
|
||||
mergeConsecutiveBlocks(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
|
||||
|
||||
assertConsistentIdentifiers(hir);
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
|
||||
enterSSA(hir);
|
||||
env.tryRecord(() => {
|
||||
enterSSA(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'SSA', value: hir});
|
||||
|
||||
eliminateRedundantPhi(hir);
|
||||
env.tryRecord(() => {
|
||||
eliminateRedundantPhi(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
|
||||
|
||||
assertConsistentIdentifiers(hir);
|
||||
|
||||
constantPropagation(hir);
|
||||
env.tryRecord(() => {
|
||||
constantPropagation(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'ConstantPropagation', value: hir});
|
||||
|
||||
inferTypes(hir);
|
||||
env.tryRecord(() => {
|
||||
inferTypes(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'InferTypes', value: hir});
|
||||
|
||||
if (env.enableValidations) {
|
||||
if (env.config.validateHooksUsage) {
|
||||
validateHooksUsage(hir).unwrap();
|
||||
validateHooksUsage(hir);
|
||||
}
|
||||
if (env.config.validateNoCapitalizedCalls) {
|
||||
validateNoCapitalizedCalls(hir).unwrap();
|
||||
validateNoCapitalizedCalls(hir);
|
||||
}
|
||||
}
|
||||
|
||||
optimizePropsMethodCalls(hir);
|
||||
env.tryRecord(() => {
|
||||
optimizePropsMethodCalls(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
|
||||
|
||||
analyseFunctions(hir);
|
||||
env.tryRecord(() => {
|
||||
analyseFunctions(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
|
||||
const mutabilityAliasingErrors = inferMutationAliasingEffects(hir);
|
||||
inferMutationAliasingEffects(hir);
|
||||
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
|
||||
if (env.enableValidations) {
|
||||
if (mutabilityAliasingErrors.isErr()) {
|
||||
throw mutabilityAliasingErrors.unwrapErr();
|
||||
}
|
||||
}
|
||||
|
||||
if (env.outputMode === 'ssr') {
|
||||
optimizeForSSR(hir);
|
||||
env.tryRecord(() => {
|
||||
optimizeForSSR(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'OptimizeForSSR', value: hir});
|
||||
}
|
||||
|
||||
// Note: Has to come after infer reference effects because "dead" code may still affect inference
|
||||
deadCodeElimination(hir);
|
||||
env.tryRecord(() => {
|
||||
deadCodeElimination(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
|
||||
pruneMaybeThrows(hir);
|
||||
env.tryRecord(() => {
|
||||
pruneMaybeThrows(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
|
||||
const mutabilityAliasingRangeErrors = inferMutationAliasingRanges(hir, {
|
||||
inferMutationAliasingRanges(hir, {
|
||||
isFunctionExpression: false,
|
||||
});
|
||||
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
|
||||
if (env.enableValidations) {
|
||||
if (mutabilityAliasingRangeErrors.isErr()) {
|
||||
throw mutabilityAliasingRangeErrors.unwrapErr();
|
||||
}
|
||||
validateLocalsNotReassignedAfterRender(hir);
|
||||
}
|
||||
|
||||
@@ -245,11 +264,11 @@ function runWithEnvironment(
|
||||
}
|
||||
|
||||
if (env.config.validateRefAccessDuringRender) {
|
||||
validateNoRefAccessInRender(hir).unwrap();
|
||||
validateNoRefAccessInRender(hir);
|
||||
}
|
||||
|
||||
if (env.config.validateNoSetStateInRender) {
|
||||
validateNoSetStateInRender(hir).unwrap();
|
||||
validateNoSetStateInRender(hir);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -270,13 +289,15 @@ function runWithEnvironment(
|
||||
}
|
||||
|
||||
if (env.config.validateNoImpureFunctionsInRender) {
|
||||
validateNoImpureFunctionsInRender(hir).unwrap();
|
||||
validateNoImpureFunctionsInRender(hir);
|
||||
}
|
||||
|
||||
validateNoFreezingKnownMutableFunctions(hir).unwrap();
|
||||
validateNoFreezingKnownMutableFunctions(hir);
|
||||
}
|
||||
|
||||
inferReactivePlaces(hir);
|
||||
env.tryRecord(() => {
|
||||
inferReactivePlaces(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
|
||||
|
||||
if (env.enableValidations) {
|
||||
@@ -285,11 +306,13 @@ function runWithEnvironment(
|
||||
env.config.validateExhaustiveEffectDependencies
|
||||
) {
|
||||
// NOTE: this relies on reactivity inference running first
|
||||
validateExhaustiveDependencies(hir).unwrap();
|
||||
validateExhaustiveDependencies(hir);
|
||||
}
|
||||
}
|
||||
|
||||
rewriteInstructionKindsBasedOnReassignment(hir);
|
||||
env.tryRecord(() => {
|
||||
rewriteInstructionKindsBasedOnReassignment(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'RewriteInstructionKindsBasedOnReassignment',
|
||||
@@ -310,11 +333,16 @@ function runWithEnvironment(
|
||||
* if inferred memoization is enabled. This makes all later passes which
|
||||
* transform reactive-scope labeled instructions no-ops.
|
||||
*/
|
||||
inferReactiveScopeVariables(hir);
|
||||
env.tryRecord(() => {
|
||||
inferReactiveScopeVariables(hir);
|
||||
});
|
||||
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
|
||||
}
|
||||
|
||||
const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
|
||||
let fbtOperands: Set<IdentifierId> = new Set();
|
||||
env.tryRecord(() => {
|
||||
fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'MemoizeFbtAndMacroOperandsInSameScope',
|
||||
@@ -322,11 +350,15 @@ function runWithEnvironment(
|
||||
});
|
||||
|
||||
if (env.config.enableJsxOutlining) {
|
||||
outlineJSX(hir);
|
||||
env.tryRecord(() => {
|
||||
outlineJSX(hir);
|
||||
});
|
||||
}
|
||||
|
||||
if (env.config.enableNameAnonymousFunctions) {
|
||||
nameAnonymousFunctions(hir);
|
||||
env.tryRecord(() => {
|
||||
nameAnonymousFunctions(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'NameAnonymousFunctions',
|
||||
@@ -335,39 +367,51 @@ function runWithEnvironment(
|
||||
}
|
||||
|
||||
if (env.config.enableFunctionOutlining) {
|
||||
outlineFunctions(hir, fbtOperands);
|
||||
env.tryRecord(() => {
|
||||
outlineFunctions(hir, fbtOperands);
|
||||
});
|
||||
log({kind: 'hir', name: 'OutlineFunctions', value: hir});
|
||||
}
|
||||
|
||||
alignMethodCallScopes(hir);
|
||||
env.tryRecord(() => {
|
||||
alignMethodCallScopes(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'AlignMethodCallScopes',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
alignObjectMethodScopes(hir);
|
||||
env.tryRecord(() => {
|
||||
alignObjectMethodScopes(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'AlignObjectMethodScopes',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
pruneUnusedLabelsHIR(hir);
|
||||
env.tryRecord(() => {
|
||||
pruneUnusedLabelsHIR(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'PruneUnusedLabelsHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
alignReactiveScopesToBlockScopesHIR(hir);
|
||||
env.tryRecord(() => {
|
||||
alignReactiveScopesToBlockScopesHIR(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'AlignReactiveScopesToBlockScopesHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
mergeOverlappingReactiveScopesHIR(hir);
|
||||
env.tryRecord(() => {
|
||||
mergeOverlappingReactiveScopesHIR(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'MergeOverlappingReactiveScopesHIR',
|
||||
@@ -375,7 +419,9 @@ function runWithEnvironment(
|
||||
});
|
||||
assertValidBlockNesting(hir);
|
||||
|
||||
buildReactiveScopeTerminalsHIR(hir);
|
||||
env.tryRecord(() => {
|
||||
buildReactiveScopeTerminalsHIR(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'BuildReactiveScopeTerminalsHIR',
|
||||
@@ -384,14 +430,18 @@ function runWithEnvironment(
|
||||
|
||||
assertValidBlockNesting(hir);
|
||||
|
||||
flattenReactiveLoopsHIR(hir);
|
||||
env.tryRecord(() => {
|
||||
flattenReactiveLoopsHIR(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'FlattenReactiveLoopsHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
flattenScopesWithHooksOrUseHIR(hir);
|
||||
env.tryRecord(() => {
|
||||
flattenScopesWithHooksOrUseHIR(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'FlattenScopesWithHooksOrUseHIR',
|
||||
@@ -399,14 +449,19 @@ function runWithEnvironment(
|
||||
});
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
assertTerminalPredsExist(hir);
|
||||
propagateScopeDependenciesHIR(hir);
|
||||
env.tryRecord(() => {
|
||||
propagateScopeDependenciesHIR(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'PropagateScopeDependenciesHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
const reactiveFunction = buildReactiveFunction(hir);
|
||||
let reactiveFunction!: ReactiveFunction;
|
||||
env.tryRecord(() => {
|
||||
reactiveFunction = buildReactiveFunction(hir);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'BuildReactiveFunction',
|
||||
@@ -415,7 +470,9 @@ function runWithEnvironment(
|
||||
|
||||
assertWellFormedBreakTargets(reactiveFunction);
|
||||
|
||||
pruneUnusedLabels(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
pruneUnusedLabels(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedLabels',
|
||||
@@ -423,108 +480,134 @@ function runWithEnvironment(
|
||||
});
|
||||
assertScopeInstructionsWithinScopes(reactiveFunction);
|
||||
|
||||
pruneNonEscapingScopes(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
pruneNonEscapingScopes(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneNonEscapingScopes',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneNonReactiveDependencies(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
pruneNonReactiveDependencies(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneNonReactiveDependencies',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneUnusedScopes(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
pruneUnusedScopes(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedScopes',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'MergeReactiveScopesThatInvalidateTogether',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneAlwaysInvalidatingScopes(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
pruneAlwaysInvalidatingScopes(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneAlwaysInvalidatingScopes',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
propagateEarlyReturns(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
propagateEarlyReturns(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PropagateEarlyReturns',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneUnusedLValues(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
pruneUnusedLValues(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedLValues',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
promoteUsedTemporaries(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
promoteUsedTemporaries(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PromoteUsedTemporaries',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
extractScopeDeclarationsFromDestructuring(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
extractScopeDeclarationsFromDestructuring(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'ExtractScopeDeclarationsFromDestructuring',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
stabilizeBlockIds(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
stabilizeBlockIds(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'StabilizeBlockIds',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
const uniqueIdentifiers = renameVariables(reactiveFunction);
|
||||
let uniqueIdentifiers: Set<string> = new Set();
|
||||
env.tryRecord(() => {
|
||||
uniqueIdentifiers = renameVariables(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'RenameVariables',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneHoistedContexts(reactiveFunction);
|
||||
env.tryRecord(() => {
|
||||
pruneHoistedContexts(reactiveFunction);
|
||||
});
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneHoistedContexts',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
|
||||
if (
|
||||
env.config.enablePreserveExistingMemoizationGuarantees ||
|
||||
env.config.validatePreserveExistingMemoizationGuarantees
|
||||
) {
|
||||
validatePreservedManualMemoization(reactiveFunction).unwrap();
|
||||
validatePreservedManualMemoization(reactiveFunction);
|
||||
}
|
||||
|
||||
const ast = codegenFunction(reactiveFunction, {
|
||||
uniqueIdentifiers,
|
||||
fbtOperands,
|
||||
}).unwrap();
|
||||
});
|
||||
log({kind: 'ast', name: 'Codegen', value: ast});
|
||||
for (const outlined of ast.outlined) {
|
||||
log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
|
||||
}
|
||||
|
||||
if (env.config.validateSourceLocations) {
|
||||
validateSourceLocations(func, ast).unwrap();
|
||||
validateSourceLocations(func, ast, env);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -536,7 +619,10 @@ function runWithEnvironment(
|
||||
throw new Error('unexpected error');
|
||||
}
|
||||
|
||||
return ast;
|
||||
if (env.hasErrors()) {
|
||||
return Err(env.aggregateErrors());
|
||||
}
|
||||
return Ok(ast);
|
||||
}
|
||||
|
||||
export function compileFn(
|
||||
@@ -550,7 +636,7 @@ export function compileFn(
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
): CodegenFunction {
|
||||
): Result<CodegenFunction, CompilerError> {
|
||||
return run(
|
||||
func,
|
||||
config,
|
||||
|
||||
@@ -704,24 +704,27 @@ function tryCompileFunction(
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
kind: 'compile',
|
||||
compiledFn: compileFn(
|
||||
fn,
|
||||
programContext.opts.environment,
|
||||
fnType,
|
||||
outputMode,
|
||||
programContext,
|
||||
programContext.opts.logger,
|
||||
programContext.filename,
|
||||
programContext.code,
|
||||
),
|
||||
};
|
||||
const result = compileFn(
|
||||
fn,
|
||||
programContext.opts.environment,
|
||||
fnType,
|
||||
outputMode,
|
||||
programContext,
|
||||
programContext.opts.logger,
|
||||
programContext.filename,
|
||||
programContext.code,
|
||||
);
|
||||
if (result.isOk()) {
|
||||
return {kind: 'compile', compiledFn: result.unwrap()};
|
||||
} else {
|
||||
return {kind: 'error', error: result.unwrapErr()};
|
||||
}
|
||||
} catch (err) {
|
||||
return {kind: 'error', error: err};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies React Compiler generated functions to the babel AST by replacing
|
||||
* existing functions in place or inserting new declarations.
|
||||
|
||||
@@ -54,7 +54,7 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
|
||||
deadCodeElimination(fn);
|
||||
const functionEffects = inferMutationAliasingRanges(fn, {
|
||||
isFunctionExpression: true,
|
||||
}).unwrap();
|
||||
});
|
||||
rewriteInstructionKindsBasedOnReassignment(fn);
|
||||
inferReactiveScopeVariables(fn);
|
||||
fn.aliasingEffects = functionEffects;
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
makeInstructionId,
|
||||
} from '../HIR';
|
||||
import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
type ManualMemoCallee = {
|
||||
kind: 'useMemo' | 'useCallback';
|
||||
@@ -389,9 +388,7 @@ function extractManualMemoizationArgs(
|
||||
* This pass also validates that useMemo callbacks return a value (not void), ensuring that useMemo
|
||||
* is only used for memoizing values and not for running arbitrary side effects.
|
||||
*/
|
||||
export function dropManualMemoization(
|
||||
func: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function dropManualMemoization(func: HIRFunction): void {
|
||||
const errors = new CompilerError();
|
||||
const isValidationEnabled =
|
||||
func.env.config.validatePreserveExistingMemoizationGuarantees ||
|
||||
@@ -553,7 +550,9 @@ export function dropManualMemoization(
|
||||
}
|
||||
}
|
||||
|
||||
return errors.asResult();
|
||||
if (errors.hasAnyErrors()) {
|
||||
func.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
function findOptionalPlaces(fn: HIRFunction): Set<IdentifierId> {
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
eachTerminalOperand,
|
||||
eachTerminalSuccessor,
|
||||
} from '../HIR/visitors';
|
||||
import {Ok, Result} from '../Utils/Result';
|
||||
|
||||
import {
|
||||
assertExhaustive,
|
||||
getOrInsertDefault,
|
||||
@@ -100,7 +100,7 @@ export function inferMutationAliasingEffects(
|
||||
{isFunctionExpression}: {isFunctionExpression: boolean} = {
|
||||
isFunctionExpression: false,
|
||||
},
|
||||
): Result<void, CompilerError> {
|
||||
): void {
|
||||
const initialState = InferenceState.empty(fn.env, isFunctionExpression);
|
||||
|
||||
// Map of blocks to the last (merged) incoming state that was processed
|
||||
@@ -220,7 +220,7 @@ export function inferMutationAliasingEffects(
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
function findHoistedContextDeclarations(
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
|
||||
import {AliasingEffect, MutationReason} from './AliasingEffects';
|
||||
|
||||
/**
|
||||
@@ -74,7 +74,7 @@ import {AliasingEffect, MutationReason} from './AliasingEffects';
|
||||
export function inferMutationAliasingRanges(
|
||||
fn: HIRFunction,
|
||||
{isFunctionExpression}: {isFunctionExpression: boolean},
|
||||
): Result<Array<AliasingEffect>, CompilerError> {
|
||||
): Array<AliasingEffect> {
|
||||
// The set of externally-visible effects
|
||||
const functionEffects: Array<AliasingEffect> = [];
|
||||
|
||||
@@ -547,10 +547,14 @@ export function inferMutationAliasingRanges(
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.hasAnyErrors() && !isFunctionExpression) {
|
||||
return Err(errors);
|
||||
if (
|
||||
errors.hasAnyErrors() &&
|
||||
!isFunctionExpression &&
|
||||
fn.env.enableValidations
|
||||
) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
return Ok(functionEffects);
|
||||
return functionEffects;
|
||||
}
|
||||
|
||||
function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
|
||||
|
||||
@@ -46,7 +46,7 @@ import {
|
||||
} from '../HIR/HIR';
|
||||
import {printIdentifier, printInstruction, printPlace} from '../HIR/PrintHIR';
|
||||
import {eachPatternOperand} from '../HIR/visitors';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
|
||||
import {GuardKind} from '../Utils/RuntimeDiagnosticConstants';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {buildReactiveFunction} from './BuildReactiveFunction';
|
||||
@@ -111,7 +111,7 @@ export function codegenFunction(
|
||||
uniqueIdentifiers: Set<string>;
|
||||
fbtOperands: Set<IdentifierId>;
|
||||
},
|
||||
): Result<CodegenFunction, CompilerError> {
|
||||
): CodegenFunction {
|
||||
const cx = new Context(
|
||||
fn.env,
|
||||
fn.id ?? '[[ anonymous ]]',
|
||||
@@ -141,11 +141,7 @@ export function codegenFunction(
|
||||
};
|
||||
}
|
||||
|
||||
const compileResult = codegenReactiveFunction(cx, fn);
|
||||
if (compileResult.isErr()) {
|
||||
return compileResult;
|
||||
}
|
||||
const compiled = compileResult.unwrap();
|
||||
const compiled = codegenReactiveFunction(cx, fn);
|
||||
|
||||
const hookGuard = fn.env.config.enableEmitHookGuards;
|
||||
if (hookGuard != null && fn.env.outputMode === 'client') {
|
||||
@@ -273,7 +269,7 @@ export function codegenFunction(
|
||||
emitInstrumentForget.globalGating,
|
||||
);
|
||||
if (assertResult.isErr()) {
|
||||
return assertResult;
|
||||
fn.env.recordErrors(assertResult.unwrapErr());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,20 +319,17 @@ export function codegenFunction(
|
||||
),
|
||||
reactiveFunction,
|
||||
);
|
||||
if (codegen.isErr()) {
|
||||
return codegen;
|
||||
}
|
||||
outlined.push({fn: codegen.unwrap(), type});
|
||||
outlined.push({fn: codegen, type});
|
||||
}
|
||||
compiled.outlined = outlined;
|
||||
|
||||
return compileResult;
|
||||
return compiled;
|
||||
}
|
||||
|
||||
function codegenReactiveFunction(
|
||||
cx: Context,
|
||||
fn: ReactiveFunction,
|
||||
): Result<CodegenFunction, CompilerError> {
|
||||
): CodegenFunction {
|
||||
for (const param of fn.params) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
cx.temp.set(place.identifier.declarationId, null);
|
||||
@@ -355,13 +348,13 @@ function codegenReactiveFunction(
|
||||
}
|
||||
|
||||
if (cx.errors.hasAnyErrors()) {
|
||||
return Err(cx.errors);
|
||||
fn.env.recordErrors(cx.errors);
|
||||
}
|
||||
|
||||
const countMemoBlockVisitor = new CountMemoBlockVisitor(fn.env);
|
||||
visitReactiveFunction(fn, countMemoBlockVisitor, undefined);
|
||||
|
||||
return Ok({
|
||||
return {
|
||||
type: 'CodegenFunction',
|
||||
loc: fn.loc,
|
||||
id: fn.id !== null ? t.identifier(fn.id) : null,
|
||||
@@ -376,7 +369,7 @@ function codegenReactiveFunction(
|
||||
prunedMemoBlocks: countMemoBlockVisitor.prunedMemoBlocks,
|
||||
prunedMemoValues: countMemoBlockVisitor.prunedMemoValues,
|
||||
outlined: [],
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
class CountMemoBlockVisitor extends ReactiveFunctionVisitor<void> {
|
||||
@@ -1665,7 +1658,7 @@ function codegenInstructionValue(
|
||||
cx.temp,
|
||||
),
|
||||
reactiveFunction,
|
||||
).unwrap();
|
||||
);
|
||||
|
||||
/*
|
||||
* ObjectMethod builder must be backwards compatible with older versions of babel.
|
||||
@@ -1864,7 +1857,7 @@ function codegenInstructionValue(
|
||||
cx.temp,
|
||||
),
|
||||
reactiveFunction,
|
||||
).unwrap();
|
||||
);
|
||||
|
||||
if (instrValue.type === 'ArrowFunctionExpression') {
|
||||
let body: t.BlockStatement | t.Expression = fn.body;
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '..';
|
||||
import {CompilerDiagnostic, CompilerError} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {Environment} from '../HIR/Environment';
|
||||
import {HIRFunction, IdentifierId, Place} from '../HIR';
|
||||
import {printPlace} from '../HIR/PrintHIR';
|
||||
import {eachInstructionValueLValue, eachPatternOperand} from '../HIR/visitors';
|
||||
@@ -17,12 +19,13 @@ import {eachInstructionValueLValue, eachPatternOperand} from '../HIR/visitors';
|
||||
*/
|
||||
export function validateContextVariableLValues(fn: HIRFunction): void {
|
||||
const identifierKinds: IdentifierKinds = new Map();
|
||||
validateContextVariableLValuesImpl(fn, identifierKinds);
|
||||
validateContextVariableLValuesImpl(fn, identifierKinds, fn.env);
|
||||
}
|
||||
|
||||
function validateContextVariableLValuesImpl(
|
||||
fn: HIRFunction,
|
||||
identifierKinds: IdentifierKinds,
|
||||
env: Environment,
|
||||
): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
@@ -30,30 +33,30 @@ function validateContextVariableLValuesImpl(
|
||||
switch (value.kind) {
|
||||
case 'DeclareContext':
|
||||
case 'StoreContext': {
|
||||
visit(identifierKinds, value.lvalue.place, 'context');
|
||||
visit(identifierKinds, value.lvalue.place, 'context', env);
|
||||
break;
|
||||
}
|
||||
case 'LoadContext': {
|
||||
visit(identifierKinds, value.place, 'context');
|
||||
visit(identifierKinds, value.place, 'context', env);
|
||||
break;
|
||||
}
|
||||
case 'StoreLocal':
|
||||
case 'DeclareLocal': {
|
||||
visit(identifierKinds, value.lvalue.place, 'local');
|
||||
visit(identifierKinds, value.lvalue.place, 'local', env);
|
||||
break;
|
||||
}
|
||||
case 'LoadLocal': {
|
||||
visit(identifierKinds, value.place, 'local');
|
||||
visit(identifierKinds, value.place, 'local', env);
|
||||
break;
|
||||
}
|
||||
case 'PostfixUpdate':
|
||||
case 'PrefixUpdate': {
|
||||
visit(identifierKinds, value.lvalue, 'local');
|
||||
visit(identifierKinds, value.lvalue, 'local', env);
|
||||
break;
|
||||
}
|
||||
case 'Destructure': {
|
||||
for (const lvalue of eachPatternOperand(value.lvalue.pattern)) {
|
||||
visit(identifierKinds, lvalue, 'destructure');
|
||||
visit(identifierKinds, lvalue, 'destructure', env);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -62,18 +65,24 @@ function validateContextVariableLValuesImpl(
|
||||
validateContextVariableLValuesImpl(
|
||||
value.loweredFunc.func,
|
||||
identifierKinds,
|
||||
env,
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
for (const _ of eachInstructionValueLValue(value)) {
|
||||
CompilerError.throwTodo({
|
||||
reason:
|
||||
'ValidateContextVariableLValues: unhandled instruction variant',
|
||||
loc: value.loc,
|
||||
description: `Handle '${value.kind} lvalues`,
|
||||
suggestions: null,
|
||||
});
|
||||
fn.env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Todo,
|
||||
reason:
|
||||
'ValidateContextVariableLValues: unhandled instruction variant',
|
||||
description: `Handle '${value.kind} lvalues`,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: value.loc,
|
||||
message: null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +99,7 @@ function visit(
|
||||
identifiers: IdentifierKinds,
|
||||
place: Place,
|
||||
kind: 'local' | 'context' | 'destructure',
|
||||
env: Environment,
|
||||
): void {
|
||||
const prev = identifiers.get(place.identifier.id);
|
||||
if (prev !== undefined) {
|
||||
@@ -97,12 +107,18 @@ function visit(
|
||||
const isContext = kind === 'context';
|
||||
if (wasContext !== isContext) {
|
||||
if (prev.kind === 'destructure' || kind === 'destructure') {
|
||||
CompilerError.throwTodo({
|
||||
reason: `Support destructuring of context variables`,
|
||||
loc: kind === 'destructure' ? place.loc : prev.place.loc,
|
||||
description: null,
|
||||
suggestions: null,
|
||||
});
|
||||
env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Todo,
|
||||
reason: `Support destructuring of context variables`,
|
||||
description: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: kind === 'destructure' ? place.loc : prev.place.loc,
|
||||
message: null,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
CompilerError.invariant(false, {
|
||||
|
||||
@@ -44,7 +44,6 @@ import {
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {Result} from '../Utils/Result';
|
||||
import {retainWhere} from '../Utils/utils';
|
||||
|
||||
const DEBUG = false;
|
||||
@@ -88,9 +87,7 @@ const DEBUG = false;
|
||||
* When we go to compute the dependencies, we then think that the user's manual dep
|
||||
* logic is part of what the memo computation logic.
|
||||
*/
|
||||
export function validateExhaustiveDependencies(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateExhaustiveDependencies(fn: HIRFunction): void {
|
||||
const env = fn.env;
|
||||
const reactive = collectReactiveIdentifiersHIR(fn);
|
||||
|
||||
@@ -217,7 +214,9 @@ export function validateExhaustiveDependencies(
|
||||
},
|
||||
false, // isFunctionExpression
|
||||
);
|
||||
return error.asResult();
|
||||
if (error.hasAnyErrors()) {
|
||||
fn.env.recordErrors(error);
|
||||
}
|
||||
}
|
||||
|
||||
function validateDependencies(
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
/**
|
||||
* Represents the possible kinds of value which may be stored at a given Place during
|
||||
@@ -88,9 +87,7 @@ function joinKinds(a: Kind, b: Kind): Kind {
|
||||
* may not appear as the callee of a conditional call.
|
||||
* See the note for Kind.PotentialHook for sources of potential hooks
|
||||
*/
|
||||
export function validateHooksUsage(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateHooksUsage(fn: HIRFunction): void {
|
||||
const unconditionalBlocks = computeUnconditionalBlocks(fn);
|
||||
|
||||
const errors = new CompilerError();
|
||||
@@ -426,7 +423,9 @@ export function validateHooksUsage(
|
||||
for (const [, error] of errorsByPlace) {
|
||||
errors.pushErrorDetail(error);
|
||||
}
|
||||
return errors.asResult();
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import {CompilerDiagnostic, CompilerError, Effect} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {Environment} from '../HIR/Environment';
|
||||
import {HIRFunction, IdentifierId, Place} from '../HIR';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
@@ -27,15 +28,15 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void {
|
||||
contextVariables,
|
||||
false,
|
||||
false,
|
||||
fn.env,
|
||||
);
|
||||
if (reassignment !== null) {
|
||||
const errors = new CompilerError();
|
||||
const variable =
|
||||
reassignment.identifier.name != null &&
|
||||
reassignment.identifier.name.kind === 'named'
|
||||
? `\`${reassignment.identifier.name.value}\``
|
||||
: 'variable';
|
||||
errors.pushDiagnostic(
|
||||
fn.env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Immutability,
|
||||
reason: 'Cannot reassign variable after render completes',
|
||||
@@ -46,7 +47,6 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void {
|
||||
message: `Cannot reassign ${variable} after render completes`,
|
||||
}),
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ function getContextReassignment(
|
||||
contextVariables: Set<IdentifierId>,
|
||||
isFunctionExpression: boolean,
|
||||
isAsync: boolean,
|
||||
env: Environment,
|
||||
): Place | null {
|
||||
const reassigningFunctions = new Map<IdentifierId, Place>();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
@@ -68,6 +69,7 @@ function getContextReassignment(
|
||||
contextVariables,
|
||||
true,
|
||||
isAsync || value.loweredFunc.func.async,
|
||||
env,
|
||||
);
|
||||
if (reassignment === null) {
|
||||
// If the function itself doesn't reassign, does one of its dependencies?
|
||||
@@ -84,13 +86,12 @@ function getContextReassignment(
|
||||
// if the function or its depends reassign, propagate that fact on the lvalue
|
||||
if (reassignment !== null) {
|
||||
if (isAsync || value.loweredFunc.func.async) {
|
||||
const errors = new CompilerError();
|
||||
const variable =
|
||||
reassignment.identifier.name !== null &&
|
||||
reassignment.identifier.name.kind === 'named'
|
||||
? `\`${reassignment.identifier.name.value}\``
|
||||
: 'variable';
|
||||
errors.pushDiagnostic(
|
||||
env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Immutability,
|
||||
reason: 'Cannot reassign variable in async function',
|
||||
@@ -102,7 +103,7 @@ function getContextReassignment(
|
||||
message: `Cannot reassign ${variable}`,
|
||||
}),
|
||||
);
|
||||
throw errors;
|
||||
return null;
|
||||
}
|
||||
reassigningFunctions.set(lvalue.identifier.id, reassignment);
|
||||
}
|
||||
|
||||
@@ -5,15 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError, EnvironmentConfig} from '..';
|
||||
import {CompilerError, CompilerErrorDetail, EnvironmentConfig} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {HIRFunction, IdentifierId} from '../HIR';
|
||||
import {DEFAULT_GLOBALS} from '../HIR/Globals';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
export function validateNoCapitalizedCalls(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateNoCapitalizedCalls(fn: HIRFunction): void {
|
||||
const envConfig: EnvironmentConfig = fn.env.config;
|
||||
const ALLOW_LIST = new Set([
|
||||
...DEFAULT_GLOBALS.keys(),
|
||||
@@ -48,13 +45,16 @@ export function validateNoCapitalizedCalls(
|
||||
const calleeIdentifier = value.callee.identifier.id;
|
||||
const calleeName = capitalLoadGlobals.get(calleeIdentifier);
|
||||
if (calleeName != null) {
|
||||
CompilerError.throwInvalidReact({
|
||||
category: ErrorCategory.CapitalizedCalls,
|
||||
reason,
|
||||
description: `${calleeName} may be a component`,
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
fn.env.recordError(
|
||||
new CompilerErrorDetail({
|
||||
category: ErrorCategory.CapitalizedCalls,
|
||||
reason,
|
||||
description: `${calleeName} may be a component`,
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -85,5 +85,7 @@ export function validateNoCapitalizedCalls(
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.asResult();
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.hasAnyErrors()) {
|
||||
throw errors;
|
||||
for (const detail of errors.details) {
|
||||
fn.env.recordError(detail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {AliasingEffect} from '../Inference/AliasingEffects';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
/**
|
||||
* Validates that functions with known mutations (ie due to types) cannot be passed
|
||||
@@ -43,9 +42,7 @@ import {Result} from '../Utils/Result';
|
||||
* This pass detects functions with *known* mutations (Store or Mutate, not ConditionallyMutate)
|
||||
* that are passed where a frozen value is expected and rejects them.
|
||||
*/
|
||||
export function validateNoFreezingKnownMutableFunctions(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateNoFreezingKnownMutableFunctions(fn: HIRFunction): void {
|
||||
const errors = new CompilerError();
|
||||
const contextMutationEffects: Map<
|
||||
IdentifierId,
|
||||
@@ -162,5 +159,7 @@ export function validateNoFreezingKnownMutableFunctions(
|
||||
visitOperand(operand);
|
||||
}
|
||||
}
|
||||
return errors.asResult();
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ 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
|
||||
@@ -20,9 +19,7 @@ import {Result} from '../Utils/Result';
|
||||
* 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> {
|
||||
export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
|
||||
const errors = new CompilerError();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
@@ -55,5 +52,7 @@ export function validateNoImpureFunctionsInRender(
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.asResult();
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
eachPatternOperand,
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {retainWhere} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
@@ -120,12 +119,14 @@ class Env {
|
||||
}
|
||||
}
|
||||
|
||||
export function validateNoRefAccessInRender(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateNoRefAccessInRender(fn: HIRFunction): void {
|
||||
const env = new Env();
|
||||
collectTemporariesSidemap(fn, env);
|
||||
return validateNoRefAccessInRenderImpl(fn, env).map(_ => undefined);
|
||||
const errors = new CompilerError();
|
||||
validateNoRefAccessInRenderImpl(fn, env, errors);
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
function collectTemporariesSidemap(fn: HIRFunction, env: Env): void {
|
||||
@@ -305,7 +306,8 @@ function joinRefAccessTypes(...types: Array<RefAccessType>): RefAccessType {
|
||||
function validateNoRefAccessInRenderImpl(
|
||||
fn: HIRFunction,
|
||||
env: Env,
|
||||
): Result<RefAccessType, CompilerError> {
|
||||
errors: CompilerError,
|
||||
): RefAccessType {
|
||||
let returnValues: Array<undefined | RefAccessType> = [];
|
||||
let place;
|
||||
for (const param of fn.params) {
|
||||
@@ -336,7 +338,6 @@ function validateNoRefAccessInRenderImpl(
|
||||
env.resetChanged();
|
||||
returnValues = [];
|
||||
const safeBlocks: Array<{block: BlockId; ref: RefId}> = [];
|
||||
const errors = new CompilerError();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
retainWhere(safeBlocks, entry => entry.block !== block.id);
|
||||
for (const phi of block.phis) {
|
||||
@@ -432,13 +433,15 @@ function validateNoRefAccessInRenderImpl(
|
||||
case 'FunctionExpression': {
|
||||
let returnType: RefAccessType = {kind: 'None'};
|
||||
let readRefEffect = false;
|
||||
const innerErrors = new CompilerError();
|
||||
const result = validateNoRefAccessInRenderImpl(
|
||||
instr.value.loweredFunc.func,
|
||||
env,
|
||||
innerErrors,
|
||||
);
|
||||
if (result.isOk()) {
|
||||
returnType = result.unwrap();
|
||||
} else if (result.isErr()) {
|
||||
if (!innerErrors.hasAnyErrors()) {
|
||||
returnType = result;
|
||||
} else {
|
||||
readRefEffect = true;
|
||||
}
|
||||
env.set(instr.lvalue.identifier.id, {
|
||||
@@ -729,7 +732,7 @@ function validateNoRefAccessInRenderImpl(
|
||||
}
|
||||
|
||||
if (errors.hasAnyErrors()) {
|
||||
return Err(errors);
|
||||
return {kind: 'None'};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,10 +741,8 @@ function validateNoRefAccessInRenderImpl(
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
|
||||
return Ok(
|
||||
joinRefAccessTypes(
|
||||
...returnValues.filter((env): env is RefAccessType => env !== undefined),
|
||||
),
|
||||
return joinRefAccessTypes(
|
||||
...returnValues.filter((env): env is RefAccessType => env !== undefined),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
|
||||
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
|
||||
import {eachInstructionValueOperand} from '../HIR/visitors';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
/**
|
||||
* Validates that the given function does not have an infinite update loop
|
||||
@@ -43,17 +42,21 @@ import {Result} from '../Utils/Result';
|
||||
* y();
|
||||
* ```
|
||||
*/
|
||||
export function validateNoSetStateInRender(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateNoSetStateInRender(fn: HIRFunction): void {
|
||||
const unconditionalSetStateFunctions: Set<IdentifierId> = new Set();
|
||||
return validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions);
|
||||
const errors = validateNoSetStateInRenderImpl(
|
||||
fn,
|
||||
unconditionalSetStateFunctions,
|
||||
);
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
function validateNoSetStateInRenderImpl(
|
||||
fn: HIRFunction,
|
||||
unconditionalSetStateFunctions: Set<IdentifierId>,
|
||||
): Result<void, CompilerError> {
|
||||
): CompilerError {
|
||||
const unconditionalBlocks = computeUnconditionalBlocks(fn);
|
||||
let activeManualMemoId: number | null = null;
|
||||
const errors = new CompilerError();
|
||||
@@ -92,7 +95,7 @@ function validateNoSetStateInRenderImpl(
|
||||
validateNoSetStateInRenderImpl(
|
||||
instr.value.loweredFunc.func,
|
||||
unconditionalSetStateFunctions,
|
||||
).isErr()
|
||||
).hasAnyErrors()
|
||||
) {
|
||||
// This function expression unconditionally calls a setState
|
||||
unconditionalSetStateFunctions.add(instr.lvalue.identifier.id);
|
||||
@@ -183,5 +186,5 @@ function validateNoSetStateInRenderImpl(
|
||||
}
|
||||
}
|
||||
|
||||
return errors.asResult();
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import {
|
||||
ReactiveFunctionVisitor,
|
||||
visitReactiveFunction,
|
||||
} from '../ReactiveScopes/visitors';
|
||||
import {Result} from '../Utils/Result';
|
||||
import {getOrInsertDefault} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
@@ -47,15 +46,15 @@ import {getOrInsertDefault} from '../Utils/utils';
|
||||
* This can occur if a value's mutable range somehow extended to include a hook and
|
||||
* was pruned.
|
||||
*/
|
||||
export function validatePreservedManualMemoization(
|
||||
fn: ReactiveFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validatePreservedManualMemoization(fn: ReactiveFunction): void {
|
||||
const state = {
|
||||
errors: new CompilerError(),
|
||||
manualMemoState: null,
|
||||
};
|
||||
visitReactiveFunction(fn, new Visitor(), state);
|
||||
return state.errors.asResult();
|
||||
for (const detail of state.errors.details) {
|
||||
fn.env.recordError(detail);
|
||||
}
|
||||
}
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
@@ -9,7 +9,7 @@ import {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import {CompilerDiagnostic, CompilerError, ErrorCategory} from '..';
|
||||
import {CodegenFunction} from '../ReactiveScopes';
|
||||
import {Result} from '../Utils/Result';
|
||||
import {Environment} from '../HIR/Environment';
|
||||
|
||||
/**
|
||||
* IMPORTANT: This validation is only intended for use in unit tests.
|
||||
@@ -123,7 +123,8 @@ export function validateSourceLocations(
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
generatedAst: CodegenFunction,
|
||||
): Result<void, CompilerError> {
|
||||
env: Environment,
|
||||
): void {
|
||||
const errors = new CompilerError();
|
||||
|
||||
/*
|
||||
@@ -309,5 +310,7 @@ export function validateSourceLocations(
|
||||
}
|
||||
}
|
||||
|
||||
return errors.asResult();
|
||||
for (const detail of errors.details) {
|
||||
env.recordError(detail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,8 @@ import {
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
export function validateUseMemo(fn: HIRFunction): void {
|
||||
const errors = new CompilerError();
|
||||
const voidMemoErrors = new CompilerError();
|
||||
const useMemos = new Set<IdentifierId>();
|
||||
@@ -177,7 +176,9 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
}
|
||||
}
|
||||
fn.env.logErrors(voidMemoErrors.asResult());
|
||||
return errors.asResult();
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
function validateNoContextVariableAssignment(
|
||||
|
||||
@@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Todo: [hoisting] EnterSSA: Expected identifier to be defined before being used
|
||||
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
|
||||
|
||||
Identifier x$1 is undefined.
|
||||
<unknown> x$1.
|
||||
|
||||
error.dont-hoist-inline-reference.ts:3:2
|
||||
error.dont-hoist-inline-reference.ts:3:21
|
||||
1 | import {identity} from 'shared-runtime';
|
||||
2 | function useInvalid() {
|
||||
> 3 | const x = identity(x);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ [hoisting] EnterSSA: Expected identifier to be defined before being used
|
||||
| ^ this is uninitialized
|
||||
4 | return x;
|
||||
5 | }
|
||||
6 |
|
||||
|
||||
@@ -29,7 +29,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: This value cannot be modified
|
||||
|
||||
@@ -43,6 +43,32 @@ error.hook-call-freezes-captured-memberexpr.ts:13:2
|
||||
14 | return <Stringify x={x} cb={cb} />;
|
||||
15 | }
|
||||
16 |
|
||||
|
||||
Error: Cannot modify local variables after render completes
|
||||
|
||||
This argument is a function which may reassign or mutate `x` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
|
||||
|
||||
error.hook-call-freezes-captured-memberexpr.ts:9:25
|
||||
7 | * After this custom hook call, it's no longer valid to mutate x.
|
||||
8 | */
|
||||
> 9 | const cb = useIdentity(() => {
|
||||
| ^^^^^^^
|
||||
> 10 | x.value++;
|
||||
| ^^^^^^^^^^^^^^
|
||||
> 11 | });
|
||||
| ^^^^ This function may (indirectly) reassign or modify `x` after render
|
||||
12 |
|
||||
13 | x.value += count;
|
||||
14 | return <Stringify x={x} cb={cb} />;
|
||||
|
||||
error.hook-call-freezes-captured-memberexpr.ts:10:4
|
||||
8 | */
|
||||
9 | const cb = useIdentity(() => {
|
||||
> 10 | x.value++;
|
||||
| ^ This modifies `x`
|
||||
11 | });
|
||||
12 |
|
||||
13 | x.value += count;
|
||||
```
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ function component(a, b) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 3 errors:
|
||||
|
||||
Error: useMemo() callbacks may not be async or generator functions
|
||||
|
||||
@@ -32,6 +32,37 @@ error.invalid-ReactUseMemo-async-callback.ts:2:24
|
||||
5 | return x;
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
error.invalid-ReactUseMemo-async-callback.ts:3:10
|
||||
1 | function component(a, b) {
|
||||
2 | let x = React.useMemo(async () => {
|
||||
> 3 | await a;
|
||||
| ^ Missing dependency `a`
|
||||
4 | }, []);
|
||||
5 | return x;
|
||||
6 | }
|
||||
|
||||
Inferred dependencies: `[a]`
|
||||
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source.
|
||||
|
||||
error.invalid-ReactUseMemo-async-callback.ts:2:24
|
||||
1 | function component(a, b) {
|
||||
> 2 | let x = React.useMemo(async () => {
|
||||
| ^^^^^^^^^^^^^
|
||||
> 3 | await a;
|
||||
| ^^^^^^^^^^^^
|
||||
> 4 | }, []);
|
||||
| ^^^^ Could not preserve existing manual memoization
|
||||
5 | return x;
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ function Component({item, cond}) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
Found 3 errors:
|
||||
|
||||
Error: Calling setState from useMemo may trigger an infinite loop
|
||||
|
||||
@@ -49,6 +49,39 @@ error.invalid-conditional-setState-in-useMemo.ts:8:6
|
||||
9 | }
|
||||
10 | }, [cond, key, init]);
|
||||
11 |
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI. Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
|
||||
|
||||
error.invalid-conditional-setState-in-useMemo.ts:7:18
|
||||
5 | useMemo(() => {
|
||||
6 | if (cond) {
|
||||
> 7 | setPrevItem(item);
|
||||
| ^^^^ Missing dependency `item`
|
||||
8 | setState(0);
|
||||
9 | }
|
||||
10 | }, [cond, key, init]);
|
||||
|
||||
error.invalid-conditional-setState-in-useMemo.ts:10:12
|
||||
8 | setState(0);
|
||||
9 | }
|
||||
> 10 | }, [cond, key, init]);
|
||||
| ^^^ Unnecessary dependency `key`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change
|
||||
11 |
|
||||
12 | return state;
|
||||
13 | }
|
||||
|
||||
error.invalid-conditional-setState-in-useMemo.ts:10:17
|
||||
8 | setState(0);
|
||||
9 | }
|
||||
> 10 | }, [cond, key, init]);
|
||||
| ^^^^ Unnecessary dependency `init`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change
|
||||
11 |
|
||||
12 | return state;
|
||||
13 | }
|
||||
|
||||
Inferred dependencies: `[cond, item]`
|
||||
```
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ function Component() {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 3 errors:
|
||||
Found 6 errors:
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
|
||||
@@ -57,6 +57,45 @@ error.invalid-impure-functions-in-render.ts:6:15
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
|
||||
`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();
|
||||
| ^^^^^^^^ 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 call impure function during render
|
||||
|
||||
`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();
|
||||
| ^^^^^^^^^^^^^^^ Cannot call impure function
|
||||
6 | const rand = Math.random();
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
8 | }
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
|
||||
`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();
|
||||
| ^^^^^^^^^^^ Cannot call impure function
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
8 | }
|
||||
9 |
|
||||
```
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ function useInvalidMutation(options) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: This value cannot be modified
|
||||
|
||||
@@ -30,6 +30,27 @@ error.invalid-mutation-in-closure.ts:4:4
|
||||
5 | }
|
||||
6 | return test;
|
||||
7 | }
|
||||
|
||||
Error: Cannot modify local variables after render completes
|
||||
|
||||
This argument is a function which may reassign or mutate `options` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
|
||||
|
||||
error.invalid-mutation-in-closure.ts:6:9
|
||||
4 | options.foo = 'bar';
|
||||
5 | }
|
||||
> 6 | return test;
|
||||
| ^^^^ This function may (indirectly) reassign or modify `options` after render
|
||||
7 | }
|
||||
8 |
|
||||
|
||||
error.invalid-mutation-in-closure.ts:4:4
|
||||
2 | function test() {
|
||||
3 | foo(options.foo); // error should not point on this line
|
||||
> 4 | options.foo = 'bar';
|
||||
| ^^^^^^^ This modifies `options`
|
||||
5 | }
|
||||
6 | return test;
|
||||
7 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ function useFoo() {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot reassign variable after render completes
|
||||
|
||||
@@ -29,6 +29,31 @@ error.invalid-reassign-local-in-hook-return-value.ts:4:4
|
||||
5 | };
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
Error: Cannot modify local variables after render completes
|
||||
|
||||
This argument is a function which may reassign or mutate `x` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
|
||||
|
||||
error.invalid-reassign-local-in-hook-return-value.ts:3:9
|
||||
1 | function useFoo() {
|
||||
2 | let x = 0;
|
||||
> 3 | return value => {
|
||||
| ^^^^^^^^^^
|
||||
> 4 | x = value;
|
||||
| ^^^^^^^^^^^^^^
|
||||
> 5 | };
|
||||
| ^^^^ This function may (indirectly) reassign or modify `x` after render
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
error.invalid-reassign-local-in-hook-return-value.ts:4:4
|
||||
2 | let x = 0;
|
||||
3 | return value => {
|
||||
> 4 | x = value;
|
||||
| ^ This modifies `x`
|
||||
5 | };
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ function Component() {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot reassign variable after render completes
|
||||
|
||||
@@ -61,6 +61,32 @@ error.invalid-reassign-local-variable-in-effect.ts:7:4
|
||||
8 | };
|
||||
9 |
|
||||
10 | const onMount = newValue => {
|
||||
|
||||
Error: Cannot modify local variables after render completes
|
||||
|
||||
This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
|
||||
|
||||
error.invalid-reassign-local-variable-in-effect.ts:33:12
|
||||
31 | };
|
||||
32 |
|
||||
> 33 | useEffect(() => {
|
||||
| ^^^^^^^
|
||||
> 34 | onMount();
|
||||
| ^^^^^^^^^^^^^^
|
||||
> 35 | }, [onMount]);
|
||||
| ^^^^ This function may (indirectly) reassign or modify `local` after render
|
||||
36 |
|
||||
37 | return 'ok';
|
||||
38 | }
|
||||
|
||||
error.invalid-reassign-local-variable-in-effect.ts:7:4
|
||||
5 |
|
||||
6 | const reassignLocal = newValue => {
|
||||
> 7 | local = newValue;
|
||||
| ^^^^^ This modifies `local`
|
||||
8 | };
|
||||
9 |
|
||||
10 | const onMount = newValue => {
|
||||
```
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ function Component() {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot reassign variable after render completes
|
||||
|
||||
@@ -62,6 +62,32 @@ error.invalid-reassign-local-variable-in-hook-argument.ts:8:4
|
||||
9 | };
|
||||
10 |
|
||||
11 | const callback = newValue => {
|
||||
|
||||
Error: Cannot modify local variables after render completes
|
||||
|
||||
This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
|
||||
|
||||
error.invalid-reassign-local-variable-in-hook-argument.ts:34:14
|
||||
32 | };
|
||||
33 |
|
||||
> 34 | useIdentity(() => {
|
||||
| ^^^^^^^
|
||||
> 35 | callback();
|
||||
| ^^^^^^^^^^^^^^^
|
||||
> 36 | });
|
||||
| ^^^^ This function may (indirectly) reassign or modify `local` after render
|
||||
37 |
|
||||
38 | return 'ok';
|
||||
39 | }
|
||||
|
||||
error.invalid-reassign-local-variable-in-hook-argument.ts:8:4
|
||||
6 |
|
||||
7 | const reassignLocal = newValue => {
|
||||
> 8 | local = newValue;
|
||||
| ^^^^^ This modifies `local`
|
||||
9 | };
|
||||
10 |
|
||||
11 | const callback = newValue => {
|
||||
```
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ function Component() {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot reassign variable after render completes
|
||||
|
||||
@@ -55,6 +55,27 @@ error.invalid-reassign-local-variable-in-jsx-callback.ts:5:4
|
||||
6 | };
|
||||
7 |
|
||||
8 | const onClick = newValue => {
|
||||
|
||||
Error: Cannot modify local variables after render completes
|
||||
|
||||
This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
|
||||
|
||||
error.invalid-reassign-local-variable-in-jsx-callback.ts:31:26
|
||||
29 | };
|
||||
30 |
|
||||
> 31 | return <button onClick={onClick}>Submit</button>;
|
||||
| ^^^^^^^ This function may (indirectly) reassign or modify `local` after render
|
||||
32 | }
|
||||
33 |
|
||||
|
||||
error.invalid-reassign-local-variable-in-jsx-callback.ts:5:4
|
||||
3 |
|
||||
4 | const reassignLocal = newValue => {
|
||||
> 5 | local = newValue;
|
||||
| ^^^^^ This modifies `local`
|
||||
6 | };
|
||||
7 |
|
||||
8 | const onClick = newValue => {
|
||||
```
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ function useKeyedState({key, init}) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 3 errors:
|
||||
|
||||
Error: Calling setState from useMemo may trigger an infinite loop
|
||||
|
||||
@@ -40,6 +40,61 @@ error.invalid-setState-in-useMemo-indirect-useCallback.ts:13:4
|
||||
14 | }, [key, init]);
|
||||
15 |
|
||||
16 | return state;
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
error.invalid-setState-in-useMemo-indirect-useCallback.ts:9:13
|
||||
7 | const fn = useCallback(() => {
|
||||
8 | setPrevKey(key);
|
||||
> 9 | setState(init);
|
||||
| ^^^^ Missing dependency `init`
|
||||
10 | });
|
||||
11 |
|
||||
12 | useMemo(() => {
|
||||
|
||||
error.invalid-setState-in-useMemo-indirect-useCallback.ts:8:15
|
||||
6 |
|
||||
7 | const fn = useCallback(() => {
|
||||
> 8 | setPrevKey(key);
|
||||
| ^^^ Missing dependency `key`
|
||||
9 | setState(init);
|
||||
10 | });
|
||||
11 |
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI. Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
|
||||
|
||||
error.invalid-setState-in-useMemo-indirect-useCallback.ts:13:4
|
||||
11 |
|
||||
12 | useMemo(() => {
|
||||
> 13 | fn();
|
||||
| ^^ Missing dependency `fn`
|
||||
14 | }, [key, init]);
|
||||
15 |
|
||||
16 | return state;
|
||||
|
||||
error.invalid-setState-in-useMemo-indirect-useCallback.ts:14:6
|
||||
12 | useMemo(() => {
|
||||
13 | fn();
|
||||
> 14 | }, [key, init]);
|
||||
| ^^^ Unnecessary dependency `key`
|
||||
15 |
|
||||
16 | return state;
|
||||
17 | }
|
||||
|
||||
error.invalid-setState-in-useMemo-indirect-useCallback.ts:14:11
|
||||
12 | useMemo(() => {
|
||||
13 | fn();
|
||||
> 14 | }, [key, init]);
|
||||
| ^^^^ Unnecessary dependency `init`
|
||||
15 |
|
||||
16 | return state;
|
||||
17 | }
|
||||
|
||||
Inferred dependencies: `[fn]`
|
||||
```
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ function component(a, b) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 3 errors:
|
||||
|
||||
Error: useMemo() callbacks may not be async or generator functions
|
||||
|
||||
@@ -32,6 +32,37 @@ error.invalid-useMemo-async-callback.ts:2:18
|
||||
5 | return x;
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
error.invalid-useMemo-async-callback.ts:3:10
|
||||
1 | function component(a, b) {
|
||||
2 | let x = useMemo(async () => {
|
||||
> 3 | await a;
|
||||
| ^ Missing dependency `a`
|
||||
4 | }, []);
|
||||
5 | return x;
|
||||
6 | }
|
||||
|
||||
Inferred dependencies: `[a]`
|
||||
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source.
|
||||
|
||||
error.invalid-useMemo-async-callback.ts:2:18
|
||||
1 | function component(a, b) {
|
||||
> 2 | let x = useMemo(async () => {
|
||||
| ^^^^^^^^^^^^^
|
||||
> 3 | await a;
|
||||
| ^^^^^^^^^^^^
|
||||
> 4 | }, []);
|
||||
| ^^^^ Could not preserve existing manual memoization
|
||||
5 | return x;
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ function component(a, b) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 3 errors:
|
||||
|
||||
Error: useMemo() callbacks may not accept parameters
|
||||
|
||||
@@ -26,6 +26,32 @@ error.invalid-useMemo-callback-args.ts:2:18
|
||||
3 | return x;
|
||||
4 | }
|
||||
5 |
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
error.invalid-useMemo-callback-args.ts:2:23
|
||||
1 | function component(a, b) {
|
||||
> 2 | let x = useMemo(c => a, []);
|
||||
| ^ Missing dependency `a`
|
||||
3 | return x;
|
||||
4 | }
|
||||
5 |
|
||||
|
||||
Inferred dependencies: `[a]`
|
||||
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source.
|
||||
|
||||
error.invalid-useMemo-callback-args.ts:2:18
|
||||
1 | function component(a, b) {
|
||||
> 2 | let x = useMemo(c => a, []);
|
||||
| ^^^^^^ Could not preserve existing manual memoization
|
||||
3 | return x;
|
||||
4 | }
|
||||
5 |
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot reassign variable after render completes
|
||||
|
||||
@@ -46,6 +46,28 @@ error.mutable-range-shared-inner-outer-function.ts:8:6
|
||||
9 | b = [];
|
||||
10 | } else {
|
||||
11 | a = {};
|
||||
|
||||
Error: Cannot modify local variables after render completes
|
||||
|
||||
This argument is a function which may reassign or mutate `a` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
|
||||
|
||||
error.mutable-range-shared-inner-outer-function.ts:17:23
|
||||
15 | b.push(false);
|
||||
16 | };
|
||||
> 17 | return <div onClick={f} />;
|
||||
| ^ This function may (indirectly) reassign or modify `a` after render
|
||||
18 | }
|
||||
19 |
|
||||
20 | export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
error.mutable-range-shared-inner-outer-function.ts:8:6
|
||||
6 | const f = () => {
|
||||
7 | if (cond) {
|
||||
> 8 | a = {};
|
||||
| ^ This modifies `a`
|
||||
9 | b = [];
|
||||
10 | } else {
|
||||
11 | a = {};
|
||||
```
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ function useHook(parentRef) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: This value cannot be modified
|
||||
|
||||
@@ -43,6 +43,27 @@ error.todo-allow-assigning-to-inferred-ref-prop-in-callback.ts:15:8
|
||||
16 | }
|
||||
17 | }
|
||||
18 | };
|
||||
|
||||
Error: Cannot modify local variables after render completes
|
||||
|
||||
This argument is a function which may reassign or mutate `parentRef` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
|
||||
|
||||
error.todo-allow-assigning-to-inferred-ref-prop-in-callback.ts:19:9
|
||||
17 | }
|
||||
18 | };
|
||||
> 19 | return handler;
|
||||
| ^^^^^^^ This function may (indirectly) reassign or modify `parentRef` after render
|
||||
20 | }
|
||||
21 |
|
||||
|
||||
error.todo-allow-assigning-to-inferred-ref-prop-in-callback.ts:15:8
|
||||
13 | } else {
|
||||
14 | // So this assignment fails since we don't know its a ref
|
||||
> 15 | parentRef.current = instance;
|
||||
| ^^^^^^^^^ This modifies `parentRef`
|
||||
16 | }
|
||||
17 | }
|
||||
18 | };
|
||||
```
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ function Component() {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot reassign variable after render completes
|
||||
|
||||
@@ -31,6 +31,27 @@ error.todo-function-expression-references-later-variable-declaration.ts:3:4
|
||||
4 | };
|
||||
5 | let onClick;
|
||||
6 |
|
||||
|
||||
Error: Cannot modify local variables after render completes
|
||||
|
||||
This argument is a function which may reassign or mutate `onClick` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
|
||||
|
||||
error.todo-function-expression-references-later-variable-declaration.ts:7:23
|
||||
5 | let onClick;
|
||||
6 |
|
||||
> 7 | return <div onClick={callback} />;
|
||||
| ^^^^^^^^ This function may (indirectly) reassign or modify `onClick` after render
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
error.todo-function-expression-references-later-variable-declaration.ts:3:4
|
||||
1 | function Component() {
|
||||
2 | let callback = () => {
|
||||
> 3 | onClick = () => {};
|
||||
| ^^^^^^^ This modifies `onClick`
|
||||
4 | };
|
||||
5 | let onClick;
|
||||
6 |
|
||||
```
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ function Component({foo}) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 3 errors:
|
||||
|
||||
Todo: Support destructuring of context variables
|
||||
|
||||
@@ -29,10 +29,34 @@ error.todo-reassign-const.ts:3:20
|
||||
1 | import {Stringify} from 'shared-runtime';
|
||||
2 |
|
||||
> 3 | function Component({foo}) {
|
||||
| ^^^ Support destructuring of context variables
|
||||
| ^^^
|
||||
4 | let bar = foo.bar;
|
||||
5 | return (
|
||||
6 | <Stringify
|
||||
|
||||
Todo: Support destructuring of context variables
|
||||
|
||||
error.todo-reassign-const.ts:3:20
|
||||
1 | import {Stringify} from 'shared-runtime';
|
||||
2 |
|
||||
> 3 | function Component({foo}) {
|
||||
| ^^^
|
||||
4 | let bar = foo.bar;
|
||||
5 | return (
|
||||
6 | <Stringify
|
||||
|
||||
Error: This value cannot be modified
|
||||
|
||||
Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
|
||||
|
||||
error.todo-reassign-const.ts:8:8
|
||||
6 | <Stringify
|
||||
7 | handler={() => {
|
||||
> 8 | foo = true;
|
||||
| ^^^ `foo` cannot be modified
|
||||
9 | }}
|
||||
10 | />
|
||||
11 | );
|
||||
```
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ function Component({x, y, z}) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 4 errors:
|
||||
Found 6 errors:
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
@@ -157,6 +157,48 @@ error.invalid-exhaustive-deps.ts:37:13
|
||||
40 | }, []);
|
||||
|
||||
Inferred dependencies: `[ref]`
|
||||
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `x.y.z.a.b`, but the source dependencies were [x?.y.z.a?.b.z]. Inferred different dependency than source.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:14:20
|
||||
12 | // ok, not our job to type check nullability
|
||||
13 | }, [x.y.z.a]);
|
||||
> 14 | const c = useMemo(() => {
|
||||
| ^^^^^^^
|
||||
> 15 | return x?.y.z.a?.b;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 16 | // error: too precise
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 17 | }, [x?.y.z.a?.b.z]);
|
||||
| ^^^^ Could not preserve existing manual memoization
|
||||
18 | const d = useMemo(() => {
|
||||
19 | return x?.y?.[(console.log(y), z?.b)];
|
||||
20 | // ok
|
||||
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `ref`, but the source dependencies were []. Inferred dependency not present in source.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:35:21
|
||||
33 | const ref2 = useRef(null);
|
||||
34 | const ref = z ? ref1 : ref2;
|
||||
> 35 | const cb = useMemo(() => {
|
||||
| ^^^^^^^
|
||||
> 36 | return () => {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
> 37 | return ref.current;
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
> 38 | };
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
> 39 | // error: ref is a stable type but reactive
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
> 40 | }, []);
|
||||
| ^^^^ Could not preserve existing manual memoization
|
||||
41 | return <Stringify results={[a, b, c, d, e, f, cb]} />;
|
||||
42 | }
|
||||
43 |
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ function useHook() {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
|
||||
@@ -38,6 +38,19 @@ error.invalid-missing-nonreactive-dep-unmemoized.ts:11:31
|
||||
14 |
|
||||
|
||||
Inferred dependencies: `[object]`
|
||||
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `object`, but the source dependencies were []. Inferred dependency not present in source.
|
||||
|
||||
error.invalid-missing-nonreactive-dep-unmemoized.ts:11:24
|
||||
9 | useIdentity();
|
||||
10 | object.x = 0;
|
||||
> 11 | const array = useMemo(() => [object], []);
|
||||
| ^^^^^^^^^^^^^^ Could not preserve existing manual memoization
|
||||
12 | return array;
|
||||
13 | }
|
||||
14 |
|
||||
```
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"category":"PreserveManualMemo","reason":"Existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":337},"end":{"line":9,"column":52,"index":358},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}},"fnLoc":null}
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":255},"end":{"line":16,"column":1,"index":482},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"category":"PreserveManualMemo","reason":"Existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":337},"end":{"line":9,"column":52,"index":358},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}}
|
||||
```
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ function Component() {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 3 errors:
|
||||
Found 6 errors:
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
|
||||
@@ -57,6 +57,45 @@ error.invalid-impure-functions-in-render.ts:6:15
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
|
||||
`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();
|
||||
| ^^^^^^^^ 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 call impure function during render
|
||||
|
||||
`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();
|
||||
| ^^^^^^^^^^^^^^^ Cannot call impure function
|
||||
6 | const rand = Math.random();
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
8 | }
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
|
||||
`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();
|
||||
| ^^^^^^^^^^^ Cannot call impure function
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
8 | }
|
||||
9 |
|
||||
```
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ function Component() {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot reassign variable after render completes
|
||||
|
||||
@@ -56,6 +56,27 @@ error.invalid-reassign-local-variable-in-jsx-callback.ts:6:4
|
||||
7 | };
|
||||
8 |
|
||||
9 | const onClick = newValue => {
|
||||
|
||||
Error: Cannot modify local variables after render completes
|
||||
|
||||
This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
|
||||
|
||||
error.invalid-reassign-local-variable-in-jsx-callback.ts:32:26
|
||||
30 | };
|
||||
31 |
|
||||
> 32 | return <button onClick={onClick}>Submit</button>;
|
||||
| ^^^^^^^ This function may (indirectly) reassign or modify `local` after render
|
||||
33 | }
|
||||
34 |
|
||||
|
||||
error.invalid-reassign-local-variable-in-jsx-callback.ts:6:4
|
||||
4 |
|
||||
5 | const reassignLocal = newValue => {
|
||||
> 6 | local = newValue;
|
||||
| ^^^^^ This modifies `local`
|
||||
7 | };
|
||||
8 |
|
||||
9 | const onClick = newValue => {
|
||||
```
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ function Component({content, refetch}) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot access variable before it is declared
|
||||
|
||||
@@ -52,6 +52,18 @@ Error: Cannot access variable before it is declared
|
||||
20 |
|
||||
21 | return <Foo data={data} onSubmit={onSubmit} />;
|
||||
22 | }
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
9 | // TDZ violation!
|
||||
10 | const onRefetch = useCallback(() => {
|
||||
> 11 | refetch(data);
|
||||
| ^^^^ Missing dependency `data`
|
||||
12 | }, [refetch]);
|
||||
13 |
|
||||
14 | // The context variable gets frozen here since it's passed to a hook
|
||||
```
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ function useFoo(input1) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
|
||||
@@ -46,6 +46,23 @@ error.useMemo-unrelated-mutation-in-depslist.ts:18:14
|
||||
21 | }
|
||||
|
||||
Inferred dependencies: `[x, y]`
|
||||
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `input1`, but the source dependencies were [y]. Inferred different dependency than source.
|
||||
|
||||
error.useMemo-unrelated-mutation-in-depslist.ts:16:27
|
||||
14 | const x = {};
|
||||
15 | const y = [input1];
|
||||
> 16 | const memoized = useMemo(() => {
|
||||
| ^^^^^^^
|
||||
> 17 | return [y];
|
||||
| ^^^^^^^^^^^^^^^
|
||||
> 18 | }, [(mutate(x), y)]);
|
||||
| ^^^^ Could not preserve existing manual memoization
|
||||
19 |
|
||||
20 | return [x, memoized];
|
||||
21 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -16,29 +16,24 @@ function Component(props) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
Found 1 error:
|
||||
|
||||
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
|
||||
Invariant: Unexpected empty block with `goto` terminal
|
||||
|
||||
error.invalid-hook-for.ts:4:9
|
||||
2 | let i = 0;
|
||||
3 | for (let x = 0; useHook(x) < 10; useHook(i), x++) {
|
||||
> 4 | i += useHook(x);
|
||||
| ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
|
||||
5 | }
|
||||
6 | return i;
|
||||
7 | }
|
||||
Block bb5 is empty.
|
||||
|
||||
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
|
||||
|
||||
error.invalid-hook-for.ts:3:35
|
||||
error.invalid-hook-for.ts:3:2
|
||||
1 | function Component(props) {
|
||||
2 | let i = 0;
|
||||
> 3 | for (let x = 0; useHook(x) < 10; useHook(i), x++) {
|
||||
| ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
|
||||
4 | i += useHook(x);
|
||||
5 | }
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 4 | i += useHook(x);
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
> 5 | }
|
||||
| ^^^^ Unexpected empty block with `goto` terminal
|
||||
6 | return i;
|
||||
7 | }
|
||||
8 |
|
||||
```
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useRef } from "react";
|
||||
|
||||
const useControllableState = (options) => {};
|
||||
|
||||
Reference in New Issue
Block a user