main
ts 1,036 lines 30.6 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 {CompilerError} from '../CompilerError';
9 import {printReactiveScopeSummary} from '../ReactiveScopes/PrintReactiveFunction';
10 import DisjointSet from '../Utils/DisjointSet';
11 import {assertExhaustive} from '../Utils/utils';
12 import type {
13 FunctionExpression,
14 HIR,
15 HIRFunction,
16 Identifier,
17 IdentifierName,
18 Instruction,
19 InstructionValue,
20 LValue,
21 ManualMemoDependency,
22 MutableRange,
23 ObjectMethod,
24 ObjectPropertyKey,
25 Pattern,
26 Phi,
27 Place,
28 ReactiveInstruction,
29 ReactiveScope,
30 ReactiveValue,
31 SourceLocation,
32 SpreadPattern,
33 Terminal,
34 Type,
35 } from './HIR';
36 import {GotoVariant, InstructionKind} from './HIR';
37 import {AliasingEffect, AliasingSignature} from '../Inference/AliasingEffects';
38
39 export type Options = {
40 indent: number;
41 };
42
43 export function printFunctionWithOutlined(fn: HIRFunction): string {
44 const output = [printFunction(fn)];
45 for (const outlined of fn.env.getOutlinedFunctions()) {
46 output.push(`\nfunction ${outlined.fn.id}:\n${printHIR(outlined.fn.body)}`);
47 }
48 return output.join('\n');
49 }
50
51 export function printFunction(fn: HIRFunction): string {
52 const output = [];
53 let definition = '';
54 if (fn.id !== null) {
55 definition += fn.id;
56 } else {
57 definition += '<<anonymous>>';
58 }
59 if (fn.nameHint != null) {
60 definition += ` ${fn.nameHint}`;
61 }
62 if (fn.params.length !== 0) {
63 definition +=
64 '(' +
65 fn.params
66 .map(param => {
67 if (param.kind === 'Identifier') {
68 return printPlace(param);
69 } else {
70 return `...${printPlace(param.place)}`;
71 }
72 })
73 .join(', ') +
74 ')';
75 } else {
76 definition += '()';
77 }
78 definition += `: ${printPlace(fn.returns)}`;
79 output.push(definition);
80 output.push(...fn.directives);
81 output.push(printHIR(fn.body));
82 return output.join('\n');
83 }
84
85 export function printHIR(ir: HIR, options: Options | null = null): string {
86 let output = [];
87 let indent = ' '.repeat(options?.indent ?? 0);
88 const push = (text: string, indent: string = ' '): void => {
89 output.push(`${indent}${text}`);
90 };
91 for (const [blockId, block] of ir.blocks) {
92 output.push(`bb${blockId} (${block.kind}):`);
93 if (block.preds.size > 0) {
94 const preds = ['predecessor blocks:'];
95 for (const pred of block.preds) {
96 preds.push(`bb${pred}`);
97 }
98 push(preds.join(' '));
99 }
100 for (const phi of block.phis) {
101 push(printPhi(phi));
102 }
103 for (const instr of block.instructions) {
104 push(printInstruction(instr));
105 }
106 const terminal = printTerminal(block.terminal);
107 if (Array.isArray(terminal)) {
108 terminal.forEach(line => push(line));
109 } else {
110 push(terminal);
111 }
112 }
113 return output.map(line => indent + line).join('\n');
114 }
115
116 export function printMixedHIR(
117 value: Instruction | InstructionValue | Terminal,
118 ): string {
119 if (!('kind' in value)) {
120 return printInstruction(value);
121 }
122 switch (value.kind) {
123 case 'try':
124 case 'maybe-throw':
125 case 'sequence':
126 case 'label':
127 case 'optional':
128 case 'branch':
129 case 'if':
130 case 'logical':
131 case 'ternary':
132 case 'return':
133 case 'switch':
134 case 'throw':
135 case 'while':
136 case 'for':
137 case 'unreachable':
138 case 'unsupported':
139 case 'goto':
140 case 'do-while':
141 case 'for-in':
142 case 'for-of':
143 case 'scope':
144 case 'pruned-scope': {
145 const terminal = printTerminal(value);
146 if (Array.isArray(terminal)) {
147 return terminal.join('; ');
148 }
149 return terminal;
150 }
151 default: {
152 return printInstructionValue(value);
153 }
154 }
155 }
156
157 export function printInstruction(instr: ReactiveInstruction): string {
158 const id = `[${instr.id}]`;
159 let value = printInstructionValue(instr.value);
160 if (instr.effects != null) {
161 value += `\n ${instr.effects.map(printAliasingEffect).join('\n ')}`;
162 }
163
164 if (instr.lvalue !== null) {
165 return `${id} ${printPlace(instr.lvalue)} = ${value}`;
166 } else {
167 return `${id} ${value}`;
168 }
169 }
170
171 export function printPhi(phi: Phi): string {
172 const items = [];
173 items.push(printPlace(phi.place));
174 items.push(printMutableRange(phi.place.identifier));
175 items.push(printType(phi.place.identifier.type));
176 items.push(': phi(');
177 const phis = [];
178 for (const [blockId, place] of phi.operands) {
179 phis.push(`bb${blockId}: ${printPlace(place)}`);
180 }
181
182 items.push(phis.join(', '));
183 items.push(')');
184 return items.join('');
185 }
186
187 export function printTerminal(terminal: Terminal): Array<string> | string {
188 let value;
189 switch (terminal.kind) {
190 case 'if': {
191 value = `[${terminal.id}] If (${printPlace(terminal.test)}) then:bb${
192 terminal.consequent
193 } else:bb${terminal.alternate}${
194 terminal.fallthrough ? ` fallthrough=bb${terminal.fallthrough}` : ''
195 }`;
196 break;
197 }
198 case 'branch': {
199 value = `[${terminal.id}] Branch (${printPlace(terminal.test)}) then:bb${
200 terminal.consequent
201 } else:bb${terminal.alternate} fallthrough:bb${terminal.fallthrough}`;
202 break;
203 }
204 case 'logical': {
205 value = `[${terminal.id}] Logical ${terminal.operator} test:bb${terminal.test} fallthrough=bb${terminal.fallthrough}`;
206 break;
207 }
208 case 'ternary': {
209 value = `[${terminal.id}] Ternary test:bb${terminal.test} fallthrough=bb${terminal.fallthrough}`;
210 break;
211 }
212 case 'optional': {
213 value = `[${terminal.id}] Optional (optional=${terminal.optional}) test:bb${terminal.test} fallthrough=bb${terminal.fallthrough}`;
214 break;
215 }
216 case 'throw': {
217 value = `[${terminal.id}] Throw ${printPlace(terminal.value)}`;
218 break;
219 }
220 case 'return': {
221 value = `[${terminal.id}] Return ${terminal.returnVariant}${
222 terminal.value != null ? ' ' + printPlace(terminal.value) : ''
223 }`;
224 if (terminal.effects != null) {
225 value += `\n ${terminal.effects.map(printAliasingEffect).join('\n ')}`;
226 }
227 break;
228 }
229 case 'goto': {
230 value = `[${terminal.id}] Goto${
231 terminal.variant === GotoVariant.Continue ? '(Continue)' : ''
232 } bb${terminal.block}`;
233 break;
234 }
235 case 'switch': {
236 const output = [];
237 output.push(`[${terminal.id}] Switch (${printPlace(terminal.test)})`);
238 terminal.cases.forEach(case_ => {
239 if (case_.test !== null) {
240 output.push(` Case ${printPlace(case_.test)}: bb${case_.block}`);
241 } else {
242 output.push(` Default: bb${case_.block}`);
243 }
244 });
245 if (terminal.fallthrough) {
246 output.push(` Fallthrough: bb${terminal.fallthrough}`);
247 }
248 value = output;
249 break;
250 }
251 case 'do-while': {
252 value = `[${terminal.id}] DoWhile loop=${`bb${terminal.loop}`} test=bb${
253 terminal.test
254 } fallthrough=${`bb${terminal.fallthrough}`}`;
255 break;
256 }
257 case 'while': {
258 value = `[${terminal.id}] While test=bb${terminal.test} loop=${
259 terminal.loop !== null ? `bb${terminal.loop}` : ''
260 } fallthrough=${terminal.fallthrough ? `bb${terminal.fallthrough}` : ''}`;
261 break;
262 }
263 case 'for': {
264 value = `[${terminal.id}] For init=bb${terminal.init} test=bb${terminal.test} loop=bb${terminal.loop} update=bb${terminal.update} fallthrough=bb${terminal.fallthrough}`;
265 break;
266 }
267 case 'for-of': {
268 value = `[${terminal.id}] ForOf init=bb${terminal.init} test=bb${terminal.test} loop=bb${terminal.loop} fallthrough=bb${terminal.fallthrough}`;
269 break;
270 }
271 case 'for-in': {
272 value = `[${terminal.id}] ForIn init=bb${terminal.init} loop=bb${terminal.loop} fallthrough=bb${terminal.fallthrough}`;
273 break;
274 }
275 case 'label': {
276 value = `[${terminal.id}] Label block=bb${terminal.block} fallthrough=${
277 terminal.fallthrough ? `bb${terminal.fallthrough}` : ''
278 }`;
279 break;
280 }
281 case 'sequence': {
282 value = `[${terminal.id}] Sequence block=bb${terminal.block} fallthrough=bb${terminal.fallthrough}`;
283 break;
284 }
285 case 'unreachable': {
286 value = `[${terminal.id}] Unreachable`;
287 break;
288 }
289 case 'unsupported': {
290 value = `[${terminal.id}] Unsupported`;
291 break;
292 }
293 case 'maybe-throw': {
294 const handlerStr =
295 terminal.handler !== null ? `bb${terminal.handler}` : '(none)';
296 value = `[${terminal.id}] MaybeThrow continuation=bb${terminal.continuation} handler=${handlerStr}`;
297 if (terminal.effects != null) {
298 value += `\n ${terminal.effects.map(printAliasingEffect).join('\n ')}`;
299 }
300 break;
301 }
302 case 'scope': {
303 value = `[${terminal.id}] Scope ${printReactiveScopeSummary(
304 terminal.scope,
305 )} block=bb${terminal.block} fallthrough=bb${terminal.fallthrough}`;
306 break;
307 }
308 case 'pruned-scope': {
309 value = `[${terminal.id}] <pruned> Scope ${printReactiveScopeSummary(
310 terminal.scope,
311 )} block=bb${terminal.block} fallthrough=bb${terminal.fallthrough}`;
312 break;
313 }
314 case 'try': {
315 value = `[${terminal.id}] Try block=bb${terminal.block} handler=bb${
316 terminal.handler
317 }${
318 terminal.handlerBinding !== null
319 ? ` handlerBinding=(${printPlace(terminal.handlerBinding)})`
320 : ''
321 } fallthrough=${
322 terminal.fallthrough != null ? `bb${terminal.fallthrough}` : ''
323 }`;
324 break;
325 }
326 default: {
327 assertExhaustive(
328 terminal,
329 `Unexpected terminal kind \`${terminal as any as Terminal}\``,
330 );
331 }
332 }
333 return value;
334 }
335
336 function printHole(): string {
337 return '<hole>';
338 }
339
340 function printObjectPropertyKey(key: ObjectPropertyKey): string {
341 switch (key.kind) {
342 case 'identifier':
343 return key.name;
344 case 'string':
345 return `"${key.name}"`;
346 case 'computed': {
347 return `[${printPlace(key.name)}]`;
348 }
349 case 'number': {
350 return String(key.name);
351 }
352 }
353 }
354
355 export function printInstructionValue(instrValue: ReactiveValue): string {
356 let value = '';
357 switch (instrValue.kind) {
358 case 'ArrayExpression': {
359 value = `Array [${instrValue.elements
360 .map(element => {
361 if (element.kind === 'Identifier') {
362 return printPlace(element);
363 } else if (element.kind === 'Hole') {
364 return printHole();
365 } else {
366 return `...${printPlace(element.place)}`;
367 }
368 })
369 .join(', ')}]`;
370 break;
371 }
372 case 'ObjectExpression': {
373 const properties = [];
374 if (instrValue.properties !== null) {
375 for (const property of instrValue.properties) {
376 if (property.kind === 'ObjectProperty') {
377 properties.push(
378 `${printObjectPropertyKey(property.key)}: ${printPlace(
379 property.place,
380 )}`,
381 );
382 } else {
383 properties.push(`...${printPlace(property.place)}`);
384 }
385 }
386 }
387 value = `Object { ${properties.join(', ')} }`;
388 break;
389 }
390 case 'UnaryExpression': {
391 value = `Unary ${printPlace(instrValue.value)}`;
392 break;
393 }
394 case 'BinaryExpression': {
395 value = `Binary ${printPlace(instrValue.left)} ${
396 instrValue.operator
397 } ${printPlace(instrValue.right)}`;
398 break;
399 }
400 case 'NewExpression': {
401 value = `New ${printPlace(instrValue.callee)}(${instrValue.args
402 .map(arg => printPattern(arg))
403 .join(', ')})`;
404 break;
405 }
406 case 'CallExpression': {
407 value = `Call ${printPlace(instrValue.callee)}(${instrValue.args
408 .map(arg => printPattern(arg))
409 .join(', ')})`;
410 break;
411 }
412 case 'MethodCall': {
413 value = `MethodCall ${printPlace(instrValue.receiver)}.${printPlace(
414 instrValue.property,
415 )}(${instrValue.args.map(arg => printPattern(arg)).join(', ')})`;
416 break;
417 }
418 case 'JSXText': {
419 value = `JSXText ${JSON.stringify(instrValue.value)}`;
420 break;
421 }
422 case 'Primitive': {
423 if (instrValue.value === undefined) {
424 value = '<undefined>';
425 } else {
426 value = JSON.stringify(instrValue.value);
427 }
428 break;
429 }
430 case 'TypeCastExpression': {
431 value = `TypeCast ${printPlace(instrValue.value)}: ${printType(
432 instrValue.type,
433 )}`;
434 break;
435 }
436 case 'JsxExpression': {
437 const propItems = [];
438 for (const attribute of instrValue.props) {
439 if (attribute.kind === 'JsxAttribute') {
440 propItems.push(
441 `${attribute.name}={${
442 attribute.place !== null ? printPlace(attribute.place) : '<empty>'
443 }}`,
444 );
445 } else {
446 propItems.push(`...${printPlace(attribute.argument)}`);
447 }
448 }
449 const tag =
450 instrValue.tag.kind === 'Identifier'
451 ? printPlace(instrValue.tag)
452 : instrValue.tag.name;
453 const props = propItems.length !== 0 ? ' ' + propItems.join(' ') : '';
454 if (instrValue.children !== null) {
455 const children = instrValue.children.map(child => {
456 return `{${printPlace(child)}}`;
457 });
458 value = `JSX <${tag}${props}${
459 props.length > 0 ? ' ' : ''
460 }>${children.join('')}</${tag}>`;
461 } else {
462 value = `JSX <${tag}${props}${props.length > 0 ? ' ' : ''}/>`;
463 }
464 break;
465 }
466 case 'JsxFragment': {
467 value = `JsxFragment [${instrValue.children
468 .map(child => printPlace(child))
469 .join(', ')}]`;
470 break;
471 }
472 case 'UnsupportedNode': {
473 value = `UnsupportedNode ${instrValue.node.type}`;
474 break;
475 }
476 case 'LoadLocal': {
477 value = `LoadLocal ${printPlace(instrValue.place)}`;
478 break;
479 }
480 case 'DeclareLocal': {
481 value = `DeclareLocal ${instrValue.lvalue.kind} ${printPlace(
482 instrValue.lvalue.place,
483 )}`;
484 break;
485 }
486 case 'DeclareContext': {
487 value = `DeclareContext ${instrValue.lvalue.kind} ${printPlace(
488 instrValue.lvalue.place,
489 )}`;
490 break;
491 }
492 case 'StoreLocal': {
493 value = `StoreLocal ${instrValue.lvalue.kind} ${printPlace(
494 instrValue.lvalue.place,
495 )} = ${printPlace(instrValue.value)}`;
496 break;
497 }
498 case 'LoadContext': {
499 value = `LoadContext ${printPlace(instrValue.place)}`;
500 break;
501 }
502 case 'StoreContext': {
503 value = `StoreContext ${instrValue.lvalue.kind} ${printPlace(
504 instrValue.lvalue.place,
505 )} = ${printPlace(instrValue.value)}`;
506 break;
507 }
508 case 'Destructure': {
509 value = `Destructure ${instrValue.lvalue.kind} ${printPattern(
510 instrValue.lvalue.pattern,
511 )} = ${printPlace(instrValue.value)}`;
512 break;
513 }
514 case 'PropertyLoad': {
515 value = `PropertyLoad ${printPlace(instrValue.object)}.${
516 instrValue.property
517 }`;
518 break;
519 }
520 case 'PropertyStore': {
521 value = `PropertyStore ${printPlace(instrValue.object)}.${
522 instrValue.property
523 } = ${printPlace(instrValue.value)}`;
524 break;
525 }
526 case 'PropertyDelete': {
527 value = `PropertyDelete ${printPlace(instrValue.object)}.${
528 instrValue.property
529 }`;
530 break;
531 }
532 case 'ComputedLoad': {
533 value = `ComputedLoad ${printPlace(instrValue.object)}[${printPlace(
534 instrValue.property,
535 )}]`;
536 break;
537 }
538 case 'ComputedStore': {
539 value = `ComputedStore ${printPlace(instrValue.object)}[${printPlace(
540 instrValue.property,
541 )}] = ${printPlace(instrValue.value)}`;
542 break;
543 }
544 case 'ComputedDelete': {
545 value = `ComputedDelete ${printPlace(instrValue.object)}[${printPlace(
546 instrValue.property,
547 )}]`;
548 break;
549 }
550 case 'ObjectMethod':
551 case 'FunctionExpression': {
552 const kind =
553 instrValue.kind === 'FunctionExpression' ? 'Function' : 'ObjectMethod';
554 const name = getFunctionName(instrValue, '');
555 const fn = printFunction(instrValue.loweredFunc.func)
556 .split('\n')
557 .map(line => ` ${line}`)
558 .join('\n');
559 const context = instrValue.loweredFunc.func.context
560 .map(dep => printPlace(dep))
561 .join(',');
562 const aliasingEffects =
563 instrValue.loweredFunc.func.aliasingEffects
564 ?.map(printAliasingEffect)
565 ?.join(', ') ?? '';
566 value = `${kind} ${name} @context[${context}] @aliasingEffects=[${aliasingEffects}]\n${fn}`;
567 break;
568 }
569 case 'TaggedTemplateExpression': {
570 value = `${printPlace(instrValue.tag)}\`${instrValue.value.raw}\``;
571 break;
572 }
573 case 'LogicalExpression': {
574 value = `Logical ${printInstructionValue(instrValue.left)} ${
575 instrValue.operator
576 } ${printInstructionValue(instrValue.right)}`;
577 break;
578 }
579 case 'SequenceExpression': {
580 value = [
581 `Sequence`,
582 ...instrValue.instructions.map(
583 instr => ` ${printInstruction(instr)}`,
584 ),
585 ` ${printInstructionValue(instrValue.value)}`,
586 ].join('\n');
587 break;
588 }
589 case 'ConditionalExpression': {
590 value = `Ternary ${printInstructionValue(
591 instrValue.test,
592 )} ? ${printInstructionValue(
593 instrValue.consequent,
594 )} : ${printInstructionValue(instrValue.alternate)}`;
595 break;
596 }
597 case 'TemplateLiteral': {
598 value = '`';
599 CompilerError.invariant(
600 instrValue.subexprs.length === instrValue.quasis.length - 1,
601 {
602 reason: 'Bad assumption about quasi length.',
603 loc: instrValue.loc,
604 },
605 );
606 for (let i = 0; i < instrValue.subexprs.length; i++) {
607 value += instrValue.quasis[i].raw;
608 value += `\${${printPlace(instrValue.subexprs[i])}}`;
609 }
610 value += instrValue.quasis.at(-1)!.raw + '`';
611 break;
612 }
613 case 'LoadGlobal': {
614 switch (instrValue.binding.kind) {
615 case 'Global': {
616 value = `LoadGlobal(global) ${instrValue.binding.name}`;
617 break;
618 }
619 case 'ModuleLocal': {
620 value = `LoadGlobal(module) ${instrValue.binding.name}`;
621 break;
622 }
623 case 'ImportDefault': {
624 value = `LoadGlobal import ${instrValue.binding.name} from '${instrValue.binding.module}'`;
625 break;
626 }
627 case 'ImportNamespace': {
628 value = `LoadGlobal import * as ${instrValue.binding.name} from '${instrValue.binding.module}'`;
629 break;
630 }
631 case 'ImportSpecifier': {
632 if (instrValue.binding.imported !== instrValue.binding.name) {
633 value = `LoadGlobal import { ${instrValue.binding.imported} as ${instrValue.binding.name} } from '${instrValue.binding.module}'`;
634 } else {
635 value = `LoadGlobal import { ${instrValue.binding.name} } from '${instrValue.binding.module}'`;
636 }
637 break;
638 }
639 default: {
640 assertExhaustive(
641 instrValue.binding,
642 `Unexpected binding kind \`${(instrValue.binding as any).kind}\``,
643 );
644 }
645 }
646 break;
647 }
648 case 'StoreGlobal': {
649 value = `StoreGlobal ${instrValue.name} = ${printPlace(
650 instrValue.value,
651 )}`;
652 break;
653 }
654 case 'OptionalExpression': {
655 value = `OptionalExpression ${printInstructionValue(instrValue.value)}`;
656 break;
657 }
658 case 'RegExpLiteral': {
659 value = `RegExp /${instrValue.pattern}/${instrValue.flags}`;
660 break;
661 }
662 case 'MetaProperty': {
663 value = `MetaProperty ${instrValue.meta}.${instrValue.property}`;
664 break;
665 }
666 case 'Await': {
667 value = `Await ${printPlace(instrValue.value)}`;
668 break;
669 }
670 case 'GetIterator': {
671 value = `GetIterator collection=${printPlace(instrValue.collection)}`;
672 break;
673 }
674 case 'IteratorNext': {
675 value = `IteratorNext iterator=${printPlace(
676 instrValue.iterator,
677 )} collection=${printPlace(instrValue.collection)}`;
678 break;
679 }
680 case 'NextPropertyOf': {
681 value = `NextPropertyOf ${printPlace(instrValue.value)}`;
682 break;
683 }
684 case 'Debugger': {
685 value = `Debugger`;
686 break;
687 }
688 case 'PostfixUpdate': {
689 value = `PostfixUpdate ${printPlace(instrValue.lvalue)} = ${printPlace(
690 instrValue.value,
691 )} ${instrValue.operation}`;
692 break;
693 }
694 case 'PrefixUpdate': {
695 value = `PrefixUpdate ${printPlace(instrValue.lvalue)} = ${
696 instrValue.operation
697 } ${printPlace(instrValue.value)}`;
698 break;
699 }
700 case 'StartMemoize': {
701 value = `StartMemoize deps=${
702 instrValue.deps?.map(dep => printManualMemoDependency(dep, false)) ??
703 '(none)'
704 }`;
705 break;
706 }
707 case 'FinishMemoize': {
708 value = `FinishMemoize decl=${printPlace(instrValue.decl)}${instrValue.pruned ? ' pruned' : ''}`;
709 break;
710 }
711 default: {
712 assertExhaustive(
713 instrValue,
714 `Unexpected instruction kind '${
715 (instrValue as any as InstructionValue).kind
716 }'`,
717 );
718 }
719 }
720 return value;
721 }
722
723 function isMutable(range: MutableRange): boolean {
724 return range.end > range.start + 1;
725 }
726
727 const DEBUG_MUTABLE_RANGES = false;
728 function printMutableRange(identifier: Identifier): string {
729 if (DEBUG_MUTABLE_RANGES) {
730 // if debugging, print both the identifier and scope range if they differ
731 const range = identifier.mutableRange;
732 const scopeRange = identifier.scope?.range;
733 if (
734 scopeRange != null &&
735 (scopeRange.start !== range.start || scopeRange.end !== range.end)
736 ) {
737 return `[${range.start}:${range.end}] scope=[${scopeRange.start}:${scopeRange.end}]`;
738 }
739 return isMutable(range) ? `[${range.start}:${range.end}]` : '';
740 }
741 // in non-debug mode, prefer the scope range if it exists
742 const range = identifier.scope?.range ?? identifier.mutableRange;
743 return isMutable(range) ? `[${range.start}:${range.end}]` : '';
744 }
745
746 export function printLValue(lval: LValue): string {
747 let lvalue = `${printPlace(lval.place)}`;
748
749 switch (lval.kind) {
750 case InstructionKind.Let: {
751 return `Let ${lvalue}`;
752 }
753 case InstructionKind.Const: {
754 return `Const ${lvalue}$`;
755 }
756 case InstructionKind.Reassign: {
757 return `Reassign ${lvalue}`;
758 }
759 case InstructionKind.Catch: {
760 return `Catch ${lvalue}`;
761 }
762 case InstructionKind.HoistedConst: {
763 return `HoistedConst ${lvalue}$`;
764 }
765 case InstructionKind.HoistedLet: {
766 return `HoistedLet ${lvalue}$`;
767 }
768 case InstructionKind.Function: {
769 return `Function ${lvalue}$`;
770 }
771 case InstructionKind.HoistedFunction: {
772 return `HoistedFunction ${lvalue}$`;
773 }
774 default: {
775 assertExhaustive(lval.kind, `Unexpected lvalue kind \`${lval.kind}\``);
776 }
777 }
778 }
779
780 export function printPattern(pattern: Pattern | Place | SpreadPattern): string {
781 switch (pattern.kind) {
782 case 'ArrayPattern': {
783 return (
784 '[ ' +
785 pattern.items
786 .map(item => {
787 if (item.kind === 'Hole') {
788 return '<hole>';
789 }
790 return printPattern(item);
791 })
792 .join(', ') +
793 ' ]'
794 );
795 }
796 case 'ObjectPattern': {
797 return (
798 '{ ' +
799 pattern.properties
800 .map(item => {
801 switch (item.kind) {
802 case 'ObjectProperty': {
803 return `${printObjectPropertyKey(item.key)}: ${printPattern(
804 item.place,
805 )}`;
806 }
807 case 'Spread': {
808 return printPattern(item);
809 }
810 default: {
811 assertExhaustive(item, 'Unexpected object property kind');
812 }
813 }
814 })
815 .join(', ') +
816 ' }'
817 );
818 }
819 case 'Spread': {
820 return `...${printPlace(pattern.place)}`;
821 }
822 case 'Identifier': {
823 return printPlace(pattern);
824 }
825 default: {
826 assertExhaustive(
827 pattern,
828 `Unexpected pattern kind \`${(pattern as any).kind}\``,
829 );
830 }
831 }
832 }
833
834 export function printPlace(place: Place): string {
835 const items = [
836 place.effect,
837 ' ',
838 printIdentifier(place.identifier),
839 printMutableRange(place.identifier),
840 printType(place.identifier.type),
841 place.reactive ? '{reactive}' : null,
842 ];
843 return items.filter(x => x != null).join('');
844 }
845
846 export function printIdentifier(id: Identifier): string {
847 return `${printName(id.name)}\$${id.id}${printScope(id.scope)}`;
848 }
849
850 function printName(name: IdentifierName | null): string {
851 if (name === null) {
852 return '';
853 }
854 return name.value;
855 }
856
857 function printScope(scope: ReactiveScope | null): string {
858 return `${scope !== null ? `_@${scope.id}` : ''}`;
859 }
860
861 export function printManualMemoDependency(
862 val: ManualMemoDependency,
863 nameOnly: boolean,
864 ): string {
865 let rootStr;
866 if (val.root.kind === 'Global') {
867 rootStr = val.root.identifierName;
868 } else {
869 CompilerError.invariant(val.root.value.identifier.name?.kind === 'named', {
870 reason: 'DepsValidation: expected named local variable in depslist',
871 loc: val.root.value.loc,
872 });
873 rootStr = nameOnly
874 ? val.root.value.identifier.name.value
875 : printIdentifier(val.root.value.identifier);
876 }
877 return `${rootStr}${val.path.map(v => `${v.optional ? '?.' : '.'}${v.property}`).join('')}`;
878 }
879 export function printType(type: Type): string {
880 if (type.kind === 'Type') return '';
881 // TODO(mofeiZ): add debugName for generated ids
882 if (type.kind === 'Object' && type.shapeId != null) {
883 return `:T${type.kind}<${type.shapeId}>`;
884 } else if (type.kind === 'Function' && type.shapeId != null) {
885 const returnType = printType(type.return);
886 return `:T${type.kind}<${type.shapeId}>()${returnType !== '' ? `: ${returnType}` : ''}`;
887 } else {
888 return `:T${type.kind}`;
889 }
890 }
891
892 export function printSourceLocation(loc: SourceLocation): string {
893 if (typeof loc === 'symbol') {
894 return 'generated';
895 } else {
896 return `${loc.start.line}:${loc.start.column}:${loc.end.line}:${loc.end.column}`;
897 }
898 }
899
900 export function printSourceLocationLine(loc: SourceLocation): string {
901 if (typeof loc === 'symbol') {
902 return 'generated';
903 } else {
904 return `${loc.start.line}:${loc.end.line}`;
905 }
906 }
907
908 export function printAliases(aliases: DisjointSet<Identifier>): string {
909 const aliasSets = aliases.buildSets();
910
911 const items = [];
912 for (const aliasSet of aliasSets) {
913 items.push([...aliasSet].map(id => printIdentifier(id)).join(','));
914 }
915
916 return items.join('\n');
917 }
918
919 function getFunctionName(
920 instrValue: ObjectMethod | FunctionExpression,
921 defaultValue: string,
922 ): string {
923 switch (instrValue.kind) {
924 case 'FunctionExpression':
925 return instrValue.name ?? defaultValue;
926 case 'ObjectMethod':
927 return defaultValue;
928 }
929 }
930
931 export function printAliasingEffect(effect: AliasingEffect): string {
932 switch (effect.kind) {
933 case 'Assign': {
934 return `Assign ${printPlaceForAliasEffect(effect.into)} = ${printPlaceForAliasEffect(effect.from)}`;
935 }
936 case 'Alias': {
937 return `Alias ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`;
938 }
939 case 'MaybeAlias': {
940 return `MaybeAlias ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`;
941 }
942 case 'Capture': {
943 return `Capture ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`;
944 }
945 case 'ImmutableCapture': {
946 return `ImmutableCapture ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`;
947 }
948 case 'Create': {
949 return `Create ${printPlaceForAliasEffect(effect.into)} = ${effect.value}`;
950 }
951 case 'CreateFrom': {
952 return `Create ${printPlaceForAliasEffect(effect.into)} = kindOf(${printPlaceForAliasEffect(effect.from)})`;
953 }
954 case 'CreateFunction': {
955 return `Function ${printPlaceForAliasEffect(effect.into)} = Function captures=[${effect.captures.map(printPlaceForAliasEffect).join(', ')}]`;
956 }
957 case 'Apply': {
958 const receiverCallee =
959 effect.receiver.identifier.id === effect.function.identifier.id
960 ? printPlaceForAliasEffect(effect.receiver)
961 : `${printPlaceForAliasEffect(effect.receiver)}.${printPlaceForAliasEffect(effect.function)}`;
962 const args = effect.args
963 .map(arg => {
964 if (arg.kind === 'Identifier') {
965 return printPlaceForAliasEffect(arg);
966 } else if (arg.kind === 'Hole') {
967 return ' ';
968 }
969 return `...${printPlaceForAliasEffect(arg.place)}`;
970 })
971 .join(', ');
972 let signature = '';
973 if (effect.signature != null) {
974 if (effect.signature.aliasing != null) {
975 signature = printAliasingSignature(effect.signature.aliasing);
976 } else {
977 signature = JSON.stringify(effect.signature, null, 2);
978 }
979 }
980 return `Apply ${printPlaceForAliasEffect(effect.into)} = ${receiverCallee}(${args})${signature != '' ? '\n ' : ''}${signature}`;
981 }
982 case 'Freeze': {
983 return `Freeze ${printPlaceForAliasEffect(effect.value)} ${effect.reason}`;
984 }
985 case 'Mutate':
986 case 'MutateConditionally':
987 case 'MutateTransitive':
988 case 'MutateTransitiveConditionally': {
989 return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}${effect.kind === 'Mutate' && effect.reason?.kind === 'AssignCurrentProperty' ? ' (assign `.current`)' : ''}`;
990 }
991 case 'MutateFrozen': {
992 return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;
993 }
994 case 'MutateGlobal': {
995 return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;
996 }
997 case 'Impure': {
998 return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;
999 }
1000 case 'Render': {
1001 return `Render ${printPlaceForAliasEffect(effect.place)}`;
1002 }
1003 default: {
1004 assertExhaustive(effect, `Unexpected kind '${(effect as any).kind}'`);
1005 }
1006 }
1007 }
1008
1009 function printPlaceForAliasEffect(place: Place): string {
1010 return printIdentifier(place.identifier);
1011 }
1012
1013 export function printAliasingSignature(signature: AliasingSignature): string {
1014 const tokens: Array<string> = ['function '];
1015 if (signature.temporaries.length !== 0) {
1016 tokens.push('<');
1017 tokens.push(
1018 signature.temporaries.map(temp => `$${temp.identifier.id}`).join(', '),
1019 );
1020 tokens.push('>');
1021 }
1022 tokens.push('(');
1023 tokens.push('this=$' + String(signature.receiver));
1024 for (const param of signature.params) {
1025 tokens.push(', $' + String(param));
1026 }
1027 if (signature.rest != null) {
1028 tokens.push(`, ...$${String(signature.rest)}`);
1029 }
1030 tokens.push('): ');
1031 tokens.push('$' + String(signature.returns) + ':');
1032 for (const effect of signature.effects) {
1033 tokens.push('\n ' + printAliasingEffect(effect));
1034 }
1035 return tokens.join('');
1036 }