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.