Refactor CompilerErrorDetails

- Made most static methods on CompilerError take a single options object   as an 
argument. With the exception of invariant which takes a condition and an options 
object. - Added a new `suggestions` field on CompilerErrorDetail, which we'll   
use to provide eslint suggestions - Updated eslint-plugin-react-forget to handle 
suggestions
This commit is contained in:
Lauren Tan
2023-06-27 14:01:39 -04:00
parent c3d6789857
commit 448ed2b67b
39 changed files with 1037 additions and 673 deletions
@@ -6,7 +6,6 @@
*/
import type { SourceLocation } from "./HIR";
import type { ExtractClassProperties } from "./Utils/types";
import { assertExhaustive } from "./Utils/utils";
export enum ErrorSeverity {
@@ -33,29 +32,61 @@ export enum ErrorSeverity {
Invariant = "Invariant",
}
export type CompilerErrorOptions = {
export enum CompilerSuggestionOperation {
InsertBefore,
InsertAfter,
Remove,
Replace,
}
export type CompilerSuggestion =
| {
op:
| CompilerSuggestionOperation.InsertAfter
| CompilerSuggestionOperation.InsertBefore
| CompilerSuggestionOperation.Replace;
range: [number, number];
description: string;
text: string;
}
| {
op: CompilerSuggestionOperation.Remove;
range: [number, number];
description: string;
};
export type CompilerErrorDetailOptions = {
reason: string;
description?: string | null | undefined;
severity: ErrorSeverity;
loc: SourceLocation | null;
suggestions: Array<CompilerSuggestion> | null;
};
type CompilerErrorDetailOptions = ExtractClassProperties<CompilerErrorDetail>;
/**
* Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then
* aggregated into a single {@link CompilerError} later.
*/
export class CompilerErrorDetail {
reason: string;
description: string | null;
severity: ErrorSeverity;
loc: SourceLocation | null;
options: CompilerErrorDetailOptions;
constructor(options: CompilerErrorDetailOptions) {
this.reason = options.reason;
this.description = options.description;
this.severity = options.severity;
this.loc = options.loc;
this.options = options;
}
get reason(): CompilerErrorDetailOptions["reason"] {
return this.options.reason;
}
get description(): CompilerErrorDetailOptions["description"] {
return this.options.description;
}
get severity(): CompilerErrorDetailOptions["severity"] {
return this.options.severity;
}
get loc(): CompilerErrorDetailOptions["loc"] {
return this.options.loc;
}
get suggestions(): CompilerErrorDetailOptions["suggestions"] {
return this.options.suggestions;
}
printErrorMessage(): string {
@@ -79,17 +110,13 @@ export class CompilerError extends Error {
static invariant(
condition: unknown,
reason: string,
loc: SourceLocation | null,
description: string | null = null
options: Omit<CompilerErrorDetailOptions, "severity">
): asserts condition {
if (!condition) {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
description,
loc,
reason,
...options,
severity: ErrorSeverity.Invariant,
})
);
@@ -97,34 +124,21 @@ export class CompilerError extends Error {
}
}
static todo(
reason: string,
loc: SourceLocation | null,
description: string | null = null
): never {
static todo(options: Omit<CompilerErrorDetailOptions, "severity">): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
description,
loc,
reason,
severity: ErrorSeverity.Todo,
})
new CompilerErrorDetail({ ...options, severity: ErrorSeverity.Todo })
);
throw errors;
}
static invalidInput(
reason: string,
loc: SourceLocation | null,
description: string | null = null
options: Omit<CompilerErrorDetailOptions, "severity">
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
description,
loc,
reason,
...options,
severity: ErrorSeverity.InvalidInput,
})
);
@@ -132,16 +146,12 @@ export class CompilerError extends Error {
}
static invalidReact(
reason: string,
loc: SourceLocation | null,
description: string | null = null
options: Omit<CompilerErrorDetailOptions, "severity">
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
description,
loc,
reason,
...options,
severity: ErrorSeverity.InvalidReact,
})
);
@@ -149,16 +159,12 @@ export class CompilerError extends Error {
}
static invalidConfig(
reason: string,
loc: SourceLocation | null,
description: string | null = null
options: Omit<CompilerErrorDetailOptions, "severity">
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
description,
loc,
reason,
...options,
severity: ErrorSeverity.InvalidConfig,
})
);
@@ -180,11 +186,12 @@ export class CompilerError extends Error {
return this.details.map((detail) => detail.toString()).join("\n\n");
}
push(options: CompilerErrorOptions): CompilerErrorDetail {
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
const detail = new CompilerErrorDetail({
reason: options.reason,
description: options.description ?? null,
severity: options.severity,
suggestions: options.suggestions,
loc: typeof options.loc === "symbol" ? null : options.loc,
});
return this.pushErrorDetail(detail);
@@ -53,11 +53,12 @@ export function compileProgram(
return;
}
CompilerError.invariant(
fn.node.id != null,
"FunctionDeclaration must have a name",
fn.node.loc ?? GeneratedSource
);
CompilerError.invariant(fn.node.id != null, {
reason: "FunctionDeclaration must have a name",
description: null,
loc: fn.node.loc ?? GeneratedSource,
suggestions: null,
});
const originalIdent = fn.node.id;
if (pass.opts.gating != null) {
@@ -65,11 +66,12 @@ export function compileProgram(
fn.node.id = addSuffix(fn.node.id, "_uncompiled");
// Rename and append compiled function
CompilerError.invariant(
compiled.id != null,
"FunctionDeclaration must produce a name",
fn.node.loc ?? GeneratedSource
);
CompilerError.invariant(compiled.id != null, {
reason: "FunctionDeclaration must produce a name",
description: null,
loc: fn.node.loc ?? GeneratedSource,
suggestions: null,
});
compiled.id = addSuffix(compiled.id, "_forget");
const compiledFn = fn.insertAfter(compiled)[0];
compiledFn.skip();
@@ -212,6 +214,7 @@ export function compileProgram(
description: violation.value.trim(),
severity: ErrorSeverity.InvalidReact,
loc: violation.loc ?? null,
suggestions: null, // TODO(@poteto) add autofix for eslint
})
);
}
@@ -357,6 +360,7 @@ function buildFunctionDeclaration(
severity: ErrorSeverity.Todo,
description: `Handle ${fn.parentPath.type}`,
loc: fn.node.loc ?? null,
suggestions: null,
});
}
const variableDeclarator = fn.parentPath;
@@ -367,6 +371,7 @@ function buildFunctionDeclaration(
severity: ErrorSeverity.Todo,
description: `Handle ${variableDeclarator.parentPath.type}`,
loc: fn.node.loc ?? null,
suggestions: null,
});
}
const variableDeclaration = variableDeclarator.parentPath;
@@ -378,6 +383,7 @@ function buildFunctionDeclaration(
severity: ErrorSeverity.Todo,
description: `Handle ${id.type}`,
loc: fn.node.loc ?? null,
suggestions: null,
});
}
@@ -407,11 +413,12 @@ function buildBlockStatement(
return wrappedBody.node;
}
CompilerError.invariant(
body.isBlockStatement(),
"Body must be a BlockStatement",
body.node.loc ?? GeneratedSource
);
CompilerError.invariant(body.isBlockStatement(), {
reason: "Body must be a BlockStatement",
description: null,
loc: body.node.loc ?? GeneratedSource,
suggestions: null,
});
return body.node;
}
@@ -426,16 +433,20 @@ function addImportsToProgram(
// Codegen currently does not rename import specifiers, so we do additional
// validation here
if (identifiers.has(importSpecifierName)) {
CompilerError.invalidConfig(
`Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
GeneratedSource
);
CompilerError.invalidConfig({
reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
description: null,
loc: GeneratedSource,
suggestions: null,
});
}
if (path.scope.hasBinding(importSpecifierName)) {
CompilerError.invalidConfig(
`Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
GeneratedSource
);
CompilerError.invalidConfig({
reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
description: null,
loc: GeneratedSource,
suggestions: null,
});
}
identifiers.add(importSpecifierName);
@@ -35,18 +35,20 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
}
}
for (const instr of block.instructions) {
CompilerError.invariant(
instr.lvalue.identifier.name === null,
`Expected all lvalues to be temporaries`,
instr.lvalue.loc,
`Found named lvalue '${instr.lvalue.identifier.name}'`
);
CompilerError.invariant(
!assignments.has(instr.lvalue.identifier.id),
`Expected lvalues to be assigned exactly once`,
instr.lvalue.loc,
`Found duplicate assignment of '${printPlace(instr.lvalue)}'`
);
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,
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,
suggestions: null,
});
assignments.add(instr.lvalue.identifier.id);
for (const operand of eachInstructionLValue(instr)) {
validate(identifiers, operand.identifier, operand.loc);
@@ -72,11 +74,11 @@ function validate(
if (previous === undefined) {
identifiers.set(identifier.id, identifier);
} else {
CompilerError.invariant(
identifier === previous,
`Duplicate identifier object`,
loc ?? GeneratedSource,
`Found duplicate identifier object for id ${identifier.id}`
);
CompilerError.invariant(identifier === previous, {
reason: `Duplicate identifier object`,
description: `Found duplicate identifier object for id ${identifier.id}`,
loc: loc ?? GeneratedSource,
suggestions: null,
});
}
}
@@ -13,14 +13,14 @@ import { mapTerminalSuccessors } from "./visitors";
export function assertTerminalSuccessorsExist(fn: HIRFunction): void {
for (const [, block] of fn.body.blocks) {
mapTerminalSuccessors(block.terminal, (successor) => {
CompilerError.invariant(
fn.body.blocks.has(successor),
`Terminal successor references unknown block`,
(block.terminal as any).loc ?? GeneratedSource,
`Block bb${successor} does not exist for terminal '${printTerminal(
CompilerError.invariant(fn.body.blocks.has(successor), {
reason: `Terminal successor references unknown block`,
description: `Block bb${successor} does not exist for terminal '${printTerminal(
block.terminal
)}'`
);
)}'`,
loc: (block.terminal as any).loc ?? GeneratedSource,
suggestions: null,
});
return successor;
});
}
@@ -88,6 +88,7 @@ export function lower(
reason: `(BuildHIR::lower) Could not find binding for param '${param.node.name}'`,
severity: ErrorSeverity.Invariant,
loc: param.node.loc ?? null,
suggestions: null,
});
return;
}
@@ -122,6 +123,7 @@ export function lower(
reason: `(BuildHIR::lower) Handle ${param.node.type} params`,
severity: ErrorSeverity.Todo,
loc: param.node.loc ?? null,
suggestions: null,
});
}
});
@@ -143,6 +145,7 @@ export function lower(
reason: `(BuildHIR::lower) Unexpected function body kind: ${body.type}}`,
severity: ErrorSeverity.InvalidInput,
loc: body.node.loc ?? null,
suggestions: null,
});
}
@@ -320,6 +323,7 @@ function lowerStatement(
"(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement",
severity: ErrorSeverity.Todo,
loc: stmt.node.loc ?? null,
suggestions: null,
});
return {
kind: "unsupported",
@@ -391,6 +395,7 @@ function lowerStatement(
reason: `(BuildHIR::lowerStatement) Handle empty test in ForStatement`,
severity: ErrorSeverity.Todo,
loc: stmt.node.loc ?? null,
suggestions: null,
});
} else {
builder.terminateWithContinuation(
@@ -540,6 +545,7 @@ function lowerStatement(
"(BuildHIR::lowerStatement) Expected at most one `default` branch in SwitchStatement, this code should have failed to parse",
severity: ErrorSeverity.InvalidInput,
loc: case_.node.loc ?? null,
suggestions: null,
});
break;
}
@@ -614,6 +620,7 @@ function lowerStatement(
reason: `(BuildHIR::lowerStatement) Handle ${nodeKind} kinds in VariableDeclaration`,
severity: ErrorSeverity.Todo,
loc: stmt.node.loc ?? null,
suggestions: null,
});
return;
}
@@ -641,6 +648,7 @@ function lowerStatement(
reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
severity: ErrorSeverity.Invariant,
loc: id.node.loc ?? null,
suggestions: null,
});
} else {
const place: Place = {
@@ -655,6 +663,7 @@ function lowerStatement(
reason: `(BuildHIR::lowerAssignment) Invalid declaration kind (const) for variable later reassigned.`,
severity: ErrorSeverity.InvalidInput,
loc: id.node.loc ?? null,
suggestions: null,
});
}
lowerValueToTemporary(builder, {
@@ -681,6 +690,7 @@ function lowerStatement(
reason: `(BuildHIR::lowerStatement) Expected variable declaration to be an identifier if no initializer was provided.`,
severity: ErrorSeverity.InvalidInput,
loc: stmt.node.loc ?? null,
suggestions: null,
});
}
}
@@ -768,11 +778,12 @@ function lowerStatement(
case "FunctionDeclaration": {
const stmt = stmtPath as NodePath<t.FunctionDeclaration>;
stmt.skip();
CompilerError.invariant(
stmt.get("id").type === "Identifier",
"function declarations must have a name",
stmt.node.loc ?? null
);
CompilerError.invariant(stmt.get("id").type === "Identifier", {
reason: "function declarations must have a name",
description: null,
loc: stmt.node.loc ?? null,
suggestions: null,
});
const id = stmt.get("id") as NodePath<t.Identifier>;
// Desugar FunctionDeclaration to FunctionExpression.
@@ -795,11 +806,13 @@ function lowerStatement(
),
])
);
CompilerError.invariant(
desugared.length === 1,
"only one declaration is created from desugaring function declaration",
stmt.node.loc ?? null
);
CompilerError.invariant(desugared.length === 1, {
reason:
"only one declaration is created from desugaring function declaration",
description: null,
loc: stmt.node.loc ?? null,
suggestions: null,
});
lowerStatement(builder, desugared.at(0)!);
return;
}
@@ -844,11 +857,12 @@ function lowerStatement(
let test: Place;
if (left.isVariableDeclaration()) {
const declarations = left.get("declarations");
CompilerError.invariant(
declarations.length === 1,
`Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
left.node.loc ?? null
);
CompilerError.invariant(declarations.length === 1, {
reason: `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
description: null,
loc: left.node.loc ?? null,
suggestions: null,
});
const id = declarations[0].get("id");
const nextIterableOf = lowerValueToTemporary(builder, {
kind: "NextIterableOf",
@@ -868,6 +882,7 @@ function lowerStatement(
reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForOfStatement`,
severity: ErrorSeverity.Todo,
loc: left.node.loc ?? null,
suggestions: null,
});
return;
}
@@ -935,6 +950,7 @@ function lowerStatement(
reason: `(BuildHIR::lowerStatement) Handle ${stmtPath.type} statements`,
severity: ErrorSeverity.Todo,
loc: stmtPath.node.loc ?? null,
suggestions: null,
});
lowerValueToTemporary(builder, {
kind: "UnsupportedNode",
@@ -1007,6 +1023,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
severity: ErrorSeverity.Todo,
loc: propertyPath.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -1016,6 +1033,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle ${valuePath.type} values in ObjectExpression`,
severity: ErrorSeverity.Todo,
loc: valuePath.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -1039,6 +1057,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.type} properties in ObjectExpression`,
severity: ErrorSeverity.Todo,
loc: propertyPath.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -1058,6 +1077,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,
severity: ErrorSeverity.Todo,
loc: null,
suggestions: null,
});
continue;
} else if (element.isExpression()) {
@@ -1073,6 +1093,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,
severity: ErrorSeverity.Todo,
loc: element.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -1091,6 +1112,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Expected Expression, got ${calleePath.type} in NewExpression (v8 intrinsics not supported): ${calleePath.type}`,
severity: ErrorSeverity.Todo,
loc: calleePath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1116,6 +1138,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Expected Expression, got ${calleePath.type} in CallExpression (v8 intrinsics not supported)`,
severity: ErrorSeverity.Todo,
loc: calleePath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1149,6 +1172,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Expected Expression, got ${leftPath.type} lval in BinaryExpression`,
severity: ErrorSeverity.Todo,
loc: leftPath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1180,6 +1204,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Expected SequenceExpression to have at least one expression`,
severity: ErrorSeverity.InvalidInput,
loc: expr.node.loc ?? null,
suggestions: null,
});
} else {
lowerValueToTemporary(builder, {
@@ -1391,6 +1416,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle ${operator} operators in AssignmentExpression`,
severity: ErrorSeverity.Todo,
loc: expr.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1463,6 +1489,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Expected Identifier or MemberExpression, got ${expr.type} lval in AssignmentExpression`,
severity: ErrorSeverity.Todo,
loc: expr.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1504,6 +1531,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`,
severity: ErrorSeverity.Todo,
loc: attribute.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -1516,14 +1544,16 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name '${name}'`,
severity: ErrorSeverity.Todo,
loc: namePath.node.loc ?? null,
suggestions: null,
});
}
} else {
CompilerError.invariant(
namePath.isJSXNamespacedName(),
"Refinement",
namePath.node.loc ?? null
);
CompilerError.invariant(namePath.isJSXNamespacedName(), {
reason: "Refinement",
description: null,
loc: namePath.node.loc ?? null,
suggestions: null,
});
const namespace = namePath.node.namespace.name;
const name = namePath.node.name.name;
propName = `${namespace}:${name}`;
@@ -1538,6 +1568,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle ${valueExpr.type} attribute values in JSXElement`,
severity: ErrorSeverity.Todo,
loc: valueExpr.node?.loc ?? null,
suggestions: null,
});
continue;
}
@@ -1547,6 +1578,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle ${expression.type} expressions in JSXExpressionContainer within JSXElement`,
severity: ErrorSeverity.Todo,
loc: valueExpr.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -1589,14 +1621,17 @@ function lowerExpression(
"(BuildHIR::lowerExpression) Handle tagged template with interpolations",
severity: ErrorSeverity.Todo,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
CompilerError.invariant(
expr.get("quasi").get("quasis").length == 1,
"there should be only one quasi as we don't support interpolations yet",
expr.node.loc ?? null
);
CompilerError.invariant(expr.get("quasi").get("quasis").length == 1, {
reason:
"there should be only one quasi as we don't support interpolations yet",
description: null,
loc: expr.node.loc ?? null,
suggestions: null,
});
const value = expr.get("quasi").get("quasis").at(0)!.node.value;
if (value.raw !== value.cooked) {
builder.errors.push({
@@ -1604,6 +1639,7 @@ function lowerExpression(
"(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value",
severity: ErrorSeverity.Todo,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1625,6 +1661,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Unexpected quasi and subexpression lengths in TemplateLiteral.`,
severity: ErrorSeverity.InvalidInput,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1634,6 +1671,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerAssignment) Handle TSType in TemplateLiteral.`,
severity: ErrorSeverity.Todo,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1675,6 +1713,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) delete on a non-member expression has no semantic meaning`,
severity: ErrorSeverity.InvalidInput,
loc: expr.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: expr.node, loc: exprLoc };
}
@@ -1712,6 +1751,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle UpdateExpression with ${argument.type} argument`,
severity: ErrorSeverity.Todo,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1720,6 +1760,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle prefix UpdateExpression`,
severity: ErrorSeverity.Todo,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1774,6 +1815,7 @@ function lowerExpression(
reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`,
severity: ErrorSeverity.Todo,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
@@ -1843,7 +1885,12 @@ function lowerOptionalMemberExpression(
loc,
};
});
CompilerError.invariant(object !== null, "Satisfy type checker", null);
CompilerError.invariant(object !== null, {
reason: "Satisfy type checker",
description: null,
loc: null,
suggestions: null,
});
// block to evaluate if the callee is non-null/undefined. arguments are lowered in this block to preserve
// the semantic of conditional evaluation depending on the callee
@@ -2047,6 +2094,7 @@ function lowerReorderableExpression(
reason: `(BuildHIR::node.lowerReorderableExpression) Expression type '${expr.type}' cannot be safely reordered`,
severity: ErrorSeverity.Todo,
loc: expr.node.loc ?? null,
suggestions: null,
});
}
return lowerExpressionToTemporary(builder, expr);
@@ -2138,6 +2186,7 @@ function lowerArguments(
reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in CallExpression`,
severity: ErrorSeverity.Todo,
loc: argPath.node.loc ?? null,
suggestions: null,
});
}
}
@@ -2167,6 +2216,7 @@ function lowerMemberExpression(
reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`,
severity: ErrorSeverity.Todo,
loc: propertyNode.node.loc ?? null,
suggestions: null,
});
return {
object,
@@ -2187,6 +2237,7 @@ function lowerMemberExpression(
reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${propertyNode.type} property`,
severity: ErrorSeverity.Todo,
loc: propertyNode.node.loc ?? null,
suggestions: null,
});
return {
object,
@@ -2243,6 +2294,7 @@ function lowerJsxElementName(
reason: `(BuildHIR::lowerJsxElementName) Expected JSXNamespacedName to have no colons in the namespace or name, got '${namespace}' : '${name}'`,
severity: ErrorSeverity.InvalidInput,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
}
const place = lowerValueToTemporary(builder, {
@@ -2256,6 +2308,7 @@ function lowerJsxElementName(
reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`,
severity: ErrorSeverity.Todo,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
return lowerValueToTemporary(builder, {
kind: "UnsupportedNode",
@@ -2275,11 +2328,12 @@ function lowerJsxMemberExpression(
if (object.isJSXMemberExpression()) {
objectPlace = lowerJsxMemberExpression(builder, object);
} else {
CompilerError.invariant(
object.isJSXIdentifier(),
`TypeScript refinement fail: expected 'JsxIdentifier', got '${object.node.type}'`,
object.node.loc ?? null
);
CompilerError.invariant(object.isJSXIdentifier(), {
reason: `TypeScript refinement fail: expected 'JsxIdentifier', got '${object.node.type}'`,
description: null,
loc: object.node.loc ?? null,
suggestions: null,
});
objectPlace = lowerIdentifier(builder, object);
}
const property = exprPath.get("property").node.name;
@@ -2310,11 +2364,12 @@ function lowerJsxElement(
if (expression.isJSXEmptyExpression()) {
return null;
} else {
CompilerError.invariant(
expression.isExpression(),
`(BuildHIR::lowerJsxElement) Expected Expression but found ${expression.type}!`,
expression.node.loc ?? null
);
CompilerError.invariant(expression.isExpression(), {
reason: `(BuildHIR::lowerJsxElement) Expected Expression but found ${expression.type}!`,
description: null,
loc: expression.node.loc ?? null,
suggestions: null,
});
return lowerExpressionToTemporary(builder, expression);
}
} else if (exprPath.isJSXText()) {
@@ -2329,6 +2384,7 @@ function lowerJsxElement(
reason: `(BuildHIR::lowerJsxElement) Unhandled JsxElement, got: ${exprPath.type}`,
severity: ErrorSeverity.Todo,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
const place = lowerValueToTemporary(builder, {
kind: "UnsupportedNode",
@@ -2481,6 +2537,7 @@ function lowerIdentifierForAssignment(
reason: `(BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.`,
severity: ErrorSeverity.InvalidReact,
loc: path.node.loc ?? null,
suggestions: null,
});
} else {
// Else its an internal error bc we couldn't find the binding
@@ -2488,6 +2545,7 @@ function lowerIdentifierForAssignment(
reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
severity: ErrorSeverity.Invariant,
loc: path.node.loc ?? null,
suggestions: null,
});
}
return null;
@@ -2530,6 +2588,7 @@ function lowerAssignment(
reason: `(BuildHIR::lowerAssignment) Invalid declaration kind (const) for variable later reassigned.`,
severity: ErrorSeverity.InvalidInput,
loc: lvalue.node.loc ?? null,
suggestions: null,
});
}
lowerValueToTemporary(builder, {
@@ -2560,11 +2619,12 @@ function lowerAssignment(
}
case "MemberExpression": {
// This can only occur because of a coding error, parsers enforce this condition
CompilerError.invariant(
kind === InstructionKind.Reassign,
"MemberExpression may only appear in an assignment expression",
lvaluePath.node.loc ?? null
);
CompilerError.invariant(kind === InstructionKind.Reassign, {
reason: "MemberExpression may only appear in an assignment expression",
description: null,
loc: lvaluePath.node.loc ?? null,
suggestions: null,
});
const lvalue = lvaluePath as NodePath<t.MemberExpression>;
const property = lvalue.get("property");
const object = lowerExpressionToTemporary(builder, lvalue.get("object"));
@@ -2574,6 +2634,7 @@ function lowerAssignment(
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`,
severity: ErrorSeverity.Todo,
loc: property.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: lvalueNode, loc };
}
@@ -2592,6 +2653,7 @@ function lowerAssignment(
"(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property",
severity: ErrorSeverity.Todo,
loc: property.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: lvalueNode, loc };
}
@@ -2624,6 +2686,7 @@ function lowerAssignment(
reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ArrayPattern`,
severity: ErrorSeverity.Todo,
loc: element.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -2691,6 +2754,7 @@ function lowerAssignment(
reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ArrayPattern`,
severity: ErrorSeverity.Todo,
loc: argument.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -2714,6 +2778,7 @@ function lowerAssignment(
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
severity: ErrorSeverity.Todo,
loc: property.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -2722,6 +2787,7 @@ function lowerAssignment(
reason: `(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern`,
severity: ErrorSeverity.Todo,
loc: property.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -2731,6 +2797,7 @@ function lowerAssignment(
reason: `(BuildHIR::lowerAssignment) Handle ${key.type} keys in ObjectPattern`,
severity: ErrorSeverity.Todo,
loc: key.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -2740,6 +2807,7 @@ function lowerAssignment(
reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
severity: ErrorSeverity.Todo,
loc: element.node.loc ?? null,
suggestions: null,
});
continue;
}
@@ -2875,6 +2943,7 @@ function lowerAssignment(
reason: `(BuildHIR::lowerAssignment) Handle ${lvaluePath.type} assignments`,
severity: ErrorSeverity.Todo,
loc: lvaluePath.node.loc ?? null,
suggestions: null,
});
return { kind: "UnsupportedNode", node: lvalueNode, loc };
}
@@ -88,7 +88,12 @@ export class Dominator<T> {
*/
get(id: T): T | null {
const dominator = this.#nodes.get(id);
CompilerError.invariant(dominator !== undefined, "Unknown node", null);
CompilerError.invariant(dominator !== undefined, {
reason: "Unknown node",
description: null,
loc: null,
suggestions: null,
});
return dominator === id ? null : dominator;
}
@@ -119,7 +124,12 @@ export class PostDominator<T> {
*/
get(id: T): T | null {
const dominator = this.#nodes.get(id);
CompilerError.invariant(dominator !== undefined, "Unknown node", null);
CompilerError.invariant(dominator !== undefined, {
reason: "Unknown node",
description: null,
loc: null,
suggestions: null,
});
return dominator === id ? null : dominator;
}
@@ -159,11 +169,12 @@ function computeImmediateDominators<T>(graph: Graph<T>): Map<T, T> {
break;
}
}
CompilerError.invariant(
newIdom !== null,
`At least one predecessor must have been visited for block ${id}`,
null
);
CompilerError.invariant(newIdom !== null, {
reason: `At least one predecessor must have been visited for block ${id}`,
description: null,
loc: null,
suggestions: null,
});
for (const pred of node.preds) {
// For all other predecessors
@@ -195,11 +195,12 @@ export class Environment {
if (config?.customHooks) {
this.#globals = new Map(DEFAULT_GLOBALS);
for (const [hookName, hook] of config.customHooks) {
CompilerError.invariant(
!this.#globals.has(hookName),
`[Globals] Found existing definition in global registry for custom hook ${hookName}`,
null
);
CompilerError.invariant(!this.#globals.has(hookName), {
reason: `[Globals] Found existing definition in global registry for custom hook ${hookName}`,
description: null,
loc: null,
suggestions: null,
});
this.#globals.set(
hookName,
addHook(this.#shapes, [], {
@@ -271,11 +272,12 @@ export class Environment {
// If an object or function has a shapeId, it must have been assigned
// by Forget (and be present in a builtin or user-defined registry)
const shape = this.#shapes.get(shapeId);
CompilerError.invariant(
shape !== undefined,
`[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
null
);
CompilerError.invariant(shape !== undefined, {
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
description: null,
loc: null,
suggestions: null,
});
return shape.properties.get(property) ?? null;
} else {
return null;
@@ -286,11 +288,12 @@ export class Environment {
const { shapeId } = type;
if (shapeId !== null) {
const shape = this.#shapes.get(shapeId);
CompilerError.invariant(
shape !== undefined,
`[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
null
);
CompilerError.invariant(shape !== undefined, {
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
description: null,
loc: null,
suggestions: null,
});
return shape.functionType;
}
return null;
@@ -137,18 +137,20 @@ function handleAssignment(
for (const property of path.get("properties")) {
if (property.isObjectProperty()) {
const valuePath = property.get("value");
CompilerError.invariant(
valuePath.isLVal(),
`[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
valuePath.node.loc ?? GeneratedSource
);
CompilerError.invariant(valuePath.isLVal(), {
reason: `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
description: null,
loc: valuePath.node.loc ?? GeneratedSource,
suggestions: null,
});
handleAssignment(reassigned, valuePath);
} else {
CompilerError.invariant(
property.isRestElement(),
`[FindContextIdentifiers] Invalid assumptions for babel types.`,
property.node.loc ?? GeneratedSource
);
CompilerError.invariant(property.isRestElement(), {
reason: `[FindContextIdentifiers] Invalid assumptions for babel types.`,
description: null,
loc: property.node.loc ?? GeneratedSource,
suggestions: null,
});
handleAssignment(reassigned, property);
}
}
@@ -170,10 +172,12 @@ function handleAssignment(
break;
}
default: {
CompilerError.todo(
`[FindContextIdentifiers] Cannot handle Object destructuring assignment target ${lvalNode.type}`,
lvalNode.loc ?? GeneratedSource
);
CompilerError.todo({
reason: `[FindContextIdentifiers] Cannot handle Object destructuring assignment target ${lvalNode.type}`,
description: null,
loc: lvalNode.loc ?? GeneratedSource,
suggestions: null,
});
}
}
}
@@ -883,7 +883,12 @@ export function isMutableEffect(
}
case Effect.Unknown: {
CompilerError.invariant(false, "Unexpected unknown effect", location);
CompilerError.invariant(false, {
reason: "Unexpected unknown effect",
description: null,
loc: location,
suggestions: null,
});
}
case Effect.Read:
case Effect.Freeze: {
@@ -921,11 +926,12 @@ const opaqueBlockId = Symbol();
export type BlockId = number & { [opaqueBlockId]: "BlockId" };
export function makeBlockId(id: number): BlockId {
CompilerError.invariant(
id >= 0 && Number.isInteger(id),
"Expected block id to be a non-negative integer",
null
);
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: "Expected block id to be a non-negative integer",
description: null,
loc: null,
suggestions: null,
});
return id as BlockId;
}
@@ -937,11 +943,12 @@ const opaqueScopeId = Symbol();
export type ScopeId = number & { [opaqueScopeId]: "ScopeId" };
export function makeScopeId(id: number): ScopeId {
CompilerError.invariant(
id >= 0 && Number.isInteger(id),
"Expected block id to be a non-negative integer",
null
);
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: "Expected block id to be a non-negative integer",
description: null,
loc: null,
suggestions: null,
});
return id as ScopeId;
}
@@ -953,11 +960,12 @@ const opaqueIdentifierId = Symbol();
export type IdentifierId = number & { [opaqueIdentifierId]: "IdentifierId" };
export function makeIdentifierId(id: number): IdentifierId {
CompilerError.invariant(
id >= 0 && Number.isInteger(id),
"Expected identifier id to be a non-negative integer",
null
);
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: "Expected identifier id to be a non-negative integer",
description: null,
loc: null,
suggestions: null,
});
return id as IdentifierId;
}
@@ -969,11 +977,12 @@ const opaqueInstructionId = Symbol();
export type InstructionId = number & { [opaqueInstructionId]: "IdentifierId" };
export function makeInstructionId(id: number): InstructionId {
CompilerError.invariant(
id >= 0 && Number.isInteger(id),
"Expected instruction id to be a non-negative integer",
null
);
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: "Expected instruction id to be a non-negative integer",
description: null,
loc: null,
suggestions: null,
});
return id as InstructionId;
}
@@ -392,8 +392,12 @@ export default class HIRBuilder {
last.kind === "label" &&
last.label === label &&
last.breakBlock === breakBlock,
"Mismatched label",
null
{
reason: "Mismatched label",
description: null,
loc: null,
suggestions: null,
}
);
return value;
}
@@ -411,8 +415,12 @@ export default class HIRBuilder {
last.kind === "switch" &&
last.label === label &&
last.breakBlock === breakBlock,
"Mismatched label",
null
{
reason: "Mismatched label",
description: null,
loc: null,
suggestions: null,
}
);
return value;
}
@@ -447,8 +455,12 @@ export default class HIRBuilder {
last.label === label &&
last.continueBlock === continueBlock &&
last.breakBlock === breakBlock,
"Mismatched loops",
null
{
reason: "Mismatched loops",
description: null,
loc: null,
suggestions: null,
}
);
return value;
}
@@ -464,11 +476,12 @@ export default class HIRBuilder {
return scope.breakBlock;
}
}
CompilerError.invariant(
false,
"Expected a loop or switch to be in scope",
null
);
CompilerError.invariant(false, {
reason: "Expected a loop or switch to be in scope",
description: null,
loc: null,
suggestions: null,
});
}
/**
@@ -484,14 +497,20 @@ export default class HIRBuilder {
return scope.continueBlock;
}
} else if (label !== null && scope.label === label) {
CompilerError.invariant(
false,
"Continue may only refer to a labeled loop",
null
);
CompilerError.invariant(false, {
reason: "Continue may only refer to a labeled loop",
description: null,
loc: null,
suggestions: null,
});
}
}
CompilerError.invariant(false, "Expected a loop to be in scope", null);
CompilerError.invariant(false, {
reason: "Expected a loop to be in scope",
description: null,
loc: null,
suggestions: null,
});
}
}
@@ -511,11 +530,12 @@ function _shrink(func: HIR): void {
return target;
}
const block = func.blocks.get(blockId);
CompilerError.invariant(
block != null,
`expected block ${blockId} to exist`,
null
);
CompilerError.invariant(block != null, {
reason: `expected block ${blockId} to exist`,
description: null,
loc: null,
suggestions: null,
});
target = getTargetIfIndirection(block);
if (target !== null) {
// the target might also be a simple goto, recurse
@@ -731,11 +751,12 @@ export function markInstructionIds(func: HIR): void {
const visited = new Set<Instruction>();
for (const [_, block] of func.blocks) {
for (const instr of block.instructions) {
CompilerError.invariant(
!visited.has(instr),
`${printInstruction(instr)} already visited!`,
instr.loc
);
CompilerError.invariant(!visited.has(instr), {
reason: `${printInstruction(instr)} already visited!`,
description: null,
loc: instr.loc,
suggestions: null,
});
visited.add(instr);
instr.id = makeInstructionId(++id);
}
@@ -47,11 +47,12 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
const originalPredecessorId = Array.from(block.preds)[0]!;
const predecessorId = merged.get(originalPredecessorId);
const predecessor = fn.body.blocks.get(predecessorId);
CompilerError.invariant(
predecessor !== undefined,
`Expected predecessor ${predecessorId} to exist`,
null
);
CompilerError.invariant(predecessor !== undefined, {
reason: `Expected predecessor ${predecessorId} to exist`,
description: null,
loc: null,
suggestions: null,
});
if (predecessor.terminal.kind !== "goto" || predecessor.kind !== "block") {
// The predecessor is not guaranteed to transfer control to this block,
// they aren't consecutive.
@@ -60,11 +61,12 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
// Replace phis in the merged block with canonical assignments to the single operand value
for (const phi of block.phis) {
CompilerError.invariant(
phi.operands.size === 1,
`Found a block with a single predecessor but where a phi has multiple (${phi.operands.size}) operands`,
null
);
CompilerError.invariant(phi.operands.size === 1, {
reason: `Found a block with a single predecessor but where a phi has multiple (${phi.operands.size}) operands`,
description: null,
loc: null,
suggestions: null,
});
const operand = Array.from(phi.operands.values())[0]!;
const instr: Instruction = {
id: predecessor.terminal.id,
@@ -103,11 +103,12 @@ function addShape(
functionType,
};
CompilerError.invariant(
!registry.has(id),
`[ObjectShape] Could not add shape to registry: name ${id} already exists.`,
null
);
CompilerError.invariant(!registry.has(id), {
reason: `[ObjectShape] Could not add shape to registry: name ${id} already exists.`,
description: null,
loc: null,
suggestions: null,
});
registry.set(id, shape);
return shape;
}
@@ -469,8 +469,12 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
value = "`";
CompilerError.invariant(
instrValue.subexprs.length === instrValue.quasis.length - 1,
"Bad assumption about quasi length.",
instrValue.loc
{
reason: "Bad assumption about quasi length.",
description: null,
loc: instrValue.loc,
suggestions: null,
}
);
for (let i = 0; i < instrValue.subexprs.length; i++) {
value += instrValue.quasis[i].raw;
@@ -62,11 +62,12 @@ const opaqueTypeId = Symbol();
export type TypeId = number & { [opaqueTypeId]: "IdentifierId" };
export function makeTypeId(id: number): TypeId {
CompilerError.invariant(
id >= 0 && Number.isInteger(id),
"Expected instruction id to be a non-negative integer",
null
);
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: "Expected instruction id to be a non-negative integer",
description: null,
loc: null,
suggestions: null,
});
return id as TypeId;
}
@@ -169,11 +169,12 @@ function infer(
// In practice this never really matters because the Component function has no
// context refs, so it will never have duplicate deps.
for (const place of context) {
CompilerError.invariant(
place.identifier.name !== null,
"context refs should always have a name",
place.loc
);
CompilerError.invariant(place.identifier.name !== null, {
reason: "context refs should always have a name",
description: null,
loc: place.loc,
suggestions: null,
});
const effect = mutations.get(place.identifier.name);
if (effect !== undefined) {
@@ -108,11 +108,12 @@ export function inferMutableLifetimes(
start = Math.min(start, operand.mutableRange.start);
end = Math.max(end, operand.mutableRange.end);
}
CompilerError.invariant(
start !== Number.MAX_SAFE_INTEGER,
"Expected phi to have a start range value",
null
);
CompilerError.invariant(start !== Number.MAX_SAFE_INTEGER, {
reason: "Expected phi to have a start range value",
description: null,
loc: null,
suggestions: null,
});
phi.id.mutableRange = {
start: makeInstructionId(start),
end: makeInstructionId(end),
@@ -191,11 +191,13 @@ class InferenceState {
* (Re)initializes a @param value with its default @param kind.
*/
initialize(value: InstructionValue, kind: ValueKind): void {
CompilerError.invariant(
value.kind !== "LoadLocal",
"Expected all top-level identifiers to be defined as variables, not values",
value.loc
);
CompilerError.invariant(value.kind !== "LoadLocal", {
reason:
"Expected all top-level identifiers to be defined as variables, not values",
description: null,
loc: value.loc,
suggestions: null,
});
this.#values.set(value, kind);
}
@@ -204,24 +206,25 @@ class InferenceState {
*/
kind(place: Place): ValueKind {
const values = this.#variables.get(place.identifier.id);
CompilerError.invariant(
values != null,
`Expected value kind to be initialized at '${printSourceLocation(
CompilerError.invariant(values != null, {
reason: `Expected value kind to be initialized at '${printSourceLocation(
place.loc
)}'`,
place.loc
);
description: null,
loc: place.loc,
suggestions: null,
});
let mergedKind: ValueKind | null = null;
for (const value of values) {
const kind = this.#values.get(value)!;
mergedKind = mergedKind !== null ? mergeValues(mergedKind, kind) : kind;
}
CompilerError.invariant(
mergedKind !== null,
`InferReferenceEffects::kind: Expected at least one value`,
place.loc,
`No value found at '${printPlace(place)}'`
);
CompilerError.invariant(mergedKind !== null, {
reason: `InferReferenceEffects::kind: Expected at least one value`,
description: `No value found at '${printPlace(place)}'`,
loc: place.loc,
suggestions: null,
});
return mergedKind;
}
@@ -230,11 +233,12 @@ class InferenceState {
*/
alias(place: Place, value: Place): void {
const values = this.#variables.get(value.identifier.id);
CompilerError.invariant(
values != null,
`Expected value for identifier \`${value.identifier.id}\` to be initialized.`,
value.loc
);
CompilerError.invariant(values != null, {
reason: `Expected value for identifier \`${value.identifier.id}\` to be initialized.`,
description: null,
loc: value.loc,
suggestions: null,
});
this.#variables.set(place.identifier.id, new Set(values));
}
@@ -242,11 +246,14 @@ class InferenceState {
* Defines (initializing or updating) a variable with a specific kind of value.
*/
define(place: Place, value: InstructionValue): void {
CompilerError.invariant(
this.#values.has(value),
`Expected value to be initialized at '${printSourceLocation(value.loc)}'`,
value.loc
);
CompilerError.invariant(this.#values.has(value), {
reason: `Expected value to be initialized at '${printSourceLocation(
value.loc
)}'`,
description: null,
loc: value.loc,
suggestions: null,
});
this.#variables.set(place.identifier.id, new Set([value]));
}
@@ -269,11 +276,12 @@ class InferenceState {
reference(place: Place, effectKind: Effect): void {
const values = this.#variables.get(place.identifier.id);
if (values === undefined) {
CompilerError.invariant(
effectKind !== Effect.Store,
"[InferReferenceEffects] Unhandled store reference effect",
place.loc
);
CompilerError.invariant(effectKind !== Effect.Store, {
reason: "[InferReferenceEffects] Unhandled store reference effect",
description: null,
loc: place.loc,
suggestions: null,
});
place.effect =
effectKind === Effect.ConditionallyMutate
? Effect.ConditionallyMutate
@@ -315,13 +323,14 @@ class InferenceState {
) {
effect = Effect.Mutate;
} else {
CompilerError.invalidReact(
`InferReferenceEffects: inferred mutation of known immutable value`,
place.loc,
`Found mutation of ${printIdentifier(place.identifier)}${printType(
place.identifier.type
)} (${valueKind})`
);
CompilerError.invalidReact({
reason: `InferReferenceEffects: inferred mutation of known immutable value`,
description: `Found mutation of ${printIdentifier(
place.identifier
)}${printType(place.identifier.type)} (${valueKind})`,
loc: place.loc,
suggestions: null,
});
}
break;
}
@@ -330,13 +339,14 @@ class InferenceState {
valueKind !== ValueKind.Mutable &&
valueKind !== ValueKind.Context
) {
CompilerError.invalidReact(
`InferReferenceEffects: inferred mutation of known immutable value`,
place.loc,
`Found mutation of ${printIdentifier(place.identifier)}${printType(
place.identifier.type
)} (${valueKind})`
);
CompilerError.invalidReact({
reason: `InferReferenceEffects: inferred mutation of known immutable value`,
description: `Found mutation of ${printIdentifier(
place.identifier
)}${printType(place.identifier.type)} (${valueKind})`,
loc: place.loc,
suggestions: null,
});
}
// TODO(gsn): This should be bailout once we add bailout infra.
@@ -365,11 +375,13 @@ class InferenceState {
break;
}
case Effect.Unknown: {
CompilerError.invariant(
false,
"Unexpected unknown effect, expected to infer a precise effect kind",
place.loc
);
CompilerError.invariant(false, {
reason:
"Unexpected unknown effect, expected to infer a precise effect kind",
description: null,
loc: place.loc,
suggestions: null,
});
}
default: {
assertExhaustive(
@@ -378,11 +390,12 @@ class InferenceState {
);
}
}
CompilerError.invariant(
effect !== null,
"Expected effect to be set",
place.loc
);
CompilerError.invariant(effect !== null, {
reason: "Expected effect to be set",
description: null,
loc: place.loc,
suggestions: null,
});
place.effect = effect;
}
@@ -753,11 +766,13 @@ function inferBlock(
continue;
}
case "MethodCall": {
CompilerError.invariant(
state.isDefined(instrValue.receiver),
"[InferReferenceEffects] Internal error: receiver of PropertyCall should have been defined by corresponding PropertyLoad",
instrValue.loc
);
CompilerError.invariant(state.isDefined(instrValue.receiver), {
reason:
"[InferReferenceEffects] Internal error: receiver of PropertyCall should have been defined by corresponding PropertyLoad",
description: null,
loc: instrValue.loc,
suggestions: null,
});
state.reference(instrValue.property, Effect.Read);
const signature = getFunctionCallSignature(
@@ -892,8 +907,13 @@ function inferBlock(
const valueKind = state.kind(instrValue.place);
CompilerError.invariant(
valueKind === ValueKind.Mutable || valueKind === ValueKind.Context,
"[InferReferenceEffects] Context variables are always mutable.",
instrValue.loc
{
reason:
"[InferReferenceEffects] Context variables are always mutable.",
description: null,
loc: instrValue.loc,
suggestions: null,
}
);
state.initialize(instrValue, valueKind);
state.define(lvalue, instrValue);
@@ -980,11 +1000,12 @@ function inferBlock(
}
for (const operand of eachInstructionOperand(instr)) {
CompilerError.invariant(
effectKind != null,
`effectKind must be set for instruction value \`${instrValue.kind}\``,
instrValue.loc
);
CompilerError.invariant(effectKind != null, {
reason: `effectKind must be set for instruction value \`${instrValue.kind}\``,
description: null,
loc: instrValue.loc,
suggestions: null,
});
state.reference(operand, effectKind);
}
@@ -100,17 +100,22 @@ export function inlineUseMemo(fn: HIRFunction): void {
}
if (body.loweredFunc.params.length > 0) {
CompilerError.invalidReact(
"useMemo callbacks may not accept any arguments",
body.loc
);
CompilerError.invalidReact({
reason: "useMemo callbacks may not accept any arguments",
description: null,
loc: body.loc,
suggestions: null,
});
}
if (body.loweredFunc.async || body.loweredFunc.generator) {
CompilerError.invalidReact(
"useMemo callbacks may not be async or generator functions",
body.loc
);
CompilerError.invalidReact({
reason:
"useMemo callbacks may not be async or generator functions",
description: null,
loc: body.loc,
suggestions: null,
});
}
// We know this function is used for useMemo and can prune it later
@@ -70,11 +70,12 @@ class Context {
this.#builders.push(builder);
fn();
const popped = this.#builders.pop();
CompilerError.invariant(
popped === builder,
"Expected push/pop to be called 1:1",
null
);
CompilerError.invariant(popped === builder, {
reason: "Expected push/pop to be called 1:1",
description: null,
loc: null,
suggestions: null,
});
return builder.complete();
}
}
@@ -94,11 +95,12 @@ class Builder {
append(item: ReactiveStatement, label: BlockId | null): void {
if (label !== null) {
CompilerError.invariant(
item.kind === "terminal",
"Only terminals may have a label",
null
);
CompilerError.invariant(item.kind === "terminal", {
reason: "Only terminals may have a label",
description: null,
loc: null,
suggestions: null,
});
item.label = label;
}
this.#instructions.push(item);
@@ -138,11 +140,12 @@ class Builder {
// "Expected all scopes to be closed when exiting a block"
// );
const first = this.#stack[0]!;
CompilerError.invariant(
first.kind === "block",
"Expected first stack item to be a basic block",
null
);
CompilerError.invariant(first.kind === "block", {
reason: "Expected first stack item to be a basic block",
description: null,
loc: null,
suggestions: null,
});
return first.block;
}
}
@@ -173,11 +176,12 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
break;
}
case "scope": {
CompilerError.invariant(
false,
"Expected the function to not have scopes already assigned",
null
);
CompilerError.invariant(false, {
reason: "Expected the function to not have scopes already assigned",
description: null,
loc: null,
suggestions: null,
});
}
default: {
assertExhaustive(
@@ -192,12 +196,14 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
export function getInstructionScope(
instr: ReactiveInstruction
): ReactiveScope | null {
CompilerError.invariant(
instr.lvalue !== null,
"Expected lvalues to not be null when assigning scopes. " +
CompilerError.invariant(instr.lvalue !== null, {
reason:
"Expected lvalues to not be null when assigning scopes. " +
"Pruning lvalues too early can result in missing scope information.",
instr.loc
);
description: null,
loc: instr.loc,
suggestions: null,
});
for (const operand of eachInstructionLValue(instr)) {
const operandScope = getPlaceScope(instr.id, operand);
if (operandScope !== null) {
@@ -65,11 +65,12 @@ class Driver {
}
visitBlock(block: BasicBlock, blockValue: ReactiveBlock): void {
CompilerError.invariant(
!this.cx.emitted.has(block.id),
`Cannot emit the same block twice: bb${block.id}`,
null
);
CompilerError.invariant(!this.cx.emitted.has(block.id), {
reason: `Cannot emit the same block twice: bb${block.id}`,
description: null,
loc: null,
suggestions: null,
});
this.cx.emitted.add(block.id);
for (const instruction of block.instructions) {
blockValue.push({
@@ -252,11 +253,12 @@ class Driver {
loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
} else {
const break_ = this.visitBreak(terminal.loop, null);
CompilerError.invariant(
break_ !== null,
"If loop body is already scheduled it must be a break",
null
);
CompilerError.invariant(break_ !== null, {
reason: "If loop body is already scheduled it must be a break",
description: null,
loc: null,
suggestions: null,
});
loopBody = [break_];
}
@@ -309,11 +311,12 @@ class Driver {
loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
} else {
const break_ = this.visitBreak(terminal.loop, null);
CompilerError.invariant(
break_ !== null,
"If loop body is already scheduled it must be a break",
null
);
CompilerError.invariant(break_ !== null, {
reason: "If loop body is already scheduled it must be a break",
description: null,
loc: null,
suggestions: null,
});
loopBody = [break_];
}
@@ -394,11 +397,12 @@ class Driver {
loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
} else {
const break_ = this.visitBreak(terminal.loop, null);
CompilerError.invariant(
break_ !== null,
"If loop body is already scheduled it must be a break",
null
);
CompilerError.invariant(break_ !== null, {
reason: "If loop body is already scheduled it must be a break",
description: null,
loc: null,
suggestions: null,
});
loopBody = [break_];
}
@@ -470,11 +474,12 @@ class Driver {
loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
} else {
const break_ = this.visitBreak(terminal.loop, null);
CompilerError.invariant(
break_ !== null,
"If loop body is already scheduled it must be a break",
null
);
CompilerError.invariant(break_ !== null, {
reason: "If loop body is already scheduled it must be a break",
description: null,
loc: null,
suggestions: null,
});
loopBody = [break_];
}
@@ -547,11 +552,13 @@ class Driver {
let block: ReactiveBlock;
if (this.cx.isScheduled(terminal.block)) {
const break_ = this.visitBreak(terminal.block, null);
CompilerError.invariant(
break_ !== null,
"Expected a break target for a label whose body is already scheduled",
terminal.loc
);
CompilerError.invariant(break_ !== null, {
reason:
"Expected a break target for a label whose body is already scheduled",
description: null,
loc: terminal.loc,
suggestions: null,
});
block = [break_];
} else {
block = this.traverseBlock(this.cx.ir.blocks.get(terminal.block)!);
@@ -630,11 +637,12 @@ class Driver {
break;
}
case "unsupported": {
CompilerError.invariant(
false,
"Unexpected unsupported terminal",
terminal.loc
);
CompilerError.invariant(false, {
reason: "Unexpected unsupported terminal",
description: null,
loc: terminal.loc,
suggestions: null,
});
}
default: {
assertExhaustive(terminal, "Unexpected terminal");
@@ -665,8 +673,13 @@ class Driver {
CompilerError.invariant(
instr.lvalue.identifier.id ===
defaultBlock.terminal.test.identifier.id,
"Expected branch block to end in an instruction that sets the test value",
instr.lvalue.loc
{
reason:
"Expected branch block to end in an instruction that sets the test value",
description: null,
loc: instr.lvalue.loc,
suggestions: null,
}
);
return {
block: defaultBlock.id,
@@ -693,11 +706,12 @@ class Driver {
} else if (defaultBlock.terminal.kind === "goto") {
const instructions = defaultBlock.instructions;
if (instructions.length === 0) {
CompilerError.invariant(
false,
"Expected goto value block to have at least one instruction",
null
);
CompilerError.invariant(false, {
reason: "Expected goto value block to have at least one instruction",
description: null,
loc: null,
suggestions: null,
});
} else if (defaultBlock.instructions.length === 1) {
const instr = defaultBlock.instructions[0]!;
let place: Place = instr.lvalue!;
@@ -792,11 +806,12 @@ class Driver {
case "optional": {
const test = this.visitValueBlock(terminal.test, terminal.loc);
const testBlock = this.cx.ir.blocks.get(test.block)!;
CompilerError.invariant(
testBlock.terminal.kind === "branch",
`Unexpected terminal kind '${testBlock.terminal.kind}' for optional call test block`,
testBlock.terminal.loc
);
CompilerError.invariant(testBlock.terminal.kind === "branch", {
reason: `Unexpected terminal kind '${testBlock.terminal.kind}' for optional call test block`,
description: null,
loc: testBlock.terminal.loc,
suggestions: null,
});
const consequent = this.visitValueBlock(
testBlock.terminal.consequent,
terminal.loc
@@ -831,11 +846,12 @@ class Driver {
case "logical": {
const test = this.visitValueBlock(terminal.test, terminal.loc);
const testBlock = this.cx.ir.blocks.get(test.block)!;
CompilerError.invariant(
testBlock.terminal.kind === "branch",
`Unexpected terminal kind '${testBlock.terminal.kind}' for logical test block`,
testBlock.terminal.loc
);
CompilerError.invariant(testBlock.terminal.kind === "branch", {
reason: `Unexpected terminal kind '${testBlock.terminal.kind}' for logical test block`,
description: null,
loc: testBlock.terminal.loc,
suggestions: null,
});
const leftFinal = this.visitValueBlock(
testBlock.terminal.consequent,
@@ -876,11 +892,12 @@ class Driver {
case "ternary": {
const test = this.visitValueBlock(terminal.test, terminal.loc);
const testBlock = this.cx.ir.blocks.get(test.block)!;
CompilerError.invariant(
testBlock.terminal.kind === "branch",
`Unexpected terminal kind '${testBlock.terminal.kind}' for ternary test block`,
testBlock.terminal.loc
);
CompilerError.invariant(testBlock.terminal.kind === "branch", {
reason: `Unexpected terminal kind '${testBlock.terminal.kind}' for ternary test block`,
description: null,
loc: testBlock.terminal.loc,
suggestions: null,
});
const consequent = this.visitValueBlock(
testBlock.terminal.consequent,
terminal.loc
@@ -905,11 +922,12 @@ class Driver {
};
}
default: {
CompilerError.invariant(
false,
`Unexpected value block terminal kind '${terminal.kind}'`,
terminal.loc
);
CompilerError.invariant(false, {
reason: `Unexpected value block terminal kind '${terminal.kind}'`,
description: null,
loc: terminal.loc,
suggestions: null,
});
}
}
}
@@ -924,7 +942,12 @@ class Driver {
): ReactiveTerminalStatement<ReactiveBreakTerminal> {
const target = this.cx.getBreakTarget(block);
if (target === null) {
CompilerError.invariant(false, "Expected a break target", null);
CompilerError.invariant(false, {
reason: "Expected a break target",
description: null,
loc: null,
suggestions: null,
});
}
switch (target.type) {
case "implicit": {
@@ -962,11 +985,12 @@ class Driver {
id: InstructionId
): ReactiveTerminalStatement<ReactiveContinueTerminal> {
const target = this.cx.getContinueTarget(block);
CompilerError.invariant(
target !== null,
`Expected continue target to be scheduled for bb${block}`,
null
);
CompilerError.invariant(target !== null, {
reason: `Expected continue target to be scheduled for bb${block}`,
description: null,
loc: null,
suggestions: null,
});
switch (target.type) {
case "implicit": {
return {
@@ -1046,11 +1070,12 @@ class Context {
*/
schedule(block: BlockId, type: "if" | "switch" | "case"): number {
const id = this.#nextScheduleId++;
CompilerError.invariant(
!this.#scheduled.has(block),
`Break block is already scheduled: bb${block}`,
null
);
CompilerError.invariant(!this.#scheduled.has(block), {
reason: `Break block is already scheduled: bb${block}`,
description: null,
loc: null,
suggestions: null,
});
this.#scheduled.add(block);
this.#controlFlowStack.push({ block, id, type });
return id;
@@ -1064,11 +1089,12 @@ class Context {
const id = this.#nextScheduleId++;
const ownsBlock = !this.#scheduled.has(fallthroughBlock);
this.#scheduled.add(fallthroughBlock);
CompilerError.invariant(
!this.#scheduled.has(continueBlock),
`Continue block is already scheduled: bb${continueBlock}`,
null
);
CompilerError.invariant(!this.#scheduled.has(continueBlock), {
reason: `Continue block is already scheduled: bb${continueBlock}`,
description: null,
loc: null,
suggestions: null,
});
this.#scheduled.add(continueBlock);
let ownsLoop = false;
if (loopBlock !== null) {
@@ -1093,11 +1119,12 @@ class Context {
*/
unschedule(scheduleId: number): void {
const last = this.#controlFlowStack.pop();
CompilerError.invariant(
last !== undefined && last.id === scheduleId,
"Can only unschedule the last target",
null
);
CompilerError.invariant(last !== undefined && last.id === scheduleId, {
reason: "Can only unschedule the last target",
description: null,
loc: null,
suggestions: null,
});
if (last.type !== "loop" || last.ownsBlock !== null) {
this.#scheduled.delete(last.block);
}
@@ -215,11 +215,12 @@ function codegenReactiveScope(
firstOutputIndex = index;
}
CompilerError.invariant(
identifier.name != null,
`Expected identifier '@${identifier.id}' to be named`,
null
);
CompilerError.invariant(identifier.name != null, {
reason: `Expected identifier '@${identifier.id}' to be named`,
description: null,
loc: null,
suggestions: null,
});
const name = convertIdentifier(identifier);
if (!cx.hasDeclared(identifier)) {
@@ -283,11 +284,12 @@ function codegenReactiveScope(
null as t.Expression | null
);
if (testCondition === null) {
CompilerError.invariant(
firstOutputIndex !== null,
`Expected scope '@${scope.id}' to have at least one declaration`,
null
);
CompilerError.invariant(firstOutputIndex !== null, {
reason: `Expected scope '@${scope.id}' to have at least one declaration`,
description: null,
loc: null,
suggestions: null,
});
testCondition = t.binaryExpression(
"===",
t.memberExpression(
@@ -344,17 +346,19 @@ function codegenTerminal(
);
}
case "for-of": {
CompilerError.invariant(
terminal.init.kind === "SequenceExpression",
`Expected a sequence expression init for ForOf`,
terminal.init.loc,
`Got '${terminal.init.kind}' expression instead`
);
CompilerError.invariant(terminal.init.kind === "SequenceExpression", {
reason: `Expected a sequence expression init for ForOf`,
description: `Got '${terminal.init.kind}' expression instead`,
loc: terminal.init.loc,
suggestions: null,
});
if (terminal.init.instructions.length !== 2) {
CompilerError.todo(
"Support non-trivial ForOf inits",
terminal.init.loc
);
CompilerError.todo({
reason: "Support non-trivial ForOf inits",
description: null,
loc: terminal.init.loc,
suggestions: null,
});
}
const iterableCollection = terminal.init.instructions[0];
const iterableItem = terminal.init.instructions[1];
@@ -369,12 +373,12 @@ function codegenTerminal(
break;
}
default:
CompilerError.invariant(
false,
`Expected a StoreLocal or Destructure to be assigned to the collection`,
iterableItem.value.loc,
`Found ${iterableItem.value.kind}`
);
CompilerError.invariant(false, {
reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
description: `Found ${iterableItem.value.kind}`,
loc: iterableItem.value.loc,
suggestions: null,
});
}
let varDeclKind: "const" | "let";
switch (iterableItem.value.lvalue.kind) {
@@ -385,11 +389,13 @@ function codegenTerminal(
varDeclKind = "let" as const;
break;
case InstructionKind.Reassign:
CompilerError.invariant(
false,
"Destructure should never be Reassign as it would be an Object/ArrayPattern",
iterableItem.loc
);
CompilerError.invariant(false, {
reason:
"Destructure should never be Reassign as it would be an Object/ArrayPattern",
description: null,
loc: iterableItem.loc,
suggestions: null,
});
default:
assertExhaustive(
iterableItem.value.lvalue.kind,
@@ -509,11 +515,13 @@ function codegenInstructionNullable(
hasDeclaration ||= !isDeclared;
}
if (hasReasign && hasDeclaration) {
CompilerError.invariant(
false,
"Encountered a destructuring operation where some identifiers are already declared (reassignments) but others are not (declarations)",
instr.loc
);
CompilerError.invariant(false, {
reason:
"Encountered a destructuring operation where some identifiers are already declared (reassignments) but others are not (declarations)",
description: null,
loc: instr.loc,
suggestions: null,
});
} else if (hasReasign) {
kind = InstructionKind.Reassign;
}
@@ -521,31 +529,34 @@ function codegenInstructionNullable(
}
switch (kind) {
case InstructionKind.Const: {
CompilerError.invariant(
instr.lvalue === null,
`Const declaration cannot be referenced as an expression`,
instr.value.loc
);
CompilerError.invariant(instr.lvalue === null, {
reason: `Const declaration cannot be referenced as an expression`,
description: null,
loc: instr.value.loc,
suggestions: null,
});
return createVariableDeclaration(instr.loc, "const", [
t.variableDeclarator(codegenLValue(lvalue), value),
]);
}
case InstructionKind.Let: {
CompilerError.invariant(
instr.lvalue === null,
`Const declaration cannot be referenced as an expression`,
instr.value.loc
);
CompilerError.invariant(instr.lvalue === null, {
reason: `Const declaration cannot be referenced as an expression`,
description: null,
loc: instr.value.loc,
suggestions: null,
});
return createVariableDeclaration(instr.loc, "let", [
t.variableDeclarator(codegenLValue(lvalue), value),
]);
}
case InstructionKind.Reassign: {
CompilerError.invariant(
value !== null,
"Expected a value for reassignment",
instr.value.loc
);
CompilerError.invariant(value !== null, {
reason: "Expected a value for reassignment",
description: null,
loc: instr.value.loc,
suggestions: null,
});
const expr = t.assignmentExpression("=", codegenLValue(lvalue), value);
if (instr.lvalue !== null) {
if (instr.value.kind !== "StoreContext") {
@@ -592,11 +603,12 @@ function codegenForInit(
}))
).body;
const declaration = body[0]!;
CompilerError.invariant(
declaration.type === "VariableDeclaration",
"Expected a variable declaration",
declaration.loc ?? null
);
CompilerError.invariant(declaration.type === "VariableDeclaration", {
reason: "Expected a variable declaration",
description: null,
loc: declaration.loc ?? null,
suggestions: null,
});
return declaration;
} else {
return codegenInstructionValue(cx, init);
@@ -744,11 +756,12 @@ function codegenInstructionValue(
switch (optionalValue.type) {
case "OptionalCallExpression":
case "CallExpression": {
CompilerError.invariant(
t.isExpression(optionalValue.callee),
"v8 intrinsics are validated during lowering",
optionalValue.callee.loc ?? null
);
CompilerError.invariant(t.isExpression(optionalValue.callee), {
reason: "v8 intrinsics are validated during lowering",
description: null,
loc: optionalValue.callee.loc ?? null,
suggestions: null,
});
value = t.optionalCallExpression(
optionalValue.callee,
optionalValue.arguments,
@@ -759,11 +772,12 @@ function codegenInstructionValue(
case "OptionalMemberExpression":
case "MemberExpression": {
const property = optionalValue.property;
CompilerError.invariant(
t.isExpression(property),
"Private names are validated during lowering",
property.loc ?? null
);
CompilerError.invariant(t.isExpression(property), {
reason: "Private names are validated during lowering",
description: null,
loc: property.loc ?? null,
suggestions: null,
});
value = t.optionalMemberExpression(
optionalValue.object,
property,
@@ -773,12 +787,13 @@ function codegenInstructionValue(
break;
}
default: {
CompilerError.invariant(
false,
"Expected an optional value to resolve to a call expression or member expression",
instrValue.loc,
`Got a '${optionalValue.type}'`
);
CompilerError.invariant(false, {
reason:
"Expected an optional value to resolve to a call expression or member expression",
description: `Got a '${optionalValue.type}'`,
loc: instrValue.loc,
suggestions: null,
});
}
}
break;
@@ -788,18 +803,28 @@ function codegenInstructionValue(
CompilerError.invariant(
t.isMemberExpression(memberExpr) ||
t.isOptionalMemberExpression(memberExpr),
"[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. " +
`Got a '${memberExpr.type}'`,
memberExpr.loc ?? null
{
reason:
"[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. " +
`Got a '${memberExpr.type}'`,
description: null,
loc: memberExpr.loc ?? null,
suggestions: null,
}
);
CompilerError.invariant(
t.isNodesEquivalent(
memberExpr.object,
codegenPlace(cx, instrValue.receiver)
),
"[Codegen] Internal error: Forget should always generate MethodCall::property " +
"as a MemberExpression of MethodCall::receiver",
memberExpr.loc ?? null
{
reason:
"[Codegen] Internal error: Forget should always generate MethodCall::property " +
"as a MemberExpression of MethodCall::receiver",
description: null,
loc: memberExpr.loc ?? null,
suggestions: null,
}
);
const args = instrValue.args.map((arg) => codegenArgument(cx, arg));
value = createCallExpression(instrValue.loc, memberExpr, args);
@@ -851,11 +876,12 @@ function codegenInstructionValue(
} else if (tagValue.type === "MemberExpression") {
tag = convertMemberExpressionToJsx(tagValue);
} else {
CompilerError.invariant(
tagValue.type === "StringLiteral",
`Expected JSX tag to be an identifier or string, got '${tagValue.type}'`,
tagValue.loc ?? null
);
CompilerError.invariant(tagValue.type === "StringLiteral", {
reason: `Expected JSX tag to be an identifier or string, got '${tagValue.type}'`,
description: null,
loc: tagValue.loc ?? null,
suggestions: null,
});
if (tagValue.value.indexOf(":") >= 0) {
const [namespace, name] = tagValue.value.split(":", 2);
tag = createJsxNamespacedName(
@@ -1049,6 +1075,7 @@ function codegenInstructionValue(
}'`,
severity: ErrorSeverity.Todo,
loc: declarator.loc ?? null,
suggestions: null,
});
return t.stringLiteral(`TODO handle ${declarator.id}`);
} else {
@@ -1056,6 +1083,7 @@ function codegenInstructionValue(
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
severity: ErrorSeverity.Todo,
loc: stmt.loc ?? null,
suggestions: null,
});
return t.stringLiteral(`TODO handle ${stmt.type}`);
}
@@ -1105,11 +1133,12 @@ function codegenInstructionValue(
case "Destructure":
case "StoreLocal":
case "StoreContext": {
CompilerError.invariant(
false,
`Unexpected ${instrValue.kind} in codegenInstructionValue`,
instrValue.loc
);
CompilerError.invariant(false, {
reason: `Unexpected ${instrValue.kind} in codegenInstructionValue`,
description: null,
loc: instrValue.loc,
suggestions: null,
});
}
default: {
assertExhaustive(
@@ -1193,20 +1222,23 @@ function codegenJsxElement(
function convertMemberExpressionToJsx(
expr: t.MemberExpression
): t.JSXMemberExpression {
CompilerError.invariant(
expr.property.type === "Identifier",
"Expected JSX member expression property to be a string",
expr.loc ?? null
);
CompilerError.invariant(expr.property.type === "Identifier", {
reason: "Expected JSX member expression property to be a string",
description: null,
loc: expr.loc ?? null,
suggestions: null,
});
const property = t.jsxIdentifier(expr.property.name);
if (expr.object.type === "Identifier") {
return t.jsxMemberExpression(t.jsxIdentifier(expr.object.name), property);
} else {
CompilerError.invariant(
expr.object.type === "MemberExpression",
"Expected JSX member expression to be an identifier or nested member expression",
expr.object.loc ?? null
);
CompilerError.invariant(expr.object.type === "MemberExpression", {
reason:
"Expected JSX member expression to be an identifier or nested member expression",
description: null,
loc: expr.object.loc ?? null,
suggestions: null,
});
const object = convertMemberExpressionToJsx(expr.object);
return t.jsxMemberExpression(object, property);
}
@@ -1288,12 +1320,14 @@ function codegenPlace(cx: Context, place: Place): t.Expression {
if (tmp != null) {
return tmp;
}
CompilerError.invariant(
place.identifier.name !== null || tmp !== undefined,
`[Codegen] No value found for temporary`,
place.loc,
`Value for '${printPlace(place)}' was not set in the codegen context`
);
CompilerError.invariant(place.identifier.name !== null || tmp !== undefined, {
reason: `[Codegen] No value found for temporary`,
description: `Value for '${printPlace(
place
)}' was not set in the codegen context`,
loc: place.loc,
suggestions: null,
});
const identifier = convertIdentifier(place.identifier);
identifier.loc = place.loc as any;
return identifier;
@@ -126,8 +126,13 @@ export class ReactiveScopeDependencyTree {
deps.every(
(dep) => dep.accessType === PropertyAccessType.UnconditionalDependency
),
"[PropagateScopeDependencies] All dependencies must be reduced to unconditional dependencies.",
null
{
reason:
"[PropagateScopeDependencies] All dependencies must be reduced to unconditional dependencies.",
description: null,
loc: null,
suggestions: null,
}
);
for (const dep of deps) {
@@ -163,11 +168,12 @@ export class ReactiveScopeDependencyTree {
promoteDepsFromExhaustiveConditionals(
trees: Array<ReactiveScopeDependencyTree>
): void {
CompilerError.invariant(
trees.length > 1,
"Expected trees to be at least 2 elements long.",
null
);
CompilerError.invariant(trees.length > 1, {
reason: "Expected trees to be at least 2 elements long.",
description: null,
loc: null,
suggestions: null,
});
for (const [id, root] of this.#roots) {
const nodesForRootId = mapNonNull(trees, (tree) => tree.#roots.get(id));
@@ -458,19 +464,23 @@ function addSubtreeIntersection(
otherProperties: Array<Map<string, DependencyNode>>,
currProperties: Map<string, DependencyNode>
): void {
CompilerError.invariant(
otherProperties.length > 1,
"[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long.",
null
);
CompilerError.invariant(otherProperties.length > 1, {
reason:
"[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long.",
description: null,
loc: null,
suggestions: null,
});
otherProperties.forEach((properties) =>
properties.forEach((node, _) =>
CompilerError.invariant(
!isUnconditional(node.accessType),
"[DeriveMinimalDependencies] Expected otherProperties to only contain unconditional nodes!",
null
)
CompilerError.invariant(!isUnconditional(node.accessType), {
reason:
"[DeriveMinimalDependencies] Expected otherProperties to only contain unconditional nodes!",
description: null,
loc: null,
suggestions: null,
})
)
);
@@ -105,11 +105,12 @@ class Visitor extends ReactiveFunctionVisitor<State> {
break;
}
case Effect.Unknown: {
CompilerError.invariant(
false,
"Unexpected unknown effect",
operand.loc
);
CompilerError.invariant(false, {
reason: "Unexpected unknown effect",
description: null,
loc: operand.loc,
suggestions: null,
});
}
default: {
assertExhaustive(
@@ -226,11 +226,12 @@ function printTerminal(writer: Writer, terminal: ReactiveTerminal): void {
writer.writeLine(`${prefix}: {`);
writer.indented(() => {
const block = case_.block;
CompilerError.invariant(
block != null,
"Expected case to have a block",
case_.test?.loc ?? null
);
CompilerError.invariant(block != null, {
reason: "Expected case to have a block",
description: null,
loc: case_.test?.loc ?? null,
suggestions: null,
});
printReactiveInstructions(writer, block);
});
writer.writeLine("}");
@@ -75,11 +75,13 @@ export function promoteUsedTemporaries(fn: ReactiveFunction): void {
}
function promoteTemporary(identifier: Identifier, state: VisitorState): void {
CompilerError.invariant(
identifier.name === null,
"promoteTemporary: Expected to be called only for temporary variables",
GeneratedSource
);
CompilerError.invariant(identifier.name === null, {
reason:
"promoteTemporary: Expected to be called only for temporary variables",
description: null,
loc: GeneratedSource,
suggestions: null,
});
if (state.tags.has(identifier.id)) {
identifier.name = `T${state.nextId++}`;
} else {
@@ -459,12 +459,13 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
// OptionalExpression value is a SequenceExpression where the instructions
// represent the code prior to the `?` and the final value represents the
// conditional code that follows.
CompilerError.invariant(
inner.kind === "SequenceExpression",
"Expected OptionalExpression value to be a SequenceExpression",
value.loc,
`Found a '${value.kind}'`
);
CompilerError.invariant(inner.kind === "SequenceExpression", {
reason:
"Expected OptionalExpression value to be a SequenceExpression",
description: `Found a '${value.kind}'`,
loc: value.loc,
suggestions: null,
});
// Instructions are the unconditionally executed portion before the `?`
for (const instr of inner.instructions) {
this.visitInstruction(instr, context);
@@ -243,11 +243,12 @@ class State {
this.scopes.set(scope.id, node);
}
const identifierNode = this.identifiers.get(identifier);
CompilerError.invariant(
identifierNode !== undefined,
"Expected identifier to be initialized",
place.loc
);
CompilerError.invariant(identifierNode !== undefined, {
reason: "Expected identifier to be initialized",
description: null,
loc: place.loc,
suggestions: null,
});
identifierNode.scopes.add(scope.id);
}
}
@@ -264,11 +265,12 @@ function computeMemoizedIdentifiers(state: State): Set<IdentifierId> {
// Visit an identifier, optionally forcing it to be memoized
function visit(id: IdentifierId, forceMemoize: boolean = false): boolean {
const node = state.identifiers.get(id);
CompilerError.invariant(
node !== undefined,
`Expected a node for all identifiers, none found for '${id}'`,
null
);
CompilerError.invariant(node !== undefined, {
reason: `Expected a node for all identifiers, none found for '${id}'`,
description: null,
loc: null,
suggestions: null,
});
if (node.seen) {
return node.memoized;
}
@@ -303,11 +305,12 @@ function computeMemoizedIdentifiers(state: State): Set<IdentifierId> {
// Force all the scope's optionally-memoizeable dependencies (not "Never") to be memoized
function forceMemoizeScopeDependencies(id: ScopeId): void {
const node = state.scopes.get(id);
CompilerError.invariant(
node !== undefined,
"Expected a node for all scopes",
null
);
CompilerError.invariant(node !== undefined, {
reason: "Expected a node for all scopes",
description: null,
loc: null,
suggestions: null,
});
if (node.seen) {
return;
}
@@ -606,7 +609,12 @@ function computeMemoizationInputs(
};
}
case "UnsupportedNode": {
CompilerError.invariant(false, `Unexpected unsupported node`, value.loc);
CompilerError.invariant(false, {
reason: `Unexpected unsupported node`,
description: null,
loc: value.loc,
suggestions: null,
});
}
default: {
assertExhaustive(value, `Unexpected value kind '${(value as any).kind}'`);
@@ -103,6 +103,11 @@ class Scopes {
this.#stack.push(next);
fn();
const last = this.#stack.pop();
CompilerError.invariant(last === next, "Mismatch push/pop calls", null);
CompilerError.invariant(last === next, {
reason: "Mismatch push/pop calls",
description: null,
loc: null,
suggestions: null,
});
}
}
@@ -83,11 +83,12 @@ export function eliminateRedundantPhi(fn: HIRFunction): void {
same = operand;
}
}
CompilerError.invariant(
same !== null,
"Expected phis to be non-empty",
null
);
CompilerError.invariant(same !== null, {
reason: "Expected phis to be non-empty",
description: null,
loc: null,
suggestions: null,
});
rewrites.set(phi.id, same);
block.phis.delete(phi);
}
@@ -67,11 +67,12 @@ class SSABuilder {
}
state(): State {
CompilerError.invariant(
this.#current !== null,
"we need to be in a block to access state!",
null
);
CompilerError.invariant(this.#current !== null, {
reason: "we need to be in a block to access state!",
description: null,
loc: null,
suggestions: null,
});
return this.#states.get(this.#current)!;
}
@@ -96,12 +97,12 @@ class SSABuilder {
definePlace(oldPlace: Place): Place {
const oldId = oldPlace.identifier;
CompilerError.invariant(
!this.#unknown.has(oldId),
`EnterSSA: Expected identifier to be defined before being used`,
oldPlace.loc,
`Identifier ${printIdentifier(oldId)} is undefined`
);
CompilerError.invariant(!this.#unknown.has(oldId), {
reason: `EnterSSA: Expected identifier to be defined before being used`,
description: `Identifier ${printIdentifier(oldId)} is undefined`,
loc: oldPlace.loc,
suggestions: null,
});
// Do not redefine context references.
if (this.#context.has(oldId)) {
@@ -237,11 +238,12 @@ function enterSSAImpl(
): void {
const visitedBlocks: Set<BasicBlock> = new Set();
for (const [blockId, block] of func.body.blocks) {
CompilerError.invariant(
!visitedBlocks.has(block),
`found a cycle! visiting bb${block.id} again`,
null
);
CompilerError.invariant(!visitedBlocks.has(block), {
reason: `found a cycle! visiting bb${block.id} again`,
description: null,
loc: null,
suggestions: null,
});
visitedBlocks.add(block);
builder.startBlock(block);
@@ -249,11 +251,12 @@ function enterSSAImpl(
if (blockId === rootEntry) {
// NOTE: func.context should be empty for the root function
if (func.env.enableOptimizeFunctionExpressions) {
CompilerError.invariant(
func.context.length === 0,
`Expected function context to be empty for outer function declarations`,
func.loc
);
CompilerError.invariant(func.context.length === 0, {
reason: `Expected function context to be empty for outer function declarations`,
description: null,
loc: func.loc,
suggestions: null,
});
} else {
func.context = func.context.map((p) => builder.defineContext(p));
}
@@ -270,11 +273,13 @@ function enterSSAImpl(
) {
const loweredFunc = instr.value.loweredFunc;
const entry = loweredFunc.body.blocks.get(loweredFunc.body.entry)!;
CompilerError.invariant(
entry.preds.size === 0,
"Expected function expression entry block to have zero predecessors",
null
);
CompilerError.invariant(entry.preds.size === 0, {
reason:
"Expected function expression entry block to have zero predecessors",
description: null,
loc: null,
suggestions: null,
});
entry.preds.add(blockId);
builder.defineFunction(loweredFunc);
builder.enter(() => {
@@ -131,12 +131,12 @@ export function leaveSSA(fn: HIRFunction): void {
if (value.kind === "DeclareLocal") {
const name = value.lvalue.place.identifier.name;
if (name !== null) {
CompilerError.invariant(
!declarations.has(name),
`Unexpected duplicate declaration`,
value.lvalue.place.loc,
`Found duplicate declaration for '${name}'`
);
CompilerError.invariant(!declarations.has(name), {
reason: `Unexpected duplicate declaration`,
description: `Found duplicate declaration for '${name}'`,
loc: value.lvalue.place.loc,
suggestions: null,
});
declarations.set(name, {
lvalue: value.lvalue,
place: value.lvalue.place,
@@ -153,8 +153,12 @@ export function leaveSSA(fn: HIRFunction): void {
) {
CompilerError.invariant(
originalLVal !== undefined || block.kind === "block",
`TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
value.lvalue.place.loc
{
reason: `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
description: null,
loc: value.lvalue.place.loc,
suggestions: null,
}
);
declarations.set(value.lvalue.place.identifier.name, {
lvalue: value.lvalue,
@@ -176,9 +180,14 @@ export function leaveSSA(fn: HIRFunction): void {
if (place.identifier.name == null) {
CompilerError.invariant(
kind === null || kind === InstructionKind.Const,
`Expected consistent kind for destructuring`,
place.loc,
`other places were '${kind}' but '${printPlace(place)}' is const`
{
reason: `Expected consistent kind for destructuring`,
description: `other places were '${kind}' but '${printPlace(
place
)}' is const`,
loc: place.loc,
suggestions: null,
}
);
kind = InstructionKind.Const;
} else {
@@ -189,8 +198,12 @@ export function leaveSSA(fn: HIRFunction): void {
) {
CompilerError.invariant(
originalLVal !== undefined || block.kind !== "value",
`TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
place.loc
{
reason: `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
description: null,
loc: place.loc,
suggestions: null,
}
);
declarations.set(place.identifier.name, {
lvalue: value.lvalue,
@@ -198,32 +211,39 @@ export function leaveSSA(fn: HIRFunction): void {
});
CompilerError.invariant(
kind === null || kind === InstructionKind.Const,
`Expected consistent kind for destructuring`,
place.loc,
`Other places were '${kind}' but '${printPlace(
place
)}' is const`
{
reason: `Expected consistent kind for destructuring`,
description: `Other places were '${kind}' but '${printPlace(
place
)}' is const`,
loc: place.loc,
suggestions: null,
}
);
kind = InstructionKind.Const;
} else {
CompilerError.invariant(
kind === null || kind === InstructionKind.Reassign,
`Expected consistent kind for destructuring`,
place.loc,
`Other places were '${kind}' but '${printPlace(
place
)}' is reassigned`
{
reason: `Expected consistent kind for destructuring`,
description: `Other places were '${kind}' but '${printPlace(
place
)}' is reassigned`,
loc: place.loc,
suggestions: null,
}
);
kind = InstructionKind.Reassign;
originalLVal.lvalue.kind = InstructionKind.Let;
}
}
}
CompilerError.invariant(
kind !== null,
"Expected at least one operand",
null
);
CompilerError.invariant(kind !== null, {
reason: "Expected at least one operand",
description: null,
loc: null,
suggestions: null,
});
value.lvalue.kind = kind;
}
rewritePlace(lvalue, rewrites, declarations);
@@ -356,11 +376,12 @@ export function leaveSSA(fn: HIRFunction): void {
// If we never saw a declaration for this phi, it may have been pruned by DCE, so synthesize
// a new Let binding
CompilerError.invariant(
phi.id.name != null,
"Expected reassignment phis to have a name",
null
);
CompilerError.invariant(phi.id.name != null, {
reason: "Expected reassignment phis to have a name",
description: null,
loc: null,
suggestions: null,
});
const declaration = declarations.get(phi.id.name);
if (declaration === undefined) {
let initValue: Place;
@@ -342,11 +342,12 @@ class Unifier {
if (type.kind === "Phi") {
const operands = new Set(type.operands.map((i) => this.get(i).kind));
CompilerError.invariant(
operands.size > 0,
"there should be at least one operand",
null
);
CompilerError.invariant(operands.size > 0, {
reason: "there should be at least one operand",
description: null,
loc: null,
suggestions: null,
});
const kind = operands.values().next().value;
// there's only one unique type and it's not a type var
@@ -20,11 +20,12 @@ export default class DisjointSet<T> {
*/
union(items: Array<T>): void {
const first = items.shift();
CompilerError.invariant(
first != null,
"Expected set to be non-empty",
null
);
CompilerError.invariant(first != null, {
reason: "Expected set to be non-empty",
description: null,
loc: null,
suggestions: null,
});
// determine an arbitrary "root" for this set: if the first
// item already has a root then use that, otherwise the first item
// will be the new root.
@@ -138,6 +138,7 @@ function validateOperand(
reason:
"Cannot use a mutable function where an immutable value is expected",
severity: ErrorSeverity.InvalidReact,
suggestions: null,
});
}
}
@@ -31,6 +31,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
"Hooks may not be referenced as normal values, they must be called. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)",
loc: typeof place.loc !== "symbol" ? place.loc : null,
severity: ErrorSeverity.InvalidReact,
suggestions: null,
})
);
};
@@ -109,6 +109,7 @@ function validateNonRefValue(error: CompilerError, operand: Place): void {
reason:
"Ref values (the `current` property) may not be accessed during render",
severity: ErrorSeverity.InvalidReact,
suggestions: null,
})
);
}
@@ -124,6 +125,7 @@ function validateNonRefObject(error: CompilerError, operand: Place): void {
reason:
"Ref values may not be passed to functions because they could read the ref value (`current` property) during render",
severity: ErrorSeverity.InvalidReact,
suggestions: null,
})
);
}
@@ -98,6 +98,7 @@ export function validateUnconditionalHooks(
"Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)",
loc,
severity: ErrorSeverity.InvalidReact,
suggestions: null,
})
);
}
@@ -9,6 +9,7 @@ export { default as BabelPlugin } from "./Babel/BabelPlugin";
export {
CompilerError,
CompilerErrorDetail,
CompilerSuggestionOperation,
ErrorSeverity,
} from "./CompilerError";
export {
@@ -8,6 +8,7 @@
import { transformFromAstSync } from "@babel/core";
import type { SourceLocation as BabelSourceLocation } from "@babel/types";
import ReactForgetBabelPlugin, {
CompilerSuggestionOperation,
ErrorSeverity,
type CompilerError,
type CompilerErrorDetail,
@@ -20,6 +21,10 @@ type CompilerErrorDetailWithLoc = Omit<CompilerErrorDetail, "loc"> & {
loc: BabelSourceLocation;
};
function assertExhaustive(_: never, errorMsg: string): never {
throw new Error(errorMsg);
}
function isReactForgetCompilerError(err: Error): err is CompilerError {
return err.name === "ReactForgetCompilerError";
}
@@ -63,6 +68,7 @@ const rule: Rule.RuleModule = {
description: "Surfaces diagnostics from React Forget",
recommended: true,
},
fixable: "code",
},
create(context: Rule.RuleContext) {
// Compat with older versions of eslint
@@ -90,12 +96,67 @@ const rule: Rule.RuleModule = {
} catch (err) {
if (isReactForgetCompilerError(err) && Array.isArray(err.details)) {
for (const detail of err.details) {
if (isReportableDiagnostic(detail)) {
context.report({
message: detail.toString(),
loc: detail.loc,
});
if (!isReportableDiagnostic(detail)) {
continue;
}
let suggest: Array<Rule.SuggestionReportDescriptor> = [];
if (Array.isArray(detail.suggestions)) {
for (const suggestion of detail.suggestions) {
switch (suggestion.op) {
case CompilerSuggestionOperation.InsertBefore:
suggest.push({
desc: suggestion.description,
fix(fixer) {
return fixer.insertTextBeforeRange(
suggestion.range,
suggestion.text
);
},
});
break;
case CompilerSuggestionOperation.InsertAfter:
suggest.push({
desc: suggestion.description,
fix(fixer) {
return fixer.insertTextAfterRange(
suggestion.range,
suggestion.text
);
},
});
break;
case CompilerSuggestionOperation.Replace:
suggest.push({
desc: suggestion.description,
fix(fixer) {
return fixer.replaceTextRange(
suggestion.range,
suggestion.text
);
},
});
break;
case CompilerSuggestionOperation.Remove:
suggest.push({
desc: suggestion.description,
fix(fixer) {
return fixer.removeRange(suggestion.range);
},
});
break;
default:
assertExhaustive(
suggestion,
"Unhandled suggestion operation"
);
}
}
}
context.report({
message: detail.toString(),
loc: detail.loc,
suggest,
});
}
} else {
throw err;