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