Make CompilerError.reason a static string
While running the latest Forget build on www I noticed that a lot of the bailouts were special-cases where we used interpolation in the error `reason` string to provide more context for debugging. This is a pretty cool result, because it means that we actually support nearly all the common syntax (at least based on a sample of the codebase). But it makes our tools for aggregating errors break down a bit. This PR adds a new, nullable `description` property to CompilerErrorDetail, and manually updates to ensure that we always pass a static `reason` and only use interpolation in the `description`. This will allow our aggregation tools to group by the reason.
Joe Savona committed
Apr 6, 2023 at 08:51 UTC
9c1f8a962c1f15935749e604cac2d095c952af04
11 files changed
+74
-44
compiler/forget/src/CompilerError.ts
+27
-4
@@ -19,6 +19,7 @@ export enum ErrorSeverity {
19
20
export type CompilerErrorOptions = {
21
reason: string;
22
+ description?: string | null | undefined;
23
severity: ErrorSeverity;
24
nodePath: AnyNodePath | null;
25
};
@@ -64,7 +65,8 @@ export function tryPrintCodeFrame(
65
try {
66
return options.nodePath
67
.buildCodeFrameError(
67
- options.reason,
68
+ options.reason +
69
+ (options.description != null ? `. ${options.description}` : ""),
70
mapSeverityToErrorCtor(options.severity)
71
)
72
.toString();
@@ -79,12 +81,14 @@ export function tryPrintCodeFrame(
81
*/
82
export class CompilerErrorDetail {
83
reason: string;
84
+ description: string | null;
85
severity: ErrorSeverity;
86
codeframe: string | null;
87
loc: BabelSourceLocation | null;
88
89
constructor(options: CompilerErrorDetailOptions) {
90
this.reason = options.reason;
91
+ this.description = options.description;
92
this.severity = options.severity;
93
this.codeframe = options.codeframe;
94
this.loc = options.loc;
@@ -95,6 +99,9 @@ export class CompilerErrorDetail {
99
return this.codeframe;
100
}
101
const buffer = [`${this.severity}: ${this.reason}`];
102
+ if (this.description !== null) {
103
+ buffer.push(`. ${this.description}`);
104
+ }
105
if (this.loc != null) {
106
buffer.push(` (${this.loc.start.line}:${this.loc.end.line})`);
107
}
@@ -109,11 +116,16 @@ export class CompilerErrorDetail {
116
export class CompilerError extends Error {
117
details: CompilerErrorDetail[] = [];
118
112
- static invariant(reason: string, loc: SourceLocation): never {
119
+ static invariant(
120
+ reason: string,
121
+ loc: SourceLocation,
122
+ description: string | null = null
123
+ ): never {
124
const errors = new CompilerError();
125
errors.pushErrorDetail(
126
new CompilerErrorDetail({
127
codeframe: null,
128
+ description,
129
loc: typeof loc === "symbol" ? null : loc,
130
reason,
131
severity: ErrorSeverity.Invariant,
@@ -122,11 +134,16 @@ export class CompilerError extends Error {
134
throw errors;
135
}
136
125
- static todo(reason: string, loc: SourceLocation): never {
137
+ static todo(
138
+ reason: string,
139
+ loc: SourceLocation,
140
+ description: string | null = null
141
+ ): never {
142
const errors = new CompilerError();
143
errors.pushErrorDetail(
144
new CompilerErrorDetail({
145
codeframe: null,
146
+ description,
147
loc: typeof loc === "symbol" ? null : loc,
148
reason,
149
severity: ErrorSeverity.Todo,
@@ -135,11 +152,16 @@ export class CompilerError extends Error {
152
throw errors;
153
}
154
138
- static invalidInput(reason: string, loc: SourceLocation): never {
155
+ static invalidInput(
156
+ reason: string,
157
+ loc: SourceLocation,
158
+ description: string | null = null
159
+ ): never {
160
const errors = new CompilerError();
161
errors.pushErrorDetail(
162
new CompilerErrorDetail({
163
codeframe: null,
164
+ description,
165
loc: typeof loc === "symbol" ? null : loc,
166
reason,
167
severity: ErrorSeverity.InvalidInput,
@@ -165,6 +187,7 @@ export class CompilerError extends Error {
187
push(options: CompilerErrorOptions): CompilerErrorDetail {
188
const detail = new CompilerErrorDetail({
189
reason: options.reason,
190
+ description: options.description ?? null,
191
severity: options.severity,
192
codeframe: tryPrintCodeFrame(options),
193
loc: options.nodePath?.node?.loc ?? null,
compiler/forget/src/HIR/ValidateConsistentIdentifiers.ts
+9
-8
@@ -37,16 +37,16 @@ export function validateConsistentIdentifiers(fn: HIRFunction): void {
37
for (const instr of block.instructions) {
38
if (instr.lvalue.identifier.name !== null) {
39
CompilerError.invariant(
40
- `Expected all lvalues to be temporaries, found '${instr.lvalue.identifier.name}'`,
41
- instr.lvalue.loc
40
+ `Expected all lvalues to be temporaries`,
41
+ instr.lvalue.loc,
42
+ `Found named lvalue '${instr.lvalue.identifier.name}'`
43
);
44
}
45
if (assignments.has(instr.lvalue.identifier.id)) {
46
CompilerError.invariant(
46
- `Expected lvalues to be assigned exactly once, found duplicate assignment of '${printPlace(
47
- instr.lvalue
48
- )}'`,
49
- instr.lvalue.loc
47
+ `Expected lvalues to be assigned exactly once`,
48
+ instr.lvalue.loc,
49
+ `Found duplicate assignment of '${printPlace(instr.lvalue)}'`
50
);
51
}
52
assignments.add(instr.lvalue.identifier.id);
@@ -75,8 +75,9 @@ function validate(
75
identifiers.set(identifier.id, identifier);
76
} else if (identifier !== previous) {
77
CompilerError.invariant(
78
- `Duplicate identifier for id ${identifier.id}`,
79
- loc ?? GeneratedSource
78
+ `Duplicate identifier object`,
79
+ loc ?? GeneratedSource,
80
+ `Found duplicate identifier object for id ${identifier.id}`
81
);
82
}
83
}
compiler/forget/src/HIR/ValidateTerminalSuccessors.ts
+3
-2
@@ -15,10 +15,11 @@ export function validateTerminalSuccessors(fn: HIRFunction): void {
15
mapTerminalSuccessors(block.terminal, (successor) => {
16
if (!fn.body.blocks.has(successor)) {
17
CompilerError.invariant(
18
+ `Terminal successor references unknown block`,
19
+ (block.terminal as any).loc ?? GeneratedSource,
20
`Block bb${successor} does not exist for terminal '${printTerminal(
21
block.terminal
20
- )}'`,
21
- (block.terminal as any).loc ?? GeneratedSource
22
+ )}'`
23
);
24
}
25
return successor;
compiler/forget/src/Inference/InferReferenceEffects.ts
+7
-7
@@ -219,10 +219,9 @@ class InferenceState {
219
}
220
if (mergedKind === null) {
221
CompilerError.invariant(
222
- `InferReferenceEffects::kind: Expected at least one value at '${printPlace(
223
- place
224
- )}'`,
225
- place.loc
222
+ `InferReferenceEffects::kind: Expected at least one value`,
223
+ place.loc,
224
+ `No value found at '${printPlace(place)}'`
225
);
226
}
227
return mergedKind;
@@ -312,10 +311,11 @@ class InferenceState {
311
} else {
312
if (shouldError) {
313
CompilerError.invalidInput(
315
- `InferReferenceEffects: inferred mutation of known immutable value ${printIdentifier(
314
+ `InferReferenceEffects: inferred mutation of known immutable value`,
315
+ place.loc,
316
+ `Found mutation of ${printIdentifier(
317
place.identifier
317
- )}${printType(place.identifier.type)} (${valueKind})`,
318
- place.loc
318
+ )}${printType(place.identifier.type)} (${valueKind})`
319
);
320
}
321
effect = Effect.Read;
compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+6
-4
@@ -318,8 +318,9 @@ function codegenTerminal(
318
case "for-of": {
319
if (terminal.init.kind !== "SequenceExpression") {
320
CompilerError.invariant(
321
- `Expected a sequence expression init for ForOf, got: ${terminal.init.kind}`,
322
- terminal.init.loc
321
+ `Expected a sequence expression init for ForOf`,
322
+ terminal.init.loc,
323
+ `Got '${terminal.init.kind}' expression instead`
324
);
325
}
326
if (terminal.init.instructions.length !== 2) {
@@ -342,8 +343,9 @@ function codegenTerminal(
343
}
344
default:
345
CompilerError.invariant(
345
- `Expected a StoreLocal or Destructure to be assigned to the collection, got: ${iterableItem.value.kind}`,
346
- iterableItem.value.loc
346
+ `Expected a StoreLocal or Destructure to be assigned to the collection`,
347
+ iterableItem.value.loc,
348
+ `Found ${iterableItem.value.kind}`
349
);
350
}
351
let varDeclKind: "const" | "let";
compiler/forget/src/SSA/EnterSSA.ts
+3
-4
@@ -86,10 +86,9 @@ class SSABuilder {
86
const oldId = oldPlace.identifier;
87
if (this.#unknown.has(oldId)) {
88
CompilerError.invariant(
89
- `identifier ${printIdentifier(
90
- oldId
91
- )} should have been defined before use`,
92
- oldPlace.loc
89
+ `EnterSSA: Expected identifier to be defined before being used`,
90
+ oldPlace.loc,
91
+ `Identifier ${printIdentifier(oldId)} is undfined`
92
);
93
}
94
compiler/forget/src/SSA/LeaveSSA.ts
+15
-11
@@ -133,8 +133,9 @@ export function leaveSSA(fn: HIRFunction): void {
133
if (name !== null) {
134
if (declarations.has(name)) {
135
CompilerError.invariant(
136
- `Unexpected duplicate declaration of '${name}'`,
137
- value.lvalue.place.loc
136
+ `Unexpected duplicate declaration`,
137
+ value.lvalue.place.loc,
138
+ `Found duplicate declaration for '${name}'`
139
);
140
}
141
declarations.set(name, {
@@ -177,10 +178,11 @@ export function leaveSSA(fn: HIRFunction): void {
178
if (place.identifier.name == null) {
179
if (kind !== null && kind !== InstructionKind.Const) {
180
CompilerError.invariant(
180
- `Expected consistent kind for destructuring, other places were '${kind}' but '${printPlace(
181
+ `Expected consistent kind for destructuring`,
182
+ place.loc,
183
+ `other places were '${kind}' but '${printPlace(
184
place
182
- )}' is const`,
183
- place.loc
185
+ )}' is const`
186
);
187
}
188
kind = InstructionKind.Const;
@@ -202,20 +204,22 @@ export function leaveSSA(fn: HIRFunction): void {
204
});
205
if (kind !== null && kind !== InstructionKind.Const) {
206
CompilerError.invariant(
205
- `Expected consistent kind for destructuring, other places were '${kind}' but '${printPlace(
207
+ `Expected consistent kind for destructuring`,
208
+ place.loc,
209
+ `Other places were '${kind}' but '${printPlace(
210
place
207
- )}' is const`,
208
- place.loc
211
+ )}' is const`
212
);
213
}
214
kind = InstructionKind.Const;
215
} else {
216
if (kind !== null && kind !== InstructionKind.Reassign) {
217
CompilerError.invariant(
215
- `Expected consistent kind for destructuring, other places were '${kind}' but '${printPlace(
218
+ `Expected consistent kind for destructuring`,
219
+ place.loc,
220
+ `Other places were '${kind}' but '${printPlace(
221
place
217
- )}' is reassigned`,
218
- place.loc
222
+ )}' is reassigned`
223
);
224
}
225
kind = InstructionKind.Reassign;
compiler/forget/src/__tests__/fixtures/compiler/error.hoisted-function-declaration.expect.md
+1
-1
@@ -17,7 +17,7 @@ function component(a) {
17
## Error
18
19
```
20
-[ReactForget] Invariant: identifier x$6 should have been defined before use (4:4)
20
+[ReactForget] Invariant: EnterSSA: Expected identifier to be defined before being used. Identifier x$6 is undfined (4:4)
21
```
22
23
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/error.mutate-after-aliased-freeze.expect.md
+1
-1
@@ -25,7 +25,7 @@ function Component(props) {
25
## Error
26
27
```
28
-[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value $42:TObject<BuiltInArray> (frozen) (13:13)
28
+[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value. Found mutation of $42:TObject<BuiltInArray> (frozen) (13:13)
29
```
30
31
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/error.mutate-after-freeze.expect.md
+1
-1
@@ -19,7 +19,7 @@ function Component(props) {
19
## Error
20
21
```
22
-[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value $25:TObject<BuiltInArray> (frozen) (7:7)
22
+[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value. Found mutation of $25:TObject<BuiltInArray> (frozen) (7:7)
23
```
24
25
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/error.mutate-captured-arg-separately.expect.md
+1
-1
@@ -19,7 +19,7 @@ function component(a) {
19
## Error
20
21
```
22
-[ReactForget] Invariant: identifier x$2 should have been defined before use (7:7)
22
+[ReactForget] Invariant: EnterSSA: Expected identifier to be defined before being used. Identifier x$2 is undfined (7:7)
23
```
24
25
\ No newline at end of file