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
/**