Compare commits

..

4 Commits

Author SHA1 Message Date
Joe Savona
26d580b79e [commit] Fix for nested optional chaining within other value blocks
Fixes a longstanding issue where we didn't support code like `useFoo(value?.bar(), value?.bar()) ?? {}` - we would attempt to construct a ReactiveFunction, recursively processing the blocks, but the inner optional `value?.bar()` wouldn't match with what the outer optional was expecting to find. It's a one-line fix!

Note: memoization in the examples is not ideal, but i've confirmed that it is not strictly related to the optional issue.
2026-01-23 11:07:42 -08:00
Joe Savona
e9cca30b18 [compiler] Fix invariant error for optional chaining in try/catch
Optional chaining and other value blocks within try/catch blocks were throwing an Invariant error ("Unexpected terminal in optional") instead of the expected Todo error. This caused hard failures instead of graceful bailouts.

The issue occurred because DropManualMemoization and ValidateExhaustiveDependencies ran before BuildReactiveFunction, and encountered `maybe-throw` terminals in their switch statements without a handler, falling through to the invariant case.

Fixed by adding explicit `maybe-throw` cases in both files that throw the proper Todo error with the message "Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement".

Also renamed the existing bug test to reflect it's now correctly handled:
- error.bug-invariant-unexpected-terminal-in-optional → error.todo-optional-chaining-within-try-catch

Closes #35570
2026-01-23 11:07:42 -08:00
Joe Savona
ff39247ee0 [compiler] Summaries of the compiler passes to assist agents in development
Autogenerated summaries of each of the compiler passes which allow agents to get the key ideas of a compiler pass, including key input/output invariants, without having to reprocess the file each time. In the subsequent diff this seemed to help.
2026-01-23 11:07:42 -08:00
Joe Savona
0c10c8f7e4 [compiler] Improve snap workflow for debugging errors
Much nicer workflow for working through errors in the compiler:
* Run `yarn snap -w`, oops there are are errors
* Hit 'p' to select a fixture => the suggestions populate with recent failures, sorted alphabetically. No need to copy/paste the name of the fixture you want to focus on!
* tab/shift-tab to pick one, hit enter to select that one
* ...Focus on fixing that test...
* 'p' to re-enter the picker. Snap tracks the last state of each fixture and continues to show all tests that failed on their last run, so you can easily move on to the next one. The currently selected test is highlighted, making it easy to move to the next one.
* 'a' at any time to run all tests
* 'd' at any time to toggle debug output on/off (while focusing on a single test)
2026-01-23 11:07:42 -08:00
644 changed files with 9035 additions and 14771 deletions

View File

@@ -1,46 +0,0 @@
# React
**Scope**: All code EXCEPT `/compiler/` (compiler has its own instructions).
## Project Structure
| Directory | Purpose |
|-----------|---------|
| `/packages/` | Publishable packages (react, react-dom, scheduler, etc.) |
| `/scripts/` | Build, test, and development scripts |
| `/fixtures/` | Test applications for manual testing |
| `/compiler/` | React Compiler (separate sub-project) |
## Key Packages
| Package | Purpose |
|---------|---------|
| `react` | Core React library |
| `react-dom` | DOM renderer |
| `react-reconciler` | Core reconciliation algorithm |
| `scheduler` | Cooperative scheduling |
| `react-server-dom-*` | Server Components |
| `react-devtools-*` | Developer Tools |
| `react-refresh` | Fast Refresh runtime |
## Requirements
- **Node**: Must be installed. Stop and prompt user if missing.
- **Package Manager**: Use `yarn` only.
## Verification
**IMPORTANT**: Use `/verify` to validate all changes before committing.
## Commands
| Command | Purpose |
|----------|----------------------|
| `/fix` | Lint and format code |
| `/test` | Run tests |
| `/flow` | Type check with Flow |
| `/flags` | Check feature flags |
## Building
Builds are handled by CI. Do not run locally unless instructed.

View File

@@ -1,44 +0,0 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "if [[ \"$PWD\" != */compiler* ]]; then cat .claude/instructions.md 2>/dev/null || true; fi"
}
]
}
]
},
"permissions": {
"allow": [
"Skill(extract-errors)",
"Skill(feature-flags)",
"Skill(fix)",
"Skill(flags)",
"Skill(flow)",
"Skill(test)",
"Skill(verify)",
"Bash(yarn test:*)",
"Bash(yarn test-www:*)",
"Bash(yarn test-classic:*)",
"Bash(yarn test-stable:*)",
"Bash(yarn linc:*)",
"Bash(yarn lint:*)",
"Bash(yarn flow:*)",
"Bash(yarn prettier:*)",
"Bash(yarn build:*)",
"Bash(yarn extract-errors:*)",
"Bash(yarn flags:*)"
],
"deny": [
"Bash(yarn download-build:*)",
"Bash(yarn download-build-for-head:*)",
"Bash(npm:*)",
"Bash(pnpm:*)",
"Bash(bun:*)",
"Bash(npx:*)"
]
}
}

View File

@@ -1,12 +0,0 @@
---
name: extract-errors
description: Use when adding new error messages to React, or seeing "unknown error code" warnings.
---
# Extract Error Codes
## Instructions
1. Run `yarn extract-errors`
2. Report if any new errors need codes assigned
3. Check if error codes are up to date

View File

@@ -1,79 +0,0 @@
---
name: feature-flags
description: Use when feature flag tests fail, flags need updating, understanding @gate pragmas, debugging channel-specific test failures, or adding new flags to React.
---
# React Feature Flags
## Flag Files
| File | Purpose |
|------|---------|
| `packages/shared/ReactFeatureFlags.js` | Default flags (canary), `__EXPERIMENTAL__` overrides |
| `packages/shared/forks/ReactFeatureFlags.www.js` | www channel, `__VARIANT__` overrides |
| `packages/shared/forks/ReactFeatureFlags.native-fb.js` | React Native, `__VARIANT__` overrides |
| `packages/shared/forks/ReactFeatureFlags.test-renderer.js` | Test renderer |
## Gating Tests
### `@gate` pragma (test-level)
Use when the feature is completely unavailable without the flag:
```javascript
// @gate enableViewTransition
it('supports view transitions', () => {
// This test only runs when enableViewTransition is true
// and is SKIPPED (not failed) when false
});
```
### `gate()` inline (assertion-level)
Use when the feature exists but behavior differs based on flag:
```javascript
it('renders component', async () => {
await act(() => root.render(<App />));
if (gate(flags => flags.enableNewBehavior)) {
expect(container.textContent).toBe('new output');
} else {
expect(container.textContent).toBe('legacy output');
}
});
```
## Adding a New Flag
1. Add to `ReactFeatureFlags.js` with default value
2. Add to each fork file (`*.www.js`, `*.native-fb.js`, etc.)
3. If it should vary in www/RN, set to `__VARIANT__` in the fork file
4. Gate tests with `@gate flagName` or inline `gate()`
## Checking Flag States
Use `/flags` to view states across channels. See the `flags` skill for full command options.
## `__VARIANT__` Flags (GKs)
Flags set to `__VARIANT__` simulate gatekeepers - tested twice (true and false):
```bash
/test www <pattern> # __VARIANT__ = true
/test www variant false <pattern> # __VARIANT__ = false
```
## Debugging Channel-Specific Failures
1. Run `/flags --diff <channel1> <channel2>` to compare values
2. Check `@gate` conditions - test may be gated to specific channels
3. Run `/test <channel> <pattern>` to isolate the failure
4. Verify flag exists in all fork files if newly added
## Common Mistakes
- **Forgetting both variants** - Always test `www` AND `www variant false` for `__VARIANT__` flags
- **Using @gate for behavior differences** - Use inline `gate()` if both paths should run
- **Missing fork files** - New flags must be added to ALL fork files, not just the main one
- **Wrong gate syntax** - It's `gate(flags => flags.name)`, not `gate('name')`

View File

@@ -1,17 +0,0 @@
---
name: fix
description: Use when you have lint errors, formatting issues, or before committing code to ensure it passes CI.
---
# Fix Lint and Formatting
## Instructions
1. Run `yarn prettier` to fix formatting
2. Run `yarn linc` to check for remaining lint issues
3. Report any remaining manual fixes needed
## Common Mistakes
- **Running prettier on wrong files** - `yarn prettier` only formats changed files
- **Ignoring linc errors** - These will fail CI, fix them before committing

View File

@@ -1,39 +0,0 @@
---
name: flags
description: Use when you need to check feature flag states, compare channels, or debug why a feature behaves differently across release channels.
---
# Feature Flags
Arguments:
- $ARGUMENTS: Optional flags
## Options
| Option | Purpose |
|--------|---------|
| (none) | Show all flags across all channels |
| `--diff <ch1> <ch2>` | Compare flags between channels |
| `--cleanup` | Show flags grouped by cleanup status |
| `--csv` | Output in CSV format |
## Channels
- `www`, `www-modern` - Meta internal
- `canary`, `next`, `experimental` - OSS channels
- `rn`, `rn-fb`, `rn-next` - React Native
## Legend
✅ enabled, ❌ disabled, 🧪 `__VARIANT__`, 📊 profiling-only
## Instructions
1. Run `yarn flags $ARGUMENTS`
2. Explain the output to the user
3. For --diff, highlight meaningful differences
## Common Mistakes
- **Forgetting `__VARIANT__` flags** - These are tested both ways in www; check both variants
- **Comparing wrong channels** - Use `--diff` to see exact differences

View File

@@ -1,30 +0,0 @@
---
name: flow
description: Use when you need to run Flow type checking, or when seeing Flow type errors in React code.
---
# Flow Type Checking
Arguments:
- $ARGUMENTS: Renderer to check (default: dom-node)
## Renderers
| Renderer | When to Use |
|----------|-------------|
| `dom-node` | Default, recommended for most changes |
| `dom-browser` | Browser-specific DOM code |
| `native` | React Native |
| `fabric` | React Native Fabric |
## Instructions
1. Run `yarn flow $ARGUMENTS` (use `dom-node` if no argument)
2. Report type errors with file locations
3. For comprehensive checking (slow), use `yarn flow-ci`
## Common Mistakes
- **Running without a renderer** - Always specify or use default `dom-node`
- **Ignoring suppressions** - Check if `$FlowFixMe` comments are masking real issues
- **Missing type imports** - Ensure types are imported from the correct package

View File

@@ -1,46 +0,0 @@
---
name: test
description: Use when you need to run tests for React core. Supports source, www, stable, and experimental channels.
---
Run tests for the React codebase.
Arguments:
- $ARGUMENTS: Channel, flags, and test pattern
Usage Examples:
- `/test ReactFiberHooks` - Run with source channel (default)
- `/test experimental ReactFiberHooks` - Run with experimental channel
- `/test www ReactFiberHooks` - Run with www-modern channel
- `/test www variant false ReactFiberHooks` - Test __VARIANT__=false
- `/test stable ReactFiberHooks` - Run with stable channel
- `/test classic ReactFiberHooks` - Run with www-classic channel
- `/test watch ReactFiberHooks` - Run in watch mode (TDD)
Release Channels:
- `(default)` - Source/canary channel, uses ReactFeatureFlags.js defaults
- `experimental` - Source/experimental channel with __EXPERIMENTAL__ flags = true
- `www` - www-modern channel with __VARIANT__ flags = true
- `www variant false` - www channel with __VARIANT__ flags = false
- `stable` - What ships to npm
- `classic` - Legacy www-classic (rarely needed)
Instructions:
1. Parse channel from arguments (default: source)
2. Map to yarn command:
- (default) → `yarn test --silent --no-watchman <pattern>`
- experimental → `yarn test -r=experimental --silent --no-watchman <pattern>`
- stable → `yarn test-stable --silent --no-watchman <pattern>`
- classic → `yarn test-classic --silent --no-watchman <pattern>`
- www → `yarn test-www --silent --no-watchman <pattern>`
- www variant false → `yarn test-www --variant=false --silent --no-watchman <pattern>`
3. Report test results and any failures
Hard Rules:
1. **Use --silent to see failures** - This limits the test output to only failures.
2. **Use --no-watchman** - This is a common failure in sandboxing.
Common Mistakes:
- **Running without a pattern** - Runs ALL tests, very slow. Always specify a pattern.
- **Forgetting both www variants** - Test `www` AND `www variant false` for `__VARIANT__` flags.
- **Test skipped unexpectedly** - Check for `@gate` pragma; see `feature-flags` skill.

View File

@@ -1,24 +0,0 @@
---
name: verify
description: Use when you want to validate changes before committing, or when you need to check all React contribution requirements.
---
# Verification
Run all verification steps.
Arguments:
- $ARGUMENTS: Test pattern for the test step
## Instructions
Run these first in sequence:
1. Run `yarn prettier` - format code (stop if fails)
2. Run `yarn linc` - lint changed files (stop if fails)
Then run these with subagents in parallel:
1. Use `/flow` to type check (stop if fails)
2. Use `/test` to test changes in source (stop if fails)
3. Use `/test www` to test changes in www (stop if fails)
If all pass, show success summary. On failure, stop immediately and report the issue with suggested fixes.

View File

@@ -507,6 +507,7 @@ module.exports = {
__IS_FIREFOX__: 'readonly',
__IS_EDGE__: 'readonly',
__IS_NATIVE__: 'readonly',
__IS_INTERNAL_MCP_BUILD__: 'readonly',
__IS_INTERNAL_VERSION__: 'readonly',
chrome: 'readonly',
},

View File

@@ -29,7 +29,6 @@ jobs:
- "7"
- "8"
- "9"
- "10"
steps:
- uses: actions/checkout@v4
with:

View File

@@ -1,8 +0,0 @@
# React
React is a JavaScript library for building user interfaces.
## Monorepo Overview
- **React**: All files outside `/compiler/`
- **React Compiler**: `/compiler/` directory (has its own instructions)

View File

@@ -1,113 +0,0 @@
---
name: investigate-error
description: Investigates React compiler errors to determine the root cause and identify potential mitigation(s). Use this agent when the user asks to 'investigate a bug', 'debug why this fixture errors', 'understand why the compiler is failing', 'find the root cause of a compiler issue', or when they provide a snippet of code and ask to debug. Use automatically when encountering a failing test case, in order to understand the root cause.
model: opus
color: pink
---
You are an expert React Compiler debugging specialist with deep knowledge of compiler internals, intermediate representations, and optimization passes. Your mission is to systematically investigate compiler bugs to identify root causes and provide actionable information for fixes.
## Your Investigation Process
### Step 1: Create Test Fixture
Create a new fixture file at `packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/<fixture-name>.js` containing the problematic code. Use a descriptive name that reflects the issue (e.g., `bug-optional-chain-in-effect.js`).
### Step 2: Run Debug Compilation
Execute `yarn snap -d -p <fixture-name>` to compile the fixture with full debug output. This shows the state of the program after each compilation pass. You can also use `yarn snap compile -d <path-to-fixture>`.
### Step 3: Analyze Compilation Results
### Step 3a: If the fixture compiles successfully
- Compare the output against the user's expected behavior
- Review each compilation pass output from the `-d` flag
- Identify the first pass where the output diverges from expected behavior
- Proceed to binary search simplification
### Step 3b: If the fixture errors
Execute `yarn snap minimize --update <path-to-fixture>` to remove non-critical aspects of the failing test case. This **updates the fixture in place**.
Re-read the fixture file to see the latest, minimal reproduction of the error.
### Step 4: Iteratively adjust the fixture until it stops erroring
After the previous step the fixture will have all extraneous aspects removed. Try to make further edits to determine the specific feature that is causing the error.
Ideas:
* Replace immediately-invoked function expressions with labeled blocks
* Remove statements
* Simplify calls (remove arguments, replace the call with its lone argument)
* Simplify control flow statements by picking a single branch. Try using a labeled block with just the selected block
* Replace optional member/call expressions with non-optional versions
* Remove items in array/object expressions
* Remove properties from member expressions
Try to make the minimal possible edit to get the fixture stop erroring.
### Step 5: Compare Debug Outputs
With both minimal versions (failing and non-failing):
- Run `yarn snap -d -p <fixture-name>` on both
- Compare the debug output pass-by-pass
- Identify the exact pass where behavior diverges
- Note specific differences in HIR, effects, or generated code
### Step 6: Investigate Compiler Logic
- Read the documentation for the problematic pass in `packages/babel-plugin-react-compiler/docs/passes/`
- Examine the pass implementation in `packages/babel-plugin-react-compiler/src/`
- Key directories to investigate:
- `src/HIR/` - IR definitions and utilities
- `src/Inference/` - Effect inference (aliasing, mutation)
- `src/Validation/` - Validation passes
- `src/Optimization/` - Optimization passes
- `src/ReactiveScopes/` - Reactive scope analysis
- Identify specific code locations that may be handling the pattern incorrectly
## Output Format
Provide a structured investigation report:
```
## Investigation Summary
### Bug Description
[Brief description of the issue]
### Minimal Failing Fixture
```javascript
// packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/<name>.js
[minimal code that reproduces the error]
```
### Minimal Non-Failing Fixture
```javascript
// The simplest change that makes it work
[code that compiles correctly]
```
### Problematic Compiler Pass
[Name of the pass where the issue occurs]
### Root Cause Analysis
[Explanation of what the compiler is doing wrong]
### Suspect Code Locations
- `packages/babel-plugin-react-compiler/src/<path>:<line>:<column>` - [description of what may be incorrect]
- [additional locations if applicable]
### Suggested Fix Direction
[Brief suggestion of how the bug might be fixed]
```
## Key Debugging Tips
1. The debug output (`-d` flag) shows the program state after each pass - use this to pinpoint where things go wrong
2. Look for `@aliasingEffects=` on FunctionExpressions to understand data flow
3. Check for `Impure`, `Render`, `Capture` effects on instructions
4. The pass ordering in `Pipeline.ts` shows when effects are populated vs validated
5. Todo errors indicate unsupported but known patterns; Invariant errors indicate unexpected states
## Important Reminders
- Always create the fixture file before running tests
- Use descriptive fixture names that indicate the bug being investigated
- Keep both failing and non-failing minimal versions for your report
- Provide specific file:line:column references when identifying suspect code
- Read the relevant pass documentation before making conclusions about the cause

View File

@@ -5,14 +5,7 @@
"Bash(yarn snap:build)",
"Bash(node scripts/enable-feature-flag.js:*)"
],
"deny": [
"Skill(extract-errors)",
"Skill(feature-flags)",
"Skill(fix)",
"Skill(flags)",
"Skill(flow)",
"Skill(test)",
"Skill(verify)"
]
"deny": [],
"ask": []
}
}

View File

@@ -35,31 +35,6 @@ yarn snap -p <file-basename> -d
yarn snap -u
```
## Compiling Arbitrary Files
Use `yarn snap compile` to compile any file (not just fixtures) with the React Compiler:
```bash
# Compile a file and see the output
yarn snap compile <path>
# Compile with debug logging to see the state after each compiler pass
# This is an alternative to `yarn snap -d -p <pattern>` when you don't have a fixture file yet
yarn snap compile --debug <path>
```
## Minimizing Test Cases
Use `yarn snap minimize` to automatically reduce a failing test case to its minimal reproduction:
```bash
# Minimize a file that causes a compiler error
yarn snap minimize <path>
# Minimize and update the file in-place with the minimized version
yarn snap minimize --update <path>
```
## Version Control
This repository uses Sapling (`sl`) for version control. Sapling is similar to Mercurial: there is not staging area, but new/deleted files must be explicitlyu added/removed.

View File

@@ -17,32 +17,7 @@ yarn snap:build
yarn snap --watch
```
`snap` is our custom test runner, which creates "golden" test files that have the expected output for each input fixture, as well as the results of executing a specific input (or sequence of inputs) in both the uncompiled and compiler versions of the input.
### Compiling Arbitrary Files
You can compile any file (not just fixtures) using:
```sh
# Compile a file and see the output
yarn snap compile <path>
# Compile with debug output to see the state after each compiler pass
# This is an alternative to `yarn snap -d -p <pattern>` when you don't have a fixture file yet
yarn snap compile --debug <path>
```
### Minimizing Test Cases
To reduce a failing test case to its minimal reproduction:
```sh
# Minimize a file that causes a compiler error
yarn snap minimize <path>
# Minimize and update the file in-place
yarn snap minimize --update <path>
```
`snap` is our custom test runner, which creates "golden" test files that have the expected output for each input fixture, as well as the results of executing a specific input (or sequence of inputs) in both the uncompiled and compiler versions of the input.
When contributing changes, we prefer to:
* Add one or more fixtures that demonstrate the current compiled output for a particular combination of input and configuration. Send this as a first PR.

View File

@@ -294,15 +294,6 @@ yarn snap -p <fixture-name>
# Run with debug output (shows all passes)
yarn snap -p <fixture-name> -d
# Compile any file (not just fixtures) and see output
yarn snap compile <path>
# Compile any file with debug output (alternative to yarn snap -d -p when you don't have a fixture)
yarn snap compile --debug <path>
# Minimize a failing test case to its minimal reproduction
yarn snap minimize <path>
# Update expected outputs
yarn snap -u
```

View File

@@ -304,12 +304,11 @@ export class CompilerError extends Error {
disabledDetails: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
printedMessage: string | null = null;
static invariant(
static simpleInvariant(
condition: unknown,
options: {
reason: CompilerDiagnosticOptions['reason'];
description?: CompilerDiagnosticOptions['description'];
message?: string | null;
loc: SourceLocation;
},
): asserts condition {
@@ -323,12 +322,28 @@ export class CompilerError extends Error {
}).withDetails({
kind: 'error',
loc: options.loc,
message: options.message ?? options.reason,
message: options.reason,
}),
);
throw errors;
}
}
static invariant(
condition: unknown,
options: Omit<CompilerDiagnosticOptions, 'category'>,
): asserts condition {
if (!condition) {
const errors = new CompilerError();
errors.pushDiagnostic(
CompilerDiagnostic.create({
reason: options.reason,
description: options.description,
category: ErrorCategory.Invariant,
}).withDetails(...options.details),
);
throw errors;
}
}
static throwDiagnostic(options: CompilerDiagnosticOptions): never {
const errors = new CompilerError();

View File

@@ -8,7 +8,6 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {CompilerError} from '../CompilerError';
import {GeneratedSource} from '../HIR';
import {ProgramContext} from './Imports';
import {ExternalFunction} from '..';
@@ -52,12 +51,26 @@ function insertAdditionalFunctionDeclaration(
CompilerError.invariant(originalFnName != null && compiled.id != null, {
reason:
'Expected function declarations that are referenced elsewhere to have a named identifier',
loc: fnPath.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: fnPath.node.loc ?? null,
message: null,
},
],
});
CompilerError.invariant(originalFnParams.length === compiledParams.length, {
reason:
'Expected React Compiler optimized function declarations to have the same number of parameters as source',
loc: fnPath.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: fnPath.node.loc ?? null,
message: null,
},
],
});
const gatingCondition = t.identifier(
@@ -141,7 +154,13 @@ export function insertGatedFunctionDeclaration(
CompilerError.invariant(compiled.type === 'FunctionDeclaration', {
reason: 'Expected compiled node type to match input type',
description: `Got ${compiled.type} but expected FunctionDeclaration`,
loc: fnPath.node.loc ?? GeneratedSource,
details: [
{
kind: 'error',
loc: fnPath.node.loc ?? null,
message: null,
},
],
});
insertAdditionalFunctionDeclaration(
fnPath,

View File

@@ -257,7 +257,14 @@ export function addImportsToProgram(
reason:
'Encountered conflicting import specifiers in generated program',
description: `Conflict from import ${loweredImport.module}:(${loweredImport.imported} as ${loweredImport.name})`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
suggestions: null,
},
);
CompilerError.invariant(
@@ -267,7 +274,13 @@ export function addImportsToProgram(
reason:
'Found inconsistent import specifier. This is an internal bug.',
description: `Expected import ${moduleName}:${specifierName} but found ${loweredImport.module}:${loweredImport.imported}`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
},
);
}

View File

@@ -315,7 +315,13 @@ function insertNewOutlinedFunctionNode(
CompilerError.invariant(insertedFuncDecl.isFunctionDeclaration(), {
reason: 'Expected inserted function declaration',
description: `Got: ${insertedFuncDecl}`,
loc: insertedFuncDecl.node?.loc ?? GeneratedSource,
details: [
{
kind: 'error',
loc: insertedFuncDecl.node?.loc ?? null,
message: null,
},
],
});
return insertedFuncDecl;
}
@@ -440,7 +446,14 @@ export function compileProgram(
for (const outlined of compiled.outlined) {
CompilerError.invariant(outlined.fn.outlined.length === 0, {
reason: 'Unexpected nested outlined functions',
loc: outlined.fn.loc,
description: null,
details: [
{
kind: 'error',
loc: outlined.fn.loc,
message: null,
},
],
});
const fn = insertNewOutlinedFunctionNode(
program,
@@ -1438,7 +1451,15 @@ export function getReactCompilerRuntimeModule(
typeof target.runtimeModule === 'string',
{
reason: 'Expected target to already be validated',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
},
);
return target.runtimeModule;

View File

@@ -163,7 +163,14 @@ export function suppressionsToCompilerError(
): CompilerError {
CompilerError.invariant(suppressionRanges.length !== 0, {
reason: `Expected at least suppression comment source range`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
const error = new CompilerError();
for (const suppressionRange of suppressionRanges) {

View File

@@ -215,7 +215,14 @@ function validateImportSpecifier(
const binding = local.scope.getBinding(local.node.name);
CompilerError.invariant(binding != null, {
reason: 'Expected binding to be found for import specifier',
loc: local.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: local.node.loc ?? null,
message: null,
},
],
});
checkFn(binding.referencePaths, state);
}
@@ -235,7 +242,14 @@ function validateNamespacedImport(
CompilerError.invariant(binding != null, {
reason: 'Expected binding to be found for import specifier',
loc: local.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: local.node.loc ?? null,
message: null,
},
],
});
const filteredReferences = new Map<
CheckInvalidReferenceFn,

View File

@@ -46,7 +46,14 @@ export function raiseUnificationErrors(
if (errs.length === 0) {
CompilerError.invariant(false, {
reason: 'Should not have array of zero errors',
loc,
description: null,
details: [
{
kind: 'error',
loc,
message: null,
},
],
});
} else if (errs.length === 1) {
CompilerError.throwInvalidJS({

View File

@@ -151,7 +151,15 @@ export type LinearId = number & {
export function makeLinearId(id: number): LinearId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected LinearId id to be a non-negative integer',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return id as LinearId;
}
@@ -164,7 +172,15 @@ export type TypeParameterId = number & {
export function makeTypeParameterId(id: number): TypeParameterId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected TypeParameterId to be a non-negative integer',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return id as TypeParameterId;
}
@@ -186,7 +202,15 @@ export type VariableId = number & {
export function makeVariableId(id: number): VariableId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected VariableId id to be a non-negative integer',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return id as VariableId;
}
@@ -393,7 +417,14 @@ function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
} else {
CompilerError.invariant(false, {
reason: `Unsupported property kind ${prop.kind}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
}
}
@@ -462,7 +493,14 @@ function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
} else {
CompilerError.invariant(false, {
reason: `Unsupported property kind ${prop.kind}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
}
}
@@ -481,7 +519,14 @@ function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
} else {
CompilerError.invariant(false, {
reason: `Unsupported property kind ${prop.kind}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
}
}
@@ -494,7 +539,14 @@ function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
}
CompilerError.invariant(false, {
reason: `Unsupported class instance type ${flowType.def.type.kind}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
}
case 'Fun':
@@ -553,7 +605,14 @@ function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
} else {
CompilerError.invariant(false, {
reason: `Unsupported component props type ${propsType.type.kind}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
}
@@ -706,7 +765,14 @@ export class FlowTypeEnv implements ITypeEnv {
// TODO: use flow-js only for web environments (e.g. playground)
CompilerError.invariant(env.config.flowTypeProvider != null, {
reason: 'Expected flowDumpTypes to be defined in environment config',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
let stdout: any;
if (source === lastFlowSource) {

View File

@@ -38,14 +38,28 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
CompilerError.invariant(instr.lvalue.identifier.name === null, {
reason: `Expected all lvalues to be temporaries`,
description: `Found named lvalue \`${instr.lvalue.identifier.name}\``,
loc: instr.lvalue.loc,
details: [
{
kind: 'error',
loc: instr.lvalue.loc,
message: null,
},
],
suggestions: null,
});
CompilerError.invariant(!assignments.has(instr.lvalue.identifier.id), {
reason: `Expected lvalues to be assigned exactly once`,
description: `Found duplicate assignment of '${printPlace(
instr.lvalue,
)}'`,
loc: instr.lvalue.loc,
details: [
{
kind: 'error',
loc: instr.lvalue.loc,
message: null,
},
],
suggestions: null,
});
assignments.add(instr.lvalue.identifier.id);
for (const operand of eachInstructionLValue(instr)) {
@@ -75,7 +89,14 @@ function validate(
CompilerError.invariant(identifier === previous, {
reason: `Duplicate identifier object`,
description: `Found duplicate identifier object for id ${identifier.id}`,
loc: loc ?? GeneratedSource,
details: [
{
kind: 'error',
loc: loc ?? GeneratedSource,
message: null,
},
],
suggestions: null,
});
}
}

View File

@@ -18,7 +18,14 @@ export function assertTerminalSuccessorsExist(fn: HIRFunction): void {
description: `Block bb${successor} does not exist for terminal '${printTerminal(
block.terminal,
)}'`,
loc: (block.terminal as any).loc ?? GeneratedSource,
details: [
{
kind: 'error',
loc: (block.terminal as any).loc ?? GeneratedSource,
message: null,
},
],
suggestions: null,
});
return successor;
});
@@ -32,14 +39,26 @@ export function assertTerminalPredsExist(fn: HIRFunction): void {
CompilerError.invariant(predBlock != null, {
reason: 'Expected predecessor block to exist',
description: `Block ${block.id} references non-existent ${pred}`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
CompilerError.invariant(
[...eachTerminalSuccessor(predBlock.terminal)].includes(block.id),
{
reason: 'Terminal successor does not reference correct predecessor',
description: `Block bb${block.id} has bb${predBlock.id} as a predecessor, but bb${predBlock.id}'s successors do not include bb${block.id}`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
},
);
}

View File

@@ -131,7 +131,13 @@ export function recursivelyTraverseItems<T, TContext>(
CompilerError.invariant(disjoint || nested, {
reason: 'Invalid nesting in program blocks or scopes',
description: `Items overlap but are not nested: ${maybeParentRange.start}:${maybeParentRange.end}(${currRange.start}:${currRange.end})`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
if (disjoint) {
exit(maybeParent, context);

View File

@@ -57,7 +57,13 @@ function validateMutableRange(
{
reason: `Invalid mutable range: [${range.start}:${range.end}]`,
description: `${printPlace(place)} in ${description}`,
loc: place.loc,
details: [
{
kind: 'error',
loc: place.loc,
message: null,
},
],
},
);
}

View File

@@ -450,7 +450,14 @@ function lowerStatement(
CompilerError.invariant(binding != null, {
reason: 'Expected to find binding for hoisted identifier',
description: `Could not find a binding for ${id.node.name}`,
loc: id.node.loc ?? GeneratedSource,
suggestions: null,
details: [
{
kind: 'error',
loc: id.node.loc ?? GeneratedSource,
message: null,
},
],
});
if (builder.environment.isHoistedIdentifier(binding.identifier)) {
// Already hoisted
@@ -492,7 +499,14 @@ function lowerStatement(
CompilerError.invariant(identifier.kind === 'Identifier', {
reason:
'Expected hoisted binding to be a local identifier, not a global',
loc: id.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: id.node.loc ?? GeneratedSource,
message: null,
},
],
});
const place: Place = {
effect: Effect.Unknown,
@@ -1024,7 +1038,15 @@ function lowerStatement(
stmt.skip();
CompilerError.invariant(stmt.get('id').type === 'Identifier', {
reason: 'function declarations must have a name',
loc: stmt.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: stmt.node.loc ?? null,
message: null,
},
],
suggestions: null,
});
const id = stmt.get('id') as NodePath<t.Identifier>;
@@ -1122,7 +1144,15 @@ function lowerStatement(
const declarations = left.get('declarations');
CompilerError.invariant(declarations.length === 1, {
reason: `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
loc: left.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: left.node.loc ?? null,
message: null,
},
],
suggestions: null,
});
const id = declarations[0].get('id');
const assign = lowerAssignment(
@@ -1137,7 +1167,14 @@ function lowerStatement(
} else {
CompilerError.invariant(left.isLVal(), {
reason: 'Expected ForOf init to be a variable declaration or lval',
loc: leftLoc,
description: null,
details: [
{
kind: 'error',
loc: leftLoc,
message: null,
},
],
});
const assign = lowerAssignment(
builder,
@@ -1213,7 +1250,15 @@ function lowerStatement(
const declarations = left.get('declarations');
CompilerError.invariant(declarations.length === 1, {
reason: `Expected only one declaration in the init of a ForInStatement, got ${declarations.length}`,
loc: left.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: left.node.loc ?? null,
message: null,
},
],
suggestions: null,
});
const id = declarations[0].get('id');
const assign = lowerAssignment(
@@ -1228,7 +1273,14 @@ function lowerStatement(
} else {
CompilerError.invariant(left.isLVal(), {
reason: 'Expected ForIn init to be a variable declaration or lval',
loc: leftLoc,
description: null,
details: [
{
kind: 'error',
loc: leftLoc,
message: null,
},
],
});
const assign = lowerAssignment(
builder,
@@ -2192,7 +2244,15 @@ function lowerExpression(
} else {
CompilerError.invariant(namePath.isJSXNamespacedName(), {
reason: 'Refinement',
loc: namePath.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: namePath.node.loc ?? null,
message: null,
},
],
suggestions: null,
});
const namespace = namePath.node.namespace.name;
const name = namePath.node.name.name;
@@ -2245,7 +2305,15 @@ function lowerExpression(
// This is already checked in builder.resolveIdentifier
CompilerError.invariant(tagIdentifier.kind !== 'Identifier', {
reason: `<${tagName}> tags should be module-level imports`,
loc: openingIdentifier.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: openingIdentifier.node.loc ?? GeneratedSource,
message: null,
},
],
suggestions: null,
});
}
// see `error.todo-multiple-fbt-plural` fixture for explanation
@@ -2347,7 +2415,15 @@ function lowerExpression(
CompilerError.invariant(expr.get('quasi').get('quasis').length == 1, {
reason:
"there should be only one quasi as we don't support interpolations yet",
loc: expr.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: expr.node.loc ?? null,
message: null,
},
],
suggestions: null,
});
const value = expr.get('quasi').get('quasis').at(0)!.node.value;
if (value.raw !== value.cooked) {
@@ -2743,7 +2819,15 @@ function lowerOptionalMemberExpression(
});
CompilerError.invariant(object !== null, {
reason: 'Satisfy type checker',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
/*
@@ -3315,7 +3399,15 @@ function lowerJsxMemberExpression(
} else {
CompilerError.invariant(object.isJSXIdentifier(), {
reason: `TypeScript refinement fail: expected 'JsxIdentifier', got \`${object.node.type}\``,
loc: object.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: object.node.loc ?? null,
message: null,
},
],
suggestions: null,
});
const kind = getLoadKind(builder, object);
@@ -3355,7 +3447,15 @@ function lowerJsxElement(
} else {
CompilerError.invariant(expression.isExpression(), {
reason: `(BuildHIR::lowerJsxElement) Expected Expression but found ${expression.type}!`,
loc: expression.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: expression.node.loc ?? null,
message: null,
},
],
suggestions: null,
});
return lowerExpressionToTemporary(builder, expression);
}
@@ -3751,7 +3851,15 @@ function lowerAssignment(
// This can only occur because of a coding error, parsers enforce this condition
CompilerError.invariant(kind === InstructionKind.Reassign, {
reason: 'MemberExpression may only appear in an assignment expression',
loc: lvaluePath.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: lvaluePath.node.loc ?? null,
message: null,
},
],
suggestions: null,
});
const lvalue = lvaluePath as NodePath<t.MemberExpression>;
const property = lvalue.get('property');

View File

@@ -234,7 +234,14 @@ function pushEndScopeTerminal(
const fallthroughId = context.fallthroughs.get(scope.id);
CompilerError.invariant(fallthroughId != null, {
reason: 'Expected scope to exist',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
context.rewrites.push({
kind: 'EndScope',

View File

@@ -31,7 +31,6 @@ import {
PropertyLiteral,
ReactiveScopeDependency,
ScopeId,
SourceLocation,
TInstruction,
} from './HIR';
@@ -245,7 +244,6 @@ class PropertyPathRegistry {
getOrCreateIdentifier(
identifier: Identifier,
reactive: boolean,
loc: SourceLocation,
): PropertyPathNode {
/**
* Reads from a statically scoped variable are always safe in JS,
@@ -262,7 +260,6 @@ class PropertyPathRegistry {
identifier,
reactive,
path: [],
loc,
},
hasOptional: false,
parent: null,
@@ -272,7 +269,14 @@ class PropertyPathRegistry {
CompilerError.invariant(reactive === rootNode.fullPath.reactive, {
reason:
'[HoistablePropertyLoads] Found inconsistencies in `reactive` flag when deduping identifier reads within the same scope',
loc: identifier.loc,
description: null,
details: [
{
kind: 'error',
loc: identifier.loc,
message: null,
},
],
});
}
return rootNode;
@@ -293,7 +297,6 @@ class PropertyPathRegistry {
identifier: parent.fullPath.identifier,
reactive: parent.fullPath.reactive,
path: parent.fullPath.path.concat(entry),
loc: entry.loc,
},
hasOptional: parent.hasOptional || entry.optional,
};
@@ -308,7 +311,7 @@ class PropertyPathRegistry {
* so all subpaths of a PropertyLoad should already exist
* (e.g. a.b is added before a.b.c),
*/
let currNode = this.getOrCreateIdentifier(n.identifier, n.reactive, n.loc);
let currNode = this.getOrCreateIdentifier(n.identifier, n.reactive);
if (n.path.length === 0) {
return currNode;
}
@@ -327,21 +330,20 @@ class PropertyPathRegistry {
}
function getMaybeNonNullInInstruction(
value: InstructionValue,
instr: InstructionValue,
context: CollectHoistablePropertyLoadsContext,
): PropertyPathNode | null {
let path: ReactiveScopeDependency | null = null;
if (value.kind === 'PropertyLoad') {
path = context.temporaries.get(value.object.identifier.id) ?? {
identifier: value.object.identifier,
reactive: value.object.reactive,
if (instr.kind === 'PropertyLoad') {
path = context.temporaries.get(instr.object.identifier.id) ?? {
identifier: instr.object.identifier,
reactive: instr.object.reactive,
path: [],
loc: value.loc,
};
} else if (value.kind === 'Destructure') {
path = context.temporaries.get(value.value.identifier.id) ?? null;
} else if (value.kind === 'ComputedLoad') {
path = context.temporaries.get(value.object.identifier.id) ?? null;
} else if (instr.kind === 'Destructure') {
path = context.temporaries.get(instr.value.identifier.id) ?? null;
} else if (instr.kind === 'ComputedLoad') {
path = context.temporaries.get(instr.object.identifier.id) ?? null;
}
return path != null ? context.registry.getOrCreateProperty(path) : null;
}
@@ -398,11 +400,7 @@ function collectNonNullsInBlocks(
) {
const identifier = fn.params[0].identifier;
knownNonNullIdentifiers.add(
context.registry.getOrCreateIdentifier(
identifier,
true,
fn.params[0].loc,
),
context.registry.getOrCreateIdentifier(identifier, true),
);
}
const nodes = new Map<
@@ -477,7 +475,6 @@ function collectNonNullsInBlocks(
identifier: dep.root.value.identifier,
path: dep.path.slice(0, i),
reactive: dep.root.value.reactive,
loc: dep.loc,
});
assumedNonNullObjects.add(depNode);
}
@@ -534,7 +531,14 @@ function propagateNonNull(
if (node == null) {
CompilerError.invariant(false, {
reason: `Bad node ${nodeId}, kind: ${direction}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
}
const neighbors = Array.from(
@@ -606,7 +610,14 @@ function propagateNonNull(
CompilerError.invariant(i++ < 100, {
reason:
'[CollectHoistablePropertyLoads] fixed point iteration did not terminate after 100 loops',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
changed = false;
@@ -638,7 +649,13 @@ export function assertNonNull<T extends NonNullable<U>, U>(
CompilerError.invariant(value != null, {
reason: 'Unexpected null',
description: source != null ? `(from ${source})` : null,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
return value;
}
@@ -664,23 +681,17 @@ function reduceMaybeOptionalChains(
changed = false;
for (const original of optionalChainNodes) {
let {
identifier,
path: origPath,
reactive,
loc: origLoc,
} = original.fullPath;
let {identifier, path: origPath, reactive} = original.fullPath;
let currNode: PropertyPathNode = registry.getOrCreateIdentifier(
identifier,
reactive,
origLoc,
);
for (let i = 0; i < origPath.length; i++) {
const entry = origPath[i];
// If the base is known to be non-null, replace with a non-optional load
const nextEntry: DependencyPathEntry =
entry.optional && nodes.has(currNode)
? {property: entry.property, optional: false, loc: entry.loc}
? {property: entry.property, optional: false}
: entry;
currNode = PropertyPathRegistry.getOrCreatePropertyEntry(
currNode,

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, SourceLocation} from '..';
import {CompilerError} from '..';
import {assertNonNull} from './CollectHoistablePropertyLoads';
import {
BlockId,
@@ -169,7 +169,6 @@ function matchOptionalTestBlock(
propertyId: IdentifierId;
storeLocalInstr: Instruction;
consequentGoto: BlockId;
propertyLoadLoc: SourceLocation;
} | null {
const consequentBlock = assertNonNull(blocks.get(terminal.consequent));
if (
@@ -187,7 +186,13 @@ function matchOptionalTestBlock(
reason:
'[OptionalChainDeps] Inconsistent optional chaining property load',
description: `Test=${printIdentifier(terminal.test.identifier)} PropertyLoad base=${printIdentifier(propertyLoad.value.object.identifier)}`,
loc: propertyLoad.loc,
details: [
{
kind: 'error',
loc: propertyLoad.loc,
message: null,
},
],
},
);
@@ -195,7 +200,14 @@ function matchOptionalTestBlock(
storeLocal.value.identifier.id === propertyLoad.lvalue.identifier.id,
{
reason: '[OptionalChainDeps] Unexpected storeLocal',
loc: propertyLoad.loc,
description: null,
details: [
{
kind: 'error',
loc: propertyLoad.loc,
message: null,
},
],
},
);
if (
@@ -212,7 +224,14 @@ function matchOptionalTestBlock(
alternate.instructions[1].value.kind === 'StoreLocal',
{
reason: 'Unexpected alternate structure',
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
},
);
@@ -222,7 +241,6 @@ function matchOptionalTestBlock(
propertyId: propertyLoad.lvalue.identifier.id,
storeLocalInstr,
consequentGoto: consequentBlock.terminal.block,
propertyLoadLoc: propertyLoad.loc,
};
}
return null;
@@ -249,7 +267,14 @@ function traverseOptionalBlock(
if (maybeTest.terminal.kind === 'branch') {
CompilerError.invariant(optional.terminal.optional, {
reason: '[OptionalChainDeps] Expect base case to be always optional',
loc: optional.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: optional.terminal.loc,
message: null,
},
],
});
/**
* Optional base expressions are currently within value blocks which cannot
@@ -277,11 +302,7 @@ function traverseOptionalBlock(
instrVal.kind === 'PropertyLoad' &&
instrVal.object.identifier.id === prevInstr.lvalue.identifier.id
) {
path.push({
property: instrVal.property,
optional: false,
loc: instrVal.loc,
});
path.push({property: instrVal.property, optional: false});
} else {
return null;
}
@@ -291,14 +312,20 @@ function traverseOptionalBlock(
maybeTest.instructions.at(-1)!.lvalue.identifier.id,
{
reason: '[OptionalChainDeps] Unexpected test expression',
loc: maybeTest.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: maybeTest.terminal.loc,
message: null,
},
],
},
);
baseObject = {
identifier: maybeTest.instructions[0].value.place.identifier,
reactive: maybeTest.instructions[0].value.place.reactive,
path,
loc: maybeTest.instructions[0].value.place.loc,
};
test = maybeTest.terminal;
} else if (maybeTest.terminal.kind === 'optional') {
@@ -381,7 +408,14 @@ function traverseOptionalBlock(
reason:
'[OptionalChainDeps] Unexpected instructions an inner optional block. ' +
'This indicates that the compiler may be incorrectly concatenating two unrelated optional chains',
loc: optional.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: optional.terminal.loc,
message: null,
},
],
});
}
const matchConsequentResult = matchOptionalTestBlock(test, context.blocks);
@@ -394,10 +428,16 @@ function traverseOptionalBlock(
{
reason: '[OptionalChainDeps] Unexpected optional goto-fallthrough',
description: `${matchConsequentResult.consequentGoto} != ${optional.terminal.fallthrough}`,
loc: optional.terminal.loc,
details: [
{
kind: 'error',
loc: optional.terminal.loc,
message: null,
},
],
},
);
const load: ReactiveScopeDependency = {
const load = {
identifier: baseObject.identifier,
reactive: baseObject.reactive,
path: [
@@ -405,10 +445,8 @@ function traverseOptionalBlock(
{
property: matchConsequentResult.property,
optional: optional.terminal.optional,
loc: matchConsequentResult.propertyLoadLoc,
},
],
loc: matchConsequentResult.propertyLoadLoc,
};
context.processedInstrsInOptional.add(matchConsequentResult.storeLocalInstr);
context.processedInstrsInOptional.add(test);

View File

@@ -5,12 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {
BlockId,
GeneratedSource,
HIRFunction,
computePostDominatorTree,
} from '.';
import {BlockId, HIRFunction, computePostDominatorTree} from '.';
import {CompilerError} from '..';
export function computeUnconditionalBlocks(fn: HIRFunction): Set<BlockId> {
@@ -29,7 +24,15 @@ export function computeUnconditionalBlocks(fn: HIRFunction): Set<BlockId> {
CompilerError.invariant(!unconditionalBlocks.has(current), {
reason:
'Internal error: non-terminating loop in ComputeUnconditionalBlocks',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
unconditionalBlocks.add(current);
current = dominators.get(current);

View File

@@ -12,7 +12,6 @@ import {
Identifier,
PropertyLiteral,
ReactiveScopeDependency,
SourceLocation,
} from '../HIR';
import {printIdentifier} from '../HIR/PrintHIR';
@@ -37,13 +36,12 @@ export class ReactiveScopeDependencyTreeHIR {
* duplicates when traversing the CFG.
*/
constructor(hoistableObjects: Iterable<ReactiveScopeDependency>) {
for (const {path, identifier, reactive, loc} of hoistableObjects) {
for (const {path, identifier, reactive} of hoistableObjects) {
let currNode = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
identifier,
reactive,
this.#hoistableObjects,
path.length > 0 && path[0].optional ? 'Optional' : 'NonNull',
loc,
);
for (let i = 0; i < path.length; i++) {
@@ -56,7 +54,14 @@ export class ReactiveScopeDependencyTreeHIR {
prevAccessType == null || prevAccessType === accessType,
{
reason: 'Conflicting access types',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
},
);
let nextNode = currNode.properties.get(path[i].property);
@@ -64,7 +69,6 @@ export class ReactiveScopeDependencyTreeHIR {
nextNode = {
properties: new Map(),
accessType,
loc: path[i].loc,
};
currNode.properties.set(path[i].property, nextNode);
}
@@ -78,7 +82,6 @@ export class ReactiveScopeDependencyTreeHIR {
reactive: boolean,
roots: Map<Identifier, TreeNode<T> & {reactive: boolean}>,
defaultAccessType: T,
loc: SourceLocation,
): TreeNode<T> {
// roots can always be accessed unconditionally in JS
let rootNode = roots.get(identifier);
@@ -88,14 +91,19 @@ export class ReactiveScopeDependencyTreeHIR {
properties: new Map(),
reactive,
accessType: defaultAccessType,
loc,
};
roots.set(identifier, rootNode);
} else {
CompilerError.invariant(reactive === rootNode.reactive, {
reason: '[DeriveMinimalDependenciesHIR] Conflicting reactive root flag',
description: `Identifier ${printIdentifier(identifier)}`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
}
return rootNode;
@@ -107,13 +115,12 @@ export class ReactiveScopeDependencyTreeHIR {
* safe-to-evaluate subpath
*/
addDependency(dep: ReactiveScopeDependency): void {
const {identifier, reactive, path, loc} = dep;
const {identifier, reactive, path} = dep;
let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
identifier,
reactive,
this.#deps,
PropertyAccessType.UnconditionalAccess,
loc,
);
/**
* hoistableCursor is null if depCursor is not an object we can hoist
@@ -159,7 +166,6 @@ export class ReactiveScopeDependencyTreeHIR {
depCursor,
entry.property,
accessType,
entry.loc,
);
} else if (
hoistableCursor != null &&
@@ -170,7 +176,6 @@ export class ReactiveScopeDependencyTreeHIR {
depCursor,
entry.property,
PropertyAccessType.UnconditionalAccess,
entry.loc,
);
} else {
/**
@@ -314,7 +319,6 @@ function merge(
type TreeNode<T extends string> = {
properties: Map<PropertyLiteral, TreeNode<T>>;
accessType: T;
loc: SourceLocation;
};
type HoistableNode = TreeNode<'Optional' | 'NonNull'>;
type DependencyNode = TreeNode<PropertyAccessType>;
@@ -332,7 +336,7 @@ function collectMinimalDependenciesInSubtree(
results: Set<ReactiveScopeDependency>,
): void {
if (isDependency(node.accessType)) {
results.add({identifier: rootIdentifier, reactive, path, loc: node.loc});
results.add({identifier: rootIdentifier, reactive, path});
} else {
for (const [childName, childNode] of node.properties) {
collectMinimalDependenciesInSubtree(
@@ -344,7 +348,6 @@ function collectMinimalDependenciesInSubtree(
{
property: childName,
optional: isOptional(childNode.accessType),
loc: childNode.loc,
},
],
results,
@@ -372,14 +375,12 @@ function makeOrMergeProperty(
node: DependencyNode,
property: PropertyLiteral,
accessType: PropertyAccessType,
loc: SourceLocation,
): DependencyNode {
let child = node.properties.get(property);
if (child == null) {
child = {
properties: new Map(),
accessType,
loc,
};
node.properties.set(property, child);
} else {

View File

@@ -7,7 +7,7 @@
import prettyFormat from 'pretty-format';
import {CompilerError} from '../CompilerError';
import {BlockId, GeneratedSource, HIRFunction} from './HIR';
import {BlockId, HIRFunction} from './HIR';
import {eachTerminalSuccessor} from './visitors';
/*
@@ -88,7 +88,15 @@ export class Dominator<T> {
const dominator = this.#nodes.get(id);
CompilerError.invariant(dominator !== undefined, {
reason: 'Unknown node',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return dominator === id ? null : dominator;
}
@@ -127,7 +135,15 @@ export class PostDominator<T> {
const dominator = this.#nodes.get(id);
CompilerError.invariant(dominator !== undefined, {
reason: 'Unknown node',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return dominator === id ? null : dominator;
}
@@ -170,7 +186,15 @@ function computeImmediateDominators<T>(graph: Graph<T>): Map<T, T> {
}
CompilerError.invariant(newIdom !== null, {
reason: `At least one predecessor must have been visited for block ${id}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
for (const pred of node.preds) {

View File

@@ -24,7 +24,6 @@ import {
BuiltInType,
Effect,
FunctionType,
GeneratedSource,
HIRFunction,
IdentifierId,
NonLocalBinding,
@@ -817,7 +816,15 @@ export class Environment {
for (const [hookName, hook] of this.config.customHooks) {
CompilerError.invariant(!this.#globals.has(hookName), {
reason: `[Globals] Found existing definition in global registry for custom hook ${hookName}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
this.#globals.set(
hookName,
@@ -849,7 +856,14 @@ export class Environment {
CompilerError.invariant(code != null, {
reason:
'Expected Environment to be initialized with source code when a Flow type provider is specified',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
});
this.#flowTypeEnvironment.init(this, code);
} else {
@@ -860,7 +874,14 @@ export class Environment {
get typeContext(): FlowTypeEnv {
CompilerError.invariant(this.#flowTypeEnvironment != null, {
reason: 'Flow type environment not initialized',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
});
return this.#flowTypeEnvironment;
}
@@ -1172,7 +1193,15 @@ export class Environment {
CompilerError.invariant(shape !== undefined, {
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return shape.properties.get('*') ?? null;
}
@@ -1195,7 +1224,15 @@ export class Environment {
const shape = this.#shapes.get(shapeId);
CompilerError.invariant(shape !== undefined, {
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
if (typeof property === 'string') {
return (
@@ -1218,7 +1255,15 @@ export class Environment {
const shape = this.#shapes.get(shapeId);
CompilerError.invariant(shape !== undefined, {
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return shape.functionType;
}

View File

@@ -183,13 +183,29 @@ function handleAssignment(
const valuePath = property.get('value');
CompilerError.invariant(valuePath.isLVal(), {
reason: `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
loc: valuePath.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: valuePath.node.loc ?? GeneratedSource,
message: null,
},
],
suggestions: null,
});
handleAssignment(currentFn, identifiers, valuePath);
} else {
CompilerError.invariant(property.isRestElement(), {
reason: `[FindContextIdentifiers] Invalid assumptions for babel types.`,
loc: property.node.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: property.node.loc ?? GeneratedSource,
message: null,
},
],
suggestions: null,
});
handleAssignment(currentFn, identifiers, property);
}

View File

@@ -612,7 +612,7 @@ export type TryTerminal = {
export type MaybeThrowTerminal = {
kind: 'maybe-throw';
continuation: BlockId;
handler: BlockId | null;
handler: BlockId;
id: InstructionId;
loc: SourceLocation;
fallthrough?: never;
@@ -1373,7 +1373,14 @@ export function promoteTemporary(identifier: Identifier): void {
CompilerError.invariant(identifier.name === null, {
reason: `Expected a temporary (unnamed) identifier`,
description: `Identifier already has a name, \`${identifier.name}\``,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
suggestions: null,
});
identifier.name = {
kind: 'promoted',
@@ -1396,7 +1403,14 @@ export function promoteTemporaryJsxTag(identifier: Identifier): void {
CompilerError.invariant(identifier.name === null, {
reason: `Expected a temporary (unnamed) identifier`,
description: `Identifier already has a name, \`${identifier.name}\``,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
suggestions: null,
});
identifier.name = {
kind: 'promoted',
@@ -1562,7 +1576,15 @@ export function isMutableEffect(
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
loc: location,
description: null,
details: [
{
kind: 'error',
loc: location,
message: null,
},
],
suggestions: null,
});
}
case Effect.Read:
@@ -1639,7 +1661,6 @@ export function makePropertyLiteral(value: string | number): PropertyLiteral {
export type DependencyPathEntry = {
property: PropertyLiteral;
optional: boolean;
loc: SourceLocation;
};
export type DependencyPath = Array<DependencyPathEntry>;
export type ReactiveScopeDependency = {
@@ -1657,7 +1678,6 @@ export type ReactiveScopeDependency = {
*/
reactive: boolean;
path: DependencyPath;
loc: SourceLocation;
};
export function areEqualPaths(a: DependencyPath, b: DependencyPath): boolean {
@@ -1717,7 +1737,15 @@ export type BlockId = number & {[opaqueBlockId]: 'BlockId'};
export function makeBlockId(id: number): BlockId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected block id to be a non-negative integer',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return id as BlockId;
}
@@ -1732,7 +1760,15 @@ export type ScopeId = number & {[opaqueScopeId]: 'ScopeId'};
export function makeScopeId(id: number): ScopeId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected block id to be a non-negative integer',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return id as ScopeId;
}
@@ -1747,7 +1783,15 @@ export type IdentifierId = number & {[opaqueIdentifierId]: 'IdentifierId'};
export function makeIdentifierId(id: number): IdentifierId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected identifier id to be a non-negative integer',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return id as IdentifierId;
}
@@ -1762,7 +1806,15 @@ export type DeclarationId = number & {[opageDeclarationId]: 'DeclarationId'};
export function makeDeclarationId(id: number): DeclarationId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected declaration id to be a non-negative integer',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return id as DeclarationId;
}
@@ -1777,7 +1829,15 @@ export type InstructionId = number & {[opaqueInstructionId]: 'IdentifierId'};
export function makeInstructionId(id: number): InstructionId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected instruction id to be a non-negative integer',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return id as InstructionId;
}

View File

@@ -506,7 +506,15 @@ export default class HIRBuilder {
last.breakBlock === breakBlock,
{
reason: 'Mismatched label',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
},
);
return value;
@@ -527,7 +535,15 @@ export default class HIRBuilder {
last.breakBlock === breakBlock,
{
reason: 'Mismatched label',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
},
);
return value;
@@ -561,7 +577,15 @@ export default class HIRBuilder {
last.breakBlock === breakBlock,
{
reason: 'Mismatched loops',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
},
);
return value;
@@ -584,7 +608,15 @@ export default class HIRBuilder {
}
CompilerError.invariant(false, {
reason: 'Expected a loop or switch to be in scope',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
}
@@ -603,13 +635,29 @@ export default class HIRBuilder {
} else if (label !== null && scope.label === label) {
CompilerError.invariant(false, {
reason: 'Continue may only refer to a labeled loop',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
}
}
CompilerError.invariant(false, {
reason: 'Expected a loop to be in scope',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
}
}
@@ -630,7 +678,15 @@ function _shrink(func: HIR): void {
const block = func.blocks.get(blockId);
CompilerError.invariant(block != null, {
reason: `expected block ${blockId} to exist`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
target = getTargetIfIndirection(block);
if (target !== null) {
@@ -761,7 +817,13 @@ function getReversePostorderedBlocks(func: HIR): HIR['blocks'] {
CompilerError.invariant(block != null, {
reason: '[HIRBuilder] Unexpected null block',
description: `expected block ${blockId} to exist`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
const successors = [...eachTerminalSuccessor(block.terminal)].reverse();
const fallthrough = terminalFallthrough(block.terminal);
@@ -816,7 +878,15 @@ export function markInstructionIds(func: HIR): void {
for (const instr of block.instructions) {
CompilerError.invariant(!visited.has(instr), {
reason: `${printInstruction(instr)} already visited!`,
loc: instr.loc,
description: null,
details: [
{
kind: 'error',
loc: instr.loc,
message: null,
},
],
suggestions: null,
});
visited.add(instr);
instr.id = makeInstructionId(++id);
@@ -838,7 +908,13 @@ export function markPredecessors(func: HIR): void {
CompilerError.invariant(block != null, {
reason: 'unexpected missing block',
description: `block ${blockId}`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
if (prevBlock) {
block.preds.add(prevBlock.id);

View File

@@ -60,7 +60,15 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
const predecessor = fn.body.blocks.get(predecessorId);
CompilerError.invariant(predecessor !== undefined, {
reason: `Expected predecessor ${predecessorId} to exist`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
if (predecessor.terminal.kind !== 'goto' || predecessor.kind !== 'block') {
/*
@@ -74,7 +82,15 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
for (const phi of block.phis) {
CompilerError.invariant(phi.operands.size === 1, {
reason: `Found a block with a single predecessor but where a phi has multiple (${phi.operands.size}) operands`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
const operand = Array.from(phi.operands.values())[0]!;
const lvalue: Place = {

View File

@@ -119,7 +119,13 @@ function parseAliasingSignatureConfig(
CompilerError.invariant(!lifetimes.has(temp), {
reason: `Invalid type configuration for module`,
description: `Expected aliasing signature to have unique names for receiver, params, rest, returns, and temporaries in module '${moduleName}'`,
loc,
details: [
{
kind: 'error',
loc,
message: null,
},
],
});
const place = signatureArgument(lifetimes.size);
lifetimes.set(temp, place);
@@ -130,7 +136,13 @@ function parseAliasingSignatureConfig(
CompilerError.invariant(place != null, {
reason: `Invalid type configuration for module`,
description: `Expected aliasing signature effects to reference known names from receiver/params/rest/returns/temporaries, but '${temp}' is not a known name in '${moduleName}'`,
loc,
details: [
{
kind: 'error',
loc,
message: null,
},
],
});
return place;
}
@@ -264,7 +276,15 @@ function addShape(
CompilerError.invariant(!registry.has(id), {
reason: `[ObjectShape] Could not add shape to registry: name ${id} already exists.`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
registry.set(id, shape);
return shape;

View File

@@ -291,9 +291,7 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
break;
}
case 'maybe-throw': {
const handlerStr =
terminal.handler !== null ? `bb${terminal.handler}` : '(none)';
value = `[${terminal.id}] MaybeThrow continuation=bb${terminal.continuation} handler=${handlerStr}`;
value = `[${terminal.id}] MaybeThrow continuation=bb${terminal.continuation} handler=bb${terminal.handler}`;
if (terminal.effects != null) {
value += `\n ${terminal.effects.map(printAliasingEffect).join('\n ')}`;
}
@@ -600,7 +598,15 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
instrValue.subexprs.length === instrValue.quasis.length - 1,
{
reason: 'Bad assumption about quasi length.',
loc: instrValue.loc,
description: null,
details: [
{
kind: 'error',
loc: instrValue.loc,
message: null,
},
],
suggestions: null,
},
);
for (let i = 0; i < instrValue.subexprs.length; i++) {
@@ -868,7 +874,15 @@ export function printManualMemoDependency(
} else {
CompilerError.invariant(val.root.value.identifier.name?.kind === 'named', {
reason: 'DepsValidation: expected named local variable in depslist',
loc: val.root.value.loc,
description: null,
suggestions: null,
details: [
{
kind: 'error',
loc: val.root.value.loc,
message: null,
},
],
});
rootStr = nameOnly
? val.root.value.identifier.name.value

View File

@@ -31,7 +31,6 @@ import {
ObjectMethod,
PropertyLiteral,
convertHoistedLValueKind,
SourceLocation,
} from './HIR';
import {
collectHoistablePropertyLoads,
@@ -87,7 +86,14 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
const hoistables = hoistablePropertyLoads.get(scope.id);
CompilerError.invariant(hoistables != null, {
reason: '[PropagateScopeDependencies] Scope not found in tracked blocks',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
/**
* Step 2: Calculate hoistable dependencies.
@@ -299,7 +305,6 @@ function collectTemporariesSidemapImpl(
value.object,
value.property,
false,
value.loc,
temporaries,
);
temporaries.set(lvalue.identifier.id, property);
@@ -320,7 +325,6 @@ function collectTemporariesSidemapImpl(
identifier: value.place.identifier,
reactive: value.place.reactive,
path: [],
loc: value.loc,
});
}
} else if (
@@ -342,7 +346,6 @@ function getProperty(
object: Place,
propertyName: PropertyLiteral,
optional: boolean,
loc: SourceLocation,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
): ReactiveScopeDependency {
/*
@@ -375,18 +378,13 @@ function getProperty(
property = {
identifier: object.identifier,
reactive: object.reactive,
path: [{property: propertyName, optional, loc}],
loc,
path: [{property: propertyName, optional}],
};
} else {
property = {
identifier: resolvedDependency.identifier,
reactive: resolvedDependency.reactive,
path: [
...resolvedDependency.path,
{property: propertyName, optional, loc},
],
loc,
path: [...resolvedDependency.path, {property: propertyName, optional}],
};
}
return property;
@@ -437,7 +435,14 @@ export class DependencyCollectionContext {
const scopedDependencies = this.#dependencies.value;
CompilerError.invariant(scopedDependencies != null, {
reason: '[PropagateScopeDeps]: Unexpected scope mismatch',
loc: scope.loc,
description: null,
details: [
{
kind: 'error',
loc: scope.loc,
message: null,
},
],
});
// Restore context of previous scope
@@ -546,7 +551,6 @@ export class DependencyCollectionContext {
identifier: place.identifier,
reactive: place.reactive,
path: [],
loc: place.loc,
},
);
}
@@ -555,13 +559,11 @@ export class DependencyCollectionContext {
object: Place,
property: PropertyLiteral,
optional: boolean,
loc: SourceLocation,
): void {
const nextDependency = getProperty(
object,
property,
optional,
loc,
this.#temporaries,
);
this.visitDependency(nextDependency);
@@ -614,7 +616,6 @@ export class DependencyCollectionContext {
identifier: maybeDependency.identifier,
reactive: maybeDependency.reactive,
path: [],
loc: maybeDependency.loc,
};
}
if (this.#checkValidDependency(maybeDependency)) {
@@ -639,7 +640,6 @@ export class DependencyCollectionContext {
identifier: place.identifier,
reactive: place.reactive,
path: [],
loc: place.loc,
})
) {
currentScope.reassignments.add(place.identifier);
@@ -693,7 +693,7 @@ export function handleInstruction(
return;
}
if (value.kind === 'PropertyLoad') {
context.visitProperty(value.object, value.property, false, value.loc);
context.visitProperty(value.object, value.property, false);
} else if (value.kind === 'StoreLocal') {
context.visitOperand(value.value);
if (value.lvalue.kind === InstructionKind.Reassign) {

View File

@@ -53,7 +53,14 @@ export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
next.phis.size === 0 && fallthrough.phis.size === 0,
{
reason: 'Unexpected phis when merging label blocks',
loc: label.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: label.terminal.loc,
message: null,
},
],
},
);
@@ -64,7 +71,14 @@ export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
fallthrough.preds.has(nextId),
{
reason: 'Unexpected block predecessors when merging label blocks',
loc: label.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: label.terminal.loc,
message: null,
},
],
},
);

View File

@@ -202,7 +202,15 @@ function writeOptionalDependency(
CompilerError.invariant(firstOptional !== -1, {
reason:
'[ScopeDependencyUtils] Internal invariant broken: expected optional path',
loc: dep.identifier.loc,
description: null,
details: [
{
kind: 'error',
loc: dep.identifier.loc,
message: null,
},
],
suggestions: null,
});
if (firstOptional === dep.path.length - 1) {
// Base case: the test block is simple
@@ -236,7 +244,15 @@ function writeOptionalDependency(
builder.enterReserved(consequent, () => {
CompilerError.invariant(testIdentifier !== null, {
reason: 'Satisfy type checker',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
lowerValueToTemporary(builder, {

View File

@@ -6,7 +6,7 @@
*/
import {CompilerError} from '../CompilerError';
import {GeneratedSource, PropertyLiteral} from './HIR';
import {PropertyLiteral} from './HIR';
export type BuiltInType = PrimitiveType | FunctionType | ObjectType;
@@ -86,7 +86,15 @@ export type TypeId = number & {[opaqueTypeId]: 'IdentifierId'};
export function makeTypeId(id: number): TypeId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected instruction id to be a non-negative integer',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return id as TypeId;
}

View File

@@ -909,7 +909,7 @@ export function mapTerminalSuccessors(
}
case 'maybe-throw': {
const continuation = fn(terminal.continuation);
const handler = terminal.handler !== null ? fn(terminal.handler) : null;
const handler = fn(terminal.handler);
return {
kind: 'maybe-throw',
continuation,
@@ -1083,9 +1083,7 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
}
case 'maybe-throw': {
yield terminal.continuation;
if (terminal.handler !== null) {
yield terminal.handler;
}
yield terminal.handler;
break;
}
case 'try': {
@@ -1262,7 +1260,14 @@ export class ScopeBlockTraversal {
CompilerError.invariant(blockInfo.scope.id === top, {
reason:
'Expected traversed block fallthrough to match top-most active scope',
loc: block.instructions[0]?.loc ?? block.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: block.instructions[0]?.loc ?? block.terminal.id,
message: null,
},
],
});
this.#activeScopes.pop();
}
@@ -1276,7 +1281,14 @@ export class ScopeBlockTraversal {
!this.blockInfos.has(block.terminal.fallthrough),
{
reason: 'Expected unique scope blocks and fallthroughs',
loc: block.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: block.terminal.loc,
message: null,
},
],
},
);
this.blockInfos.set(block.terminal.block, {

View File

@@ -78,7 +78,14 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
case 'Apply': {
CompilerError.invariant(false, {
reason: `[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects`,
loc: effect.function.loc,
description: null,
details: [
{
kind: 'error',
loc: effect.function.loc,
message: null,
},
],
});
}
case 'Mutate':

View File

@@ -74,10 +74,7 @@ export function collectMaybeMemoDependencies(
return {
root: object.root,
// TODO: determine if the access is optional
path: [
...object.path,
{property: value.property, optional, loc: value.loc},
],
path: [...object.path, {property: value.property, optional}],
loc: value.loc,
};
}
@@ -587,14 +584,24 @@ function findOptionalPlaces(fn: HIRFunction): Set<IdentifierId> {
break;
}
case 'maybe-throw': {
testBlock = fn.body.blocks.get(terminal.continuation)!;
break;
CompilerError.throwTodo({
reason: `Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement`,
description: null,
loc: terminal.loc,
suggestions: null,
});
}
default: {
CompilerError.invariant(false, {
reason: `Unexpected terminal in optional`,
message: `Unexpected ${terminal.kind} in optional`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: `Unexpected ${terminal.kind} in optional`,
},
],
});
}
}

View File

@@ -438,7 +438,14 @@ function rewriteSplices(
{
reason:
'[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted',
loc: originalInstrs[cursor].loc,
description: null,
details: [
{
kind: 'error',
loc: originalInstrs[cursor].loc,
message: null,
},
],
},
);
currBlock.instructions.push(originalInstrs[cursor]);
@@ -447,7 +454,14 @@ function rewriteSplices(
CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, {
reason:
'[InferEffectDependencies] Internal invariant broken: splice location not found',
loc: originalInstrs[cursor].loc,
description: null,
details: [
{
kind: 'error',
loc: originalInstrs[cursor].loc,
message: null,
},
],
});
if (rewrite.kind === 'instr') {
@@ -467,7 +481,14 @@ function rewriteSplices(
{
reason:
'[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough',
loc: entryBlock.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: entryBlock.terminal.loc,
message: null,
},
],
},
);
const originalTerminal = currBlock.terminal;
@@ -566,7 +587,14 @@ function inferMinimalDependencies(
CompilerError.invariant(hoistableToFnEntry != null, {
reason:
'[InferEffectDependencies] Internal invariant broken: missing entry block',
loc: fnInstr.loc,
description: null,
details: [
{
kind: 'error',
loc: fnInstr.loc,
message: null,
},
],
});
const dependencies = inferDependencies(
@@ -622,7 +650,14 @@ function inferDependencies(
CompilerError.invariant(resultUnfiltered != null, {
reason:
'[InferEffectDependencies] Internal invariant broken: missing scope dependencies',
loc: fn.loc,
description: null,
details: [
{
kind: 'error',
loc: fn.loc,
message: null,
},
],
});
const fnContext = new Set(fn.context.map(dep => dep.identifier.id));

View File

@@ -134,7 +134,15 @@ export function inferMutationAliasingEffects(
CompilerError.invariant(fn.params.length <= 2, {
reason:
'Expected React component to have not more than two parameters: one for props and for ref',
loc: fn.loc,
description: null,
details: [
{
kind: 'error',
loc: fn.loc,
message: null,
},
],
suggestions: null,
});
const [props, ref] = fn.params;
if (props != null) {
@@ -201,7 +209,13 @@ export function inferMutationAliasingEffects(
CompilerError.invariant(false, {
reason: `[InferMutationAliasingEffects] Potential infinite loop`,
description: `A value, temporary place, or effect was not cached properly`,
loc: fn.loc,
details: [
{
kind: 'error',
loc: fn.loc,
message: null,
},
],
});
}
for (const [blockId, block] of fn.body.blocks) {
@@ -508,13 +522,20 @@ function inferBlock(
const terminal = block.terminal;
if (terminal.kind === 'try' && terminal.handlerBinding != null) {
context.catchHandlers.set(terminal.handler, terminal.handlerBinding);
} else if (terminal.kind === 'maybe-throw' && terminal.handler !== null) {
} else if (terminal.kind === 'maybe-throw') {
const handlerParam = context.catchHandlers.get(terminal.handler);
if (handlerParam != null) {
CompilerError.invariant(state.kind(handlerParam) != null, {
reason:
'Expected catch binding to be intialized with a DeclareLocal Catch instruction',
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
const effects: Array<AliasingEffect> = [];
for (const instr of block.instructions) {
@@ -664,7 +685,14 @@ function applySignature(
) {
CompilerError.invariant(false, {
reason: `Expected instruction lvalue to be initialized`,
loc: instruction.loc,
description: null,
details: [
{
kind: 'error',
loc: instruction.loc,
message: null,
},
],
});
}
return effects.length !== 0 ? effects : null;
@@ -693,7 +721,13 @@ function applyEffect(
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
reason: `Cannot re-initialize variable within an instruction`,
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
loc: effect.into.loc,
details: [
{
kind: 'error',
loc: effect.into.loc,
message: null,
},
],
});
initialized.add(effect.into.identifier.id);
@@ -732,7 +766,13 @@ function applyEffect(
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
reason: `Cannot re-initialize variable within an instruction`,
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
loc: effect.into.loc,
details: [
{
kind: 'error',
loc: effect.into.loc,
message: null,
},
],
});
initialized.add(effect.into.identifier.id);
@@ -792,7 +832,13 @@ function applyEffect(
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
reason: `Cannot re-initialize variable within an instruction`,
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
loc: effect.into.loc,
details: [
{
kind: 'error',
loc: effect.into.loc,
message: null,
},
],
});
initialized.add(effect.into.identifier.id);
@@ -870,7 +916,13 @@ function applyEffect(
description:
`Destination ${printPlace(effect.into)} is not initialized in this ` +
`instruction for effect ${printAliasingEffect(effect)}`,
loc: effect.into.loc,
details: [
{
kind: 'error',
loc: effect.into.loc,
message: null,
},
],
},
);
/*
@@ -948,7 +1000,13 @@ function applyEffect(
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
reason: `Cannot re-initialize variable within an instruction`,
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
loc: effect.into.loc,
details: [
{
kind: 'error',
loc: effect.into.loc,
message: null,
},
],
});
initialized.add(effect.into.identifier.id);
@@ -1348,7 +1406,15 @@ class InferenceState {
CompilerError.invariant(value.kind !== 'LoadLocal', {
reason:
'[InferMutationAliasingEffects] Expected all top-level identifiers to be defined as variables, not values',
loc: value.loc,
description: null,
details: [
{
kind: 'error',
loc: value.loc,
message: null,
},
],
suggestions: null,
});
this.#values.set(value, kind);
}
@@ -1358,8 +1424,14 @@ class InferenceState {
CompilerError.invariant(values != null, {
reason: `[InferMutationAliasingEffects] Expected value kind to be initialized`,
description: `${printPlace(place)}`,
message: 'this is uninitialized',
loc: place.loc,
details: [
{
kind: 'error',
loc: place.loc,
message: 'this is uninitialized',
},
],
suggestions: null,
});
return Array.from(values);
}
@@ -1370,8 +1442,14 @@ class InferenceState {
CompilerError.invariant(values != null, {
reason: `[InferMutationAliasingEffects] Expected value kind to be initialized`,
description: `${printPlace(place)}`,
message: 'this is uninitialized',
loc: place.loc,
details: [
{
kind: 'error',
loc: place.loc,
message: 'this is uninitialized',
},
],
suggestions: null,
});
let mergedKind: AbstractValue | null = null;
for (const value of values) {
@@ -1382,7 +1460,14 @@ class InferenceState {
CompilerError.invariant(mergedKind !== null, {
reason: `[InferMutationAliasingEffects] Expected at least one value`,
description: `No value found at \`${printPlace(place)}\``,
loc: place.loc,
details: [
{
kind: 'error',
loc: place.loc,
message: null,
},
],
suggestions: null,
});
return mergedKind;
}
@@ -1393,8 +1478,14 @@ class InferenceState {
CompilerError.invariant(values != null, {
reason: `[InferMutationAliasingEffects] Expected value for identifier to be initialized`,
description: `${printIdentifier(value.identifier)}`,
message: 'Expected value for identifier to be initialized',
loc: value.loc,
details: [
{
kind: 'error',
loc: value.loc,
message: 'Expected value for identifier to be initialized',
},
],
suggestions: null,
});
this.#variables.set(place.identifier.id, new Set(values));
}
@@ -1404,8 +1495,14 @@ class InferenceState {
CompilerError.invariant(values != null, {
reason: `[InferMutationAliasingEffects] Expected value for identifier to be initialized`,
description: `${printIdentifier(value.identifier)}`,
message: 'Expected value for identifier to be initialized',
loc: value.loc,
details: [
{
kind: 'error',
loc: value.loc,
message: 'Expected value for identifier to be initialized',
},
],
suggestions: null,
});
const prevValues = this.values(place);
this.#variables.set(
@@ -1419,7 +1516,14 @@ class InferenceState {
CompilerError.invariant(this.#values.has(value), {
reason: `[InferMutationAliasingEffects] Expected value to be initialized`,
description: printInstructionValue(value),
loc: value.loc,
details: [
{
kind: 'error',
loc: value.loc,
message: 'Expected value for identifier to be initialized',
},
],
suggestions: null,
});
this.#variables.set(place.identifier.id, new Set([value]));
}
@@ -2859,7 +2963,15 @@ export function isKnownMutableEffect(effect: Effect): boolean {
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
suggestions: null,
});
}
case Effect.Read:
@@ -2967,7 +3079,13 @@ function mergeValueKinds(a: ValueKind, b: ValueKind): ValueKind {
{
reason: `Unexpected value kind in mergeValues()`,
description: `Found kinds ${a} and ${b}`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
},
);
return ValueKind.Primitive;

View File

@@ -229,7 +229,14 @@ export function inferMutationAliasingRanges(
} else {
CompilerError.invariant(effect.kind === 'Freeze', {
reason: `Unexpected '${effect.kind}' effect for MaybeThrow terminal`,
loc: block.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: block.terminal.loc,
message: null,
},
],
});
}
}
@@ -378,7 +385,14 @@ export function inferMutationAliasingRanges(
case 'Apply': {
CompilerError.invariant(false, {
reason: `[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects`,
loc: effect.function.loc,
description: null,
details: [
{
kind: 'error',
loc: effect.function.loc,
message: null,
},
],
});
}
case 'MutateTransitive':
@@ -525,7 +539,14 @@ export function inferMutationAliasingRanges(
const fromNode = state.nodes.get(from.identifier);
CompilerError.invariant(fromNode != null, {
reason: `Expected a node to exist for all parameters and context variables`,
loc: into.loc,
description: null,
details: [
{
kind: 'error',
loc: into.loc,
message: null,
},
],
});
if (fromNode.lastMutated === mutationIndex) {
if (into.identifier.id === fn.returns.identifier.id) {

View File

@@ -310,7 +310,15 @@ export function inferReactivePlaces(fn: HIRFunction): void {
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
loc: operand.loc,
description: null,
details: [
{
kind: 'error',
loc: operand.loc,
message: null,
},
],
suggestions: null,
});
}
default: {

View File

@@ -8,7 +8,6 @@
import {isValidIdentifier} from '@babel/types';
import {CompilerError} from '../CompilerError';
import {
GeneratedSource,
GotoVariant,
HIRFunction,
IdentifierId,
@@ -192,7 +191,15 @@ function evaluatePhi(phi: Phi, constants: Constants): Constant | null {
case 'Primitive': {
CompilerError.invariant(value.kind === 'Primitive', {
reason: 'value kind expected to be Primitive',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
// different constant values, can't constant propogate
@@ -204,7 +211,15 @@ function evaluatePhi(phi: Phi, constants: Constants): Constant | null {
case 'LoadGlobal': {
CompilerError.invariant(value.kind === 'LoadGlobal', {
reason: 'value kind expected to be LoadGlobal',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
// different global values, can't constant propogate

View File

@@ -709,7 +709,14 @@ function createPropsProperties(
const spreadProp = jsxSpreadAttributes[0];
CompilerError.invariant(spreadProp.kind === 'JsxSpreadAttribute', {
reason: 'Spread prop attribute must be of kind JSXSpreadAttribute',
loc: instr.loc,
description: null,
details: [
{
kind: 'error',
loc: instr.loc,
message: null,
},
],
});
propsProperty = {
kind: 'ObjectProperty',

View File

@@ -78,10 +78,17 @@ export function instructionReordering(fn: HIRFunction): void {
}
CompilerError.invariant(shared.size === 0, {
reason: `InstructionReordering: expected all reorderable nodes to have been emitted`,
loc:
[...shared.values()]
.map(node => node.instruction?.loc)
.filter(loc => loc != null)[0] ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc:
[...shared.values()]
.map(node => node.instruction?.loc)
.filter(loc => loc != null)[0] ?? GeneratedSource,
message: null,
},
],
});
markInstructionIds(fn.body);
}
@@ -302,11 +309,17 @@ function reorderBlock(
node.reorderability === Reorderability.Reorderable,
{
reason: `Expected all remaining instructions to be reorderable`,
details: [
{
kind: 'error',
loc: node.instruction?.loc ?? block.terminal.loc,
message: null,
},
],
description:
node.instruction != null
? `Instruction [${node.instruction.id}] was not emitted yet but is not reorderable`
: `Lvalue $${id} was not emitted yet but is not reorderable`,
loc: node.instruction?.loc ?? block.terminal.loc,
},
);

View File

@@ -178,8 +178,14 @@ export function optimizeForSSR(fn: HIRFunction): void {
{
reason:
'Expected a valid destructuring pattern for inlined state',
message: 'Expected a valid destructuring pattern',
loc: value.loc,
description: null,
details: [
{
kind: 'error',
message: 'Expected a valid destructuring pattern',
loc: value.loc,
},
],
},
);
const store: StoreLocal = {

View File

@@ -9,6 +9,7 @@ import {CompilerError} from '..';
import {
BlockId,
GeneratedSource,
GotoVariant,
HIRFunction,
Instruction,
assertConsistentIdentifiers,
@@ -24,15 +25,9 @@ import {
} from '../HIR/HIRBuilder';
import {printPlace} from '../HIR/PrintHIR';
/**
* This pass updates `maybe-throw` terminals for blocks that can provably *never* throw,
* nulling out the handler to indicate that control will always continue. Note that
* rewriting to a `goto` disrupts the structure of the HIR, making it more difficult to
* reconstruct an ast during BuildReactiveFunction. Preserving the maybe-throw makes the
* continuations clear, while nulling out the handler tells us that control cannot flow
* to the handler.
*
* For now the analysis is very conservative, and only affects blocks with primitives or
/*
* This pass prunes `maybe-throw` terminals for blocks that can provably *never* throw.
* For now this is very conservative, and only affects blocks with primitives or
* array/object literals. Even a variable reference could throw bc of the TDZ.
*/
export function pruneMaybeThrows(fn: HIRFunction): void {
@@ -57,10 +52,17 @@ export function pruneMaybeThrows(fn: HIRFunction): void {
const mappedTerminal = terminalMapping.get(predecessor);
CompilerError.invariant(mappedTerminal != null, {
reason: `Expected non-existing phi operand's predecessor to have been mapped to a new terminal`,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
description: `Could not find mapping for predecessor bb${predecessor} in block bb${
block.id
} for phi ${printPlace(phi.place)}`,
loc: GeneratedSource,
suggestions: null,
});
phi.operands.delete(predecessor);
phi.operands.set(mappedTerminal, operand);
@@ -87,7 +89,13 @@ function pruneMaybeThrowsImpl(fn: HIRFunction): Map<BlockId, BlockId> | null {
if (!canThrow) {
const source = terminalMapping.get(block.id) ?? block.id;
terminalMapping.set(terminal.continuation, source);
terminal.handler = null;
block.terminal = {
kind: 'goto',
block: terminal.continuation,
variant: GotoVariant.Break,
id: terminal.id,
loc: terminal.loc,
};
}
}
return terminalMapping.size > 0 ? terminalMapping : null;

View File

@@ -41,7 +41,15 @@ function findScopesToMerge(fn: HIRFunction): DisjointSet<ReactiveScope> {
{
reason:
'Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.',
loc: GeneratedSource,
description: null,
suggestions: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
},
);
mergeScopesBuilder.union([operandScope, lvalueScope]);

View File

@@ -170,7 +170,14 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
CompilerError.invariant(!valueBlockNodes.has(fallthrough), {
reason: 'Expect hir blocks to have unique fallthroughs',
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
if (node != null) {
valueBlockNodes.set(fallthrough, node);
@@ -252,7 +259,14 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
// Transition from block->value block, derive the outer block range
CompilerError.invariant(fallthrough !== null, {
reason: `Expected a fallthrough for value block`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
const fallthroughBlock = fn.body.blocks.get(fallthrough)!;
const nextId =

View File

@@ -84,7 +84,14 @@ class CheckInstructionsAgainstScopesVisitor extends ReactiveFunctionVisitor<
reason:
'Encountered an instruction that should be part of a scope, but where that scope has already completed',
description: `Instruction [${id}] is part of scope @${scope.id}, but that scope has already completed`,
loc: place.loc,
details: [
{
kind: 'error',
loc: place.loc,
message: null,
},
],
suggestions: null,
});
}
}

View File

@@ -28,7 +28,14 @@ class Visitor extends ReactiveFunctionVisitor<Set<BlockId>> {
if (terminal.kind === 'break' || terminal.kind === 'continue') {
CompilerError.invariant(seenLabels.has(terminal.target), {
reason: 'Unexpected break to invalid label',
loc: stmt.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: stmt.terminal.loc,
message: null,
},
],
});
}
}

View File

@@ -9,7 +9,6 @@ import {CompilerError} from '../CompilerError';
import {
BasicBlock,
BlockId,
GeneratedSource,
GotoVariant,
HIR,
InstructionId,
@@ -22,7 +21,6 @@ import {
ReactiveBreakTerminal,
ReactiveContinueTerminal,
ReactiveFunction,
ReactiveInstruction,
ReactiveLogicalValue,
ReactiveSequenceValue,
ReactiveTerminalStatement,
@@ -63,139 +61,6 @@ class Driver {
this.cx = cx;
}
/*
* Wraps a continuation result with preceding instructions. If there are no
* instructions, returns the continuation as-is. Otherwise, wraps the continuation's
* value in a SequenceExpression with the instructions prepended.
*/
wrapWithSequence(
instructions: Array<ReactiveInstruction>,
continuation: {
block: BlockId;
value: ReactiveValue;
place: Place;
id: InstructionId;
},
loc: SourceLocation,
): {block: BlockId; value: ReactiveValue; place: Place; id: InstructionId} {
if (instructions.length === 0) {
return continuation;
}
const sequence: ReactiveSequenceValue = {
kind: 'SequenceExpression',
instructions,
id: continuation.id,
value: continuation.value,
loc,
};
return {
block: continuation.block,
value: sequence,
place: continuation.place,
id: continuation.id,
};
}
/*
* Extracts the result value from instructions at the end of a value block.
* Value blocks generally end in a StoreLocal to assign the value of the
* expression. These StoreLocal instructions can be pruned since we represent
* value blocks as compound values in ReactiveFunction (no phis). However,
* it's also possible to have a value block that ends in an AssignmentExpression,
* which we need to keep. So we only prune StoreLocal for temporaries.
*/
extractValueBlockResult(
instructions: BasicBlock['instructions'],
blockId: BlockId,
loc: SourceLocation,
): {block: BlockId; place: Place; value: ReactiveValue; id: InstructionId} {
CompilerError.invariant(instructions.length !== 0, {
reason: `Expected non-empty instructions in extractValueBlockResult`,
description: null,
loc,
});
const instr = instructions.at(-1)!;
let place: Place = instr.lvalue;
let value: ReactiveValue = instr.value;
if (
value.kind === 'StoreLocal' &&
value.lvalue.place.identifier.name === null
) {
place = value.lvalue.place;
value = {
kind: 'LoadLocal',
place: value.value,
loc: value.value.loc,
};
}
if (instructions.length === 1) {
return {block: blockId, place, value, id: instr.id};
}
const sequence: ReactiveSequenceValue = {
kind: 'SequenceExpression',
instructions: instructions.slice(0, -1),
id: instr.id,
value,
loc,
};
return {block: blockId, place, value: sequence, id: instr.id};
}
/*
* Converts the result of visitValueBlock into a SequenceExpression that includes
* the instruction with its lvalue. This is needed for for/for-of/for-in init/test
* blocks where the instruction's lvalue assignment must be preserved.
*
* This also flattens nested SequenceExpressions that can occur from MaybeThrow
* handling in try-catch blocks.
*/
valueBlockResultToSequence(
result: {
block: BlockId;
value: ReactiveValue;
place: Place;
id: InstructionId;
},
loc: SourceLocation,
): ReactiveSequenceValue {
// Collect all instructions from potentially nested SequenceExpressions
const instructions: Array<ReactiveInstruction> = [];
let innerValue: ReactiveValue = result.value;
// Flatten nested SequenceExpressions
while (innerValue.kind === 'SequenceExpression') {
instructions.push(...innerValue.instructions);
innerValue = innerValue.value;
}
/*
* Only add the final instruction if the innermost value is not just a LoadLocal
* of the same place we're storing to (which would be a no-op).
* This happens when MaybeThrow blocks cause the sequence to already contain
* all the necessary instructions.
*/
const isLoadOfSamePlace =
innerValue.kind === 'LoadLocal' &&
innerValue.place.identifier.id === result.place.identifier.id;
if (!isLoadOfSamePlace) {
instructions.push({
id: result.id,
lvalue: result.place,
value: innerValue,
loc,
});
}
return {
kind: 'SequenceExpression',
instructions,
id: result.id,
value: {kind: 'Primitive', value: undefined, loc},
loc,
};
}
traverseBlock(block: BasicBlock): ReactiveBlock {
const blockValue: ReactiveBlock = [];
this.visitBlock(block, blockValue);
@@ -205,7 +70,15 @@ class Driver {
visitBlock(block: BasicBlock, blockValue: ReactiveBlock): void {
CompilerError.invariant(!this.cx.emitted.has(block.id), {
reason: `Cannot emit the same block twice: bb${block.id}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
this.cx.emitted.add(block.id);
for (const instruction of block.instructions) {
@@ -264,7 +137,14 @@ class Driver {
if (this.cx.isScheduled(terminal.consequent)) {
CompilerError.invariant(false, {
reason: `Unexpected 'if' where the consequent is already scheduled`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
} else {
consequent = this.traverseBlock(
@@ -277,7 +157,14 @@ class Driver {
if (this.cx.isScheduled(alternateId)) {
CompilerError.invariant(false, {
reason: `Unexpected 'if' where the alternate is already scheduled`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
} else {
alternate = this.traverseBlock(this.cx.ir.blocks.get(alternateId)!);
@@ -330,7 +217,14 @@ class Driver {
if (this.cx.isScheduled(case_.block)) {
CompilerError.invariant(case_.block === terminal.fallthrough, {
reason: `Unexpected 'switch' where a case is already scheduled and block is not the fallthrough`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
return;
} else {
@@ -389,7 +283,14 @@ class Driver {
} else {
CompilerError.invariant(false, {
reason: `Unexpected 'do-while' where the loop is already scheduled`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
}
@@ -450,7 +351,14 @@ class Driver {
} else {
CompilerError.invariant(false, {
reason: `Unexpected 'while' where the loop is already scheduled`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
}
@@ -496,7 +404,29 @@ class Driver {
scheduleIds.push(scheduleId);
const init = this.visitValueBlock(terminal.init, terminal.loc);
const initValue = this.valueBlockResultToSequence(init, terminal.loc);
const initBlock = this.cx.ir.blocks.get(init.block)!;
let initValue = init.value;
if (initValue.kind === 'SequenceExpression') {
const last = initBlock.instructions.at(-1)!;
initValue.instructions.push(last);
initValue.value = {
kind: 'Primitive',
value: undefined,
loc: terminal.loc,
};
} else {
initValue = {
kind: 'SequenceExpression',
instructions: [initBlock.instructions.at(-1)!],
id: terminal.id,
loc: terminal.loc,
value: {
kind: 'Primitive',
value: undefined,
loc: terminal.loc,
},
};
}
const testValue = this.visitValueBlock(
terminal.test,
@@ -514,7 +444,14 @@ class Driver {
} else {
CompilerError.invariant(false, {
reason: `Unexpected 'for' where the loop is already scheduled`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
}
@@ -557,10 +494,54 @@ class Driver {
scheduleIds.push(scheduleId);
const init = this.visitValueBlock(terminal.init, terminal.loc);
const initValue = this.valueBlockResultToSequence(init, terminal.loc);
const initBlock = this.cx.ir.blocks.get(init.block)!;
let initValue = init.value;
if (initValue.kind === 'SequenceExpression') {
const last = initBlock.instructions.at(-1)!;
initValue.instructions.push(last);
initValue.value = {
kind: 'Primitive',
value: undefined,
loc: terminal.loc,
};
} else {
initValue = {
kind: 'SequenceExpression',
instructions: [initBlock.instructions.at(-1)!],
id: terminal.id,
loc: terminal.loc,
value: {
kind: 'Primitive',
value: undefined,
loc: terminal.loc,
},
};
}
const test = this.visitValueBlock(terminal.test, terminal.loc);
const testValue = this.valueBlockResultToSequence(test, terminal.loc);
const testBlock = this.cx.ir.blocks.get(test.block)!;
let testValue = test.value;
if (testValue.kind === 'SequenceExpression') {
const last = testBlock.instructions.at(-1)!;
testValue.instructions.push(last);
testValue.value = {
kind: 'Primitive',
value: undefined,
loc: terminal.loc,
};
} else {
testValue = {
kind: 'SequenceExpression',
instructions: [testBlock.instructions.at(-1)!],
id: terminal.id,
loc: terminal.loc,
value: {
kind: 'Primitive',
value: undefined,
loc: terminal.loc,
},
};
}
let loopBody: ReactiveBlock;
if (loopId) {
@@ -568,7 +549,14 @@ class Driver {
} else {
CompilerError.invariant(false, {
reason: `Unexpected 'for-of' where the loop is already scheduled`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
}
@@ -610,7 +598,29 @@ class Driver {
scheduleIds.push(scheduleId);
const init = this.visitValueBlock(terminal.init, terminal.loc);
const initValue = this.valueBlockResultToSequence(init, terminal.loc);
const initBlock = this.cx.ir.blocks.get(init.block)!;
let initValue = init.value;
if (initValue.kind === 'SequenceExpression') {
const last = initBlock.instructions.at(-1)!;
initValue.instructions.push(last);
initValue.value = {
kind: 'Primitive',
value: undefined,
loc: terminal.loc,
};
} else {
initValue = {
kind: 'SequenceExpression',
instructions: [initBlock.instructions.at(-1)!],
id: terminal.id,
loc: terminal.loc,
value: {
kind: 'Primitive',
value: undefined,
loc: terminal.loc,
},
};
}
let loopBody: ReactiveBlock;
if (loopId) {
@@ -618,7 +628,14 @@ class Driver {
} else {
CompilerError.invariant(false, {
reason: `Unexpected 'for-in' where the loop is already scheduled`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
}
@@ -661,7 +678,14 @@ class Driver {
if (this.cx.isScheduled(terminal.alternate)) {
CompilerError.invariant(false, {
reason: `Unexpected 'branch' where the alternate is already scheduled`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
} else {
alternate = this.traverseBlock(
@@ -699,7 +723,14 @@ class Driver {
if (this.cx.isScheduled(terminal.block)) {
CompilerError.invariant(false, {
reason: `Unexpected 'label' where the block is already scheduled`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
} else {
block = this.traverseBlock(this.cx.ir.blocks.get(terminal.block)!);
@@ -857,7 +888,14 @@ class Driver {
if (this.cx.isScheduled(terminal.block)) {
CompilerError.invariant(false, {
reason: `Unexpected 'scope' where the block is already scheduled`,
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
} else {
block = this.traverseBlock(this.cx.ir.blocks.get(terminal.block)!);
@@ -882,7 +920,15 @@ class Driver {
case 'unsupported': {
CompilerError.invariant(false, {
reason: 'Unexpected unsupported terminal',
loc: terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
suggestions: null,
});
}
default: {
@@ -892,138 +938,180 @@ class Driver {
}
visitValueBlock(
blockId: BlockId,
id: BlockId,
loc: SourceLocation,
fallthrough: BlockId | null = null,
): {block: BlockId; value: ReactiveValue; place: Place; id: InstructionId} {
const block = this.cx.ir.blocks.get(blockId)!;
// If we've reached the fallthrough block, stop recursing
if (fallthrough !== null && blockId === fallthrough) {
CompilerError.invariant(false, {
reason: 'Did not expect to reach the fallthrough of a value block',
description: `Reached bb${blockId}, which is the fallthrough for this value block`,
loc,
});
}
if (block.terminal.kind === 'branch') {
if (block.instructions.length === 0) {
const defaultBlock = this.cx.ir.blocks.get(id)!;
if (defaultBlock.terminal.kind === 'branch') {
const instructions = defaultBlock.instructions;
if (instructions.length === 0) {
return {
block: block.id,
place: block.terminal.test,
block: defaultBlock.id,
place: defaultBlock.terminal.test,
value: {
kind: 'LoadLocal',
place: block.terminal.test,
loc: block.terminal.test.loc,
place: defaultBlock.terminal.test,
loc: defaultBlock.terminal.test.loc,
},
id: block.terminal.id,
id: defaultBlock.terminal.id,
};
} else if (defaultBlock.instructions.length === 1) {
const instr = defaultBlock.instructions[0]!;
CompilerError.invariant(
instr.lvalue.identifier.id ===
defaultBlock.terminal.test.identifier.id,
{
reason:
'Expected branch block to end in an instruction that sets the test value',
description: null,
details: [
{
kind: 'error',
loc: instr.lvalue.loc,
message: null,
},
],
suggestions: null,
},
);
return {
block: defaultBlock.id,
place: instr.lvalue!,
value: instr.value,
id: instr.id,
};
} else {
const instr = defaultBlock.instructions.at(-1)!;
const sequence: ReactiveSequenceValue = {
kind: 'SequenceExpression',
instructions: defaultBlock.instructions.slice(0, -1),
id: instr.id,
value: instr.value,
loc: loc,
};
return {
block: defaultBlock.id,
place: defaultBlock.terminal.test,
value: sequence,
id: defaultBlock.terminal.id,
};
}
return this.extractValueBlockResult(block.instructions, block.id, loc);
} else if (block.terminal.kind === 'goto') {
if (block.instructions.length === 0) {
} else if (defaultBlock.terminal.kind === 'goto') {
const instructions = defaultBlock.instructions;
if (instructions.length === 0) {
CompilerError.invariant(false, {
reason: 'Unexpected empty block with `goto` terminal',
description: `Block bb${block.id} is empty`,
loc,
reason: 'Expected goto value block to have at least one instruction',
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
} else if (defaultBlock.instructions.length === 1) {
const instr = defaultBlock.instructions[0]!;
let place: Place = instr.lvalue;
let value: ReactiveValue = instr.value;
if (
/*
* Value blocks generally end in a StoreLocal to assign the value of the
* expression for this branch. These StoreLocal instructions can be pruned,
* since we represent the value blocks as a compund value in ReactiveFunction
* (no phis). However, it's also possible to have a value block that ends in
* an AssignmentExpression, which we need to keep. So we only prune
* StoreLocal for temporaries — any named/promoted values must be used
* elsewhere and aren't safe to prune.
*/
value.kind === 'StoreLocal' &&
value.lvalue.place.identifier.name === null
) {
place = value.lvalue.place;
value = {
kind: 'LoadLocal',
place: value.value,
loc: value.value.loc,
};
}
return {
block: defaultBlock.id,
place,
value,
id: instr.id,
};
} else {
const instr = defaultBlock.instructions.at(-1)!;
let place: Place = instr.lvalue;
let value: ReactiveValue = instr.value;
if (
/*
* Value blocks generally end in a StoreLocal to assign the value of the
* expression for this branch. These StoreLocal instructions can be pruned,
* since we represent the value blocks as a compund value in ReactiveFunction
* (no phis). However, it's also possible to have a value block that ends in
* an AssignmentExpression, which we need to keep. So we only prune
* StoreLocal for temporaries — any named/promoted values must be used
* elsewhere and aren't safe to prune.
*/
value.kind === 'StoreLocal' &&
value.lvalue.place.identifier.name === null
) {
place = value.lvalue.place;
value = {
kind: 'LoadLocal',
place: value.value,
loc: value.value.loc,
};
}
const sequence: ReactiveSequenceValue = {
kind: 'SequenceExpression',
instructions: defaultBlock.instructions.slice(0, -1),
id: instr.id,
value,
loc: loc,
};
return {
block: defaultBlock.id,
place,
value: sequence,
id: instr.id,
};
}
return this.extractValueBlockResult(block.instructions, block.id, loc);
} else if (block.terminal.kind === 'maybe-throw') {
/*
* ReactiveFunction does not explicitly model maybe-throw semantics,
* so maybe-throw terminals in value blocks flatten away. In general
* we recurse to the continuation block.
*
* However, if the last portion
* of the value block is a potentially throwing expression, then the
* value block could be of the form
* ```
* bb1:
* ...StoreLocal for the value block...
* maybe-throw continuation=bb2
* bb2:
* goto (exit the value block)
* ```
*
* Ie what would have been a StoreLocal+goto is split up because of
* the maybe-throw. We detect this case and return the value of the
* current block as the result of the value block
*/
const continuationId = block.terminal.continuation;
const continuationBlock = this.cx.ir.blocks.get(continuationId)!;
if (
continuationBlock.instructions.length === 0 &&
continuationBlock.terminal.kind === 'goto'
) {
return this.extractValueBlockResult(
block.instructions,
continuationBlock.id,
loc,
);
}
const continuation = this.visitValueBlock(
continuationId,
loc,
fallthrough,
);
return this.wrapWithSequence(block.instructions, continuation, loc);
} else {
/*
* The value block ended in a value terminal, recurse to get the value
* of that terminal and stitch them together in a sequence.
* of that terminal
*/
const init = this.visitValueBlockTerminal(block.terminal);
const init = this.visitValueBlockTerminal(defaultBlock.terminal);
// Code following the logical terminal
const final = this.visitValueBlock(init.fallthrough, loc);
return this.wrapWithSequence(
[
...block.instructions,
{id: init.id, loc, lvalue: init.place, value: init.value},
// Stitch the two together...
const sequence: ReactiveSequenceValue = {
kind: 'SequenceExpression',
instructions: [
...defaultBlock.instructions,
{
id: init.id,
loc,
lvalue: init.place,
value: init.value,
},
],
final,
id: final.id,
value: final.value,
loc,
);
};
return {
block: final.block,
value: sequence,
place: final.place,
id: final.id,
};
}
}
/*
* Visits the test block of a value terminal (optional, logical, ternary) and
* returns the result along with the branch terminal. Throws a todo error if
* the test block does not end in a branch terminal.
*/
visitTestBlock(
testBlockId: BlockId,
loc: SourceLocation,
terminalKind: string,
): {
test: {
block: BlockId;
value: ReactiveValue;
place: Place;
id: InstructionId;
};
branch: {consequent: BlockId; alternate: BlockId; loc: SourceLocation};
} {
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,
loc: testBlock.terminal.loc,
suggestions: null,
});
}
return {
test,
branch: {
consequent: testBlock.terminal.consequent,
alternate: testBlock.terminal.alternate,
loc: testBlock.terminal.loc,
},
};
}
visitValueBlockTerminal(terminal: Terminal): {
value: ReactiveValue;
place: Place;
@@ -1032,11 +1120,7 @@ class Driver {
} {
switch (terminal.kind) {
case 'sequence': {
const block = this.visitValueBlock(
terminal.block,
terminal.loc,
terminal.fallthrough,
);
const block = this.visitValueBlock(terminal.block, terminal.loc);
return {
value: block.value,
place: block.place,
@@ -1045,22 +1129,26 @@ class Driver {
};
}
case 'optional': {
const {test, branch} = this.visitTestBlock(
terminal.test,
terminal.loc,
'optional',
);
const test = this.visitValueBlock(terminal.test, terminal.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 optional test block`,
description: null,
loc: testBlock.terminal.loc,
suggestions: null,
});
}
const consequent = this.visitValueBlock(
branch.consequent,
testBlock.terminal.consequent,
terminal.loc,
terminal.fallthrough,
);
const call: ReactiveSequenceValue = {
kind: 'SequenceExpression',
instructions: [
{
id: test.id,
loc: branch.loc,
loc: testBlock.terminal.loc,
lvalue: test.place,
value: test.value,
},
@@ -1083,15 +1171,20 @@ class Driver {
};
}
case 'logical': {
const {test, branch} = this.visitTestBlock(
terminal.test,
terminal.loc,
'logical',
);
const test = this.visitValueBlock(terminal.test, terminal.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 logical test block`,
description: null,
loc: testBlock.terminal.loc,
suggestions: null,
});
}
const leftFinal = this.visitValueBlock(
branch.consequent,
testBlock.terminal.consequent,
terminal.loc,
terminal.fallthrough,
);
const left: ReactiveSequenceValue = {
kind: 'SequenceExpression',
@@ -1108,9 +1201,8 @@ class Driver {
loc: terminal.loc,
};
const right = this.visitValueBlock(
branch.alternate,
testBlock.terminal.alternate,
terminal.loc,
terminal.fallthrough,
);
const value: ReactiveLogicalValue = {
kind: 'LogicalExpression',
@@ -1127,20 +1219,23 @@ class Driver {
};
}
case 'ternary': {
const {test, branch} = this.visitTestBlock(
terminal.test,
terminal.loc,
'ternary',
);
const test = this.visitValueBlock(terminal.test, terminal.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 ternary test block`,
description: null,
loc: testBlock.terminal.loc,
suggestions: null,
});
}
const consequent = this.visitValueBlock(
branch.consequent,
testBlock.terminal.consequent,
terminal.loc,
terminal.fallthrough,
);
const alternate = this.visitValueBlock(
branch.alternate,
testBlock.terminal.alternate,
terminal.loc,
terminal.fallthrough,
);
const value: ReactiveTernaryValue = {
kind: 'ConditionalExpression',
@@ -1158,10 +1253,11 @@ class Driver {
};
}
case 'maybe-throw': {
CompilerError.invariant(false, {
reason: `Unexpected maybe-throw in visitValueBlockTerminal - should be handled in visitValueBlock`,
CompilerError.throwTodo({
reason: `Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement`,
description: null,
loc: terminal.loc,
suggestions: null,
});
}
case 'label': {
@@ -1196,13 +1292,28 @@ class Driver {
if (target === null) {
CompilerError.invariant(false, {
reason: 'Expected a break target',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
}
if (this.cx.scopeFallthroughs.has(target.block)) {
CompilerError.invariant(target.type === 'implicit', {
reason: 'Expected reactive scope to implicitly break to fallthrough',
loc,
description: null,
details: [
{
kind: 'error',
loc,
message: null,
},
],
});
return null;
}
@@ -1227,7 +1338,15 @@ class Driver {
const target = this.cx.getContinueTarget(block);
CompilerError.invariant(target !== null, {
reason: `Expected continue target to be scheduled for bb${block}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return {
@@ -1300,7 +1419,15 @@ class Context {
const id = this.#nextScheduleId++;
CompilerError.invariant(!this.#scheduled.has(block), {
reason: `Break block is already scheduled: bb${block}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
this.#scheduled.add(block);
this.#controlFlowStack.push({block, id, type});
@@ -1317,7 +1444,15 @@ class Context {
this.#scheduled.add(fallthroughBlock);
CompilerError.invariant(!this.#scheduled.has(continueBlock), {
reason: `Continue block is already scheduled: bb${continueBlock}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
this.#scheduled.add(continueBlock);
let ownsLoop = false;
@@ -1343,7 +1478,15 @@ class Context {
const last = this.#controlFlowStack.pop();
CompilerError.invariant(last !== undefined && last.id === scheduleId, {
reason: 'Can only unschedule the last target',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
if (last.type !== 'loop' || last.ownsBlock !== null) {
this.#scheduled.delete(last.block);
@@ -1416,7 +1559,15 @@ class Context {
CompilerError.invariant(false, {
reason: 'Expected a break target',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
}

View File

@@ -297,7 +297,15 @@ export function codegenFunction(
CompilerError.invariant(globalGating != null, {
reason:
'Bad config not caught! Expected at least one of gating or globalGating',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
ifTest = globalGating;
}
@@ -501,7 +509,15 @@ function codegenBlock(cx: Context, block: ReactiveBlock): t.BlockStatement {
}
CompilerError.invariant(temp.get(key)! === value, {
reason: 'Expected temporary value to be unchanged',
loc: GeneratedSource,
description: null,
suggestions: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
});
}
cx.temp = temp;
@@ -672,7 +688,14 @@ function codegenReactiveScope(
description: `Declaration \`${printIdentifier(
identifier,
)}\` is unnamed in scope @${scope.id}`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
const name = convertIdentifier(identifier);
@@ -708,7 +731,14 @@ function codegenReactiveScope(
CompilerError.invariant(firstOutputIndex !== null, {
reason: `Expected scope to have at least one declaration`,
description: `Scope '@${scope.id}' has no declarations`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
testCondition = t.binaryExpression(
'===',
@@ -730,7 +760,13 @@ function codegenReactiveScope(
{
reason: `Expected to not have both change detection enabled and memoization disabled`,
description: `Incompatible config options`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
},
);
testCondition = t.logicalExpression(
@@ -914,7 +950,15 @@ function codegenReactiveScope(
earlyReturnValue.value.name.kind === 'named',
{
reason: `Expected early return value to be promoted to a named variable`,
loc: earlyReturnValue.loc,
description: null,
details: [
{
kind: 'error',
loc: earlyReturnValue.loc,
message: null,
},
],
suggestions: null,
},
);
const name: ValidIdentifierName = earlyReturnValue.value.name.value;
@@ -976,7 +1020,14 @@ function codegenTerminal(
CompilerError.invariant(terminal.init.kind === 'SequenceExpression', {
reason: `Expected a sequence expression init for for..in`,
description: `Got \`${terminal.init.kind}\` expression instead`,
loc: terminal.init.loc,
details: [
{
kind: 'error',
loc: terminal.init.loc,
message: null,
},
],
suggestions: null,
});
if (terminal.init.instructions.length !== 2) {
CompilerError.throwTodo({
@@ -1010,7 +1061,14 @@ function codegenTerminal(
CompilerError.invariant(false, {
reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
description: `Found ${iterableItem.value.kind}`,
loc: iterableItem.value.loc,
details: [
{
kind: 'error',
loc: iterableItem.value.loc,
message: null,
},
],
suggestions: null,
});
}
let varDeclKind: 'const' | 'let';
@@ -1025,7 +1083,15 @@ function codegenTerminal(
CompilerError.invariant(false, {
reason:
'Destructure should never be Reassign as it would be an Object/ArrayPattern',
loc: iterableItem.loc,
description: null,
details: [
{
kind: 'error',
loc: iterableItem.loc,
message: null,
},
],
suggestions: null,
});
case InstructionKind.Catch:
case InstructionKind.HoistedConst:
@@ -1034,7 +1100,15 @@ function codegenTerminal(
case InstructionKind.Function:
CompilerError.invariant(false, {
reason: `Unexpected ${iterableItem.value.lvalue.kind} variable in for..in collection`,
loc: iterableItem.loc,
description: null,
details: [
{
kind: 'error',
loc: iterableItem.loc,
message: null,
},
],
suggestions: null,
});
default:
assertExhaustive(
@@ -1063,7 +1137,14 @@ function codegenTerminal(
{
reason: `Expected a single-expression sequence expression init for for..of`,
description: `Got \`${terminal.init.kind}\` expression instead`,
loc: terminal.init.loc,
details: [
{
kind: 'error',
loc: terminal.init.loc,
message: null,
},
],
suggestions: null,
},
);
const iterableCollection = terminal.init.instructions[0].value;
@@ -1071,7 +1152,14 @@ function codegenTerminal(
CompilerError.invariant(terminal.test.kind === 'SequenceExpression', {
reason: `Expected a sequence expression test for for..of`,
description: `Got \`${terminal.init.kind}\` expression instead`,
loc: terminal.test.loc,
details: [
{
kind: 'error',
loc: terminal.test.loc,
message: null,
},
],
suggestions: null,
});
if (terminal.test.instructions.length !== 2) {
CompilerError.throwTodo({
@@ -1104,7 +1192,14 @@ function codegenTerminal(
CompilerError.invariant(false, {
reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
description: `Found ${iterableItem.value.kind}`,
loc: iterableItem.value.loc,
details: [
{
kind: 'error',
loc: iterableItem.value.loc,
message: null,
},
],
suggestions: null,
});
}
let varDeclKind: 'const' | 'let';
@@ -1123,7 +1218,15 @@ function codegenTerminal(
case InstructionKind.Function:
CompilerError.invariant(false, {
reason: `Unexpected ${iterableItem.value.lvalue.kind} variable in for..of collection`,
loc: iterableItem.loc,
description: null,
details: [
{
kind: 'error',
loc: iterableItem.loc,
message: null,
},
],
suggestions: null,
});
default:
assertExhaustive(
@@ -1160,9 +1263,9 @@ function codegenTerminal(
const value = codegenPlaceToExpression(cx, terminal.value);
if (value.type === 'Identifier' && value.name === 'undefined') {
// Use implicit undefined
return createReturnStatement(terminal.loc);
return t.returnStatement();
}
return createReturnStatement(terminal.loc, value);
return t.returnStatement(value);
}
case 'switch': {
return createSwitchStatement(
@@ -1273,8 +1376,15 @@ function codegenInstructionNullable(
case InstructionKind.Const: {
CompilerError.invariant(instr.lvalue === null, {
reason: `Const declaration cannot be referenced as an expression`,
message: `this is ${kind}`,
loc: instr.value.loc,
description: null,
details: [
{
kind: 'error',
loc: instr.value.loc,
message: `this is ${kind}`,
},
],
suggestions: null,
});
return createVariableDeclaration(instr.loc, 'const', [
createVariableDeclarator(codegenLValue(cx, lvalue), value),
@@ -1283,17 +1393,40 @@ function codegenInstructionNullable(
case InstructionKind.Function: {
CompilerError.invariant(instr.lvalue === null, {
reason: `Function declaration cannot be referenced as an expression`,
loc: instr.value.loc,
description: null,
details: [
{
kind: 'error',
loc: instr.value.loc,
message: `this is ${kind}`,
},
],
suggestions: null,
});
const genLvalue = codegenLValue(cx, lvalue);
CompilerError.invariant(genLvalue.type === 'Identifier', {
reason: 'Expected an identifier as a function declaration lvalue',
loc: instr.value.loc,
description: null,
details: [
{
kind: 'error',
loc: instr.value.loc,
message: null,
},
],
suggestions: null,
});
CompilerError.invariant(value?.type === 'FunctionExpression', {
reason: 'Expected a function as a function declaration value',
description: `Got ${value == null ? String(value) : value.type} at ${printInstruction(instr)}`,
loc: instr.value.loc,
details: [
{
kind: 'error',
loc: instr.value.loc,
message: null,
},
],
suggestions: null,
});
return createFunctionDeclaration(
instr.loc,
@@ -1307,8 +1440,15 @@ function codegenInstructionNullable(
case InstructionKind.Let: {
CompilerError.invariant(instr.lvalue === null, {
reason: `Const declaration cannot be referenced as an expression`,
message: `this is ${kind}`,
loc: instr.value.loc,
description: null,
details: [
{
kind: 'error',
loc: instr.value.loc,
message: 'this is const',
},
],
suggestions: null,
});
return createVariableDeclaration(instr.loc, 'let', [
createVariableDeclarator(codegenLValue(cx, lvalue), value),
@@ -1317,7 +1457,15 @@ function codegenInstructionNullable(
case InstructionKind.Reassign: {
CompilerError.invariant(value !== null, {
reason: 'Expected a value for reassignment',
loc: instr.value.loc,
description: null,
details: [
{
kind: 'error',
loc: instr.value.loc,
message: null,
},
],
suggestions: null,
});
const expr = t.assignmentExpression(
'=',
@@ -1348,7 +1496,15 @@ function codegenInstructionNullable(
case InstructionKind.HoistedFunction: {
CompilerError.invariant(false, {
reason: `Expected ${kind} to have been pruned in PruneHoistedContexts`,
loc: instr.loc,
description: null,
details: [
{
kind: 'error',
loc: instr.loc,
message: null,
},
],
suggestions: null,
});
}
default: {
@@ -1365,7 +1521,15 @@ function codegenInstructionNullable(
} else if (instr.value.kind === 'ObjectMethod') {
CompilerError.invariant(instr.lvalue, {
reason: 'Expected object methods to have a temp lvalue',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
cx.objectMethods.set(instr.lvalue.identifier.id, instr.value);
return null;
@@ -1411,8 +1575,15 @@ function codegenForInit(
(instr.kind === 'let' || instr.kind === 'const'),
{
reason: 'Expected a variable declaration',
details: [
{
kind: 'error',
loc: init.loc,
message: null,
},
],
description: `Got ${instr.type}`,
loc: init.loc,
suggestions: null,
},
);
if (instr.kind === 'let') {
@@ -1423,7 +1594,15 @@ function codegenForInit(
});
CompilerError.invariant(declarators.length > 0, {
reason: 'Expected a variable declaration',
loc: init.loc,
details: [
{
kind: 'error',
loc: init.loc,
message: null,
},
],
description: null,
suggestions: null,
});
return t.variableDeclaration(kind, declarators);
} else {
@@ -1545,7 +1724,6 @@ const createThrowStatement = withLoc(t.throwStatement);
const createTryStatement = withLoc(t.tryStatement);
const createBreakStatement = withLoc(t.breakStatement);
const createContinueStatement = withLoc(t.continueStatement);
const createReturnStatement = withLoc(t.returnStatement);
function createVariableDeclarator(
id: t.LVal,
@@ -1780,7 +1958,15 @@ function codegenInstructionValue(
case 'CallExpression': {
CompilerError.invariant(t.isExpression(optionalValue.callee), {
reason: 'v8 intrinsics are validated during lowering',
loc: optionalValue.callee.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: optionalValue.callee.loc ?? null,
message: null,
},
],
suggestions: null,
});
value = t.optionalCallExpression(
optionalValue.callee,
@@ -1794,7 +1980,15 @@ function codegenInstructionValue(
const property = optionalValue.property;
CompilerError.invariant(t.isExpression(property), {
reason: 'Private names are validated during lowering',
loc: property.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: property.loc ?? null,
message: null,
},
],
suggestions: null,
});
value = t.optionalMemberExpression(
optionalValue.object,
@@ -1809,7 +2003,14 @@ function codegenInstructionValue(
reason:
'Expected an optional value to resolve to a call expression or member expression',
description: `Got a \`${optionalValue.type}\``,
loc: instrValue.loc,
details: [
{
kind: 'error',
loc: instrValue.loc,
message: null,
},
],
suggestions: null,
});
}
}
@@ -1825,8 +2026,15 @@ function codegenInstructionValue(
{
reason:
'[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression',
message: `Got: '${memberExpr.type}'`,
loc: memberExpr.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: memberExpr.loc ?? null,
message: `Got: '${memberExpr.type}'`,
},
],
suggestions: null,
},
);
CompilerError.invariant(
@@ -1838,7 +2046,15 @@ function codegenInstructionValue(
reason:
'[Codegen] Internal error: Forget should always generate MethodCall::property ' +
'as a MemberExpression of MethodCall::receiver',
loc: memberExpr.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: memberExpr.loc ?? null,
message: null,
},
],
suggestions: null,
},
);
const args = instrValue.args.map(arg => codegenArgument(cx, arg));
@@ -1882,7 +2098,15 @@ function codegenInstructionValue(
const method = cx.objectMethods.get(property.place.identifier.id);
CompilerError.invariant(method, {
reason: 'Expected ObjectMethod instruction',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
const loweredFunc = method.loweredFunc;
const reactiveFunction = buildReactiveFunction(loweredFunc.func);
@@ -1951,7 +2175,15 @@ function codegenInstructionValue(
} else {
CompilerError.invariant(tagValue.type === 'StringLiteral', {
reason: `Expected JSX tag to be an identifier or string, got \`${tagValue.type}\``,
loc: tagValue.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: tagValue.loc ?? null,
message: null,
},
],
suggestions: null,
});
if (tagValue.value.indexOf(':') >= 0) {
const [namespace, name] = tagValue.value.split(':', 2);
@@ -1970,8 +2202,16 @@ function codegenInstructionValue(
SINGLE_CHILD_FBT_TAGS.has(tagValue.value)
) {
CompilerError.invariant(instrValue.children != null, {
details: [
{
kind: 'error',
loc: instrValue.loc,
message: null,
},
],
reason: 'Expected fbt element to have children',
loc: instrValue.loc,
suggestions: null,
description: null,
});
children = instrValue.children.map(child =>
codegenJsxFbtChildElement(cx, child),
@@ -2282,7 +2522,15 @@ function codegenInstructionValue(
instrValue.lvalue.kind === InstructionKind.Reassign,
{
reason: `Unexpected StoreLocal in codegenInstructionValue`,
loc: instrValue.loc,
description: null,
details: [
{
kind: 'error',
loc: instrValue.loc,
message: null,
},
],
suggestions: null,
},
);
value = t.assignmentExpression(
@@ -2310,7 +2558,15 @@ function codegenInstructionValue(
case 'StoreContext': {
CompilerError.invariant(false, {
reason: `Unexpected ${instrValue.kind} in codegenInstructionValue`,
loc: instrValue.loc,
description: null,
details: [
{
kind: 'error',
loc: instrValue.loc,
message: null,
},
],
suggestions: null,
});
}
default: {
@@ -2457,7 +2713,15 @@ function convertMemberExpressionToJsx(
): t.JSXMemberExpression {
CompilerError.invariant(expr.property.type === 'Identifier', {
reason: 'Expected JSX member expression property to be a string',
loc: expr.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: expr.loc ?? null,
message: null,
},
],
suggestions: null,
});
const property = t.jsxIdentifier(expr.property.name);
if (expr.object.type === 'Identifier') {
@@ -2466,7 +2730,15 @@ function convertMemberExpressionToJsx(
CompilerError.invariant(expr.object.type === 'MemberExpression', {
reason:
'Expected JSX member expression to be an identifier or nested member expression',
loc: expr.object.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: expr.object.loc ?? null,
message: null,
},
],
suggestions: null,
});
const object = convertMemberExpressionToJsx(expr.object);
return t.jsxMemberExpression(object, property);
@@ -2488,7 +2760,15 @@ function codegenObjectPropertyKey(
const expr = codegenPlace(cx, key.name);
CompilerError.invariant(t.isExpression(expr), {
reason: 'Expected object property key to be an expression',
loc: key.name.loc,
description: null,
details: [
{
kind: 'error',
loc: key.name.loc,
message: null,
},
],
suggestions: null,
});
return expr;
}
@@ -2636,7 +2916,14 @@ function codegenPlace(cx: Context, place: Place): t.Expression | t.JSXText {
description: `Value for '${printPlace(
place,
)}' was not set in the codegen context`,
loc: place.loc,
details: [
{
kind: 'error',
loc: place.loc,
message: null,
},
],
suggestions: null,
});
const identifier = convertIdentifier(place.identifier);
identifier.loc = place.loc as any;
@@ -2648,8 +2935,15 @@ function convertIdentifier(identifier: Identifier): t.Identifier {
identifier.name !== null && identifier.name.kind === 'named',
{
reason: `Expected temporaries to be promoted to named identifiers in an earlier pass`,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
description: `identifier ${identifier.id} is unnamed`,
loc: GeneratedSource,
suggestions: null,
},
);
return createIdentifier(identifier.loc, identifier.name.value);
@@ -2663,7 +2957,14 @@ function compareScopeDependency(
a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named',
{
reason: '[Codegen] Expected named identifier for dependency',
loc: a.identifier.loc,
description: null,
details: [
{
kind: 'error',
loc: a.identifier.loc,
message: null,
},
],
},
);
const aName = [
@@ -2687,7 +2988,14 @@ function compareScopeDeclaration(
a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named',
{
reason: '[Codegen] Expected named identifier for declaration',
loc: a.identifier.loc,
description: null,
details: [
{
kind: 'error',
loc: a.identifier.loc,
message: null,
},
],
},
);
const aName = a.identifier.name.value;

View File

@@ -75,7 +75,13 @@ export function flattenScopesWithHooksOrUseHIR(fn: HIRFunction): void {
CompilerError.invariant(terminal.kind === 'scope', {
reason: `Expected block to have a scope terminal`,
description: `Expected block bb${block.id} to end in a scope terminal`,
loc: terminal.loc,
details: [
{
kind: 'error',
loc: terminal.loc,
message: null,
},
],
});
const body = fn.body.blocks.get(terminal.block)!;
if (

View File

@@ -162,10 +162,16 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
});
CompilerError.invariant(false, {
reason: `Invalid mutable range for scope`,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
description: `Scope @${scope.id} has range [${scope.range.start}:${
scope.range.end
}] but the valid range is [1:${maxInstruction + 1}]`,
loc: GeneratedSource,
});
}
}

View File

@@ -8,7 +8,6 @@
import {CompilerError} from '..';
import {
DeclarationId,
GeneratedSource,
InstructionId,
InstructionKind,
Place,
@@ -162,7 +161,15 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
CompilerError.invariant(current !== null, {
reason:
'MergeConsecutiveScopes: expected current scope to be non-null if reset()',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
if (current.to > current.from + 1) {
merged.push(current);
@@ -376,7 +383,15 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
CompilerError.invariant(mergedScope.kind === 'scope', {
reason:
'MergeConsecutiveScopes: Expected scope starting index to be a scope',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
nextInstructions.push(mergedScope);
index++;
@@ -470,7 +485,6 @@ function canMergeScopes(
identifier: declaration.identifier,
reactive: true,
path: [],
loc: GeneratedSource,
})),
),
next.scope.dependencies,

View File

@@ -7,7 +7,6 @@
import {CompilerError} from '../CompilerError';
import {
GeneratedSource,
PrunedReactiveScopeBlock,
ReactiveFunction,
ReactiveScope,
@@ -22,7 +21,6 @@ import {
printIdentifier,
printInstructionValue,
printPlace,
printSourceLocation,
printType,
} from '../HIR/PrintHIR';
import {assertExhaustive} from '../Utils/utils';
@@ -115,7 +113,7 @@ export function printDependency(dependency: ReactiveScopeDependency): string {
const identifier =
printIdentifier(dependency.identifier) +
printType(dependency.identifier.type);
return `${identifier}${dependency.path.map(token => `${token.optional ? '?.' : '.'}${token.property}`).join('')}_${printSourceLocation(dependency.loc)}`;
return `${identifier}${dependency.path.map(token => `${token.optional ? '?.' : '.'}${token.property}`).join('')}`;
}
export function printReactiveInstructions(
@@ -324,7 +322,15 @@ function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
const block = case_.block;
CompilerError.invariant(block != null, {
reason: 'Expected case to have a block',
loc: case_.test?.loc ?? GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: case_.test?.loc ?? null,
message: null,
},
],
suggestions: null,
});
writeReactiveInstructions(writer, block);
});

View File

@@ -290,7 +290,14 @@ class PromoteInterposedTemporaries extends ReactiveFunctionVisitor<InterState> {
CompilerError.invariant(lval.identifier.name != null, {
reason:
'PromoteInterposedTemporaries: Assignment targets not expected to be temporaries',
loc: instruction.loc,
description: null,
details: [
{
kind: 'error',
loc: instruction.loc,
message: null,
},
],
});
}
@@ -453,7 +460,15 @@ function promoteIdentifier(identifier: Identifier, state: State): void {
CompilerError.invariant(identifier.name === null, {
reason:
'promoteTemporary: Expected to be called only for temporary variables',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
suggestions: null,
});
if (state.tags.has(identifier.declarationId)) {
promoteTemporaryJsxTag(identifier);

View File

@@ -145,7 +145,14 @@ class Visitor extends ReactiveFunctionTransform<VisitorState> {
if (maybeHoistedFn != null) {
CompilerError.invariant(maybeHoistedFn.kind === 'func', {
reason: '[PruneHoistedContexts] Unexpected hoisted function',
loc: instruction.loc,
description: null,
details: [
{
kind: 'error',
loc: instruction.loc,
message: null,
},
],
});
maybeHoistedFn.definition = instruction.value.lvalue.place;
/**

View File

@@ -196,7 +196,14 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
): void {
CompilerError.invariant(state !== 'Create', {
reason: "Visiting a terminal statement with state 'Create'",
loc: stmt.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: stmt.terminal.loc,
message: null,
},
],
});
super.visitTerminal(stmt, state);
}

View File

@@ -9,7 +9,6 @@ import {CompilerError} from '../CompilerError';
import {
DeclarationId,
Environment,
GeneratedSource,
Identifier,
InstructionId,
Pattern,
@@ -265,7 +264,14 @@ class State {
CompilerError.invariant(identifierNode !== undefined, {
reason: 'Expected identifier to be initialized',
description: `[${id}] operand=${printPlace(place)} for identifier declaration ${identifier}`,
loc: place.loc,
details: [
{
kind: 'error',
loc: place.loc,
message: null,
},
],
suggestions: null,
});
identifierNode.scopes.add(scope.id);
}
@@ -285,7 +291,15 @@ function computeMemoizedIdentifiers(state: State): Set<DeclarationId> {
const node = state.identifiers.get(id);
CompilerError.invariant(node !== undefined, {
reason: `Expected a node for all identifiers, none found for \`${id}\``,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
if (node.seen) {
return node.memoized;
@@ -325,7 +339,15 @@ function computeMemoizedIdentifiers(state: State): Set<DeclarationId> {
const node = state.scopes.get(id);
CompilerError.invariant(node !== undefined, {
reason: 'Expected a node for all scopes',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
if (node.seen) {
return;
@@ -972,7 +994,15 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
);
CompilerError.invariant(identifierNode !== undefined, {
reason: 'Expected identifier to be initialized',
loc: stmt.terminal.loc,
description: null,
details: [
{
kind: 'error',
loc: stmt.terminal.loc,
message: null,
},
],
suggestions: null,
});
for (const scope of scopes) {
identifierNode.scopes.add(scope.id);
@@ -995,7 +1025,15 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
);
CompilerError.invariant(identifierNode !== undefined, {
reason: 'Expected identifier to be initialized',
loc: reassignment.loc,
description: null,
details: [
{
kind: 'error',
loc: reassignment.loc,
message: null,
},
],
suggestions: null,
});
for (const scope of scopes) {
identifierNode.scopes.add(scope.id);

View File

@@ -9,7 +9,6 @@ import {ProgramContext} from '..';
import {CompilerError} from '../CompilerError';
import {
DeclarationId,
GeneratedSource,
Identifier,
IdentifierName,
InstructionId,
@@ -186,7 +185,15 @@ class Scopes {
const last = this.#stack.pop();
CompilerError.invariant(last === next, {
reason: 'Mismatch push/pop calls',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
}
}

View File

@@ -6,13 +6,7 @@
*/
import {CompilerError} from '../CompilerError';
import {
BlockId,
GeneratedSource,
HIRFunction,
Identifier,
Place,
} from '../HIR/HIR';
import {BlockId, HIRFunction, Identifier, Place} from '../HIR/HIR';
import {
eachInstructionLValue,
eachInstructionOperand,
@@ -102,7 +96,15 @@ export function eliminateRedundantPhi(
}
CompilerError.invariant(same !== null, {
reason: 'Expected phis to be non-empty',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
rewrites.set(phi.place.identifier, same);
block.phis.delete(phi);
@@ -153,12 +155,26 @@ export function eliminateRedundantPhi(
for (const phi of block.phis) {
CompilerError.invariant(!rewrites.has(phi.place.identifier), {
reason: '[EliminateRedundantPhis]: rewrite not complete',
loc: phi.place.loc,
description: null,
details: [
{
kind: 'error',
loc: phi.place.loc,
message: null,
},
],
});
for (const [, operand] of phi.operands) {
CompilerError.invariant(!rewrites.has(operand.identifier), {
reason: '[EliminateRedundantPhis]: rewrite not complete',
loc: phi.place.loc,
description: null,
details: [
{
kind: 'error',
loc: phi.place.loc,
message: null,
},
],
});
}
}

View File

@@ -10,7 +10,6 @@ import {Environment} from '../HIR/Environment';
import {
BasicBlock,
BlockId,
GeneratedSource,
HIRFunction,
Identifier,
IdentifierId,
@@ -70,7 +69,15 @@ class SSABuilder {
state(): State {
CompilerError.invariant(this.#current !== null, {
reason: 'we need to be in a block to access state!',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
return this.#states.get(this.#current)!;
}
@@ -251,7 +258,15 @@ function enterSSAImpl(
for (const [blockId, block] of func.body.blocks) {
CompilerError.invariant(!visitedBlocks.has(block), {
reason: `found a cycle! visiting bb${block.id} again`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
visitedBlocks.add(block);
@@ -262,7 +277,15 @@ function enterSSAImpl(
// NOTE: func.context should be empty for the root function
CompilerError.invariant(func.context.length === 0, {
reason: `Expected function context to be empty for outer function declarations`,
loc: func.loc,
description: null,
details: [
{
kind: 'error',
loc: func.loc,
message: null,
},
],
suggestions: null,
});
func.params = func.params.map(param => {
if (param.kind === 'Identifier') {
@@ -289,7 +312,15 @@ function enterSSAImpl(
CompilerError.invariant(entry.preds.size === 0, {
reason:
'Expected function expression entry block to have zero predecessors',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
entry.preds.add(blockId);
builder.defineFunction(loweredFunc);

View File

@@ -8,7 +8,6 @@
import {CompilerError} from '../CompilerError';
import {
DeclarationId,
GeneratedSource,
HIRFunction,
InstructionKind,
LValue,
@@ -60,7 +59,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
{
reason: `Expected variable not to be defined prior to declaration`,
description: `${printPlace(lvalue.place)} was already defined`,
loc: lvalue.place.loc,
details: [
{
kind: 'error',
loc: lvalue.place.loc,
message: null,
},
],
},
);
declarations.set(lvalue.place.identifier.declarationId, lvalue);
@@ -78,7 +83,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
{
reason: `Expected variable not to be defined prior to declaration`,
description: `${printPlace(lvalue.place)} was already defined`,
loc: lvalue.place.loc,
details: [
{
kind: 'error',
loc: lvalue.place.loc,
message: null,
},
],
},
);
declarations.set(lvalue.place.identifier.declarationId, lvalue);
@@ -102,7 +113,14 @@ export function rewriteInstructionKindsBasedOnReassignment(
description: `other places were \`${kind}\` but '${printPlace(
place,
)}' is const`,
loc: place.loc,
details: [
{
kind: 'error',
loc: place.loc,
message: 'Expected consistent kind for destructuring',
},
],
suggestions: null,
},
);
kind = InstructionKind.Const;
@@ -113,7 +131,15 @@ export function rewriteInstructionKindsBasedOnReassignment(
if (declaration === undefined) {
CompilerError.invariant(block.kind !== 'value', {
reason: `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
loc: place.loc,
description: null,
details: [
{
kind: 'error',
loc: place.loc,
message: null,
},
],
suggestions: null,
});
declarations.set(place.identifier.declarationId, lvalue);
CompilerError.invariant(
@@ -123,7 +149,14 @@ export function rewriteInstructionKindsBasedOnReassignment(
description: `Other places were \`${kind}\` but '${printPlace(
place,
)}' is const`,
loc: place.loc,
details: [
{
kind: 'error',
loc: place.loc,
message: 'Expected consistent kind for destructuring',
},
],
suggestions: null,
},
);
kind = InstructionKind.Const;
@@ -135,7 +168,14 @@ export function rewriteInstructionKindsBasedOnReassignment(
description: `Other places were \`${kind}\` but '${printPlace(
place,
)}' is reassigned`,
loc: place.loc,
details: [
{
kind: 'error',
loc: place.loc,
message: 'Expected consistent kind for destructuring',
},
],
suggestions: null,
},
);
kind = InstructionKind.Reassign;
@@ -145,7 +185,15 @@ export function rewriteInstructionKindsBasedOnReassignment(
}
CompilerError.invariant(kind !== null, {
reason: 'Expected at least one operand',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
lvalue.kind = kind;
break;
@@ -157,7 +205,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
CompilerError.invariant(declaration !== undefined, {
reason: `Expected variable to have been defined`,
description: `No declaration for ${printPlace(lvalue)}`,
loc: lvalue.loc,
details: [
{
kind: 'error',
loc: lvalue.loc,
message: null,
},
],
});
declaration.kind = InstructionKind.Let;
break;

View File

@@ -10,7 +10,6 @@ import {CompilerError} from '../CompilerError';
import {Environment} from '../HIR';
import {lowerType} from '../HIR/BuildHIR';
import {
GeneratedSource,
HIRFunction,
Identifier,
IdentifierId,
@@ -660,7 +659,15 @@ class Unifier {
if (type.kind === 'Phi') {
CompilerError.invariant(type.operands.length > 0, {
reason: 'there should be at least one operand',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
let candidateType: Type | null = null;

View File

@@ -6,7 +6,6 @@
*/
import {CompilerError} from '../CompilerError';
import {GeneratedSource} from '../HIR/HIR';
// Represents items which form disjoint sets.
export default class DisjointSet<T> {
@@ -21,7 +20,15 @@ export default class DisjointSet<T> {
const first = items.shift();
CompilerError.invariant(first != null, {
reason: 'Expected set to be non-empty',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
/*
* determine an arbitrary "root" for this set: if the first

View File

@@ -14,7 +14,6 @@ import {
PluginOptions,
} from '../Entrypoint';
import {EnvironmentConfig} from '..';
import {GeneratedSource} from '../HIR/HIR';
import {
EnvironmentConfigSchema,
PartialEnvironmentConfig,
@@ -156,7 +155,14 @@ function parseConfigPragmaEnvironmentForTest(
CompilerError.invariant(false, {
reason: 'Internal error, could not parse config from pragma string',
description: `${fromZodError(config.error)}`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
}

View File

@@ -108,9 +108,17 @@ function visit(
CompilerError.invariant(false, {
reason:
'Expected all references to a variable to be consistently local or context references',
description: `Identifier ${printPlace(place)} is referenced as a ${kind} variable, but was previously referenced as a ${prev.kind} variable`,
message: `this is ${prev.kind}`,
loc: place.loc,
description: `Identifier ${printPlace(
place,
)} is referenced as a ${kind} variable, but was previously referenced as a ${prev.kind} variable`,
suggestions: null,
details: [
{
kind: 'error',
loc: place.loc,
message: `this is ${prev.kind}`,
},
],
});
}
}

View File

@@ -111,7 +111,7 @@ export function validateExhaustiveDependencies(
dependencies: Set<InferredDependency>,
locals: Set<IdentifierId>,
): void {
CompilerError.invariant(startMemo == null, {
CompilerError.simpleInvariant(startMemo == null, {
reason: 'Unexpected nested memo calls',
loc: value.loc,
});
@@ -124,7 +124,7 @@ export function validateExhaustiveDependencies(
dependencies: Set<InferredDependency>,
locals: Set<IdentifierId>,
): void {
CompilerError.invariant(
CompilerError.simpleInvariant(
startMemo != null && startMemo.manualMemoId === value.manualMemoId,
{
reason: 'Found FinishMemoize without corresponding StartMemoize',
@@ -233,7 +233,7 @@ function validateDependencies(
if (a.kind === 'Global' && b.kind == 'Global') {
return a.binding.name.localeCompare(b.binding.name);
} else if (a.kind == 'Local' && b.kind == 'Local') {
CompilerError.invariant(
CompilerError.simpleInvariant(
a.identifier.name != null &&
a.identifier.name.kind === 'named' &&
b.identifier.name != null &&
@@ -320,7 +320,7 @@ function validateDependencies(
}
continue;
}
CompilerError.invariant(inferredDependency.kind === 'Local', {
CompilerError.simpleInvariant(inferredDependency.kind === 'Local', {
reason: 'Unexpected function dependency',
loc: inferredDependency.loc,
});
@@ -361,7 +361,7 @@ function validateDependencies(
continue;
}
if (dep.root.kind === 'NamedLocal' && dep.root.constant) {
CompilerError.invariant(
CompilerError.simpleInvariant(
!dep.root.value.reactive && isPrimitiveType(dep.root.value.identifier),
{
reason: 'Expected constant-folded dependency to be non-reactive',
@@ -756,7 +756,6 @@ function collectDependencies(
{
optional,
property: value.property,
loc: value.loc,
},
],
loc: value.loc,
@@ -872,7 +871,7 @@ function printInferredDependency(dep: InferredDependency): string {
return dep.binding.name;
}
case 'Local': {
CompilerError.invariant(
CompilerError.simpleInvariant(
dep.identifier.name != null && dep.identifier.name.kind === 'named',
{
reason: 'Expected dependencies to be named variables',
@@ -890,7 +889,7 @@ function printManualMemoDependency(dep: ManualMemoDependency): string {
identifierName = dep.root.identifierName;
} else {
const name = dep.root.value.identifier.name;
CompilerError.invariant(name != null && name.kind === 'named', {
CompilerError.simpleInvariant(name != null && name.kind === 'named', {
reason: 'Expected manual dependencies to be named variables',
loc: dep.root.value.loc,
});
@@ -977,7 +976,7 @@ export function findOptionalPlaces(
switch (terminal.kind) {
case 'branch': {
const isOptional = queue.pop();
CompilerError.invariant(isOptional !== undefined, {
CompilerError.simpleInvariant(isOptional !== undefined, {
reason:
'Expected an optional value for each optional test condition',
loc: terminal.test.loc,
@@ -1018,19 +1017,22 @@ export function findOptionalPlaces(
break;
}
case 'maybe-throw': {
testBlock = fn.body.blocks.get(terminal.continuation)!;
break;
CompilerError.throwTodo({
reason: `Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement`,
description: null,
loc: terminal.loc,
suggestions: null,
});
}
default: {
CompilerError.invariant(false, {
CompilerError.simpleInvariant(false, {
reason: `Unexpected terminal in optional`,
message: `Unexpected ${terminal.kind} in optional`,
loc: terminal.loc,
});
}
}
}
CompilerError.invariant(queue.length === 0, {
CompilerError.simpleInvariant(queue.length === 0, {
reason:
'Expected a matching number of conditional blocks and branch points',
loc: block.terminal.loc,
@@ -1097,7 +1099,7 @@ function createDiagnostic(
break;
}
default: {
CompilerError.invariant(false, {
CompilerError.simpleInvariant(false, {
reason: `Unexpected error category: ${category}`,
loc: GeneratedSource,
});

View File

@@ -191,7 +191,14 @@ function getContextReassignment(
for (const operand of operands) {
CompilerError.invariant(operand.effect !== Effect.Unknown, {
reason: `Expected effects to be inferred prior to ValidateLocalsNotReassignedAfterRender`,
loc: operand.loc,
description: null,
details: [
{
kind: 'error',
loc: operand.loc,
message: '',
},
],
});
const reassignment = reassigningFunctions.get(
operand.identifier.id,

View File

@@ -83,7 +83,14 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
const dependencies: Array<IdentifierId> = deps.elements.map(dep => {
CompilerError.invariant(dep.kind === 'Identifier', {
reason: `Dependency is checked as a place above`,
loc: value.loc,
description: null,
details: [
{
kind: 'error',
loc: value.loc,
message: 'this is checked as a place above',
},
],
});
return locals.get(dep.identifier.id) ?? dep.identifier.id;
});

View File

@@ -244,7 +244,13 @@ export function validateNoDerivedComputationsInEffects_exp(
reason:
'[ValidateNoDerivedComputationsInEffects] Fixpoint iteration failed to converge.',
description: `Fixpoint iteration exceeded ${MAX_FIXPOINT_ITERATIONS} iterations while tracking derivations. This suggests a cyclic dependency in the derivation cache.`,
loc: fn.loc,
details: [
{
kind: 'error',
loc: fn.loc,
message: `Exceeded ${MAX_FIXPOINT_ITERATIONS} iterations in ValidateNoDerivedComputationsInEffects`,
},
],
});
} while (context.derivationCache.snapshot());
@@ -478,7 +484,14 @@ function recordInstructionDerivations(
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
loc: operand.loc,
description: null,
details: [
{
kind: 'error',
loc: operand.loc,
message: 'Unexpected unknown effect',
},
],
});
}
default: {
@@ -526,7 +539,14 @@ function buildTreeNode(
CompilerError.invariant(childId !== sourceId, {
reason:
'Unexpected self-reference: a value should not have itself as a source',
loc: sourceMetadata.place.loc,
description: null,
details: [
{
kind: 'error',
loc: sourceMetadata.place.loc,
message: null,
},
],
});
const childNodes = buildTreeNode(

View File

@@ -12,7 +12,6 @@ import {
} from '../CompilerError';
import {
BlockId,
GeneratedSource,
HIRFunction,
IdentifierId,
Identifier,
@@ -59,7 +58,15 @@ type RefId = number & {[opaqueRefId]: 'RefId'};
function makeRefId(id: number): RefId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected identifier id to be a non-negative integer',
loc: GeneratedSource,
description: null,
suggestions: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
});
return id as RefId;
}
@@ -197,19 +204,40 @@ function tyEqual(a: RefAccessType, b: RefAccessType): boolean {
case 'Guard':
CompilerError.invariant(b.kind === 'Guard', {
reason: 'Expected ref value',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
});
return a.refId === b.refId;
case 'RefValue':
CompilerError.invariant(b.kind === 'RefValue', {
reason: 'Expected ref value',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
});
return a.loc == b.loc;
case 'Structure': {
CompilerError.invariant(b.kind === 'Structure', {
reason: 'Expected structure',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
});
const fnTypesEqual =
(a.fn === null && b.fn === null) ||
@@ -248,7 +276,14 @@ function joinRefAccessTypes(...types: Array<RefAccessType>): RefAccessType {
a.kind === 'Structure' && b.kind === 'Structure',
{
reason: 'Expected structure',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
},
);
const fn =
@@ -747,7 +782,14 @@ function validateNoRefAccessInRenderImpl(
CompilerError.invariant(!env.hasChanged(), {
reason: 'Ref type environment did not converge',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
});
return Ok(

View File

@@ -97,7 +97,7 @@ export function validateNoSetStateInEffects(
case 'CallExpression': {
const callee =
instr.value.kind === 'MethodCall'
? instr.value.property
? instr.value.receiver
: instr.value.callee;
if (isUseEffectEventType(callee.identifier)) {
@@ -264,7 +264,15 @@ function getSetStateCall(
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
loc: operand.loc,
description: null,
details: [
{
kind: 'error',
loc: operand.loc,
message: null,
},
],
suggestions: null,
});
}
default: {

View File

@@ -102,7 +102,14 @@ function validateNoSetStateInRenderImpl(
case 'StartMemoize': {
CompilerError.invariant(activeManualMemoId === null, {
reason: 'Unexpected nested StartMemoize instructions',
loc: instr.value.loc,
description: null,
details: [
{
kind: 'error',
loc: instr.value.loc,
message: null,
},
],
});
activeManualMemoId = instr.value.manualMemoId;
break;
@@ -113,7 +120,14 @@ function validateNoSetStateInRenderImpl(
{
reason:
'Expected FinishMemoize to align with previous StartMemoize instruction',
loc: instr.value.loc,
description: null,
details: [
{
kind: 'error',
loc: instr.value.loc,
message: null,
},
],
},
);
activeManualMemoId = null;

View File

@@ -248,7 +248,15 @@ function validateInferredDep(
CompilerError.invariant(dep.identifier.name?.kind === 'named', {
reason:
'ValidatePreservedManualMemoization: expected scope dependency to be named',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
suggestions: null,
});
normalizedDep = {
root: {
@@ -496,7 +504,14 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
CompilerError.invariant(state.manualMemoState == null, {
reason: 'Unexpected nested StartMemoize instructions',
description: `Bad manual memoization ids: ${state.manualMemoState?.manualMemoId}, ${value.manualMemoId}`,
loc: value.loc,
details: [
{
kind: 'error',
loc: value.loc,
message: null,
},
],
suggestions: null,
});
state.manualMemoState = {
@@ -556,7 +571,14 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
{
reason: 'Unexpected mismatch between StartMemoize and FinishMemoize',
description: `Encountered StartMemoize id=${state.manualMemoState?.manualMemoId} followed by FinishMemoize id=${value.manualMemoId}`,
loc: value.loc,
details: [
{
kind: 'error',
loc: value.loc,
message: null,
},
],
suggestions: null,
},
);
const reassignments = state.manualMemoState.reassignments;

View File

@@ -149,23 +149,6 @@ export function validateSourceLocations(
return;
}
/*
* Skip return statements inside arrow functions that will be simplified to expression body.
* The compiler transforms `() => { return expr }` to `() => expr` in CodegenReactiveFunction
*/
if (t.isReturnStatement(node) && node.argument != null) {
const parentBody = path.parentPath;
const parentFunc = parentBody?.parentPath;
if (
parentBody?.isBlockStatement() &&
parentFunc?.isArrowFunctionExpression() &&
parentBody.node.body.length === 1 &&
parentBody.node.directives.length === 0
) {
return;
}
}
// Collect the location if it exists
if (node.loc) {
const key = locationKey(node.loc);

View File

@@ -46,7 +46,6 @@ function foo(cond) {
} else {
a = $[1];
}
return a;
}

View File

@@ -145,7 +145,6 @@ function useFoo(t0) {
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
return s;
}

View File

@@ -61,7 +61,6 @@ function useFoo(t0) {
} else {
items = $[2];
}
return items;
}

View File

@@ -47,7 +47,6 @@ function Foo() {
} else {
thing = $[0];
}
return thing;
} catch {}
}

View File

@@ -33,7 +33,6 @@ function Component(props) {
} else {
x = $[1];
}
return x;
}

View File

@@ -23,7 +23,6 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
function useFoo() {
const update = _temp;
return update;
}
function _temp() {

View File

@@ -36,7 +36,6 @@ function useBar(props) {
z = t0;
}
}
return z;
}

View File

@@ -53,7 +53,6 @@ function Component(props) {
default:
}
const outerHandlers = handlers;
return outerHandlers;
}

View File

@@ -98,7 +98,6 @@ function useFoo(t0) {
}
result = t1;
}
return result;
}

View File

@@ -40,7 +40,6 @@ function component(a) {
} else {
x = $[1];
}
return x;
}

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