From 448ed2b67b7bbfe2882aadf9eb92e252a0ff4b1f Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Tue, 27 Jun 2023 14:01:39 -0400 Subject: [PATCH] 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 --- .../src/CompilerError.ts | 101 ++++---- .../src/Entrypoint/Program.ts | 57 +++-- .../src/HIR/AssertConsistentIdentifiers.ts | 38 +-- .../src/HIR/AssertTerminalSuccessorsExist.ts | 14 +- .../src/HIR/BuildHIR.ts | 151 ++++++++--- .../src/HIR/Dominator.ts | 25 +- .../src/HIR/Environment.ts | 33 +-- .../src/HIR/FindContextIdentifiers.ts | 32 ++- .../babel-plugin-react-forget/src/HIR/HIR.ts | 51 ++-- .../src/HIR/HIRBuilder.ts | 75 ++++-- .../src/HIR/MergeConsecutiveBlocks.ts | 22 +- .../src/HIR/ObjectShape.ts | 11 +- .../src/HIR/PrintHIR.ts | 8 +- .../src/HIR/Types.ts | 11 +- .../src/Inference/AnalyseFunctions.ts | 11 +- .../src/Inference/InferMutableLifetimes.ts | 11 +- .../src/Inference/InferReferenceEffects.ts | 155 ++++++----- .../src/Inference/InlineUseMemo.ts | 21 +- .../src/ReactiveScopes/BuildReactiveBlocks.ts | 56 ++-- .../ReactiveScopes/BuildReactiveFunction.ts | 193 ++++++++------ .../ReactiveScopes/CodegenReactiveFunction.ts | 242 ++++++++++-------- .../DeriveMinimalDependencies.ts | 44 ++-- .../InferReactiveIdentifiers.ts | 11 +- .../ReactiveScopes/PrintReactiveFunction.ts | 11 +- .../ReactiveScopes/PromoteUsedTemporaries.ts | 12 +- .../PropagateScopeDependencies.ts | 13 +- .../ReactiveScopes/PruneNonEscapingScopes.ts | 40 +-- .../src/ReactiveScopes/RenameVariables.ts | 7 +- .../src/SSA/EliminateRedundantPhi.ts | 11 +- .../src/SSA/EnterSSA.ts | 57 +++-- .../src/SSA/LeaveSSA.ts | 87 ++++--- .../src/TypeInference/InferTypes.ts | 11 +- .../src/Utils/DisjointSet.ts | 11 +- .../src/Validation/ValidateFrozenLambdas.ts | 1 + .../src/Validation/ValidateHooksUsage.ts | 1 + .../Validation/ValidateNoRefAccesInRender.ts | 2 + .../Validation/ValidateUnconditionalHooks.ts | 1 + .../babel-plugin-react-forget/src/index.ts | 1 + .../src/rules/ReactForgetDiagnostics.ts | 71 ++++- 39 files changed, 1037 insertions(+), 673 deletions(-) diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/CompilerError.ts b/compiler/forget/packages/babel-plugin-react-forget/src/CompilerError.ts index 7f37a3c5f3..7c4e68e2be 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/CompilerError.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/CompilerError.ts @@ -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 | null; }; -type CompilerErrorDetailOptions = ExtractClassProperties; /** * 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 ): 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): 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 ): 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 ): 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 ): 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); diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts b/compiler/forget/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts index df1ce61f46..07bec4b07f 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts @@ -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); diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertConsistentIdentifiers.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertConsistentIdentifiers.ts index d783610b60..692e90f053 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertConsistentIdentifiers.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertConsistentIdentifiers.ts @@ -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, + }); } } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertTerminalSuccessorsExist.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertTerminalSuccessorsExist.ts index 0eb75f28c9..493ff54c03 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertTerminalSuccessorsExist.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertTerminalSuccessorsExist.ts @@ -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; }); } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts index d2c1593b40..a2eca27f43 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts @@ -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; 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; // 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; 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 }; } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Dominator.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Dominator.ts index 4193d197dd..9bca7fd9ba 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Dominator.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Dominator.ts @@ -88,7 +88,12 @@ export class Dominator { */ 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 { */ 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(graph: Graph): Map { 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 diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Environment.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Environment.ts index 9a09dbde6f..e77aedb2f6 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Environment.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Environment.ts @@ -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; diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/FindContextIdentifiers.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/FindContextIdentifiers.ts index 40817642fb..6496c02579 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/FindContextIdentifiers.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/FindContextIdentifiers.ts @@ -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, + }); } } } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIR.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIR.ts index fc11bd154b..0e8666ad58 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIR.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIR.ts @@ -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; } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts index f2e0c54f4c..8c9d63955c 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts @@ -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(); 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); } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts index 3a6f5e71ee..d11d273942 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts @@ -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, diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/ObjectShape.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/ObjectShape.ts index c2a87981ec..34262d7aed 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/ObjectShape.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/ObjectShape.ts @@ -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; } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts index f0775f7d33..a0882e8a05 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts @@ -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; diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Types.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Types.ts index 3b8a3c08be..334adc10f6 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Types.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/Types.ts @@ -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; } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/Inference/AnalyseFunctions.ts b/compiler/forget/packages/babel-plugin-react-forget/src/Inference/AnalyseFunctions.ts index 974f8053cf..d2635bd4af 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/Inference/AnalyseFunctions.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/Inference/AnalyseFunctions.ts @@ -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) { diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferMutableLifetimes.ts b/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferMutableLifetimes.ts index d833479423..f5632cef1b 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferMutableLifetimes.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferMutableLifetimes.ts @@ -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), diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts b/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts index ab6aa026c0..7c1c67507a 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts @@ -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); } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InlineUseMemo.ts b/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InlineUseMemo.ts index d146c82e63..8103be12f0 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InlineUseMemo.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/Inference/InlineUseMemo.ts @@ -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 diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveBlocks.ts b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveBlocks.ts index 6296fc60f7..c074bd3b4d 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveBlocks.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveBlocks.ts @@ -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) { diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts index ce85b2515f..3378064bb7 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts @@ -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 { 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 { 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); } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts index f5488e2f18..dcd8fc6827 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -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; diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/DeriveMinimalDependencies.ts b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/DeriveMinimalDependencies.ts index e3d572fccb..05eee7f507 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/DeriveMinimalDependencies.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/DeriveMinimalDependencies.ts @@ -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 ): 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>, currProperties: Map ): 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, + }) ) ); diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/InferReactiveIdentifiers.ts b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/InferReactiveIdentifiers.ts index 454f3fe026..49c7537d34 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/InferReactiveIdentifiers.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/InferReactiveIdentifiers.ts @@ -105,11 +105,12 @@ class Visitor extends ReactiveFunctionVisitor { 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( diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts index ad8925953d..e1c2ca5190 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts @@ -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("}"); diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PromoteUsedTemporaries.ts b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PromoteUsedTemporaries.ts index 11694883b3..427202b573 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PromoteUsedTemporaries.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PromoteUsedTemporaries.ts @@ -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 { diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts index a1cc48b822..df07b38564 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts @@ -459,12 +459,13 @@ class PropagationVisitor extends ReactiveFunctionVisitor { // 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); diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonEscapingScopes.ts b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonEscapingScopes.ts index be72ed0192..2200392871 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonEscapingScopes.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonEscapingScopes.ts @@ -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 { // 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 { // 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}'`); diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/RenameVariables.ts b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/RenameVariables.ts index c39856fdea..3878f8ea95 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/RenameVariables.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/RenameVariables.ts @@ -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, + }); } } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts b/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts index 54e6557814..eaa331a1b6 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts @@ -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); } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EnterSSA.ts b/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EnterSSA.ts index 04c8225a43..224d920cb0 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EnterSSA.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EnterSSA.ts @@ -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 = 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(() => { diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts b/compiler/forget/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts index b65a757fc0..86d74d5c2a 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts @@ -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; diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/TypeInference/InferTypes.ts b/compiler/forget/packages/babel-plugin-react-forget/src/TypeInference/InferTypes.ts index 3b53736d26..81dcdf77e0 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/TypeInference/InferTypes.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/TypeInference/InferTypes.ts @@ -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 diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/Utils/DisjointSet.ts b/compiler/forget/packages/babel-plugin-react-forget/src/Utils/DisjointSet.ts index 1da35d460b..36d7c411ab 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/Utils/DisjointSet.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/Utils/DisjointSet.ts @@ -20,11 +20,12 @@ export default class DisjointSet { */ union(items: Array): 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. diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateFrozenLambdas.ts b/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateFrozenLambdas.ts index 4658062942..fe81cfa762 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateFrozenLambdas.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateFrozenLambdas.ts @@ -138,6 +138,7 @@ function validateOperand( reason: "Cannot use a mutable function where an immutable value is expected", severity: ErrorSeverity.InvalidReact, + suggestions: null, }); } } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateHooksUsage.ts b/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateHooksUsage.ts index 5e4f38776c..884dd20572 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateHooksUsage.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateHooksUsage.ts @@ -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, }) ); }; diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateNoRefAccesInRender.ts b/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateNoRefAccesInRender.ts index cebcb09124..da1f299bda 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateNoRefAccesInRender.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateNoRefAccesInRender.ts @@ -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, }) ); } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateUnconditionalHooks.ts b/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateUnconditionalHooks.ts index e7c441d2c3..4b4fb19a57 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateUnconditionalHooks.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateUnconditionalHooks.ts @@ -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, }) ); } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/index.ts b/compiler/forget/packages/babel-plugin-react-forget/src/index.ts index 669f53022f..8561810127 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/index.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/index.ts @@ -9,6 +9,7 @@ export { default as BabelPlugin } from "./Babel/BabelPlugin"; export { CompilerError, CompilerErrorDetail, + CompilerSuggestionOperation, ErrorSeverity, } from "./CompilerError"; export { diff --git a/compiler/forget/packages/eslint-plugin-react-forget/src/rules/ReactForgetDiagnostics.ts b/compiler/forget/packages/eslint-plugin-react-forget/src/rules/ReactForgetDiagnostics.ts index a58ea8df97..5d17d3cd34 100644 --- a/compiler/forget/packages/eslint-plugin-react-forget/src/rules/ReactForgetDiagnostics.ts +++ b/compiler/forget/packages/eslint-plugin-react-forget/src/rules/ReactForgetDiagnostics.ts @@ -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 & { 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 = []; + 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;