main
ts 1,404 lines 45.2 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import {assertExhaustive} from '../Utils/utils';
9 import 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';
26 import type {Type} from './Types';
27 import type {AliasingEffect} from '../Inference/AliasingEffects';
28 import type {CompilerDiagnostic, CompilerErrorDetail} from '../CompilerError';
29 import type {IdentifierId, ScopeId} from './HIR';
30
31 export function printDebugHIR(fn: HIRFunction): string {
32 const printer = new DebugPrinter();
33 printer.formatFunction(fn);
34
35 const outlined = fn.env.getOutlinedFunctions();
36 for (let i = 0; i < outlined.length; i++) {
37 printer.line('');
38 printer.formatFunction(outlined[i].fn);
39 }
40
41 printer.line('');
42 printer.line('Environment:');
43 printer.indent();
44 const errors = fn.env.aggregateErrors();
45 printer.formatErrors(errors);
46 printer.dedent();
47
48 return printer.toString();
49 }
50
51 export class DebugPrinter {
52 seenIdentifiers: Set<IdentifierId> = new Set();
53 seenScopes: Set<ScopeId> = new Set();
54 output: Array<string> = [];
55 indentLevel: number = 0;
56
57 line(text: string): void {
58 this.output.push(' '.repeat(this.indentLevel) + text);
59 }
60
61 indent(): void {
62 this.indentLevel++;
63 }
64
65 dedent(): void {
66 this.indentLevel--;
67 }
68
69 toString(): string {
70 return this.output.join('\n');
71 }
72
73 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)}`);
83
84 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();
97
98 this.line('returns:');
99 this.indent();
100 this.formatPlaceField('value', fn.returns);
101 this.dedent();
102
103 this.line('context:');
104 this.indent();
105 fn.context.forEach((ctx, i) => {
106 this.formatPlaceField(`[${i}]`, ctx);
107 });
108 this.dedent();
109
110 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 }
120
121 this.line('directives:');
122 this.indent();
123 fn.directives.forEach((d, i) => {
124 this.line(`[${i}] "${d}"`);
125 });
126 this.dedent();
127
128 this.line(
129 `returnTypeAnnotation: ${fn.returnTypeAnnotation !== null ? fn.returnTypeAnnotation.type : 'null'}`,
130 );
131
132 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 }
141
142 formatBlock(blockId: number, block: BasicBlock): void {
143 this.line(`bb${blockId} (${block.kind}):`);
144 this.indent();
145
146 const preds = [...block.preds];
147 this.line(`preds: [${preds.map(p => `bb${p}`).join(', ')}]`);
148
149 this.line('phis:');
150 this.indent();
151 for (const phi of block.phis) {
152 this.formatPhi(phi);
153 }
154 this.dedent();
155
156 this.line('instructions:');
157 this.indent();
158 block.instructions.forEach((instr, i) => {
159 this.formatInstruction(instr, i);
160 });
161 this.dedent();
162
163 this.line('terminal:');
164 this.indent();
165 this.formatTerminal(block.terminal);
166 this.dedent();
167
168 this.dedent();
169 }
170
171 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 }
187
188 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 }
211
212 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 loses
340 * information and diverges from the Rust debug printer (NaN/Infinity).
341 */
342 const val =
343 instrValue.value === undefined
344 ? '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.path
738 .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 }
770
771 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 }
1073
1074 /**
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 }
1098
1099 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 }
1123
1124 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.path
1139 .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 }
1177
1178 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 }
1200
1201 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 }
1207
1208 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.args
1230 .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 }
1266
1267 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 }
1274
1275 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 }
1334
1335 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 }
1347
1348 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 }
1367
1368 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 }
1393
1394 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 }