@samitouri / QOS-React-2 / commits / 448ed2b67b

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

Lauren Tan committed Jun 27, 2023 at 14:01 UTC 448ed2b67b7bbfe2882aadf9eb92e252a0ff4b1f
39 files changed +1037 -673
compiler/forget/packages/babel-plugin-react-forget/src/CompilerError.ts
+54 -47
@@ -6,7 +6,6 @@
6 */
7
8 import type { SourceLocation } from "./HIR";
9 -import type { ExtractClassProperties } from "./Utils/types";
9 import { assertExhaustive } from "./Utils/utils";
10
11 export enum ErrorSeverity {
@@ -33,29 +32,61 @@ export enum ErrorSeverity {
32 Invariant = "Invariant",
33 }
34
36 -export type CompilerErrorOptions = {
35 +export enum CompilerSuggestionOperation {
36 + InsertBefore,
37 + InsertAfter,
38 + Remove,
39 + Replace,
40 +}
41 +export type CompilerSuggestion =
42 + | {
43 + op:
44 + | CompilerSuggestionOperation.InsertAfter
45 + | CompilerSuggestionOperation.InsertBefore
46 + | CompilerSuggestionOperation.Replace;
47 + range: [number, number];
48 + description: string;
49 + text: string;
50 + }
51 + | {
52 + op: CompilerSuggestionOperation.Remove;
53 + range: [number, number];
54 + description: string;
55 + };
56 +
57 +export type CompilerErrorDetailOptions = {
58 reason: string;
59 description?: string | null | undefined;
60 severity: ErrorSeverity;
61 loc: SourceLocation | null;
62 + suggestions: Array<CompilerSuggestion> | null;
63 };
42 -type CompilerErrorDetailOptions = ExtractClassProperties<CompilerErrorDetail>;
64
65 /**
66 * Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then
67 * aggregated into a single {@link CompilerError} later.
68 */
69 export class CompilerErrorDetail {
49 - reason: string;
50 - description: string | null;
51 - severity: ErrorSeverity;
52 - loc: SourceLocation | null;
70 + options: CompilerErrorDetailOptions;
71
72 constructor(options: CompilerErrorDetailOptions) {
55 - this.reason = options.reason;
56 - this.description = options.description;
57 - this.severity = options.severity;
58 - this.loc = options.loc;
73 + this.options = options;
74 + }
75 +
76 + get reason(): CompilerErrorDetailOptions["reason"] {
77 + return this.options.reason;
78 + }
79 + get description(): CompilerErrorDetailOptions["description"] {
80 + return this.options.description;
81 + }
82 + get severity(): CompilerErrorDetailOptions["severity"] {
83 + return this.options.severity;
84 + }
85 + get loc(): CompilerErrorDetailOptions["loc"] {
86 + return this.options.loc;
87 + }
88 + get suggestions(): CompilerErrorDetailOptions["suggestions"] {
89 + return this.options.suggestions;
90 }
91
92 printErrorMessage(): string {
@@ -79,17 +110,13 @@ export class CompilerError extends Error {
110
111 static invariant(
112 condition: unknown,
82 - reason: string,
83 - loc: SourceLocation | null,
84 - description: string | null = null
113 + options: Omit<CompilerErrorDetailOptions, "severity">
114 ): asserts condition {
115 if (!condition) {
116 const errors = new CompilerError();
117 errors.pushErrorDetail(
118 new CompilerErrorDetail({
90 - description,
91 - loc,
92 - reason,
119 + ...options,
120 severity: ErrorSeverity.Invariant,
121 })
122 );
@@ -97,34 +124,21 @@ export class CompilerError extends Error {
124 }
125 }
126
100 - static todo(
101 - reason: string,
102 - loc: SourceLocation | null,
103 - description: string | null = null
104 - ): never {
127 + static todo(options: Omit<CompilerErrorDetailOptions, "severity">): never {
128 const errors = new CompilerError();
129 errors.pushErrorDetail(
107 - new CompilerErrorDetail({
108 - description,
109 - loc,
110 - reason,
111 - severity: ErrorSeverity.Todo,
112 - })
130 + new CompilerErrorDetail({ ...options, severity: ErrorSeverity.Todo })
131 );
132 throw errors;
133 }
134
135 static invalidInput(
118 - reason: string,
119 - loc: SourceLocation | null,
120 - description: string | null = null
136 + options: Omit<CompilerErrorDetailOptions, "severity">
137 ): never {
138 const errors = new CompilerError();
139 errors.pushErrorDetail(
140 new CompilerErrorDetail({
125 - description,
126 - loc,
127 - reason,
141 + ...options,
142 severity: ErrorSeverity.InvalidInput,
143 })
144 );
@@ -132,16 +146,12 @@ export class CompilerError extends Error {
146 }
147
148 static invalidReact(
135 - reason: string,
136 - loc: SourceLocation | null,
137 - description: string | null = null
149 + options: Omit<CompilerErrorDetailOptions, "severity">
150 ): never {
151 const errors = new CompilerError();
152 errors.pushErrorDetail(
153 new CompilerErrorDetail({
142 - description,
143 - loc,
144 - reason,
154 + ...options,
155 severity: ErrorSeverity.InvalidReact,
156 })
157 );
@@ -149,16 +159,12 @@ export class CompilerError extends Error {
159 }
160
161 static invalidConfig(
152 - reason: string,
153 - loc: SourceLocation | null,
154 - description: string | null = null
162 + options: Omit<CompilerErrorDetailOptions, "severity">
163 ): never {
164 const errors = new CompilerError();
165 errors.pushErrorDetail(
166 new CompilerErrorDetail({
159 - description,
160 - loc,
161 - reason,
167 + ...options,
168 severity: ErrorSeverity.InvalidConfig,
169 })
170 );
@@ -180,11 +186,12 @@ export class CompilerError extends Error {
186 return this.details.map((detail) => detail.toString()).join("\n\n");
187 }
188
183 - push(options: CompilerErrorOptions): CompilerErrorDetail {
189 + push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
190 const detail = new CompilerErrorDetail({
191 reason: options.reason,
192 description: options.description ?? null,
193 severity: options.severity,
194 + suggestions: options.suggestions,
195 loc: typeof options.loc === "symbol" ? null : options.loc,
196 });
197 return this.pushErrorDetail(detail);
compiler/forget/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts
+34 -23
@@ -53,11 +53,12 @@ export function compileProgram(
53 return;
54 }
55
56 - CompilerError.invariant(
57 - fn.node.id != null,
58 - "FunctionDeclaration must have a name",
59 - fn.node.loc ?? GeneratedSource
60 - );
56 + CompilerError.invariant(fn.node.id != null, {
57 + reason: "FunctionDeclaration must have a name",
58 + description: null,
59 + loc: fn.node.loc ?? GeneratedSource,
60 + suggestions: null,
61 + });
62 const originalIdent = fn.node.id;
63
64 if (pass.opts.gating != null) {
@@ -65,11 +66,12 @@ export function compileProgram(
66 fn.node.id = addSuffix(fn.node.id, "_uncompiled");
67
68 // Rename and append compiled function
68 - CompilerError.invariant(
69 - compiled.id != null,
70 - "FunctionDeclaration must produce a name",
71 - fn.node.loc ?? GeneratedSource
72 - );
69 + CompilerError.invariant(compiled.id != null, {
70 + reason: "FunctionDeclaration must produce a name",
71 + description: null,
72 + loc: fn.node.loc ?? GeneratedSource,
73 + suggestions: null,
74 + });
75 compiled.id = addSuffix(compiled.id, "_forget");
76 const compiledFn = fn.insertAfter(compiled)[0];
77 compiledFn.skip();
@@ -212,6 +214,7 @@ export function compileProgram(
214 description: violation.value.trim(),
215 severity: ErrorSeverity.InvalidReact,
216 loc: violation.loc ?? null,
217 + suggestions: null, // TODO(@poteto) add autofix for eslint
218 })
219 );
220 }
@@ -357,6 +360,7 @@ function buildFunctionDeclaration(
360 severity: ErrorSeverity.Todo,
361 description: `Handle ${fn.parentPath.type}`,
362 loc: fn.node.loc ?? null,
363 + suggestions: null,
364 });
365 }
366 const variableDeclarator = fn.parentPath;
@@ -367,6 +371,7 @@ function buildFunctionDeclaration(
371 severity: ErrorSeverity.Todo,
372 description: `Handle ${variableDeclarator.parentPath.type}`,
373 loc: fn.node.loc ?? null,
374 + suggestions: null,
375 });
376 }
377 const variableDeclaration = variableDeclarator.parentPath;
@@ -378,6 +383,7 @@ function buildFunctionDeclaration(
383 severity: ErrorSeverity.Todo,
384 description: `Handle ${id.type}`,
385 loc: fn.node.loc ?? null,
386 + suggestions: null,
387 });
388 }
389
@@ -407,11 +413,12 @@ function buildBlockStatement(
413 return wrappedBody.node;
414 }
415
410 - CompilerError.invariant(
411 - body.isBlockStatement(),
412 - "Body must be a BlockStatement",
413 - body.node.loc ?? GeneratedSource
414 - );
416 + CompilerError.invariant(body.isBlockStatement(), {
417 + reason: "Body must be a BlockStatement",
418 + description: null,
419 + loc: body.node.loc ?? GeneratedSource,
420 + suggestions: null,
421 + });
422
423 return body.node;
424 }
@@ -426,16 +433,20 @@ function addImportsToProgram(
433 // Codegen currently does not rename import specifiers, so we do additional
434 // validation here
435 if (identifiers.has(importSpecifierName)) {
429 - CompilerError.invalidConfig(
430 - `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
431 - GeneratedSource
432 - );
436 + CompilerError.invalidConfig({
437 + reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
438 + description: null,
439 + loc: GeneratedSource,
440 + suggestions: null,
441 + });
442 }
443 if (path.scope.hasBinding(importSpecifierName)) {
435 - CompilerError.invalidConfig(
436 - `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
437 - GeneratedSource
438 - );
444 + CompilerError.invalidConfig({
445 + reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
446 + description: null,
447 + loc: GeneratedSource,
448 + suggestions: null,
449 + });
450 }
451 identifiers.add(importSpecifierName);
452
compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertConsistentIdentifiers.ts
+20 -18
@@ -35,18 +35,20 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
35 }
36 }
37 for (const instr of block.instructions) {
38 - CompilerError.invariant(
39 - instr.lvalue.identifier.name === null,
40 - `Expected all lvalues to be temporaries`,
41 - instr.lvalue.loc,
42 - `Found named lvalue '${instr.lvalue.identifier.name}'`
43 - );
44 - CompilerError.invariant(
45 - !assignments.has(instr.lvalue.identifier.id),
46 - `Expected lvalues to be assigned exactly once`,
47 - instr.lvalue.loc,
48 - `Found duplicate assignment of '${printPlace(instr.lvalue)}'`
49 - );
38 + CompilerError.invariant(instr.lvalue.identifier.name === null, {
39 + reason: `Expected all lvalues to be temporaries`,
40 + description: `Found named lvalue '${instr.lvalue.identifier.name}'`,
41 + loc: instr.lvalue.loc,
42 + suggestions: null,
43 + });
44 + CompilerError.invariant(!assignments.has(instr.lvalue.identifier.id), {
45 + reason: `Expected lvalues to be assigned exactly once`,
46 + description: `Found duplicate assignment of '${printPlace(
47 + instr.lvalue
48 + )}'`,
49 + loc: instr.lvalue.loc,
50 + suggestions: null,
51 + });
52 assignments.add(instr.lvalue.identifier.id);
53 for (const operand of eachInstructionLValue(instr)) {
54 validate(identifiers, operand.identifier, operand.loc);
@@ -72,11 +74,11 @@ function validate(
74 if (previous === undefined) {
75 identifiers.set(identifier.id, identifier);
76 } else {
75 - CompilerError.invariant(
76 - identifier === previous,
77 - `Duplicate identifier object`,
78 - loc ?? GeneratedSource,
79 - `Found duplicate identifier object for id ${identifier.id}`
80 - );
77 + CompilerError.invariant(identifier === previous, {
78 + reason: `Duplicate identifier object`,
79 + description: `Found duplicate identifier object for id ${identifier.id}`,
80 + loc: loc ?? GeneratedSource,
81 + suggestions: null,
82 + });
83 }
84 }
compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertTerminalSuccessorsExist.ts
+7 -7
@@ -13,14 +13,14 @@ import { mapTerminalSuccessors } from "./visitors";
13 export function assertTerminalSuccessorsExist(fn: HIRFunction): void {
14 for (const [, block] of fn.body.blocks) {
15 mapTerminalSuccessors(block.terminal, (successor) => {
16 - CompilerError.invariant(
17 - fn.body.blocks.has(successor),
18 - `Terminal successor references unknown block`,
19 - (block.terminal as any).loc ?? GeneratedSource,
20 - `Block bb${successor} does not exist for terminal '${printTerminal(
16 + CompilerError.invariant(fn.body.blocks.has(successor), {
17 + reason: `Terminal successor references unknown block`,
18 + description: `Block bb${successor} does not exist for terminal '${printTerminal(
19 block.terminal
22 - )}'`
23 - );
20 + )}'`,
21 + loc: (block.terminal as any).loc ?? GeneratedSource,
22 + suggestions: null,
23 + });
24 return successor;
25 });
26 }
compiler/forget/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+110 -41
@@ -88,6 +88,7 @@ export function lower(
88 reason: `(BuildHIR::lower) Could not find binding for param '${param.node.name}'`,
89 severity: ErrorSeverity.Invariant,
90 loc: param.node.loc ?? null,
91 + suggestions: null,
92 });
93 return;
94 }
@@ -122,6 +123,7 @@ export function lower(
123 reason: `(BuildHIR::lower) Handle ${param.node.type} params`,
124 severity: ErrorSeverity.Todo,
125 loc: param.node.loc ?? null,
126 + suggestions: null,
127 });
128 }
129 });
@@ -143,6 +145,7 @@ export function lower(
145 reason: `(BuildHIR::lower) Unexpected function body kind: ${body.type}}`,
146 severity: ErrorSeverity.InvalidInput,
147 loc: body.node.loc ?? null,
148 + suggestions: null,
149 });
150 }
151
@@ -320,6 +323,7 @@ function lowerStatement(
323 "(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement",
324 severity: ErrorSeverity.Todo,
325 loc: stmt.node.loc ?? null,
326 + suggestions: null,
327 });
328 return {
329 kind: "unsupported",
@@ -391,6 +395,7 @@ function lowerStatement(
395 reason: `(BuildHIR::lowerStatement) Handle empty test in ForStatement`,
396 severity: ErrorSeverity.Todo,
397 loc: stmt.node.loc ?? null,
398 + suggestions: null,
399 });
400 } else {
401 builder.terminateWithContinuation(
@@ -540,6 +545,7 @@ function lowerStatement(
545 "(BuildHIR::lowerStatement) Expected at most one `default` branch in SwitchStatement, this code should have failed to parse",
546 severity: ErrorSeverity.InvalidInput,
547 loc: case_.node.loc ?? null,
548 + suggestions: null,
549 });
550 break;
551 }
@@ -614,6 +620,7 @@ function lowerStatement(
620 reason: `(BuildHIR::lowerStatement) Handle ${nodeKind} kinds in VariableDeclaration`,
621 severity: ErrorSeverity.Todo,
622 loc: stmt.node.loc ?? null,
623 + suggestions: null,
624 });
625 return;
626 }
@@ -641,6 +648,7 @@ function lowerStatement(
648 reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
649 severity: ErrorSeverity.Invariant,
650 loc: id.node.loc ?? null,
651 + suggestions: null,
652 });
653 } else {
654 const place: Place = {
@@ -655,6 +663,7 @@ function lowerStatement(
663 reason: `(BuildHIR::lowerAssignment) Invalid declaration kind (const) for variable later reassigned.`,
664 severity: ErrorSeverity.InvalidInput,
665 loc: id.node.loc ?? null,
666 + suggestions: null,
667 });
668 }
669 lowerValueToTemporary(builder, {
@@ -681,6 +690,7 @@ function lowerStatement(
690 reason: `(BuildHIR::lowerStatement) Expected variable declaration to be an identifier if no initializer was provided.`,
691 severity: ErrorSeverity.InvalidInput,
692 loc: stmt.node.loc ?? null,
693 + suggestions: null,
694 });
695 }
696 }
@@ -768,11 +778,12 @@ function lowerStatement(
778 case "FunctionDeclaration": {
779 const stmt = stmtPath as NodePath<t.FunctionDeclaration>;
780 stmt.skip();
771 - CompilerError.invariant(
772 - stmt.get("id").type === "Identifier",
773 - "function declarations must have a name",
774 - stmt.node.loc ?? null
775 - );
781 + CompilerError.invariant(stmt.get("id").type === "Identifier", {
782 + reason: "function declarations must have a name",
783 + description: null,
784 + loc: stmt.node.loc ?? null,
785 + suggestions: null,
786 + });
787 const id = stmt.get("id") as NodePath<t.Identifier>;
788
789 // Desugar FunctionDeclaration to FunctionExpression.
@@ -795,11 +806,13 @@ function lowerStatement(
806 ),
807 ])
808 );
798 - CompilerError.invariant(
799 - desugared.length === 1,
800 - "only one declaration is created from desugaring function declaration",
801 - stmt.node.loc ?? null
802 - );
809 + CompilerError.invariant(desugared.length === 1, {
810 + reason:
811 + "only one declaration is created from desugaring function declaration",
812 + description: null,
813 + loc: stmt.node.loc ?? null,
814 + suggestions: null,
815 + });
816 lowerStatement(builder, desugared.at(0)!);
817 return;
818 }
@@ -844,11 +857,12 @@ function lowerStatement(
857 let test: Place;
858 if (left.isVariableDeclaration()) {
859 const declarations = left.get("declarations");
847 - CompilerError.invariant(
848 - declarations.length === 1,
849 - `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
850 - left.node.loc ?? null
851 - );
860 + CompilerError.invariant(declarations.length === 1, {
861 + reason: `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
862 + description: null,
863 + loc: left.node.loc ?? null,
864 + suggestions: null,
865 + });
866 const id = declarations[0].get("id");
867 const nextIterableOf = lowerValueToTemporary(builder, {
868 kind: "NextIterableOf",
@@ -868,6 +882,7 @@ function lowerStatement(
882 reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForOfStatement`,
883 severity: ErrorSeverity.Todo,
884 loc: left.node.loc ?? null,
885 + suggestions: null,
886 });
887 return;
888 }
@@ -935,6 +950,7 @@ function lowerStatement(
950 reason: `(BuildHIR::lowerStatement) Handle ${stmtPath.type} statements`,
951 severity: ErrorSeverity.Todo,
952 loc: stmtPath.node.loc ?? null,
953 + suggestions: null,
954 });
955 lowerValueToTemporary(builder, {
956 kind: "UnsupportedNode",
@@ -1007,6 +1023,7 @@ function lowerExpression(
1023 reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
1024 severity: ErrorSeverity.Todo,
1025 loc: propertyPath.node.loc ?? null,
1026 + suggestions: null,
1027 });
1028 continue;
1029 }
@@ -1016,6 +1033,7 @@ function lowerExpression(
1033 reason: `(BuildHIR::lowerExpression) Handle ${valuePath.type} values in ObjectExpression`,
1034 severity: ErrorSeverity.Todo,
1035 loc: valuePath.node.loc ?? null,
1036 + suggestions: null,
1037 });
1038 continue;
1039 }
@@ -1039,6 +1057,7 @@ function lowerExpression(
1057 reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.type} properties in ObjectExpression`,
1058 severity: ErrorSeverity.Todo,
1059 loc: propertyPath.node.loc ?? null,
1060 + suggestions: null,
1061 });
1062 continue;
1063 }
@@ -1058,6 +1077,7 @@ function lowerExpression(
1077 reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,
1078 severity: ErrorSeverity.Todo,
1079 loc: null,
1080 + suggestions: null,
1081 });
1082 continue;
1083 } else if (element.isExpression()) {
@@ -1073,6 +1093,7 @@ function lowerExpression(
1093 reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,
1094 severity: ErrorSeverity.Todo,
1095 loc: element.node.loc ?? null,
1096 + suggestions: null,
1097 });
1098 continue;
1099 }
@@ -1091,6 +1112,7 @@ function lowerExpression(
1112 reason: `(BuildHIR::lowerExpression) Expected Expression, got ${calleePath.type} in NewExpression (v8 intrinsics not supported): ${calleePath.type}`,
1113 severity: ErrorSeverity.Todo,
1114 loc: calleePath.node.loc ?? null,
1115 + suggestions: null,
1116 });
1117 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1118 }
@@ -1116,6 +1138,7 @@ function lowerExpression(
1138 reason: `(BuildHIR::lowerExpression) Expected Expression, got ${calleePath.type} in CallExpression (v8 intrinsics not supported)`,
1139 severity: ErrorSeverity.Todo,
1140 loc: calleePath.node.loc ?? null,
1141 + suggestions: null,
1142 });
1143 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1144 }
@@ -1149,6 +1172,7 @@ function lowerExpression(
1172 reason: `(BuildHIR::lowerExpression) Expected Expression, got ${leftPath.type} lval in BinaryExpression`,
1173 severity: ErrorSeverity.Todo,
1174 loc: leftPath.node.loc ?? null,
1175 + suggestions: null,
1176 });
1177 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1178 }
@@ -1180,6 +1204,7 @@ function lowerExpression(
1204 reason: `(BuildHIR::lowerExpression) Expected SequenceExpression to have at least one expression`,
1205 severity: ErrorSeverity.InvalidInput,
1206 loc: expr.node.loc ?? null,
1207 + suggestions: null,
1208 });
1209 } else {
1210 lowerValueToTemporary(builder, {
@@ -1391,6 +1416,7 @@ function lowerExpression(
1416 reason: `(BuildHIR::lowerExpression) Handle ${operator} operators in AssignmentExpression`,
1417 severity: ErrorSeverity.Todo,
1418 loc: expr.node.loc ?? null,
1419 + suggestions: null,
1420 });
1421 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1422 }
@@ -1463,6 +1489,7 @@ function lowerExpression(
1489 reason: `(BuildHIR::lowerExpression) Expected Identifier or MemberExpression, got ${expr.type} lval in AssignmentExpression`,
1490 severity: ErrorSeverity.Todo,
1491 loc: expr.node.loc ?? null,
1492 + suggestions: null,
1493 });
1494 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1495 }
@@ -1504,6 +1531,7 @@ function lowerExpression(
1531 reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`,
1532 severity: ErrorSeverity.Todo,
1533 loc: attribute.node.loc ?? null,
1534 + suggestions: null,
1535 });
1536 continue;
1537 }
@@ -1516,14 +1544,16 @@ function lowerExpression(
1544 reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name '${name}'`,
1545 severity: ErrorSeverity.Todo,
1546 loc: namePath.node.loc ?? null,
1547 + suggestions: null,
1548 });
1549 }
1550 } else {
1522 - CompilerError.invariant(
1523 - namePath.isJSXNamespacedName(),
1524 - "Refinement",
1525 - namePath.node.loc ?? null
1526 - );
1551 + CompilerError.invariant(namePath.isJSXNamespacedName(), {
1552 + reason: "Refinement",
1553 + description: null,
1554 + loc: namePath.node.loc ?? null,
1555 + suggestions: null,
1556 + });
1557 const namespace = namePath.node.namespace.name;
1558 const name = namePath.node.name.name;
1559 propName = `${namespace}:${name}`;
@@ -1538,6 +1568,7 @@ function lowerExpression(
1568 reason: `(BuildHIR::lowerExpression) Handle ${valueExpr.type} attribute values in JSXElement`,
1569 severity: ErrorSeverity.Todo,
1570 loc: valueExpr.node?.loc ?? null,
1571 + suggestions: null,
1572 });
1573 continue;
1574 }
@@ -1547,6 +1578,7 @@ function lowerExpression(
1578 reason: `(BuildHIR::lowerExpression) Handle ${expression.type} expressions in JSXExpressionContainer within JSXElement`,
1579 severity: ErrorSeverity.Todo,
1580 loc: valueExpr.node.loc ?? null,
1581 + suggestions: null,
1582 });
1583 continue;
1584 }
@@ -1589,14 +1621,17 @@ function lowerExpression(
1621 "(BuildHIR::lowerExpression) Handle tagged template with interpolations",
1622 severity: ErrorSeverity.Todo,
1623 loc: exprPath.node.loc ?? null,
1624 + suggestions: null,
1625 });
1626 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1627 }
1595 - CompilerError.invariant(
1596 - expr.get("quasi").get("quasis").length == 1,
1597 - "there should be only one quasi as we don't support interpolations yet",
1598 - expr.node.loc ?? null
1599 - );
1628 + CompilerError.invariant(expr.get("quasi").get("quasis").length == 1, {
1629 + reason:
1630 + "there should be only one quasi as we don't support interpolations yet",
1631 + description: null,
1632 + loc: expr.node.loc ?? null,
1633 + suggestions: null,
1634 + });
1635 const value = expr.get("quasi").get("quasis").at(0)!.node.value;
1636 if (value.raw !== value.cooked) {
1637 builder.errors.push({
@@ -1604,6 +1639,7 @@ function lowerExpression(
1639 "(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value",
1640 severity: ErrorSeverity.Todo,
1641 loc: exprPath.node.loc ?? null,
1642 + suggestions: null,
1643 });
1644 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1645 }
@@ -1625,6 +1661,7 @@ function lowerExpression(
1661 reason: `(BuildHIR::lowerExpression) Unexpected quasi and subexpression lengths in TemplateLiteral.`,
1662 severity: ErrorSeverity.InvalidInput,
1663 loc: exprPath.node.loc ?? null,
1664 + suggestions: null,
1665 });
1666 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1667 }
@@ -1634,6 +1671,7 @@ function lowerExpression(
1671 reason: `(BuildHIR::lowerAssignment) Handle TSType in TemplateLiteral.`,
1672 severity: ErrorSeverity.Todo,
1673 loc: exprPath.node.loc ?? null,
1674 + suggestions: null,
1675 });
1676 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1677 }
@@ -1675,6 +1713,7 @@ function lowerExpression(
1713 reason: `(BuildHIR::lowerExpression) delete on a non-member expression has no semantic meaning`,
1714 severity: ErrorSeverity.InvalidInput,
1715 loc: expr.node.loc ?? null,
1716 + suggestions: null,
1717 });
1718 return { kind: "UnsupportedNode", node: expr.node, loc: exprLoc };
1719 }
@@ -1712,6 +1751,7 @@ function lowerExpression(
1751 reason: `(BuildHIR::lowerExpression) Handle UpdateExpression with ${argument.type} argument`,
1752 severity: ErrorSeverity.Todo,
1753 loc: exprPath.node.loc ?? null,
1754 + suggestions: null,
1755 });
1756 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1757 }
@@ -1720,6 +1760,7 @@ function lowerExpression(
1760 reason: `(BuildHIR::lowerExpression) Handle prefix UpdateExpression`,
1761 severity: ErrorSeverity.Todo,
1762 loc: exprPath.node.loc ?? null,
1763 + suggestions: null,
1764 });
1765 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1766 }
@@ -1774,6 +1815,7 @@ function lowerExpression(
1815 reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`,
1816 severity: ErrorSeverity.Todo,
1817 loc: exprPath.node.loc ?? null,
1818 + suggestions: null,
1819 });
1820 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1821 }
@@ -1843,7 +1885,12 @@ function lowerOptionalMemberExpression(
1885 loc,
1886 };
1887 });
1846 - CompilerError.invariant(object !== null, "Satisfy type checker", null);
1888 + CompilerError.invariant(object !== null, {
1889 + reason: "Satisfy type checker",
1890 + description: null,
1891 + loc: null,
1892 + suggestions: null,
1893 + });
1894
1895 // block to evaluate if the callee is non-null/undefined. arguments are lowered in this block to preserve
1896 // the semantic of conditional evaluation depending on the callee
@@ -2047,6 +2094,7 @@ function lowerReorderableExpression(
2094 reason: `(BuildHIR::node.lowerReorderableExpression) Expression type '${expr.type}' cannot be safely reordered`,
2095 severity: ErrorSeverity.Todo,
2096 loc: expr.node.loc ?? null,
2097 + suggestions: null,
2098 });
2099 }
2100 return lowerExpressionToTemporary(builder, expr);
@@ -2138,6 +2186,7 @@ function lowerArguments(
2186 reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in CallExpression`,
2187 severity: ErrorSeverity.Todo,
2188 loc: argPath.node.loc ?? null,
2189 + suggestions: null,
2190 });
2191 }
2192 }
@@ -2167,6 +2216,7 @@ function lowerMemberExpression(
2216 reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`,
2217 severity: ErrorSeverity.Todo,
2218 loc: propertyNode.node.loc ?? null,
2219 + suggestions: null,
2220 });
2221 return {
2222 object,
@@ -2187,6 +2237,7 @@ function lowerMemberExpression(
2237 reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${propertyNode.type} property`,
2238 severity: ErrorSeverity.Todo,
2239 loc: propertyNode.node.loc ?? null,
2240 + suggestions: null,
2241 });
2242 return {
2243 object,
@@ -2243,6 +2294,7 @@ function lowerJsxElementName(
2294 reason: `(BuildHIR::lowerJsxElementName) Expected JSXNamespacedName to have no colons in the namespace or name, got '${namespace}' : '${name}'`,
2295 severity: ErrorSeverity.InvalidInput,
2296 loc: exprPath.node.loc ?? null,
2297 + suggestions: null,
2298 });
2299 }
2300 const place = lowerValueToTemporary(builder, {
@@ -2256,6 +2308,7 @@ function lowerJsxElementName(
2308 reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`,
2309 severity: ErrorSeverity.Todo,
2310 loc: exprPath.node.loc ?? null,
2311 + suggestions: null,
2312 });
2313 return lowerValueToTemporary(builder, {
2314 kind: "UnsupportedNode",
@@ -2275,11 +2328,12 @@ function lowerJsxMemberExpression(
2328 if (object.isJSXMemberExpression()) {
2329 objectPlace = lowerJsxMemberExpression(builder, object);
2330 } else {
2278 - CompilerError.invariant(
2279 - object.isJSXIdentifier(),
2280 - `TypeScript refinement fail: expected 'JsxIdentifier', got '${object.node.type}'`,
2281 - object.node.loc ?? null
2282 - );
2331 + CompilerError.invariant(object.isJSXIdentifier(), {
2332 + reason: `TypeScript refinement fail: expected 'JsxIdentifier', got '${object.node.type}'`,
2333 + description: null,
2334 + loc: object.node.loc ?? null,
2335 + suggestions: null,
2336 + });
2337 objectPlace = lowerIdentifier(builder, object);
2338 }
2339 const property = exprPath.get("property").node.name;
@@ -2310,11 +2364,12 @@ function lowerJsxElement(
2364 if (expression.isJSXEmptyExpression()) {
2365 return null;
2366 } else {
2313 - CompilerError.invariant(
2314 - expression.isExpression(),
2315 - `(BuildHIR::lowerJsxElement) Expected Expression but found ${expression.type}!`,
2316 - expression.node.loc ?? null
2317 - );
2367 + CompilerError.invariant(expression.isExpression(), {
2368 + reason: `(BuildHIR::lowerJsxElement) Expected Expression but found ${expression.type}!`,
2369 + description: null,
2370 + loc: expression.node.loc ?? null,
2371 + suggestions: null,
2372 + });
2373 return lowerExpressionToTemporary(builder, expression);
2374 }
2375 } else if (exprPath.isJSXText()) {
@@ -2329,6 +2384,7 @@ function lowerJsxElement(
2384 reason: `(BuildHIR::lowerJsxElement) Unhandled JsxElement, got: ${exprPath.type}`,
2385 severity: ErrorSeverity.Todo,
2386 loc: exprPath.node.loc ?? null,
2387 + suggestions: null,
2388 });
2389 const place = lowerValueToTemporary(builder, {
2390 kind: "UnsupportedNode",
@@ -2481,6 +2537,7 @@ function lowerIdentifierForAssignment(
2537 reason: `(BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.`,
2538 severity: ErrorSeverity.InvalidReact,
2539 loc: path.node.loc ?? null,
2540 + suggestions: null,
2541 });
2542 } else {
2543 // Else its an internal error bc we couldn't find the binding
@@ -2488,6 +2545,7 @@ function lowerIdentifierForAssignment(
2545 reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
2546 severity: ErrorSeverity.Invariant,
2547 loc: path.node.loc ?? null,
2548 + suggestions: null,
2549 });
2550 }
2551 return null;
@@ -2530,6 +2588,7 @@ function lowerAssignment(
2588 reason: `(BuildHIR::lowerAssignment) Invalid declaration kind (const) for variable later reassigned.`,
2589 severity: ErrorSeverity.InvalidInput,
2590 loc: lvalue.node.loc ?? null,
2591 + suggestions: null,
2592 });
2593 }
2594 lowerValueToTemporary(builder, {
@@ -2560,11 +2619,12 @@ function lowerAssignment(
2619 }
2620 case "MemberExpression": {
2621 // This can only occur because of a coding error, parsers enforce this condition
2563 - CompilerError.invariant(
2564 - kind === InstructionKind.Reassign,
2565 - "MemberExpression may only appear in an assignment expression",
2566 - lvaluePath.node.loc ?? null
2567 - );
2622 + CompilerError.invariant(kind === InstructionKind.Reassign, {
2623 + reason: "MemberExpression may only appear in an assignment expression",
2624 + description: null,
2625 + loc: lvaluePath.node.loc ?? null,
2626 + suggestions: null,
2627 + });
2628 const lvalue = lvaluePath as NodePath<t.MemberExpression>;
2629 const property = lvalue.get("property");
2630 const object = lowerExpressionToTemporary(builder, lvalue.get("object"));
@@ -2574,6 +2634,7 @@ function lowerAssignment(
2634 reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`,
2635 severity: ErrorSeverity.Todo,
2636 loc: property.node.loc ?? null,
2637 + suggestions: null,
2638 });
2639 return { kind: "UnsupportedNode", node: lvalueNode, loc };
2640 }
@@ -2592,6 +2653,7 @@ function lowerAssignment(
2653 "(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property",
2654 severity: ErrorSeverity.Todo,
2655 loc: property.node.loc ?? null,
2656 + suggestions: null,
2657 });
2658 return { kind: "UnsupportedNode", node: lvalueNode, loc };
2659 }
@@ -2624,6 +2686,7 @@ function lowerAssignment(
2686 reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ArrayPattern`,
2687 severity: ErrorSeverity.Todo,
2688 loc: element.node.loc ?? null,
2689 + suggestions: null,
2690 });
2691 continue;
2692 }
@@ -2691,6 +2754,7 @@ function lowerAssignment(
2754 reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ArrayPattern`,
2755 severity: ErrorSeverity.Todo,
2756 loc: argument.node.loc ?? null,
2757 + suggestions: null,
2758 });
2759 continue;
2760 }
@@ -2714,6 +2778,7 @@ function lowerAssignment(
2778 reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
2779 severity: ErrorSeverity.Todo,
2780 loc: property.node.loc ?? null,
2781 + suggestions: null,
2782 });
2783 continue;
2784 }
@@ -2722,6 +2787,7 @@ function lowerAssignment(
2787 reason: `(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern`,
2788 severity: ErrorSeverity.Todo,
2789 loc: property.node.loc ?? null,
2790 + suggestions: null,
2791 });
2792 continue;
2793 }
@@ -2731,6 +2797,7 @@ function lowerAssignment(
2797 reason: `(BuildHIR::lowerAssignment) Handle ${key.type} keys in ObjectPattern`,
2798 severity: ErrorSeverity.Todo,
2799 loc: key.node.loc ?? null,
2800 + suggestions: null,
2801 });
2802 continue;
2803 }
@@ -2740,6 +2807,7 @@ function lowerAssignment(
2807 reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
2808 severity: ErrorSeverity.Todo,
2809 loc: element.node.loc ?? null,
2810 + suggestions: null,
2811 });
2812 continue;
2813 }
@@ -2875,6 +2943,7 @@ function lowerAssignment(
2943 reason: `(BuildHIR::lowerAssignment) Handle ${lvaluePath.type} assignments`,
2944 severity: ErrorSeverity.Todo,
2945 loc: lvaluePath.node.loc ?? null,
2946 + suggestions: null,
2947 });
2948 return { kind: "UnsupportedNode", node: lvalueNode, loc };
2949 }
compiler/forget/packages/babel-plugin-react-forget/src/HIR/Dominator.ts
+18 -7
@@ -88,7 +88,12 @@ export class Dominator<T> {
88 */
89 get(id: T): T | null {
90 const dominator = this.#nodes.get(id);
91 - CompilerError.invariant(dominator !== undefined, "Unknown node", null);
91 + CompilerError.invariant(dominator !== undefined, {
92 + reason: "Unknown node",
93 + description: null,
94 + loc: null,
95 + suggestions: null,
96 + });
97 return dominator === id ? null : dominator;
98 }
99
@@ -119,7 +124,12 @@ export class PostDominator<T> {
124 */
125 get(id: T): T | null {
126 const dominator = this.#nodes.get(id);
122 - CompilerError.invariant(dominator !== undefined, "Unknown node", null);
127 + CompilerError.invariant(dominator !== undefined, {
128 + reason: "Unknown node",
129 + description: null,
130 + loc: null,
131 + suggestions: null,
132 + });
133 return dominator === id ? null : dominator;
134 }
135
@@ -159,11 +169,12 @@ function computeImmediateDominators<T>(graph: Graph<T>): Map<T, T> {
169 break;
170 }
171 }
162 - CompilerError.invariant(
163 - newIdom !== null,
164 - `At least one predecessor must have been visited for block ${id}`,
165 - null
166 - );
172 + CompilerError.invariant(newIdom !== null, {
173 + reason: `At least one predecessor must have been visited for block ${id}`,
174 + description: null,
175 + loc: null,
176 + suggestions: null,
177 + });
178
179 for (const pred of node.preds) {
180 // For all other predecessors
compiler/forget/packages/babel-plugin-react-forget/src/HIR/Environment.ts
+18 -15
@@ -195,11 +195,12 @@ export class Environment {
195 if (config?.customHooks) {
196 this.#globals = new Map(DEFAULT_GLOBALS);
197 for (const [hookName, hook] of config.customHooks) {
198 - CompilerError.invariant(
199 - !this.#globals.has(hookName),
200 - `[Globals] Found existing definition in global registry for custom hook ${hookName}`,
201 - null
202 - );
198 + CompilerError.invariant(!this.#globals.has(hookName), {
199 + reason: `[Globals] Found existing definition in global registry for custom hook ${hookName}`,
200 + description: null,
201 + loc: null,
202 + suggestions: null,
203 + });
204 this.#globals.set(
205 hookName,
206 addHook(this.#shapes, [], {
@@ -271,11 +272,12 @@ export class Environment {
272 // If an object or function has a shapeId, it must have been assigned
273 // by Forget (and be present in a builtin or user-defined registry)
274 const shape = this.#shapes.get(shapeId);
274 - CompilerError.invariant(
275 - shape !== undefined,
276 - `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
277 - null
278 - );
275 + CompilerError.invariant(shape !== undefined, {
276 + reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
277 + description: null,
278 + loc: null,
279 + suggestions: null,
280 + });
281 return shape.properties.get(property) ?? null;
282 } else {
283 return null;
@@ -286,11 +288,12 @@ export class Environment {
288 const { shapeId } = type;
289 if (shapeId !== null) {
290 const shape = this.#shapes.get(shapeId);
289 - CompilerError.invariant(
290 - shape !== undefined,
291 - `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
292 - null
293 - );
291 + CompilerError.invariant(shape !== undefined, {
292 + reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
293 + description: null,
294 + loc: null,
295 + suggestions: null,
296 + });
297 return shape.functionType;
298 }
299 return null;
compiler/forget/packages/babel-plugin-react-forget/src/HIR/FindContextIdentifiers.ts
+18 -14
@@ -137,18 +137,20 @@ function handleAssignment(
137 for (const property of path.get("properties")) {
138 if (property.isObjectProperty()) {
139 const valuePath = property.get("value");
140 - CompilerError.invariant(
141 - valuePath.isLVal(),
142 - `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
143 - valuePath.node.loc ?? GeneratedSource
144 - );
140 + CompilerError.invariant(valuePath.isLVal(), {
141 + reason: `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
142 + description: null,
143 + loc: valuePath.node.loc ?? GeneratedSource,
144 + suggestions: null,
145 + });
146 handleAssignment(reassigned, valuePath);
147 } else {
147 - CompilerError.invariant(
148 - property.isRestElement(),
149 - `[FindContextIdentifiers] Invalid assumptions for babel types.`,
150 - property.node.loc ?? GeneratedSource
151 - );
148 + CompilerError.invariant(property.isRestElement(), {
149 + reason: `[FindContextIdentifiers] Invalid assumptions for babel types.`,
150 + description: null,
151 + loc: property.node.loc ?? GeneratedSource,
152 + suggestions: null,
153 + });
154 handleAssignment(reassigned, property);
155 }
156 }
@@ -170,10 +172,12 @@ function handleAssignment(
172 break;
173 }
174 default: {
173 - CompilerError.todo(
174 - `[FindContextIdentifiers] Cannot handle Object destructuring assignment target ${lvalNode.type}`,
175 - lvalNode.loc ?? GeneratedSource
176 - );
175 + CompilerError.todo({
176 + reason: `[FindContextIdentifiers] Cannot handle Object destructuring assignment target ${lvalNode.type}`,
177 + description: null,
178 + loc: lvalNode.loc ?? GeneratedSource,
179 + suggestions: null,
180 + });
181 }
182 }
183 }
compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+30 -21
@@ -883,7 +883,12 @@ export function isMutableEffect(
883 }
884
885 case Effect.Unknown: {
886 - CompilerError.invariant(false, "Unexpected unknown effect", location);
886 + CompilerError.invariant(false, {
887 + reason: "Unexpected unknown effect",
888 + description: null,
889 + loc: location,
890 + suggestions: null,
891 + });
892 }
893 case Effect.Read:
894 case Effect.Freeze: {
@@ -921,11 +926,12 @@ const opaqueBlockId = Symbol();
926 export type BlockId = number & { [opaqueBlockId]: "BlockId" };
927
928 export function makeBlockId(id: number): BlockId {
924 - CompilerError.invariant(
925 - id >= 0 && Number.isInteger(id),
926 - "Expected block id to be a non-negative integer",
927 - null
928 - );
929 + CompilerError.invariant(id >= 0 && Number.isInteger(id), {
930 + reason: "Expected block id to be a non-negative integer",
931 + description: null,
932 + loc: null,
933 + suggestions: null,
934 + });
935 return id as BlockId;
936 }
937
@@ -937,11 +943,12 @@ const opaqueScopeId = Symbol();
943 export type ScopeId = number & { [opaqueScopeId]: "ScopeId" };
944
945 export function makeScopeId(id: number): ScopeId {
940 - CompilerError.invariant(
941 - id >= 0 && Number.isInteger(id),
942 - "Expected block id to be a non-negative integer",
943 - null
944 - );
946 + CompilerError.invariant(id >= 0 && Number.isInteger(id), {
947 + reason: "Expected block id to be a non-negative integer",
948 + description: null,
949 + loc: null,
950 + suggestions: null,
951 + });
952 return id as ScopeId;
953 }
954
@@ -953,11 +960,12 @@ const opaqueIdentifierId = Symbol();
960 export type IdentifierId = number & { [opaqueIdentifierId]: "IdentifierId" };
961
962 export function makeIdentifierId(id: number): IdentifierId {
956 - CompilerError.invariant(
957 - id >= 0 && Number.isInteger(id),
958 - "Expected identifier id to be a non-negative integer",
959 - null
960 - );
963 + CompilerError.invariant(id >= 0 && Number.isInteger(id), {
964 + reason: "Expected identifier id to be a non-negative integer",
965 + description: null,
966 + loc: null,
967 + suggestions: null,
968 + });
969 return id as IdentifierId;
970 }
971
@@ -969,11 +977,12 @@ const opaqueInstructionId = Symbol();
977 export type InstructionId = number & { [opaqueInstructionId]: "IdentifierId" };
978
979 export function makeInstructionId(id: number): InstructionId {
972 - CompilerError.invariant(
973 - id >= 0 && Number.isInteger(id),
974 - "Expected instruction id to be a non-negative integer",
975 - null
976 - );
980 + CompilerError.invariant(id >= 0 && Number.isInteger(id), {
981 + reason: "Expected instruction id to be a non-negative integer",
982 + description: null,
983 + loc: null,
984 + suggestions: null,
985 + });
986 return id as InstructionId;
987 }
988
compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+48 -27
@@ -392,8 +392,12 @@ export default class HIRBuilder {
392 last.kind === "label" &&
393 last.label === label &&
394 last.breakBlock === breakBlock,
395 - "Mismatched label",
396 - null
395 + {
396 + reason: "Mismatched label",
397 + description: null,
398 + loc: null,
399 + suggestions: null,
400 + }
401 );
402 return value;
403 }
@@ -411,8 +415,12 @@ export default class HIRBuilder {
415 last.kind === "switch" &&
416 last.label === label &&
417 last.breakBlock === breakBlock,
414 - "Mismatched label",
415 - null
418 + {
419 + reason: "Mismatched label",
420 + description: null,
421 + loc: null,
422 + suggestions: null,
423 + }
424 );
425 return value;
426 }
@@ -447,8 +455,12 @@ export default class HIRBuilder {
455 last.label === label &&
456 last.continueBlock === continueBlock &&
457 last.breakBlock === breakBlock,
450 - "Mismatched loops",
451 - null
458 + {
459 + reason: "Mismatched loops",
460 + description: null,
461 + loc: null,
462 + suggestions: null,
463 + }
464 );
465 return value;
466 }
@@ -464,11 +476,12 @@ export default class HIRBuilder {
476 return scope.breakBlock;
477 }
478 }
467 - CompilerError.invariant(
468 - false,
469 - "Expected a loop or switch to be in scope",
470 - null
471 - );
479 + CompilerError.invariant(false, {
480 + reason: "Expected a loop or switch to be in scope",
481 + description: null,
482 + loc: null,
483 + suggestions: null,
484 + });
485 }
486
487 /**
@@ -484,14 +497,20 @@ export default class HIRBuilder {
497 return scope.continueBlock;
498 }
499 } else if (label !== null && scope.label === label) {
487 - CompilerError.invariant(
488 - false,
489 - "Continue may only refer to a labeled loop",
490 - null
491 - );
500 + CompilerError.invariant(false, {
501 + reason: "Continue may only refer to a labeled loop",
502 + description: null,
503 + loc: null,
504 + suggestions: null,
505 + });
506 }
507 }
494 - CompilerError.invariant(false, "Expected a loop to be in scope", null);
508 + CompilerError.invariant(false, {
509 + reason: "Expected a loop to be in scope",
510 + description: null,
511 + loc: null,
512 + suggestions: null,
513 + });
514 }
515 }
516
@@ -511,11 +530,12 @@ function _shrink(func: HIR): void {
530 return target;
531 }
532 const block = func.blocks.get(blockId);
514 - CompilerError.invariant(
515 - block != null,
516 - `expected block ${blockId} to exist`,
517 - null
518 - );
533 + CompilerError.invariant(block != null, {
534 + reason: `expected block ${blockId} to exist`,
535 + description: null,
536 + loc: null,
537 + suggestions: null,
538 + });
539 target = getTargetIfIndirection(block);
540 if (target !== null) {
541 // the target might also be a simple goto, recurse
@@ -731,11 +751,12 @@ export function markInstructionIds(func: HIR): void {
751 const visited = new Set<Instruction>();
752 for (const [_, block] of func.blocks) {
753 for (const instr of block.instructions) {
734 - CompilerError.invariant(
735 - !visited.has(instr),
736 - `${printInstruction(instr)} already visited!`,
737 - instr.loc
738 - );
754 + CompilerError.invariant(!visited.has(instr), {
755 + reason: `${printInstruction(instr)} already visited!`,
756 + description: null,
757 + loc: instr.loc,
758 + suggestions: null,
759 + });
760 visited.add(instr);
761 instr.id = makeInstructionId(++id);
762 }
compiler/forget/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts
+12 -10
@@ -47,11 +47,12 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
47 const originalPredecessorId = Array.from(block.preds)[0]!;
48 const predecessorId = merged.get(originalPredecessorId);
49 const predecessor = fn.body.blocks.get(predecessorId);
50 - CompilerError.invariant(
51 - predecessor !== undefined,
52 - `Expected predecessor ${predecessorId} to exist`,
53 - null
54 - );
50 + CompilerError.invariant(predecessor !== undefined, {
51 + reason: `Expected predecessor ${predecessorId} to exist`,
52 + description: null,
53 + loc: null,
54 + suggestions: null,
55 + });
56 if (predecessor.terminal.kind !== "goto" || predecessor.kind !== "block") {
57 // The predecessor is not guaranteed to transfer control to this block,
58 // they aren't consecutive.
@@ -60,11 +61,12 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
61
62 // Replace phis in the merged block with canonical assignments to the single operand value
63 for (const phi of block.phis) {
63 - CompilerError.invariant(
64 - phi.operands.size === 1,
65 - `Found a block with a single predecessor but where a phi has multiple (${phi.operands.size}) operands`,
66 - null
67 - );
64 + CompilerError.invariant(phi.operands.size === 1, {
65 + reason: `Found a block with a single predecessor but where a phi has multiple (${phi.operands.size}) operands`,
66 + description: null,
67 + loc: null,
68 + suggestions: null,
69 + });
70 const operand = Array.from(phi.operands.values())[0]!;
71 const instr: Instruction = {
72 id: predecessor.terminal.id,
compiler/forget/packages/babel-plugin-react-forget/src/HIR/ObjectShape.ts
+6 -5
@@ -103,11 +103,12 @@ function addShape(
103 functionType,
104 };
105
106 - CompilerError.invariant(
107 - !registry.has(id),
108 - `[ObjectShape] Could not add shape to registry: name ${id} already exists.`,
109 - null
110 - );
106 + CompilerError.invariant(!registry.has(id), {
107 + reason: `[ObjectShape] Could not add shape to registry: name ${id} already exists.`,
108 + description: null,
109 + loc: null,
110 + suggestions: null,
111 + });
112 registry.set(id, shape);
113 return shape;
114 }
compiler/forget/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts
+6 -2
@@ -469,8 +469,12 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
469 value = "`";
470 CompilerError.invariant(
471 instrValue.subexprs.length === instrValue.quasis.length - 1,
472 - "Bad assumption about quasi length.",
473 - instrValue.loc
472 + {
473 + reason: "Bad assumption about quasi length.",
474 + description: null,
475 + loc: instrValue.loc,
476 + suggestions: null,
477 + }
478 );
479 for (let i = 0; i < instrValue.subexprs.length; i++) {
480 value += instrValue.quasis[i].raw;
compiler/forget/packages/babel-plugin-react-forget/src/HIR/Types.ts
+6 -5
@@ -62,11 +62,12 @@ const opaqueTypeId = Symbol();
62 export type TypeId = number & { [opaqueTypeId]: "IdentifierId" };
63
64 export function makeTypeId(id: number): TypeId {
65 - CompilerError.invariant(
66 - id >= 0 && Number.isInteger(id),
67 - "Expected instruction id to be a non-negative integer",
68 - null
69 - );
65 + CompilerError.invariant(id >= 0 && Number.isInteger(id), {
66 + reason: "Expected instruction id to be a non-negative integer",
67 + description: null,
68 + loc: null,
69 + suggestions: null,
70 + });
71 return id as TypeId;
72 }
73
compiler/forget/packages/babel-plugin-react-forget/src/Inference/AnalyseFunctions.ts
+6 -5
@@ -169,11 +169,12 @@ function infer(
169 // In practice this never really matters because the Component function has no
170 // context refs, so it will never have duplicate deps.
171 for (const place of context) {
172 - CompilerError.invariant(
173 - place.identifier.name !== null,
174 - "context refs should always have a name",
175 - place.loc
176 - );
172 + CompilerError.invariant(place.identifier.name !== null, {
173 + reason: "context refs should always have a name",
174 + description: null,
175 + loc: place.loc,
176 + suggestions: null,
177 + });
178
179 const effect = mutations.get(place.identifier.name);
180 if (effect !== undefined) {
compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferMutableLifetimes.ts
+6 -5
@@ -108,11 +108,12 @@ export function inferMutableLifetimes(
108 start = Math.min(start, operand.mutableRange.start);
109 end = Math.max(end, operand.mutableRange.end);
110 }
111 - CompilerError.invariant(
112 - start !== Number.MAX_SAFE_INTEGER,
113 - "Expected phi to have a start range value",
114 - null
115 - );
111 + CompilerError.invariant(start !== Number.MAX_SAFE_INTEGER, {
112 + reason: "Expected phi to have a start range value",
113 + description: null,
114 + loc: null,
115 + suggestions: null,
116 + });
117 phi.id.mutableRange = {
118 start: makeInstructionId(start),
119 end: makeInstructionId(end),
compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts
+88 -67
@@ -191,11 +191,13 @@ class InferenceState {
191 * (Re)initializes a @param value with its default @param kind.
192 */
193 initialize(value: InstructionValue, kind: ValueKind): void {
194 - CompilerError.invariant(
195 - value.kind !== "LoadLocal",
196 - "Expected all top-level identifiers to be defined as variables, not values",
197 - value.loc
198 - );
194 + CompilerError.invariant(value.kind !== "LoadLocal", {
195 + reason:
196 + "Expected all top-level identifiers to be defined as variables, not values",
197 + description: null,
198 + loc: value.loc,
199 + suggestions: null,
200 + });
201 this.#values.set(value, kind);
202 }
203
@@ -204,24 +206,25 @@ class InferenceState {
206 */
207 kind(place: Place): ValueKind {
208 const values = this.#variables.get(place.identifier.id);
207 - CompilerError.invariant(
208 - values != null,
209 - `Expected value kind to be initialized at '${printSourceLocation(
209 + CompilerError.invariant(values != null, {
210 + reason: `Expected value kind to be initialized at '${printSourceLocation(
211 place.loc
212 )}'`,
212 - place.loc
213 - );
213 + description: null,
214 + loc: place.loc,
215 + suggestions: null,
216 + });
217 let mergedKind: ValueKind | null = null;
218 for (const value of values) {
219 const kind = this.#values.get(value)!;
220 mergedKind = mergedKind !== null ? mergeValues(mergedKind, kind) : kind;
221 }
219 - CompilerError.invariant(
220 - mergedKind !== null,
221 - `InferReferenceEffects::kind: Expected at least one value`,
222 - place.loc,
223 - `No value found at '${printPlace(place)}'`
224 - );
222 + CompilerError.invariant(mergedKind !== null, {
223 + reason: `InferReferenceEffects::kind: Expected at least one value`,
224 + description: `No value found at '${printPlace(place)}'`,
225 + loc: place.loc,
226 + suggestions: null,
227 + });
228 return mergedKind;
229 }
230
@@ -230,11 +233,12 @@ class InferenceState {
233 */
234 alias(place: Place, value: Place): void {
235 const values = this.#variables.get(value.identifier.id);
233 - CompilerError.invariant(
234 - values != null,
235 - `Expected value for identifier \`${value.identifier.id}\` to be initialized.`,
236 - value.loc
237 - );
236 + CompilerError.invariant(values != null, {
237 + reason: `Expected value for identifier \`${value.identifier.id}\` to be initialized.`,
238 + description: null,
239 + loc: value.loc,
240 + suggestions: null,
241 + });
242 this.#variables.set(place.identifier.id, new Set(values));
243 }
244
@@ -242,11 +246,14 @@ class InferenceState {
246 * Defines (initializing or updating) a variable with a specific kind of value.
247 */
248 define(place: Place, value: InstructionValue): void {
245 - CompilerError.invariant(
246 - this.#values.has(value),
247 - `Expected value to be initialized at '${printSourceLocation(value.loc)}'`,
248 - value.loc
249 - );
249 + CompilerError.invariant(this.#values.has(value), {
250 + reason: `Expected value to be initialized at '${printSourceLocation(
251 + value.loc
252 + )}'`,
253 + description: null,
254 + loc: value.loc,
255 + suggestions: null,
256 + });
257 this.#variables.set(place.identifier.id, new Set([value]));
258 }
259
@@ -269,11 +276,12 @@ class InferenceState {
276 reference(place: Place, effectKind: Effect): void {
277 const values = this.#variables.get(place.identifier.id);
278 if (values === undefined) {
272 - CompilerError.invariant(
273 - effectKind !== Effect.Store,
274 - "[InferReferenceEffects] Unhandled store reference effect",
275 - place.loc
276 - );
279 + CompilerError.invariant(effectKind !== Effect.Store, {
280 + reason: "[InferReferenceEffects] Unhandled store reference effect",
281 + description: null,
282 + loc: place.loc,
283 + suggestions: null,
284 + });
285 place.effect =
286 effectKind === Effect.ConditionallyMutate
287 ? Effect.ConditionallyMutate
@@ -315,13 +323,14 @@ class InferenceState {
323 ) {
324 effect = Effect.Mutate;
325 } else {
318 - CompilerError.invalidReact(
319 - `InferReferenceEffects: inferred mutation of known immutable value`,
320 - place.loc,
321 - `Found mutation of ${printIdentifier(place.identifier)}${printType(
322 - place.identifier.type
323 - )} (${valueKind})`
324 - );
326 + CompilerError.invalidReact({
327 + reason: `InferReferenceEffects: inferred mutation of known immutable value`,
328 + description: `Found mutation of ${printIdentifier(
329 + place.identifier
330 + )}${printType(place.identifier.type)} (${valueKind})`,
331 + loc: place.loc,
332 + suggestions: null,
333 + });
334 }
335 break;
336 }
@@ -330,13 +339,14 @@ class InferenceState {
339 valueKind !== ValueKind.Mutable &&
340 valueKind !== ValueKind.Context
341 ) {
333 - CompilerError.invalidReact(
334 - `InferReferenceEffects: inferred mutation of known immutable value`,
335 - place.loc,
336 - `Found mutation of ${printIdentifier(place.identifier)}${printType(
337 - place.identifier.type
338 - )} (${valueKind})`
339 - );
342 + CompilerError.invalidReact({
343 + reason: `InferReferenceEffects: inferred mutation of known immutable value`,
344 + description: `Found mutation of ${printIdentifier(
345 + place.identifier
346 + )}${printType(place.identifier.type)} (${valueKind})`,
347 + loc: place.loc,
348 + suggestions: null,
349 + });
350 }
351
352 // TODO(gsn): This should be bailout once we add bailout infra.
@@ -365,11 +375,13 @@ class InferenceState {
375 break;
376 }
377 case Effect.Unknown: {
368 - CompilerError.invariant(
369 - false,
370 - "Unexpected unknown effect, expected to infer a precise effect kind",
371 - place.loc
372 - );
378 + CompilerError.invariant(false, {
379 + reason:
380 + "Unexpected unknown effect, expected to infer a precise effect kind",
381 + description: null,
382 + loc: place.loc,
383 + suggestions: null,
384 + });
385 }
386 default: {
387 assertExhaustive(
@@ -378,11 +390,12 @@ class InferenceState {
390 );
391 }
392 }
381 - CompilerError.invariant(
382 - effect !== null,
383 - "Expected effect to be set",
384 - place.loc
385 - );
393 + CompilerError.invariant(effect !== null, {
394 + reason: "Expected effect to be set",
395 + description: null,
396 + loc: place.loc,
397 + suggestions: null,
398 + });
399 place.effect = effect;
400 }
401
@@ -753,11 +766,13 @@ function inferBlock(
766 continue;
767 }
768 case "MethodCall": {
756 - CompilerError.invariant(
757 - state.isDefined(instrValue.receiver),
758 - "[InferReferenceEffects] Internal error: receiver of PropertyCall should have been defined by corresponding PropertyLoad",
759 - instrValue.loc
760 - );
769 + CompilerError.invariant(state.isDefined(instrValue.receiver), {
770 + reason:
771 + "[InferReferenceEffects] Internal error: receiver of PropertyCall should have been defined by corresponding PropertyLoad",
772 + description: null,
773 + loc: instrValue.loc,
774 + suggestions: null,
775 + });
776 state.reference(instrValue.property, Effect.Read);
777
778 const signature = getFunctionCallSignature(
@@ -892,8 +907,13 @@ function inferBlock(
907 const valueKind = state.kind(instrValue.place);
908 CompilerError.invariant(
909 valueKind === ValueKind.Mutable || valueKind === ValueKind.Context,
895 - "[InferReferenceEffects] Context variables are always mutable.",
896 - instrValue.loc
910 + {
911 + reason:
912 + "[InferReferenceEffects] Context variables are always mutable.",
913 + description: null,
914 + loc: instrValue.loc,
915 + suggestions: null,
916 + }
917 );
918 state.initialize(instrValue, valueKind);
919 state.define(lvalue, instrValue);
@@ -980,11 +1000,12 @@ function inferBlock(
1000 }
1001
1002 for (const operand of eachInstructionOperand(instr)) {
983 - CompilerError.invariant(
984 - effectKind != null,
985 - `effectKind must be set for instruction value \`${instrValue.kind}\``,
986 - instrValue.loc
987 - );
1003 + CompilerError.invariant(effectKind != null, {
1004 + reason: `effectKind must be set for instruction value \`${instrValue.kind}\``,
1005 + description: null,
1006 + loc: instrValue.loc,
1007 + suggestions: null,
1008 + });
1009 state.reference(operand, effectKind);
1010 }
1011
compiler/forget/packages/babel-plugin-react-forget/src/Inference/InlineUseMemo.ts
+13 -8
@@ -100,17 +100,22 @@ export function inlineUseMemo(fn: HIRFunction): void {
100 }
101
102 if (body.loweredFunc.params.length > 0) {
103 - CompilerError.invalidReact(
104 - "useMemo callbacks may not accept any arguments",
105 - body.loc
106 - );
103 + CompilerError.invalidReact({
104 + reason: "useMemo callbacks may not accept any arguments",
105 + description: null,
106 + loc: body.loc,
107 + suggestions: null,
108 + });
109 }
110
111 if (body.loweredFunc.async || body.loweredFunc.generator) {
110 - CompilerError.invalidReact(
111 - "useMemo callbacks may not be async or generator functions",
112 - body.loc
113 - );
112 + CompilerError.invalidReact({
113 + reason:
114 + "useMemo callbacks may not be async or generator functions",
115 + description: null,
116 + loc: body.loc,
117 + suggestions: null,
118 + });
119 }
120
121 // We know this function is used for useMemo and can prune it later
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveBlocks.ts
+31 -25
@@ -70,11 +70,12 @@ class Context {
70 this.#builders.push(builder);
71 fn();
72 const popped = this.#builders.pop();
73 - CompilerError.invariant(
74 - popped === builder,
75 - "Expected push/pop to be called 1:1",
76 - null
77 - );
73 + CompilerError.invariant(popped === builder, {
74 + reason: "Expected push/pop to be called 1:1",
75 + description: null,
76 + loc: null,
77 + suggestions: null,
78 + });
79 return builder.complete();
80 }
81 }
@@ -94,11 +95,12 @@ class Builder {
95
96 append(item: ReactiveStatement, label: BlockId | null): void {
97 if (label !== null) {
97 - CompilerError.invariant(
98 - item.kind === "terminal",
99 - "Only terminals may have a label",
100 - null
101 - );
98 + CompilerError.invariant(item.kind === "terminal", {
99 + reason: "Only terminals may have a label",
100 + description: null,
101 + loc: null,
102 + suggestions: null,
103 + });
104 item.label = label;
105 }
106 this.#instructions.push(item);
@@ -138,11 +140,12 @@ class Builder {
140 // "Expected all scopes to be closed when exiting a block"
141 // );
142 const first = this.#stack[0]!;
141 - CompilerError.invariant(
142 - first.kind === "block",
143 - "Expected first stack item to be a basic block",
144 - null
145 - );
143 + CompilerError.invariant(first.kind === "block", {
144 + reason: "Expected first stack item to be a basic block",
145 + description: null,
146 + loc: null,
147 + suggestions: null,
148 + });
149 return first.block;
150 }
151 }
@@ -173,11 +176,12 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
176 break;
177 }
178 case "scope": {
176 - CompilerError.invariant(
177 - false,
178 - "Expected the function to not have scopes already assigned",
179 - null
180 - );
179 + CompilerError.invariant(false, {
180 + reason: "Expected the function to not have scopes already assigned",
181 + description: null,
182 + loc: null,
183 + suggestions: null,
184 + });
185 }
186 default: {
187 assertExhaustive(
@@ -192,12 +196,14 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
196 export function getInstructionScope(
197 instr: ReactiveInstruction
198 ): ReactiveScope | null {
195 - CompilerError.invariant(
196 - instr.lvalue !== null,
197 - "Expected lvalues to not be null when assigning scopes. " +
199 + CompilerError.invariant(instr.lvalue !== null, {
200 + reason:
201 + "Expected lvalues to not be null when assigning scopes. " +
202 "Pruning lvalues too early can result in missing scope information.",
199 - instr.loc
200 - );
203 + description: null,
204 + loc: instr.loc,
205 + suggestions: null,
206 + });
207 for (const operand of eachInstructionLValue(instr)) {
208 const operandScope = getPlaceScope(instr.id, operand);
209 if (operandScope !== null) {
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+110 -83
@@ -65,11 +65,12 @@ class Driver {
65 }
66
67 visitBlock(block: BasicBlock, blockValue: ReactiveBlock): void {
68 - CompilerError.invariant(
69 - !this.cx.emitted.has(block.id),
70 - `Cannot emit the same block twice: bb${block.id}`,
71 - null
72 - );
68 + CompilerError.invariant(!this.cx.emitted.has(block.id), {
69 + reason: `Cannot emit the same block twice: bb${block.id}`,
70 + description: null,
71 + loc: null,
72 + suggestions: null,
73 + });
74 this.cx.emitted.add(block.id);
75 for (const instruction of block.instructions) {
76 blockValue.push({
@@ -252,11 +253,12 @@ class Driver {
253 loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
254 } else {
255 const break_ = this.visitBreak(terminal.loop, null);
255 - CompilerError.invariant(
256 - break_ !== null,
257 - "If loop body is already scheduled it must be a break",
258 - null
259 - );
256 + CompilerError.invariant(break_ !== null, {
257 + reason: "If loop body is already scheduled it must be a break",
258 + description: null,
259 + loc: null,
260 + suggestions: null,
261 + });
262 loopBody = [break_];
263 }
264
@@ -309,11 +311,12 @@ class Driver {
311 loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
312 } else {
313 const break_ = this.visitBreak(terminal.loop, null);
312 - CompilerError.invariant(
313 - break_ !== null,
314 - "If loop body is already scheduled it must be a break",
315 - null
316 - );
314 + CompilerError.invariant(break_ !== null, {
315 + reason: "If loop body is already scheduled it must be a break",
316 + description: null,
317 + loc: null,
318 + suggestions: null,
319 + });
320 loopBody = [break_];
321 }
322
@@ -394,11 +397,12 @@ class Driver {
397 loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
398 } else {
399 const break_ = this.visitBreak(terminal.loop, null);
397 - CompilerError.invariant(
398 - break_ !== null,
399 - "If loop body is already scheduled it must be a break",
400 - null
401 - );
400 + CompilerError.invariant(break_ !== null, {
401 + reason: "If loop body is already scheduled it must be a break",
402 + description: null,
403 + loc: null,
404 + suggestions: null,
405 + });
406 loopBody = [break_];
407 }
408
@@ -470,11 +474,12 @@ class Driver {
474 loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
475 } else {
476 const break_ = this.visitBreak(terminal.loop, null);
473 - CompilerError.invariant(
474 - break_ !== null,
475 - "If loop body is already scheduled it must be a break",
476 - null
477 - );
477 + CompilerError.invariant(break_ !== null, {
478 + reason: "If loop body is already scheduled it must be a break",
479 + description: null,
480 + loc: null,
481 + suggestions: null,
482 + });
483 loopBody = [break_];
484 }
485
@@ -547,11 +552,13 @@ class Driver {
552 let block: ReactiveBlock;
553 if (this.cx.isScheduled(terminal.block)) {
554 const break_ = this.visitBreak(terminal.block, null);
550 - CompilerError.invariant(
551 - break_ !== null,
552 - "Expected a break target for a label whose body is already scheduled",
553 - terminal.loc
554 - );
555 + CompilerError.invariant(break_ !== null, {
556 + reason:
557 + "Expected a break target for a label whose body is already scheduled",
558 + description: null,
559 + loc: terminal.loc,
560 + suggestions: null,
561 + });
562 block = [break_];
563 } else {
564 block = this.traverseBlock(this.cx.ir.blocks.get(terminal.block)!);
@@ -630,11 +637,12 @@ class Driver {
637 break;
638 }
639 case "unsupported": {
633 - CompilerError.invariant(
634 - false,
635 - "Unexpected unsupported terminal",
636 - terminal.loc
637 - );
640 + CompilerError.invariant(false, {
641 + reason: "Unexpected unsupported terminal",
642 + description: null,
643 + loc: terminal.loc,
644 + suggestions: null,
645 + });
646 }
647 default: {
648 assertExhaustive(terminal, "Unexpected terminal");
@@ -665,8 +673,13 @@ class Driver {
673 CompilerError.invariant(
674 instr.lvalue.identifier.id ===
675 defaultBlock.terminal.test.identifier.id,
668 - "Expected branch block to end in an instruction that sets the test value",
669 - instr.lvalue.loc
676 + {
677 + reason:
678 + "Expected branch block to end in an instruction that sets the test value",
679 + description: null,
680 + loc: instr.lvalue.loc,
681 + suggestions: null,
682 + }
683 );
684 return {
685 block: defaultBlock.id,
@@ -693,11 +706,12 @@ class Driver {
706 } else if (defaultBlock.terminal.kind === "goto") {
707 const instructions = defaultBlock.instructions;
708 if (instructions.length === 0) {
696 - CompilerError.invariant(
697 - false,
698 - "Expected goto value block to have at least one instruction",
699 - null
700 - );
709 + CompilerError.invariant(false, {
710 + reason: "Expected goto value block to have at least one instruction",
711 + description: null,
712 + loc: null,
713 + suggestions: null,
714 + });
715 } else if (defaultBlock.instructions.length === 1) {
716 const instr = defaultBlock.instructions[0]!;
717 let place: Place = instr.lvalue!;
@@ -792,11 +806,12 @@ class Driver {
806 case "optional": {
807 const test = this.visitValueBlock(terminal.test, terminal.loc);
808 const testBlock = this.cx.ir.blocks.get(test.block)!;
795 - CompilerError.invariant(
796 - testBlock.terminal.kind === "branch",
797 - `Unexpected terminal kind '${testBlock.terminal.kind}' for optional call test block`,
798 - testBlock.terminal.loc
799 - );
809 + CompilerError.invariant(testBlock.terminal.kind === "branch", {
810 + reason: `Unexpected terminal kind '${testBlock.terminal.kind}' for optional call test block`,
811 + description: null,
812 + loc: testBlock.terminal.loc,
813 + suggestions: null,
814 + });
815 const consequent = this.visitValueBlock(
816 testBlock.terminal.consequent,
817 terminal.loc
@@ -831,11 +846,12 @@ class Driver {
846 case "logical": {
847 const test = this.visitValueBlock(terminal.test, terminal.loc);
848 const testBlock = this.cx.ir.blocks.get(test.block)!;
834 - CompilerError.invariant(
835 - testBlock.terminal.kind === "branch",
836 - `Unexpected terminal kind '${testBlock.terminal.kind}' for logical test block`,
837 - testBlock.terminal.loc
838 - );
849 + CompilerError.invariant(testBlock.terminal.kind === "branch", {
850 + reason: `Unexpected terminal kind '${testBlock.terminal.kind}' for logical test block`,
851 + description: null,
852 + loc: testBlock.terminal.loc,
853 + suggestions: null,
854 + });
855
856 const leftFinal = this.visitValueBlock(
857 testBlock.terminal.consequent,
@@ -876,11 +892,12 @@ class Driver {
892 case "ternary": {
893 const test = this.visitValueBlock(terminal.test, terminal.loc);
894 const testBlock = this.cx.ir.blocks.get(test.block)!;
879 - CompilerError.invariant(
880 - testBlock.terminal.kind === "branch",
881 - `Unexpected terminal kind '${testBlock.terminal.kind}' for ternary test block`,
882 - testBlock.terminal.loc
883 - );
895 + CompilerError.invariant(testBlock.terminal.kind === "branch", {
896 + reason: `Unexpected terminal kind '${testBlock.terminal.kind}' for ternary test block`,
897 + description: null,
898 + loc: testBlock.terminal.loc,
899 + suggestions: null,
900 + });
901 const consequent = this.visitValueBlock(
902 testBlock.terminal.consequent,
903 terminal.loc
@@ -905,11 +922,12 @@ class Driver {
922 };
923 }
924 default: {
908 - CompilerError.invariant(
909 - false,
910 - `Unexpected value block terminal kind '${terminal.kind}'`,
911 - terminal.loc
912 - );
925 + CompilerError.invariant(false, {
926 + reason: `Unexpected value block terminal kind '${terminal.kind}'`,
927 + description: null,
928 + loc: terminal.loc,
929 + suggestions: null,
930 + });
931 }
932 }
933 }
@@ -924,7 +942,12 @@ class Driver {
942 ): ReactiveTerminalStatement<ReactiveBreakTerminal> {
943 const target = this.cx.getBreakTarget(block);
944 if (target === null) {
927 - CompilerError.invariant(false, "Expected a break target", null);
945 + CompilerError.invariant(false, {
946 + reason: "Expected a break target",
947 + description: null,
948 + loc: null,
949 + suggestions: null,
950 + });
951 }
952 switch (target.type) {
953 case "implicit": {
@@ -962,11 +985,12 @@ class Driver {
985 id: InstructionId
986 ): ReactiveTerminalStatement<ReactiveContinueTerminal> {
987 const target = this.cx.getContinueTarget(block);
965 - CompilerError.invariant(
966 - target !== null,
967 - `Expected continue target to be scheduled for bb${block}`,
968 - null
969 - );
988 + CompilerError.invariant(target !== null, {
989 + reason: `Expected continue target to be scheduled for bb${block}`,
990 + description: null,
991 + loc: null,
992 + suggestions: null,
993 + });
994 switch (target.type) {
995 case "implicit": {
996 return {
@@ -1046,11 +1070,12 @@ class Context {
1070 */
1071 schedule(block: BlockId, type: "if" | "switch" | "case"): number {
1072 const id = this.#nextScheduleId++;
1049 - CompilerError.invariant(
1050 - !this.#scheduled.has(block),
1051 - `Break block is already scheduled: bb${block}`,
1052 - null
1053 - );
1073 + CompilerError.invariant(!this.#scheduled.has(block), {
1074 + reason: `Break block is already scheduled: bb${block}`,
1075 + description: null,
1076 + loc: null,
1077 + suggestions: null,
1078 + });
1079 this.#scheduled.add(block);
1080 this.#controlFlowStack.push({ block, id, type });
1081 return id;
@@ -1064,11 +1089,12 @@ class Context {
1089 const id = this.#nextScheduleId++;
1090 const ownsBlock = !this.#scheduled.has(fallthroughBlock);
1091 this.#scheduled.add(fallthroughBlock);
1067 - CompilerError.invariant(
1068 - !this.#scheduled.has(continueBlock),
1069 - `Continue block is already scheduled: bb${continueBlock}`,
1070 - null
1071 - );
1092 + CompilerError.invariant(!this.#scheduled.has(continueBlock), {
1093 + reason: `Continue block is already scheduled: bb${continueBlock}`,
1094 + description: null,
1095 + loc: null,
1096 + suggestions: null,
1097 + });
1098 this.#scheduled.add(continueBlock);
1099 let ownsLoop = false;
1100 if (loopBlock !== null) {
@@ -1093,11 +1119,12 @@ class Context {
1119 */
1120 unschedule(scheduleId: number): void {
1121 const last = this.#controlFlowStack.pop();
1096 - CompilerError.invariant(
1097 - last !== undefined && last.id === scheduleId,
1098 - "Can only unschedule the last target",
1099 - null
1100 - );
1122 + CompilerError.invariant(last !== undefined && last.id === scheduleId, {
1123 + reason: "Can only unschedule the last target",
1124 + description: null,
1125 + loc: null,
1126 + suggestions: null,
1127 + });
1128 if (last.type !== "loop" || last.ownsBlock !== null) {
1129 this.#scheduled.delete(last.block);
1130 }
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+138 -104
@@ -215,11 +215,12 @@ function codegenReactiveScope(
215 firstOutputIndex = index;
216 }
217
218 - CompilerError.invariant(
219 - identifier.name != null,
220 - `Expected identifier '@${identifier.id}' to be named`,
221 - null
222 - );
218 + CompilerError.invariant(identifier.name != null, {
219 + reason: `Expected identifier '@${identifier.id}' to be named`,
220 + description: null,
221 + loc: null,
222 + suggestions: null,
223 + });
224
225 const name = convertIdentifier(identifier);
226 if (!cx.hasDeclared(identifier)) {
@@ -283,11 +284,12 @@ function codegenReactiveScope(
284 null as t.Expression | null
285 );
286 if (testCondition === null) {
286 - CompilerError.invariant(
287 - firstOutputIndex !== null,
288 - `Expected scope '@${scope.id}' to have at least one declaration`,
289 - null
290 - );
287 + CompilerError.invariant(firstOutputIndex !== null, {
288 + reason: `Expected scope '@${scope.id}' to have at least one declaration`,
289 + description: null,
290 + loc: null,
291 + suggestions: null,
292 + });
293 testCondition = t.binaryExpression(
294 "===",
295 t.memberExpression(
@@ -344,17 +346,19 @@ function codegenTerminal(
346 );
347 }
348 case "for-of": {
347 - CompilerError.invariant(
348 - terminal.init.kind === "SequenceExpression",
349 - `Expected a sequence expression init for ForOf`,
350 - terminal.init.loc,
351 - `Got '${terminal.init.kind}' expression instead`
352 - );
349 + CompilerError.invariant(terminal.init.kind === "SequenceExpression", {
350 + reason: `Expected a sequence expression init for ForOf`,
351 + description: `Got '${terminal.init.kind}' expression instead`,
352 + loc: terminal.init.loc,
353 + suggestions: null,
354 + });
355 if (terminal.init.instructions.length !== 2) {
354 - CompilerError.todo(
355 - "Support non-trivial ForOf inits",
356 - terminal.init.loc
357 - );
356 + CompilerError.todo({
357 + reason: "Support non-trivial ForOf inits",
358 + description: null,
359 + loc: terminal.init.loc,
360 + suggestions: null,
361 + });
362 }
363 const iterableCollection = terminal.init.instructions[0];
364 const iterableItem = terminal.init.instructions[1];
@@ -369,12 +373,12 @@ function codegenTerminal(
373 break;
374 }
375 default:
372 - CompilerError.invariant(
373 - false,
374 - `Expected a StoreLocal or Destructure to be assigned to the collection`,
375 - iterableItem.value.loc,
376 - `Found ${iterableItem.value.kind}`
377 - );
376 + CompilerError.invariant(false, {
377 + reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
378 + description: `Found ${iterableItem.value.kind}`,
379 + loc: iterableItem.value.loc,
380 + suggestions: null,
381 + });
382 }
383 let varDeclKind: "const" | "let";
384 switch (iterableItem.value.lvalue.kind) {
@@ -385,11 +389,13 @@ function codegenTerminal(
389 varDeclKind = "let" as const;
390 break;
391 case InstructionKind.Reassign:
388 - CompilerError.invariant(
389 - false,
390 - "Destructure should never be Reassign as it would be an Object/ArrayPattern",
391 - iterableItem.loc
392 - );
392 + CompilerError.invariant(false, {
393 + reason:
394 + "Destructure should never be Reassign as it would be an Object/ArrayPattern",
395 + description: null,
396 + loc: iterableItem.loc,
397 + suggestions: null,
398 + });
399 default:
400 assertExhaustive(
401 iterableItem.value.lvalue.kind,
@@ -509,11 +515,13 @@ function codegenInstructionNullable(
515 hasDeclaration ||= !isDeclared;
516 }
517 if (hasReasign && hasDeclaration) {
512 - CompilerError.invariant(
513 - false,
514 - "Encountered a destructuring operation where some identifiers are already declared (reassignments) but others are not (declarations)",
515 - instr.loc
516 - );
518 + CompilerError.invariant(false, {
519 + reason:
520 + "Encountered a destructuring operation where some identifiers are already declared (reassignments) but others are not (declarations)",
521 + description: null,
522 + loc: instr.loc,
523 + suggestions: null,
524 + });
525 } else if (hasReasign) {
526 kind = InstructionKind.Reassign;
527 }
@@ -521,31 +529,34 @@ function codegenInstructionNullable(
529 }
530 switch (kind) {
531 case InstructionKind.Const: {
524 - CompilerError.invariant(
525 - instr.lvalue === null,
526 - `Const declaration cannot be referenced as an expression`,
527 - instr.value.loc
528 - );
532 + CompilerError.invariant(instr.lvalue === null, {
533 + reason: `Const declaration cannot be referenced as an expression`,
534 + description: null,
535 + loc: instr.value.loc,
536 + suggestions: null,
537 + });
538 return createVariableDeclaration(instr.loc, "const", [
539 t.variableDeclarator(codegenLValue(lvalue), value),
540 ]);
541 }
542 case InstructionKind.Let: {
534 - CompilerError.invariant(
535 - instr.lvalue === null,
536 - `Const declaration cannot be referenced as an expression`,
537 - instr.value.loc
538 - );
543 + CompilerError.invariant(instr.lvalue === null, {
544 + reason: `Const declaration cannot be referenced as an expression`,
545 + description: null,
546 + loc: instr.value.loc,
547 + suggestions: null,
548 + });
549 return createVariableDeclaration(instr.loc, "let", [
550 t.variableDeclarator(codegenLValue(lvalue), value),
551 ]);
552 }
553 case InstructionKind.Reassign: {
544 - CompilerError.invariant(
545 - value !== null,
546 - "Expected a value for reassignment",
547 - instr.value.loc
548 - );
554 + CompilerError.invariant(value !== null, {
555 + reason: "Expected a value for reassignment",
556 + description: null,
557 + loc: instr.value.loc,
558 + suggestions: null,
559 + });
560 const expr = t.assignmentExpression("=", codegenLValue(lvalue), value);
561 if (instr.lvalue !== null) {
562 if (instr.value.kind !== "StoreContext") {
@@ -592,11 +603,12 @@ function codegenForInit(
603 }))
604 ).body;
605 const declaration = body[0]!;
595 - CompilerError.invariant(
596 - declaration.type === "VariableDeclaration",
597 - "Expected a variable declaration",
598 - declaration.loc ?? null
599 - );
606 + CompilerError.invariant(declaration.type === "VariableDeclaration", {
607 + reason: "Expected a variable declaration",
608 + description: null,
609 + loc: declaration.loc ?? null,
610 + suggestions: null,
611 + });
612 return declaration;
613 } else {
614 return codegenInstructionValue(cx, init);
@@ -744,11 +756,12 @@ function codegenInstructionValue(
756 switch (optionalValue.type) {
757 case "OptionalCallExpression":
758 case "CallExpression": {
747 - CompilerError.invariant(
748 - t.isExpression(optionalValue.callee),
749 - "v8 intrinsics are validated during lowering",
750 - optionalValue.callee.loc ?? null
751 - );
759 + CompilerError.invariant(t.isExpression(optionalValue.callee), {
760 + reason: "v8 intrinsics are validated during lowering",
761 + description: null,
762 + loc: optionalValue.callee.loc ?? null,
763 + suggestions: null,
764 + });
765 value = t.optionalCallExpression(
766 optionalValue.callee,
767 optionalValue.arguments,
@@ -759,11 +772,12 @@ function codegenInstructionValue(
772 case "OptionalMemberExpression":
773 case "MemberExpression": {
774 const property = optionalValue.property;
762 - CompilerError.invariant(
763 - t.isExpression(property),
764 - "Private names are validated during lowering",
765 - property.loc ?? null
766 - );
775 + CompilerError.invariant(t.isExpression(property), {
776 + reason: "Private names are validated during lowering",
777 + description: null,
778 + loc: property.loc ?? null,
779 + suggestions: null,
780 + });
781 value = t.optionalMemberExpression(
782 optionalValue.object,
783 property,
@@ -773,12 +787,13 @@ function codegenInstructionValue(
787 break;
788 }
789 default: {
776 - CompilerError.invariant(
777 - false,
778 - "Expected an optional value to resolve to a call expression or member expression",
779 - instrValue.loc,
780 - `Got a '${optionalValue.type}'`
781 - );
790 + CompilerError.invariant(false, {
791 + reason:
792 + "Expected an optional value to resolve to a call expression or member expression",
793 + description: `Got a '${optionalValue.type}'`,
794 + loc: instrValue.loc,
795 + suggestions: null,
796 + });
797 }
798 }
799 break;
@@ -788,18 +803,28 @@ function codegenInstructionValue(
803 CompilerError.invariant(
804 t.isMemberExpression(memberExpr) ||
805 t.isOptionalMemberExpression(memberExpr),
791 - "[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. " +
792 - `Got a '${memberExpr.type}'`,
793 - memberExpr.loc ?? null
806 + {
807 + reason:
808 + "[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. " +
809 + `Got a '${memberExpr.type}'`,
810 + description: null,
811 + loc: memberExpr.loc ?? null,
812 + suggestions: null,
813 + }
814 );
815 CompilerError.invariant(
816 t.isNodesEquivalent(
817 memberExpr.object,
818 codegenPlace(cx, instrValue.receiver)
819 ),
800 - "[Codegen] Internal error: Forget should always generate MethodCall::property " +
801 - "as a MemberExpression of MethodCall::receiver",
802 - memberExpr.loc ?? null
820 + {
821 + reason:
822 + "[Codegen] Internal error: Forget should always generate MethodCall::property " +
823 + "as a MemberExpression of MethodCall::receiver",
824 + description: null,
825 + loc: memberExpr.loc ?? null,
826 + suggestions: null,
827 + }
828 );
829 const args = instrValue.args.map((arg) => codegenArgument(cx, arg));
830 value = createCallExpression(instrValue.loc, memberExpr, args);
@@ -851,11 +876,12 @@ function codegenInstructionValue(
876 } else if (tagValue.type === "MemberExpression") {
877 tag = convertMemberExpressionToJsx(tagValue);
878 } else {
854 - CompilerError.invariant(
855 - tagValue.type === "StringLiteral",
856 - `Expected JSX tag to be an identifier or string, got '${tagValue.type}'`,
857 - tagValue.loc ?? null
858 - );
879 + CompilerError.invariant(tagValue.type === "StringLiteral", {
880 + reason: `Expected JSX tag to be an identifier or string, got '${tagValue.type}'`,
881 + description: null,
882 + loc: tagValue.loc ?? null,
883 + suggestions: null,
884 + });
885 if (tagValue.value.indexOf(":") >= 0) {
886 const [namespace, name] = tagValue.value.split(":", 2);
887 tag = createJsxNamespacedName(
@@ -1049,6 +1075,7 @@ function codegenInstructionValue(
1075 }'`,
1076 severity: ErrorSeverity.Todo,
1077 loc: declarator.loc ?? null,
1078 + suggestions: null,
1079 });
1080 return t.stringLiteral(`TODO handle ${declarator.id}`);
1081 } else {
@@ -1056,6 +1083,7 @@ function codegenInstructionValue(
1083 reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
1084 severity: ErrorSeverity.Todo,
1085 loc: stmt.loc ?? null,
1086 + suggestions: null,
1087 });
1088 return t.stringLiteral(`TODO handle ${stmt.type}`);
1089 }
@@ -1105,11 +1133,12 @@ function codegenInstructionValue(
1133 case "Destructure":
1134 case "StoreLocal":
1135 case "StoreContext": {
1108 - CompilerError.invariant(
1109 - false,
1110 - `Unexpected ${instrValue.kind} in codegenInstructionValue`,
1111 - instrValue.loc
1112 - );
1136 + CompilerError.invariant(false, {
1137 + reason: `Unexpected ${instrValue.kind} in codegenInstructionValue`,
1138 + description: null,
1139 + loc: instrValue.loc,
1140 + suggestions: null,
1141 + });
1142 }
1143 default: {
1144 assertExhaustive(
@@ -1193,20 +1222,23 @@ function codegenJsxElement(
1222 function convertMemberExpressionToJsx(
1223 expr: t.MemberExpression
1224 ): t.JSXMemberExpression {
1196 - CompilerError.invariant(
1197 - expr.property.type === "Identifier",
1198 - "Expected JSX member expression property to be a string",
1199 - expr.loc ?? null
1200 - );
1225 + CompilerError.invariant(expr.property.type === "Identifier", {
1226 + reason: "Expected JSX member expression property to be a string",
1227 + description: null,
1228 + loc: expr.loc ?? null,
1229 + suggestions: null,
1230 + });
1231 const property = t.jsxIdentifier(expr.property.name);
1232 if (expr.object.type === "Identifier") {
1233 return t.jsxMemberExpression(t.jsxIdentifier(expr.object.name), property);
1234 } else {
1205 - CompilerError.invariant(
1206 - expr.object.type === "MemberExpression",
1207 - "Expected JSX member expression to be an identifier or nested member expression",
1208 - expr.object.loc ?? null
1209 - );
1235 + CompilerError.invariant(expr.object.type === "MemberExpression", {
1236 + reason:
1237 + "Expected JSX member expression to be an identifier or nested member expression",
1238 + description: null,
1239 + loc: expr.object.loc ?? null,
1240 + suggestions: null,
1241 + });
1242 const object = convertMemberExpressionToJsx(expr.object);
1243 return t.jsxMemberExpression(object, property);
1244 }
@@ -1288,12 +1320,14 @@ function codegenPlace(cx: Context, place: Place): t.Expression {
1320 if (tmp != null) {
1321 return tmp;
1322 }
1291 - CompilerError.invariant(
1292 - place.identifier.name !== null || tmp !== undefined,
1293 - `[Codegen] No value found for temporary`,
1294 - place.loc,
1295 - `Value for '${printPlace(place)}' was not set in the codegen context`
1296 - );
1323 + CompilerError.invariant(place.identifier.name !== null || tmp !== undefined, {
1324 + reason: `[Codegen] No value found for temporary`,
1325 + description: `Value for '${printPlace(
1326 + place
1327 + )}' was not set in the codegen context`,
1328 + loc: place.loc,
1329 + suggestions: null,
1330 + });
1331 const identifier = convertIdentifier(place.identifier);
1332 identifier.loc = place.loc as any;
1333 return identifier;
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/DeriveMinimalDependencies.ts
+27 -17
@@ -126,8 +126,13 @@ export class ReactiveScopeDependencyTree {
126 deps.every(
127 (dep) => dep.accessType === PropertyAccessType.UnconditionalDependency
128 ),
129 - "[PropagateScopeDependencies] All dependencies must be reduced to unconditional dependencies.",
130 - null
129 + {
130 + reason:
131 + "[PropagateScopeDependencies] All dependencies must be reduced to unconditional dependencies.",
132 + description: null,
133 + loc: null,
134 + suggestions: null,
135 + }
136 );
137
138 for (const dep of deps) {
@@ -163,11 +168,12 @@ export class ReactiveScopeDependencyTree {
168 promoteDepsFromExhaustiveConditionals(
169 trees: Array<ReactiveScopeDependencyTree>
170 ): void {
166 - CompilerError.invariant(
167 - trees.length > 1,
168 - "Expected trees to be at least 2 elements long.",
169 - null
170 - );
171 + CompilerError.invariant(trees.length > 1, {
172 + reason: "Expected trees to be at least 2 elements long.",
173 + description: null,
174 + loc: null,
175 + suggestions: null,
176 + });
177
178 for (const [id, root] of this.#roots) {
179 const nodesForRootId = mapNonNull(trees, (tree) => tree.#roots.get(id));
@@ -458,19 +464,23 @@ function addSubtreeIntersection(
464 otherProperties: Array<Map<string, DependencyNode>>,
465 currProperties: Map<string, DependencyNode>
466 ): void {
461 - CompilerError.invariant(
462 - otherProperties.length > 1,
463 - "[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long.",
464 - null
465 - );
467 + CompilerError.invariant(otherProperties.length > 1, {
468 + reason:
469 + "[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long.",
470 + description: null,
471 + loc: null,
472 + suggestions: null,
473 + });
474
475 otherProperties.forEach((properties) =>
476 properties.forEach((node, _) =>
469 - CompilerError.invariant(
470 - !isUnconditional(node.accessType),
471 - "[DeriveMinimalDependencies] Expected otherProperties to only contain unconditional nodes!",
472 - null
473 - )
477 + CompilerError.invariant(!isUnconditional(node.accessType), {
478 + reason:
479 + "[DeriveMinimalDependencies] Expected otherProperties to only contain unconditional nodes!",
480 + description: null,
481 + loc: null,
482 + suggestions: null,
483 + })
484 )
485 );
486
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/InferReactiveIdentifiers.ts
+6 -5
@@ -105,11 +105,12 @@ class Visitor extends ReactiveFunctionVisitor<State> {
105 break;
106 }
107 case Effect.Unknown: {
108 - CompilerError.invariant(
109 - false,
110 - "Unexpected unknown effect",
111 - operand.loc
112 - );
108 + CompilerError.invariant(false, {
109 + reason: "Unexpected unknown effect",
110 + description: null,
111 + loc: operand.loc,
112 + suggestions: null,
113 + });
114 }
115 default: {
116 assertExhaustive(
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts
+6 -5
@@ -226,11 +226,12 @@ function printTerminal(writer: Writer, terminal: ReactiveTerminal): void {
226 writer.writeLine(`${prefix}: {`);
227 writer.indented(() => {
228 const block = case_.block;
229 - CompilerError.invariant(
230 - block != null,
231 - "Expected case to have a block",
232 - case_.test?.loc ?? null
233 - );
229 + CompilerError.invariant(block != null, {
230 + reason: "Expected case to have a block",
231 + description: null,
232 + loc: case_.test?.loc ?? null,
233 + suggestions: null,
234 + });
235 printReactiveInstructions(writer, block);
236 });
237 writer.writeLine("}");
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PromoteUsedTemporaries.ts
+7 -5
@@ -75,11 +75,13 @@ export function promoteUsedTemporaries(fn: ReactiveFunction): void {
75 }
76
77 function promoteTemporary(identifier: Identifier, state: VisitorState): void {
78 - CompilerError.invariant(
79 - identifier.name === null,
80 - "promoteTemporary: Expected to be called only for temporary variables",
81 - GeneratedSource
82 - );
78 + CompilerError.invariant(identifier.name === null, {
79 + reason:
80 + "promoteTemporary: Expected to be called only for temporary variables",
81 + description: null,
82 + loc: GeneratedSource,
83 + suggestions: null,
84 + });
85 if (state.tags.has(identifier.id)) {
86 identifier.name = `T${state.nextId++}`;
87 } else {
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+7 -6
@@ -459,12 +459,13 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
459 // OptionalExpression value is a SequenceExpression where the instructions
460 // represent the code prior to the `?` and the final value represents the
461 // conditional code that follows.
462 - CompilerError.invariant(
463 - inner.kind === "SequenceExpression",
464 - "Expected OptionalExpression value to be a SequenceExpression",
465 - value.loc,
466 - `Found a '${value.kind}'`
467 - );
462 + CompilerError.invariant(inner.kind === "SequenceExpression", {
463 + reason:
464 + "Expected OptionalExpression value to be a SequenceExpression",
465 + description: `Found a '${value.kind}'`,
466 + loc: value.loc,
467 + suggestions: null,
468 + });
469 // Instructions are the unconditionally executed portion before the `?`
470 for (const instr of inner.instructions) {
471 this.visitInstruction(instr, context);
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
+24 -16
@@ -243,11 +243,12 @@ class State {
243 this.scopes.set(scope.id, node);
244 }
245 const identifierNode = this.identifiers.get(identifier);
246 - CompilerError.invariant(
247 - identifierNode !== undefined,
248 - "Expected identifier to be initialized",
249 - place.loc
250 - );
246 + CompilerError.invariant(identifierNode !== undefined, {
247 + reason: "Expected identifier to be initialized",
248 + description: null,
249 + loc: place.loc,
250 + suggestions: null,
251 + });
252 identifierNode.scopes.add(scope.id);
253 }
254 }
@@ -264,11 +265,12 @@ function computeMemoizedIdentifiers(state: State): Set<IdentifierId> {
265 // Visit an identifier, optionally forcing it to be memoized
266 function visit(id: IdentifierId, forceMemoize: boolean = false): boolean {
267 const node = state.identifiers.get(id);
267 - CompilerError.invariant(
268 - node !== undefined,
269 - `Expected a node for all identifiers, none found for '${id}'`,
270 - null
271 - );
268 + CompilerError.invariant(node !== undefined, {
269 + reason: `Expected a node for all identifiers, none found for '${id}'`,
270 + description: null,
271 + loc: null,
272 + suggestions: null,
273 + });
274 if (node.seen) {
275 return node.memoized;
276 }
@@ -303,11 +305,12 @@ function computeMemoizedIdentifiers(state: State): Set<IdentifierId> {
305 // Force all the scope's optionally-memoizeable dependencies (not "Never") to be memoized
306 function forceMemoizeScopeDependencies(id: ScopeId): void {
307 const node = state.scopes.get(id);
306 - CompilerError.invariant(
307 - node !== undefined,
308 - "Expected a node for all scopes",
309 - null
310 - );
308 + CompilerError.invariant(node !== undefined, {
309 + reason: "Expected a node for all scopes",
310 + description: null,
311 + loc: null,
312 + suggestions: null,
313 + });
314 if (node.seen) {
315 return;
316 }
@@ -606,7 +609,12 @@ function computeMemoizationInputs(
609 };
610 }
611 case "UnsupportedNode": {
609 - CompilerError.invariant(false, `Unexpected unsupported node`, value.loc);
612 + CompilerError.invariant(false, {
613 + reason: `Unexpected unsupported node`,
614 + description: null,
615 + loc: value.loc,
616 + suggestions: null,
617 + });
618 }
619 default: {
620 assertExhaustive(value, `Unexpected value kind '${(value as any).kind}'`);
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/RenameVariables.ts
+6 -1
@@ -103,6 +103,11 @@ class Scopes {
103 this.#stack.push(next);
104 fn();
105 const last = this.#stack.pop();
106 - CompilerError.invariant(last === next, "Mismatch push/pop calls", null);
106 + CompilerError.invariant(last === next, {
107 + reason: "Mismatch push/pop calls",
108 + description: null,
109 + loc: null,
110 + suggestions: null,
111 + });
112 }
113 }
compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts
+6 -5
@@ -83,11 +83,12 @@ export function eliminateRedundantPhi(fn: HIRFunction): void {
83 same = operand;
84 }
85 }
86 - CompilerError.invariant(
87 - same !== null,
88 - "Expected phis to be non-empty",
89 - null
90 - );
86 + CompilerError.invariant(same !== null, {
87 + reason: "Expected phis to be non-empty",
88 + description: null,
89 + loc: null,
90 + suggestions: null,
91 + });
92 rewrites.set(phi.id, same);
93 block.phis.delete(phi);
94 }
compiler/forget/packages/babel-plugin-react-forget/src/SSA/EnterSSA.ts
+31 -26
@@ -67,11 +67,12 @@ class SSABuilder {
67 }
68
69 state(): State {
70 - CompilerError.invariant(
71 - this.#current !== null,
72 - "we need to be in a block to access state!",
73 - null
74 - );
70 + CompilerError.invariant(this.#current !== null, {
71 + reason: "we need to be in a block to access state!",
72 + description: null,
73 + loc: null,
74 + suggestions: null,
75 + });
76 return this.#states.get(this.#current)!;
77 }
78
@@ -96,12 +97,12 @@ class SSABuilder {
97
98 definePlace(oldPlace: Place): Place {
99 const oldId = oldPlace.identifier;
99 - CompilerError.invariant(
100 - !this.#unknown.has(oldId),
101 - `EnterSSA: Expected identifier to be defined before being used`,
102 - oldPlace.loc,
103 - `Identifier ${printIdentifier(oldId)} is undefined`
104 - );
100 + CompilerError.invariant(!this.#unknown.has(oldId), {
101 + reason: `EnterSSA: Expected identifier to be defined before being used`,
102 + description: `Identifier ${printIdentifier(oldId)} is undefined`,
103 + loc: oldPlace.loc,
104 + suggestions: null,
105 + });
106
107 // Do not redefine context references.
108 if (this.#context.has(oldId)) {
@@ -237,11 +238,12 @@ function enterSSAImpl(
238 ): void {
239 const visitedBlocks: Set<BasicBlock> = new Set();
240 for (const [blockId, block] of func.body.blocks) {
240 - CompilerError.invariant(
241 - !visitedBlocks.has(block),
242 - `found a cycle! visiting bb${block.id} again`,
243 - null
244 - );
241 + CompilerError.invariant(!visitedBlocks.has(block), {
242 + reason: `found a cycle! visiting bb${block.id} again`,
243 + description: null,
244 + loc: null,
245 + suggestions: null,
246 + });
247 visitedBlocks.add(block);
248
249 builder.startBlock(block);
@@ -249,11 +251,12 @@ function enterSSAImpl(
251 if (blockId === rootEntry) {
252 // NOTE: func.context should be empty for the root function
253 if (func.env.enableOptimizeFunctionExpressions) {
252 - CompilerError.invariant(
253 - func.context.length === 0,
254 - `Expected function context to be empty for outer function declarations`,
255 - func.loc
256 - );
254 + CompilerError.invariant(func.context.length === 0, {
255 + reason: `Expected function context to be empty for outer function declarations`,
256 + description: null,
257 + loc: func.loc,
258 + suggestions: null,
259 + });
260 } else {
261 func.context = func.context.map((p) => builder.defineContext(p));
262 }
@@ -270,11 +273,13 @@ function enterSSAImpl(
273 ) {
274 const loweredFunc = instr.value.loweredFunc;
275 const entry = loweredFunc.body.blocks.get(loweredFunc.body.entry)!;
273 - CompilerError.invariant(
274 - entry.preds.size === 0,
275 - "Expected function expression entry block to have zero predecessors",
276 - null
277 - );
276 + CompilerError.invariant(entry.preds.size === 0, {
277 + reason:
278 + "Expected function expression entry block to have zero predecessors",
279 + description: null,
280 + loc: null,
281 + suggestions: null,
282 + });
283 entry.preds.add(blockId);
284 builder.defineFunction(loweredFunc);
285 builder.enter(() => {
compiler/forget/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts
+54 -33
@@ -131,12 +131,12 @@ export function leaveSSA(fn: HIRFunction): void {
131 if (value.kind === "DeclareLocal") {
132 const name = value.lvalue.place.identifier.name;
133 if (name !== null) {
134 - CompilerError.invariant(
135 - !declarations.has(name),
136 - `Unexpected duplicate declaration`,
137 - value.lvalue.place.loc,
138 - `Found duplicate declaration for '${name}'`
139 - );
134 + CompilerError.invariant(!declarations.has(name), {
135 + reason: `Unexpected duplicate declaration`,
136 + description: `Found duplicate declaration for '${name}'`,
137 + loc: value.lvalue.place.loc,
138 + suggestions: null,
139 + });
140 declarations.set(name, {
141 lvalue: value.lvalue,
142 place: value.lvalue.place,
@@ -153,8 +153,12 @@ export function leaveSSA(fn: HIRFunction): void {
153 ) {
154 CompilerError.invariant(
155 originalLVal !== undefined || block.kind === "block",
156 - `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
157 - value.lvalue.place.loc
156 + {
157 + reason: `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
158 + description: null,
159 + loc: value.lvalue.place.loc,
160 + suggestions: null,
161 + }
162 );
163 declarations.set(value.lvalue.place.identifier.name, {
164 lvalue: value.lvalue,
@@ -176,9 +180,14 @@ export function leaveSSA(fn: HIRFunction): void {
180 if (place.identifier.name == null) {
181 CompilerError.invariant(
182 kind === null || kind === InstructionKind.Const,
179 - `Expected consistent kind for destructuring`,
180 - place.loc,
181 - `other places were '${kind}' but '${printPlace(place)}' is const`
183 + {
184 + reason: `Expected consistent kind for destructuring`,
185 + description: `other places were '${kind}' but '${printPlace(
186 + place
187 + )}' is const`,
188 + loc: place.loc,
189 + suggestions: null,
190 + }
191 );
192 kind = InstructionKind.Const;
193 } else {
@@ -189,8 +198,12 @@ export function leaveSSA(fn: HIRFunction): void {
198 ) {
199 CompilerError.invariant(
200 originalLVal !== undefined || block.kind !== "value",
192 - `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
193 - place.loc
201 + {
202 + reason: `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
203 + description: null,
204 + loc: place.loc,
205 + suggestions: null,
206 + }
207 );
208 declarations.set(place.identifier.name, {
209 lvalue: value.lvalue,
@@ -198,32 +211,39 @@ export function leaveSSA(fn: HIRFunction): void {
211 });
212 CompilerError.invariant(
213 kind === null || kind === InstructionKind.Const,
201 - `Expected consistent kind for destructuring`,
202 - place.loc,
203 - `Other places were '${kind}' but '${printPlace(
204 - place
205 - )}' is const`
214 + {
215 + reason: `Expected consistent kind for destructuring`,
216 + description: `Other places were '${kind}' but '${printPlace(
217 + place
218 + )}' is const`,
219 + loc: place.loc,
220 + suggestions: null,
221 + }
222 );
223 kind = InstructionKind.Const;
224 } else {
225 CompilerError.invariant(
226 kind === null || kind === InstructionKind.Reassign,
211 - `Expected consistent kind for destructuring`,
212 - place.loc,
213 - `Other places were '${kind}' but '${printPlace(
214 - place
215 - )}' is reassigned`
227 + {
228 + reason: `Expected consistent kind for destructuring`,
229 + description: `Other places were '${kind}' but '${printPlace(
230 + place
231 + )}' is reassigned`,
232 + loc: place.loc,
233 + suggestions: null,
234 + }
235 );
236 kind = InstructionKind.Reassign;
237 originalLVal.lvalue.kind = InstructionKind.Let;
238 }
239 }
240 }
222 - CompilerError.invariant(
223 - kind !== null,
224 - "Expected at least one operand",
225 - null
226 - );
241 + CompilerError.invariant(kind !== null, {
242 + reason: "Expected at least one operand",
243 + description: null,
244 + loc: null,
245 + suggestions: null,
246 + });
247 value.lvalue.kind = kind;
248 }
249 rewritePlace(lvalue, rewrites, declarations);
@@ -356,11 +376,12 @@ export function leaveSSA(fn: HIRFunction): void {
376
377 // If we never saw a declaration for this phi, it may have been pruned by DCE, so synthesize
378 // a new Let binding
359 - CompilerError.invariant(
360 - phi.id.name != null,
361 - "Expected reassignment phis to have a name",
362 - null
363 - );
379 + CompilerError.invariant(phi.id.name != null, {
380 + reason: "Expected reassignment phis to have a name",
381 + description: null,
382 + loc: null,
383 + suggestions: null,
384 + });
385 const declaration = declarations.get(phi.id.name);
386 if (declaration === undefined) {
387 let initValue: Place;
compiler/forget/packages/babel-plugin-react-forget/src/TypeInference/InferTypes.ts
+6 -5
@@ -342,11 +342,12 @@ class Unifier {
342 if (type.kind === "Phi") {
343 const operands = new Set(type.operands.map((i) => this.get(i).kind));
344
345 - CompilerError.invariant(
346 - operands.size > 0,
347 - "there should be at least one operand",
348 - null
349 - );
345 + CompilerError.invariant(operands.size > 0, {
346 + reason: "there should be at least one operand",
347 + description: null,
348 + loc: null,
349 + suggestions: null,
350 + });
351 const kind = operands.values().next().value;
352
353 // there's only one unique type and it's not a type var
compiler/forget/packages/babel-plugin-react-forget/src/Utils/DisjointSet.ts
+6 -5
@@ -20,11 +20,12 @@ export default class DisjointSet<T> {
20 */
21 union(items: Array<T>): void {
22 const first = items.shift();
23 - CompilerError.invariant(
24 - first != null,
25 - "Expected set to be non-empty",
26 - null
27 - );
23 + CompilerError.invariant(first != null, {
24 + reason: "Expected set to be non-empty",
25 + description: null,
26 + loc: null,
27 + suggestions: null,
28 + });
29 // determine an arbitrary "root" for this set: if the first
30 // item already has a root then use that, otherwise the first item
31 // will be the new root.
compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateFrozenLambdas.ts
+1
@@ -138,6 +138,7 @@ function validateOperand(
138 reason:
139 "Cannot use a mutable function where an immutable value is expected",
140 severity: ErrorSeverity.InvalidReact,
141 + suggestions: null,
142 });
143 }
144 }
compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateHooksUsage.ts
+1
@@ -31,6 +31,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
31 "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)",
32 loc: typeof place.loc !== "symbol" ? place.loc : null,
33 severity: ErrorSeverity.InvalidReact,
34 + suggestions: null,
35 })
36 );
37 };
compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateNoRefAccesInRender.ts
+2
@@ -109,6 +109,7 @@ function validateNonRefValue(error: CompilerError, operand: Place): void {
109 reason:
110 "Ref values (the `current` property) may not be accessed during render",
111 severity: ErrorSeverity.InvalidReact,
112 + suggestions: null,
113 })
114 );
115 }
@@ -124,6 +125,7 @@ function validateNonRefObject(error: CompilerError, operand: Place): void {
125 reason:
126 "Ref values may not be passed to functions because they could read the ref value (`current` property) during render",
127 severity: ErrorSeverity.InvalidReact,
128 + suggestions: null,
129 })
130 );
131 }
compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateUnconditionalHooks.ts
+1
@@ -98,6 +98,7 @@ export function validateUnconditionalHooks(
98 "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)",
99 loc,
100 severity: ErrorSeverity.InvalidReact,
101 + suggestions: null,
102 })
103 );
104 }
compiler/forget/packages/babel-plugin-react-forget/src/index.ts
+1
@@ -9,6 +9,7 @@ export { default as BabelPlugin } from "./Babel/BabelPlugin";
9 export {
10 CompilerError,
11 CompilerErrorDetail,
12 + CompilerSuggestionOperation,
13 ErrorSeverity,
14 } from "./CompilerError";
15 export {
compiler/forget/packages/eslint-plugin-react-forget/src/rules/ReactForgetDiagnostics.ts
+66 -5
@@ -8,6 +8,7 @@
8 import { transformFromAstSync } from "@babel/core";
9 import type { SourceLocation as BabelSourceLocation } from "@babel/types";
10 import ReactForgetBabelPlugin, {
11 + CompilerSuggestionOperation,
12 ErrorSeverity,
13 type CompilerError,
14 type CompilerErrorDetail,
@@ -20,6 +21,10 @@ type CompilerErrorDetailWithLoc = Omit<CompilerErrorDetail, "loc"> & {
21 loc: BabelSourceLocation;
22 };
23
24 +function assertExhaustive(_: never, errorMsg: string): never {
25 + throw new Error(errorMsg);
26 +}
27 +
28 function isReactForgetCompilerError(err: Error): err is CompilerError {
29 return err.name === "ReactForgetCompilerError";
30 }
@@ -63,6 +68,7 @@ const rule: Rule.RuleModule = {
68 description: "Surfaces diagnostics from React Forget",
69 recommended: true,
70 },
71 + fixable: "code",
72 },
73 create(context: Rule.RuleContext) {
74 // Compat with older versions of eslint
@@ -90,12 +96,67 @@ const rule: Rule.RuleModule = {
96 } catch (err) {
97 if (isReactForgetCompilerError(err) && Array.isArray(err.details)) {
98 for (const detail of err.details) {
93 - if (isReportableDiagnostic(detail)) {
94 - context.report({
95 - message: detail.toString(),
96 - loc: detail.loc,
97 - });
99 + if (!isReportableDiagnostic(detail)) {
100 + continue;
101 + }
102 + let suggest: Array<Rule.SuggestionReportDescriptor> = [];
103 + if (Array.isArray(detail.suggestions)) {
104 + for (const suggestion of detail.suggestions) {
105 + switch (suggestion.op) {
106 + case CompilerSuggestionOperation.InsertBefore:
107 + suggest.push({
108 + desc: suggestion.description,
109 + fix(fixer) {
110 + return fixer.insertTextBeforeRange(
111 + suggestion.range,
112 + suggestion.text
113 + );
114 + },
115 + });
116 + break;
117 + case CompilerSuggestionOperation.InsertAfter:
118 + suggest.push({
119 + desc: suggestion.description,
120 + fix(fixer) {
121 + return fixer.insertTextAfterRange(
122 + suggestion.range,
123 + suggestion.text
124 + );
125 + },
126 + });
127 + break;
128 + case CompilerSuggestionOperation.Replace:
129 + suggest.push({
130 + desc: suggestion.description,
131 + fix(fixer) {
132 + return fixer.replaceTextRange(
133 + suggestion.range,
134 + suggestion.text
135 + );
136 + },
137 + });
138 + break;
139 + case CompilerSuggestionOperation.Remove:
140 + suggest.push({
141 + desc: suggestion.description,
142 + fix(fixer) {
143 + return fixer.removeRange(suggestion.range);
144 + },
145 + });
146 + break;
147 + default:
148 + assertExhaustive(
149 + suggestion,
150 + "Unhandled suggestion operation"
151 + );
152 + }
153 + }
154 }
155 + context.report({
156 + message: detail.toString(),
157 + loc: detail.loc,
158 + suggest,
159 + });
160 }
161 } else {
162 throw err;