compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts TYPESCRIPT 2,480 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,480.
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 * as t from '@babel/types';9import {createHmac} from 'crypto';10import {11  pruneHoistedContexts,12  pruneUnusedLValues,13  pruneUnusedLabels,14  renameVariables,15} from '.';16import {17  CompilerError,18  CompilerErrorDetail,19  ErrorCategory,20} from '../CompilerError';21import {Environment, ExternalFunction} from '../HIR';22import {23  ArrayPattern,24  BlockId,25  DeclarationId,26  GeneratedSource,27  Identifier,28  IdentifierId,29  InstructionKind,30  JsxAttribute,31  ObjectMethod,32  ObjectPropertyKey,33  Pattern,34  Place,35  PrunedReactiveScopeBlock,36  ReactiveBlock,37  ReactiveFunction,38  ReactiveInstruction,39  ReactiveScope,40  ReactiveScopeBlock,41  ReactiveScopeDeclaration,42  ReactiveScopeDependency,43  ReactiveTerminal,44  ReactiveValue,45  SourceLocation,46  SpreadPattern,47  ValidIdentifierName,48  getHookKind,49  makeIdentifierName,50} from '../HIR/HIR';51import {printIdentifier, printInstruction, printPlace} from '../HIR/PrintHIR';52import {eachPatternOperand} from '../HIR/visitors';5354import {GuardKind} from '../Utils/RuntimeDiagnosticConstants';55import {assertExhaustive} from '../Utils/utils';56import {buildReactiveFunction} from './BuildReactiveFunction';57import {SINGLE_CHILD_FBT_TAGS} from './MemoizeFbtAndMacroOperandsInSameScope';58import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';59import {ReactFunctionType} from '../HIR/Environment';60import {ProgramContext} from '../Entrypoint';6162export const MEMO_CACHE_SENTINEL = 'react.memo_cache_sentinel';63export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel';6465export type CodegenFunction = {66  type: 'CodegenFunction';67  id: t.Identifier | null;68  nameHint: string | null;69  params: t.FunctionDeclaration['params'];70  body: t.BlockStatement;71  generator: boolean;72  async: boolean;73  loc: SourceLocation;7475  /*76   * Compiler info for logging and heuristics77   * Number of memo slots (value passed to useMemoCache)78   */79  memoSlotsUsed: number;80  /*81   * Number of memo *blocks* (reactive scopes) regardless of82   * how many inputs/outputs each block has83   */84  memoBlocks: number;8586  /**87   * Number of memoized values across all reactive scopes88   */89  memoValues: number;9091  /**92   * The number of reactive scopes that were created but had to be discarded93   * because they contained hook calls.94   */95  prunedMemoBlocks: number;9697  /**98   * The total number of values that should have been memoized but weren't99   * because they were part of a pruned memo block.100   */101  prunedMemoValues: number;102103  outlined: Array<{104    fn: CodegenFunction;105    type: ReactFunctionType | null;106  }>;107};108109export function codegenFunction(110  fn: ReactiveFunction,111  {112    uniqueIdentifiers,113    fbtOperands,114  }: {115    uniqueIdentifiers: Set<string>;116    fbtOperands: Set<IdentifierId>;117  },118): CodegenFunction {119  const cx = new Context(120    fn.env,121    fn.id ?? '[[ anonymous ]]',122    uniqueIdentifiers,123    fbtOperands,124    null,125  );126127  /**128   * Fast Refresh reuses component instances at runtime even as the source of the component changes.129   * The generated code needs to prevent values from one version of the code being reused after a code cange.130   * If HMR detection is enabled and we know the source code of the component, assign a cache slot to track131   * the source hash, and later, emit code to check for source changes and reset the cache on source changes.132   */133  let fastRefreshState: {134    cacheIndex: number;135    hash: string;136  } | null = null;137  if (138    fn.env.config.enableResetCacheOnSourceFileChanges &&139    fn.env.code !== null140  ) {141    const hash = createHmac('sha256', fn.env.code).digest('hex');142    fastRefreshState = {143      cacheIndex: cx.nextCacheIndex,144      hash,145    };146  }147148  const compiled = codegenReactiveFunction(cx, fn);149150  const hookGuard = fn.env.config.enableEmitHookGuards;151  if (hookGuard != null && fn.env.outputMode === 'client') {152    compiled.body = t.blockStatement([153      createHookGuard(154        hookGuard,155        fn.env.programContext,156        compiled.body.body,157        GuardKind.PushHookGuard,158        GuardKind.PopHookGuard,159      ),160    ]);161  }162163  const cacheCount = compiled.memoSlotsUsed;164  if (cacheCount !== 0) {165    const preface: Array<t.Statement> = [];166    const useMemoCacheIdentifier =167      fn.env.programContext.addMemoCacheImport().name;168169    // The import declaration for `useMemoCache` is inserted in the Babel plugin170    preface.push(171      t.variableDeclaration('const', [172        t.variableDeclarator(173          t.identifier(cx.synthesizeName('$')),174          t.callExpression(t.identifier(useMemoCacheIdentifier), [175            t.numericLiteral(cacheCount),176          ]),177        ),178      ]),179    );180    if (fastRefreshState !== null) {181      // HMR detection is enabled, emit code to reset the memo cache on source changes182      const index = cx.synthesizeName('$i');183      preface.push(184        t.ifStatement(185          t.binaryExpression(186            '!==',187            t.memberExpression(188              t.identifier(cx.synthesizeName('$')),189              t.numericLiteral(fastRefreshState.cacheIndex),190              true,191            ),192            t.stringLiteral(fastRefreshState.hash),193          ),194          t.blockStatement([195            t.forStatement(196              t.variableDeclaration('let', [197                t.variableDeclarator(t.identifier(index), t.numericLiteral(0)),198              ]),199              t.binaryExpression(200                '<',201                t.identifier(index),202                t.numericLiteral(cacheCount),203              ),204              t.assignmentExpression(205                '+=',206                t.identifier(index),207                t.numericLiteral(1),208              ),209              t.blockStatement([210                t.expressionStatement(211                  t.assignmentExpression(212                    '=',213                    t.memberExpression(214                      t.identifier(cx.synthesizeName('$')),215                      t.identifier(index),216                      true,217                    ),218                    t.callExpression(219                      t.memberExpression(220                        t.identifier('Symbol'),221                        t.identifier('for'),222                      ),223                      [t.stringLiteral(MEMO_CACHE_SENTINEL)],224                    ),225                  ),226                ),227              ]),228            ),229            t.expressionStatement(230              t.assignmentExpression(231                '=',232                t.memberExpression(233                  t.identifier(cx.synthesizeName('$')),234                  t.numericLiteral(fastRefreshState.cacheIndex),235                  true,236                ),237                t.stringLiteral(fastRefreshState.hash),238              ),239            ),240          ]),241        ),242      );243    }244    compiled.body.body.unshift(...preface);245  }246247  const emitInstrumentForget = fn.env.config.enableEmitInstrumentForget;248  if (249    emitInstrumentForget != null &&250    fn.id != null &&251    fn.env.outputMode === 'client'252  ) {253    /*254     * Technically, this is a conditional hook call. However, we expect255     * __DEV__ and gating identifier to be runtime constants256     */257    const gating =258      emitInstrumentForget.gating != null259        ? t.identifier(260            fn.env.programContext.addImportSpecifier(261              emitInstrumentForget.gating,262            ).name,263          )264        : null;265266    const globalGating =267      emitInstrumentForget.globalGating != null268        ? t.identifier(emitInstrumentForget.globalGating)269        : null;270271    if (emitInstrumentForget.globalGating != null) {272      const assertResult = fn.env.programContext.assertGlobalBinding(273        emitInstrumentForget.globalGating,274      );275      if (assertResult.isErr()) {276        fn.env.recordErrors(assertResult.unwrapErr());277      }278    }279280    let ifTest: t.Expression;281    if (gating != null && globalGating != null) {282      ifTest = t.logicalExpression('&&', globalGating, gating);283    } else if (gating != null) {284      ifTest = gating;285    } else {286      CompilerError.invariant(globalGating != null, {287        reason:288          'Bad config not caught! Expected at least one of gating or globalGating',289        loc: GeneratedSource,290      });291      ifTest = globalGating;292    }293294    const instrumentFnIdentifier = fn.env.programContext.addImportSpecifier(295      emitInstrumentForget.fn,296    ).name;297    const test: t.IfStatement = t.ifStatement(298      ifTest,299      t.expressionStatement(300        t.callExpression(t.identifier(instrumentFnIdentifier), [301          t.stringLiteral(fn.id),302          t.stringLiteral(fn.env.filename ?? ''),303        ]),304      ),305    );306    compiled.body.body.unshift(test);307  }308309  const outlined: CodegenFunction['outlined'] = [];310  for (const {fn: outlinedFunction, type} of cx.env.getOutlinedFunctions()) {311    const reactiveFunction = buildReactiveFunction(outlinedFunction);312    pruneUnusedLabels(reactiveFunction);313    pruneUnusedLValues(reactiveFunction);314    pruneHoistedContexts(reactiveFunction);315316    const identifiers = renameVariables(reactiveFunction);317    const codegen = codegenReactiveFunction(318      new Context(319        cx.env,320        reactiveFunction.id ?? '[[ anonymous ]]',321        identifiers,322        cx.fbtOperands,323      ),324      reactiveFunction,325    );326    outlined.push({fn: codegen, type});327  }328  compiled.outlined = outlined;329330  return compiled;331}332333function codegenReactiveFunction(334  cx: Context,335  fn: ReactiveFunction,336): CodegenFunction {337  for (const param of fn.params) {338    const place = param.kind === 'Identifier' ? param : param.place;339    cx.temp.set(place.identifier.declarationId, null);340    cx.declare(place.identifier);341  }342343  const params = fn.params.map(param => convertParameter(param));344  const body: t.BlockStatement = codegenBlock(cx, fn.body);345  body.directives = fn.directives.map(d => t.directive(t.directiveLiteral(d)));346  const statements = body.body;347  if (statements.length !== 0) {348    const last = statements[statements.length - 1];349    if (last.type === 'ReturnStatement' && last.argument == null) {350      statements.pop();351    }352  }353354  const countMemoBlockVisitor = new CountMemoBlockVisitor(fn.env);355  visitReactiveFunction(fn, countMemoBlockVisitor, undefined);356357  return {358    type: 'CodegenFunction',359    loc: fn.loc,360    id: fn.id !== null ? t.identifier(fn.id) : null,361    nameHint: fn.nameHint,362    params,363    body,364    generator: fn.generator,365    async: fn.async,366    memoSlotsUsed: cx.nextCacheIndex,367    memoBlocks: countMemoBlockVisitor.memoBlocks,368    memoValues: countMemoBlockVisitor.memoValues,369    prunedMemoBlocks: countMemoBlockVisitor.prunedMemoBlocks,370    prunedMemoValues: countMemoBlockVisitor.prunedMemoValues,371    outlined: [],372  };373}374375class CountMemoBlockVisitor extends ReactiveFunctionVisitor<void> {376  env: Environment;377  memoBlocks: number = 0;378  memoValues: number = 0;379  prunedMemoBlocks: number = 0;380  prunedMemoValues: number = 0;381382  constructor(env: Environment) {383    super();384    this.env = env;385  }386387  override visitScope(scopeBlock: ReactiveScopeBlock, state: void): void {388    this.memoBlocks += 1;389    this.memoValues += scopeBlock.scope.declarations.size;390    this.traverseScope(scopeBlock, state);391  }392393  override visitPrunedScope(394    scopeBlock: PrunedReactiveScopeBlock,395    state: void,396  ): void {397    this.prunedMemoBlocks += 1;398    this.prunedMemoValues += scopeBlock.scope.declarations.size;399    this.traversePrunedScope(scopeBlock, state);400  }401}402403function convertParameter(404  param: Place | SpreadPattern,405): t.Identifier | t.RestElement {406  if (param.kind === 'Identifier') {407    return convertIdentifier(param.identifier);408  } else {409    return t.restElement(convertIdentifier(param.place.identifier));410  }411}412413class Context {414  env: Environment;415  fnName: string;416  #nextCacheIndex: number = 0;417  /**418   * Tracks which named variables have been declared to dedupe declarations,419   * so this uses DeclarationId instead of IdentifierId420   */421  #declarations: Set<DeclarationId> = new Set();422  temp: Temporaries;423  objectMethods: Map<IdentifierId, ObjectMethod> = new Map();424  uniqueIdentifiers: Set<string>;425  fbtOperands: Set<IdentifierId>;426  synthesizedNames: Map<string, ValidIdentifierName> = new Map();427428  constructor(429    env: Environment,430    fnName: string,431    uniqueIdentifiers: Set<string>,432    fbtOperands: Set<IdentifierId>,433    temporaries: Temporaries | null = null,434  ) {435    this.env = env;436    this.fnName = fnName;437    this.uniqueIdentifiers = uniqueIdentifiers;438    this.fbtOperands = fbtOperands;439    this.temp = temporaries !== null ? new Map(temporaries) : new Map();440  }441442  recordError(error: CompilerErrorDetail): void {443    this.env.recordError(error);444  }445446  get nextCacheIndex(): number {447    return this.#nextCacheIndex++;448  }449450  declare(identifier: Identifier): void {451    this.#declarations.add(identifier.declarationId);452  }453454  hasDeclared(identifier: Identifier): boolean {455    return this.#declarations.has(identifier.declarationId);456  }457458  synthesizeName(name: string): ValidIdentifierName {459    const previous = this.synthesizedNames.get(name);460    if (previous !== undefined) {461      return previous;462    }463    let validated = makeIdentifierName(name).value;464    let index = 0;465    while (this.uniqueIdentifiers.has(validated)) {466      validated = makeIdentifierName(`${name}${index++}`).value;467    }468    this.uniqueIdentifiers.add(validated);469    this.synthesizedNames.set(name, validated);470    return validated;471  }472}473474function codegenBlock(cx: Context, block: ReactiveBlock): t.BlockStatement {475  const temp = new Map(cx.temp);476  const result = codegenBlockNoReset(cx, block);477  /*478   * Check that the block only added new temporaries and did not update the479   * value of any existing temporary480   */481  for (const [key, value] of cx.temp) {482    if (!temp.has(key)) {483      continue;484    }485    CompilerError.invariant(temp.get(key)! === value, {486      reason: 'Expected temporary value to be unchanged',487      loc: GeneratedSource,488    });489  }490  cx.temp = temp;491  return result;492}493494/*495 * Generates code for the block, without resetting the Context's temporary state.496 * This should not be used unless it is expected that temporaries from this block497 * can be referenced later, which is currently only true for sequence expressions498 * where the final `value` is expected to reference the temporary created in the499 * preceding instructions of the sequence.500 */501function codegenBlockNoReset(502  cx: Context,503  block: ReactiveBlock,504): t.BlockStatement {505  const statements: Array<t.Statement> = [];506  for (const item of block) {507    switch (item.kind) {508      case 'instruction': {509        const statement = codegenInstructionNullable(cx, item.instruction);510        if (statement !== null) {511          statements.push(statement);512        }513        break;514      }515      case 'pruned-scope': {516        const scopeBlock = codegenBlockNoReset(cx, item.instructions);517        statements.push(...scopeBlock.body);518        break;519      }520      case 'scope': {521        const temp = new Map(cx.temp);522        codegenReactiveScope(cx, statements, item.scope, item.instructions);523        cx.temp = temp;524        break;525      }526      case 'terminal': {527        const statement = codegenTerminal(cx, item.terminal);528        if (statement === null) {529          break;530        }531        if (item.label !== null && !item.label.implicit) {532          const block =533            statement.type === 'BlockStatement' && statement.body.length === 1534              ? statement.body[0]535              : statement;536          statements.push(537            t.labeledStatement(538              t.identifier(codegenLabel(item.label.id)),539              block,540            ),541          );542        } else if (statement.type === 'BlockStatement') {543          statements.push(...statement.body);544        } else {545          statements.push(statement);546        }547        break;548      }549      default: {550        assertExhaustive(551          item,552          `Unexpected item kind \`${(item as any).kind}\``,553        );554      }555    }556  }557  return t.blockStatement(statements);558}559560function codegenReactiveScope(561  cx: Context,562  statements: Array<t.Statement>,563  scope: ReactiveScope,564  block: ReactiveBlock,565): void {566  const cacheStoreStatements: Array<t.Statement> = [];567  const cacheLoadStatements: Array<t.Statement> = [];568  const cacheLoads: Array<{569    name: t.Identifier;570    index: number;571    value: t.Expression;572  }> = [];573  const changeExpressions: Array<t.Expression> = [];574575  for (const dep of [...scope.dependencies].sort(compareScopeDependency)) {576    const index = cx.nextCacheIndex;577    const comparison = t.binaryExpression(578      '!==',579      t.memberExpression(580        t.identifier(cx.synthesizeName('$')),581        t.numericLiteral(index),582        true,583      ),584      codegenDependency(cx, dep),585    );586    changeExpressions.push(comparison);587    /*588     * Adding directly to cacheStoreStatements rather than cacheLoads, because there589     * is no corresponding cacheLoadStatement for dependencies590     */591    cacheStoreStatements.push(592      t.expressionStatement(593        t.assignmentExpression(594          '=',595          t.memberExpression(596            t.identifier(cx.synthesizeName('$')),597            t.numericLiteral(index),598            true,599          ),600          codegenDependency(cx, dep),601        ),602      ),603    );604  }605  let firstOutputIndex: number | null = null;606607  for (const [, {identifier}] of [...scope.declarations].sort(([, a], [, b]) =>608    compareScopeDeclaration(a, b),609  )) {610    const index = cx.nextCacheIndex;611    if (firstOutputIndex === null) {612      firstOutputIndex = index;613    }614615    CompilerError.invariant(identifier.name != null, {616      reason: `Expected scope declaration identifier to be named`,617      description: `Declaration \`${printIdentifier(618        identifier,619      )}\` is unnamed in scope @${scope.id}`,620      loc: GeneratedSource,621    });622623    const name = convertIdentifier(identifier);624    if (!cx.hasDeclared(identifier)) {625      statements.push(626        t.variableDeclaration('let', [createVariableDeclarator(name, null)]),627      );628    }629    cacheLoads.push({name, index, value: name});630    cx.declare(identifier);631  }632  for (const reassignment of scope.reassignments) {633    const index = cx.nextCacheIndex;634    if (firstOutputIndex === null) {635      firstOutputIndex = index;636    }637    const name = convertIdentifier(reassignment);638    cacheLoads.push({name, index, value: name});639  }640641  let testCondition = (changeExpressions as Array<t.Expression>).reduce(642    (acc: t.Expression | null, ident: t.Expression) => {643      if (acc == null) {644        return ident;645      }646      return t.logicalExpression('||', acc, ident);647    },648    null as t.Expression | null,649  );650  if (testCondition === null) {651    CompilerError.invariant(firstOutputIndex !== null, {652      reason: `Expected scope to have at least one declaration`,653      description: `Scope '@${scope.id}' has no declarations`,654      loc: GeneratedSource,655    });656    testCondition = t.binaryExpression(657      '===',658      t.memberExpression(659        t.identifier(cx.synthesizeName('$')),660        t.numericLiteral(firstOutputIndex),661        true,662      ),663      t.callExpression(664        t.memberExpression(t.identifier('Symbol'), t.identifier('for')),665        [t.stringLiteral(MEMO_CACHE_SENTINEL)],666      ),667    );668  }669670  let computationBlock = codegenBlock(cx, block);671672  let memoStatement;673  for (const {name, index, value} of cacheLoads) {674    cacheStoreStatements.push(675      t.expressionStatement(676        t.assignmentExpression(677          '=',678          t.memberExpression(679            t.identifier(cx.synthesizeName('$')),680            t.numericLiteral(index),681            true,682          ),683          value,684        ),685      ),686    );687    cacheLoadStatements.push(688      t.expressionStatement(689        t.assignmentExpression(690          '=',691          name,692          t.memberExpression(693            t.identifier(cx.synthesizeName('$')),694            t.numericLiteral(index),695            true,696          ),697        ),698      ),699    );700  }701  computationBlock.body.push(...cacheStoreStatements);702  memoStatement = t.ifStatement(703    testCondition,704    computationBlock,705    t.blockStatement(cacheLoadStatements),706  );707708  statements.push(memoStatement);709710  const earlyReturnValue = scope.earlyReturnValue;711  if (earlyReturnValue !== null) {712    CompilerError.invariant(713      earlyReturnValue.value.name !== null &&714        earlyReturnValue.value.name.kind === 'named',715      {716        reason: `Expected early return value to be promoted to a named variable`,717        loc: earlyReturnValue.loc,718      },719    );720    const name: ValidIdentifierName = earlyReturnValue.value.name.value;721    statements.push(722      t.ifStatement(723        t.binaryExpression(724          '!==',725          t.identifier(name),726          t.callExpression(727            t.memberExpression(t.identifier('Symbol'), t.identifier('for')),728            [t.stringLiteral(EARLY_RETURN_SENTINEL)],729          ),730        ),731        t.blockStatement([t.returnStatement(t.identifier(name))]),732      ),733    );734  }735}736737function codegenTerminal(738  cx: Context,739  terminal: ReactiveTerminal,740): t.Statement | null {741  switch (terminal.kind) {742    case 'break': {743      if (terminal.targetKind === 'implicit') {744        return null;745      }746      return createBreakStatement(747        terminal.loc,748        terminal.targetKind === 'labeled'749          ? t.identifier(codegenLabel(terminal.target))750          : null,751      );752    }753    case 'continue': {754      if (terminal.targetKind === 'implicit') {755        return null;756      }757      return createContinueStatement(758        terminal.loc,759        terminal.targetKind === 'labeled'760          ? t.identifier(codegenLabel(terminal.target))761          : null,762      );763    }764    case 'for': {765      return createForStatement(766        terminal.loc,767        codegenForInit(cx, terminal.init),768        codegenInstructionValueToExpression(cx, terminal.test),769        terminal.update !== null770          ? codegenInstructionValueToExpression(cx, terminal.update)771          : null,772        codegenBlock(cx, terminal.loop),773      );774    }775    case 'for-in': {776      CompilerError.invariant(terminal.init.kind === 'SequenceExpression', {777        reason: `Expected a sequence expression init for for..in`,778        description: `Got \`${terminal.init.kind}\` expression instead`,779        loc: terminal.init.loc,780      });781      if (terminal.init.instructions.length !== 2) {782        cx.recordError(783          new CompilerErrorDetail({784            reason: 'Support non-trivial for..in inits',785            category: ErrorCategory.Todo,786            loc: terminal.init.loc,787            suggestions: null,788          }),789        );790        return t.emptyStatement();791      }792      const iterableCollection = terminal.init.instructions[0];793      const iterableItem = terminal.init.instructions[1];794      let lval: t.LVal;795      switch (iterableItem.value.kind) {796        case 'StoreLocal': {797          lval = codegenLValue(cx, iterableItem.value.lvalue.place);798          break;799        }800        case 'Destructure': {801          lval = codegenLValue(cx, iterableItem.value.lvalue.pattern);802          break;803        }804        case 'StoreContext': {805          cx.recordError(806            new CompilerErrorDetail({807              reason: 'Support non-trivial for..in inits',808              category: ErrorCategory.Todo,809              loc: terminal.init.loc,810              suggestions: null,811            }),812          );813          return t.emptyStatement();814        }815        default:816          CompilerError.invariant(false, {817            reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,818            description: `Found ${iterableItem.value.kind}`,819            loc: iterableItem.value.loc,820          });821      }822      let varDeclKind: 'const' | 'let';823      switch (iterableItem.value.lvalue.kind) {824        case InstructionKind.Const:825          varDeclKind = 'const' as const;826          break;827        case InstructionKind.Let:828          varDeclKind = 'let' as const;829          break;830        case InstructionKind.Reassign:831          CompilerError.invariant(false, {832            reason:833              'Destructure should never be Reassign as it would be an Object/ArrayPattern',834            loc: iterableItem.loc,835          });836        case InstructionKind.Catch:837        case InstructionKind.HoistedConst:838        case InstructionKind.HoistedLet:839        case InstructionKind.HoistedFunction:840        case InstructionKind.Function:841          CompilerError.invariant(false, {842            reason: `Unexpected ${iterableItem.value.lvalue.kind} variable in for..in collection`,843            loc: iterableItem.loc,844          });845        default:846          assertExhaustive(847            iterableItem.value.lvalue.kind,848            `Unhandled lvalue kind: ${iterableItem.value.lvalue.kind}`,849          );850      }851      return createForInStatement(852        terminal.loc,853        /*854         * Special handling here since we only want the VariableDeclarators without any inits855         * This needs to be updated when we handle non-trivial ForOf inits856         */857        createVariableDeclaration(iterableItem.value.loc, varDeclKind, [858          t.variableDeclarator(lval, null),859        ]),860        codegenInstructionValueToExpression(cx, iterableCollection.value),861        codegenBlock(cx, terminal.loop),862      );863    }864    case 'for-of': {865      CompilerError.invariant(866        terminal.init.kind === 'SequenceExpression' &&867          terminal.init.instructions.length === 1 &&868          terminal.init.instructions[0].value.kind === 'GetIterator',869        {870          reason: `Expected a single-expression sequence expression init for for..of`,871          description: `Got \`${terminal.init.kind}\` expression instead`,872          loc: terminal.init.loc,873        },874      );875      const iterableCollection = terminal.init.instructions[0].value;876877      CompilerError.invariant(terminal.test.kind === 'SequenceExpression', {878        reason: `Expected a sequence expression test for for..of`,879        description: `Got \`${terminal.init.kind}\` expression instead`,880        loc: terminal.test.loc,881      });882      if (terminal.test.instructions.length !== 2) {883        cx.recordError(884          new CompilerErrorDetail({885            reason: 'Support non-trivial for..of inits',886            category: ErrorCategory.Todo,887            loc: terminal.init.loc,888            suggestions: null,889          }),890        );891        return t.emptyStatement();892      }893      const iterableItem = terminal.test.instructions[1];894      let lval: t.LVal;895      switch (iterableItem.value.kind) {896        case 'StoreLocal': {897          lval = codegenLValue(cx, iterableItem.value.lvalue.place);898          break;899        }900        case 'Destructure': {901          lval = codegenLValue(cx, iterableItem.value.lvalue.pattern);902          break;903        }904        case 'StoreContext': {905          cx.recordError(906            new CompilerErrorDetail({907              reason: 'Support non-trivial for..of inits',908              category: ErrorCategory.Todo,909              loc: terminal.init.loc,910              suggestions: null,911            }),912          );913          return t.emptyStatement();914        }915        default:916          CompilerError.invariant(false, {917            reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,918            description: `Found ${iterableItem.value.kind}`,919            loc: iterableItem.value.loc,920          });921      }922      let varDeclKind: 'const' | 'let';923      switch (iterableItem.value.lvalue.kind) {924        case InstructionKind.Const:925          varDeclKind = 'const' as const;926          break;927        case InstructionKind.Let:928          varDeclKind = 'let' as const;929          break;930        case InstructionKind.Reassign:931        case InstructionKind.Catch:932        case InstructionKind.HoistedConst:933        case InstructionKind.HoistedLet:934        case InstructionKind.HoistedFunction:935        case InstructionKind.Function:936          CompilerError.invariant(false, {937            reason: `Unexpected ${iterableItem.value.lvalue.kind} variable in for..of collection`,938            loc: iterableItem.loc,939          });940        default:941          assertExhaustive(942            iterableItem.value.lvalue.kind,943            `Unhandled lvalue kind: ${iterableItem.value.lvalue.kind}`,944          );945      }946      return createForOfStatement(947        terminal.loc,948        /*949         * Special handling here since we only want the VariableDeclarators without any inits950         * This needs to be updated when we handle non-trivial ForOf inits951         */952        createVariableDeclaration(iterableItem.value.loc, varDeclKind, [953          t.variableDeclarator(lval, null),954        ]),955        codegenInstructionValueToExpression(cx, iterableCollection),956        codegenBlock(cx, terminal.loop),957      );958    }959    case 'if': {960      const test = codegenPlaceToExpression(cx, terminal.test);961      const consequent = codegenBlock(cx, terminal.consequent);962      let alternate: t.Statement | null = null;963      if (terminal.alternate !== null) {964        const block = codegenBlock(cx, terminal.alternate);965        if (block.body.length !== 0) {966          alternate = block;967        }968      }969      return createIfStatement(terminal.loc, test, consequent, alternate);970    }971    case 'return': {972      const value = codegenPlaceToExpression(cx, terminal.value);973      if (value.type === 'Identifier' && value.name === 'undefined') {974        // Use implicit undefined975        return createReturnStatement(terminal.loc);976      }977      return createReturnStatement(terminal.loc, value);978    }979    case 'switch': {980      return createSwitchStatement(981        terminal.loc,982        codegenPlaceToExpression(cx, terminal.test),983        terminal.cases.map(case_ => {984          const test =985            case_.test !== null986              ? codegenPlaceToExpression(cx, case_.test)987              : null;988          const block = codegenBlock(cx, case_.block!);989          return t.switchCase(test, block.body.length === 0 ? [] : [block]);990        }),991      );992    }993    case 'throw': {994      return createThrowStatement(995        terminal.loc,996        codegenPlaceToExpression(cx, terminal.value),997      );998    }999    case 'do-while': {1000      const test = codegenInstructionValueToExpression(cx, terminal.test);1001      return createDoWhileStatement(1002        terminal.loc,1003        test,1004        codegenBlock(cx, terminal.loop),1005      );1006    }1007    case 'while': {1008      const test = codegenInstructionValueToExpression(cx, terminal.test);1009      return createWhileStatement(1010        terminal.loc,1011        test,1012        codegenBlock(cx, terminal.loop),1013      );1014    }1015    case 'label': {1016      return codegenBlock(cx, terminal.block);1017    }1018    case 'try': {1019      let catchParam = null;1020      if (terminal.handlerBinding !== null) {1021        catchParam = convertIdentifier(terminal.handlerBinding.identifier);1022        cx.temp.set(terminal.handlerBinding.identifier.declarationId, null);1023      }1024      return createTryStatement(1025        terminal.loc,1026        codegenBlock(cx, terminal.block),1027        t.catchClause(catchParam, codegenBlock(cx, terminal.handler)),1028      );1029    }1030    default: {1031      assertExhaustive(1032        terminal,1033        `Unexpected terminal kind \`${(terminal as any).kind}\``,1034      );1035    }1036  }1037}10381039function codegenInstructionNullable(1040  cx: Context,1041  instr: ReactiveInstruction,1042): t.Statement | null {1043  if (1044    instr.value.kind === 'StoreLocal' ||1045    instr.value.kind === 'StoreContext' ||1046    instr.value.kind === 'Destructure' ||1047    instr.value.kind === 'DeclareLocal' ||1048    instr.value.kind === 'DeclareContext'1049  ) {1050    let kind: InstructionKind = instr.value.lvalue.kind;1051    let lvalue: Place | Pattern;1052    let value: t.Expression | null;1053    if (instr.value.kind === 'StoreLocal') {1054      kind = cx.hasDeclared(instr.value.lvalue.place.identifier)1055        ? InstructionKind.Reassign1056        : kind;1057      lvalue = instr.value.lvalue.place;1058      value = codegenPlaceToExpression(cx, instr.value.value);1059    } else if (instr.value.kind === 'StoreContext') {1060      lvalue = instr.value.lvalue.place;1061      value = codegenPlaceToExpression(cx, instr.value.value);1062    } else if (1063      instr.value.kind === 'DeclareLocal' ||1064      instr.value.kind === 'DeclareContext'1065    ) {1066      if (cx.hasDeclared(instr.value.lvalue.place.identifier)) {1067        return null;1068      }1069      kind = instr.value.lvalue.kind;1070      lvalue = instr.value.lvalue.place;1071      value = null;1072    } else {1073      lvalue = instr.value.lvalue.pattern;1074      for (const place of eachPatternOperand(lvalue)) {1075        if (1076          kind !== InstructionKind.Reassign &&1077          place.identifier.name === null1078        ) {1079          cx.temp.set(place.identifier.declarationId, null);1080        }1081      }1082      value = codegenPlaceToExpression(cx, instr.value.value);1083    }1084    switch (kind) {1085      case InstructionKind.Const: {1086        CompilerError.invariant(instr.lvalue === null, {1087          reason: `Const declaration cannot be referenced as an expression`,1088          message: `this is ${kind}`,1089          loc: instr.value.loc,1090        });1091        return createVariableDeclaration(instr.loc, 'const', [1092          createVariableDeclarator(codegenLValue(cx, lvalue), value),1093        ]);1094      }1095      case InstructionKind.Function: {1096        CompilerError.invariant(instr.lvalue === null, {1097          reason: `Function declaration cannot be referenced as an expression`,1098          loc: instr.value.loc,1099        });1100        const genLvalue = codegenLValue(cx, lvalue);1101        CompilerError.invariant(genLvalue.type === 'Identifier', {1102          reason: 'Expected an identifier as a function declaration lvalue',1103          loc: instr.value.loc,1104        });1105        CompilerError.invariant(value?.type === 'FunctionExpression', {1106          reason: 'Expected a function as a function declaration value',1107          description: `Got ${value == null ? String(value) : value.type} at ${printInstruction(instr)}`,1108          loc: instr.value.loc,1109        });1110        return createFunctionDeclaration(1111          instr.loc,1112          genLvalue,1113          value.params,1114          value.body,1115          value.generator,1116          value.async,1117        );1118      }1119      case InstructionKind.Let: {1120        CompilerError.invariant(instr.lvalue === null, {1121          reason: `Const declaration cannot be referenced as an expression`,1122          message: `this is ${kind}`,1123          loc: instr.value.loc,1124        });1125        return createVariableDeclaration(instr.loc, 'let', [1126          createVariableDeclarator(codegenLValue(cx, lvalue), value),1127        ]);1128      }1129      case InstructionKind.Reassign: {1130        CompilerError.invariant(value !== null, {1131          reason: 'Expected a value for reassignment',1132          loc: instr.value.loc,1133        });1134        const expr = t.assignmentExpression(1135          '=',1136          codegenLValue(cx, lvalue),1137          value,1138        );1139        if (instr.lvalue !== null) {1140          if (instr.value.kind !== 'StoreContext') {1141            cx.temp.set(instr.lvalue.identifier.declarationId, expr);1142            return null;1143          } else {1144            // Handle chained reassignments for context variables1145            const statement = codegenInstruction(cx, instr, expr);1146            if (statement.type === 'EmptyStatement') {1147              return null;1148            }1149            return statement;1150          }1151        } else {1152          return createExpressionStatement(instr.loc, expr);1153        }1154      }1155      case InstructionKind.Catch: {1156        return t.emptyStatement();1157      }1158      case InstructionKind.HoistedLet:1159      case InstructionKind.HoistedConst:1160      case InstructionKind.HoistedFunction: {1161        CompilerError.invariant(false, {1162          reason: `Expected ${kind} to have been pruned in PruneHoistedContexts`,1163          loc: instr.loc,1164        });1165      }1166      default: {1167        assertExhaustive(kind, `Unexpected instruction kind \`${kind}\``);1168      }1169    }1170  } else if (1171    instr.value.kind === 'StartMemoize' ||1172    instr.value.kind === 'FinishMemoize'1173  ) {1174    return null;1175  } else if (instr.value.kind === 'Debugger') {1176    return t.debuggerStatement();1177  } else if (instr.value.kind === 'ObjectMethod') {1178    CompilerError.invariant(instr.lvalue, {1179      reason: 'Expected object methods to have a temp lvalue',1180      loc: GeneratedSource,1181    });1182    cx.objectMethods.set(instr.lvalue.identifier.id, instr.value);1183    return null;1184  } else {1185    const value = codegenInstructionValue(cx, instr.value);1186    const statement = codegenInstruction(cx, instr, value);1187    if (statement.type === 'EmptyStatement') {1188      return null;1189    }1190    return statement;1191  }1192}11931194function codegenForInit(1195  cx: Context,1196  init: ReactiveValue,1197): t.Expression | t.VariableDeclaration | null {1198  if (init.kind === 'SequenceExpression') {1199    const body = codegenBlock(1200      cx,1201      init.instructions.map(instruction => ({1202        kind: 'instruction',1203        instruction,1204      })),1205    ).body;1206    const declarators: Array<t.VariableDeclarator> = [];1207    let kind: 'let' | 'const' = 'const';1208    body.forEach(instr => {1209      let top: undefined | t.VariableDeclarator = undefined;1210      if (1211        instr.type === 'ExpressionStatement' &&1212        instr.expression.type === 'AssignmentExpression' &&1213        instr.expression.operator === '=' &&1214        instr.expression.left.type === 'Identifier' &&1215        (top = declarators.at(-1))?.id.type === 'Identifier' &&1216        top?.id.name === instr.expression.left.name &&1217        top?.init == null1218      ) {1219        top.init = instr.expression.right;1220      } else {1221        CompilerError.invariant(1222          instr.type === 'VariableDeclaration' &&1223            (instr.kind === 'let' || instr.kind === 'const'),1224          {1225            reason: 'Expected a variable declaration',1226            description: `Got ${instr.type}`,1227            loc: init.loc,1228          },1229        );1230        if (instr.kind === 'let') {1231          kind = 'let';1232        }1233        declarators.push(...instr.declarations);1234      }1235    });1236    CompilerError.invariant(declarators.length > 0, {1237      reason: 'Expected a variable declaration',1238      loc: init.loc,1239    });1240    return t.variableDeclaration(kind, declarators);1241  } else {1242    return codegenInstructionValueToExpression(cx, init);1243  }1244}12451246function codegenDependency(1247  cx: Context,1248  dependency: ReactiveScopeDependency,1249): t.Expression {1250  let object: t.Expression = convertIdentifier(dependency.identifier);1251  if (dependency.path.length !== 0) {1252    const hasOptional = dependency.path.some(path => path.optional);1253    for (const path of dependency.path) {1254      const property =1255        typeof path.property === 'string'1256          ? t.identifier(path.property)1257          : t.numericLiteral(path.property);1258      const isComputed = typeof path.property !== 'string';1259      if (hasOptional) {1260        object = t.optionalMemberExpression(1261          object,1262          property,1263          isComputed,1264          path.optional,1265        );1266      } else {1267        object = t.memberExpression(object, property, isComputed);1268      }1269    }1270  }1271  return object;1272}12731274function withLoc<T extends (...args: Array<any>) => t.Node>(1275  fn: T,1276): (1277  loc: SourceLocation | null | undefined,1278  ...args: Parameters<T>1279) => ReturnType<T> {1280  return (1281    loc: SourceLocation | null | undefined,1282    ...args: Parameters<T>1283  ): ReturnType<T> => {1284    const node = fn(...args);1285    if (loc != null && loc != GeneratedSource) {1286      node.loc = loc;1287    }1288    return node as ReturnType<T>;1289  };1290}12911292const createIdentifier = withLoc(t.identifier);1293const createArrayPattern = withLoc(t.arrayPattern);1294const createObjectPattern = withLoc(t.objectPattern);1295const createBinaryExpression = withLoc(t.binaryExpression);1296const createExpressionStatement = withLoc(t.expressionStatement);1297const _createLabelledStatement = withLoc(t.labeledStatement);1298const createVariableDeclaration = withLoc(t.variableDeclaration);1299const createFunctionDeclaration = withLoc(t.functionDeclaration);1300const createWhileStatement = withLoc(t.whileStatement);1301const createDoWhileStatement = withLoc(t.doWhileStatement);1302const createSwitchStatement = withLoc(t.switchStatement);1303const createIfStatement = withLoc(t.ifStatement);1304const createForStatement = withLoc(t.forStatement);1305const createForOfStatement = withLoc(t.forOfStatement);1306const createForInStatement = withLoc(t.forInStatement);1307const createTaggedTemplateExpression = withLoc(t.taggedTemplateExpression);1308const createLogicalExpression = withLoc(t.logicalExpression);1309const createSequenceExpression = withLoc(t.sequenceExpression);1310const createConditionalExpression = withLoc(t.conditionalExpression);1311const createTemplateLiteral = withLoc(t.templateLiteral);1312const createJsxNamespacedName = withLoc(t.jsxNamespacedName);1313const createJsxElement = withLoc(t.jsxElement);1314const createJsxAttribute = withLoc(t.jsxAttribute);1315const createJsxIdentifier = withLoc(t.jsxIdentifier);1316const createJsxExpressionContainer = withLoc(t.jsxExpressionContainer);1317const createJsxText = withLoc(t.jsxText);1318const createJsxClosingElement = withLoc(t.jsxClosingElement);1319const createJsxOpeningElement = withLoc(t.jsxOpeningElement);1320const createStringLiteral = withLoc(t.stringLiteral);1321const createThrowStatement = withLoc(t.throwStatement);1322const createTryStatement = withLoc(t.tryStatement);1323const createBreakStatement = withLoc(t.breakStatement);1324const createContinueStatement = withLoc(t.continueStatement);1325const createReturnStatement = withLoc(t.returnStatement);13261327function createVariableDeclarator(1328  id: t.LVal,1329  init?: t.Expression | null,1330): t.VariableDeclarator {1331  const node = t.variableDeclarator(id, init);13321333  /*1334   * The variable declarator location is not preserved in HIR, however, we can use the1335   * start location of the id and the end location of the init to recreate the1336   * exact original variable declarator location.1337   *1338   * Or if init is null, we likely have a declaration without an initializer, so we can use the id.loc.end as the end location.1339   */1340  if (id.loc && (init === null || init?.loc)) {1341    node.loc = {1342      start: id.loc.start,1343      end: init?.loc?.end ?? id.loc.end,1344      filename: id.loc.filename,1345      identifierName: undefined,1346    };1347  }13481349  return node;1350}13511352function createHookGuard(1353  guard: ExternalFunction,1354  context: ProgramContext,1355  stmts: Array<t.Statement>,1356  before: GuardKind,1357  after: GuardKind,1358): t.TryStatement {1359  const guardFnName = context.addImportSpecifier(guard).name;1360  function createHookGuardImpl(kind: number): t.ExpressionStatement {1361    return t.expressionStatement(1362      t.callExpression(t.identifier(guardFnName), [t.numericLiteral(kind)]),1363    );1364  }13651366  return t.tryStatement(1367    t.blockStatement([createHookGuardImpl(before), ...stmts]),1368    null,1369    t.blockStatement([createHookGuardImpl(after)]),1370  );1371}13721373/**1374 * Create a call expression.1375 * If enableEmitHookGuards is set and the callExpression is a hook call,1376 * the following transform will be made.1377 * ```js1378 * // source1379 * useHook(arg1, arg2)1380 *1381 * // codegen1382 * (() => {1383 *   try {1384 *     $dispatcherGuard(PUSH_EXPECT_HOOK);1385 *     return useHook(arg1, arg2);1386 *   } finally {1387 *     $dispatcherGuard(POP_EXPECT_HOOK);1388 *   }1389 * })()1390 * ```1391 */1392function createCallExpression(1393  env: Environment,1394  callee: t.Expression,1395  args: Array<t.Expression | t.SpreadElement>,1396  loc: SourceLocation | null,1397  isHook: boolean,1398): t.CallExpression {1399  const callExpr = t.callExpression(callee, args);1400  if (loc != null && loc != GeneratedSource) {1401    callExpr.loc = loc;1402  }14031404  const hookGuard = env.config.enableEmitHookGuards;1405  if (hookGuard != null && isHook && env.outputMode === 'client') {1406    const iife = t.functionExpression(1407      null,1408      [],1409      t.blockStatement([1410        createHookGuard(1411          hookGuard,1412          env.programContext,1413          [t.returnStatement(callExpr)],1414          GuardKind.AllowHook,1415          GuardKind.DisallowHook,1416        ),1417      ]),1418    );1419    return t.callExpression(iife, []);1420  } else {1421    return callExpr;1422  }1423}14241425type Temporaries = Map<DeclarationId, t.Expression | t.JSXText | null>;14261427function codegenLabel(id: BlockId): string {1428  return `bb${id}`;1429}14301431function codegenInstruction(1432  cx: Context,1433  instr: ReactiveInstruction,1434  value: t.Expression | t.JSXText,1435): t.Statement {1436  if (t.isStatement(value)) {1437    return value;1438  }1439  if (instr.lvalue === null) {1440    return t.expressionStatement(convertValueToExpression(value));1441  }1442  if (instr.lvalue.identifier.name === null) {1443    // temporary1444    cx.temp.set(instr.lvalue.identifier.declarationId, value);1445    return t.emptyStatement();1446  } else {1447    const expressionValue = convertValueToExpression(value);1448    if (cx.hasDeclared(instr.lvalue.identifier)) {1449      return createExpressionStatement(1450        instr.loc,1451        t.assignmentExpression(1452          '=',1453          convertIdentifier(instr.lvalue.identifier),1454          expressionValue,1455        ),1456      );1457    } else {1458      return createVariableDeclaration(instr.loc, 'const', [1459        createVariableDeclarator(1460          convertIdentifier(instr.lvalue.identifier),1461          expressionValue,1462        ),1463      ]);1464    }1465  }1466}14671468function convertValueToExpression(1469  value: t.JSXText | t.Expression,1470): t.Expression {1471  if (value.type === 'JSXText') {1472    return createStringLiteral(value.loc, value.value);1473  }1474  return value;1475}14761477function codegenInstructionValueToExpression(1478  cx: Context,1479  instrValue: ReactiveValue,1480): t.Expression {1481  const value = codegenInstructionValue(cx, instrValue);1482  return convertValueToExpression(value);1483}14841485function codegenInstructionValue(1486  cx: Context,1487  instrValue: ReactiveValue,1488): t.Expression | t.JSXText {1489  let value: t.Expression | t.JSXText;1490  switch (instrValue.kind) {1491    case 'ArrayExpression': {1492      const elements = instrValue.elements.map(element => {1493        if (element.kind === 'Identifier') {1494          return codegenPlaceToExpression(cx, element);1495        } else if (element.kind === 'Spread') {1496          return t.spreadElement(codegenPlaceToExpression(cx, element.place));1497        } else {1498          return null;1499        }1500      });1501      value = t.arrayExpression(elements);1502      break;1503    }1504    case 'BinaryExpression': {1505      const left = codegenPlaceToExpression(cx, instrValue.left);1506      const right = codegenPlaceToExpression(cx, instrValue.right);1507      value = createBinaryExpression(1508        instrValue.loc,1509        instrValue.operator,1510        left,1511        right,1512      );1513      break;1514    }1515    case 'UnaryExpression': {1516      value = t.unaryExpression(1517        instrValue.operator,1518        codegenPlaceToExpression(cx, instrValue.value),1519      );1520      break;1521    }1522    case 'Primitive': {1523      value = codegenValue(cx, instrValue.loc, instrValue.value);1524      break;1525    }1526    case 'CallExpression': {1527      if (cx.env.config.enableForest) {1528        const callee = codegenPlaceToExpression(cx, instrValue.callee);1529        const args = instrValue.args.map(arg => codegenArgument(cx, arg));1530        value = t.callExpression(callee, args);1531        if (instrValue.typeArguments != null) {1532          value.typeArguments = t.typeParameterInstantiation(1533            instrValue.typeArguments,1534          );1535        }1536        break;1537      }1538      const isHook = getHookKind(cx.env, instrValue.callee.identifier) != null;1539      const callee = codegenPlaceToExpression(cx, instrValue.callee);1540      const args = instrValue.args.map(arg => codegenArgument(cx, arg));1541      value = createCallExpression(1542        cx.env,1543        callee,1544        args,1545        instrValue.loc,1546        isHook,1547      );1548      break;1549    }1550    case 'OptionalExpression': {1551      const optionalValue = codegenInstructionValueToExpression(1552        cx,1553        instrValue.value,1554      );1555      switch (optionalValue.type) {1556        case 'OptionalCallExpression':1557        case 'CallExpression': {1558          CompilerError.invariant(t.isExpression(optionalValue.callee), {1559            reason: 'v8 intrinsics are validated during lowering',1560            loc: optionalValue.callee.loc ?? GeneratedSource,1561          });1562          value = t.optionalCallExpression(1563            optionalValue.callee,1564            optionalValue.arguments,1565            instrValue.optional,1566          );1567          break;1568        }1569        case 'OptionalMemberExpression':1570        case 'MemberExpression': {1571          const property = optionalValue.property;1572          CompilerError.invariant(t.isExpression(property), {1573            reason: 'Private names are validated during lowering',1574            loc: property.loc ?? GeneratedSource,1575          });1576          value = t.optionalMemberExpression(1577            optionalValue.object,1578            property,1579            optionalValue.computed,1580            instrValue.optional,1581          );1582          break;1583        }1584        default: {1585          CompilerError.invariant(false, {1586            reason:1587              'Expected an optional value to resolve to a call expression or member expression',1588            description: `Got a \`${optionalValue.type}\``,1589            loc: instrValue.loc,1590          });1591        }1592      }1593      break;1594    }1595    case 'MethodCall': {1596      const isHook =1597        getHookKind(cx.env, instrValue.property.identifier) != null;1598      const memberExpr = codegenPlaceToExpression(cx, instrValue.property);1599      CompilerError.invariant(1600        t.isMemberExpression(memberExpr) ||1601          t.isOptionalMemberExpression(memberExpr),1602        {1603          reason:1604            '[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression',1605          message: `Got: '${memberExpr.type}'`,1606          loc: memberExpr.loc ?? GeneratedSource,1607        },1608      );1609      CompilerError.invariant(1610        t.isNodesEquivalent(1611          memberExpr.object,1612          codegenPlaceToExpression(cx, instrValue.receiver),1613        ),1614        {1615          reason:1616            '[Codegen] Internal error: Forget should always generate MethodCall::property ' +1617            'as a MemberExpression of MethodCall::receiver',1618          loc: memberExpr.loc ?? GeneratedSource,1619        },1620      );1621      const args = instrValue.args.map(arg => codegenArgument(cx, arg));1622      value = createCallExpression(1623        cx.env,1624        memberExpr,1625        args,1626        instrValue.loc,1627        isHook,1628      );1629      break;1630    }1631    case 'NewExpression': {1632      const callee = codegenPlaceToExpression(cx, instrValue.callee);1633      const args = instrValue.args.map(arg => codegenArgument(cx, arg));1634      value = t.newExpression(callee, args);1635      break;1636    }1637    case 'ObjectExpression': {1638      const properties = [];1639      for (const property of instrValue.properties) {1640        if (property.kind === 'ObjectProperty') {1641          const key = codegenObjectPropertyKey(cx, property.key);16421643          switch (property.type) {1644            case 'property': {1645              const value = codegenPlaceToExpression(cx, property.place);1646              properties.push(1647                t.objectProperty(1648                  key,1649                  value,1650                  property.key.kind === 'computed',1651                  key.type === 'Identifier' &&1652                    value.type === 'Identifier' &&1653                    value.name === key.name,1654                ),1655              );1656              break;1657            }1658            case 'method': {1659              const method = cx.objectMethods.get(property.place.identifier.id);1660              CompilerError.invariant(method, {1661                reason: 'Expected ObjectMethod instruction',1662                loc: GeneratedSource,1663              });1664              const loweredFunc = method.loweredFunc;1665              const reactiveFunction = buildReactiveFunction(loweredFunc.func);1666              pruneUnusedLabels(reactiveFunction);1667              pruneUnusedLValues(reactiveFunction);1668              const fn = codegenReactiveFunction(1669                new Context(1670                  cx.env,1671                  reactiveFunction.id ?? '[[ anonymous ]]',1672                  cx.uniqueIdentifiers,1673                  cx.fbtOperands,1674                  cx.temp,1675                ),1676                reactiveFunction,1677              );16781679              /*1680               * ObjectMethod builder must be backwards compatible with older versions of babel.1681               * https://github.com/babel/babel/blob/v7.7.4/packages/babel-types/src/definitions/core.js#L599-L6031682               */1683              const babelNode = t.objectMethod(1684                'method',1685                key,1686                fn.params,1687                fn.body,1688                false,1689              );1690              babelNode.async = fn.async;1691              babelNode.generator = fn.generator;1692              properties.push(babelNode);1693              break;1694            }1695            default:1696              assertExhaustive(1697                property.type,1698                `Unexpected property type: ${property.type}`,1699              );1700          }1701        } else {1702          properties.push(1703            t.spreadElement(codegenPlaceToExpression(cx, property.place)),1704          );1705        }1706      }1707      value = t.objectExpression(properties);1708      break;1709    }1710    case 'JSXText': {1711      value = createJsxText(instrValue.loc, instrValue.value);1712      break;1713    }1714    case 'JsxExpression': {1715      const attributes: Array<t.JSXAttribute | t.JSXSpreadAttribute> = [];1716      for (const attribute of instrValue.props) {1717        attributes.push(codegenJsxAttribute(cx, attribute));1718      }1719      let tagValue =1720        instrValue.tag.kind === 'Identifier'1721          ? codegenPlaceToExpression(cx, instrValue.tag)1722          : t.stringLiteral(instrValue.tag.name);1723      let tag: t.JSXIdentifier | t.JSXNamespacedName | t.JSXMemberExpression;1724      if (tagValue.type === 'Identifier') {1725        tag = createJsxIdentifier(instrValue.tag.loc, tagValue.name);1726      } else if (tagValue.type === 'MemberExpression') {1727        tag = convertMemberExpressionToJsx(tagValue);1728      } else {1729        CompilerError.invariant(tagValue.type === 'StringLiteral', {1730          reason: `Expected JSX tag to be an identifier or string, got \`${tagValue.type}\``,1731          loc: tagValue.loc ?? GeneratedSource,1732        });1733        if (tagValue.value.indexOf(':') >= 0) {1734          const [namespace, name] = tagValue.value.split(':', 2);1735          tag = createJsxNamespacedName(1736            instrValue.tag.loc,1737            createJsxIdentifier(instrValue.tag.loc, namespace),1738            createJsxIdentifier(instrValue.tag.loc, name),1739          );1740        } else {1741          tag = createJsxIdentifier(instrValue.loc, tagValue.value);1742        }1743      }1744      let children;1745      if (1746        tagValue.type === 'StringLiteral' &&1747        SINGLE_CHILD_FBT_TAGS.has(tagValue.value)1748      ) {1749        CompilerError.invariant(instrValue.children != null, {1750          reason: 'Expected fbt element to have children',1751          loc: instrValue.loc,1752        });1753        children = instrValue.children.map(child =>1754          codegenJsxFbtChildElement(cx, child),1755        );1756      } else {1757        children =1758          instrValue.children !== null1759            ? instrValue.children.map(child => codegenJsxElement(cx, child))1760            : [];1761      }1762      value = createJsxElement(1763        instrValue.loc,1764        createJsxOpeningElement(1765          instrValue.openingLoc,1766          tag,1767          attributes,1768          instrValue.children === null,1769        ),1770        instrValue.children !== null1771          ? createJsxClosingElement(instrValue.closingLoc, tag)1772          : null,1773        children,1774        instrValue.children === null,1775      );1776      break;1777    }1778    case 'JsxFragment': {1779      value = t.jsxFragment(1780        t.jsxOpeningFragment(),1781        t.jsxClosingFragment(),1782        instrValue.children.map(child => codegenJsxElement(cx, child)),1783      );1784      break;1785    }1786    case 'UnsupportedNode': {1787      const node = instrValue.node;1788      if (!t.isExpression(node)) {1789        return node as any; // TODO handle statements, jsx fragments1790      }1791      value = node;1792      break;1793    }1794    case 'PropertyStore':1795    case 'PropertyLoad':1796    case 'PropertyDelete': {1797      let memberExpr;1798      /*1799       * We currently only lower single chains of optional memberexpr.1800       * (See BuildHIR.ts for more detail.)1801       */1802      if (typeof instrValue.property === 'string') {1803        memberExpr = t.memberExpression(1804          codegenPlaceToExpression(cx, instrValue.object),1805          t.identifier(instrValue.property),1806        );1807      } else {1808        memberExpr = t.memberExpression(1809          codegenPlaceToExpression(cx, instrValue.object),1810          t.numericLiteral(instrValue.property),1811          true,1812        );1813      }1814      if (instrValue.kind === 'PropertyStore') {1815        value = t.assignmentExpression(1816          '=',1817          memberExpr,1818          codegenPlaceToExpression(cx, instrValue.value),1819        );1820      } else if (instrValue.kind === 'PropertyLoad') {1821        value = memberExpr;1822      } else {1823        value = t.unaryExpression('delete', memberExpr);1824      }1825      break;1826    }1827    case 'ComputedStore': {1828      value = t.assignmentExpression(1829        '=',1830        t.memberExpression(1831          codegenPlaceToExpression(cx, instrValue.object),1832          codegenPlaceToExpression(cx, instrValue.property),1833          true,1834        ),1835        codegenPlaceToExpression(cx, instrValue.value),1836      );1837      break;1838    }1839    case 'ComputedLoad': {1840      const object = codegenPlaceToExpression(cx, instrValue.object);1841      const property = codegenPlaceToExpression(cx, instrValue.property);1842      value = t.memberExpression(object, property, true);1843      break;1844    }1845    case 'ComputedDelete': {1846      value = t.unaryExpression(1847        'delete',1848        t.memberExpression(1849          codegenPlaceToExpression(cx, instrValue.object),1850          codegenPlaceToExpression(cx, instrValue.property),1851          true,1852        ),1853      );1854      break;1855    }1856    case 'LoadLocal':1857    case 'LoadContext': {1858      value = codegenPlaceToExpression(cx, instrValue.place);1859      break;1860    }1861    case 'FunctionExpression': {1862      const loweredFunc = instrValue.loweredFunc.func;1863      const reactiveFunction = buildReactiveFunction(loweredFunc);1864      pruneUnusedLabels(reactiveFunction);1865      pruneUnusedLValues(reactiveFunction);1866      pruneHoistedContexts(reactiveFunction);1867      const fn = codegenReactiveFunction(1868        new Context(1869          cx.env,1870          reactiveFunction.id ?? '[[ anonymous ]]',1871          cx.uniqueIdentifiers,1872          cx.fbtOperands,1873          cx.temp,1874        ),1875        reactiveFunction,1876      );18771878      if (instrValue.type === 'ArrowFunctionExpression') {1879        let body: t.BlockStatement | t.Expression = fn.body;1880        if (body.body.length === 1 && loweredFunc.directives.length == 0) {1881          const stmt = body.body[0]!;1882          if (stmt.type === 'ReturnStatement' && stmt.argument != null) {1883            body = stmt.argument;1884          }1885        }1886        value = t.arrowFunctionExpression(fn.params, body, fn.async);1887      } else {1888        value = t.functionExpression(1889          instrValue.name != null ? t.identifier(instrValue.name) : null,1890          fn.params,1891          fn.body,1892          fn.generator,1893          fn.async,1894        );1895      }1896      if (1897        cx.env.config.enableNameAnonymousFunctions &&1898        instrValue.name == null &&1899        instrValue.nameHint != null1900      ) {1901        const name = instrValue.nameHint;1902        value = t.memberExpression(1903          t.objectExpression([t.objectProperty(t.stringLiteral(name), value)]),1904          t.stringLiteral(name),1905          true,1906          false,1907        );1908      }1909      break;1910    }1911    case 'TaggedTemplateExpression': {1912      value = createTaggedTemplateExpression(1913        instrValue.loc,1914        codegenPlaceToExpression(cx, instrValue.tag),1915        t.templateLiteral([t.templateElement(instrValue.value)], []),1916      );1917      break;1918    }1919    case 'TypeCastExpression': {1920      if (t.isTSType(instrValue.typeAnnotation)) {1921        if (instrValue.typeAnnotationKind === 'satisfies') {1922          value = t.tsSatisfiesExpression(1923            codegenPlaceToExpression(cx, instrValue.value),1924            instrValue.typeAnnotation,1925          );1926        } else {1927          value = t.tsAsExpression(1928            codegenPlaceToExpression(cx, instrValue.value),1929            instrValue.typeAnnotation,1930          );1931        }1932      } else {1933        value = t.typeCastExpression(1934          codegenPlaceToExpression(cx, instrValue.value),1935          t.typeAnnotation(instrValue.typeAnnotation),1936        );1937      }1938      break;1939    }1940    case 'LogicalExpression': {1941      value = createLogicalExpression(1942        instrValue.loc,1943        instrValue.operator,1944        codegenInstructionValueToExpression(cx, instrValue.left),1945        codegenInstructionValueToExpression(cx, instrValue.right),1946      );1947      break;1948    }1949    case 'ConditionalExpression': {1950      value = createConditionalExpression(1951        instrValue.loc,1952        codegenInstructionValueToExpression(cx, instrValue.test),1953        codegenInstructionValueToExpression(cx, instrValue.consequent),1954        codegenInstructionValueToExpression(cx, instrValue.alternate),1955      );1956      break;1957    }1958    case 'SequenceExpression': {1959      const body = codegenBlockNoReset(1960        cx,1961        instrValue.instructions.map(instruction => ({1962          kind: 'instruction',1963          instruction,1964        })),1965      ).body;1966      const expressions = body.map(stmt => {1967        if (stmt.type === 'ExpressionStatement') {1968          return stmt.expression;1969        } else {1970          if (t.isVariableDeclaration(stmt)) {1971            const declarator = stmt.declarations[0];1972            cx.recordError(1973              new CompilerErrorDetail({1974                reason: `(CodegenReactiveFunction::codegenInstructionValue) Cannot declare variables in a value block, tried to declare '${1975                  (declarator.id as t.Identifier).name1976                }'`,1977                category: ErrorCategory.Todo,1978                loc: declarator.loc ?? null,1979                suggestions: null,1980              }),1981            );1982            return t.stringLiteral(`TODO handle ${declarator.id}`);1983          } else {1984            cx.recordError(1985              new CompilerErrorDetail({1986                reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,1987                category: ErrorCategory.Todo,1988                loc: stmt.loc ?? null,1989                suggestions: null,1990              }),1991            );1992            return t.stringLiteral(`TODO handle ${stmt.type}`);1993          }1994        }1995      });1996      if (expressions.length === 0) {1997        value = codegenInstructionValueToExpression(cx, instrValue.value);1998      } else {1999        value = createSequenceExpression(instrValue.loc, [2000          ...expressions,

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.