compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts TYPESCRIPT 4,578 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 4,578.
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 {NodePath, Scope} from '@babel/traverse';9import * as t from '@babel/types';10import invariant from 'invariant';11import {12  CompilerDiagnostic,13  CompilerError,14  CompilerErrorDetail,15  CompilerSuggestionOperation,16  ErrorCategory,17} from '../CompilerError';18import {assertExhaustive, hasNode} from '../Utils/utils';19import {Environment} from './Environment';20import {21  ArrayExpression,22  ArrayPattern,23  BlockId,24  BranchTerminal,25  BuiltinTag,26  Case,27  Effect,28  GeneratedSource,29  GotoVariant,30  HIRFunction,31  IfTerminal,32  InstructionKind,33  InstructionValue,34  JsxAttribute,35  LoweredFunction,36  ObjectPattern,37  ObjectProperty,38  ObjectPropertyKey,39  Place,40  PropertyLiteral,41  ReturnTerminal,42  SourceLocation,43  SpreadPattern,44  ThrowTerminal,45  Type,46  makeInstructionId,47  makePropertyLiteral,48  makeType,49  promoteTemporary,50  validateIdentifierName,51} from './HIR';52import HIRBuilder, {Bindings, createTemporaryPlace} from './HIRBuilder';53import {BuiltInArrayId} from './ObjectShape';5455/*56 * *******************************************************************************************57 * *******************************************************************************************58 * ************************************* Lowering to HIR *************************************59 * *******************************************************************************************60 * *******************************************************************************************61 */6263/*64 * Converts a function into a high-level intermediate form (HIR) which represents65 * the code as a control-flow graph. All normal control-flow is modeled as accurately66 * as possible to allow precise, expression-level memoization. The main exceptions are67 * try/catch statements and exceptions: we currently bail out (skip compilation) for68 * try/catch and do not attempt to model control flow of exceptions, which can occur69 * ~anywhere in JavaScript. The compiler assumes that exceptions will be handled by70 * the runtime, ie by invalidating memoization.71 */72export function lower(73  func: NodePath<t.Function>,74  env: Environment,75  // Bindings captured from the outer function, in case lower() is called recursively (for lambdas)76  bindings: Bindings | null = null,77  capturedRefs: Map<t.Identifier, SourceLocation> = new Map(),78): HIRFunction {79  const builder = new HIRBuilder(env, {80    bindings,81    context: capturedRefs,82  });83  const context: HIRFunction['context'] = [];8485  for (const [ref, loc] of capturedRefs ?? []) {86    context.push({87      kind: 'Identifier',88      identifier: builder.resolveBinding(ref),89      effect: Effect.Unknown,90      reactive: false,91      loc,92    });93  }9495  let id: string | null = null;96  if (func.isFunctionDeclaration() || func.isFunctionExpression()) {97    const idNode = (98      func as NodePath<t.FunctionDeclaration | t.FunctionExpression>99    ).get('id');100    if (hasNode(idNode)) {101      id = idNode.node.name;102    }103  }104  const params: Array<Place | SpreadPattern> = [];105  func.get('params').forEach(param => {106    if (param.isIdentifier()) {107      const binding = builder.resolveIdentifier(param);108      if (binding.kind !== 'Identifier') {109        builder.recordError(110          CompilerDiagnostic.create({111            category: ErrorCategory.Invariant,112            reason: 'Could not find binding',113            description: `[BuildHIR] Could not find binding for param \`${param.node.name}\``,114          }).withDetails({115            kind: 'error',116            loc: param.node.loc ?? null,117            message: 'Could not find binding',118          }),119        );120        return;121      }122      const place: Place = {123        kind: 'Identifier',124        identifier: binding.identifier,125        effect: Effect.Unknown,126        reactive: false,127        loc: param.node.loc ?? GeneratedSource,128      };129      params.push(place);130    } else if (131      param.isObjectPattern() ||132      param.isArrayPattern() ||133      param.isAssignmentPattern()134    ) {135      const place: Place = {136        kind: 'Identifier',137        identifier: builder.makeTemporary(param.node.loc ?? GeneratedSource),138        effect: Effect.Unknown,139        reactive: false,140        loc: param.node.loc ?? GeneratedSource,141      };142      promoteTemporary(place.identifier);143      params.push(place);144      lowerAssignment(145        builder,146        param.node.loc ?? GeneratedSource,147        InstructionKind.Let,148        param,149        place,150        'Assignment',151      );152    } else if (param.isRestElement()) {153      const place: Place = {154        kind: 'Identifier',155        identifier: builder.makeTemporary(param.node.loc ?? GeneratedSource),156        effect: Effect.Unknown,157        reactive: false,158        loc: param.node.loc ?? GeneratedSource,159      };160      params.push({161        kind: 'Spread',162        place,163      });164      lowerAssignment(165        builder,166        param.node.loc ?? GeneratedSource,167        InstructionKind.Let,168        param.get('argument'),169        place,170        'Assignment',171      );172    } else {173      builder.recordError(174        CompilerDiagnostic.create({175          category: ErrorCategory.Todo,176          reason: `Handle ${param.node.type} parameters`,177          description: `[BuildHIR] Add support for ${param.node.type} parameters`,178        }).withDetails({179          kind: 'error',180          loc: param.node.loc ?? null,181          message: 'Unsupported parameter type',182        }),183      );184    }185  });186187  let directives: Array<string> = [];188  const body = func.get('body');189  if (body.isExpression()) {190    const fallthrough = builder.reserve('block');191    const terminal: ReturnTerminal = {192      kind: 'return',193      returnVariant: 'Implicit',194      loc: GeneratedSource,195      value: lowerExpressionToTemporary(builder, body),196      id: makeInstructionId(0),197      effects: null,198    };199    builder.terminateWithContinuation(terminal, fallthrough);200  } else if (body.isBlockStatement()) {201    lowerStatement(builder, body);202    directives = body.get('directives').map(d => d.node.value.value);203  } else {204    builder.recordError(205      CompilerDiagnostic.create({206        category: ErrorCategory.Syntax,207        reason: `Unexpected function body kind`,208        description: `Expected function body to be an expression or a block statement, got \`${body.type}\``,209      }).withDetails({210        kind: 'error',211        loc: body.node.loc ?? null,212        message: 'Expected a block statement or expression',213      }),214    );215  }216217  let validatedId: HIRFunction['id'] = null;218  if (id != null) {219    const idResult = validateIdentifierName(id);220    if (idResult.isErr()) {221      for (const detail of idResult.unwrapErr().details) {222        builder.recordError(detail);223      }224    } else {225      validatedId = idResult.unwrap().value;226    }227  }228229  builder.terminate(230    {231      kind: 'return',232      returnVariant: 'Void',233      loc: GeneratedSource,234      value: lowerValueToTemporary(builder, {235        kind: 'Primitive',236        value: undefined,237        loc: GeneratedSource,238      }),239      id: makeInstructionId(0),240      effects: null,241    },242    null,243  );244245  const hirBody = builder.build();246247  return {248    id: validatedId,249    nameHint: null,250    params,251    fnType: bindings == null ? env.fnType : 'Other',252    returnTypeAnnotation: null, // TODO: extract the actual return type node if present253    returns: createTemporaryPlace(env, func.node.loc ?? GeneratedSource),254    body: hirBody,255    context,256    generator: func.node.generator === true,257    async: func.node.async === true,258    loc: func.node.loc ?? GeneratedSource,259    env,260    aliasingEffects: null,261    directives,262  };263}264265// Helper to lower a statement266function lowerStatement(267  builder: HIRBuilder,268  stmtPath: NodePath<t.Statement>,269  label: string | null = null,270): void {271  const stmtNode = stmtPath.node;272  switch (stmtNode.type) {273    case 'ThrowStatement': {274      const stmt = stmtPath as NodePath<t.ThrowStatement>;275      const value = lowerExpressionToTemporary(builder, stmt.get('argument'));276      const handler = builder.resolveThrowHandler();277      if (handler != null) {278        /*279         * NOTE: we could support this, but a `throw` inside try/catch is using exceptions280         * for control-flow and is generally considered an anti-pattern. we can likely281         * just not support this pattern, unless it really becomes necessary for some reason.282         */283        builder.recordError(284          new CompilerErrorDetail({285            reason:286              '(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch',287            category: ErrorCategory.Todo,288            loc: stmt.node.loc ?? null,289            suggestions: null,290          }),291        );292      }293      const terminal: ThrowTerminal = {294        kind: 'throw',295        value,296        id: makeInstructionId(0),297        loc: stmt.node.loc ?? GeneratedSource,298      };299      builder.terminate(terminal, 'block');300      return;301    }302    case 'ReturnStatement': {303      const stmt = stmtPath as NodePath<t.ReturnStatement>;304      const argument = stmt.get('argument');305      let value;306      if (argument.node === null) {307        value = lowerValueToTemporary(builder, {308          kind: 'Primitive',309          value: undefined,310          loc: GeneratedSource,311        });312      } else {313        value = lowerExpressionToTemporary(314          builder,315          argument as NodePath<t.Expression>,316        );317      }318      const terminal: ReturnTerminal = {319        kind: 'return',320        returnVariant: 'Explicit',321        loc: stmt.node.loc ?? GeneratedSource,322        value,323        id: makeInstructionId(0),324        effects: null,325      };326      builder.terminate(terminal, 'block');327      return;328    }329    case 'IfStatement': {330      const stmt = stmtPath as NodePath<t.IfStatement>;331      //  Block for code following the if332      const continuationBlock = builder.reserve('block');333      //  Block for the consequent (if the test is truthy)334      const consequentBlock = builder.enter('block', _blockId => {335        const consequent = stmt.get('consequent');336        lowerStatement(builder, consequent);337        return {338          kind: 'goto',339          block: continuationBlock.id,340          variant: GotoVariant.Break,341          id: makeInstructionId(0),342          loc: consequent.node.loc ?? GeneratedSource,343        };344      });345      //  Block for the alternate (if the test is not truthy)346      let alternateBlock: BlockId;347      const alternate = stmt.get('alternate');348      if (hasNode(alternate)) {349        alternateBlock = builder.enter('block', _blockId => {350          lowerStatement(builder, alternate);351          return {352            kind: 'goto',353            block: continuationBlock.id,354            variant: GotoVariant.Break,355            id: makeInstructionId(0),356            loc: alternate.node?.loc ?? GeneratedSource,357          };358        });359      } else {360        //  If there is no else clause, use the continuation directly361        alternateBlock = continuationBlock.id;362      }363      const test = lowerExpressionToTemporary(builder, stmt.get('test'));364      const terminal: IfTerminal = {365        kind: 'if',366        test,367        consequent: consequentBlock,368        alternate: alternateBlock,369        fallthrough: continuationBlock.id,370        id: makeInstructionId(0),371        loc: stmt.node.loc ?? GeneratedSource,372      };373      builder.terminateWithContinuation(terminal, continuationBlock);374      return;375    }376    case 'BlockStatement': {377      const stmt = stmtPath as NodePath<t.BlockStatement>;378      const statements = stmt.get('body');379      /**380       * Hoistable identifier bindings defined for this precise block381       * scope (excluding bindings from parent or child block scopes).382       */383      const hoistableIdentifiers: Set<t.Identifier> = new Set();384385      for (const [, binding] of Object.entries(stmt.scope.bindings)) {386        // refs to params are always valid / never need to be hoisted387        if (binding.kind !== 'param') {388          hoistableIdentifiers.add(binding.identifier);389        }390      }391392      for (const s of statements) {393        const willHoist = new Set<NodePath<t.Identifier>>();394        /*395         * If we see a hoistable identifier before its declaration, it should be hoisted just396         * before the statement that references it.397         */398        let fnDepth = s.isFunctionDeclaration() ? 1 : 0;399        const withFunctionContext = {400          enter: (): void => {401            fnDepth++;402          },403          exit: (): void => {404            fnDepth--;405          },406        };407        s.traverse({408          FunctionExpression: withFunctionContext,409          FunctionDeclaration: withFunctionContext,410          ArrowFunctionExpression: withFunctionContext,411          ObjectMethod: withFunctionContext,412          Identifier(id: NodePath<t.Identifier>) {413            const id2 = id;414            if (415              !id2.isReferencedIdentifier() &&416              // isReferencedIdentifier is broken and returns false for reassignments417              id.parent.type !== 'AssignmentExpression'418            ) {419              return;420            }421            const binding = id.scope.getBinding(id.node.name);422            /**423             * We can only hoist an identifier decl if424             * 1. the reference occurs within an inner function425             * or426             * 2. the declaration itself is hoistable427             */428            if (429              binding != null &&430              hoistableIdentifiers.has(binding.identifier) &&431              (fnDepth > 0 || binding.kind === 'hoisted')432            ) {433              willHoist.add(id);434            }435          },436        });437        /*438         * After visiting the declaration, hoisting is no longer required439         */440        s.traverse({441          Identifier(path: NodePath<t.Identifier>) {442            if (hoistableIdentifiers.has(path.node)) {443              hoistableIdentifiers.delete(path.node);444            }445          },446        });447448        // Hoist declarations that need it to the earliest point where they are needed449        for (const id of willHoist) {450          const binding = stmt.scope.getBinding(id.node.name);451          CompilerError.invariant(binding != null, {452            reason: 'Expected to find binding for hoisted identifier',453            description: `Could not find a binding for ${id.node.name}`,454            loc: id.node.loc ?? GeneratedSource,455          });456          if (builder.environment.isHoistedIdentifier(binding.identifier)) {457            // Already hoisted458            continue;459          }460461          let kind:462            | InstructionKind.Let463            | InstructionKind.HoistedConst464            | InstructionKind.HoistedLet465            | InstructionKind.HoistedFunction;466          if (binding.kind === 'const' || binding.kind === 'var') {467            kind = InstructionKind.HoistedConst;468          } else if (binding.kind === 'let') {469            kind = InstructionKind.HoistedLet;470          } else if (binding.path.isFunctionDeclaration()) {471            kind = InstructionKind.HoistedFunction;472          } else if (!binding.path.isVariableDeclarator()) {473            builder.recordError(474              new CompilerErrorDetail({475                category: ErrorCategory.Todo,476                reason: 'Unsupported declaration type for hoisting',477                description: `variable "${binding.identifier.name}" declared with ${binding.path.type}`,478                suggestions: null,479                loc: id.parentPath.node.loc ?? GeneratedSource,480              }),481            );482            continue;483          } else {484            builder.recordError(485              new CompilerErrorDetail({486                category: ErrorCategory.Todo,487                reason: 'Handle non-const declarations for hoisting',488                description: `variable "${binding.identifier.name}" declared with ${binding.kind}`,489                suggestions: null,490                loc: id.parentPath.node.loc ?? GeneratedSource,491              }),492            );493            continue;494          }495496          const identifier = builder.resolveIdentifier(id);497          CompilerError.invariant(identifier.kind === 'Identifier', {498            reason:499              'Expected hoisted binding to be a local identifier, not a global',500            loc: id.node.loc ?? GeneratedSource,501          });502          const place: Place = {503            effect: Effect.Unknown,504            identifier: identifier.identifier,505            kind: 'Identifier',506            reactive: false,507            loc: id.node.loc ?? GeneratedSource,508          };509          lowerValueToTemporary(builder, {510            kind: 'DeclareContext',511            lvalue: {512              kind,513              place,514            },515            loc: id.node.loc ?? GeneratedSource,516          });517          builder.environment.addHoistedIdentifier(binding.identifier);518        }519        lowerStatement(builder, s);520      }521522      return;523    }524    case 'BreakStatement': {525      const stmt = stmtPath as NodePath<t.BreakStatement>;526      const block = builder.lookupBreak(stmt.node.label?.name ?? null);527      builder.terminate(528        {529          kind: 'goto',530          block,531          variant: GotoVariant.Break,532          id: makeInstructionId(0),533          loc: stmt.node.loc ?? GeneratedSource,534        },535        'block',536      );537      return;538    }539    case 'ContinueStatement': {540      const stmt = stmtPath as NodePath<t.ContinueStatement>;541      const block = builder.lookupContinue(stmt.node.label?.name ?? null);542      builder.terminate(543        {544          kind: 'goto',545          block,546          variant: GotoVariant.Continue,547          id: makeInstructionId(0),548          loc: stmt.node.loc ?? GeneratedSource,549        },550        'block',551      );552      return;553    }554    case 'ForStatement': {555      const stmt = stmtPath as NodePath<t.ForStatement>;556557      const testBlock = builder.reserve('loop');558      //  Block for code following the loop559      const continuationBlock = builder.reserve('block');560561      const initBlock = builder.enter('loop', _blockId => {562        const init = stmt.get('init');563        if (init.node == null) {564          /*565           * No init expression (e.g., `for (; ...)`), add a placeholder to avoid566           * invariant about empty blocks567           */568          lowerValueToTemporary(builder, {569            kind: 'Primitive',570            value: undefined,571            loc: stmt.node.loc ?? GeneratedSource,572          });573          return {574            kind: 'goto',575            block: testBlock.id,576            variant: GotoVariant.Break,577            id: makeInstructionId(0),578            loc: stmt.node.loc ?? GeneratedSource,579          };580        }581        if (!init.isVariableDeclaration()) {582          builder.recordError(583            new CompilerErrorDetail({584              reason:585                '(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement',586              category: ErrorCategory.Todo,587              loc: stmt.node.loc ?? null,588              suggestions: null,589            }),590          );591          // Lower the init expression as best-effort and continue592          if (init.isExpression()) {593            lowerExpressionToTemporary(builder, init as NodePath<t.Expression>);594          }595          return {596            kind: 'goto',597            block: testBlock.id,598            variant: GotoVariant.Break,599            id: makeInstructionId(0),600            loc: init.node?.loc ?? GeneratedSource,601          };602        }603        lowerStatement(builder, init);604        return {605          kind: 'goto',606          block: testBlock.id,607          variant: GotoVariant.Break,608          id: makeInstructionId(0),609          loc: init.node.loc ?? GeneratedSource,610        };611      });612613      let updateBlock: BlockId | null = null;614      const update = stmt.get('update');615      if (hasNode(update)) {616        updateBlock = builder.enter('loop', _blockId => {617          lowerExpressionToTemporary(builder, update);618          return {619            kind: 'goto',620            block: testBlock.id,621            variant: GotoVariant.Break,622            id: makeInstructionId(0),623            loc: update.node?.loc ?? GeneratedSource,624          };625        });626      }627628      const bodyBlock = builder.enter('block', _blockId => {629        return builder.loop(630          label,631          updateBlock ?? testBlock.id,632          continuationBlock.id,633          () => {634            const body = stmt.get('body');635            lowerStatement(builder, body);636            return {637              kind: 'goto',638              block: updateBlock ?? testBlock.id,639              variant: GotoVariant.Continue,640              id: makeInstructionId(0),641              loc: body.node.loc ?? GeneratedSource,642            };643          },644        );645      });646647      builder.terminateWithContinuation(648        {649          kind: 'for',650          loc: stmtNode.loc ?? GeneratedSource,651          init: initBlock,652          test: testBlock.id,653          update: updateBlock,654          loop: bodyBlock,655          fallthrough: continuationBlock.id,656          id: makeInstructionId(0),657        },658        testBlock,659      );660661      const test = stmt.get('test');662      if (test.node == null) {663        builder.recordError(664          new CompilerErrorDetail({665            reason: `(BuildHIR::lowerStatement) Handle empty test in ForStatement`,666            category: ErrorCategory.Todo,667            loc: stmt.node.loc ?? null,668            suggestions: null,669          }),670        );671        // Treat `for(;;)` as `while(true)` to keep the builder state consistent672        builder.terminateWithContinuation(673          {674            kind: 'branch',675            test: lowerValueToTemporary(builder, {676              kind: 'Primitive',677              value: true,678              loc: stmt.node.loc ?? GeneratedSource,679            }),680            consequent: bodyBlock,681            alternate: continuationBlock.id,682            fallthrough: continuationBlock.id,683            id: makeInstructionId(0),684            loc: stmt.node.loc ?? GeneratedSource,685          },686          continuationBlock,687        );688      } else {689        builder.terminateWithContinuation(690          {691            kind: 'branch',692            test: lowerExpressionToTemporary(693              builder,694              test as NodePath<t.Expression>,695            ),696            consequent: bodyBlock,697            alternate: continuationBlock.id,698            fallthrough: continuationBlock.id,699            id: makeInstructionId(0),700            loc: stmt.node.loc ?? GeneratedSource,701          },702          continuationBlock,703        );704      }705      return;706    }707    case 'WhileStatement': {708      const stmt = stmtPath as NodePath<t.WhileStatement>;709      //  Block used to evaluate whether to (re)enter or exit the loop710      const conditionalBlock = builder.reserve('loop');711      //  Block for code following the loop712      const continuationBlock = builder.reserve('block');713      //  Loop body714      const loopBlock = builder.enter('block', _blockId => {715        return builder.loop(716          label,717          conditionalBlock.id,718          continuationBlock.id,719          () => {720            const body = stmt.get('body');721            lowerStatement(builder, body);722            return {723              kind: 'goto',724              block: conditionalBlock.id,725              variant: GotoVariant.Continue,726              id: makeInstructionId(0),727              loc: body.node.loc ?? GeneratedSource,728            };729          },730        );731      });732      /*733       * The code leading up to the loop must jump to the conditional block,734       * to evaluate whether to enter the loop or bypass to the continuation.735       */736      const loc = stmt.node.loc ?? GeneratedSource;737      builder.terminateWithContinuation(738        {739          kind: 'while',740          loc,741          test: conditionalBlock.id,742          loop: loopBlock,743          fallthrough: continuationBlock.id,744          id: makeInstructionId(0),745        },746        conditionalBlock,747      );748      const test = lowerExpressionToTemporary(builder, stmt.get('test'));749      const terminal: BranchTerminal = {750        kind: 'branch',751        test,752        consequent: loopBlock,753        alternate: continuationBlock.id,754        fallthrough: conditionalBlock.id,755        id: makeInstructionId(0),756        loc: stmt.node.loc ?? GeneratedSource,757      };758      //  Complete the conditional and continue with code after the loop759      builder.terminateWithContinuation(terminal, continuationBlock);760      return;761    }762    case 'LabeledStatement': {763      const stmt = stmtPath as NodePath<t.LabeledStatement>;764      const label = stmt.node.label.name;765      const body = stmt.get('body');766      switch (body.node.type) {767        case 'ForInStatement':768        case 'ForOfStatement':769        case 'ForStatement':770        case 'WhileStatement':771        case 'DoWhileStatement': {772          /*773           * labeled loops are special because of continue, so push the label774           * down775           */776          lowerStatement(builder, stmt.get('body'), label);777          break;778        }779        default: {780          /*781           * All other statements create a continuation block to allow `break`,782           * explicitly *don't* pass the label down783           */784          const continuationBlock = builder.reserve('block');785          const block = builder.enter('block', () => {786            const body = stmt.get('body');787            builder.label(label, continuationBlock.id, () => {788              lowerStatement(builder, body);789            });790            return {791              kind: 'goto',792              block: continuationBlock.id,793              variant: GotoVariant.Break,794              id: makeInstructionId(0),795              loc: body.node.loc ?? GeneratedSource,796            };797          });798          builder.terminateWithContinuation(799            {800              kind: 'label',801              block,802              fallthrough: continuationBlock.id,803              id: makeInstructionId(0),804              loc: stmt.node.loc ?? GeneratedSource,805            },806            continuationBlock,807          );808        }809      }810      return;811    }812    case 'SwitchStatement': {813      const stmt = stmtPath as NodePath<t.SwitchStatement>;814      //  Block following the switch815      const continuationBlock = builder.reserve('block');816      /*817       * The goto target for any cases that fallthrough, which initially starts818       * as the continuation block and is then updated as we iterate through cases819       * in reverse order.820       */821      let fallthrough = continuationBlock.id;822      /*823       * Iterate through cases in reverse order, so that previous blocks can fallthrough824       * to successors825       */826      const cases: Array<Case> = [];827      let hasDefault = false;828      for (let ii = stmt.get('cases').length - 1; ii >= 0; ii--) {829        const case_: NodePath<t.SwitchCase> = stmt.get('cases')[ii];830        const testExpr = case_.get('test');831        if (testExpr.node == null) {832          if (hasDefault) {833            builder.recordError(834              new CompilerErrorDetail({835                reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`,836                category: ErrorCategory.Syntax,837                loc: case_.node.loc ?? null,838                suggestions: null,839              }),840            );841            break;842          }843          hasDefault = true;844        }845        const block = builder.enter('block', _blockId => {846          return builder.switch(label, continuationBlock.id, () => {847            case_848              .get('consequent')849              .forEach(consequent => lowerStatement(builder, consequent));850            /*851             * always generate a fallthrough to the next block, this may be dead code852             * if there was an explicit break, but if so it will be pruned later.853             */854            return {855              kind: 'goto',856              block: fallthrough,857              variant: GotoVariant.Break,858              id: makeInstructionId(0),859              loc: case_.node.loc ?? GeneratedSource,860            };861          });862        });863        let test: Place | null = null;864        if (hasNode(testExpr)) {865          test = lowerReorderableExpression(builder, testExpr);866        }867        cases.push({868          test,869          block,870        });871        fallthrough = block;872      }873      /*874       * it doesn't matter for our analysis purposes, but reverse the order of the cases875       * back to the original to make it match the original code/intent.876       */877      cases.reverse();878      /*879       * If there wasn't an explicit default case, generate one to model the fact that execution880       * could bypass any of the other cases and jump directly to the continuation.881       */882      if (!hasDefault) {883        cases.push({test: null, block: continuationBlock.id});884      }885886      const test = lowerExpressionToTemporary(887        builder,888        stmt.get('discriminant'),889      );890      builder.terminateWithContinuation(891        {892          kind: 'switch',893          test,894          cases,895          fallthrough: continuationBlock.id,896          id: makeInstructionId(0),897          loc: stmt.node.loc ?? GeneratedSource,898        },899        continuationBlock,900      );901      return;902    }903    case 'VariableDeclaration': {904      const stmt = stmtPath as NodePath<t.VariableDeclaration>;905      const nodeKind: t.VariableDeclaration['kind'] = stmt.node.kind;906      if (907        nodeKind === 'var' ||908        nodeKind === 'using' ||909        nodeKind === 'await using'910      ) {911        builder.recordError(912          new CompilerErrorDetail({913            reason: `(BuildHIR::lowerStatement) Handle ${nodeKind} kinds in VariableDeclaration`,914            category: ErrorCategory.Todo,915            loc: stmt.node.loc ?? null,916            suggestions: null,917          }),918        );919        /*920         * Treat `var` as `let` and `using`/`await using` as `const` so921         * references to the variable don't break while the error unwinds922         */923      }924      const kind =925        nodeKind === 'let' || nodeKind === 'var'926          ? InstructionKind.Let927          : InstructionKind.Const;928      for (const declaration of stmt.get('declarations')) {929        const id = declaration.get('id');930        const init = declaration.get('init');931        if (hasNode(init)) {932          const value = lowerExpressionToTemporary(builder, init);933          lowerAssignment(934            builder,935            stmt.node.loc ?? GeneratedSource,936            kind,937            id,938            value,939            id.isObjectPattern() || id.isArrayPattern()940              ? 'Destructure'941              : 'Assignment',942          );943        } else if (id.isIdentifier()) {944          const binding = builder.resolveIdentifier(id);945          if (binding.kind !== 'Identifier') {946            builder.recordError(947              new CompilerErrorDetail({948                reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,949                category: ErrorCategory.Invariant,950                loc: id.node.loc ?? null,951                suggestions: null,952              }),953            );954          } else {955            const place: Place = {956              effect: Effect.Unknown,957              identifier: binding.identifier,958              kind: 'Identifier',959              reactive: false,960              loc: id.node.loc ?? GeneratedSource,961            };962            if (builder.isContextIdentifier(id)) {963              if (kind === InstructionKind.Const) {964                const declRangeStart = declaration.parentPath.node.start!;965                builder.recordError(966                  new CompilerErrorDetail({967                    reason: `Expect \`const\` declaration not to be reassigned`,968                    category: ErrorCategory.Syntax,969                    loc: id.node.loc ?? null,970                    suggestions: [971                      {972                        description: 'Change to a `let` declaration',973                        op: CompilerSuggestionOperation.Replace,974                        range: [declRangeStart, declRangeStart + 5], // "const".length975                        text: 'let',976                      },977                    ],978                  }),979                );980              }981              lowerValueToTemporary(builder, {982                kind: 'DeclareContext',983                lvalue: {984                  kind: InstructionKind.Let,985                  place,986                },987                loc: id.node.loc ?? GeneratedSource,988              });989            } else {990              const typeAnnotation = id.get('typeAnnotation');991              let type: t.FlowType | t.TSType | null;992              if (typeAnnotation.isTSTypeAnnotation()) {993                const typePath = typeAnnotation.get('typeAnnotation');994                type = typePath.node;995              } else if (typeAnnotation.isTypeAnnotation()) {996                const typePath = typeAnnotation.get('typeAnnotation');997                type = typePath.node;998              } else {999                type = null;1000              }1001              lowerValueToTemporary(builder, {1002                kind: 'DeclareLocal',1003                lvalue: {1004                  kind,1005                  place,1006                },1007                type,1008                loc: id.node.loc ?? GeneratedSource,1009              });1010            }1011          }1012        } else {1013          builder.recordError(1014            new CompilerErrorDetail({1015              reason: `Expected variable declaration to be an identifier if no initializer was provided`,1016              description: `Got a \`${id.type}\``,1017              category: ErrorCategory.Syntax,1018              loc: stmt.node.loc ?? null,1019              suggestions: null,1020            }),1021          );1022        }1023      }1024      return;1025    }1026    case 'ExpressionStatement': {1027      const stmt = stmtPath as NodePath<t.ExpressionStatement>;1028      const expression = stmt.get('expression');1029      lowerExpressionToTemporary(builder, expression);1030      return;1031    }1032    case 'DoWhileStatement': {1033      const stmt = stmtPath as NodePath<t.DoWhileStatement>;1034      //  Block used to evaluate whether to (re)enter or exit the loop1035      const conditionalBlock = builder.reserve('loop');1036      //  Block for code following the loop1037      const continuationBlock = builder.reserve('block');1038      //  Loop body, executed at least once uncondtionally prior to exit1039      const loopBlock = builder.enter('block', _loopBlockId => {1040        return builder.loop(1041          label,1042          conditionalBlock.id,1043          continuationBlock.id,1044          () => {1045            const body = stmt.get('body');1046            lowerStatement(builder, body);1047            return {1048              kind: 'goto',1049              block: conditionalBlock.id,1050              variant: GotoVariant.Continue,1051              id: makeInstructionId(0),1052              loc: body.node.loc ?? GeneratedSource,1053            };1054          },1055        );1056      });1057      /*1058       * Jump to the conditional block to evaluate whether to (re)enter the loop or exit to the1059       * continuation block.1060       */1061      const loc = stmt.node.loc ?? GeneratedSource;1062      builder.terminateWithContinuation(1063        {1064          kind: 'do-while',1065          loc,1066          test: conditionalBlock.id,1067          loop: loopBlock,1068          fallthrough: continuationBlock.id,1069          id: makeInstructionId(0),1070        },1071        conditionalBlock,1072      );1073      /*1074       * The conditional block is empty and exists solely as conditional for1075       * (re)entering or exiting the loop1076       */1077      const test = lowerExpressionToTemporary(builder, stmt.get('test'));1078      const terminal: BranchTerminal = {1079        kind: 'branch',1080        test,1081        consequent: loopBlock,1082        alternate: continuationBlock.id,1083        fallthrough: conditionalBlock.id,1084        id: makeInstructionId(0),1085        loc,1086      };1087      //  Complete the conditional and continue with code after the loop1088      builder.terminateWithContinuation(terminal, continuationBlock);1089      return;1090    }1091    case 'FunctionDeclaration': {1092      const stmt = stmtPath as NodePath<t.FunctionDeclaration>;1093      stmt.skip();1094      CompilerError.invariant(stmt.get('id').type === 'Identifier', {1095        reason: 'function declarations must have a name',1096        loc: stmt.node.loc ?? GeneratedSource,1097      });1098      const id = stmt.get('id') as NodePath<t.Identifier>;10991100      const fn = lowerValueToTemporary(1101        builder,1102        lowerFunctionToValue(builder, stmt),1103      );1104      lowerAssignment(1105        builder,1106        stmt.node.loc ?? GeneratedSource,1107        InstructionKind.Function,1108        id,1109        fn,1110        'Assignment',1111      );11121113      return;1114    }1115    case 'ForOfStatement': {1116      const stmt = stmtPath as NodePath<t.ForOfStatement>;1117      const continuationBlock = builder.reserve('block');1118      const initBlock = builder.reserve('loop');1119      const testBlock = builder.reserve('loop');11201121      if (stmt.node.await) {1122        builder.recordError(1123          new CompilerErrorDetail({1124            reason: `(BuildHIR::lowerStatement) Handle for-await loops`,1125            category: ErrorCategory.Todo,1126            loc: stmt.node.loc ?? null,1127            suggestions: null,1128          }),1129        );1130        return;1131      }11321133      const loopBlock = builder.enter('block', _blockId => {1134        return builder.loop(label, initBlock.id, continuationBlock.id, () => {1135          const body = stmt.get('body');1136          lowerStatement(builder, body);1137          return {1138            kind: 'goto',1139            block: initBlock.id,1140            variant: GotoVariant.Continue,1141            id: makeInstructionId(0),1142            loc: body.node.loc ?? GeneratedSource,1143          };1144        });1145      });11461147      const loc = stmt.node.loc ?? GeneratedSource;1148      const value = lowerExpressionToTemporary(builder, stmt.get('right'));1149      builder.terminateWithContinuation(1150        {1151          kind: 'for-of',1152          loc,1153          init: initBlock.id,1154          test: testBlock.id,1155          loop: loopBlock,1156          fallthrough: continuationBlock.id,1157          id: makeInstructionId(0),1158        },1159        initBlock,1160      );11611162      /*1163       * The init of a ForOf statement is compound over a left (VariableDeclaration | LVal) and1164       * right (Expression), so we synthesize a new InstrValue and assignment (potentially multiple1165       * instructions when we handle other syntax like Patterns)1166       */1167      const iterator = lowerValueToTemporary(builder, {1168        kind: 'GetIterator',1169        loc: value.loc,1170        collection: {...value},1171      });1172      builder.terminateWithContinuation(1173        {1174          id: makeInstructionId(0),1175          kind: 'goto',1176          block: testBlock.id,1177          variant: GotoVariant.Break,1178          loc: stmt.node.loc ?? GeneratedSource,1179        },1180        testBlock,1181      );11821183      const left = stmt.get('left');1184      const leftLoc = left.node.loc ?? GeneratedSource;1185      let test: Place;1186      const advanceIterator = lowerValueToTemporary(builder, {1187        kind: 'IteratorNext',1188        loc: leftLoc,1189        iterator: {...iterator},1190        collection: {...value},1191      });1192      if (left.isVariableDeclaration()) {1193        const declarations = left.get('declarations');1194        CompilerError.invariant(declarations.length === 1, {1195          reason: `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,1196          loc: left.node.loc ?? GeneratedSource,1197        });1198        const id = declarations[0].get('id');1199        const assign = lowerAssignment(1200          builder,1201          leftLoc,1202          InstructionKind.Let,1203          id,1204          advanceIterator,1205          'Assignment',1206        );1207        test = lowerValueToTemporary(builder, assign);1208      } else {1209        CompilerError.invariant(left.isLVal(), {1210          reason: 'Expected ForOf init to be a variable declaration or lval',1211          loc: leftLoc,1212        });1213        const assign = lowerAssignment(1214          builder,1215          leftLoc,1216          InstructionKind.Reassign,1217          left,1218          advanceIterator,1219          'Assignment',1220        );1221        test = lowerValueToTemporary(builder, assign);1222      }1223      builder.terminateWithContinuation(1224        {1225          id: makeInstructionId(0),1226          kind: 'branch',1227          test,1228          consequent: loopBlock,1229          alternate: continuationBlock.id,1230          loc: stmt.node.loc ?? GeneratedSource,1231          fallthrough: continuationBlock.id,1232        },1233        continuationBlock,1234      );1235      return;1236    }1237    case 'ForInStatement': {1238      const stmt = stmtPath as NodePath<t.ForInStatement>;1239      const continuationBlock = builder.reserve('block');1240      const initBlock = builder.reserve('loop');12411242      const loopBlock = builder.enter('block', _blockId => {1243        return builder.loop(label, initBlock.id, continuationBlock.id, () => {1244          const body = stmt.get('body');1245          lowerStatement(builder, body);1246          return {1247            kind: 'goto',1248            block: initBlock.id,1249            variant: GotoVariant.Continue,1250            id: makeInstructionId(0),1251            loc: body.node.loc ?? GeneratedSource,1252          };1253        });1254      });12551256      const loc = stmt.node.loc ?? GeneratedSource;1257      const value = lowerExpressionToTemporary(builder, stmt.get('right'));1258      builder.terminateWithContinuation(1259        {1260          kind: 'for-in',1261          loc,1262          init: initBlock.id,1263          loop: loopBlock,1264          fallthrough: continuationBlock.id,1265          id: makeInstructionId(0),1266        },1267        initBlock,1268      );12691270      /*1271       * The init of a ForIn statement is compound over a left (VariableDeclaration | LVal) and1272       * right (Expression), so we synthesize a new InstrValue and assignment (potentially multiple1273       * instructions when we handle other syntax like Patterns)1274       */1275      const left = stmt.get('left');1276      const leftLoc = left.node.loc ?? GeneratedSource;1277      let test: Place;1278      const nextPropertyTemp = lowerValueToTemporary(builder, {1279        kind: 'NextPropertyOf',1280        loc: leftLoc,1281        value,1282      });1283      if (left.isVariableDeclaration()) {1284        const declarations = left.get('declarations');1285        CompilerError.invariant(declarations.length === 1, {1286          reason: `Expected only one declaration in the init of a ForInStatement, got ${declarations.length}`,1287          loc: left.node.loc ?? GeneratedSource,1288        });1289        const id = declarations[0].get('id');1290        const assign = lowerAssignment(1291          builder,1292          leftLoc,1293          InstructionKind.Let,1294          id,1295          nextPropertyTemp,1296          'Assignment',1297        );1298        test = lowerValueToTemporary(builder, assign);1299      } else {1300        CompilerError.invariant(left.isLVal(), {1301          reason: 'Expected ForIn init to be a variable declaration or lval',1302          loc: leftLoc,1303        });1304        const assign = lowerAssignment(1305          builder,1306          leftLoc,1307          InstructionKind.Reassign,1308          left,1309          nextPropertyTemp,1310          'Assignment',1311        );1312        test = lowerValueToTemporary(builder, assign);1313      }1314      builder.terminateWithContinuation(1315        {1316          id: makeInstructionId(0),1317          kind: 'branch',1318          test,1319          consequent: loopBlock,1320          alternate: continuationBlock.id,1321          fallthrough: continuationBlock.id,1322          loc: stmt.node.loc ?? GeneratedSource,1323        },1324        continuationBlock,1325      );1326      return;1327    }1328    case 'DebuggerStatement': {1329      const stmt = stmtPath as NodePath<t.DebuggerStatement>;1330      const loc = stmt.node.loc ?? GeneratedSource;1331      builder.push({1332        id: makeInstructionId(0),1333        lvalue: buildTemporaryPlace(builder, loc),1334        value: {1335          kind: 'Debugger',1336          loc,1337        },1338        effects: null,1339        loc,1340      });1341      return;1342    }1343    case 'EmptyStatement': {1344      return;1345    }1346    case 'TryStatement': {1347      const stmt = stmtPath as NodePath<t.TryStatement>;1348      const continuationBlock = builder.reserve('block');13491350      const handlerPath = stmt.get('handler');1351      if (!hasNode(handlerPath)) {1352        builder.recordError(1353          new CompilerErrorDetail({1354            reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch clause`,1355            category: ErrorCategory.Todo,1356            loc: stmt.node.loc ?? null,1357            suggestions: null,1358          }),1359        );1360        return;1361      }1362      if (hasNode(stmt.get('finalizer'))) {1363        builder.recordError(1364          new CompilerErrorDetail({1365            reason: `(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`,1366            category: ErrorCategory.Todo,1367            loc: stmt.node.loc ?? null,1368            suggestions: null,1369          }),1370        );1371      }13721373      const handlerBindingPath = handlerPath.get('param');1374      let handlerBinding: {1375        place: Place;1376        path: NodePath<t.Identifier | t.ArrayPattern | t.ObjectPattern>;1377      } | null = null;1378      if (hasNode(handlerBindingPath)) {1379        const place: Place = {1380          kind: 'Identifier',1381          identifier: builder.makeTemporary(1382            handlerBindingPath.node.loc ?? GeneratedSource,1383          ),1384          effect: Effect.Unknown,1385          reactive: false,1386          loc: handlerBindingPath.node.loc ?? GeneratedSource,1387        };1388        promoteTemporary(place.identifier);1389        lowerValueToTemporary(builder, {1390          kind: 'DeclareLocal',1391          lvalue: {1392            kind: InstructionKind.Catch,1393            place: {...place},1394          },1395          type: null,1396          loc: handlerBindingPath.node.loc ?? GeneratedSource,1397        });13981399        handlerBinding = {1400          path: handlerBindingPath,1401          place,1402        };1403      }14041405      const handler = builder.enter('catch', _blockId => {1406        if (handlerBinding !== null) {1407          lowerAssignment(1408            builder,1409            handlerBinding.path.node.loc ?? GeneratedSource,1410            InstructionKind.Catch,1411            handlerBinding.path,1412            {...handlerBinding.place},1413            'Assignment',1414          );1415        }1416        lowerStatement(builder, handlerPath.get('body'));1417        return {1418          kind: 'goto',1419          block: continuationBlock.id,1420          variant: GotoVariant.Break,1421          id: makeInstructionId(0),1422          loc: handlerPath.node.loc ?? GeneratedSource,1423        };1424      });14251426      const block = builder.enter('block', _blockId => {1427        const block = stmt.get('block');1428        builder.enterTryCatch(handler, () => {1429          lowerStatement(builder, block);1430        });1431        return {1432          kind: 'goto',1433          block: continuationBlock.id,1434          variant: GotoVariant.Try,1435          id: makeInstructionId(0),1436          loc: block.node.loc ?? GeneratedSource,1437        };1438      });14391440      builder.terminateWithContinuation(1441        {1442          kind: 'try',1443          block,1444          handlerBinding:1445            handlerBinding !== null ? {...handlerBinding.place} : null,1446          handler,1447          fallthrough: continuationBlock.id,1448          id: makeInstructionId(0),1449          loc: stmt.node.loc ?? GeneratedSource,1450        },1451        continuationBlock,1452      );14531454      return;1455    }1456    case 'WithStatement': {1457      builder.recordError(1458        new CompilerErrorDetail({1459          reason: `JavaScript 'with' syntax is not supported`,1460          description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`,1461          category: ErrorCategory.UnsupportedSyntax,1462          loc: stmtPath.node.loc ?? null,1463          suggestions: null,1464        }),1465      );1466      lowerValueToTemporary(builder, {1467        kind: 'UnsupportedNode',1468        loc: stmtPath.node.loc ?? GeneratedSource,1469        node: stmtPath.node,1470      });1471      return;1472    }1473    case 'ClassDeclaration': {1474      /**1475       * In theory we could support inline class declarations, but this is rare enough in practice1476       * and complex enough to support that we don't anticipate supporting anytime soon. Developers1477       * are encouraged to lift classes out of component/hook declarations.1478       */1479      builder.recordError(1480        new CompilerErrorDetail({1481          reason: 'Inline `class` declarations are not supported',1482          description: `Move class declarations outside of components/hooks`,1483          category: ErrorCategory.UnsupportedSyntax,1484          loc: stmtPath.node.loc ?? null,1485          suggestions: null,1486        }),1487      );1488      lowerValueToTemporary(builder, {1489        kind: 'UnsupportedNode',1490        loc: stmtPath.node.loc ?? GeneratedSource,1491        node: stmtPath.node,1492      });1493      return;1494    }1495    case 'EnumDeclaration':1496    case 'TSEnumDeclaration': {1497      lowerValueToTemporary(builder, {1498        kind: 'UnsupportedNode',1499        loc: stmtPath.node.loc ?? GeneratedSource,1500        node: stmtPath.node,1501      });1502      return;1503    }1504    case 'ExportAllDeclaration':1505    case 'ExportDefaultDeclaration':1506    case 'ExportNamedDeclaration':1507    case 'ImportDeclaration':1508    case 'TSExportAssignment':1509    case 'TSImportEqualsDeclaration': {1510      builder.recordError(1511        new CompilerErrorDetail({1512          reason:1513            'JavaScript `import` and `export` statements may only appear at the top level of a module',1514          category: ErrorCategory.Syntax,1515          loc: stmtPath.node.loc ?? null,1516          suggestions: null,1517        }),1518      );1519      lowerValueToTemporary(builder, {1520        kind: 'UnsupportedNode',1521        loc: stmtPath.node.loc ?? GeneratedSource,1522        node: stmtPath.node,1523      });1524      return;1525    }1526    case 'TSNamespaceExportDeclaration': {1527      builder.recordError(1528        new CompilerErrorDetail({1529          reason:1530            'TypeScript `namespace` statements may only appear at the top level of a module',1531          category: ErrorCategory.Syntax,1532          loc: stmtPath.node.loc ?? null,1533          suggestions: null,1534        }),1535      );1536      lowerValueToTemporary(builder, {1537        kind: 'UnsupportedNode',1538        loc: stmtPath.node.loc ?? GeneratedSource,1539        node: stmtPath.node,1540      });1541      return;1542    }1543    case 'DeclareClass':1544    case 'DeclareExportAllDeclaration':1545    case 'DeclareExportDeclaration':1546    case 'DeclareFunction':1547    case 'DeclareInterface':1548    case 'DeclareModule':1549    case 'DeclareModuleExports':1550    case 'DeclareOpaqueType':1551    case 'DeclareTypeAlias':1552    case 'DeclareVariable':1553    case 'InterfaceDeclaration':1554    case 'OpaqueType':1555    case 'TSDeclareFunction':1556    case 'TSInterfaceDeclaration':1557    case 'TSModuleDeclaration':1558    case 'TSTypeAliasDeclaration':1559    case 'TypeAlias': {1560      // We do not preserve type annotations/syntax through transformation1561      return;1562    }1563    default: {1564      return assertExhaustive(1565        stmtNode,1566        `Unsupported statement kind '${1567          (stmtNode as any as NodePath<t.Statement>).type1568        }'`,1569      );1570    }1571  }1572}15731574function lowerObjectMethod(1575  builder: HIRBuilder,1576  property: NodePath<t.ObjectMethod>,1577): InstructionValue {1578  const loc = property.node.loc ?? GeneratedSource;1579  const loweredFunc = lowerFunction(builder, property);15801581  return {1582    kind: 'ObjectMethod',1583    loc,1584    loweredFunc,1585  };1586}15871588function lowerObjectPropertyKey(1589  builder: HIRBuilder,1590  property: NodePath<t.ObjectProperty | t.ObjectMethod>,1591): ObjectPropertyKey | null {1592  const key = property.get('key');1593  if (key.isStringLiteral()) {1594    return {1595      kind: 'string',1596      name: key.node.value,1597    };1598  } else if (property.node.computed && key.isExpression()) {1599    const place = lowerExpressionToTemporary(builder, key);1600    return {1601      kind: 'computed',1602      name: place,1603    };1604  } else if (key.isIdentifier()) {1605    return {1606      kind: 'identifier',1607      name: key.node.name,1608    };1609  } else if (key.isNumericLiteral()) {1610    return {1611      kind: 'identifier',1612      name: String(key.node.value),1613    };1614  }16151616  builder.recordError(1617    new CompilerErrorDetail({1618      reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,1619      category: ErrorCategory.Todo,1620      loc: key.node.loc ?? null,1621      suggestions: null,1622    }),1623  );1624  return null;1625}16261627function lowerExpression(1628  builder: HIRBuilder,1629  exprPath: NodePath<t.Expression>,1630): InstructionValue {1631  const exprNode = exprPath.node;1632  const exprLoc = exprNode.loc ?? GeneratedSource;1633  switch (exprNode.type) {1634    case 'Identifier': {1635      const expr = exprPath as NodePath<t.Identifier>;1636      const place = lowerIdentifier(builder, expr);1637      return {1638        kind: getLoadKind(builder, expr),1639        place,1640        loc: exprLoc,1641      };1642    }1643    case 'NullLiteral': {1644      return {1645        kind: 'Primitive',1646        value: null,1647        loc: exprLoc,1648      };1649    }1650    case 'BooleanLiteral':1651    case 'NumericLiteral':1652    case 'StringLiteral': {1653      const expr = exprPath as NodePath<1654        t.StringLiteral | t.BooleanLiteral | t.NumericLiteral1655      >;1656      const value = expr.node.value;1657      return {1658        kind: 'Primitive',1659        value,1660        loc: exprLoc,1661      };1662    }1663    case 'ObjectExpression': {1664      const expr = exprPath as NodePath<t.ObjectExpression>;1665      const propertyPaths = expr.get('properties');1666      const properties: Array<ObjectProperty | SpreadPattern> = [];1667      for (const propertyPath of propertyPaths) {1668        if (propertyPath.isObjectProperty()) {1669          const loweredKey = lowerObjectPropertyKey(builder, propertyPath);1670          if (!loweredKey) {1671            continue;1672          }1673          const valuePath = propertyPath.get('value');1674          if (!valuePath.isExpression()) {1675            builder.recordError(1676              new CompilerErrorDetail({1677                reason: `(BuildHIR::lowerExpression) Handle ${valuePath.type} values in ObjectExpression`,1678                category: ErrorCategory.Todo,1679                loc: valuePath.node.loc ?? null,1680                suggestions: null,1681              }),1682            );1683            continue;1684          }1685          const value = lowerExpressionToTemporary(builder, valuePath);1686          properties.push({1687            kind: 'ObjectProperty',1688            type: 'property',1689            place: value,1690            key: loweredKey,1691          });1692        } else if (propertyPath.isSpreadElement()) {1693          const place = lowerExpressionToTemporary(1694            builder,1695            propertyPath.get('argument'),1696          );1697          properties.push({1698            kind: 'Spread',1699            place,1700          });1701        } else if (propertyPath.isObjectMethod()) {1702          if (propertyPath.node.kind !== 'method') {1703            builder.recordError(1704              new CompilerErrorDetail({1705                reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.node.kind} functions in ObjectExpression`,1706                category: ErrorCategory.Todo,1707                loc: propertyPath.node.loc ?? null,1708                suggestions: null,1709              }),1710            );1711            continue;1712          }1713          const method = lowerObjectMethod(builder, propertyPath);1714          const place = lowerValueToTemporary(builder, method);1715          const loweredKey = lowerObjectPropertyKey(builder, propertyPath);1716          if (!loweredKey) {1717            continue;1718          }1719          properties.push({1720            kind: 'ObjectProperty',1721            type: 'method',1722            place,1723            key: loweredKey,1724          });1725        } else {1726          builder.recordError(1727            new CompilerErrorDetail({1728              reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.type} properties in ObjectExpression`,1729              category: ErrorCategory.Todo,1730              loc: propertyPath.node.loc ?? null,1731              suggestions: null,1732            }),1733          );1734          continue;1735        }1736      }1737      return {1738        kind: 'ObjectExpression',1739        properties,1740        loc: exprLoc,1741      };1742    }1743    case 'ArrayExpression': {1744      const expr = exprPath as NodePath<t.ArrayExpression>;1745      let elements: ArrayExpression['elements'] = [];1746      for (const element of expr.get('elements')) {1747        if (element.node == null) {1748          elements.push({1749            kind: 'Hole',1750          });1751          continue;1752        } else if (element.isExpression()) {1753          elements.push(lowerExpressionToTemporary(builder, element));1754        } else if (element.isSpreadElement()) {1755          const place = lowerExpressionToTemporary(1756            builder,1757            element.get('argument'),1758          );1759          elements.push({kind: 'Spread', place});1760        } else {1761          builder.recordError(1762            new CompilerErrorDetail({1763              reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,1764              category: ErrorCategory.Todo,1765              loc: element.node.loc ?? null,1766              suggestions: null,1767            }),1768          );1769          continue;1770        }1771      }1772      return {1773        kind: 'ArrayExpression',1774        elements,1775        loc: exprLoc,1776      };1777    }1778    case 'NewExpression': {1779      const expr = exprPath as NodePath<t.NewExpression>;1780      const calleePath = expr.get('callee');1781      if (!calleePath.isExpression()) {1782        builder.recordError(1783          new CompilerErrorDetail({1784            reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`,1785            description: `Got a \`${calleePath.node.type}\``,1786            category: ErrorCategory.Syntax,1787            loc: calleePath.node.loc ?? null,1788            suggestions: null,1789          }),1790        );1791        return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc};1792      }1793      const callee = lowerExpressionToTemporary(builder, calleePath);1794      const args = lowerArguments(builder, expr.get('arguments'));17951796      return {1797        kind: 'NewExpression',1798        callee,1799        args,1800        loc: exprLoc,1801      };1802    }1803    case 'OptionalCallExpression': {1804      const expr = exprPath as NodePath<t.OptionalCallExpression>;1805      return lowerOptionalCallExpression(builder, expr, null);1806    }1807    case 'CallExpression': {1808      const expr = exprPath as NodePath<t.CallExpression>;1809      const calleePath = expr.get('callee');1810      if (!calleePath.isExpression()) {1811        builder.recordError(1812          new CompilerErrorDetail({1813            reason: `Expected Expression, got ${calleePath.type} in CallExpression (v8 intrinsics not supported). This error is likely caused by a bug in React Compiler. Please file an issue`,1814            category: ErrorCategory.Todo,1815            loc: calleePath.node.loc ?? null,1816            suggestions: null,1817          }),1818        );1819        return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc};1820      }1821      if (calleePath.isMemberExpression()) {1822        const memberExpr = lowerMemberExpression(builder, calleePath);1823        const propertyPlace = lowerValueToTemporary(builder, memberExpr.value);1824        const args = lowerArguments(builder, expr.get('arguments'));1825        return {1826          kind: 'MethodCall',1827          receiver: memberExpr.object,1828          property: {...propertyPlace},1829          args,1830          loc: exprLoc,1831        };1832      } else {1833        const callee = lowerExpressionToTemporary(builder, calleePath);1834        const args = lowerArguments(builder, expr.get('arguments'));1835        return {1836          kind: 'CallExpression',1837          callee,1838          args,1839          loc: exprLoc,1840        };1841      }1842    }1843    case 'BinaryExpression': {1844      const expr = exprPath as NodePath<t.BinaryExpression>;1845      const leftPath = expr.get('left');1846      if (!leftPath.isExpression()) {1847        builder.recordError(1848          new CompilerErrorDetail({1849            reason: `(BuildHIR::lowerExpression) Expected Expression, got ${leftPath.type} lval in BinaryExpression`,1850            category: ErrorCategory.Todo,1851            loc: leftPath.node.loc ?? null,1852            suggestions: null,1853          }),1854        );1855        return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc};1856      }1857      const left = lowerExpressionToTemporary(builder, leftPath);1858      const right = lowerExpressionToTemporary(builder, expr.get('right'));1859      const operator = expr.node.operator;1860      if (operator === '|>') {1861        builder.recordError(1862          new CompilerErrorDetail({1863            reason: `(BuildHIR::lowerExpression) Pipe operator not supported`,1864            category: ErrorCategory.Todo,1865            loc: leftPath.node.loc ?? null,1866            suggestions: null,1867          }),1868        );1869        return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc};1870      }1871      return {1872        kind: 'BinaryExpression',1873        operator,1874        left,1875        right,1876        loc: exprLoc,1877      };1878    }1879    case 'SequenceExpression': {1880      const expr = exprPath as NodePath<t.SequenceExpression>;1881      const exprLoc = expr.node.loc ?? GeneratedSource;18821883      const continuationBlock = builder.reserve(builder.currentBlockKind());1884      const place = buildTemporaryPlace(builder, exprLoc);18851886      const sequenceBlock = builder.enter('sequence', _ => {1887        let last: Place | null = null;1888        for (const item of expr.get('expressions')) {1889          last = lowerExpressionToTemporary(builder, item);1890        }1891        if (last === null) {1892          builder.recordError(1893            new CompilerErrorDetail({1894              reason: `Expected sequence expression to have at least one expression`,1895              category: ErrorCategory.Syntax,1896              loc: expr.node.loc ?? null,1897              suggestions: null,1898            }),1899          );1900        } else {1901          lowerValueToTemporary(builder, {1902            kind: 'StoreLocal',1903            lvalue: {kind: InstructionKind.Const, place: {...place}},1904            value: last,1905            type: null,1906            loc: exprLoc,1907          });1908        }1909        return {1910          kind: 'goto',1911          id: makeInstructionId(0),1912          block: continuationBlock.id,1913          loc: exprLoc,1914          variant: GotoVariant.Break,1915        };1916      });19171918      builder.terminateWithContinuation(1919        {1920          kind: 'sequence',1921          block: sequenceBlock,1922          fallthrough: continuationBlock.id,1923          id: makeInstructionId(0),1924          loc: exprLoc,1925        },1926        continuationBlock,1927      );1928      return {kind: 'LoadLocal', place, loc: place.loc};1929    }1930    case 'ConditionalExpression': {1931      const expr = exprPath as NodePath<t.ConditionalExpression>;1932      const exprLoc = expr.node.loc ?? GeneratedSource;19331934      //  Block for code following the if1935      const continuationBlock = builder.reserve(builder.currentBlockKind());1936      const testBlock = builder.reserve('value');1937      const place = buildTemporaryPlace(builder, exprLoc);19381939      //  Block for the consequent (if the test is truthy)1940      const consequentBlock = builder.enter('value', _blockId => {1941        const consequentPath = expr.get('consequent');1942        const consequent = lowerExpressionToTemporary(builder, consequentPath);1943        lowerValueToTemporary(builder, {1944          kind: 'StoreLocal',1945          lvalue: {kind: InstructionKind.Const, place: {...place}},1946          value: consequent,1947          type: null,1948          loc: exprLoc,1949        });1950        return {1951          kind: 'goto',1952          block: continuationBlock.id,1953          variant: GotoVariant.Break,1954          id: makeInstructionId(0),1955          loc: consequentPath.node.loc ?? GeneratedSource,1956        };1957      });1958      //  Block for the alternate (if the test is not truthy)1959      const alternateBlock = builder.enter('value', _blockId => {1960        const alternatePath = expr.get('alternate');1961        const alternate = lowerExpressionToTemporary(builder, alternatePath);1962        lowerValueToTemporary(builder, {1963          kind: 'StoreLocal',1964          lvalue: {kind: InstructionKind.Const, place: {...place}},1965          value: alternate,1966          type: null,1967          loc: exprLoc,1968        });1969        return {1970          kind: 'goto',1971          block: continuationBlock.id,1972          variant: GotoVariant.Break,1973          id: makeInstructionId(0),1974          loc: alternatePath.node.loc ?? GeneratedSource,1975        };1976      });19771978      builder.terminateWithContinuation(1979        {1980          kind: 'ternary',1981          fallthrough: continuationBlock.id,1982          id: makeInstructionId(0),1983          test: testBlock.id,1984          loc: exprLoc,1985        },1986        testBlock,1987      );1988      const testPlace = lowerExpressionToTemporary(builder, expr.get('test'));1989      builder.terminateWithContinuation(1990        {1991          kind: 'branch',1992          test: {...testPlace},1993          consequent: consequentBlock,1994          alternate: alternateBlock,1995          fallthrough: continuationBlock.id,1996          id: makeInstructionId(0),1997          loc: exprLoc,1998        },1999        continuationBlock,2000      );

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.