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.