Make CompilerError.invariant assert the condition
This allows us to mimic the `invariant` api, which means you can just assert that something holds true after execution proceeds to the next line without throwing
Lauren Tan committed
Jun 22, 2023 at 10:18 UTC
3f2b346ba0df118e2e6a1bbc4387159d82437873
15 files changed
+179
-193
compiler/forget/packages/babel-plugin-react-forget/src/CompilerError.ts
+14
-11
@@ -70,20 +70,23 @@ export class CompilerError extends Error {
70
details: CompilerErrorDetail[] = [];
71
72
static invariant(
73
+ condition: unknown,
74
reason: string,
75
loc: SourceLocation,
76
description: string | null = null
76
- ): never {
77
- const errors = new CompilerError();
78
- errors.pushErrorDetail(
79
- new CompilerErrorDetail({
80
- description,
81
- loc,
82
- reason,
83
- severity: ErrorSeverity.Invariant,
84
- })
85
- );
86
- throw errors;
77
+ ): asserts condition {
78
+ if (!condition) {
79
+ const errors = new CompilerError();
80
+ errors.pushErrorDetail(
81
+ new CompilerErrorDetail({
82
+ description,
83
+ loc,
84
+ reason,
85
+ severity: ErrorSeverity.Invariant,
86
+ })
87
+ );
88
+ throw errors;
89
+ }
90
}
91
92
static todo(
compiler/forget/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts
+16
-18
@@ -53,12 +53,11 @@ export function compileProgram(
53
return;
54
}
55
56
- if (fn.node.id == null) {
57
- CompilerError.invariant(
58
- "FunctionDeclaration must have a name",
59
- fn.node.loc ?? GeneratedSource
60
- );
61
- }
56
+ CompilerError.invariant(
57
+ fn.node.id != null,
58
+ "FunctionDeclaration must have a name",
59
+ fn.node.loc ?? GeneratedSource
60
+ );
61
const originalIdent = fn.node.id;
62
63
if (pass.opts.gating != null) {
@@ -66,12 +65,11 @@ export function compileProgram(
65
fn.node.id = addSuffix(fn.node.id, "_uncompiled");
66
67
// Rename and append compiled function
69
- if (compiled.id == null) {
70
- CompilerError.invariant(
71
- "FunctionDeclaration must produce a name",
72
- fn.node.loc ?? GeneratedSource
73
- );
74
- }
68
+ CompilerError.invariant(
69
+ compiled.id != null,
70
+ "FunctionDeclaration must produce a name",
71
+ fn.node.loc ?? GeneratedSource
72
+ );
73
compiled.id = addSuffix(compiled.id, "_forget");
74
const compiledFn = fn.insertAfter(compiled)[0];
75
compiledFn.skip();
@@ -409,12 +407,12 @@ function buildBlockStatement(
407
return wrappedBody.node;
408
}
409
412
- if (!body.isBlockStatement()) {
413
- CompilerError.invariant(
414
- "Body must be a BlockStatement",
415
- body.node.loc ?? GeneratedSource
416
- );
417
- }
410
+ CompilerError.invariant(
411
+ body.isBlockStatement(),
412
+ "Body must be a BlockStatement",
413
+ body.node.loc ?? GeneratedSource
414
+ );
415
+
416
return body.node;
417
}
418
compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertConsistentIdentifiers.ts
+14
-15
@@ -35,20 +35,18 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
35
}
36
}
37
for (const instr of block.instructions) {
38
- if (instr.lvalue.identifier.name !== null) {
39
- CompilerError.invariant(
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(
47
- `Expected lvalues to be assigned exactly once`,
48
- instr.lvalue.loc,
49
- `Found duplicate assignment of '${printPlace(instr.lvalue)}'`
50
- );
51
- }
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
+ );
50
assignments.add(instr.lvalue.identifier.id);
51
for (const operand of eachInstructionLValue(instr)) {
52
validate(identifiers, operand.identifier, operand.loc);
@@ -73,8 +71,9 @@ function validate(
71
const previous = identifiers.get(identifier.id);
72
if (previous === undefined) {
73
identifiers.set(identifier.id, identifier);
76
- } else if (identifier !== previous) {
74
+ } else {
75
CompilerError.invariant(
76
+ identifier === previous,
77
`Duplicate identifier object`,
78
loc ?? GeneratedSource,
79
`Found duplicate identifier object for id ${identifier.id}`
compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertTerminalSuccessorsExist.ts
+8
-9
@@ -13,15 +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
- 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
22
- )}'`
23
- );
24
- }
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(
21
+ block.terminal
22
+ )}'`
23
+ );
24
return successor;
25
});
26
}
compiler/forget/packages/babel-plugin-react-forget/src/HIR/FindContextIdentifiers.ts
+10
-12
@@ -137,20 +137,18 @@ function handleAssignment(
137
for (const property of path.get("properties")) {
138
if (property.isObjectProperty()) {
139
const valuePath = property.get("value");
140
- if (!valuePath.isLVal()) {
141
- CompilerError.invariant(
142
- `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
143
- valuePath.node.loc ?? GeneratedSource
144
- );
145
- }
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
+ );
145
handleAssignment(reassigned, valuePath);
146
} else {
148
- if (!property.isRestElement()) {
149
- CompilerError.invariant(
150
- `[FindContextIdentifiers] Invalid assumptions for babel types.`,
151
- property.node.loc ?? GeneratedSource
152
- );
153
- }
147
+ CompilerError.invariant(
148
+ property.isRestElement(),
149
+ `[FindContextIdentifiers] Invalid assumptions for babel types.`,
150
+ property.node.loc ?? GeneratedSource
151
+ );
152
handleAssignment(reassigned, property);
153
}
154
}
compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+1
-1
@@ -884,7 +884,7 @@ export function isMutableEffect(
884
}
885
886
case Effect.Unknown: {
887
- CompilerError.invariant("Unexpected unknown effect", location);
887
+ CompilerError.invariant(false, "Unexpected unknown effect", location);
888
}
889
case Effect.Read:
890
case Effect.Freeze: {
compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts
+11
-13
@@ -215,13 +215,12 @@ class InferenceState {
215
const kind = this.#values.get(value)!;
216
mergedKind = mergedKind !== null ? mergeValues(mergedKind, kind) : kind;
217
}
218
- if (mergedKind === null) {
219
- CompilerError.invariant(
220
- `InferReferenceEffects::kind: Expected at least one value`,
221
- place.loc,
222
- `No value found at '${printPlace(place)}'`
223
- );
224
- }
218
+ CompilerError.invariant(
219
+ mergedKind !== null,
220
+ `InferReferenceEffects::kind: Expected at least one value`,
221
+ place.loc,
222
+ `No value found at '${printPlace(place)}'`
223
+ );
224
return mergedKind;
225
}
226
@@ -268,12 +267,11 @@ class InferenceState {
267
reference(place: Place, effectKind: Effect): void {
268
const values = this.#variables.get(place.identifier.id);
269
if (values === undefined) {
271
- if (effectKind === Effect.Store) {
272
- CompilerError.invariant(
273
- "[InferReferenceEffects] Unhandled store reference effect",
274
- place.loc
275
- );
276
- }
270
+ CompilerError.invariant(
271
+ effectKind !== Effect.Store,
272
+ "[InferReferenceEffects] Unhandled store reference effect",
273
+ place.loc
274
+ );
275
place.effect =
276
effectKind === Effect.ConditionallyMutate
277
? Effect.ConditionallyMutate
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+5
-6
@@ -543,12 +543,11 @@ class Driver {
543
let block: ReactiveBlock;
544
if (this.cx.isScheduled(terminal.block)) {
545
const break_ = this.visitBreak(terminal.block, null);
546
- if (break_ === null) {
547
- CompilerError.invariant(
548
- "Expected a break target for a label whose body is already scheduled",
549
- terminal.loc
550
- );
551
- }
546
+ CompilerError.invariant(
547
+ break_ !== null,
548
+ "Expected a break target for a label whose body is already scheduled",
549
+ terminal.loc
550
+ );
551
block = [break_];
552
} else {
553
block = this.traverseBlock(this.cx.ir.blocks.get(terminal.block)!);
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+27
-26
@@ -345,13 +345,12 @@ function codegenTerminal(
345
);
346
}
347
case "for-of": {
348
- if (terminal.init.kind !== "SequenceExpression") {
349
- CompilerError.invariant(
350
- `Expected a sequence expression init for ForOf`,
351
- terminal.init.loc,
352
- `Got '${terminal.init.kind}' expression instead`
353
- );
354
- }
348
+ CompilerError.invariant(
349
+ terminal.init.kind === "SequenceExpression",
350
+ `Expected a sequence expression init for ForOf`,
351
+ terminal.init.loc,
352
+ `Got '${terminal.init.kind}' expression instead`
353
+ );
354
if (terminal.init.instructions.length !== 2) {
355
CompilerError.todo(
356
"Support non-trivial ForOf inits",
@@ -372,6 +371,7 @@ function codegenTerminal(
371
}
372
default:
373
CompilerError.invariant(
374
+ false,
375
`Expected a StoreLocal or Destructure to be assigned to the collection`,
376
iterableItem.value.loc,
377
`Found ${iterableItem.value.kind}`
@@ -387,6 +387,7 @@ function codegenTerminal(
387
break;
388
case InstructionKind.Reassign:
389
CompilerError.invariant(
390
+ false,
391
"Destructure should never be Reassign as it would be an Object/ArrayPattern",
392
iterableItem.loc
393
);
@@ -510,6 +511,7 @@ function codegenInstructionNullable(
511
}
512
if (hasReasign && hasDeclaration) {
513
CompilerError.invariant(
514
+ false,
515
"Encountered a destructuring operation where some identifiers are already declared (reassignments) but others are not (declarations)",
516
instr.loc
517
);
@@ -520,23 +522,21 @@ function codegenInstructionNullable(
522
}
523
switch (kind) {
524
case InstructionKind.Const: {
523
- if (instr.lvalue !== null) {
524
- CompilerError.invariant(
525
- `Const declaration cannot be referenced as an expression`,
526
- instr.value.loc
527
- );
528
- }
525
+ CompilerError.invariant(
526
+ instr.lvalue === null,
527
+ `Const declaration cannot be referenced as an expression`,
528
+ instr.value.loc
529
+ );
530
return createVariableDeclaration(instr.loc, "const", [
531
t.variableDeclarator(codegenLValue(lvalue), value),
532
]);
533
}
534
case InstructionKind.Let: {
534
- if (instr.lvalue !== null) {
535
- CompilerError.invariant(
536
- `Const declaration cannot be referenced as an expression`,
537
- instr.value.loc
538
- );
539
- }
535
+ CompilerError.invariant(
536
+ instr.lvalue === null,
537
+ `Const declaration cannot be referenced as an expression`,
538
+ instr.value.loc
539
+ );
540
return createVariableDeclaration(instr.loc, "let", [
541
t.variableDeclarator(codegenLValue(lvalue), value),
542
]);
@@ -768,6 +768,7 @@ function codegenInstructionValue(
768
}
769
default: {
770
CompilerError.invariant(
771
+ false,
772
"Expected an optional value to resolve to a call expression or member expression",
773
instrValue.loc,
774
`Got a '${optionalValue.type}'`
@@ -1097,6 +1098,7 @@ function codegenInstructionValue(
1098
case "StoreLocal":
1099
case "StoreContext": {
1100
CompilerError.invariant(
1101
+ false,
1102
`Unexpected ${instrValue.kind} in codegenInstructionValue`,
1103
instrValue.loc
1104
);
@@ -1276,13 +1278,12 @@ function codegenPlace(cx: Context, place: Place): t.Expression {
1278
if (tmp != null) {
1279
return tmp;
1280
}
1279
- if (place.identifier.name === null && tmp === undefined) {
1280
- CompilerError.invariant(
1281
- `[Codegen] No value found for temporary`,
1282
- place.loc,
1283
- `Value for '${printPlace(place)}' was not set in the codegen context`
1284
- );
1285
- }
1281
+ CompilerError.invariant(
1282
+ place.identifier.name !== null || tmp !== undefined,
1283
+ `[Codegen] No value found for temporary`,
1284
+ place.loc,
1285
+ `Value for '${printPlace(place)}' was not set in the codegen context`
1286
+ );
1287
const identifier = convertIdentifier(place.identifier);
1288
identifier.loc = place.loc as any;
1289
return identifier;
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/InferReactiveIdentifiers.ts
+5
-1
@@ -105,7 +105,11 @@ class Visitor extends ReactiveFunctionVisitor<State> {
105
break;
106
}
107
case Effect.Unknown: {
108
- CompilerError.invariant("Unexpected unknown effect", operand.loc);
108
+ CompilerError.invariant(
109
+ false,
110
+ "Unexpected unknown effect",
111
+ operand.loc
112
+ );
113
}
114
default: {
115
assertExhaustive(
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PromoteUsedTemporaries.ts
+5
-6
@@ -75,12 +75,11 @@ export function promoteUsedTemporaries(fn: ReactiveFunction): void {
75
}
76
77
function promoteTemporary(identifier: Identifier, state: VisitorState): void {
78
- if (identifier.name !== null) {
79
- CompilerError.invariant(
80
- "promoteTemporary: Expected to be called only for temporary variables",
81
- GeneratedSource
82
- );
83
- }
78
+ CompilerError.invariant(
79
+ identifier.name === null,
80
+ "promoteTemporary: Expected to be called only for temporary variables",
81
+ GeneratedSource
82
+ );
83
if (state.tags.has(identifier.id)) {
84
identifier.name = `T${state.nextId++}`;
85
} else {
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+13
-15
@@ -459,22 +459,20 @@ 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
- if (inner.kind === "SequenceExpression") {
463
- // Instructions are the unconditionally executed portion before the `?`
464
- for (const instr of inner.instructions) {
465
- this.visitInstruction(instr, context);
466
- }
467
- // The final value is the conditional portion following the `?`
468
- context.enterConditional(() => {
469
- this.visitReactiveValue(context, id, inner.value);
470
- });
471
- } else {
472
- CompilerError.invariant(
473
- "Expected OptionalExpression value to be a SequenceExpression",
474
- value.loc,
475
- `Found a '${value.kind}'`
476
- );
462
+ CompilerError.invariant(
463
+ inner.kind === "SequenceExpression",
464
+ "Expected OptionalExpression value to be a SequenceExpression",
465
+ value.loc,
466
+ `Found a '${value.kind}'`
467
+ );
468
+ // Instructions are the unconditionally executed portion before the `?`
469
+ for (const instr of inner.instructions) {
470
+ this.visitInstruction(instr, context);
471
}
472
+ // The final value is the conditional portion following the `?`
473
+ context.enterConditional(() => {
474
+ this.visitReactiveValue(context, id, inner.value);
475
+ });
476
break;
477
}
478
case "LogicalExpression": {
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
+1
-1
@@ -601,7 +601,7 @@ function computeMemoizationInputs(
601
};
602
}
603
case "UnsupportedNode": {
604
- CompilerError.invariant(`Unexpected unsupported node`, value.loc);
604
+ CompilerError.invariant(false, `Unexpected unsupported node`, value.loc);
605
}
606
default: {
607
assertExhaustive(value, `Unexpected value kind '${(value as any).kind}'`);
compiler/forget/packages/babel-plugin-react-forget/src/SSA/EnterSSA.ts
+11
-13
@@ -96,13 +96,12 @@ class SSABuilder {
96
97
definePlace(oldPlace: Place): Place {
98
const oldId = oldPlace.identifier;
99
- if (this.#unknown.has(oldId)) {
100
- CompilerError.invariant(
101
- `EnterSSA: Expected identifier to be defined before being used`,
102
- oldPlace.loc,
103
- `Identifier ${printIdentifier(oldId)} is undefined`
104
- );
105
- }
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
+ );
105
106
// Do not redefine context references.
107
if (this.#context.has(oldId)) {
@@ -249,12 +248,11 @@ function enterSSAImpl(
248
if (blockId === rootEntry) {
249
// NOTE: func.context should be empty for the root function
250
if (func.env.enableOptimizeFunctionExpressions) {
252
- if (func.context.length !== 0) {
253
- CompilerError.invariant(
254
- `Expected function context to be empty for outer function declarations`,
255
- func.loc
256
- );
257
- }
251
+ CompilerError.invariant(
252
+ func.context.length === 0,
253
+ `Expected function context to be empty for outer function declarations`,
254
+ func.loc
255
+ );
256
} else {
257
func.context = func.context.map((p) => builder.defineContext(p));
258
}
compiler/forget/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts
+38
-46
@@ -132,13 +132,12 @@ export function leaveSSA(fn: HIRFunction): void {
132
if (value.kind === "DeclareLocal") {
133
const name = value.lvalue.place.identifier.name;
134
if (name !== null) {
135
- if (declarations.has(name)) {
136
- CompilerError.invariant(
137
- `Unexpected duplicate declaration`,
138
- value.lvalue.place.loc,
139
- `Found duplicate declaration for '${name}'`
140
- );
141
- }
135
+ CompilerError.invariant(
136
+ !declarations.has(name),
137
+ `Unexpected duplicate declaration`,
138
+ value.lvalue.place.loc,
139
+ `Found duplicate declaration for '${name}'`
140
+ );
141
declarations.set(name, {
142
lvalue: value.lvalue,
143
place: value.lvalue.place,
@@ -153,12 +152,11 @@ export function leaveSSA(fn: HIRFunction): void {
152
originalLVal === undefined ||
153
originalLVal.lvalue === value.lvalue // in case this was pre-declared for the `for` initializer
154
) {
156
- if (originalLVal === undefined && block.kind !== "block") {
157
- CompilerError.invariant(
158
- `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
159
- value.lvalue.place.loc
160
- );
161
- }
155
+ CompilerError.invariant(
156
+ originalLVal !== undefined || block.kind === "block",
157
+ `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
158
+ value.lvalue.place.loc
159
+ );
160
declarations.set(value.lvalue.place.identifier.name, {
161
lvalue: value.lvalue,
162
place: value.lvalue.place,
@@ -177,15 +175,12 @@ export function leaveSSA(fn: HIRFunction): void {
175
let kind: InstructionKind | null = null;
176
for (const place of eachPatternOperand(value.lvalue.pattern)) {
177
if (place.identifier.name == null) {
180
- if (kind !== null && kind !== InstructionKind.Const) {
181
- CompilerError.invariant(
182
- `Expected consistent kind for destructuring`,
183
- place.loc,
184
- `other places were '${kind}' but '${printPlace(
185
- place
186
- )}' is const`
187
- );
188
- }
178
+ CompilerError.invariant(
179
+ kind === null || kind === InstructionKind.Const,
180
+ `Expected consistent kind for destructuring`,
181
+ place.loc,
182
+ `other places were '${kind}' but '${printPlace(place)}' is const`
183
+ );
184
kind = InstructionKind.Const;
185
} else {
186
const originalLVal = declarations.get(place.identifier.name);
@@ -193,36 +188,33 @@ export function leaveSSA(fn: HIRFunction): void {
188
originalLVal === undefined ||
189
originalLVal.lvalue === value.lvalue
190
) {
196
- if (originalLVal === undefined && block.kind === "value") {
197
- CompilerError.invariant(
198
- `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
199
- place.loc
200
- );
201
- }
191
+ CompilerError.invariant(
192
+ originalLVal !== undefined || block.kind !== "value",
193
+ `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
194
+ place.loc
195
+ );
196
declarations.set(place.identifier.name, {
197
lvalue: value.lvalue,
198
place,
199
});
206
- if (kind !== null && kind !== InstructionKind.Const) {
207
- CompilerError.invariant(
208
- `Expected consistent kind for destructuring`,
209
- place.loc,
210
- `Other places were '${kind}' but '${printPlace(
211
- place
212
- )}' is const`
213
- );
214
- }
200
+ CompilerError.invariant(
201
+ kind === null || kind === InstructionKind.Const,
202
+ `Expected consistent kind for destructuring`,
203
+ place.loc,
204
+ `Other places were '${kind}' but '${printPlace(
205
+ place
206
+ )}' is const`
207
+ );
208
kind = InstructionKind.Const;
209
} else {
217
- if (kind !== null && kind !== InstructionKind.Reassign) {
218
- CompilerError.invariant(
219
- `Expected consistent kind for destructuring`,
220
- place.loc,
221
- `Other places were '${kind}' but '${printPlace(
222
- place
223
- )}' is reassigned`
224
- );
225
- }
210
+ CompilerError.invariant(
211
+ kind === null || kind === InstructionKind.Reassign,
212
+ `Expected consistent kind for destructuring`,
213
+ place.loc,
214
+ `Other places were '${kind}' but '${printPlace(
215
+ place
216
+ )}' is reassigned`
217
+ );
218
kind = InstructionKind.Reassign;
219
originalLVal.lvalue.kind = InstructionKind.Let;
220
}