@samitouri / QOS-React-2 / commits / 3f2b346ba0

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 }