ReactiveFunctionTransform allows replacing values w transformValue
Previously the only way to replace a value was to override transformInstruction and transformTerminal, and to be careful to find nested values. This PR adds `ReactiveFunctionTransform#transformValue()` which allows returning an optional new value, which if present will replace the value in whatever context it appeared. ReactiveFunctionTransform now reimplements all the methods necessary to replace any value anywhere in the AST. See the next PR for an example usage.
Joe Savona committed
Dec 11, 2023 at 11:34 UTC
3f4a9bd330a3500824cd223bd9a8984b60153eb9
2 files changed
+208
-1
compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts
+7
-1
@@ -339,7 +339,13 @@ export function mapInstructionOperands(
339
instr: Instruction,
340
fn: (place: Place) => Place
341
): void {
342
- const instrValue = instr.value;
342
+ mapInstructionValueOperands(instr.value, fn);
343
+}
344
+
345
+export function mapInstructionValueOperands(
346
+ instrValue: InstructionValue,
347
+ fn: (place: Place) => Place
348
+): void {
349
switch (instrValue.kind) {
350
case "BinaryExpression": {
351
instrValue.left = fn(instrValue.left);
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/visitors.ts
+201
@@ -206,6 +206,10 @@ export class ReactiveFunctionVisitor<TState = void> {
206
}
207
}
208
209
+export type TransformedValue =
210
+ | { kind: "keep" }
211
+ | { kind: "replace"; value: ReactiveValue };
212
+
213
export type Transformed<T> =
214
| { kind: "remove" }
215
| { kind: "keep" }
@@ -294,6 +298,203 @@ export class ReactiveFunctionTransform<
298
this.visitScope(scope, state);
299
return { kind: "keep" };
300
}
301
+
302
+ transformValue(
303
+ id: InstructionId,
304
+ value: ReactiveValue,
305
+ state: TState
306
+ ): TransformedValue {
307
+ this.visitValue(id, value, state);
308
+ return { kind: "keep" };
309
+ }
310
+
311
+ override traverseValue(
312
+ id: InstructionId,
313
+ value: ReactiveValue,
314
+ state: TState
315
+ ): void {
316
+ switch (value.kind) {
317
+ case "OptionalExpression": {
318
+ const nextValue = this.transformValue(id, value.value, state);
319
+ if (nextValue.kind === "replace") {
320
+ value.value = nextValue.value;
321
+ }
322
+ break;
323
+ }
324
+ case "LogicalExpression": {
325
+ const left = this.transformValue(id, value.left, state);
326
+ if (left.kind === "replace") {
327
+ value.left = left.value;
328
+ }
329
+ const right = this.transformValue(id, value.right, state);
330
+ if (right.kind === "replace") {
331
+ value.right = right.value;
332
+ }
333
+ break;
334
+ }
335
+ case "ConditionalExpression": {
336
+ const test = this.transformValue(id, value.test, state);
337
+ if (test.kind === "replace") {
338
+ value.test = test.value;
339
+ }
340
+ const consequent = this.transformValue(id, value.consequent, state);
341
+ if (consequent.kind === "replace") {
342
+ value.consequent = consequent.value;
343
+ }
344
+ const alternate = this.transformValue(id, value.alternate, state);
345
+ if (alternate.kind === "replace") {
346
+ value.alternate = alternate.value;
347
+ }
348
+ break;
349
+ }
350
+ case "SequenceExpression": {
351
+ for (const instr of value.instructions) {
352
+ this.visitInstruction(instr, state);
353
+ }
354
+ const nextValue = this.transformValue(value.id, value.value, state);
355
+ if (nextValue.kind === "replace") {
356
+ value.value = nextValue.value;
357
+ }
358
+ break;
359
+ }
360
+ default: {
361
+ for (const place of eachReactiveValueOperand(value)) {
362
+ this.visitPlace(id, place, state);
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+ override traverseInstruction(
369
+ instruction: ReactiveInstruction,
370
+ state: TState
371
+ ): void {
372
+ this.visitID(instruction.id, state);
373
+ for (const operand of eachInstructionLValue(instruction)) {
374
+ this.visitLValue(instruction.id, operand, state);
375
+ }
376
+ const nextValue = this.transformValue(
377
+ instruction.id,
378
+ instruction.value,
379
+ state
380
+ );
381
+ if (nextValue.kind === "replace") {
382
+ instruction.value = nextValue.value;
383
+ }
384
+ }
385
+
386
+ override traverseTerminal(
387
+ stmt: ReactiveTerminalStatement,
388
+ state: TState
389
+ ): void {
390
+ const { terminal } = stmt;
391
+ if (terminal.id !== null) {
392
+ this.visitID(terminal.id, state);
393
+ }
394
+ switch (terminal.kind) {
395
+ case "break":
396
+ case "continue": {
397
+ break;
398
+ }
399
+ case "return": {
400
+ this.visitPlace(terminal.id, terminal.value, state);
401
+ break;
402
+ }
403
+ case "throw": {
404
+ this.visitPlace(terminal.id, terminal.value, state);
405
+ break;
406
+ }
407
+ case "for": {
408
+ const init = this.transformValue(terminal.id, terminal.init, state);
409
+ if (init.kind === "replace") {
410
+ terminal.init = init.value;
411
+ }
412
+ const test = this.transformValue(terminal.id, terminal.test, state);
413
+ if (test.kind === "replace") {
414
+ terminal.test = test.value;
415
+ }
416
+ if (terminal.update !== null) {
417
+ const update = this.transformValue(
418
+ terminal.id,
419
+ terminal.update,
420
+ state
421
+ );
422
+ if (update.kind === "replace") {
423
+ terminal.update = update.value;
424
+ }
425
+ }
426
+ this.visitBlock(terminal.loop, state);
427
+ break;
428
+ }
429
+ case "for-of": {
430
+ const init = this.transformValue(terminal.id, terminal.init, state);
431
+ if (init.kind === "replace") {
432
+ terminal.init = init.value;
433
+ }
434
+ this.visitBlock(terminal.loop, state);
435
+ break;
436
+ }
437
+ case "for-in": {
438
+ const init = this.transformValue(terminal.id, terminal.init, state);
439
+ if (init.kind === "replace") {
440
+ terminal.init = init.value;
441
+ }
442
+ this.visitBlock(terminal.loop, state);
443
+ break;
444
+ }
445
+ case "do-while": {
446
+ this.visitBlock(terminal.loop, state);
447
+ const test = this.transformValue(terminal.id, terminal.test, state);
448
+ if (test.kind === "replace") {
449
+ terminal.test = test.value;
450
+ }
451
+ break;
452
+ }
453
+ case "while": {
454
+ const test = this.transformValue(terminal.id, terminal.test, state);
455
+ if (test.kind === "replace") {
456
+ terminal.test = test.value;
457
+ }
458
+ this.visitBlock(terminal.loop, state);
459
+ break;
460
+ }
461
+ case "if": {
462
+ this.visitPlace(terminal.id, terminal.test, state);
463
+ this.visitBlock(terminal.consequent, state);
464
+ if (terminal.alternate !== null) {
465
+ this.visitBlock(terminal.alternate, state);
466
+ }
467
+ break;
468
+ }
469
+ case "switch": {
470
+ this.visitPlace(terminal.id, terminal.test, state);
471
+ for (const case_ of terminal.cases) {
472
+ if (case_.test !== null) {
473
+ this.visitPlace(terminal.id, case_.test, state);
474
+ }
475
+ if (case_.block !== undefined) {
476
+ this.visitBlock(case_.block, state);
477
+ }
478
+ }
479
+ break;
480
+ }
481
+ case "label": {
482
+ this.visitBlock(terminal.block, state);
483
+ break;
484
+ }
485
+ case "try": {
486
+ this.visitBlock(terminal.block, state);
487
+ this.visitBlock(terminal.handler, state);
488
+ break;
489
+ }
490
+ default: {
491
+ assertExhaustive(
492
+ terminal,
493
+ `Unexpected terminal kind '${(terminal as any).kind}'`
494
+ );
495
+ }
496
+ }
497
+ }
498
}
499
500
export function* eachReactiveValueOperand(