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

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(