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 {assertExhaustive} from '../Utils/utils';9import type {10 BasicBlock,11 HIRFunction,12 Identifier,13 Instruction,14 InstructionValue,15 LValue,16 NonLocalBinding,17 ObjectPropertyKey,18 Pattern,19 Phi,20 Place,21 ReactiveScope,22 SourceLocation,23 SpreadPattern,24 Terminal,25} from './HIR';26import type {Type} from './Types';27import type {AliasingEffect} from '../Inference/AliasingEffects';28import type {CompilerDiagnostic, CompilerErrorDetail} from '../CompilerError';29import type {IdentifierId, ScopeId} from './HIR';3031export function printDebugHIR(fn: HIRFunction): string {32 const printer = new DebugPrinter();33 printer.formatFunction(fn);3435 const outlined = fn.env.getOutlinedFunctions();36 for (let i = 0; i < outlined.length; i++) {37 printer.line('');38 printer.formatFunction(outlined[i].fn);39 }4041 printer.line('');42 printer.line('Environment:');43 printer.indent();44 const errors = fn.env.aggregateErrors();45 printer.formatErrors(errors);46 printer.dedent();4748 return printer.toString();49}5051export class DebugPrinter {52 seenIdentifiers: Set<IdentifierId> = new Set();53 seenScopes: Set<ScopeId> = new Set();54 output: Array<string> = [];55 indentLevel: number = 0;5657 line(text: string): void {58 this.output.push(' '.repeat(this.indentLevel) + text);59 }6061 indent(): void {62 this.indentLevel++;63 }6465 dedent(): void {66 this.indentLevel--;67 }6869 toString(): string {70 return this.output.join('\n');71 }7273 formatFunction(fn: HIRFunction): void {74 this.indent();75 this.line(`id: ${fn.id !== null ? `"${fn.id}"` : 'null'}`);76 this.line(77 `name_hint: ${fn.nameHint !== null ? `"${fn.nameHint}"` : 'null'}`,78 );79 this.line(`fn_type: ${fn.fnType}`);80 this.line(`generator: ${fn.generator}`);81 this.line(`is_async: ${fn.async}`);82 this.line(`loc: ${this.formatLoc(fn.loc)}`);8384 this.line('params:');85 this.indent();86 fn.params.forEach((param, i) => {87 if (param.kind === 'Identifier') {88 this.formatPlaceField(`[${i}]`, param);89 } else {90 this.line(`[${i}] Spread:`);91 this.indent();92 this.formatPlaceField('place', param.place);93 this.dedent();94 }95 });96 this.dedent();9798 this.line('returns:');99 this.indent();100 this.formatPlaceField('value', fn.returns);101 this.dedent();102103 this.line('context:');104 this.indent();105 fn.context.forEach((ctx, i) => {106 this.formatPlaceField(`[${i}]`, ctx);107 });108 this.dedent();109110 if (fn.aliasingEffects !== null) {111 this.line('aliasingEffects:');112 this.indent();113 fn.aliasingEffects.forEach((effect, i) => {114 this.line(`[${i}] ${this.formatAliasingEffect(effect)}`);115 });116 this.dedent();117 } else {118 this.line('aliasingEffects: null');119 }120121 this.line('directives:');122 this.indent();123 fn.directives.forEach((d, i) => {124 this.line(`[${i}] "${d}"`);125 });126 this.dedent();127128 this.line(129 `returnTypeAnnotation: ${fn.returnTypeAnnotation !== null ? fn.returnTypeAnnotation.type : 'null'}`,130 );131132 this.line('');133 this.line('Blocks:');134 this.indent();135 for (const [blockId, block] of fn.body.blocks) {136 this.formatBlock(blockId, block);137 }138 this.dedent();139 this.dedent();140 }141142 formatBlock(blockId: number, block: BasicBlock): void {143 this.line(`bb${blockId} (${block.kind}):`);144 this.indent();145146 const preds = [...block.preds];147 this.line(`preds: [${preds.map(p => `bb${p}`).join(', ')}]`);148149 this.line('phis:');150 this.indent();151 for (const phi of block.phis) {152 this.formatPhi(phi);153 }154 this.dedent();155156 this.line('instructions:');157 this.indent();158 block.instructions.forEach((instr, i) => {159 this.formatInstruction(instr, i);160 });161 this.dedent();162163 this.line('terminal:');164 this.indent();165 this.formatTerminal(block.terminal);166 this.dedent();167168 this.dedent();169 }170171 formatPhi(phi: Phi): void {172 this.line('Phi {');173 this.indent();174 this.formatPlaceField('place', phi.place);175 this.line('operands:');176 this.indent();177 for (const [blockId, place] of phi.operands) {178 this.line(`bb${blockId}:`);179 this.indent();180 this.formatPlaceField('value', place);181 this.dedent();182 }183 this.dedent();184 this.dedent();185 this.line('}');186 }187188 formatInstruction(instr: Instruction, index: number): void {189 this.line(`[${index}] Instruction {`);190 this.indent();191 this.line(`id: ${instr.id}`);192 this.formatPlaceField('lvalue', instr.lvalue);193 this.line('value:');194 this.indent();195 this.formatInstructionValue(instr.value);196 this.dedent();197 if (instr.effects !== null) {198 this.line('effects:');199 this.indent();200 instr.effects.forEach((effect, i) => {201 this.line(`[${i}] ${this.formatAliasingEffect(effect)}`);202 });203 this.dedent();204 } else {205 this.line('effects: null');206 }207 this.line(`loc: ${this.formatLoc(instr.loc)}`);208 this.dedent();209 this.line('}');210 }211212 formatInstructionValue(instrValue: InstructionValue): void {213 switch (instrValue.kind) {214 case 'ArrayExpression': {215 this.line(`ArrayExpression {`);216 this.indent();217 this.line('elements:');218 this.indent();219 instrValue.elements.forEach((element, i) => {220 if (element.kind === 'Identifier') {221 this.formatPlaceField(`[${i}]`, element);222 } else if (element.kind === 'Hole') {223 this.line(`[${i}] Hole`);224 } else {225 this.line(`[${i}] Spread:`);226 this.indent();227 this.formatPlaceField('place', element.place);228 this.dedent();229 }230 });231 this.dedent();232 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);233 this.dedent();234 this.line('}');235 break;236 }237 case 'ObjectExpression': {238 this.line('ObjectExpression {');239 this.indent();240 this.line('properties:');241 this.indent();242 instrValue.properties.forEach((prop, i) => {243 if (prop.kind === 'ObjectProperty') {244 this.line(`[${i}] ObjectProperty {`);245 this.indent();246 this.line(`key: ${this.formatObjectPropertyKey(prop.key)}`);247 this.line(`type: "${prop.type}"`);248 this.formatPlaceField('place', prop.place);249 this.dedent();250 this.line('}');251 } else {252 this.line(`[${i}] Spread:`);253 this.indent();254 this.formatPlaceField('place', prop.place);255 this.dedent();256 }257 });258 this.dedent();259 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);260 this.dedent();261 this.line('}');262 break;263 }264 case 'UnaryExpression': {265 this.line(`UnaryExpression {`);266 this.indent();267 this.line(`operator: "${instrValue.operator}"`);268 this.formatPlaceField('value', instrValue.value);269 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);270 this.dedent();271 this.line('}');272 break;273 }274 case 'BinaryExpression': {275 this.line('BinaryExpression {');276 this.indent();277 this.line(`operator: "${instrValue.operator}"`);278 this.formatPlaceField('left', instrValue.left);279 this.formatPlaceField('right', instrValue.right);280 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);281 this.dedent();282 this.line('}');283 break;284 }285 case 'NewExpression': {286 this.line('NewExpression {');287 this.indent();288 this.formatPlaceField('callee', instrValue.callee);289 this.line('args:');290 this.indent();291 instrValue.args.forEach((arg, i) => {292 this.formatArgument(arg, i);293 });294 this.dedent();295 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);296 this.dedent();297 this.line('}');298 break;299 }300 case 'CallExpression': {301 this.line('CallExpression {');302 this.indent();303 this.formatPlaceField('callee', instrValue.callee);304 this.line('args:');305 this.indent();306 instrValue.args.forEach((arg, i) => {307 this.formatArgument(arg, i);308 });309 this.dedent();310 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);311 this.dedent();312 this.line('}');313 break;314 }315 case 'MethodCall': {316 this.line('MethodCall {');317 this.indent();318 this.formatPlaceField('receiver', instrValue.receiver);319 this.formatPlaceField('property', instrValue.property);320 this.line('args:');321 this.indent();322 instrValue.args.forEach((arg, i) => {323 this.formatArgument(arg, i);324 });325 this.dedent();326 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);327 this.dedent();328 this.line('}');329 break;330 }331 case 'JSXText': {332 this.line(333 `JSXText { value: ${JSON.stringify(instrValue.value)}, loc: ${this.formatLoc(instrValue.loc)} }`,334 );335 break;336 }337 case 'Primitive': {338 /*339 * JSON.stringify maps non-finite numbers to "null", which both loses340 * information and diverges from the Rust debug printer (NaN/Infinity).341 */342 const val =343 instrValue.value === undefined344 ? 'undefined'345 : typeof instrValue.value === 'number' &&346 !Number.isFinite(instrValue.value)347 ? String(instrValue.value)348 : JSON.stringify(instrValue.value);349 this.line(350 `Primitive { value: ${val}, loc: ${this.formatLoc(instrValue.loc)} }`,351 );352 break;353 }354 case 'TypeCastExpression': {355 this.line('TypeCastExpression {');356 this.indent();357 this.formatPlaceField('value', instrValue.value);358 this.line(`type: ${this.formatType(instrValue.type)}`);359 this.line(`typeAnnotation: ${instrValue.typeAnnotation.type}`);360 this.line(`typeAnnotationKind: "${instrValue.typeAnnotationKind}"`);361 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);362 this.dedent();363 this.line('}');364 break;365 }366 case 'JsxExpression': {367 this.line('JsxExpression {');368 this.indent();369 if (instrValue.tag.kind === 'Identifier') {370 this.formatPlaceField('tag', instrValue.tag);371 } else {372 this.line(`tag: BuiltinTag("${instrValue.tag.name}")`);373 }374 this.line('props:');375 this.indent();376 instrValue.props.forEach((prop, i) => {377 if (prop.kind === 'JsxAttribute') {378 this.line(`[${i}] JsxAttribute {`);379 this.indent();380 this.line(`name: "${prop.name}"`);381 this.formatPlaceField('place', prop.place);382 this.dedent();383 this.line('}');384 } else {385 this.line(`[${i}] JsxSpreadAttribute:`);386 this.indent();387 this.formatPlaceField('argument', prop.argument);388 this.dedent();389 }390 });391 this.dedent();392 if (instrValue.children !== null) {393 this.line('children:');394 this.indent();395 instrValue.children.forEach((child, i) => {396 this.formatPlaceField(`[${i}]`, child);397 });398 this.dedent();399 } else {400 this.line('children: null');401 }402 this.line(`openingLoc: ${this.formatLoc(instrValue.openingLoc)}`);403 this.line(`closingLoc: ${this.formatLoc(instrValue.closingLoc)}`);404 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);405 this.dedent();406 this.line('}');407 break;408 }409 case 'JsxFragment': {410 this.line('JsxFragment {');411 this.indent();412 this.line('children:');413 this.indent();414 instrValue.children.forEach((child, i) => {415 this.formatPlaceField(`[${i}]`, child);416 });417 this.dedent();418 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);419 this.dedent();420 this.line('}');421 break;422 }423 case 'UnsupportedNode': {424 this.line(425 `UnsupportedNode { type: "${instrValue.node.type}", loc: ${this.formatLoc(instrValue.loc)} }`,426 );427 break;428 }429 case 'LoadLocal': {430 this.line('LoadLocal {');431 this.indent();432 this.formatPlaceField('place', instrValue.place);433 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);434 this.dedent();435 this.line('}');436 break;437 }438 case 'DeclareLocal': {439 this.line('DeclareLocal {');440 this.indent();441 this.formatLValue('lvalue', instrValue.lvalue);442 this.line(443 `type: ${instrValue.type !== null ? instrValue.type.type : 'null'}`,444 );445 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);446 this.dedent();447 this.line('}');448 break;449 }450 case 'DeclareContext': {451 this.line('DeclareContext {');452 this.indent();453 this.line('lvalue:');454 this.indent();455 this.line(`kind: ${instrValue.lvalue.kind}`);456 this.formatPlaceField('place', instrValue.lvalue.place);457 this.dedent();458 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);459 this.dedent();460 this.line('}');461 break;462 }463 case 'StoreLocal': {464 this.line('StoreLocal {');465 this.indent();466 this.formatLValue('lvalue', instrValue.lvalue);467 this.formatPlaceField('value', instrValue.value);468 this.line(469 `type: ${instrValue.type !== null ? instrValue.type.type : 'null'}`,470 );471 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);472 this.dedent();473 this.line('}');474 break;475 }476 case 'LoadContext': {477 this.line('LoadContext {');478 this.indent();479 this.formatPlaceField('place', instrValue.place);480 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);481 this.dedent();482 this.line('}');483 break;484 }485 case 'StoreContext': {486 this.line('StoreContext {');487 this.indent();488 this.line('lvalue:');489 this.indent();490 this.line(`kind: ${instrValue.lvalue.kind}`);491 this.formatPlaceField('place', instrValue.lvalue.place);492 this.dedent();493 this.formatPlaceField('value', instrValue.value);494 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);495 this.dedent();496 this.line('}');497 break;498 }499 case 'Destructure': {500 this.line('Destructure {');501 this.indent();502 this.line('lvalue:');503 this.indent();504 this.line(`kind: ${instrValue.lvalue.kind}`);505 this.formatPattern(instrValue.lvalue.pattern);506 this.dedent();507 this.formatPlaceField('value', instrValue.value);508 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);509 this.dedent();510 this.line('}');511 break;512 }513 case 'PropertyLoad': {514 this.line('PropertyLoad {');515 this.indent();516 this.formatPlaceField('object', instrValue.object);517 this.line(`property: "${instrValue.property}"`);518 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);519 this.dedent();520 this.line('}');521 break;522 }523 case 'PropertyStore': {524 this.line('PropertyStore {');525 this.indent();526 this.formatPlaceField('object', instrValue.object);527 this.line(`property: "${instrValue.property}"`);528 this.formatPlaceField('value', instrValue.value);529 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);530 this.dedent();531 this.line('}');532 break;533 }534 case 'PropertyDelete': {535 this.line('PropertyDelete {');536 this.indent();537 this.formatPlaceField('object', instrValue.object);538 this.line(`property: "${instrValue.property}"`);539 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);540 this.dedent();541 this.line('}');542 break;543 }544 case 'ComputedLoad': {545 this.line('ComputedLoad {');546 this.indent();547 this.formatPlaceField('object', instrValue.object);548 this.formatPlaceField('property', instrValue.property);549 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);550 this.dedent();551 this.line('}');552 break;553 }554 case 'ComputedStore': {555 this.line('ComputedStore {');556 this.indent();557 this.formatPlaceField('object', instrValue.object);558 this.formatPlaceField('property', instrValue.property);559 this.formatPlaceField('value', instrValue.value);560 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);561 this.dedent();562 this.line('}');563 break;564 }565 case 'ComputedDelete': {566 this.line('ComputedDelete {');567 this.indent();568 this.formatPlaceField('object', instrValue.object);569 this.formatPlaceField('property', instrValue.property);570 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);571 this.dedent();572 this.line('}');573 break;574 }575 case 'LoadGlobal': {576 this.line('LoadGlobal {');577 this.indent();578 this.line(`binding: ${this.formatNonLocalBinding(instrValue.binding)}`);579 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);580 this.dedent();581 this.line('}');582 break;583 }584 case 'StoreGlobal': {585 this.line('StoreGlobal {');586 this.indent();587 this.line(`name: "${instrValue.name}"`);588 this.formatPlaceField('value', instrValue.value);589 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);590 this.dedent();591 this.line('}');592 break;593 }594 case 'ObjectMethod':595 case 'FunctionExpression': {596 const kind = instrValue.kind;597 this.line(`${kind} {`);598 this.indent();599 if (instrValue.kind === 'FunctionExpression') {600 this.line(601 `name: ${instrValue.name !== null ? `"${instrValue.name}"` : 'null'}`,602 );603 this.line(604 `nameHint: ${instrValue.nameHint !== null ? `"${instrValue.nameHint}"` : 'null'}`,605 );606 this.line(`type: "${instrValue.type}"`);607 }608 this.line(`loweredFunc:`);609 this.formatFunction(instrValue.loweredFunc.func);610 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);611 this.dedent();612 this.line('}');613 break;614 }615 case 'TaggedTemplateExpression': {616 this.line('TaggedTemplateExpression {');617 this.indent();618 this.formatPlaceField('tag', instrValue.tag);619 this.line(`raw: ${JSON.stringify(instrValue.value.raw)}`);620 this.line(621 `cooked: ${instrValue.value.cooked !== undefined ? JSON.stringify(instrValue.value.cooked) : 'undefined'}`,622 );623 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);624 this.dedent();625 this.line('}');626 break;627 }628 case 'TemplateLiteral': {629 this.line('TemplateLiteral {');630 this.indent();631 this.line('subexprs:');632 this.indent();633 instrValue.subexprs.forEach((sub, i) => {634 this.formatPlaceField(`[${i}]`, sub);635 });636 this.dedent();637 this.line('quasis:');638 this.indent();639 instrValue.quasis.forEach((q, i) => {640 this.line(641 `[${i}] { raw: ${JSON.stringify(q.raw)}, cooked: ${q.cooked !== undefined ? JSON.stringify(q.cooked) : 'undefined'} }`,642 );643 });644 this.dedent();645 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);646 this.dedent();647 this.line('}');648 break;649 }650 case 'RegExpLiteral': {651 this.line(652 `RegExpLiteral { pattern: "${instrValue.pattern}", flags: "${instrValue.flags}", loc: ${this.formatLoc(instrValue.loc)} }`,653 );654 break;655 }656 case 'MetaProperty': {657 this.line(658 `MetaProperty { meta: "${instrValue.meta}", property: "${instrValue.property}", loc: ${this.formatLoc(instrValue.loc)} }`,659 );660 break;661 }662 case 'Await': {663 this.line('Await {');664 this.indent();665 this.formatPlaceField('value', instrValue.value);666 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);667 this.dedent();668 this.line('}');669 break;670 }671 case 'GetIterator': {672 this.line('GetIterator {');673 this.indent();674 this.formatPlaceField('collection', instrValue.collection);675 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);676 this.dedent();677 this.line('}');678 break;679 }680 case 'IteratorNext': {681 this.line('IteratorNext {');682 this.indent();683 this.formatPlaceField('iterator', instrValue.iterator);684 this.formatPlaceField('collection', instrValue.collection);685 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);686 this.dedent();687 this.line('}');688 break;689 }690 case 'NextPropertyOf': {691 this.line('NextPropertyOf {');692 this.indent();693 this.formatPlaceField('value', instrValue.value);694 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);695 this.dedent();696 this.line('}');697 break;698 }699 case 'Debugger': {700 this.line(`Debugger { loc: ${this.formatLoc(instrValue.loc)} }`);701 break;702 }703 case 'PostfixUpdate': {704 this.line('PostfixUpdate {');705 this.indent();706 this.formatPlaceField('lvalue', instrValue.lvalue);707 this.line(`operation: "${instrValue.operation}"`);708 this.formatPlaceField('value', instrValue.value);709 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);710 this.dedent();711 this.line('}');712 break;713 }714 case 'PrefixUpdate': {715 this.line('PrefixUpdate {');716 this.indent();717 this.formatPlaceField('lvalue', instrValue.lvalue);718 this.line(`operation: "${instrValue.operation}"`);719 this.formatPlaceField('value', instrValue.value);720 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);721 this.dedent();722 this.line('}');723 break;724 }725 case 'StartMemoize': {726 this.line('StartMemoize {');727 this.indent();728 this.line(`manualMemoId: ${instrValue.manualMemoId}`);729 if (instrValue.deps !== null) {730 this.line('deps:');731 this.indent();732 instrValue.deps.forEach((dep, i) => {733 const rootStr =734 dep.root.kind === 'Global'735 ? `Global("${dep.root.identifierName}")`736 : `NamedLocal(${dep.root.value.identifier.id}, constant=${dep.root.constant})`;737 const pathStr = dep.path738 .map(p => `${p.optional ? '?.' : '.'}${p.property}`)739 .join('');740 this.line(`[${i}] ${rootStr}${pathStr}`);741 });742 this.dedent();743 } else {744 this.line('deps: null');745 }746 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);747 this.dedent();748 this.line('}');749 break;750 }751 case 'FinishMemoize': {752 this.line('FinishMemoize {');753 this.indent();754 this.line(`manualMemoId: ${instrValue.manualMemoId}`);755 this.formatPlaceField('decl', instrValue.decl);756 this.line(`pruned: ${instrValue.pruned === true}`);757 this.line(`loc: ${this.formatLoc(instrValue.loc)}`);758 this.dedent();759 this.line('}');760 break;761 }762 default: {763 assertExhaustive(764 instrValue,765 `Unexpected instruction kind '${(instrValue as any).kind}'`,766 );767 }768 }769 }770771 formatTerminal(terminal: Terminal): void {772 switch (terminal.kind) {773 case 'if': {774 this.line('If {');775 this.indent();776 this.line(`id: ${terminal.id}`);777 this.formatPlaceField('test', terminal.test);778 this.line(`consequent: bb${terminal.consequent}`);779 this.line(`alternate: bb${terminal.alternate}`);780 this.line(`fallthrough: bb${terminal.fallthrough}`);781 this.line(`loc: ${this.formatLoc(terminal.loc)}`);782 this.dedent();783 this.line('}');784 break;785 }786 case 'branch': {787 this.line('Branch {');788 this.indent();789 this.line(`id: ${terminal.id}`);790 this.formatPlaceField('test', terminal.test);791 this.line(`consequent: bb${terminal.consequent}`);792 this.line(`alternate: bb${terminal.alternate}`);793 this.line(`fallthrough: bb${terminal.fallthrough}`);794 this.line(`loc: ${this.formatLoc(terminal.loc)}`);795 this.dedent();796 this.line('}');797 break;798 }799 case 'logical': {800 this.line('Logical {');801 this.indent();802 this.line(`id: ${terminal.id}`);803 this.line(`operator: "${terminal.operator}"`);804 this.line(`test: bb${terminal.test}`);805 this.line(`fallthrough: bb${terminal.fallthrough}`);806 this.line(`loc: ${this.formatLoc(terminal.loc)}`);807 this.dedent();808 this.line('}');809 break;810 }811 case 'ternary': {812 this.line('Ternary {');813 this.indent();814 this.line(`id: ${terminal.id}`);815 this.line(`test: bb${terminal.test}`);816 this.line(`fallthrough: bb${terminal.fallthrough}`);817 this.line(`loc: ${this.formatLoc(terminal.loc)}`);818 this.dedent();819 this.line('}');820 break;821 }822 case 'optional': {823 this.line('Optional {');824 this.indent();825 this.line(`id: ${terminal.id}`);826 this.line(`optional: ${terminal.optional}`);827 this.line(`test: bb${terminal.test}`);828 this.line(`fallthrough: bb${terminal.fallthrough}`);829 this.line(`loc: ${this.formatLoc(terminal.loc)}`);830 this.dedent();831 this.line('}');832 break;833 }834 case 'throw': {835 this.line('Throw {');836 this.indent();837 this.line(`id: ${terminal.id}`);838 this.formatPlaceField('value', terminal.value);839 this.line(`loc: ${this.formatLoc(terminal.loc)}`);840 this.dedent();841 this.line('}');842 break;843 }844 case 'return': {845 this.line('Return {');846 this.indent();847 this.line(`id: ${terminal.id}`);848 this.line(`returnVariant: ${terminal.returnVariant}`);849 this.formatPlaceField('value', terminal.value);850 if (terminal.effects !== null) {851 this.line('effects:');852 this.indent();853 terminal.effects.forEach((effect, i) => {854 this.line(`[${i}] ${this.formatAliasingEffect(effect)}`);855 });856 this.dedent();857 } else {858 this.line('effects: null');859 }860 this.line(`loc: ${this.formatLoc(terminal.loc)}`);861 this.dedent();862 this.line('}');863 break;864 }865 case 'goto': {866 this.line('Goto {');867 this.indent();868 this.line(`id: ${terminal.id}`);869 this.line(`block: bb${terminal.block}`);870 this.line(`variant: ${terminal.variant}`);871 this.line(`loc: ${this.formatLoc(terminal.loc)}`);872 this.dedent();873 this.line('}');874 break;875 }876 case 'switch': {877 this.line('Switch {');878 this.indent();879 this.line(`id: ${terminal.id}`);880 this.formatPlaceField('test', terminal.test);881 this.line('cases:');882 this.indent();883 terminal.cases.forEach((case_, i) => {884 if (case_.test !== null) {885 this.line(`[${i}] Case {`);886 this.indent();887 this.formatPlaceField('test', case_.test);888 this.line(`block: bb${case_.block}`);889 this.dedent();890 this.line('}');891 } else {892 this.line(`[${i}] Default { block: bb${case_.block} }`);893 }894 });895 this.dedent();896 this.line(`fallthrough: bb${terminal.fallthrough}`);897 this.line(`loc: ${this.formatLoc(terminal.loc)}`);898 this.dedent();899 this.line('}');900 break;901 }902 case 'do-while': {903 this.line('DoWhile {');904 this.indent();905 this.line(`id: ${terminal.id}`);906 this.line(`loop: bb${terminal.loop}`);907 this.line(`test: bb${terminal.test}`);908 this.line(`fallthrough: bb${terminal.fallthrough}`);909 this.line(`loc: ${this.formatLoc(terminal.loc)}`);910 this.dedent();911 this.line('}');912 break;913 }914 case 'while': {915 this.line('While {');916 this.indent();917 this.line(`id: ${terminal.id}`);918 this.line(`test: bb${terminal.test}`);919 this.line(`loop: bb${terminal.loop}`);920 this.line(`fallthrough: bb${terminal.fallthrough}`);921 this.line(`loc: ${this.formatLoc(terminal.loc)}`);922 this.dedent();923 this.line('}');924 break;925 }926 case 'for': {927 this.line('For {');928 this.indent();929 this.line(`id: ${terminal.id}`);930 this.line(`init: bb${terminal.init}`);931 this.line(`test: bb${terminal.test}`);932 this.line(933 `update: ${terminal.update !== null ? `bb${terminal.update}` : 'null'}`,934 );935 this.line(`loop: bb${terminal.loop}`);936 this.line(`fallthrough: bb${terminal.fallthrough}`);937 this.line(`loc: ${this.formatLoc(terminal.loc)}`);938 this.dedent();939 this.line('}');940 break;941 }942 case 'for-of': {943 this.line('ForOf {');944 this.indent();945 this.line(`id: ${terminal.id}`);946 this.line(`init: bb${terminal.init}`);947 this.line(`test: bb${terminal.test}`);948 this.line(`loop: bb${terminal.loop}`);949 this.line(`fallthrough: bb${terminal.fallthrough}`);950 this.line(`loc: ${this.formatLoc(terminal.loc)}`);951 this.dedent();952 this.line('}');953 break;954 }955 case 'for-in': {956 this.line('ForIn {');957 this.indent();958 this.line(`id: ${terminal.id}`);959 this.line(`init: bb${terminal.init}`);960 this.line(`loop: bb${terminal.loop}`);961 this.line(`fallthrough: bb${terminal.fallthrough}`);962 this.line(`loc: ${this.formatLoc(terminal.loc)}`);963 this.dedent();964 this.line('}');965 break;966 }967 case 'label': {968 this.line('Label {');969 this.indent();970 this.line(`id: ${terminal.id}`);971 this.line(`block: bb${terminal.block}`);972 this.line(`fallthrough: bb${terminal.fallthrough}`);973 this.line(`loc: ${this.formatLoc(terminal.loc)}`);974 this.dedent();975 this.line('}');976 break;977 }978 case 'sequence': {979 this.line('Sequence {');980 this.indent();981 this.line(`id: ${terminal.id}`);982 this.line(`block: bb${terminal.block}`);983 this.line(`fallthrough: bb${terminal.fallthrough}`);984 this.line(`loc: ${this.formatLoc(terminal.loc)}`);985 this.dedent();986 this.line('}');987 break;988 }989 case 'unreachable': {990 this.line(991 `Unreachable { id: ${terminal.id}, loc: ${this.formatLoc(terminal.loc)} }`,992 );993 break;994 }995 case 'unsupported': {996 this.line(997 `Unsupported { id: ${terminal.id}, loc: ${this.formatLoc(terminal.loc)} }`,998 );999 break;1000 }1001 case 'maybe-throw': {1002 this.line('MaybeThrow {');1003 this.indent();1004 this.line(`id: ${terminal.id}`);1005 this.line(`continuation: bb${terminal.continuation}`);1006 this.line(1007 `handler: ${terminal.handler !== null ? `bb${terminal.handler}` : 'null'}`,1008 );1009 if (terminal.effects !== null) {1010 this.line('effects:');1011 this.indent();1012 terminal.effects.forEach((effect, i) => {1013 this.line(`[${i}] ${this.formatAliasingEffect(effect)}`);1014 });1015 this.dedent();1016 } else {1017 this.line('effects: null');1018 }1019 this.line(`loc: ${this.formatLoc(terminal.loc)}`);1020 this.dedent();1021 this.line('}');1022 break;1023 }1024 case 'scope': {1025 this.line('Scope {');1026 this.indent();1027 this.line(`id: ${terminal.id}`);1028 this.formatScopeField('scope', terminal.scope);1029 this.line(`block: bb${terminal.block}`);1030 this.line(`fallthrough: bb${terminal.fallthrough}`);1031 this.line(`loc: ${this.formatLoc(terminal.loc)}`);1032 this.dedent();1033 this.line('}');1034 break;1035 }1036 case 'pruned-scope': {1037 this.line('PrunedScope {');1038 this.indent();1039 this.line(`id: ${terminal.id}`);1040 this.formatScopeField('scope', terminal.scope);1041 this.line(`block: bb${terminal.block}`);1042 this.line(`fallthrough: bb${terminal.fallthrough}`);1043 this.line(`loc: ${this.formatLoc(terminal.loc)}`);1044 this.dedent();1045 this.line('}');1046 break;1047 }1048 case 'try': {1049 this.line('Try {');1050 this.indent();1051 this.line(`id: ${terminal.id}`);1052 this.line(`block: bb${terminal.block}`);1053 this.line(`handler: bb${terminal.handler}`);1054 if (terminal.handlerBinding !== null) {1055 this.formatPlaceField('handlerBinding', terminal.handlerBinding);1056 } else {1057 this.line('handlerBinding: null');1058 }1059 this.line(`fallthrough: bb${terminal.fallthrough}`);1060 this.line(`loc: ${this.formatLoc(terminal.loc)}`);1061 this.dedent();1062 this.line('}');1063 break;1064 }1065 default: {1066 assertExhaustive(1067 terminal,1068 `Unexpected terminal kind \`${(terminal as any).kind}\``,1069 );1070 }1071 }1072 }10731074 /**1075 * Print a Place as a named field. If the identifier is first-seen, expands to multiple lines.1076 * If abbreviated, stays on one line.1077 */1078 formatPlaceField(fieldName: string, place: Place): void {1079 const isSeen = this.seenIdentifiers.has(place.identifier.id);1080 if (isSeen) {1081 this.line(1082 `${fieldName}: Place { identifier: Identifier(${place.identifier.id}), effect: ${place.effect}, reactive: ${place.reactive}, loc: ${this.formatLoc(place.loc)} }`,1083 );1084 } else {1085 this.line(`${fieldName}: Place {`);1086 this.indent();1087 this.line('identifier:');1088 this.indent();1089 this.formatIdentifier(place.identifier);1090 this.dedent();1091 this.line(`effect: ${place.effect}`);1092 this.line(`reactive: ${place.reactive}`);1093 this.line(`loc: ${this.formatLoc(place.loc)}`);1094 this.dedent();1095 this.line('}');1096 }1097 }10981099 formatIdentifier(id: Identifier): void {1100 this.seenIdentifiers.add(id.id);1101 this.line('Identifier {');1102 this.indent();1103 this.line(`id: ${id.id}`);1104 this.line(`declarationId: ${id.declarationId}`);1105 if (id.name !== null) {1106 this.line(`name: { kind: "${id.name.kind}", value: "${id.name.value}" }`);1107 } else {1108 this.line('name: null');1109 }1110 this.line(1111 `mutableRange: [${id.mutableRange.start}:${id.mutableRange.end}]`,1112 );1113 if (id.scope !== null) {1114 this.formatScopeField('scope', id.scope);1115 } else {1116 this.line('scope: null');1117 }1118 this.line(`type: ${this.formatType(id.type)}`);1119 this.line(`loc: ${this.formatLoc(id.loc)}`);1120 this.dedent();1121 this.line('}');1122 }11231124 formatScopeField(fieldName: string, scope: ReactiveScope): void {1125 const isSeen = this.seenScopes.has(scope.id);1126 if (isSeen) {1127 this.line(`${fieldName}: Scope(${scope.id})`);1128 } else {1129 this.seenScopes.add(scope.id);1130 this.line(`${fieldName}: Scope {`);1131 this.indent();1132 this.line(`id: ${scope.id}`);1133 this.line(`range: [${scope.range.start}:${scope.range.end}]`);1134 this.line('dependencies:');1135 this.indent();1136 let depIndex = 0;1137 for (const dep of scope.dependencies) {1138 const pathStr = dep.path1139 .map(p => `${p.optional ? '?.' : '.'}${p.property}`)1140 .join('');1141 this.line(1142 `[${depIndex}] { identifier: ${dep.identifier.id}, reactive: ${dep.reactive}, path: "${pathStr}" }`,1143 );1144 depIndex++;1145 }1146 this.dedent();1147 this.line('declarations:');1148 this.indent();1149 for (const [identId, decl] of scope.declarations) {1150 this.line(1151 `${identId}: { identifier: ${decl.identifier.id}, scope: ${decl.scope.id} }`,1152 );1153 }1154 this.dedent();1155 this.line('reassignments:');1156 this.indent();1157 for (const ident of scope.reassignments) {1158 this.line(`${ident.id}`);1159 }1160 this.dedent();1161 if (scope.earlyReturnValue !== null) {1162 this.line('earlyReturnValue:');1163 this.indent();1164 this.line(`value: ${scope.earlyReturnValue.value.id}`);1165 this.line(`loc: ${this.formatLoc(scope.earlyReturnValue.loc)}`);1166 this.line(`label: bb${scope.earlyReturnValue.label}`);1167 this.dedent();1168 } else {1169 this.line('earlyReturnValue: null');1170 }1171 this.line(`merged: [${[...scope.merged].join(', ')}]`);1172 this.line(`loc: ${this.formatLoc(scope.loc)}`);1173 this.dedent();1174 this.line('}');1175 }1176 }11771178 formatType(type: Type): string {1179 switch (type.kind) {1180 case 'Primitive':1181 return 'Primitive';1182 case 'Function':1183 return `Function { shapeId: ${type.shapeId !== null ? `"${type.shapeId}"` : 'null'}, return: ${this.formatType(type.return)}, isConstructor: ${type.isConstructor} }`;1184 case 'Object':1185 return `Object { shapeId: ${type.shapeId !== null ? `"${type.shapeId}"` : 'null'} }`;1186 case 'Type':1187 return `Type(${type.id})`;1188 case 'Poly':1189 return 'Poly';1190 case 'Phi':1191 return `Phi { operands: [${type.operands.map(op => this.formatType(op)).join(', ')}] }`;1192 case 'Property':1193 return `Property { objectType: ${this.formatType(type.objectType)}, objectName: "${type.objectName}", propertyName: ${type.propertyName.kind === 'literal' ? `"${type.propertyName.value}"` : `computed(${this.formatType(type.propertyName.value)})`} }`;1194 case 'ObjectMethod':1195 return 'ObjectMethod';1196 default:1197 assertExhaustive(type, `Unexpected type kind '${(type as any).kind}'`);1198 }1199 }12001201 formatLoc(loc: SourceLocation): string {1202 if (typeof loc === 'symbol') {1203 return 'generated';1204 }1205 return `${loc.start.line}:${loc.start.column}-${loc.end.line}:${loc.end.column}`;1206 }12071208 formatAliasingEffect(effect: AliasingEffect): string {1209 switch (effect.kind) {1210 case 'Assign':1211 return `Assign { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`;1212 case 'Alias':1213 return `Alias { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`;1214 case 'MaybeAlias':1215 return `MaybeAlias { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`;1216 case 'Capture':1217 return `Capture { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`;1218 case 'ImmutableCapture':1219 return `ImmutableCapture { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`;1220 case 'Create':1221 return `Create { into: ${effect.into.identifier.id}, value: ${effect.value}, reason: ${effect.reason} }`;1222 case 'CreateFrom':1223 return `CreateFrom { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`;1224 case 'CreateFunction': {1225 const captures = effect.captures.map(c => c.identifier.id).join(', ');1226 return `CreateFunction { into: ${effect.into.identifier.id}, captures: [${captures}] }`;1227 }1228 case 'Apply': {1229 const args = effect.args1230 .map(arg => {1231 if (arg.kind === 'Identifier') {1232 return String(arg.identifier.id);1233 } else if (arg.kind === 'Hole') {1234 return 'Hole';1235 }1236 return `...${arg.place.identifier.id}`;1237 })1238 .join(', ');1239 return `Apply { into: ${effect.into.identifier.id}, receiver: ${effect.receiver.identifier.id}, function: ${effect.function.identifier.id}, mutatesFunction: ${effect.mutatesFunction}, args: [${args}], loc: ${this.formatLoc(effect.loc)} }`;1240 }1241 case 'Freeze':1242 return `Freeze { value: ${effect.value.identifier.id}, reason: ${effect.reason} }`;1243 case 'Mutate':1244 return `Mutate { value: ${effect.value.identifier.id}${effect.reason?.kind === 'AssignCurrentProperty' ? ', reason: AssignCurrentProperty' : ''} }`;1245 case 'MutateConditionally':1246 return `MutateConditionally { value: ${effect.value.identifier.id} }`;1247 case 'MutateTransitive':1248 return `MutateTransitive { value: ${effect.value.identifier.id} }`;1249 case 'MutateTransitiveConditionally':1250 return `MutateTransitiveConditionally { value: ${effect.value.identifier.id} }`;1251 case 'MutateFrozen':1252 return `MutateFrozen { place: ${effect.place.identifier.id}, reason: ${JSON.stringify(effect.error.reason)} }`;1253 case 'MutateGlobal':1254 return `MutateGlobal { place: ${effect.place.identifier.id}, reason: ${JSON.stringify(effect.error.reason)} }`;1255 case 'Impure':1256 return `Impure { place: ${effect.place.identifier.id}, reason: ${JSON.stringify(effect.error.reason)} }`;1257 case 'Render':1258 return `Render { place: ${effect.place.identifier.id} }`;1259 default:1260 assertExhaustive(1261 effect,1262 `Unexpected effect kind '${(effect as any).kind}'`,1263 );1264 }1265 }12661267 formatLValue(fieldName: string, lvalue: LValue): void {1268 this.line(`${fieldName}:`);1269 this.indent();1270 this.line(`kind: ${lvalue.kind}`);1271 this.formatPlaceField('place', lvalue.place);1272 this.dedent();1273 }12741275 formatPattern(pattern: Pattern): void {1276 switch (pattern.kind) {1277 case 'ArrayPattern': {1278 this.line('pattern: ArrayPattern {');1279 this.indent();1280 this.line('items:');1281 this.indent();1282 pattern.items.forEach((item, i) => {1283 if (item.kind === 'Hole') {1284 this.line(`[${i}] Hole`);1285 } else if (item.kind === 'Identifier') {1286 this.formatPlaceField(`[${i}]`, item);1287 } else {1288 this.line(`[${i}] Spread:`);1289 this.indent();1290 this.formatPlaceField('place', item.place);1291 this.dedent();1292 }1293 });1294 this.dedent();1295 this.line(`loc: ${this.formatLoc(pattern.loc)}`);1296 this.dedent();1297 this.line('}');1298 break;1299 }1300 case 'ObjectPattern': {1301 this.line('pattern: ObjectPattern {');1302 this.indent();1303 this.line('properties:');1304 this.indent();1305 pattern.properties.forEach((prop, i) => {1306 if (prop.kind === 'ObjectProperty') {1307 this.line(`[${i}] ObjectProperty {`);1308 this.indent();1309 this.line(`key: ${this.formatObjectPropertyKey(prop.key)}`);1310 this.line(`type: "${prop.type}"`);1311 this.formatPlaceField('place', prop.place);1312 this.dedent();1313 this.line('}');1314 } else {1315 this.line(`[${i}] Spread:`);1316 this.indent();1317 this.formatPlaceField('place', prop.place);1318 this.dedent();1319 }1320 });1321 this.dedent();1322 this.line(`loc: ${this.formatLoc(pattern.loc)}`);1323 this.dedent();1324 this.line('}');1325 break;1326 }1327 default:1328 assertExhaustive(1329 pattern,1330 `Unexpected pattern kind '${(pattern as any).kind}'`,1331 );1332 }1333 }13341335 formatObjectPropertyKey(key: ObjectPropertyKey): string {1336 switch (key.kind) {1337 case 'identifier':1338 return `Identifier("${key.name}")`;1339 case 'string':1340 return `String("${key.name}")`;1341 case 'computed':1342 return `Computed(${key.name.identifier.id})`;1343 case 'number':1344 return `Number(${key.name})`;1345 }1346 }13471348 formatNonLocalBinding(binding: NonLocalBinding): string {1349 switch (binding.kind) {1350 case 'Global':1351 return `Global { name: "${binding.name}" }`;1352 case 'ModuleLocal':1353 return `ModuleLocal { name: "${binding.name}" }`;1354 case 'ImportDefault':1355 return `ImportDefault { name: "${binding.name}", module: "${binding.module}" }`;1356 case 'ImportNamespace':1357 return `ImportNamespace { name: "${binding.name}", module: "${binding.module}" }`;1358 case 'ImportSpecifier':1359 return `ImportSpecifier { name: "${binding.name}", module: "${binding.module}", imported: "${binding.imported}" }`;1360 default:1361 assertExhaustive(1362 binding,1363 `Unexpected binding kind '${(binding as any).kind}'`,1364 );1365 }1366 }13671368 formatErrors(errors: {1369 details: Array<CompilerErrorDetail | CompilerDiagnostic>;1370 }): void {1371 if (errors.details.length === 0) {1372 this.line('Errors: []');1373 return;1374 }1375 this.line('Errors:');1376 this.indent();1377 errors.details.forEach((detail, i) => {1378 this.line(`[${i}] {`);1379 this.indent();1380 this.line(`severity: ${detail.severity}`);1381 this.line(`reason: ${JSON.stringify(detail.reason)}`);1382 this.line(1383 `description: ${detail.description !== null && detail.description !== undefined ? JSON.stringify(detail.description) : 'null'}`,1384 );1385 this.line(`category: ${detail.category}`);1386 const loc = detail.primaryLocation();1387 this.line(`loc: ${loc !== null ? this.formatLoc(loc) : 'null'}`);1388 this.dedent();1389 this.line('}');1390 });1391 this.dedent();1392 }13931394 private formatArgument(arg: Place | SpreadPattern, index: number): void {1395 if (arg.kind === 'Identifier') {1396 this.formatPlaceField(`[${index}]`, arg);1397 } else {1398 this.line(`[${index}] Spread:`);1399 this.indent();1400 this.formatPlaceField('place', arg.place);1401 this.dedent();1402 }1403 }1404}
Findings
✓ No findings reported for this file.