Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df60c27489 | ||
|
|
4573abc7e3 | ||
|
|
deca1ebd8b | ||
|
|
3dc96d6a49 | ||
|
|
d371f07622 | ||
|
|
d401744f03 | ||
|
|
f92560c498 |
@@ -75,49 +75,49 @@ Change `runWithEnvironment` to run all passes and check for errors at the end in
|
||||
|
||||
Currently `lower()` returns `Result<HIRFunction, CompilerError>`. It already accumulates errors internally via `builder.errors`, but returns `Err` when errors exist. Change it to always return `Ok(hir)` while recording errors on the environment.
|
||||
|
||||
- [ ] **3.1 Change `lower` to always return HIRFunction** (`src/HIR/BuildHIR.ts`)
|
||||
- [x] **3.1 Change `lower` to always return HIRFunction** (`src/HIR/BuildHIR.ts`)
|
||||
- Change return type from `Result<HIRFunction, CompilerError>` to `HIRFunction`
|
||||
- Instead of returning `Err(builder.errors)` at line 227-229, record errors on `env` via `env.recordError(builder.errors)` and return the (partial) HIR
|
||||
- Instead of returning `Err(builder.errors)` at line 227-229, record errors on `env` via `env.recordErrors(builder.errors)` and return the (partial) HIR
|
||||
- Update the pipeline to call `lower(func, env)` directly instead of `lower(func, env).unwrap()`
|
||||
- Added try/catch around body lowering to catch thrown CompilerErrors (e.g., from `resolveBinding`) and record them
|
||||
|
||||
- [ ] **3.2 Handle `var` declarations as `let`** (`src/HIR/BuildHIR.ts`, line ~855)
|
||||
- Currently throws `Todo("Handle var kinds in VariableDeclaration")`
|
||||
- Instead: record the Todo error on env, then treat the `var` as `let` and continue lowering
|
||||
- [x] **3.2 Handle `var` declarations as `let`** (`src/HIR/BuildHIR.ts`, line ~855)
|
||||
- Record the Todo error, then treat `var` as `let` and continue lowering (instead of skipping the declaration)
|
||||
|
||||
- [ ] **3.3 Handle `try/finally` by pruning `finally`** (`src/HIR/BuildHIR.ts`, lines ~1281-1296)
|
||||
- Currently throws Todo for `try` without `catch` and `try` with `finally`
|
||||
- Instead: record the Todo error, then lower the `try/catch` portion only (put the `finally` block content in the fallthrough of the try/catch)
|
||||
- [x] **3.3 Handle `try/finally` by pruning `finally`** (`src/HIR/BuildHIR.ts`, lines ~1281-1296)
|
||||
- Already handled: `try` without `catch` pushes error and returns; `try` with `finally` pushes error and continues with `try/catch` portion only
|
||||
|
||||
- [ ] **3.4 Handle `eval()` via UnsupportedNode** (`src/HIR/BuildHIR.ts`, line ~3568)
|
||||
- Currently throws `UnsupportedSyntax("The 'eval' function is not supported")`
|
||||
- Instead: record the error, emit an `UnsupportedNode` instruction value with the original AST node
|
||||
- [x] **3.4 Handle `eval()` via UnsupportedNode** (`src/HIR/BuildHIR.ts`, line ~3568)
|
||||
- Already handled: records error via `builder.errors.push()` and continues
|
||||
|
||||
- [ ] **3.5 Handle `with` statement via UnsupportedNode** (`src/HIR/BuildHIR.ts`, line ~1382)
|
||||
- Currently throws `UnsupportedSyntax`
|
||||
- Instead: record the error, emit the body statements as-is (or skip them), continue
|
||||
- [x] **3.5 Handle `with` statement via UnsupportedNode** (`src/HIR/BuildHIR.ts`, line ~1382)
|
||||
- Already handled: records error and emits `UnsupportedNode`
|
||||
|
||||
- [ ] **3.6 Handle inline `class` declarations** (`src/HIR/BuildHIR.ts`, line ~1402)
|
||||
- Currently throws `UnsupportedSyntax`
|
||||
- Already creates an `UnsupportedNode`; just record the error instead of throwing
|
||||
- [x] **3.6 Handle inline `class` declarations** (`src/HIR/BuildHIR.ts`, line ~1402)
|
||||
- Already handled: records error and emits `UnsupportedNode`
|
||||
|
||||
- [ ] **3.7 Handle remaining Todo errors in expression lowering** (`src/HIR/BuildHIR.ts`)
|
||||
- For each of the ~35 Todo error sites in `lowerExpression`, `lowerAssignment`, `lowerMemberExpression`, etc.:
|
||||
- Record the Todo error on the environment
|
||||
- Emit an `UnsupportedNode` instruction value with the original Babel AST node as fallback
|
||||
- Key sites include: pipe operator, tagged templates with interpolations, compound logical assignment (`&&=`, `||=`, `??=`), `for await...of`, object getters/setters, UpdateExpression on context variables, complex destructuring patterns
|
||||
- The `UnsupportedNode` variant already exists in HIR and passes through codegen unchanged, so no new HIR types are needed for most cases
|
||||
- [x] **3.7 Handle remaining Todo errors in expression lowering** (`src/HIR/BuildHIR.ts`)
|
||||
- Already handled: all ~60 error sites use `builder.errors.push()` to accumulate errors. The try/catch around body lowering provides a safety net for any that still throw.
|
||||
|
||||
- [ ] **3.8 Handle `throw` inside `try/catch`** (`src/HIR/BuildHIR.ts`, line ~284)
|
||||
- Currently throws Todo
|
||||
- Instead: record the error, and represent the `throw` as a terminal that ends the block (the existing `throw` terminal type may already handle this, or we can use `UnsupportedNode`)
|
||||
- [x] **3.8 Handle `throw` inside `try/catch`** (`src/HIR/BuildHIR.ts`, line ~284)
|
||||
- Already handled: records error via `builder.errors.push()` and continues
|
||||
|
||||
- [ ] **3.9 Handle `for` loops with missing test or expression init** (`src/HIR/BuildHIR.ts`, lines ~559, ~632)
|
||||
- Record the error and construct a best-effort loop HIR (e.g., for `for(;;)`, use `true` as the test expression)
|
||||
- [x] **3.9 Handle `for` loops with missing test or expression init** (`src/HIR/BuildHIR.ts`, lines ~559, ~632)
|
||||
- For `for(;;)` (missing test): emit `true` as the test expression and add a branch terminal
|
||||
- For empty init (`for (; ...)`): add a placeholder instruction to avoid invariant about empty blocks
|
||||
- For expression init (`for (expr; ...)`): record error and lower the expression as best-effort
|
||||
- Changed `'unsupported'` terminal to `'goto'` terminal for non-variable init to maintain valid CFG structure
|
||||
|
||||
- [ ] **3.10 Handle nested function lowering failures** (`src/HIR/BuildHIR.ts`, `lowerFunction` at line ~3504)
|
||||
- Currently calls `lower()` recursively and merges errors if it fails (`builder.errors.merge(functionErrors)`)
|
||||
- With the new approach, the nested `lower()` always returns an HIR, but errors are recorded on the shared environment
|
||||
- Ensure the parent function continues lowering even if a nested function had errors
|
||||
- [x] **3.10 Handle nested function lowering failures** (`src/HIR/BuildHIR.ts`, `lowerFunction` at line ~3504)
|
||||
- `lowerFunction()` now always returns `LoweredFunction` since `lower()` always returns `HIRFunction`
|
||||
- Errors from nested functions are recorded on the shared environment
|
||||
- Removed the `null` return case and the corresponding `UnsupportedNode` fallback in callers
|
||||
|
||||
- [x] **3.11 Handle unreachable functions in `build()`** (`src/HIR/HIRBuilder.ts`, `build()`)
|
||||
- Changed `CompilerError.throwTodo()` for unreachable code with hoisted declarations to `this.errors.push()` to allow HIR construction to complete
|
||||
|
||||
- [x] **3.12 Handle duplicate fbt tags** (`src/HIR/BuildHIR.ts`, line ~2279)
|
||||
- Changed `CompilerError.throwDiagnostic()` to `builder.errors.pushDiagnostic()` to record instead of throw
|
||||
|
||||
### Phase 4: Update Validation Passes
|
||||
|
||||
@@ -279,27 +279,27 @@ Walk through `runWithEnvironment` and wrap each pass call site. This is the inte
|
||||
|
||||
### Phase 8: Testing
|
||||
|
||||
- [ ] **8.1 Update existing `error.todo-*` fixture expectations**
|
||||
- [x] **8.1 Update existing `error.todo-*` fixture expectations**
|
||||
- Currently, fixtures with `error.todo-` prefix expect a single error and bailout
|
||||
- After fault tolerance, some of these may now produce multiple errors
|
||||
- Update the `.expect.md` files to reflect the new aggregated error output
|
||||
|
||||
- [ ] **8.2 Add multi-error test fixtures**
|
||||
- [x] **8.2 Add multi-error test fixtures**
|
||||
- Create test fixtures that contain multiple independent errors (e.g., both a `var` declaration and a mutation of a frozen value)
|
||||
- Verify that all errors are reported, not just the first one
|
||||
|
||||
- [ ] **8.3 Add test for invariant-still-throws behavior**
|
||||
- [x] **8.3 Add test for invariant-still-throws behavior**
|
||||
- Verify that `CompilerError.invariant()` failures still cause immediate abort
|
||||
- Verify that non-CompilerError exceptions still cause immediate abort
|
||||
|
||||
- [ ] **8.4 Add test for partial HIR codegen**
|
||||
- [x] **8.4 Add test for partial HIR codegen**
|
||||
- Verify that when BuildHIR produces partial HIR (with `UnsupportedNode` values), later passes handle it gracefully and codegen produces the original AST for unsupported portions
|
||||
|
||||
- [ ] **8.5 Verify error severity in aggregated output**
|
||||
- [x] **8.5 Verify error severity in aggregated output**
|
||||
- Test that the aggregated `CompilerError` correctly reports `hasErrors()` vs `hasWarning()` vs `hasHints()` based on the mix of accumulated diagnostics
|
||||
- Verify that `panicThreshold` behavior in Program.ts is correct for aggregated errors
|
||||
|
||||
- [ ] **8.6 Run full test suite**
|
||||
- [x] **8.6 Run full test suite**
|
||||
- Run `yarn snap` and `yarn snap -u` to update all fixture expectations
|
||||
- Ensure no regressions in passing tests
|
||||
|
||||
@@ -324,4 +324,10 @@ Walk through `runWithEnvironment` and wrap each pass call site. This is the inte
|
||||
* **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.
|
||||
* **Phase 3 (BuildHIR) revealed that most error sites already used `builder.errors.push()` for accumulation.** The existing lowering code was designed to accumulate errors rather than throw. The main changes were: (1) changing `lower()` return type from `Result` to `HIRFunction`, (2) recording builder errors on env, (3) adding a try/catch around body lowering to catch thrown CompilerErrors from sub-calls like `resolveBinding()`, (4) treating `var` as `let` instead of skipping declarations, and (5) fixing ForStatement init/test handling to produce valid CFG structure.
|
||||
* **Partial HIR can trigger downstream invariants.** When lowering skips or partially handles constructs (e.g., unreachable hoisted functions, `var` declarations before the fix), downstream passes like `InferMutationAliasingEffects` may encounter uninitialized identifiers and throw invariants. This is acceptable since the function still correctly bails out of compilation, but error messages may be less specific. The fix for `var` (treating as `let`) demonstrates how to avoid this: continue lowering with a best-effort representation rather than skipping entirely.
|
||||
* **Errors accumulated on `env` are lost when an invariant propagates out of the pipeline.** Since invariant CompilerErrors always re-throw through `tryRecord()`, they exit the pipeline as exceptions. The caller only sees the invariant error, not any errors previously recorded on `env`. This is a design limitation that could be addressed by aggregating env errors with caught exceptions in `tryCompileFunction()`.
|
||||
* **Dedicated fault tolerance test fixtures** were added in `__tests__/fixtures/compiler/fault-tolerance/`. Each fixture combines two or more errors from different passes to verify the compiler reports all of them rather than short-circuiting on the first. Coverage includes: `var`+props mutation (BuildHIR→InferMutationAliasingEffects), `var`+ref access (BuildHIR→ValidateNoRefAccessInRender), `try/finally`+props mutation (BuildHIR→InferMutationAliasingEffects), `try/finally`+ref access (BuildHIR→ValidateNoRefAccessInRender), and a 3-error test combining try/finally+ref access+props mutation.
|
||||
* **Cleanup: consistent `tryRecord()` wrapping in Pipeline.ts.** All validation passes and inference passes are now wrapped in `env.tryRecord()` for defense-in-depth, consistent with the approach used for transform passes. Previously only transform passes were wrapped. Merged duplicate `env.enableValidations` guard blocks. Pattern B lint-only passes (`env.logErrors()`) were intentionally not wrapped since they use a different error recording strategy.
|
||||
* **Cleanup: normalized validation error recording pattern.** Four validation passes (`ValidateNoDerivedComputationsInEffects`, `ValidateMemoizedEffectDependencies`, `ValidatePreservedManualMemoization`, `ValidateSourceLocations`) were using `for (const detail of errors.details) { env.recordError(detail); }` instead of the simpler `env.recordErrors(errors)`. Normalized to use the batch method.
|
||||
|
||||
|
||||
@@ -254,6 +254,7 @@ export type LoggerEvent =
|
||||
| CompileErrorEvent
|
||||
| CompileDiagnosticEvent
|
||||
| CompileSkipEvent
|
||||
| CompileUnexpectedThrowEvent
|
||||
| PipelineErrorEvent
|
||||
| TimingEvent;
|
||||
|
||||
@@ -288,6 +289,11 @@ export type PipelineErrorEvent = {
|
||||
fnLoc: t.SourceLocation | null;
|
||||
data: string;
|
||||
};
|
||||
export type CompileUnexpectedThrowEvent = {
|
||||
kind: 'CompileUnexpectedThrow';
|
||||
fnLoc: t.SourceLocation | null;
|
||||
data: string;
|
||||
};
|
||||
export type TimingEvent = {
|
||||
kind: 'Timing';
|
||||
measurement: PerformanceMeasure;
|
||||
|
||||
@@ -13,7 +13,6 @@ import {CompilerError} from '../CompilerError';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
ReactiveFunction,
|
||||
assertConsistentIdentifiers,
|
||||
assertTerminalPredsExist,
|
||||
@@ -156,12 +155,10 @@ function runWithEnvironment(
|
||||
const log = (value: CompilerPipelineValue): void => {
|
||||
env.logger?.debugLogIRs?.(value);
|
||||
};
|
||||
const hir = lower(func, env).unwrap();
|
||||
const hir = lower(func, env);
|
||||
log({kind: 'hir', name: 'HIR', value: hir});
|
||||
|
||||
env.tryRecord(() => {
|
||||
pruneMaybeThrows(hir);
|
||||
});
|
||||
pruneMaybeThrows(hir);
|
||||
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
|
||||
validateContextVariableLValues(hir);
|
||||
@@ -172,43 +169,31 @@ function runWithEnvironment(
|
||||
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
|
||||
}
|
||||
|
||||
env.tryRecord(() => {
|
||||
inlineImmediatelyInvokedFunctionExpressions(hir);
|
||||
});
|
||||
inlineImmediatelyInvokedFunctionExpressions(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'InlineImmediatelyInvokedFunctionExpressions',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
mergeConsecutiveBlocks(hir);
|
||||
});
|
||||
mergeConsecutiveBlocks(hir);
|
||||
log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
|
||||
|
||||
assertConsistentIdentifiers(hir);
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
|
||||
env.tryRecord(() => {
|
||||
enterSSA(hir);
|
||||
});
|
||||
enterSSA(hir);
|
||||
log({kind: 'hir', name: 'SSA', value: hir});
|
||||
|
||||
env.tryRecord(() => {
|
||||
eliminateRedundantPhi(hir);
|
||||
});
|
||||
eliminateRedundantPhi(hir);
|
||||
log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
|
||||
|
||||
assertConsistentIdentifiers(hir);
|
||||
|
||||
env.tryRecord(() => {
|
||||
constantPropagation(hir);
|
||||
});
|
||||
constantPropagation(hir);
|
||||
log({kind: 'hir', name: 'ConstantPropagation', value: hir});
|
||||
|
||||
env.tryRecord(() => {
|
||||
inferTypes(hir);
|
||||
});
|
||||
inferTypes(hir);
|
||||
log({kind: 'hir', name: 'InferTypes', value: hir});
|
||||
|
||||
if (env.enableValidations) {
|
||||
@@ -220,34 +205,24 @@ function runWithEnvironment(
|
||||
}
|
||||
}
|
||||
|
||||
env.tryRecord(() => {
|
||||
optimizePropsMethodCalls(hir);
|
||||
});
|
||||
optimizePropsMethodCalls(hir);
|
||||
log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
|
||||
|
||||
env.tryRecord(() => {
|
||||
analyseFunctions(hir);
|
||||
});
|
||||
analyseFunctions(hir);
|
||||
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
|
||||
inferMutationAliasingEffects(hir);
|
||||
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
|
||||
|
||||
if (env.outputMode === 'ssr') {
|
||||
env.tryRecord(() => {
|
||||
optimizeForSSR(hir);
|
||||
});
|
||||
optimizeForSSR(hir);
|
||||
log({kind: 'hir', name: 'OptimizeForSSR', value: hir});
|
||||
}
|
||||
|
||||
// Note: Has to come after infer reference effects because "dead" code may still affect inference
|
||||
env.tryRecord(() => {
|
||||
deadCodeElimination(hir);
|
||||
});
|
||||
deadCodeElimination(hir);
|
||||
log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
|
||||
env.tryRecord(() => {
|
||||
pruneMaybeThrows(hir);
|
||||
});
|
||||
pruneMaybeThrows(hir);
|
||||
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
|
||||
inferMutationAliasingRanges(hir, {
|
||||
@@ -256,9 +231,7 @@ function runWithEnvironment(
|
||||
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
|
||||
if (env.enableValidations) {
|
||||
validateLocalsNotReassignedAfterRender(hir);
|
||||
}
|
||||
|
||||
if (env.enableValidations) {
|
||||
if (env.config.assertValidMutableRanges) {
|
||||
assertValidMutableRanges(hir);
|
||||
}
|
||||
@@ -295,9 +268,7 @@ function runWithEnvironment(
|
||||
validateNoFreezingKnownMutableFunctions(hir);
|
||||
}
|
||||
|
||||
env.tryRecord(() => {
|
||||
inferReactivePlaces(hir);
|
||||
});
|
||||
inferReactivePlaces(hir);
|
||||
log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
|
||||
|
||||
if (env.enableValidations) {
|
||||
@@ -310,9 +281,7 @@ function runWithEnvironment(
|
||||
}
|
||||
}
|
||||
|
||||
env.tryRecord(() => {
|
||||
rewriteInstructionKindsBasedOnReassignment(hir);
|
||||
});
|
||||
rewriteInstructionKindsBasedOnReassignment(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'RewriteInstructionKindsBasedOnReassignment',
|
||||
@@ -333,16 +302,11 @@ function runWithEnvironment(
|
||||
* if inferred memoization is enabled. This makes all later passes which
|
||||
* transform reactive-scope labeled instructions no-ops.
|
||||
*/
|
||||
env.tryRecord(() => {
|
||||
inferReactiveScopeVariables(hir);
|
||||
});
|
||||
inferReactiveScopeVariables(hir);
|
||||
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
|
||||
}
|
||||
|
||||
let fbtOperands: Set<IdentifierId> = new Set();
|
||||
env.tryRecord(() => {
|
||||
fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
|
||||
});
|
||||
const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'MemoizeFbtAndMacroOperandsInSameScope',
|
||||
@@ -350,15 +314,11 @@ function runWithEnvironment(
|
||||
});
|
||||
|
||||
if (env.config.enableJsxOutlining) {
|
||||
env.tryRecord(() => {
|
||||
outlineJSX(hir);
|
||||
});
|
||||
outlineJSX(hir);
|
||||
}
|
||||
|
||||
if (env.config.enableNameAnonymousFunctions) {
|
||||
env.tryRecord(() => {
|
||||
nameAnonymousFunctions(hir);
|
||||
});
|
||||
nameAnonymousFunctions(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'NameAnonymousFunctions',
|
||||
@@ -367,51 +327,39 @@ function runWithEnvironment(
|
||||
}
|
||||
|
||||
if (env.config.enableFunctionOutlining) {
|
||||
env.tryRecord(() => {
|
||||
outlineFunctions(hir, fbtOperands);
|
||||
});
|
||||
outlineFunctions(hir, fbtOperands);
|
||||
log({kind: 'hir', name: 'OutlineFunctions', value: hir});
|
||||
}
|
||||
|
||||
env.tryRecord(() => {
|
||||
alignMethodCallScopes(hir);
|
||||
});
|
||||
alignMethodCallScopes(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'AlignMethodCallScopes',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
alignObjectMethodScopes(hir);
|
||||
});
|
||||
alignObjectMethodScopes(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'AlignObjectMethodScopes',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
pruneUnusedLabelsHIR(hir);
|
||||
});
|
||||
pruneUnusedLabelsHIR(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'PruneUnusedLabelsHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
alignReactiveScopesToBlockScopesHIR(hir);
|
||||
});
|
||||
alignReactiveScopesToBlockScopesHIR(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'AlignReactiveScopesToBlockScopesHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
mergeOverlappingReactiveScopesHIR(hir);
|
||||
});
|
||||
mergeOverlappingReactiveScopesHIR(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'MergeOverlappingReactiveScopesHIR',
|
||||
@@ -419,9 +367,7 @@ function runWithEnvironment(
|
||||
});
|
||||
assertValidBlockNesting(hir);
|
||||
|
||||
env.tryRecord(() => {
|
||||
buildReactiveScopeTerminalsHIR(hir);
|
||||
});
|
||||
buildReactiveScopeTerminalsHIR(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'BuildReactiveScopeTerminalsHIR',
|
||||
@@ -430,18 +376,14 @@ function runWithEnvironment(
|
||||
|
||||
assertValidBlockNesting(hir);
|
||||
|
||||
env.tryRecord(() => {
|
||||
flattenReactiveLoopsHIR(hir);
|
||||
});
|
||||
flattenReactiveLoopsHIR(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'FlattenReactiveLoopsHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
flattenScopesWithHooksOrUseHIR(hir);
|
||||
});
|
||||
flattenScopesWithHooksOrUseHIR(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'FlattenScopesWithHooksOrUseHIR',
|
||||
@@ -449,19 +391,15 @@ function runWithEnvironment(
|
||||
});
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
assertTerminalPredsExist(hir);
|
||||
env.tryRecord(() => {
|
||||
propagateScopeDependenciesHIR(hir);
|
||||
});
|
||||
|
||||
propagateScopeDependenciesHIR(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'PropagateScopeDependenciesHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
let reactiveFunction!: ReactiveFunction;
|
||||
env.tryRecord(() => {
|
||||
reactiveFunction = buildReactiveFunction(hir);
|
||||
});
|
||||
const reactiveFunction = buildReactiveFunction(hir);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'BuildReactiveFunction',
|
||||
@@ -470,9 +408,7 @@ function runWithEnvironment(
|
||||
|
||||
assertWellFormedBreakTargets(reactiveFunction);
|
||||
|
||||
env.tryRecord(() => {
|
||||
pruneUnusedLabels(reactiveFunction);
|
||||
});
|
||||
pruneUnusedLabels(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedLabels',
|
||||
@@ -480,109 +416,84 @@ function runWithEnvironment(
|
||||
});
|
||||
assertScopeInstructionsWithinScopes(reactiveFunction);
|
||||
|
||||
env.tryRecord(() => {
|
||||
pruneNonEscapingScopes(reactiveFunction);
|
||||
});
|
||||
pruneNonEscapingScopes(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneNonEscapingScopes',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
pruneNonReactiveDependencies(reactiveFunction);
|
||||
});
|
||||
pruneNonReactiveDependencies(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneNonReactiveDependencies',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
pruneUnusedScopes(reactiveFunction);
|
||||
});
|
||||
pruneUnusedScopes(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedScopes',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
|
||||
});
|
||||
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'MergeReactiveScopesThatInvalidateTogether',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
pruneAlwaysInvalidatingScopes(reactiveFunction);
|
||||
});
|
||||
pruneAlwaysInvalidatingScopes(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneAlwaysInvalidatingScopes',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
propagateEarlyReturns(reactiveFunction);
|
||||
});
|
||||
propagateEarlyReturns(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PropagateEarlyReturns',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
pruneUnusedLValues(reactiveFunction);
|
||||
});
|
||||
pruneUnusedLValues(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedLValues',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
promoteUsedTemporaries(reactiveFunction);
|
||||
});
|
||||
promoteUsedTemporaries(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PromoteUsedTemporaries',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
extractScopeDeclarationsFromDestructuring(reactiveFunction);
|
||||
});
|
||||
extractScopeDeclarationsFromDestructuring(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'ExtractScopeDeclarationsFromDestructuring',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
stabilizeBlockIds(reactiveFunction);
|
||||
});
|
||||
stabilizeBlockIds(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'StabilizeBlockIds',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
let uniqueIdentifiers: Set<string> = new Set();
|
||||
env.tryRecord(() => {
|
||||
uniqueIdentifiers = renameVariables(reactiveFunction);
|
||||
});
|
||||
const uniqueIdentifiers = renameVariables(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'RenameVariables',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
env.tryRecord(() => {
|
||||
pruneHoistedContexts(reactiveFunction);
|
||||
});
|
||||
pruneHoistedContexts(reactiveFunction);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneHoistedContexts',
|
||||
|
||||
@@ -720,6 +720,20 @@ function tryCompileFunction(
|
||||
return {kind: 'error', error: result.unwrapErr()};
|
||||
}
|
||||
} catch (err) {
|
||||
/**
|
||||
* A pass incorrectly threw instead of recording the error.
|
||||
* Log for detection in development.
|
||||
*/
|
||||
if (
|
||||
err instanceof CompilerError &&
|
||||
err.details.every(detail => detail.category !== ErrorCategory.Invariant)
|
||||
) {
|
||||
programContext.logEvent({
|
||||
kind: 'CompileUnexpectedThrow',
|
||||
fnLoc: fn.node.loc ?? null,
|
||||
data: err.toString(),
|
||||
});
|
||||
}
|
||||
return {kind: 'error', error: err};
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -310,16 +310,13 @@ function traverseOptionalBlock(
|
||||
* - a optional base block with a separate nested optional-chain (e.g. a(c?.d)?.d)
|
||||
*/
|
||||
const testBlock = context.blocks.get(maybeTest.terminal.fallthrough)!;
|
||||
if (testBlock!.terminal.kind !== 'branch') {
|
||||
/**
|
||||
* Fallthrough of the inner optional should be a block with no
|
||||
* instructions, terminating with Test($<temporary written to from
|
||||
* StoreLocal>)
|
||||
*/
|
||||
CompilerError.throwTodo({
|
||||
reason: `Unexpected terminal kind \`${testBlock.terminal.kind}\` for optional fallthrough block`,
|
||||
loc: maybeTest.terminal.loc,
|
||||
});
|
||||
/**
|
||||
* Fallthrough of the inner optional should be a block with no
|
||||
* instructions, terminating with Test($<temporary written to from
|
||||
* StoreLocal>)
|
||||
*/
|
||||
if (testBlock.terminal.kind !== 'branch') {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Recurse into inner optional blocks to collect inner optional-chain
|
||||
|
||||
@@ -773,29 +773,6 @@ export class Environment {
|
||||
return this.#errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a callback in try/catch: if the callback throws a CompilerError
|
||||
* that is NOT an invariant, the error is recorded and execution continues.
|
||||
* Non-CompilerError exceptions and invariants are re-thrown.
|
||||
*/
|
||||
tryRecord(fn: () => void): void {
|
||||
try {
|
||||
fn();
|
||||
} catch (err) {
|
||||
if (err instanceof CompilerError) {
|
||||
// Check if any detail is an invariant — if so, re-throw
|
||||
for (const detail of err.details) {
|
||||
if (detail.category === ErrorCategory.Invariant) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
this.recordErrors(err);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isContextIdentifier(node: t.Identifier): boolean {
|
||||
return this.#contextIdentifiers.has(node);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
|
||||
import {Binding, NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import {CompilerError, ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerDiagnostic,
|
||||
CompilerErrorDetail,
|
||||
ErrorCategory,
|
||||
} from '../CompilerError';
|
||||
import {Environment} from './Environment';
|
||||
import {
|
||||
BasicBlock,
|
||||
@@ -110,7 +115,6 @@ export default class HIRBuilder {
|
||||
#bindings: Bindings;
|
||||
#env: Environment;
|
||||
#exceptionHandlerStack: Array<BlockId> = [];
|
||||
errors: CompilerError = new CompilerError();
|
||||
/**
|
||||
* Traversal context: counts the number of `fbt` tag parents
|
||||
* of the current babel node.
|
||||
@@ -148,6 +152,10 @@ export default class HIRBuilder {
|
||||
this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block');
|
||||
}
|
||||
|
||||
recordError(error: CompilerDiagnostic | CompilerErrorDetail): void {
|
||||
this.#env.recordError(error);
|
||||
}
|
||||
|
||||
currentBlockKind(): BlockKind {
|
||||
return this.#current.kind;
|
||||
}
|
||||
@@ -308,34 +316,28 @@ export default class HIRBuilder {
|
||||
|
||||
resolveBinding(node: t.Identifier): Identifier {
|
||||
if (node.name === 'fbt') {
|
||||
CompilerError.throwDiagnostic({
|
||||
category: ErrorCategory.Todo,
|
||||
reason: 'Support local variables named `fbt`',
|
||||
description:
|
||||
'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
message: 'Rename to avoid conflict with fbt plugin',
|
||||
loc: node.loc ?? GeneratedSource,
|
||||
},
|
||||
],
|
||||
});
|
||||
this.recordError(
|
||||
new CompilerErrorDetail({
|
||||
category: ErrorCategory.Todo,
|
||||
reason: 'Support local variables named `fbt`',
|
||||
description:
|
||||
'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',
|
||||
loc: node.loc ?? GeneratedSource,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (node.name === 'this') {
|
||||
CompilerError.throwDiagnostic({
|
||||
category: ErrorCategory.UnsupportedSyntax,
|
||||
reason: '`this` is not supported syntax',
|
||||
description:
|
||||
'React Compiler does not support compiling functions that use `this`',
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
message: '`this` was used here',
|
||||
loc: node.loc ?? GeneratedSource,
|
||||
},
|
||||
],
|
||||
});
|
||||
this.recordError(
|
||||
new CompilerErrorDetail({
|
||||
category: ErrorCategory.UnsupportedSyntax,
|
||||
reason: '`this` is not supported syntax',
|
||||
description:
|
||||
'React Compiler does not support compiling functions that use `this`',
|
||||
loc: node.loc ?? GeneratedSource,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const originalName = node.name;
|
||||
let name = originalName;
|
||||
@@ -381,12 +383,15 @@ export default class HIRBuilder {
|
||||
instr => instr.value.kind === 'FunctionExpression',
|
||||
)
|
||||
) {
|
||||
CompilerError.throwTodo({
|
||||
reason: `Support functions with unreachable code that may contain hoisted declarations`,
|
||||
loc: block.instructions[0]?.loc ?? block.terminal.loc,
|
||||
description: null,
|
||||
suggestions: null,
|
||||
});
|
||||
this.recordError(
|
||||
new CompilerErrorDetail({
|
||||
reason: `Support functions with unreachable code that may contain hoisted declarations`,
|
||||
loc: block.instructions[0]?.loc ?? block.terminal.loc,
|
||||
description: null,
|
||||
suggestions: null,
|
||||
category: ErrorCategory.Todo,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
ir.blocks = rpoBlocks;
|
||||
|
||||
@@ -293,7 +293,7 @@ function extractManualMemoizationArgs(
|
||||
instr: TInstruction<CallExpression> | TInstruction<MethodCall>,
|
||||
kind: 'useCallback' | 'useMemo',
|
||||
sidemap: IdentifierSidemap,
|
||||
errors: CompilerError,
|
||||
env: Environment,
|
||||
): {
|
||||
fnPlace: Place;
|
||||
depsList: Array<ManualMemoDependency> | null;
|
||||
@@ -303,7 +303,7 @@ function extractManualMemoizationArgs(
|
||||
Place | SpreadPattern | undefined
|
||||
>;
|
||||
if (fnPlace == null || fnPlace.kind !== 'Identifier') {
|
||||
errors.pushDiagnostic(
|
||||
env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason: `Expected a callback function to be passed to ${kind}`,
|
||||
@@ -335,7 +335,7 @@ function extractManualMemoizationArgs(
|
||||
? sidemap.maybeDepsLists.get(depsListPlace.identifier.id)
|
||||
: null;
|
||||
if (maybeDepsList == null) {
|
||||
errors.pushDiagnostic(
|
||||
env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason: `Expected the dependency list for ${kind} to be an array literal`,
|
||||
@@ -354,7 +354,7 @@ function extractManualMemoizationArgs(
|
||||
for (const dep of maybeDepsList.deps) {
|
||||
const maybeDep = sidemap.maybeDeps.get(dep.identifier.id);
|
||||
if (maybeDep == null) {
|
||||
errors.pushDiagnostic(
|
||||
env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||
@@ -389,7 +389,6 @@ function extractManualMemoizationArgs(
|
||||
* is only used for memoizing values and not for running arbitrary side effects.
|
||||
*/
|
||||
export function dropManualMemoization(func: HIRFunction): void {
|
||||
const errors = new CompilerError();
|
||||
const isValidationEnabled =
|
||||
func.env.config.validatePreserveExistingMemoizationGuarantees ||
|
||||
func.env.config.validateNoSetStateInRender ||
|
||||
@@ -436,7 +435,7 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
instr as TInstruction<CallExpression> | TInstruction<MethodCall>,
|
||||
manualMemo.kind,
|
||||
sidemap,
|
||||
errors,
|
||||
func.env,
|
||||
);
|
||||
|
||||
if (memoDetails == null) {
|
||||
@@ -464,7 +463,7 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
* is rare and likely sketchy.
|
||||
*/
|
||||
if (!sidemap.functions.has(fnPlace.identifier.id)) {
|
||||
errors.pushDiagnostic(
|
||||
func.env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason: `Expected the first argument to be an inline function expression`,
|
||||
@@ -549,10 +548,6 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
markInstructionIds(func.body);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.hasAnyErrors()) {
|
||||
func.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
function findOptionalPlaces(fn: HIRFunction): Set<IdentifierId> {
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
Place,
|
||||
isPrimitiveType,
|
||||
} from '../HIR/HIR';
|
||||
import {Environment} from '../HIR/Environment';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionValueOperand,
|
||||
@@ -107,7 +108,7 @@ export function inferMutationAliasingRanges(
|
||||
|
||||
let index = 0;
|
||||
|
||||
const errors = new CompilerError();
|
||||
const shouldRecordErrors = !isFunctionExpression && fn.env.enableValidations;
|
||||
|
||||
for (const param of [...fn.params, ...fn.context, fn.returns]) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
@@ -200,7 +201,9 @@ export function inferMutationAliasingRanges(
|
||||
effect.kind === 'MutateGlobal' ||
|
||||
effect.kind === 'Impure'
|
||||
) {
|
||||
errors.pushDiagnostic(effect.error);
|
||||
if (shouldRecordErrors) {
|
||||
fn.env.recordError(effect.error);
|
||||
}
|
||||
functionEffects.push(effect);
|
||||
} else if (effect.kind === 'Render') {
|
||||
renders.push({index: index++, place: effect.place});
|
||||
@@ -245,11 +248,15 @@ export function inferMutationAliasingRanges(
|
||||
mutation.kind,
|
||||
mutation.place.loc,
|
||||
mutation.reason,
|
||||
errors,
|
||||
shouldRecordErrors ? fn.env : null,
|
||||
);
|
||||
}
|
||||
for (const render of renders) {
|
||||
state.render(render.index, render.place.identifier, errors);
|
||||
state.render(
|
||||
render.index,
|
||||
render.place.identifier,
|
||||
shouldRecordErrors ? fn.env : null,
|
||||
);
|
||||
}
|
||||
for (const param of [...fn.context, ...fn.params]) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
@@ -498,7 +505,6 @@ export function inferMutationAliasingRanges(
|
||||
* would be transitively mutated needs a capture relationship.
|
||||
*/
|
||||
const tracked: Array<Place> = [];
|
||||
const ignoredErrors = new CompilerError();
|
||||
for (const param of [...fn.params, ...fn.context, fn.returns]) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
tracked.push(place);
|
||||
@@ -513,7 +519,7 @@ export function inferMutationAliasingRanges(
|
||||
MutationKind.Conditional,
|
||||
into.loc,
|
||||
null,
|
||||
ignoredErrors,
|
||||
null,
|
||||
);
|
||||
for (const from of tracked) {
|
||||
if (
|
||||
@@ -547,23 +553,17 @@ export function inferMutationAliasingRanges(
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
errors.hasAnyErrors() &&
|
||||
!isFunctionExpression &&
|
||||
fn.env.enableValidations
|
||||
) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
return functionEffects;
|
||||
}
|
||||
|
||||
function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
|
||||
function appendFunctionErrors(env: Environment | null, fn: HIRFunction): void {
|
||||
if (env == null) return;
|
||||
for (const effect of fn.aliasingEffects ?? []) {
|
||||
switch (effect.kind) {
|
||||
case 'Impure':
|
||||
case 'MutateFrozen':
|
||||
case 'MutateGlobal': {
|
||||
errors.pushDiagnostic(effect.error);
|
||||
env.recordError(effect.error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -664,7 +664,7 @@ class AliasingState {
|
||||
}
|
||||
}
|
||||
|
||||
render(index: number, start: Identifier, errors: CompilerError): void {
|
||||
render(index: number, start: Identifier, env: Environment | null): void {
|
||||
const seen = new Set<Identifier>();
|
||||
const queue: Array<Identifier> = [start];
|
||||
while (queue.length !== 0) {
|
||||
@@ -678,7 +678,7 @@ class AliasingState {
|
||||
continue;
|
||||
}
|
||||
if (node.value.kind === 'Function') {
|
||||
appendFunctionErrors(errors, node.value.function);
|
||||
appendFunctionErrors(env, node.value.function);
|
||||
}
|
||||
for (const [alias, when] of node.createdFrom) {
|
||||
if (when >= index) {
|
||||
@@ -710,7 +710,7 @@ class AliasingState {
|
||||
startKind: MutationKind,
|
||||
loc: SourceLocation,
|
||||
reason: MutationReason | null,
|
||||
errors: CompilerError,
|
||||
env: Environment | null,
|
||||
): void {
|
||||
const seen = new Map<Identifier, MutationKind>();
|
||||
const queue: Array<{
|
||||
@@ -742,7 +742,7 @@ class AliasingState {
|
||||
node.transitive == null &&
|
||||
node.local == null
|
||||
) {
|
||||
appendFunctionErrors(errors, node.value.function);
|
||||
appendFunctionErrors(env, node.value.function);
|
||||
}
|
||||
if (transitive) {
|
||||
if (node.transitive == null || node.transitive.kind < kind) {
|
||||
|
||||
@@ -1007,11 +1007,10 @@ class Driver {
|
||||
const test = this.visitValueBlock(testBlockId, loc);
|
||||
const testBlock = this.cx.ir.blocks.get(test.block)!;
|
||||
if (testBlock.terminal.kind !== 'branch') {
|
||||
CompilerError.throwTodo({
|
||||
reason: `Unexpected terminal kind \`${testBlock.terminal.kind}\` for ${terminalKind} test block`,
|
||||
description: null,
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected a branch terminal for ${terminalKind} test block`,
|
||||
description: `Got \`${testBlock.terminal.kind}\``,
|
||||
loc: testBlock.terminal.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -13,7 +13,11 @@ import {
|
||||
pruneUnusedLabels,
|
||||
renameVariables,
|
||||
} from '.';
|
||||
import {CompilerError, ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerErrorDetail,
|
||||
ErrorCategory,
|
||||
} from '../CompilerError';
|
||||
import {Environment, ExternalFunction} from '../HIR';
|
||||
import {
|
||||
ArrayPattern,
|
||||
@@ -347,10 +351,6 @@ function codegenReactiveFunction(
|
||||
}
|
||||
}
|
||||
|
||||
if (cx.errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(cx.errors);
|
||||
}
|
||||
|
||||
const countMemoBlockVisitor = new CountMemoBlockVisitor(fn.env);
|
||||
visitReactiveFunction(fn, countMemoBlockVisitor, undefined);
|
||||
|
||||
@@ -420,7 +420,6 @@ class Context {
|
||||
*/
|
||||
#declarations: Set<DeclarationId> = new Set();
|
||||
temp: Temporaries;
|
||||
errors: CompilerError = new CompilerError();
|
||||
objectMethods: Map<IdentifierId, ObjectMethod> = new Map();
|
||||
uniqueIdentifiers: Set<string>;
|
||||
fbtOperands: Set<IdentifierId>;
|
||||
@@ -439,6 +438,10 @@ class Context {
|
||||
this.fbtOperands = fbtOperands;
|
||||
this.temp = temporaries !== null ? new Map(temporaries) : new Map();
|
||||
}
|
||||
|
||||
recordError(error: CompilerErrorDetail): void {
|
||||
this.env.recordError(error);
|
||||
}
|
||||
get nextCacheIndex(): number {
|
||||
return this.#nextCacheIndex++;
|
||||
}
|
||||
@@ -775,12 +778,15 @@ function codegenTerminal(
|
||||
loc: terminal.init.loc,
|
||||
});
|
||||
if (terminal.init.instructions.length !== 2) {
|
||||
CompilerError.throwTodo({
|
||||
reason: 'Support non-trivial for..in inits',
|
||||
description: null,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
cx.recordError(
|
||||
new CompilerErrorDetail({
|
||||
reason: 'Support non-trivial for..in inits',
|
||||
category: ErrorCategory.Todo,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
return t.emptyStatement();
|
||||
}
|
||||
const iterableCollection = terminal.init.instructions[0];
|
||||
const iterableItem = terminal.init.instructions[1];
|
||||
@@ -795,12 +801,15 @@ function codegenTerminal(
|
||||
break;
|
||||
}
|
||||
case 'StoreContext': {
|
||||
CompilerError.throwTodo({
|
||||
reason: 'Support non-trivial for..in inits',
|
||||
description: null,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
cx.recordError(
|
||||
new CompilerErrorDetail({
|
||||
reason: 'Support non-trivial for..in inits',
|
||||
category: ErrorCategory.Todo,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
return t.emptyStatement();
|
||||
}
|
||||
default:
|
||||
CompilerError.invariant(false, {
|
||||
@@ -870,12 +879,15 @@ function codegenTerminal(
|
||||
loc: terminal.test.loc,
|
||||
});
|
||||
if (terminal.test.instructions.length !== 2) {
|
||||
CompilerError.throwTodo({
|
||||
reason: 'Support non-trivial for..of inits',
|
||||
description: null,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
cx.recordError(
|
||||
new CompilerErrorDetail({
|
||||
reason: 'Support non-trivial for..of inits',
|
||||
category: ErrorCategory.Todo,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
return t.emptyStatement();
|
||||
}
|
||||
const iterableItem = terminal.test.instructions[1];
|
||||
let lval: t.LVal;
|
||||
@@ -889,12 +901,15 @@ function codegenTerminal(
|
||||
break;
|
||||
}
|
||||
case 'StoreContext': {
|
||||
CompilerError.throwTodo({
|
||||
reason: 'Support non-trivial for..of inits',
|
||||
description: null,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
cx.recordError(
|
||||
new CompilerErrorDetail({
|
||||
reason: 'Support non-trivial for..of inits',
|
||||
category: ErrorCategory.Todo,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
return t.emptyStatement();
|
||||
}
|
||||
default:
|
||||
CompilerError.invariant(false, {
|
||||
@@ -1953,22 +1968,26 @@ function codegenInstructionValue(
|
||||
} else {
|
||||
if (t.isVariableDeclaration(stmt)) {
|
||||
const declarator = stmt.declarations[0];
|
||||
cx.errors.push({
|
||||
reason: `(CodegenReactiveFunction::codegenInstructionValue) Cannot declare variables in a value block, tried to declare '${
|
||||
(declarator.id as t.Identifier).name
|
||||
}'`,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: declarator.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
cx.recordError(
|
||||
new CompilerErrorDetail({
|
||||
reason: `(CodegenReactiveFunction::codegenInstructionValue) Cannot declare variables in a value block, tried to declare '${
|
||||
(declarator.id as t.Identifier).name
|
||||
}'`,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: declarator.loc ?? null,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
return t.stringLiteral(`TODO handle ${declarator.id}`);
|
||||
} else {
|
||||
cx.errors.push({
|
||||
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: stmt.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
cx.recordError(
|
||||
new CompilerErrorDetail({
|
||||
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: stmt.loc ?? null,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
return t.stringLiteral(`TODO handle ${stmt.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,6 @@ export function validateExhaustiveDependencies(fn: HIRFunction): void {
|
||||
loc: place.loc,
|
||||
});
|
||||
}
|
||||
const error = new CompilerError();
|
||||
let startMemo: StartMemoize | null = null;
|
||||
|
||||
function onStartMemoize(
|
||||
@@ -143,7 +142,7 @@ export function validateExhaustiveDependencies(fn: HIRFunction): void {
|
||||
'all',
|
||||
);
|
||||
if (diagnostic != null) {
|
||||
error.pushDiagnostic(diagnostic);
|
||||
fn.env.recordError(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,15 +207,12 @@ export function validateExhaustiveDependencies(fn: HIRFunction): void {
|
||||
effectReportMode,
|
||||
);
|
||||
if (diagnostic != null) {
|
||||
error.pushDiagnostic(diagnostic);
|
||||
fn.env.recordError(diagnostic);
|
||||
}
|
||||
},
|
||||
},
|
||||
false, // isFunctionExpression
|
||||
);
|
||||
if (error.hasAnyErrors()) {
|
||||
fn.env.recordErrors(error);
|
||||
}
|
||||
}
|
||||
|
||||
function validateDependencies(
|
||||
|
||||
@@ -6,13 +6,9 @@
|
||||
*/
|
||||
|
||||
import * as t from '@babel/types';
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerErrorDetail,
|
||||
ErrorCategory,
|
||||
} from '../CompilerError';
|
||||
import {CompilerErrorDetail, ErrorCategory} from '../CompilerError';
|
||||
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
|
||||
import {isHookName} from '../HIR/Environment';
|
||||
import {Environment, isHookName} from '../HIR/Environment';
|
||||
import {
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
@@ -90,15 +86,14 @@ function joinKinds(a: Kind, b: Kind): Kind {
|
||||
export function validateHooksUsage(fn: HIRFunction): void {
|
||||
const unconditionalBlocks = computeUnconditionalBlocks(fn);
|
||||
|
||||
const errors = new CompilerError();
|
||||
const errorsByPlace = new Map<t.SourceLocation, CompilerErrorDetail>();
|
||||
|
||||
function recordError(
|
||||
function trackError(
|
||||
loc: SourceLocation,
|
||||
errorDetail: CompilerErrorDetail,
|
||||
): void {
|
||||
if (typeof loc === 'symbol') {
|
||||
errors.pushErrorDetail(errorDetail);
|
||||
fn.env.recordError(errorDetail);
|
||||
} else {
|
||||
errorsByPlace.set(loc, errorDetail);
|
||||
}
|
||||
@@ -118,7 +113,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
|
||||
* If that same place is also used as a conditional call, upgrade the error to a conditonal hook error
|
||||
*/
|
||||
if (previousError === undefined || previousError.reason !== reason) {
|
||||
recordError(
|
||||
trackError(
|
||||
place.loc,
|
||||
new CompilerErrorDetail({
|
||||
category: ErrorCategory.Hooks,
|
||||
@@ -134,7 +129,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
|
||||
const previousError =
|
||||
typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined;
|
||||
if (previousError === undefined) {
|
||||
recordError(
|
||||
trackError(
|
||||
place.loc,
|
||||
new CompilerErrorDetail({
|
||||
category: ErrorCategory.Hooks,
|
||||
@@ -151,7 +146,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
|
||||
const previousError =
|
||||
typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined;
|
||||
if (previousError === undefined) {
|
||||
recordError(
|
||||
trackError(
|
||||
place.loc,
|
||||
new CompilerErrorDetail({
|
||||
category: ErrorCategory.Hooks,
|
||||
@@ -396,7 +391,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
|
||||
}
|
||||
case 'ObjectMethod':
|
||||
case 'FunctionExpression': {
|
||||
visitFunctionExpression(errors, instr.value.loweredFunc.func);
|
||||
visitFunctionExpression(fn.env, instr.value.loweredFunc.func);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -421,20 +416,17 @@ export function validateHooksUsage(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
for (const [, error] of errorsByPlace) {
|
||||
errors.pushErrorDetail(error);
|
||||
}
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
fn.env.recordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
|
||||
function visitFunctionExpression(env: Environment, fn: HIRFunction): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
switch (instr.value.kind) {
|
||||
case 'ObjectMethod':
|
||||
case 'FunctionExpression': {
|
||||
visitFunctionExpression(errors, instr.value.loweredFunc.func);
|
||||
visitFunctionExpression(env, instr.value.loweredFunc.func);
|
||||
break;
|
||||
}
|
||||
case 'MethodCall':
|
||||
@@ -445,7 +437,7 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
|
||||
: instr.value.property;
|
||||
const hookKind = getHookKind(fn.env, callee.identifier);
|
||||
if (hookKind != null) {
|
||||
errors.pushErrorDetail(
|
||||
env.recordError(
|
||||
new CompilerErrorDetail({
|
||||
category: ErrorCategory.Hooks,
|
||||
reason:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError, CompilerErrorDetail, EnvironmentConfig} from '..';
|
||||
import {CompilerErrorDetail, EnvironmentConfig} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {HIRFunction, IdentifierId} from '../HIR';
|
||||
import {DEFAULT_GLOBALS} from '../HIR/Globals';
|
||||
@@ -20,7 +20,6 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
|
||||
return ALLOW_LIST.has(name);
|
||||
};
|
||||
|
||||
const errors = new CompilerError();
|
||||
const capitalLoadGlobals = new Map<IdentifierId, string>();
|
||||
const capitalizedProperties = new Map<IdentifierId, string>();
|
||||
const reason =
|
||||
@@ -72,20 +71,19 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
|
||||
const propertyIdentifier = value.property.identifier.id;
|
||||
const propertyName = capitalizedProperties.get(propertyIdentifier);
|
||||
if (propertyName != null) {
|
||||
errors.push({
|
||||
category: ErrorCategory.CapitalizedCalls,
|
||||
reason,
|
||||
description: `${propertyName} may be a component`,
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
fn.env.recordError(
|
||||
new CompilerErrorDetail({
|
||||
category: ErrorCategory.CapitalizedCalls,
|
||||
reason,
|
||||
description: `${propertyName} may be a component`,
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import {CompilerError, SourceLocation} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {CompilerErrorDetail, ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
ArrayExpression,
|
||||
BlockId,
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {Environment} from '../HIR/Environment';
|
||||
|
||||
/**
|
||||
* Validates that useEffect is not used for derived computations which could/should
|
||||
@@ -49,8 +50,6 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
const functions: Map<IdentifierId, FunctionExpression> = new Map();
|
||||
const locals: Map<IdentifierId, IdentifierId> = new Map();
|
||||
|
||||
const errors = new CompilerError();
|
||||
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const instr of block.instructions) {
|
||||
const {lvalue, value} = instr;
|
||||
@@ -90,22 +89,19 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
validateEffect(
|
||||
effectFunction.loweredFunc.func,
|
||||
dependencies,
|
||||
errors,
|
||||
fn.env,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const detail of errors.details) {
|
||||
fn.env.recordError(detail);
|
||||
}
|
||||
}
|
||||
|
||||
function validateEffect(
|
||||
effectFunction: HIRFunction,
|
||||
effectDeps: Array<IdentifierId>,
|
||||
errors: CompilerError,
|
||||
env: Environment,
|
||||
): void {
|
||||
for (const operand of effectFunction.context) {
|
||||
if (isSetStateType(operand.identifier)) {
|
||||
@@ -219,13 +215,15 @@ function validateEffect(
|
||||
}
|
||||
|
||||
for (const loc of setStateLocations) {
|
||||
errors.push({
|
||||
category: ErrorCategory.EffectDerivationsOfState,
|
||||
reason:
|
||||
'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)',
|
||||
description: null,
|
||||
loc,
|
||||
suggestions: null,
|
||||
});
|
||||
env.recordError(
|
||||
new CompilerErrorDetail({
|
||||
category: ErrorCategory.EffectDerivationsOfState,
|
||||
reason:
|
||||
'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)',
|
||||
description: null,
|
||||
loc,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerDiagnostic, CompilerError, Effect} from '..';
|
||||
import {CompilerDiagnostic, Effect} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
HIRFunction,
|
||||
@@ -43,7 +43,6 @@ import {AliasingEffect} from '../Inference/AliasingEffects';
|
||||
* that are passed where a frozen value is expected and rejects them.
|
||||
*/
|
||||
export function validateNoFreezingKnownMutableFunctions(fn: HIRFunction): void {
|
||||
const errors = new CompilerError();
|
||||
const contextMutationEffects: Map<
|
||||
IdentifierId,
|
||||
Extract<AliasingEffect, {kind: 'Mutate'} | {kind: 'MutateTransitive'}>
|
||||
@@ -60,7 +59,7 @@ export function validateNoFreezingKnownMutableFunctions(fn: HIRFunction): void {
|
||||
place.identifier.name.kind === 'named'
|
||||
? `\`${place.identifier.name.value}\``
|
||||
: 'a local variable';
|
||||
errors.pushDiagnostic(
|
||||
fn.env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Immutability,
|
||||
reason: 'Cannot modify local variables after render completes',
|
||||
@@ -159,7 +158,4 @@ export function validateNoFreezingKnownMutableFunctions(fn: HIRFunction): void {
|
||||
visitOperand(operand);
|
||||
}
|
||||
}
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerDiagnostic, CompilerError} from '..';
|
||||
import {CompilerDiagnostic} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {HIRFunction} from '../HIR';
|
||||
import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffects';
|
||||
@@ -20,7 +20,6 @@ import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffect
|
||||
* and use it here.
|
||||
*/
|
||||
export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
|
||||
const errors = new CompilerError();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
const value = instr.value;
|
||||
@@ -32,7 +31,7 @@ export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
|
||||
callee.identifier.type,
|
||||
);
|
||||
if (signature != null && signature.impure === true) {
|
||||
errors.pushDiagnostic(
|
||||
fn.env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Purity,
|
||||
reason: 'Cannot call impure function during render',
|
||||
@@ -52,7 +51,4 @@ export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +124,8 @@ export function validateNoRefAccessInRender(fn: HIRFunction): void {
|
||||
collectTemporariesSidemap(fn, env);
|
||||
const errors = new CompilerError();
|
||||
validateNoRefAccessInRenderImpl(fn, env, errors);
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
for (const detail of errors.details) {
|
||||
fn.env.recordError(detail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ export function validateNoSetStateInRender(fn: HIRFunction): void {
|
||||
fn,
|
||||
unconditionalSetStateFunctions,
|
||||
);
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
for (const detail of errors.details) {
|
||||
fn.env.recordError(detail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
ScopeId,
|
||||
SourceLocation,
|
||||
} from '../HIR';
|
||||
import {Environment} from '../HIR/Environment';
|
||||
import {printIdentifier, printManualMemoDependency} from '../HIR/PrintHIR';
|
||||
import {
|
||||
eachInstructionValueLValue,
|
||||
@@ -48,13 +49,10 @@ import {getOrInsertDefault} from '../Utils/utils';
|
||||
*/
|
||||
export function validatePreservedManualMemoization(fn: ReactiveFunction): void {
|
||||
const state = {
|
||||
errors: new CompilerError(),
|
||||
env: fn.env,
|
||||
manualMemoState: null,
|
||||
};
|
||||
visitReactiveFunction(fn, new Visitor(), state);
|
||||
for (const detail of state.errors.details) {
|
||||
fn.env.recordError(detail);
|
||||
}
|
||||
}
|
||||
|
||||
const DEBUG = false;
|
||||
@@ -112,7 +110,7 @@ type ManualMemoBlockState = {
|
||||
};
|
||||
|
||||
type VisitorState = {
|
||||
errors: CompilerError;
|
||||
env: Environment;
|
||||
manualMemoState: ManualMemoBlockState | null;
|
||||
};
|
||||
|
||||
@@ -232,7 +230,7 @@ function validateInferredDep(
|
||||
temporaries: Map<IdentifierId, ManualMemoDependency>,
|
||||
declsWithinMemoBlock: Set<DeclarationId>,
|
||||
validDepsInMemoBlock: Array<ManualMemoDependency>,
|
||||
errorState: CompilerError,
|
||||
env: Environment,
|
||||
memoLocation: SourceLocation,
|
||||
): void {
|
||||
let normalizedDep: ManualMemoDependency;
|
||||
@@ -282,7 +280,7 @@ function validateInferredDep(
|
||||
errorDiagnostic = merge(errorDiagnostic ?? compareResult, compareResult);
|
||||
}
|
||||
}
|
||||
errorState.pushDiagnostic(
|
||||
env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.PreserveManualMemo,
|
||||
reason: 'Existing memoization could not be preserved',
|
||||
@@ -428,7 +426,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
this.temporaries,
|
||||
state.manualMemoState.decls,
|
||||
state.manualMemoState.depsFromSource,
|
||||
state.errors,
|
||||
state.env,
|
||||
state.manualMemoState.loc,
|
||||
);
|
||||
}
|
||||
@@ -531,7 +529,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
!this.scopes.has(identifier.scope.id) &&
|
||||
!this.prunedScopes.has(identifier.scope.id)
|
||||
) {
|
||||
state.errors.pushDiagnostic(
|
||||
state.env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.PreserveManualMemo,
|
||||
reason: 'Existing memoization could not be preserved',
|
||||
@@ -577,7 +575,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
|
||||
for (const identifier of decls) {
|
||||
if (isUnmemoized(identifier, this.scopes)) {
|
||||
state.errors.pushDiagnostic(
|
||||
state.env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.PreserveManualMemo,
|
||||
reason: 'Existing memoization could not be preserved',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import {CompilerDiagnostic, CompilerError, ErrorCategory} from '..';
|
||||
import {CompilerDiagnostic, ErrorCategory} from '..';
|
||||
import {CodegenFunction} from '../ReactiveScopes';
|
||||
import {Environment} from '../HIR/Environment';
|
||||
|
||||
@@ -125,8 +125,6 @@ export function validateSourceLocations(
|
||||
generatedAst: CodegenFunction,
|
||||
env: Environment,
|
||||
): void {
|
||||
const errors = new CompilerError();
|
||||
|
||||
/*
|
||||
* Step 1: Collect important locations from the original source
|
||||
* Note: Multiple node types can share the same location (e.g. VariableDeclarator and Identifier)
|
||||
@@ -241,7 +239,7 @@ export function validateSourceLocations(
|
||||
loc: t.SourceLocation,
|
||||
nodeType: string,
|
||||
): void => {
|
||||
errors.pushDiagnostic(
|
||||
env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Todo,
|
||||
reason: 'Important source location missing in generated code',
|
||||
@@ -261,7 +259,7 @@ export function validateSourceLocations(
|
||||
expectedType: string,
|
||||
actualTypes: Set<string>,
|
||||
): void => {
|
||||
errors.pushDiagnostic(
|
||||
env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Todo,
|
||||
reason:
|
||||
@@ -309,8 +307,4 @@ export function validateSourceLocations(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const detail of errors.details) {
|
||||
env.recordError(detail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ import {
|
||||
IdentifierId,
|
||||
SourceLocation,
|
||||
} from '../HIR';
|
||||
import {Environment} from '../HIR/Environment';
|
||||
import {
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
|
||||
export function validateUseMemo(fn: HIRFunction): void {
|
||||
const errors = new CompilerError();
|
||||
const voidMemoErrors = new CompilerError();
|
||||
const useMemos = new Set<IdentifierId>();
|
||||
const react = new Set<IdentifierId>();
|
||||
@@ -90,7 +90,7 @@ export function validateUseMemo(fn: HIRFunction): void {
|
||||
firstParam.kind === 'Identifier'
|
||||
? firstParam.loc
|
||||
: firstParam.place.loc;
|
||||
errors.pushDiagnostic(
|
||||
fn.env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason: 'useMemo() callbacks may not accept parameters',
|
||||
@@ -106,7 +106,7 @@ export function validateUseMemo(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
|
||||
errors.pushDiagnostic(
|
||||
fn.env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason:
|
||||
@@ -122,7 +122,7 @@ export function validateUseMemo(fn: HIRFunction): void {
|
||||
);
|
||||
}
|
||||
|
||||
validateNoContextVariableAssignment(body.loweredFunc.func, errors);
|
||||
validateNoContextVariableAssignment(body.loweredFunc.func, fn.env);
|
||||
|
||||
if (fn.env.config.validateNoVoidUseMemo) {
|
||||
if (!hasNonVoidReturn(body.loweredFunc.func)) {
|
||||
@@ -176,14 +176,11 @@ export function validateUseMemo(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
fn.env.logErrors(voidMemoErrors.asResult());
|
||||
if (errors.hasAnyErrors()) {
|
||||
fn.env.recordErrors(errors);
|
||||
}
|
||||
}
|
||||
|
||||
function validateNoContextVariableAssignment(
|
||||
fn: HIRFunction,
|
||||
errors: CompilerError,
|
||||
env: Environment,
|
||||
): void {
|
||||
const context = new Set(fn.context.map(place => place.identifier.id));
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
@@ -192,7 +189,7 @@ function validateNoContextVariableAssignment(
|
||||
switch (value.kind) {
|
||||
case 'StoreContext': {
|
||||
if (context.has(value.lvalue.place.identifier.id)) {
|
||||
errors.pushDiagnostic(
|
||||
env.recordError(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason:
|
||||
|
||||
@@ -24,18 +24,9 @@ function useThing(fn) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Compilation Skipped: `this` is not supported syntax
|
||||
Error: Expected a non-reserved identifier name
|
||||
|
||||
React Compiler does not support compiling functions that use `this`.
|
||||
|
||||
error.reserved-words.ts:8:28
|
||||
6 |
|
||||
7 | if (ref.current === null) {
|
||||
> 8 | ref.current = function (this: unknown, ...args) {
|
||||
| ^^^^^^^^^^^^^ `this` was used here
|
||||
9 | return fnRef.current.call(this, ...args);
|
||||
10 | };
|
||||
11 | }
|
||||
`this` is a reserved word in JavaScript and cannot be used as an identifier name.
|
||||
```
|
||||
|
||||
|
||||
@@ -17,16 +17,17 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Todo: (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern
|
||||
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
|
||||
|
||||
error._todo.computed-lval-in-destructure.ts:3:9
|
||||
1 | function Component(props) {
|
||||
2 | const computedKey = props.key;
|
||||
> 3 | const {[computedKey]: x} = props.val;
|
||||
| ^^^^^^^^^^^^^^^^ (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern
|
||||
<unknown> x$8.
|
||||
|
||||
error._todo.computed-lval-in-destructure.ts:5:9
|
||||
3 | const {[computedKey]: x} = props.val;
|
||||
4 |
|
||||
5 | return x;
|
||||
> 5 | return x;
|
||||
| ^ this is uninitialized
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
|
||||
Todo: [hoisting] EnterSSA: Expected identifier to be defined before being used
|
||||
|
||||
<unknown> x$1.
|
||||
Identifier x$1 is undefined.
|
||||
|
||||
error.dont-hoist-inline-reference.ts:3:21
|
||||
error.dont-hoist-inline-reference.ts:3:2
|
||||
1 | import {identity} from 'shared-runtime';
|
||||
2 | function useInvalid() {
|
||||
> 3 | const x = identity(x);
|
||||
| ^ this is uninitialized
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ [hoisting] EnterSSA: Expected identifier to be defined before being used
|
||||
4 | return x;
|
||||
5 | }
|
||||
6 |
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender
|
||||
/**
|
||||
* This fixture tests fault tolerance: the compiler should report
|
||||
* multiple independent errors rather than stopping at the first one.
|
||||
*
|
||||
* Error 1: Ref access during render (ref.current)
|
||||
* Error 2: Mutation of frozen value (props)
|
||||
*/
|
||||
function Component(props) {
|
||||
const ref = useRef(null);
|
||||
|
||||
// Error: reading ref during render
|
||||
const value = ref.current;
|
||||
|
||||
// Error: mutating frozen value (props, which is frozen after hook call)
|
||||
props.items = [];
|
||||
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Error: This value cannot be modified
|
||||
|
||||
Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
|
||||
|
||||
error.fault-tolerance-reports-multiple-errors.ts:16:2
|
||||
14 |
|
||||
15 | // Error: mutating frozen value (props, which is frozen after hook call)
|
||||
> 16 | props.items = [];
|
||||
| ^^^^^ value cannot be modified
|
||||
17 |
|
||||
18 | return <div>{value}</div>;
|
||||
19 | }
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.fault-tolerance-reports-multiple-errors.ts:13:16
|
||||
11 |
|
||||
12 | // Error: reading ref during render
|
||||
> 13 | const value = ref.current;
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
14 |
|
||||
15 | // Error: mutating frozen value (props, which is frozen after hook call)
|
||||
16 | props.items = [];
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// @validateRefAccessDuringRender
|
||||
/**
|
||||
* This fixture tests fault tolerance: the compiler should report
|
||||
* multiple independent errors rather than stopping at the first one.
|
||||
*
|
||||
* Error 1: Ref access during render (ref.current)
|
||||
* Error 2: Mutation of frozen value (props)
|
||||
*/
|
||||
function Component(props) {
|
||||
const ref = useRef(null);
|
||||
|
||||
// Error: reading ref during render
|
||||
const value = ref.current;
|
||||
|
||||
// Error: mutating frozen value (props, which is frozen after hook call)
|
||||
props.items = [];
|
||||
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
@@ -18,15 +18,18 @@ function Component() {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Todo: Support functions with unreachable code that may contain hoisted declarations
|
||||
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
|
||||
|
||||
error.todo-hoisted-function-in-unreachable-code.ts:6:2
|
||||
<unknown> Foo$0.
|
||||
|
||||
error.todo-hoisted-function-in-unreachable-code.ts:3:10
|
||||
1 | // @compilationMode:"infer"
|
||||
2 | function Component() {
|
||||
> 3 | return <Foo />;
|
||||
| ^^^ this is uninitialized
|
||||
4 |
|
||||
5 | // This is unreachable from a control-flow perspective, but it gets hoisted
|
||||
> 6 | function Foo() {}
|
||||
| ^^^^^^^^^^^^^^^^^ Support functions with unreachable code that may contain hoisted declarations
|
||||
7 | }
|
||||
8 |
|
||||
6 | function Foo() {}
|
||||
```
|
||||
|
||||
|
||||
@@ -79,43 +79,11 @@ let moduleLocal = false;
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 10 errors:
|
||||
Found 1 error:
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration
|
||||
Invariant: Expected a variable declaration
|
||||
|
||||
error.todo-kitchensink.ts:3:2
|
||||
1 | function foo([a, b], {c, d, e = 'e'}, f = 'f', ...args) {
|
||||
2 | let i = 0;
|
||||
> 3 | var x = [];
|
||||
| ^^^^^^^^^^^ (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration
|
||||
4 |
|
||||
5 | class Bar {
|
||||
6 | #secretSauce = 42;
|
||||
|
||||
Compilation Skipped: Inline `class` declarations are not supported
|
||||
|
||||
Move class declarations outside of components/hooks.
|
||||
|
||||
error.todo-kitchensink.ts:5:2
|
||||
3 | var x = [];
|
||||
4 |
|
||||
> 5 | class Bar {
|
||||
| ^^^^^^^^^^^
|
||||
> 6 | #secretSauce = 42;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 7 | constructor() {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 8 | console.log(this.#secretSauce);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 9 | }
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 10 | }
|
||||
| ^^^^ Inline `class` declarations are not supported
|
||||
11 |
|
||||
12 | const g = {b() {}, c: () => {}};
|
||||
13 | const {z, aa = 'aa'} = useCustom();
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
|
||||
Got ExpressionStatement.
|
||||
|
||||
error.todo-kitchensink.ts:20:2
|
||||
18 | const j = function bar([quz, qux], ...args) {};
|
||||
@@ -125,103 +93,10 @@ error.todo-kitchensink.ts:20:2
|
||||
> 21 | x.push(i);
|
||||
| ^^^^^^^^^^^^^^
|
||||
> 22 | }
|
||||
| ^^^^ (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
|
||||
| ^^^^ Expected a variable declaration
|
||||
23 | for (; i < 3; ) {
|
||||
24 | break;
|
||||
25 | }
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
|
||||
|
||||
error.todo-kitchensink.ts:23:2
|
||||
21 | x.push(i);
|
||||
22 | }
|
||||
> 23 | for (; i < 3; ) {
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
> 24 | break;
|
||||
| ^^^^^^^^^^
|
||||
> 25 | }
|
||||
| ^^^^ (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
|
||||
26 | for (;;) {
|
||||
27 | break;
|
||||
28 | }
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
|
||||
|
||||
error.todo-kitchensink.ts:26:2
|
||||
24 | break;
|
||||
25 | }
|
||||
> 26 | for (;;) {
|
||||
| ^^^^^^^^^^
|
||||
> 27 | break;
|
||||
| ^^^^^^^^^^
|
||||
> 28 | }
|
||||
| ^^^^ (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
|
||||
29 |
|
||||
30 | graphql`
|
||||
31 | ${g}
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle empty test in ForStatement
|
||||
|
||||
error.todo-kitchensink.ts:26:2
|
||||
24 | break;
|
||||
25 | }
|
||||
> 26 | for (;;) {
|
||||
| ^^^^^^^^^^
|
||||
> 27 | break;
|
||||
| ^^^^^^^^^^
|
||||
> 28 | }
|
||||
| ^^^^ (BuildHIR::lowerStatement) Handle empty test in ForStatement
|
||||
29 |
|
||||
30 | graphql`
|
||||
31 | ${g}
|
||||
|
||||
Todo: (BuildHIR::lowerExpression) Handle tagged template with interpolations
|
||||
|
||||
error.todo-kitchensink.ts:30:2
|
||||
28 | }
|
||||
29 |
|
||||
> 30 | graphql`
|
||||
| ^^^^^^^^
|
||||
> 31 | ${g}
|
||||
| ^^^^^^^^
|
||||
> 32 | `;
|
||||
| ^^^^ (BuildHIR::lowerExpression) Handle tagged template with interpolations
|
||||
33 |
|
||||
34 | graphql`\\t\n`;
|
||||
35 |
|
||||
|
||||
Todo: (BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value
|
||||
|
||||
error.todo-kitchensink.ts:34:2
|
||||
32 | `;
|
||||
33 |
|
||||
> 34 | graphql`\\t\n`;
|
||||
| ^^^^^^^^^^^^^^ (BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value
|
||||
35 |
|
||||
36 | for (c of [1, 2]) {
|
||||
37 | }
|
||||
|
||||
Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `MemberExpression` cannot be safely reordered
|
||||
|
||||
error.todo-kitchensink.ts:57:9
|
||||
55 | case foo(): {
|
||||
56 | }
|
||||
> 57 | case x.y: {
|
||||
| ^^^ (BuildHIR::node.lowerReorderableExpression) Expression type `MemberExpression` cannot be safely reordered
|
||||
58 | }
|
||||
59 | default: {
|
||||
60 | }
|
||||
|
||||
Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `BinaryExpression` cannot be safely reordered
|
||||
|
||||
error.todo-kitchensink.ts:53:9
|
||||
51 |
|
||||
52 | switch (i) {
|
||||
> 53 | case 1 + 1: {
|
||||
| ^^^^^ (BuildHIR::node.lowerReorderableExpression) Expression type `BinaryExpression` cannot be safely reordered
|
||||
54 | }
|
||||
55 | case foo(): {
|
||||
56 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function useFoo(props: {value: {x: string; y: string} | null}) {
|
||||
const value = props.value;
|
||||
return createArray(value?.x, value?.y)?.join(', ');
|
||||
}
|
||||
|
||||
function createArray<T>(...args: Array<T>): Array<T> {
|
||||
return args;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPONT = {
|
||||
fn: useFoo,
|
||||
props: [{value: null}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Todo: Unexpected terminal kind `optional` for optional fallthrough block
|
||||
|
||||
error.todo-optional-call-chain-in-optional.ts:3:21
|
||||
1 | function useFoo(props: {value: {x: string; y: string} | null}) {
|
||||
2 | const value = props.value;
|
||||
> 3 | return createArray(value?.x, value?.y)?.join(', ');
|
||||
| ^^^^^^^^ Unexpected terminal kind `optional` for optional fallthrough block
|
||||
4 | }
|
||||
5 |
|
||||
6 | function createArray<T>(...args: Array<T>): Array<T> {
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ function component(a, b) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Todo: (BuildHIR::lowerExpression) Handle YieldExpression expressions
|
||||
|
||||
@@ -30,6 +30,23 @@ error.useMemo-callback-generator.ts:6:4
|
||||
7 | }, []);
|
||||
8 | return x;
|
||||
9 | }
|
||||
|
||||
Error: useMemo() callbacks may not be async or generator functions
|
||||
|
||||
useMemo() callbacks are called once and must synchronously return a value.
|
||||
|
||||
error.useMemo-callback-generator.ts:5:18
|
||||
3 | // useful for now, but adding this test in case we do
|
||||
4 | // add support for generators in the future.
|
||||
> 5 | let x = useMemo(function* () {
|
||||
| ^^^^^^^^^^^^^^
|
||||
> 6 | yield a;
|
||||
| ^^^^^^^^^^^^
|
||||
> 7 | }, []);
|
||||
| ^^^^ Async and generator functions are not supported
|
||||
8 | return x;
|
||||
9 | }
|
||||
10 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Fault tolerance test: two independent errors should both be reported.
|
||||
*
|
||||
* Error 1 (BuildHIR): `try/finally` is not supported
|
||||
* Error 2 (InferMutationAliasingEffects): Mutation of frozen props
|
||||
*/
|
||||
function Component(props) {
|
||||
// Error: try/finally (Todo from BuildHIR)
|
||||
try {
|
||||
doWork();
|
||||
} finally {
|
||||
doCleanup();
|
||||
}
|
||||
|
||||
// Error: mutating frozen props
|
||||
props.value = 1;
|
||||
|
||||
return <div>{props.value}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
|
||||
|
||||
error.try-finally-and-mutation-of-props.ts:9:2
|
||||
7 | function Component(props) {
|
||||
8 | // Error: try/finally (Todo from BuildHIR)
|
||||
> 9 | try {
|
||||
| ^^^^^
|
||||
> 10 | doWork();
|
||||
| ^^^^^^^^^^^^^
|
||||
> 11 | } finally {
|
||||
| ^^^^^^^^^^^^^
|
||||
> 12 | doCleanup();
|
||||
| ^^^^^^^^^^^^^
|
||||
> 13 | }
|
||||
| ^^^^ (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
|
||||
14 |
|
||||
15 | // Error: mutating frozen props
|
||||
16 | props.value = 1;
|
||||
|
||||
Error: This value cannot be modified
|
||||
|
||||
Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
|
||||
|
||||
error.try-finally-and-mutation-of-props.ts:16:2
|
||||
14 |
|
||||
15 | // Error: mutating frozen props
|
||||
> 16 | props.value = 1;
|
||||
| ^^^^^ value cannot be modified
|
||||
17 |
|
||||
18 | return <div>{props.value}</div>;
|
||||
19 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Fault tolerance test: two independent errors should both be reported.
|
||||
*
|
||||
* Error 1 (BuildHIR): `try/finally` is not supported
|
||||
* Error 2 (InferMutationAliasingEffects): Mutation of frozen props
|
||||
*/
|
||||
function Component(props) {
|
||||
// Error: try/finally (Todo from BuildHIR)
|
||||
try {
|
||||
doWork();
|
||||
} finally {
|
||||
doCleanup();
|
||||
}
|
||||
|
||||
// Error: mutating frozen props
|
||||
props.value = 1;
|
||||
|
||||
return <div>{props.value}</div>;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender
|
||||
/**
|
||||
* Fault tolerance test: two independent errors should both be reported.
|
||||
*
|
||||
* Error 1 (BuildHIR): `try/finally` is not supported
|
||||
* Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
|
||||
*/
|
||||
function Component() {
|
||||
const ref = useRef(null);
|
||||
|
||||
// Error: try/finally (Todo from BuildHIR)
|
||||
try {
|
||||
doSomething();
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// Error: reading ref during render
|
||||
const value = ref.current;
|
||||
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
|
||||
|
||||
error.try-finally-and-ref-access.ts:12:2
|
||||
10 |
|
||||
11 | // Error: try/finally (Todo from BuildHIR)
|
||||
> 12 | try {
|
||||
| ^^^^^
|
||||
> 13 | doSomething();
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
> 14 | } finally {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
> 15 | cleanup();
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
> 16 | }
|
||||
| ^^^^ (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
|
||||
17 |
|
||||
18 | // Error: reading ref during render
|
||||
19 | const value = ref.current;
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.try-finally-and-ref-access.ts:19:16
|
||||
17 |
|
||||
18 | // Error: reading ref during render
|
||||
> 19 | const value = ref.current;
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
20 |
|
||||
21 | return <div>{value}</div>;
|
||||
22 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// @validateRefAccessDuringRender
|
||||
/**
|
||||
* Fault tolerance test: two independent errors should both be reported.
|
||||
*
|
||||
* Error 1 (BuildHIR): `try/finally` is not supported
|
||||
* Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
|
||||
*/
|
||||
function Component() {
|
||||
const ref = useRef(null);
|
||||
|
||||
// Error: try/finally (Todo from BuildHIR)
|
||||
try {
|
||||
doSomething();
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// Error: reading ref during render
|
||||
const value = ref.current;
|
||||
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender
|
||||
/**
|
||||
* Fault tolerance test: three independent errors should all be reported.
|
||||
*
|
||||
* Error 1 (BuildHIR): `try/finally` is not supported
|
||||
* Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
|
||||
* Error 3 (InferMutationAliasingEffects): Mutation of frozen props
|
||||
*/
|
||||
function Component(props) {
|
||||
const ref = useRef(null);
|
||||
|
||||
// Error: try/finally (Todo from BuildHIR)
|
||||
try {
|
||||
doWork();
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// Error: reading ref during render
|
||||
const value = ref.current;
|
||||
|
||||
// Error: mutating frozen props
|
||||
props.items = [];
|
||||
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 3 errors:
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
|
||||
|
||||
error.try-finally-ref-access-and-mutation.ts:13:2
|
||||
11 |
|
||||
12 | // Error: try/finally (Todo from BuildHIR)
|
||||
> 13 | try {
|
||||
| ^^^^^
|
||||
> 14 | doWork();
|
||||
| ^^^^^^^^^^^^^
|
||||
> 15 | } finally {
|
||||
| ^^^^^^^^^^^^^
|
||||
> 16 | cleanup();
|
||||
| ^^^^^^^^^^^^^
|
||||
> 17 | }
|
||||
| ^^^^ (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
|
||||
18 |
|
||||
19 | // Error: reading ref during render
|
||||
20 | const value = ref.current;
|
||||
|
||||
Error: This value cannot be modified
|
||||
|
||||
Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
|
||||
|
||||
error.try-finally-ref-access-and-mutation.ts:23:2
|
||||
21 |
|
||||
22 | // Error: mutating frozen props
|
||||
> 23 | props.items = [];
|
||||
| ^^^^^ value cannot be modified
|
||||
24 |
|
||||
25 | return <div>{value}</div>;
|
||||
26 | }
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.try-finally-ref-access-and-mutation.ts:20:16
|
||||
18 |
|
||||
19 | // Error: reading ref during render
|
||||
> 20 | const value = ref.current;
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
21 |
|
||||
22 | // Error: mutating frozen props
|
||||
23 | props.items = [];
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// @validateRefAccessDuringRender
|
||||
/**
|
||||
* Fault tolerance test: three independent errors should all be reported.
|
||||
*
|
||||
* Error 1 (BuildHIR): `try/finally` is not supported
|
||||
* Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
|
||||
* Error 3 (InferMutationAliasingEffects): Mutation of frozen props
|
||||
*/
|
||||
function Component(props) {
|
||||
const ref = useRef(null);
|
||||
|
||||
// Error: try/finally (Todo from BuildHIR)
|
||||
try {
|
||||
doWork();
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// Error: reading ref during render
|
||||
const value = ref.current;
|
||||
|
||||
// Error: mutating frozen props
|
||||
props.items = [];
|
||||
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Fault tolerance test: two independent errors should both be reported.
|
||||
*
|
||||
* Error 1 (BuildHIR): `var` declarations are not supported (treated as `let`)
|
||||
* Error 2 (InferMutationAliasingEffects): Mutation of frozen props
|
||||
*/
|
||||
function Component(props) {
|
||||
// Error: var declaration (Todo from BuildHIR)
|
||||
var items = props.items;
|
||||
|
||||
// Error: mutating frozen props (detected during inference)
|
||||
props.items = [];
|
||||
|
||||
return <div>{items.length}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration
|
||||
|
||||
error.var-declaration-and-mutation-of-props.ts:9:2
|
||||
7 | function Component(props) {
|
||||
8 | // Error: var declaration (Todo from BuildHIR)
|
||||
> 9 | var items = props.items;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration
|
||||
10 |
|
||||
11 | // Error: mutating frozen props (detected during inference)
|
||||
12 | props.items = [];
|
||||
|
||||
Error: This value cannot be modified
|
||||
|
||||
Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
|
||||
|
||||
error.var-declaration-and-mutation-of-props.ts:12:2
|
||||
10 |
|
||||
11 | // Error: mutating frozen props (detected during inference)
|
||||
> 12 | props.items = [];
|
||||
| ^^^^^ value cannot be modified
|
||||
13 |
|
||||
14 | return <div>{items.length}</div>;
|
||||
15 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Fault tolerance test: two independent errors should both be reported.
|
||||
*
|
||||
* Error 1 (BuildHIR): `var` declarations are not supported (treated as `let`)
|
||||
* Error 2 (InferMutationAliasingEffects): Mutation of frozen props
|
||||
*/
|
||||
function Component(props) {
|
||||
// Error: var declaration (Todo from BuildHIR)
|
||||
var items = props.items;
|
||||
|
||||
// Error: mutating frozen props (detected during inference)
|
||||
props.items = [];
|
||||
|
||||
return <div>{items.length}</div>;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender
|
||||
/**
|
||||
* Fault tolerance test: two independent errors should both be reported.
|
||||
*
|
||||
* Error 1 (BuildHIR): `var` declarations are not supported (treated as `let`)
|
||||
* Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
|
||||
*/
|
||||
function Component() {
|
||||
const ref = useRef(null);
|
||||
|
||||
// Error: var declaration (Todo from BuildHIR)
|
||||
var items = [1, 2, 3];
|
||||
|
||||
// Error: reading ref during render
|
||||
const value = ref.current;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{value}
|
||||
{items.length}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration
|
||||
|
||||
error.var-declaration-and-ref-access.ts:12:2
|
||||
10 |
|
||||
11 | // Error: var declaration (Todo from BuildHIR)
|
||||
> 12 | var items = [1, 2, 3];
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration
|
||||
13 |
|
||||
14 | // Error: reading ref during render
|
||||
15 | const value = ref.current;
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.var-declaration-and-ref-access.ts:15:16
|
||||
13 |
|
||||
14 | // Error: reading ref during render
|
||||
> 15 | const value = ref.current;
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
16 |
|
||||
17 | return (
|
||||
18 | <div>
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// @validateRefAccessDuringRender
|
||||
/**
|
||||
* Fault tolerance test: two independent errors should both be reported.
|
||||
*
|
||||
* Error 1 (BuildHIR): `var` declarations are not supported (treated as `let`)
|
||||
* Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
|
||||
*/
|
||||
function Component() {
|
||||
const ref = useRef(null);
|
||||
|
||||
// Error: var declaration (Todo from BuildHIR)
|
||||
var items = [1, 2, 3];
|
||||
|
||||
// Error: reading ref during render
|
||||
const value = ref.current;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{value}
|
||||
{items.length}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 4 errors:
|
||||
|
||||
Todo: Support local variables named `fbt`
|
||||
|
||||
@@ -60,10 +60,49 @@ error.todo-fbt-as-local.ts:18:19
|
||||
16 |
|
||||
17 | function Foo(props) {
|
||||
> 18 | const getText1 = fbt =>
|
||||
| ^^^ Rename to avoid conflict with fbt plugin
|
||||
| ^^^ Support local variables named `fbt`
|
||||
19 | fbt(
|
||||
20 | `Hello, ${fbt.param('(key) name', identity(props.name))}!`,
|
||||
21 | '(description) Greeting'
|
||||
|
||||
Todo: Support local variables named `fbt`
|
||||
|
||||
Local variables named `fbt` may conflict with the fbt plugin and are not yet supported.
|
||||
|
||||
error.todo-fbt-as-local.ts:18:19
|
||||
16 |
|
||||
17 | function Foo(props) {
|
||||
> 18 | const getText1 = fbt =>
|
||||
| ^^^ Support local variables named `fbt`
|
||||
19 | fbt(
|
||||
20 | `Hello, ${fbt.param('(key) name', identity(props.name))}!`,
|
||||
21 | '(description) Greeting'
|
||||
|
||||
Todo: Support local variables named `fbt`
|
||||
|
||||
Local variables named `fbt` may conflict with the fbt plugin and are not yet supported.
|
||||
|
||||
error.todo-fbt-as-local.ts:18:19
|
||||
16 |
|
||||
17 | function Foo(props) {
|
||||
> 18 | const getText1 = fbt =>
|
||||
| ^^^ Support local variables named `fbt`
|
||||
19 | fbt(
|
||||
20 | `Hello, ${fbt.param('(key) name', identity(props.name))}!`,
|
||||
21 | '(description) Greeting'
|
||||
|
||||
Todo: Support local variables named `fbt`
|
||||
|
||||
Local variables named `fbt` may conflict with the fbt plugin and are not yet supported.
|
||||
|
||||
error.todo-fbt-as-local.ts:24:19
|
||||
22 | );
|
||||
23 |
|
||||
> 24 | const getText2 = fbt =>
|
||||
| ^^^ Support local variables named `fbt`
|
||||
25 | fbt(
|
||||
26 | `Goodbye, ${fbt.param('(key) name', identity(props.name))}!`,
|
||||
27 | '(description) Greeting2'
|
||||
```
|
||||
|
||||
|
||||
@@ -16,17 +16,15 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Todo: Support local variables named `fbt`
|
||||
Invariant: <fbt> tags should be module-level imports
|
||||
|
||||
Local variables named `fbt` may conflict with the fbt plugin and are not yet supported.
|
||||
|
||||
error.todo-locally-require-fbt.ts:2:8
|
||||
1 | function Component(props) {
|
||||
> 2 | const fbt = require('fbt');
|
||||
| ^^^ Rename to avoid conflict with fbt plugin
|
||||
error.todo-locally-require-fbt.ts:4:10
|
||||
2 | const fbt = require('fbt');
|
||||
3 |
|
||||
4 | return <fbt desc="Description">{'Text'}</fbt>;
|
||||
> 4 | return <fbt desc="Description">{'Text'}</fbt>;
|
||||
| ^^^ <fbt> tags should be module-level imports
|
||||
5 | }
|
||||
6 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
function useFoo(props: {value: {x: string; y: string} | null}) {
|
||||
const value = props.value;
|
||||
return createArray(value?.x, value?.y)?.join(', ');
|
||||
}
|
||||
|
||||
function createArray<T>(...args: Array<T>): Array<T> {
|
||||
return args;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPONT = {
|
||||
fn: useFoo,
|
||||
props: [{value: null}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Todo: Unexpected terminal kind `optional` for optional fallthrough block
|
||||
|
||||
error.todo-optional-call-chain-in-optional.ts:4:21
|
||||
2 | function useFoo(props: {value: {x: string; y: string} | null}) {
|
||||
3 | const value = props.value;
|
||||
> 4 | return createArray(value?.x, value?.y)?.join(', ');
|
||||
| ^^^^^^^^ Unexpected terminal kind `optional` for optional fallthrough block
|
||||
5 | }
|
||||
6 |
|
||||
7 | function createArray<T>(...args: Array<T>): Array<T> {
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
function useFoo(props: {value: {x: string; y: string} | null}) {
|
||||
const value = props.value;
|
||||
return createArray(value?.x, value?.y)?.join(', ');
|
||||
}
|
||||
|
||||
function createArray<T>(...args: Array<T>): Array<T> {
|
||||
return args;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPONT = {
|
||||
fn: useFoo,
|
||||
props: [{value: null}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
function useFoo(props) {
|
||||
const $ = _c(3);
|
||||
const value = props.value;
|
||||
let t0;
|
||||
if ($[0] !== value?.x || $[1] !== value?.y) {
|
||||
t0 = createArray(value?.x, value?.y)?.join(", ");
|
||||
$[0] = value?.x;
|
||||
$[1] = value?.y;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
function createArray(...t0) {
|
||||
const args = t0;
|
||||
return args;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPONT = {
|
||||
fn: useFoo,
|
||||
props: [{ value: null }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function useFoo(props: {value: {x: string; y: string} | null}) {
|
||||
const value = props.value;
|
||||
return createArray(value?.x, value?.y)?.join(', ');
|
||||
}
|
||||
|
||||
function createArray<T>(...args: Array<T>): Array<T> {
|
||||
return args;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPONT = {
|
||||
fn: useFoo,
|
||||
props: [{value: null}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function useFoo(props) {
|
||||
const $ = _c(3);
|
||||
const value = props.value;
|
||||
let t0;
|
||||
if ($[0] !== value?.x || $[1] !== value?.y) {
|
||||
t0 = createArray(value?.x, value?.y)?.join(", ");
|
||||
$[0] = value?.x;
|
||||
$[1] = value?.y;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
function createArray(...t0) {
|
||||
const args = t0;
|
||||
return args;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPONT = {
|
||||
fn: useFoo,
|
||||
props: [{ value: null }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -23,16 +23,18 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Todo: (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern
|
||||
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
|
||||
|
||||
todo.error.object-pattern-computed-key.ts:5:9
|
||||
3 | const SCALE = 2;
|
||||
<unknown> value$3.
|
||||
|
||||
todo.error.object-pattern-computed-key.ts:6:9
|
||||
4 | function Component(props) {
|
||||
> 5 | const {[props.name]: value} = props;
|
||||
| ^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern
|
||||
6 | return value;
|
||||
5 | const {[props.name]: value} = props;
|
||||
> 6 | return value;
|
||||
| ^^^^^ this is uninitialized
|
||||
7 | }
|
||||
8 |
|
||||
9 | export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
|
||||
@@ -378,6 +378,17 @@ export async function transformFixtureInput(
|
||||
msg: 'Expected nothing to be compiled (from `// @expectNothingCompiled`), but some functions compiled or errored',
|
||||
};
|
||||
}
|
||||
const unexpectedThrows = logs.filter(
|
||||
log => log.event.kind === 'CompileUnexpectedThrow',
|
||||
);
|
||||
if (unexpectedThrows.length > 0) {
|
||||
return {
|
||||
kind: 'err',
|
||||
msg:
|
||||
`Compiler pass(es) threw instead of recording errors:\n` +
|
||||
unexpectedThrows.map(l => (l.event as any).data).join('\n'),
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: 'ok',
|
||||
value: {
|
||||
|
||||
Reference in New Issue
Block a user