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