compiler/packages/babel-plugin-react-compiler/src/HIR/DebugPrintHIR.ts TYPESCRIPT 1,405 lines View on github.com → Search inside
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.