compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts TYPESCRIPT 1,311 lines View on github.com → Search inside
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 */78import {assertExhaustive} from '../Utils/utils';9import {CompilerError} from '..';10import {11  BasicBlock,12  BlockId,13  Instruction,14  InstructionKind,15  InstructionValue,16  makeInstructionId,17  Pattern,18  Place,19  ReactiveInstruction,20  ReactiveScope,21  ReactiveValue,22  ScopeId,23  SpreadPattern,24  Terminal,25} from './HIR';2627export function* eachInstructionLValue(28  instr: ReactiveInstruction,29): Iterable<Place> {30  if (instr.lvalue !== null) {31    yield instr.lvalue;32  }33  yield* eachInstructionValueLValue(instr.value);34}3536export function* eachInstructionLValueWithKind(37  instr: ReactiveInstruction,38): Iterable<[Place, InstructionKind]> {39  switch (instr.value.kind) {40    case 'DeclareContext':41    case 'StoreContext':42    case 'DeclareLocal':43    case 'StoreLocal': {44      yield [instr.value.lvalue.place, instr.value.lvalue.kind];45      break;46    }47    case 'Destructure': {48      const kind = instr.value.lvalue.kind;49      for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {50        yield [place, kind];51      }52      break;53    }54    case 'PostfixUpdate':55    case 'PrefixUpdate': {56      yield [instr.value.lvalue, InstructionKind.Reassign];57      break;58    }59  }60}6162export function* eachInstructionValueLValue(63  value: ReactiveValue,64): Iterable<Place> {65  switch (value.kind) {66    case 'DeclareContext':67    case 'StoreContext':68    case 'DeclareLocal':69    case 'StoreLocal': {70      yield value.lvalue.place;71      break;72    }73    case 'Destructure': {74      yield* eachPatternOperand(value.lvalue.pattern);75      break;76    }77    case 'PostfixUpdate':78    case 'PrefixUpdate': {79      yield value.lvalue;80      break;81    }82  }83}8485export function* eachInstructionOperand(instr: Instruction): Iterable<Place> {86  yield* eachInstructionValueOperand(instr.value);87}88export function* eachInstructionValueOperand(89  instrValue: InstructionValue,90): Iterable<Place> {91  switch (instrValue.kind) {92    case 'NewExpression':93    case 'CallExpression': {94      yield instrValue.callee;95      yield* eachCallArgument(instrValue.args);96      break;97    }98    case 'BinaryExpression': {99      yield instrValue.left;100      yield instrValue.right;101      break;102    }103    case 'MethodCall': {104      yield instrValue.receiver;105      yield instrValue.property;106      yield* eachCallArgument(instrValue.args);107      break;108    }109    case 'DeclareContext':110    case 'DeclareLocal': {111      break;112    }113    case 'LoadLocal':114    case 'LoadContext': {115      yield instrValue.place;116      break;117    }118    case 'StoreLocal': {119      yield instrValue.value;120      break;121    }122    case 'StoreContext': {123      yield instrValue.lvalue.place;124      yield instrValue.value;125      break;126    }127    case 'StoreGlobal': {128      yield instrValue.value;129      break;130    }131    case 'Destructure': {132      yield instrValue.value;133      break;134    }135    case 'PropertyLoad': {136      yield instrValue.object;137      break;138    }139    case 'PropertyDelete': {140      yield instrValue.object;141      break;142    }143    case 'PropertyStore': {144      yield instrValue.object;145      yield instrValue.value;146      break;147    }148    case 'ComputedLoad': {149      yield instrValue.object;150      yield instrValue.property;151      break;152    }153    case 'ComputedDelete': {154      yield instrValue.object;155      yield instrValue.property;156      break;157    }158    case 'ComputedStore': {159      yield instrValue.object;160      yield instrValue.property;161      yield instrValue.value;162      break;163    }164    case 'UnaryExpression': {165      yield instrValue.value;166      break;167    }168    case 'JsxExpression': {169      if (instrValue.tag.kind === 'Identifier') {170        yield instrValue.tag;171      }172      for (const attribute of instrValue.props) {173        switch (attribute.kind) {174          case 'JsxAttribute': {175            yield attribute.place;176            break;177          }178          case 'JsxSpreadAttribute': {179            yield attribute.argument;180            break;181          }182          default: {183            assertExhaustive(184              attribute,185              `Unexpected attribute kind \`${(attribute as any).kind}\``,186            );187          }188        }189      }190      if (instrValue.children) {191        yield* instrValue.children;192      }193      break;194    }195    case 'JsxFragment': {196      yield* instrValue.children;197      break;198    }199    case 'ObjectExpression': {200      for (const property of instrValue.properties) {201        if (202          property.kind === 'ObjectProperty' &&203          property.key.kind === 'computed'204        ) {205          yield property.key.name;206        }207        yield property.place;208      }209      break;210    }211    case 'ArrayExpression': {212      for (const element of instrValue.elements) {213        if (element.kind === 'Identifier') {214          yield element;215        } else if (element.kind === 'Spread') {216          yield element.place;217        }218      }219      break;220    }221    case 'ObjectMethod':222    case 'FunctionExpression': {223      yield* instrValue.loweredFunc.func.context;224      break;225    }226    case 'TaggedTemplateExpression': {227      yield instrValue.tag;228      break;229    }230    case 'TypeCastExpression': {231      yield instrValue.value;232      break;233    }234    case 'TemplateLiteral': {235      yield* instrValue.subexprs;236      break;237    }238    case 'Await': {239      yield instrValue.value;240      break;241    }242    case 'GetIterator': {243      yield instrValue.collection;244      break;245    }246    case 'IteratorNext': {247      yield instrValue.iterator;248      yield instrValue.collection;249      break;250    }251    case 'NextPropertyOf': {252      yield instrValue.value;253      break;254    }255    case 'PostfixUpdate':256    case 'PrefixUpdate': {257      yield instrValue.value;258      break;259    }260    case 'StartMemoize': {261      if (instrValue.deps != null) {262        for (const dep of instrValue.deps) {263          if (dep.root.kind === 'NamedLocal') {264            yield dep.root.value;265          }266        }267      }268      break;269    }270    case 'FinishMemoize': {271      yield instrValue.decl;272      break;273    }274    case 'Debugger':275    case 'RegExpLiteral':276    case 'MetaProperty':277    case 'LoadGlobal':278    case 'UnsupportedNode':279    case 'Primitive':280    case 'JSXText': {281      break;282    }283    default: {284      assertExhaustive(285        instrValue,286        `Unexpected instruction kind \`${(instrValue as any).kind}\``,287      );288    }289  }290}291292export function* eachCallArgument(293  args: Array<Place | SpreadPattern>,294): Iterable<Place> {295  for (const arg of args) {296    if (arg.kind === 'Identifier') {297      yield arg;298    } else {299      yield arg.place;300    }301  }302}303304export function doesPatternContainSpreadElement(pattern: Pattern): boolean {305  switch (pattern.kind) {306    case 'ArrayPattern': {307      for (const item of pattern.items) {308        if (item.kind === 'Spread') {309          return true;310        }311      }312      break;313    }314    case 'ObjectPattern': {315      for (const property of pattern.properties) {316        if (property.kind === 'Spread') {317          return true;318        }319      }320      break;321    }322    default: {323      assertExhaustive(324        pattern,325        `Unexpected pattern kind \`${(pattern as any).kind}\``,326      );327    }328  }329  return false;330}331332export function* eachPatternOperand(pattern: Pattern): Iterable<Place> {333  switch (pattern.kind) {334    case 'ArrayPattern': {335      for (const item of pattern.items) {336        if (item.kind === 'Identifier') {337          yield item;338        } else if (item.kind === 'Spread') {339          yield item.place;340        } else if (item.kind === 'Hole') {341          continue;342        } else {343          assertExhaustive(344            item,345            `Unexpected item kind \`${(item as any).kind}\``,346          );347        }348      }349      break;350    }351    case 'ObjectPattern': {352      for (const property of pattern.properties) {353        if (property.kind === 'ObjectProperty') {354          yield property.place;355        } else if (property.kind === 'Spread') {356          yield property.place;357        } else {358          assertExhaustive(359            property,360            `Unexpected item kind \`${(property as any).kind}\``,361          );362        }363      }364      break;365    }366    default: {367      assertExhaustive(368        pattern,369        `Unexpected pattern kind \`${(pattern as any).kind}\``,370      );371    }372  }373}374375export function* eachPatternItem(376  pattern: Pattern,377): Iterable<Place | SpreadPattern> {378  switch (pattern.kind) {379    case 'ArrayPattern': {380      for (const item of pattern.items) {381        if (item.kind === 'Identifier') {382          yield item;383        } else if (item.kind === 'Spread') {384          yield item;385        } else if (item.kind === 'Hole') {386          continue;387        } else {388          assertExhaustive(389            item,390            `Unexpected item kind \`${(item as any).kind}\``,391          );392        }393      }394      break;395    }396    case 'ObjectPattern': {397      for (const property of pattern.properties) {398        if (property.kind === 'ObjectProperty') {399          yield property.place;400        } else if (property.kind === 'Spread') {401          yield property;402        } else {403          assertExhaustive(404            property,405            `Unexpected item kind \`${(property as any).kind}\``,406          );407        }408      }409      break;410    }411    default: {412      assertExhaustive(413        pattern,414        `Unexpected pattern kind \`${(pattern as any).kind}\``,415      );416    }417  }418}419420export function mapInstructionLValues(421  instr: Instruction,422  fn: (place: Place) => Place,423): void {424  switch (instr.value.kind) {425    case 'DeclareLocal':426    case 'StoreLocal': {427      const lvalue = instr.value.lvalue;428      lvalue.place = fn(lvalue.place);429      break;430    }431    case 'Destructure': {432      mapPatternOperands(instr.value.lvalue.pattern, fn);433      break;434    }435    case 'PostfixUpdate':436    case 'PrefixUpdate': {437      instr.value.lvalue = fn(instr.value.lvalue);438      break;439    }440  }441  if (instr.lvalue !== null) {442    instr.lvalue = fn(instr.lvalue);443  }444}445446export function mapInstructionOperands(447  instr: Instruction,448  fn: (place: Place) => Place,449): void {450  mapInstructionValueOperands(instr.value, fn);451}452453export function mapInstructionValueOperands(454  instrValue: InstructionValue,455  fn: (place: Place) => Place,456): void {457  switch (instrValue.kind) {458    case 'BinaryExpression': {459      instrValue.left = fn(instrValue.left);460      instrValue.right = fn(instrValue.right);461      break;462    }463    case 'PropertyLoad': {464      instrValue.object = fn(instrValue.object);465      break;466    }467    case 'PropertyDelete': {468      instrValue.object = fn(instrValue.object);469      break;470    }471    case 'PropertyStore': {472      instrValue.object = fn(instrValue.object);473      instrValue.value = fn(instrValue.value);474      break;475    }476    case 'ComputedLoad': {477      instrValue.object = fn(instrValue.object);478      instrValue.property = fn(instrValue.property);479      break;480    }481    case 'ComputedDelete': {482      instrValue.object = fn(instrValue.object);483      instrValue.property = fn(instrValue.property);484      break;485    }486    case 'ComputedStore': {487      instrValue.object = fn(instrValue.object);488      instrValue.property = fn(instrValue.property);489      instrValue.value = fn(instrValue.value);490      break;491    }492    case 'DeclareContext':493    case 'DeclareLocal': {494      break;495    }496    case 'LoadLocal':497    case 'LoadContext': {498      instrValue.place = fn(instrValue.place);499      break;500    }501    case 'StoreLocal': {502      instrValue.value = fn(instrValue.value);503      break;504    }505    case 'StoreContext': {506      instrValue.lvalue.place = fn(instrValue.lvalue.place);507      instrValue.value = fn(instrValue.value);508      break;509    }510    case 'StoreGlobal': {511      instrValue.value = fn(instrValue.value);512      break;513    }514    case 'Destructure': {515      instrValue.value = fn(instrValue.value);516      break;517    }518    case 'NewExpression':519    case 'CallExpression': {520      instrValue.callee = fn(instrValue.callee);521      instrValue.args = mapCallArguments(instrValue.args, fn);522      break;523    }524    case 'MethodCall': {525      instrValue.receiver = fn(instrValue.receiver);526      instrValue.property = fn(instrValue.property);527      instrValue.args = mapCallArguments(instrValue.args, fn);528      break;529    }530    case 'UnaryExpression': {531      instrValue.value = fn(instrValue.value);532      break;533    }534    case 'JsxExpression': {535      if (instrValue.tag.kind === 'Identifier') {536        instrValue.tag = fn(instrValue.tag);537      }538      for (const attribute of instrValue.props) {539        switch (attribute.kind) {540          case 'JsxAttribute': {541            attribute.place = fn(attribute.place);542            break;543          }544          case 'JsxSpreadAttribute': {545            attribute.argument = fn(attribute.argument);546            break;547          }548          default: {549            assertExhaustive(550              attribute,551              `Unexpected attribute kind \`${(attribute as any).kind}\``,552            );553          }554        }555      }556      if (instrValue.children) {557        instrValue.children = instrValue.children.map(p => fn(p));558      }559      break;560    }561    case 'ObjectExpression': {562      for (const property of instrValue.properties) {563        if (564          property.kind === 'ObjectProperty' &&565          property.key.kind === 'computed'566        ) {567          property.key.name = fn(property.key.name);568        }569        property.place = fn(property.place);570      }571      break;572    }573    case 'ArrayExpression': {574      instrValue.elements = instrValue.elements.map(element => {575        if (element.kind === 'Identifier') {576          return fn(element);577        } else if (element.kind === 'Spread') {578          element.place = fn(element.place);579          return element;580        } else {581          return element;582        }583      });584      break;585    }586    case 'JsxFragment': {587      instrValue.children = instrValue.children.map(e => fn(e));588      break;589    }590    case 'ObjectMethod':591    case 'FunctionExpression': {592      instrValue.loweredFunc.func.context =593        instrValue.loweredFunc.func.context.map(d => fn(d));594595      break;596    }597    case 'TaggedTemplateExpression': {598      instrValue.tag = fn(instrValue.tag);599      break;600    }601    case 'TypeCastExpression': {602      instrValue.value = fn(instrValue.value);603      break;604    }605    case 'TemplateLiteral': {606      instrValue.subexprs = instrValue.subexprs.map(fn);607      break;608    }609    case 'Await': {610      instrValue.value = fn(instrValue.value);611      break;612    }613    case 'GetIterator': {614      instrValue.collection = fn(instrValue.collection);615      break;616    }617    case 'IteratorNext': {618      instrValue.iterator = fn(instrValue.iterator);619      instrValue.collection = fn(instrValue.collection);620      break;621    }622    case 'NextPropertyOf': {623      instrValue.value = fn(instrValue.value);624      break;625    }626    case 'PostfixUpdate':627    case 'PrefixUpdate': {628      instrValue.value = fn(instrValue.value);629      break;630    }631    case 'StartMemoize': {632      if (instrValue.deps != null) {633        for (const dep of instrValue.deps) {634          if (dep.root.kind === 'NamedLocal') {635            dep.root.value = fn(dep.root.value);636          }637        }638      }639      break;640    }641    case 'FinishMemoize': {642      instrValue.decl = fn(instrValue.decl);643      break;644    }645    case 'Debugger':646    case 'RegExpLiteral':647    case 'MetaProperty':648    case 'LoadGlobal':649    case 'UnsupportedNode':650    case 'Primitive':651    case 'JSXText': {652      break;653    }654    default: {655      assertExhaustive(instrValue, 'Unexpected instruction kind');656    }657  }658}659660export function mapCallArguments(661  args: Array<Place | SpreadPattern>,662  fn: (place: Place) => Place,663): Array<Place | SpreadPattern> {664  return args.map(arg => {665    if (arg.kind === 'Identifier') {666      return fn(arg);667    } else {668      arg.place = fn(arg.place);669      return arg;670    }671  });672}673674export function mapPatternOperands(675  pattern: Pattern,676  fn: (place: Place) => Place,677): void {678  switch (pattern.kind) {679    case 'ArrayPattern': {680      pattern.items = pattern.items.map(item => {681        if (item.kind === 'Identifier') {682          return fn(item);683        } else if (item.kind === 'Spread') {684          item.place = fn(item.place);685          return item;686        } else {687          return item;688        }689      });690      break;691    }692    case 'ObjectPattern': {693      for (const property of pattern.properties) {694        property.place = fn(property.place);695      }696      break;697    }698    default: {699      assertExhaustive(700        pattern,701        `Unexpected pattern kind \`${(pattern as any).kind}\``,702      );703    }704  }705}706707// Maps a terminal node's block assignments using the provided function.708export function mapTerminalSuccessors(709  terminal: Terminal,710  fn: (block: BlockId) => BlockId,711): Terminal {712  switch (terminal.kind) {713    case 'goto': {714      const target = fn(terminal.block);715      return {716        kind: 'goto',717        block: target,718        variant: terminal.variant,719        id: makeInstructionId(0),720        loc: terminal.loc,721      };722    }723    case 'if': {724      const consequent = fn(terminal.consequent);725      const alternate = fn(terminal.alternate);726      const fallthrough = fn(terminal.fallthrough);727      return {728        kind: 'if',729        test: terminal.test,730        consequent,731        alternate,732        fallthrough,733        id: makeInstructionId(0),734        loc: terminal.loc,735      };736    }737    case 'branch': {738      const consequent = fn(terminal.consequent);739      const alternate = fn(terminal.alternate);740      const fallthrough = fn(terminal.fallthrough);741      return {742        kind: 'branch',743        test: terminal.test,744        consequent,745        alternate,746        fallthrough,747        id: makeInstructionId(0),748        loc: terminal.loc,749      };750    }751    case 'switch': {752      const cases = terminal.cases.map(case_ => {753        const target = fn(case_.block);754        return {755          test: case_.test,756          block: target,757        };758      });759      const fallthrough = fn(terminal.fallthrough);760      return {761        kind: 'switch',762        test: terminal.test,763        cases,764        fallthrough,765        id: makeInstructionId(0),766        loc: terminal.loc,767      };768    }769    case 'logical': {770      const test = fn(terminal.test);771      const fallthrough = fn(terminal.fallthrough);772      return {773        kind: 'logical',774        test,775        fallthrough,776        operator: terminal.operator,777        id: makeInstructionId(0),778        loc: terminal.loc,779      };780    }781    case 'ternary': {782      const test = fn(terminal.test);783      const fallthrough = fn(terminal.fallthrough);784      return {785        kind: 'ternary',786        test,787        fallthrough,788        id: makeInstructionId(0),789        loc: terminal.loc,790      };791    }792    case 'optional': {793      const test = fn(terminal.test);794      const fallthrough = fn(terminal.fallthrough);795      return {796        kind: 'optional',797        optional: terminal.optional,798        test,799        fallthrough,800        id: makeInstructionId(0),801        loc: terminal.loc,802      };803    }804    case 'return': {805      return {806        kind: 'return',807        returnVariant: terminal.returnVariant,808        loc: terminal.loc,809        value: terminal.value,810        id: makeInstructionId(0),811        effects: terminal.effects,812      };813    }814    case 'throw': {815      return terminal;816    }817    case 'do-while': {818      const loop = fn(terminal.loop);819      const test = fn(terminal.test);820      const fallthrough = fn(terminal.fallthrough);821      return {822        kind: 'do-while',823        loc: terminal.loc,824        test,825        loop,826        fallthrough,827        id: makeInstructionId(0),828      };829    }830    case 'while': {831      const test = fn(terminal.test);832      const loop = fn(terminal.loop);833      const fallthrough = fn(terminal.fallthrough);834      return {835        kind: 'while',836        loc: terminal.loc,837        test,838        loop,839        fallthrough,840        id: makeInstructionId(0),841      };842    }843    case 'for': {844      const init = fn(terminal.init);845      const test = fn(terminal.test);846      const update = terminal.update !== null ? fn(terminal.update) : null;847      const loop = fn(terminal.loop);848      const fallthrough = fn(terminal.fallthrough);849      return {850        kind: 'for',851        loc: terminal.loc,852        init,853        test,854        update,855        loop,856        fallthrough,857        id: makeInstructionId(0),858      };859    }860    case 'for-of': {861      const init = fn(terminal.init);862      const loop = fn(terminal.loop);863      const test = fn(terminal.test);864      const fallthrough = fn(terminal.fallthrough);865      return {866        kind: 'for-of',867        loc: terminal.loc,868        init,869        test,870        loop,871        fallthrough,872        id: makeInstructionId(0),873      };874    }875    case 'for-in': {876      const init = fn(terminal.init);877      const loop = fn(terminal.loop);878      const fallthrough = fn(terminal.fallthrough);879      return {880        kind: 'for-in',881        loc: terminal.loc,882        init,883        loop,884        fallthrough,885        id: makeInstructionId(0),886      };887    }888    case 'label': {889      const block = fn(terminal.block);890      const fallthrough = fn(terminal.fallthrough);891      return {892        kind: 'label',893        block,894        fallthrough,895        id: makeInstructionId(0),896        loc: terminal.loc,897      };898    }899    case 'sequence': {900      const block = fn(terminal.block);901      const fallthrough = fn(terminal.fallthrough);902      return {903        kind: 'sequence',904        block,905        fallthrough,906        id: makeInstructionId(0),907        loc: terminal.loc,908      };909    }910    case 'maybe-throw': {911      const continuation = fn(terminal.continuation);912      const handler = terminal.handler !== null ? fn(terminal.handler) : null;913      return {914        kind: 'maybe-throw',915        continuation,916        handler,917        id: makeInstructionId(0),918        loc: terminal.loc,919        effects: terminal.effects,920      };921    }922    case 'try': {923      const block = fn(terminal.block);924      const handler = fn(terminal.handler);925      const fallthrough = fn(terminal.fallthrough);926      return {927        kind: 'try',928        block,929        handlerBinding: terminal.handlerBinding,930        handler,931        fallthrough,932        id: makeInstructionId(0),933        loc: terminal.loc,934      };935    }936    case 'scope':937    case 'pruned-scope': {938      const block = fn(terminal.block);939      const fallthrough = fn(terminal.fallthrough);940      return {941        kind: terminal.kind,942        scope: terminal.scope,943        block,944        fallthrough,945        id: makeInstructionId(0),946        loc: terminal.loc,947      };948    }949    case 'unreachable':950    case 'unsupported': {951      return terminal;952    }953    default: {954      assertExhaustive(955        terminal,956        `Unexpected terminal kind \`${(terminal as any as Terminal).kind}\``,957      );958    }959  }960}961962export function terminalHasFallthrough<963  T extends Terminal,964  U extends T & {fallthrough: BlockId},965>(terminal: T): terminal is U {966  switch (terminal.kind) {967    case 'maybe-throw':968    case 'goto':969    case 'return':970    case 'throw':971    case 'unreachable':972    case 'unsupported': {973      const _: undefined = terminal.fallthrough;974      return false;975    }976    case 'branch':977    case 'try':978    case 'do-while':979    case 'for-of':980    case 'for-in':981    case 'for':982    case 'if':983    case 'label':984    case 'logical':985    case 'optional':986    case 'sequence':987    case 'switch':988    case 'ternary':989    case 'while':990    case 'scope':991    case 'pruned-scope': {992      const _: BlockId = terminal.fallthrough;993      return true;994    }995    default: {996      assertExhaustive(997        terminal,998        `Unexpected terminal kind \`${(terminal as any).kind}\``,999      );1000    }1001  }1002}10031004/*1005 * Helper to get a terminal's fallthrough. The main reason to extract this as a helper1006 * function is to ensure that we use an exhaustive switch to ensure that we add new terminal1007 * variants as appropriate.1008 */1009export function terminalFallthrough(terminal: Terminal): BlockId | null {1010  if (terminalHasFallthrough(terminal)) {1011    return terminal.fallthrough;1012  } else {1013    return null;1014  }1015}10161017/*1018 * Iterates over the successor block ids of the provided terminal. The function is called1019 * specifically for the successors that define the standard control flow, and not1020 * pseduo-successors such as fallthroughs.1021 */1022export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {1023  switch (terminal.kind) {1024    case 'goto': {1025      yield terminal.block;1026      break;1027    }1028    case 'if': {1029      yield terminal.consequent;1030      yield terminal.alternate;1031      break;1032    }1033    case 'branch': {1034      yield terminal.consequent;1035      yield terminal.alternate;1036      break;1037    }1038    case 'switch': {1039      for (const case_ of terminal.cases) {1040        yield case_.block;1041      }1042      break;1043    }1044    case 'optional':1045    case 'ternary':1046    case 'logical': {1047      yield terminal.test;1048      break;1049    }1050    case 'return': {1051      break;1052    }1053    case 'throw': {1054      break;1055    }1056    case 'do-while': {1057      yield terminal.loop;1058      break;1059    }1060    case 'while': {1061      yield terminal.test;1062      break;1063    }1064    case 'for': {1065      yield terminal.init;1066      break;1067    }1068    case 'for-of': {1069      yield terminal.init;1070      break;1071    }1072    case 'for-in': {1073      yield terminal.init;1074      break;1075    }1076    case 'label': {1077      yield terminal.block;1078      break;1079    }1080    case 'sequence': {1081      yield terminal.block;1082      break;1083    }1084    case 'maybe-throw': {1085      yield terminal.continuation;1086      if (terminal.handler !== null) {1087        yield terminal.handler;1088      }1089      break;1090    }1091    case 'try': {1092      yield terminal.block;1093      break;1094    }1095    case 'scope':1096    case 'pruned-scope': {1097      yield terminal.block;1098      break;1099    }1100    case 'unreachable':1101    case 'unsupported':1102      break;1103    default: {1104      assertExhaustive(1105        terminal,1106        `Unexpected terminal kind \`${(terminal as any as Terminal).kind}\``,1107      );1108    }1109  }1110}11111112export function mapTerminalOperands(1113  terminal: Terminal,1114  fn: (place: Place) => Place,1115): void {1116  switch (terminal.kind) {1117    case 'if': {1118      terminal.test = fn(terminal.test);1119      break;1120    }1121    case 'branch': {1122      terminal.test = fn(terminal.test);1123      break;1124    }1125    case 'switch': {1126      terminal.test = fn(terminal.test);1127      for (const case_ of terminal.cases) {1128        if (case_.test === null) {1129          continue;1130        }1131        case_.test = fn(case_.test);1132      }1133      break;1134    }1135    case 'return':1136    case 'throw': {1137      terminal.value = fn(terminal.value);1138      break;1139    }1140    case 'try': {1141      if (terminal.handlerBinding !== null) {1142        terminal.handlerBinding = fn(terminal.handlerBinding);1143      } else {1144        terminal.handlerBinding = null;1145      }1146      break;1147    }1148    case 'maybe-throw':1149    case 'sequence':1150    case 'label':1151    case 'optional':1152    case 'ternary':1153    case 'logical':1154    case 'do-while':1155    case 'while':1156    case 'for':1157    case 'for-of':1158    case 'for-in':1159    case 'goto':1160    case 'unreachable':1161    case 'unsupported':1162    case 'scope':1163    case 'pruned-scope': {1164      // no-op1165      break;1166    }1167    default: {1168      assertExhaustive(1169        terminal,1170        `Unexpected terminal kind \`${(terminal as any).kind}\``,1171      );1172    }1173  }1174}11751176export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {1177  switch (terminal.kind) {1178    case 'if': {1179      yield terminal.test;1180      break;1181    }1182    case 'branch': {1183      yield terminal.test;1184      break;1185    }1186    case 'switch': {1187      yield terminal.test;1188      for (const case_ of terminal.cases) {1189        if (case_.test === null) {1190          continue;1191        }1192        yield case_.test;1193      }1194      break;1195    }1196    case 'return':1197    case 'throw': {1198      yield terminal.value;1199      break;1200    }1201    case 'try': {1202      if (terminal.handlerBinding !== null) {1203        yield terminal.handlerBinding;1204      }1205      break;1206    }1207    case 'maybe-throw':1208    case 'sequence':1209    case 'label':1210    case 'optional':1211    case 'ternary':1212    case 'logical':1213    case 'do-while':1214    case 'while':1215    case 'for':1216    case 'for-of':1217    case 'for-in':1218    case 'goto':1219    case 'unreachable':1220    case 'unsupported':1221    case 'scope':1222    case 'pruned-scope': {1223      // no-op1224      break;1225    }1226    default: {1227      assertExhaustive(1228        terminal,1229        `Unexpected terminal kind \`${(terminal as any).kind}\``,1230      );1231    }1232  }1233}12341235/**1236 * Helper class for traversing scope blocks in HIR-form.1237 */1238export class ScopeBlockTraversal {1239  // Live stack of active scopes1240  #activeScopes: Array<ScopeId> = [];1241  blockInfos: Map<1242    BlockId,1243    | {1244        kind: 'end';1245        scope: ReactiveScope;1246        pruned: boolean;1247      }1248    | {1249        kind: 'begin';1250        scope: ReactiveScope;1251        pruned: boolean;1252        fallthrough: BlockId;1253      }1254  > = new Map();12551256  recordScopes(block: BasicBlock): void {1257    const blockInfo = this.blockInfos.get(block.id);1258    if (blockInfo?.kind === 'begin') {1259      this.#activeScopes.push(blockInfo.scope.id);1260    } else if (blockInfo?.kind === 'end') {1261      const top = this.#activeScopes.at(-1);1262      CompilerError.invariant(blockInfo.scope.id === top, {1263        reason:1264          'Expected traversed block fallthrough to match top-most active scope',1265        loc: block.instructions[0]?.loc ?? block.terminal.loc,1266      });1267      this.#activeScopes.pop();1268    }12691270    if (1271      block.terminal.kind === 'scope' ||1272      block.terminal.kind === 'pruned-scope'1273    ) {1274      CompilerError.invariant(1275        !this.blockInfos.has(block.terminal.block) &&1276          !this.blockInfos.has(block.terminal.fallthrough),1277        {1278          reason: 'Expected unique scope blocks and fallthroughs',1279          loc: block.terminal.loc,1280        },1281      );1282      this.blockInfos.set(block.terminal.block, {1283        kind: 'begin',1284        scope: block.terminal.scope,1285        pruned: block.terminal.kind === 'pruned-scope',1286        fallthrough: block.terminal.fallthrough,1287      });1288      this.blockInfos.set(block.terminal.fallthrough, {1289        kind: 'end',1290        scope: block.terminal.scope,1291        pruned: block.terminal.kind === 'pruned-scope',1292      });1293    }1294  }12951296  /**1297   * @returns if the given scope is currently 'active', i.e. if the scope start1298   * block but not the scope fallthrough has been recorded.1299   */1300  isScopeActive(scopeId: ScopeId): boolean {1301    return this.#activeScopes.indexOf(scopeId) !== -1;1302  }13031304  /**1305   * The current, innermost active scope.1306   */1307  get currentScope(): ScopeId | null {1308    return this.#activeScopes.at(-1) ?? null;1309  }1310}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.