@samitouri / QOS-React / commits / 545e53c428

Use CompilerErrors to record errors

This moves the bailout recording mechanism into a separate CompilerErrors class instead of repurposing HIRBuilder. This is to allow other passes to also record errors instead of immediately throwing.

Lauren Tan committed Jan 30, 2023 at 10:29 UTC 545e53c4289e9c59104eb0c198c9e277b3f16f4c
4 files changed +77 -75
compiler/forget/src/CompilerError.ts
+26 -3
@@ -92,13 +92,36 @@ export class CompilerErrorDetail {
92 export class CompilerError extends Error {
93 details: CompilerErrorDetail[] = [];
94
95 - constructor(details: CompilerErrorDetail[], ...args: any[]) {
95 + constructor(...args: any[]) {
96 super(...args);
97 - this.details = details;
98 - this.message = this.toString();
97 }
98
99 + override get message(): string {
100 + return this.toString();
101 + }
102 +
103 + override set message(_message: string) {}
104 +
105 override toString() {
106 return this.details.map((detail) => detail.toString()).join("\n\n");
107 }
108 +
109 + push(options: CompilerErrorOptions): CompilerErrorDetail {
110 + const detail = new CompilerErrorDetail({
111 + reason: options.reason,
112 + severity: options.severity,
113 + codeframe: tryPrintCodeFrame(options),
114 + loc: options.nodePath?.node?.loc ?? null,
115 + });
116 + return this.pushErrorDetail(detail);
117 + }
118 +
119 + pushErrorDetail(detail: CompilerErrorDetail): CompilerErrorDetail {
120 + this.details.push(detail);
121 + return detail;
122 + }
123 +
124 + hasErrors(): boolean {
125 + return this.details.length > 0;
126 + }
127 }
compiler/forget/src/CompilerPipeline.ts
+2 -2
@@ -46,8 +46,8 @@ export type CompilerPipelineValue =
46 export function* run(
47 func: NodePath<t.FunctionDeclaration>
48 ): Generator<CompilerPipelineValue, t.Function> {
49 - const lowering = lower(func).orElse((details) => {
50 - throw new CompilerError(details);
49 + const lowering = lower(func).orElse((error) => {
50 + throw error;
51 });
52
53 const hir = lowering.unwrap();
compiler/forget/src/HIR/BuildHIR.ts
+47 -45
@@ -8,7 +8,7 @@
8 import { NodePath, Scope } from "@babel/traverse";
9 import * as t from "@babel/types";
10 import invariant from "invariant";
11 -import { CompilerErrorDetail, ErrorSeverity } from "../CompilerError";
11 +import { CompilerError, ErrorSeverity } from "../CompilerError";
12 import { Err, Ok, Result } from "../lib/Result";
13 import { assertExhaustive } from "../Utils/utils";
14 import {
@@ -49,7 +49,7 @@ import HIRBuilder, { Environment } from "./HIRBuilder";
49 */
50 export function lower(
51 func: NodePath<t.Function>
52 -): Result<HIRFunction, CompilerErrorDetail[]> {
52 +): Result<HIRFunction, CompilerError> {
53 const env = new Environment();
54 const builder = new HIRBuilder(env);
55
@@ -88,7 +88,7 @@ export function lower(
88 place
89 );
90 } else {
91 - builder.recordError({
91 + builder.errors.push({
92 reason: `(BuildHIR::lower) Handle ${param.node.type} params`,
93 severity: ErrorSeverity.Todo,
94 nodePath: param,
@@ -109,14 +109,14 @@ export function lower(
109 } else if (body.isBlockStatement()) {
110 lowerStatement(builder, body);
111 } else {
112 - builder.recordError({
112 + builder.errors.push({
113 reason: `(BuildHIR::lower) Unexpected function body kind: ${body.type}}`,
114 severity: ErrorSeverity.InvalidInput,
115 nodePath: body,
116 });
117 }
118
119 - if (builder.hasErrors()) {
119 + if (builder.errors.hasErrors()) {
120 return Err(builder.errors);
121 }
122
@@ -356,7 +356,7 @@ function lowerStatement(
356 const initBlock = builder.enter("value", (blockId) => {
357 const init = stmt.get("init");
358 if (!init.isVariableDeclaration()) {
359 - builder.recordError({
359 + builder.errors.push({
360 reason:
361 "(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement",
362 severity: ErrorSeverity.Todo,
@@ -376,7 +376,7 @@ function lowerStatement(
376 const updateBlock = builder.enter("value", (blockId) => {
377 const update = stmt.get("update");
378 if (update.node == null) {
379 - builder.recordError({
379 + builder.errors.push({
380 reason: `(BuildHIR::lowerStatement) Handle empty update in ForStatement`,
381 severity: ErrorSeverity.Todo,
382 nodePath: stmt,
@@ -419,7 +419,7 @@ function lowerStatement(
419
420 const test = stmt.get("test");
421 if (test.node == null) {
422 - builder.recordError({
422 + builder.errors.push({
423 reason: `(BuildHIR::lowerStatement) Handle empty test in ForStatement`,
424 severity: ErrorSeverity.Todo,
425 nodePath: stmt,
@@ -588,7 +588,7 @@ function lowerStatement(
588 const test = case_.get("test");
589 if (test.node == null) {
590 if (hasDefault) {
591 - builder.recordError({
591 + builder.errors.push({
592 reason:
593 "(BuildHIR::lowerStatement) Expected at most one `default` branch in SwitchStatement, this code should have failed to parse",
594 severity: ErrorSeverity.InvalidInput,
@@ -674,7 +674,7 @@ function lowerStatement(
674 const stmt = stmtPath as NodePath<t.VariableDeclaration>;
675 const nodeKind: string = stmt.node.kind;
676 if (nodeKind === "var") {
677 - builder.recordError({
677 + builder.errors.push({
678 reason: `(BuildHIR::lowerStatement) Handle ${nodeKind} kinds in VariableDeclaration`,
679 severity: ErrorSeverity.Todo,
680 nodePath: stmt,
@@ -757,7 +757,7 @@ function lowerStatement(
757 case "TSNamespaceExportDeclaration":
758 case "TSTypeAliasDeclaration":
759 case "WithStatement": {
760 - builder.recordError({
760 + builder.errors.push({
761 reason: `(BuildHIR::lowerStatement) Handle ${stmtPath.type} statements`,
762 severity: ErrorSeverity.Todo,
763 nodePath: stmtPath,
@@ -829,7 +829,7 @@ function lowerExpression(
829 let hasError = false;
830 for (const propertyPath of propertyPaths) {
831 if (!propertyPath.isObjectProperty()) {
832 - builder.recordError({
832 + builder.errors.push({
833 reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.type} properties in ObjectExpression`,
834 severity: ErrorSeverity.Todo,
835 nodePath: propertyPath,
@@ -839,7 +839,7 @@ function lowerExpression(
839 }
840 const key = propertyPath.node.key;
841 if (key.type !== "Identifier") {
842 - builder.recordError({
842 + builder.errors.push({
843 reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
844 severity: ErrorSeverity.InvalidInput,
845 nodePath: propertyPath,
@@ -849,7 +849,7 @@ function lowerExpression(
849 }
850 const valuePath = propertyPath.get("value");
851 if (!valuePath.isExpression()) {
852 - builder.recordError({
852 + builder.errors.push({
853 reason: `(BuildHIR::lowerExpression) Handle ${valuePath.type} values in ObjectExpression`,
854 severity: ErrorSeverity.Todo,
855 nodePath: valuePath,
@@ -874,7 +874,7 @@ function lowerExpression(
874 let elements: Place[] = [];
875 for (const element of expr.get("elements")) {
876 if (element.node == null || !element.isExpression()) {
877 - builder.recordError({
877 + builder.errors.push({
878 reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,
879 severity: ErrorSeverity.Todo,
880 nodePath: element,
@@ -898,7 +898,7 @@ function lowerExpression(
898 const expr = exprPath as NodePath<t.NewExpression>;
899 const calleePath = expr.get("callee");
900 if (!calleePath.isExpression()) {
901 - builder.recordError({
901 + builder.errors.push({
902 reason: `(BuildHIR::lowerExpression) Expected Expression, got ${calleePath.type} in NewExpression (v8 intrinsics not supported): ${calleePath.type}`,
903 severity: ErrorSeverity.InvalidInput,
904 nodePath: calleePath,
@@ -910,7 +910,7 @@ function lowerExpression(
910 let hasError = false;
911 for (const argPath of expr.get("arguments")) {
912 if (!argPath.isExpression()) {
913 - builder.recordError({
913 + builder.errors.push({
914 reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in NewExpression`,
915 severity: ErrorSeverity.Todo,
916 nodePath: argPath,
@@ -935,7 +935,7 @@ function lowerExpression(
935 const calleePath = expr.get("callee");
936 let hasError = false;
937 if (!calleePath.isExpression()) {
938 - builder.recordError({
938 + builder.errors.push({
939 reason: `(BuildHIR::lowerExpression) Expected Expression, got ${calleePath.type} in CallExpression (v8 intrinsics not supported)`,
940 severity: ErrorSeverity.InvalidInput,
941 nodePath: calleePath,
@@ -950,7 +950,7 @@ function lowerExpression(
950 let args: Place[] = [];
951 for (const argPath of expr.get("arguments")) {
952 if (!argPath.isExpression()) {
953 - builder.recordError({
953 + builder.errors.push({
954 reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in CallExpression`,
955 severity: ErrorSeverity.Todo,
956 nodePath: argPath,
@@ -982,7 +982,7 @@ function lowerExpression(
982 let args: Place[] = [];
983 for (const argPath of expr.get("arguments")) {
984 if (!argPath.isExpression()) {
985 - builder.recordError({
985 + builder.errors.push({
986 reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in CallExpression`,
987 severity: ErrorSeverity.Todo,
988 nodePath: argPath,
@@ -1006,7 +1006,7 @@ function lowerExpression(
1006 const expr = exprPath as NodePath<t.BinaryExpression>;
1007 const leftPath = expr.get("left");
1008 if (!leftPath.isExpression()) {
1009 - builder.recordError({
1009 + builder.errors.push({
1010 reason: `(BuildHIR::lowerExpression) Expected Expression, got ${leftPath.type} lval in BinaryExpression`,
1011 severity: ErrorSeverity.InvalidInput,
1012 nodePath: leftPath,
@@ -1130,7 +1130,7 @@ function lowerExpression(
1130 };
1131 const binaryOperator = operators[operator];
1132 if (binaryOperator == null) {
1133 - builder.recordError({
1133 + builder.errors.push({
1134 reason: `(BuildHIR::lowerExpression) Handle ${operator} operaators in AssignmentExpression`,
1135 severity: ErrorSeverity.Todo,
1136 nodePath: expr.get("operator"),
@@ -1169,7 +1169,7 @@ function lowerExpression(
1169 // Extract the final property to be read from and re-assigned, eg 'c'
1170 const property = leftExpr.get("property");
1171 if (!property.isIdentifier()) {
1172 - builder.recordError({
1172 + builder.errors.push({
1173 reason: `(BuildHIR::lowerExpression) Handle ${property.type} properties in MemberExpression`,
1174 severity: ErrorSeverity.Todo,
1175 nodePath: property,
@@ -1228,7 +1228,7 @@ function lowerExpression(
1228 };
1229 }
1230 default: {
1231 - builder.recordError({
1231 + builder.errors.push({
1232 reason: `(BuildHIR::lowerExpression) Expected Identifier or MemberExpression, got ${expr.type} lval in AssignmentExpression`,
1233 severity: ErrorSeverity.InvalidInput,
1234 nodePath: expr,
@@ -1263,7 +1263,7 @@ function lowerExpression(
1263 let hasError = false;
1264 for (const attribute of opening.get("attributes")) {
1265 if (!attribute.isJSXAttribute()) {
1266 - builder.recordError({
1266 + builder.errors.push({
1267 reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`,
1268 severity: ErrorSeverity.Todo,
1269 nodePath: attribute,
@@ -1273,7 +1273,7 @@ function lowerExpression(
1273 }
1274 const name = attribute.get("name");
1275 if (!name.isJSXIdentifier()) {
1276 - builder.recordError({
1276 + builder.errors.push({
1277 reason: `(BuildHIR::lowerExpression) Handle ${name.type} attribute names in JSXElement`,
1278 severity: ErrorSeverity.Todo,
1279 nodePath: name,
@@ -1287,7 +1287,7 @@ function lowerExpression(
1287 value = lowerExpressionToPlace(builder, valueExpr);
1288 } else {
1289 if (!valueExpr.isJSXExpressionContainer()) {
1290 - builder.recordError({
1290 + builder.errors.push({
1291 reason: `(BuildHIR::lowerExpression) Handle ${valueExpr.type} attribute values in JSXElement`,
1292 severity: ErrorSeverity.Todo,
1293 nodePath: valueExpr,
@@ -1297,7 +1297,7 @@ function lowerExpression(
1297 }
1298 const expression = valueExpr.get("expression");
1299 if (!expression.isExpression()) {
1300 - builder.recordError({
1300 + builder.errors.push({
1301 reason: `(BuildHIR::lowerExpression) Handle ${expression.type} expressions in JSXExpressionContainer within JSXElement`,
1302 severity: ErrorSeverity.Todo,
1303 nodePath: valueExpr,
@@ -1349,7 +1349,9 @@ function lowerExpression(
1349 const lowering = lower(expr);
1350 let loweredFunc: HIRFunction;
1351 if (lowering.isErr()) {
1352 - lowering.unwrapErr().forEach((e) => builder.pushErrorDetail(e));
1352 + lowering
1353 + .unwrapErr()
1354 + .details.forEach((detail) => builder.errors.pushErrorDetail(detail));
1355 return {
1356 kind: "UnsupportedNode",
1357 node: exprNode,
@@ -1362,7 +1364,7 @@ function lowerExpression(
1364 const params: Array<string> = [];
1365 for (const p of expr.get("params")) {
1366 if (!p.isIdentifier()) {
1365 - builder.recordError({
1367 + builder.errors.push({
1368 reason: `(BuildHIR::lowerExpression) Handle ${p.type} params in FunctionExpression`,
1369 severity: ErrorSeverity.Todo,
1370 nodePath: p,
@@ -1388,7 +1390,7 @@ function lowerExpression(
1390 case "TaggedTemplateExpression": {
1391 const expr = exprPath as NodePath<t.TaggedTemplateExpression>;
1392 if (expr.get("quasi").get("expressions").length !== 0) {
1391 - builder.recordError({
1393 + builder.errors.push({
1394 reason: "Unhandled tagged template with interpolations",
1395 severity: ErrorSeverity.Todo,
1396 nodePath: exprPath,
@@ -1402,7 +1404,7 @@ function lowerExpression(
1404
1405 const value = expr.get("quasi").get("quasis").at(0)!.node.value;
1406 if (value.raw !== value.cooked) {
1405 - builder.recordError({
1407 + builder.errors.push({
1408 reason:
1409 "Unhandled tagged template where cooked value is different from raw value",
1410 severity: ErrorSeverity.Todo,
@@ -1428,7 +1430,7 @@ function lowerExpression(
1430 };
1431 }
1432 default: {
1431 - builder.recordError({
1433 + builder.errors.push({
1434 reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`,
1435 severity: ErrorSeverity.Todo,
1436 nodePath: exprPath,
@@ -1448,7 +1450,7 @@ function lowerMemberExpression(
1450 const property = expr.get("property");
1451 if (!expr.node.computed) {
1452 if (!property.isIdentifier()) {
1451 - builder.recordError({
1453 + builder.errors.push({
1454 reason: `(BuildHIR::lowerExpression) Handle ${property.type} property`,
1455 severity: ErrorSeverity.Todo,
1456 nodePath: property,
@@ -1469,7 +1471,7 @@ function lowerMemberExpression(
1471 return { object, property: property.node.name, value };
1472 } else {
1473 if (!property.isExpression()) {
1472 - builder.recordError({
1474 + builder.errors.push({
1475 reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${property.type} property`,
1476 severity: ErrorSeverity.InvalidInput,
1477 nodePath: property,
@@ -1558,7 +1560,7 @@ function lowerJsxElementName(
1560 const exprNode = exprPath.node;
1561 const exprLoc = exprNode.loc ?? GeneratedSource;
1562 if (!exprPath.isJSXIdentifier()) {
1561 - builder.recordError({
1563 + builder.errors.push({
1564 reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`,
1565 severity: ErrorSeverity.Todo,
1566 nodePath: exprPath,
@@ -1619,7 +1621,7 @@ function lowerJsxElement(
1621 } else if (exprPath.isJSXExpressionContainer()) {
1622 const expression = exprPath.get("expression");
1623 if (!expression.isExpression()) {
1622 - builder.recordError({
1624 + builder.errors.push({
1625 reason: `(BuildHIR::lowerJsxElement) Handle ${expression.type} expressions`,
1626 severity: ErrorSeverity.Todo,
1627 nodePath: expression,
@@ -1653,7 +1655,7 @@ function lowerJsxElement(
1655 return place;
1656 } else {
1657 if (!(t.isJSXFragment(exprNode) || t.isJSXSpreadChild(exprNode))) {
1656 - builder.recordError({
1658 + builder.errors.push({
1659 reason: `(BuildHIR::lowerJsxElement) Expected refinement to work, got: ${exprPath.type}`,
1660 severity: ErrorSeverity.InvalidInput,
1661 nodePath: exprPath,
@@ -1780,7 +1782,7 @@ function lowerAssignment(
1782 }
1783 if (!lvalue.node.computed) {
1784 if (!property.isIdentifier()) {
1783 - builder.recordError({
1785 + builder.errors.push({
1786 reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`,
1787 severity: ErrorSeverity.Todo,
1788 nodePath: property,
@@ -1796,7 +1798,7 @@ function lowerAssignment(
1798 };
1799 } else {
1800 if (!property.isExpression()) {
1799 - builder.recordError({
1801 + builder.errors.push({
1802 reason:
1803 "Expected private name to appear as a non-computed property",
1804 severity: ErrorSeverity.InvalidInput,
@@ -1831,7 +1833,7 @@ function lowerAssignment(
1833 continue;
1834 }
1835 if (element.node.type === "RestElement") {
1834 - builder.recordError({
1836 + builder.errors.push({
1837 reason: `(BuildHIR::lowerAssignment) Handle ${element.type} in ArrayPattern`,
1838 severity: ErrorSeverity.Todo,
1839 nodePath: element,
@@ -1879,7 +1881,7 @@ function lowerAssignment(
1881 for (let i = 0; i < properties.length; i++) {
1882 const property = properties[i];
1883 if (!property.isObjectProperty()) {
1882 - builder.recordError({
1884 + builder.errors.push({
1885 reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
1886 severity: ErrorSeverity.Todo,
1887 nodePath: property,
@@ -1889,7 +1891,7 @@ function lowerAssignment(
1891 }
1892 const key = property.get("key");
1893 if (!key.isIdentifier()) {
1892 - builder.recordError({
1894 + builder.errors.push({
1895 reason: `(BuildHIR::lowerAssignment) Handle ${key.type} keys in ObjectPattern`,
1896 severity: ErrorSeverity.Todo,
1897 nodePath: key,
@@ -1899,7 +1901,7 @@ function lowerAssignment(
1901 }
1902 const element = property.get("value");
1903 if (!element.isLVal()) {
1902 - builder.recordError({
1904 + builder.errors.push({
1905 reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
1906 severity: ErrorSeverity.InvalidInput,
1907 nodePath: element,
@@ -1921,7 +1923,7 @@ function lowerAssignment(
1923 : objectPlace;
1924 }
1925 default: {
1924 - builder.recordError({
1926 + builder.errors.push({
1927 reason: `(BuildHIR::lowerAssignment) Handle ${lvaluePath.type} assignments`,
1928 severity: ErrorSeverity.Todo,
1929 nodePath: lvaluePath,
compiler/forget/src/HIR/HIRBuilder.ts
+2 -25
@@ -8,11 +8,7 @@
8 import { NodePath } from "@babel/traverse";
9 import * as t from "@babel/types";
10 import invariant from "invariant";
11 -import {
12 - CompilerErrorDetail,
13 - CompilerErrorOptions,
14 - tryPrintCodeFrame,
15 -} from "../CompilerError";
11 +import { CompilerError } from "../CompilerError";
12 import { logHIR } from "../Utils/logger";
13 import { assertExhaustive } from "../Utils/utils";
14 import { getOrAddGlobal } from "./Globals";
@@ -95,7 +91,7 @@ export default class HIRBuilder {
91 #bindings: Map<string, { node: t.Identifier; identifier: Identifier }> =
92 new Map();
93 #env: Environment;
98 - errors: CompilerErrorDetail[] = [];
94 + errors: CompilerError = new CompilerError();
95
96 get nextIdentifierId() {
97 return this.#env.nextIdentifierId;
@@ -418,25 +414,6 @@ export default class HIRBuilder {
414 }
415 invariant(false, "Expected a loop to be in scope");
416 }
421 -
422 - recordError(options: CompilerErrorOptions): CompilerErrorDetail {
423 - const detail = new CompilerErrorDetail({
424 - reason: options.reason,
425 - severity: options.severity,
426 - codeframe: tryPrintCodeFrame(options),
427 - loc: options.nodePath?.node?.loc ?? null,
428 - });
429 - return this.pushErrorDetail(detail);
430 - }
431 -
432 - pushErrorDetail(detail: CompilerErrorDetail): CompilerErrorDetail {
433 - this.errors.push(detail);
434 - return detail;
435 - }
436 -
437 - hasErrors(): boolean {
438 - return this.errors.length > 0;
439 - }
417 }
418
419 /**