/
githubmr
/
facebook-react
Обзор
Документация
Войти
/
githubmr
/
facebook-react
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts
1 050 строк
31 KB
Joseph Savona
[compiler] Improve name hints for outlined functions (#34434)
09 сен 2025, 22:14
Не верифицирован
09 сен 2025, 22:14
665de2e
Код
Авторство
О чём код?
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import {CompilerError} from '../CompilerError'; import {printReactiveScopeSummary} from '../ReactiveScopes/PrintReactiveFunction'; import DisjointSet from '../Utils/DisjointSet'; import {assertExhaustive} from '../Utils/utils'; import type { FunctionExpression, HIR, HIRFunction, Identifier, IdentifierName, Instruction, InstructionValue, LValue, ManualMemoDependency, MutableRange, ObjectMethod, ObjectPropertyKey, Pattern, Phi, Place, ReactiveInstruction, ReactiveScope, ReactiveValue, SourceLocation, SpreadPattern, Terminal, Type, } from './HIR'; import {GotoVariant, InstructionKind} from './HIR'; import {AliasingEffect, AliasingSignature} from '../Inference/AliasingEffects'; export type Options = { indent: number; }; export function printFunctionWithOutlined(fn: HIRFunction): string { const output = [printFunction(fn)]; for (const outlined of fn.env.getOutlinedFunctions()) { output.push(`\nfunction ${outlined.fn.id}:\n${printHIR(outlined.fn.body)}`); } return output.join('\n'); } export function printFunction(fn: HIRFunction): string { const output = []; let definition = ''; if (fn.id !== null) { definition += fn.id; } else { definition += '<<anonymous>>'; } if (fn.nameHint != null) { definition += ` ${fn.nameHint}`; } if (fn.params.length !== 0) { definition += '(' + fn.params .map(param => { if (param.kind === 'Identifier') { return printPlace(param); } else { return `...${printPlace(param.place)}`; } }) .join(', ') + ')'; } else { definition += '()'; } definition += `: ${printPlace(fn.returns)}`; output.push(definition); output.push(...fn.directives); output.push(printHIR(fn.body)); return output.join('\n'); } export function printHIR(ir: HIR, options: Options | null = null): string { let output = []; let indent = ' '.repeat(options?.indent ?? 0); const push = (text: string, indent: string = ' '): void => { output.push(`${indent}${text}`); }; for (const [blockId, block] of ir.blocks) { output.push(`bb${blockId} (${block.kind}):`); if (block.preds.size > 0) { const preds = ['predecessor blocks:']; for (const pred of block.preds) { preds.push(`bb${pred}`); } push(preds.join(' ')); } for (const phi of block.phis) { push(printPhi(phi)); } for (const instr of block.instructions) { push(printInstruction(instr)); } const terminal = printTerminal(block.terminal); if (Array.isArray(terminal)) { terminal.forEach(line => push(line)); } else { push(terminal); } } return output.map(line => indent + line).join('\n'); } export function printMixedHIR( value: Instruction | InstructionValue | Terminal, ): string { if (!('kind' in value)) { return printInstruction(value); } switch (value.kind) { case 'try': case 'maybe-throw': case 'sequence': case 'label': case 'optional': case 'branch': case 'if': case 'logical': case 'ternary': case 'return': case 'switch': case 'throw': case 'while': case 'for': case 'unreachable': case 'unsupported': case 'goto': case 'do-while': case 'for-in': case 'for-of': case 'scope': case 'pruned-scope': { const terminal = printTerminal(value); if (Array.isArray(terminal)) { return terminal.join('; '); } return terminal; } default: { return printInstructionValue(value); } } } export function printInstruction(instr: ReactiveInstruction): string { const id = `[${instr.id}]`; let value = printInstructionValue(instr.value); if (instr.effects != null) { value += `\n ${instr.effects.map(printAliasingEffect).join('\n ')}`; } if (instr.lvalue !== null) { return `${id} ${printPlace(instr.lvalue)} = ${value}`; } else { return `${id} ${value}`; } } export function printPhi(phi: Phi): string { const items = []; items.push(printPlace(phi.place)); items.push(printMutableRange(phi.place.identifier)); items.push(printType(phi.place.identifier.type)); items.push(': phi('); const phis = []; for (const [blockId, place] of phi.operands) { phis.push(`bb${blockId}: ${printPlace(place)}`); } items.push(phis.join(', ')); items.push(')'); return items.join(''); } export function printTerminal(terminal: Terminal): Array<string> | string { let value; switch (terminal.kind) { case 'if': { value = `[${terminal.id}] If (${printPlace(terminal.test)}) then:bb${ terminal.consequent } else:bb${terminal.alternate}${ terminal.fallthrough ? ` fallthrough=bb${terminal.fallthrough}` : '' }`; break; } case 'branch': { value = `[${terminal.id}] Branch (${printPlace(terminal.test)}) then:bb${ terminal.consequent } else:bb${terminal.alternate} fallthrough:bb${terminal.fallthrough}`; break; } case 'logical': { value = `[${terminal.id}] Logical ${terminal.operator} test:bb${terminal.test} fallthrough=bb${terminal.fallthrough}`; break; } case 'ternary': { value = `[${terminal.id}] Ternary test:bb${terminal.test} fallthrough=bb${terminal.fallthrough}`; break; } case 'optional': { value = `[${terminal.id}] Optional (optional=${terminal.optional}) test:bb${terminal.test} fallthrough=bb${terminal.fallthrough}`; break; } case 'throw': { value = `[${terminal.id}] Throw ${printPlace(terminal.value)}`; break; } case 'return': { value = `[${terminal.id}] Return ${terminal.returnVariant}${ terminal.value != null ? ' ' + printPlace(terminal.value) : '' }`; if (terminal.effects != null) { value += `\n ${terminal.effects.map(printAliasingEffect).join('\n ')}`; } break; } case 'goto': { value = `[${terminal.id}] Goto${ terminal.variant === GotoVariant.Continue ? '(Continue)' : '' } bb${terminal.block}`; break; } case 'switch': { const output = []; output.push(`[${terminal.id}] Switch (${printPlace(terminal.test)})`); terminal.cases.forEach(case_ => { if (case_.test !== null) { output.push(` Case ${printPlace(case_.test)}: bb${case_.block}`); } else { output.push(` Default: bb${case_.block}`); } }); if (terminal.fallthrough) { output.push(` Fallthrough: bb${terminal.fallthrough}`); } value = output; break; } case 'do-while': { value = `[${terminal.id}] DoWhile loop=${`bb${terminal.loop}`} test=bb${ terminal.test } fallthrough=${`bb${terminal.fallthrough}`}`; break; } case 'while': { value = `[${terminal.id}] While test=bb${terminal.test} loop=${ terminal.loop !== null ? `bb${terminal.loop}` : '' } fallthrough=${terminal.fallthrough ? `bb${terminal.fallthrough}` : ''}`; break; } case 'for': { value = `[${terminal.id}] For init=bb${terminal.init} test=bb${terminal.test} loop=bb${terminal.loop} update=bb${terminal.update} fallthrough=bb${terminal.fallthrough}`; break; } case 'for-of': { value = `[${terminal.id}] ForOf init=bb${terminal.init} test=bb${terminal.test} loop=bb${terminal.loop} fallthrough=bb${terminal.fallthrough}`; break; } case 'for-in': { value = `[${terminal.id}] ForIn init=bb${terminal.init} loop=bb${terminal.loop} fallthrough=bb${terminal.fallthrough}`; break; } case 'label': { value = `[${terminal.id}] Label block=bb${terminal.block} fallthrough=${ terminal.fallthrough ? `bb${terminal.fallthrough}` : '' }`; break; } case 'sequence': { value = `[${terminal.id}] Sequence block=bb${terminal.block} fallthrough=bb${terminal.fallthrough}`; break; } case 'unreachable': { value = `[${terminal.id}] Unreachable`; break; } case 'unsupported': { value = `[${terminal.id}] Unsupported`; break; } case 'maybe-throw': { value = `[${terminal.id}] MaybeThrow continuation=bb${terminal.continuation} handler=bb${terminal.handler}`; if (terminal.effects != null) { value += `\n ${terminal.effects.map(printAliasingEffect).join('\n ')}`; } break; } case 'scope': { value = `[${terminal.id}] Scope ${printReactiveScopeSummary( terminal.scope, )} block=bb${terminal.block} fallthrough=bb${terminal.fallthrough}`; break; } case 'pruned-scope': { value = `[${terminal.id}] <pruned> Scope ${printReactiveScopeSummary( terminal.scope, )} block=bb${terminal.block} fallthrough=bb${terminal.fallthrough}`; break; } case 'try': { value = `[${terminal.id}] Try block=bb${terminal.block} handler=bb${ terminal.handler }${ terminal.handlerBinding !== null ? ` handlerBinding=(${printPlace(terminal.handlerBinding)})` : '' } fallthrough=${ terminal.fallthrough != null ? `bb${terminal.fallthrough}` : '' }`; break; } default: { assertExhaustive( terminal, `Unexpected terminal kind \`${terminal as any as Terminal}\``, ); } } return value; } function printHole(): string { return '<hole>'; } function printObjectPropertyKey(key: ObjectPropertyKey): string { switch (key.kind) { case 'identifier': return key.name; case 'string': return `"${key.name}"`; case 'computed': { return `[${printPlace(key.name)}]`; } case 'number': { return String(key.name); } } } export function printInstructionValue(instrValue: ReactiveValue): string { let value = ''; switch (instrValue.kind) { case 'ArrayExpression': { value = `Array [${instrValue.elements .map(element => { if (element.kind === 'Identifier') { return printPlace(element); } else if (element.kind === 'Hole') { return printHole(); } else { return `...${printPlace(element.place)}`; } }) .join(', ')}]`; break; } case 'ObjectExpression': { const properties = []; if (instrValue.properties !== null) { for (const property of instrValue.properties) { if (property.kind === 'ObjectProperty') { properties.push( `${printObjectPropertyKey(property.key)}: ${printPlace( property.place, )}`, ); } else { properties.push(`...${printPlace(property.place)}`); } } } value = `Object { ${properties.join(', ')} }`; break; } case 'UnaryExpression': { value = `Unary ${printPlace(instrValue.value)}`; break; } case 'BinaryExpression': { value = `Binary ${printPlace(instrValue.left)} ${ instrValue.operator } ${printPlace(instrValue.right)}`; break; } case 'NewExpression': { value = `New ${printPlace(instrValue.callee)}(${instrValue.args .map(arg => printPattern(arg)) .join(', ')})`; break; } case 'CallExpression': { value = `Call ${printPlace(instrValue.callee)}(${instrValue.args .map(arg => printPattern(arg)) .join(', ')})`; break; } case 'MethodCall': { value = `MethodCall ${printPlace(instrValue.receiver)}.${printPlace( instrValue.property, )}(${instrValue.args.map(arg => printPattern(arg)).join(', ')})`; break; } case 'JSXText': { value = `JSXText ${JSON.stringify(instrValue.value)}`; break; } case 'Primitive': { if (instrValue.value === undefined) { value = '<undefined>'; } else { value = JSON.stringify(instrValue.value); } break; } case 'TypeCastExpression': { value = `TypeCast ${printPlace(instrValue.value)}: ${printType( instrValue.type, )}`; break; } case 'JsxExpression': { const propItems = []; for (const attribute of instrValue.props) { if (attribute.kind === 'JsxAttribute') { propItems.push( `${attribute.name}={${ attribute.place !== null ? printPlace(attribute.place) : '<empty>' }}`, ); } else { propItems.push(`...${printPlace(attribute.argument)}`); } } const tag = instrValue.tag.kind === 'Identifier' ? printPlace(instrValue.tag) : instrValue.tag.name; const props = propItems.length !== 0 ? ' ' + propItems.join(' ') : ''; if (instrValue.children !== null) { const children = instrValue.children.map(child => { return `{${printPlace(child)}}`; }); value = `JSX <${tag}${props}${ props.length > 0 ? ' ' : '' }>${children.join('')}</${tag}>`; } else { value = `JSX <${tag}${props}${props.length > 0 ? ' ' : ''}/>`; } break; } case 'JsxFragment': { value = `JsxFragment [${instrValue.children .map(child => printPlace(child)) .join(', ')}]`; break; } case 'UnsupportedNode': { value = `UnsupportedNode ${instrValue.node.type}`; break; } case 'LoadLocal': { value = `LoadLocal ${printPlace(instrValue.place)}`; break; } case 'DeclareLocal': { value = `DeclareLocal ${instrValue.lvalue.kind} ${printPlace( instrValue.lvalue.place, )}`; break; } case 'DeclareContext': { value = `DeclareContext ${instrValue.lvalue.kind} ${printPlace( instrValue.lvalue.place, )}`; break; } case 'StoreLocal': { value = `StoreLocal ${instrValue.lvalue.kind} ${printPlace( instrValue.lvalue.place, )} = ${printPlace(instrValue.value)}`; break; } case 'LoadContext': { value = `LoadContext ${printPlace(instrValue.place)}`; break; } case 'StoreContext': { value = `StoreContext ${instrValue.lvalue.kind} ${printPlace( instrValue.lvalue.place, )} = ${printPlace(instrValue.value)}`; break; } case 'Destructure': { value = `Destructure ${instrValue.lvalue.kind} ${printPattern( instrValue.lvalue.pattern, )} = ${printPlace(instrValue.value)}`; break; } case 'PropertyLoad': { value = `PropertyLoad ${printPlace(instrValue.object)}.${ instrValue.property }`; break; } case 'PropertyStore': { value = `PropertyStore ${printPlace(instrValue.object)}.${ instrValue.property } = ${printPlace(instrValue.value)}`; break; } case 'PropertyDelete': { value = `PropertyDelete ${printPlace(instrValue.object)}.${ instrValue.property }`; break; } case 'ComputedLoad': { value = `ComputedLoad ${printPlace(instrValue.object)}[${printPlace( instrValue.property, )}]`; break; } case 'ComputedStore': { value = `ComputedStore ${printPlace(instrValue.object)}[${printPlace( instrValue.property, )}] = ${printPlace(instrValue.value)}`; break; } case 'ComputedDelete': { value = `ComputedDelete ${printPlace(instrValue.object)}[${printPlace( instrValue.property, )}]`; break; } case 'ObjectMethod': case 'FunctionExpression': { const kind = instrValue.kind === 'FunctionExpression' ? 'Function' : 'ObjectMethod'; const name = getFunctionName(instrValue, ''); const fn = printFunction(instrValue.loweredFunc.func) .split('\n') .map(line => ` ${line}`) .join('\n'); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); const aliasingEffects = instrValue.loweredFunc.func.aliasingEffects ?.map(printAliasingEffect) ?.join(', ') ?? ''; value = `${kind} ${name} @context[${context}] @aliasingEffects=[${aliasingEffects}]\n${fn}`; break; } case 'TaggedTemplateExpression': { value = `${printPlace(instrValue.tag)}\`${instrValue.value.raw}\``; break; } case 'LogicalExpression': { value = `Logical ${printInstructionValue(instrValue.left)} ${ instrValue.operator } ${printInstructionValue(instrValue.right)}`; break; } case 'SequenceExpression': { value = [ `Sequence`, ...instrValue.instructions.map( instr => ` ${printInstruction(instr)}`, ), ` ${printInstructionValue(instrValue.value)}`, ].join('\n'); break; } case 'ConditionalExpression': { value = `Ternary ${printInstructionValue( instrValue.test, )} ? ${printInstructionValue( instrValue.consequent, )} : ${printInstructionValue(instrValue.alternate)}`; break; } case 'TemplateLiteral': { value = '`'; CompilerError.invariant( instrValue.subexprs.length === instrValue.quasis.length - 1, { reason: 'Bad assumption about quasi length.', description: null, details: [ { kind: 'error', loc: instrValue.loc, message: null, }, ], suggestions: null, }, ); for (let i = 0; i < instrValue.subexprs.length; i++) { value += instrValue.quasis[i].raw; value += `\${${printPlace(instrValue.subexprs[i])}}`; } value += instrValue.quasis.at(-1)!.raw + '`'; break; } case 'LoadGlobal': { switch (instrValue.binding.kind) { case 'Global': { value = `LoadGlobal(global) ${instrValue.binding.name}`; break; } case 'ModuleLocal': { value = `LoadGlobal(module) ${instrValue.binding.name}`; break; } case 'ImportDefault': { value = `LoadGlobal import ${instrValue.binding.name} from '${instrValue.binding.module}'`; break; } case 'ImportNamespace': { value = `LoadGlobal import * as ${instrValue.binding.name} from '${instrValue.binding.module}'`; break; } case 'ImportSpecifier': { if (instrValue.binding.imported !== instrValue.binding.name) { value = `LoadGlobal import { ${instrValue.binding.imported} as ${instrValue.binding.name} } from '${instrValue.binding.module}'`; } else { value = `LoadGlobal import { ${instrValue.binding.name} } from '${instrValue.binding.module}'`; } break; } default: { assertExhaustive( instrValue.binding, `Unexpected binding kind \`${(instrValue.binding as any).kind}\``, ); } } break; } case 'StoreGlobal': { value = `StoreGlobal ${instrValue.name} = ${printPlace( instrValue.value, )}`; break; } case 'OptionalExpression': { value = `OptionalExpression ${printInstructionValue(instrValue.value)}`; break; } case 'RegExpLiteral': { value = `RegExp /${instrValue.pattern}/${instrValue.flags}`; break; } case 'MetaProperty': { value = `MetaProperty ${instrValue.meta}.${instrValue.property}`; break; } case 'Await': { value = `Await ${printPlace(instrValue.value)}`; break; } case 'GetIterator': { value = `GetIterator collection=${printPlace(instrValue.collection)}`; break; } case 'IteratorNext': { value = `IteratorNext iterator=${printPlace( instrValue.iterator, )} collection=${printPlace(instrValue.collection)}`; break; } case 'NextPropertyOf': { value = `NextPropertyOf ${printPlace(instrValue.value)}`; break; } case 'Debugger': { value = `Debugger`; break; } case 'PostfixUpdate': { value = `PostfixUpdate ${printPlace(instrValue.lvalue)} = ${printPlace( instrValue.value, )} ${instrValue.operation}`; break; } case 'PrefixUpdate': { value = `PrefixUpdate ${printPlace(instrValue.lvalue)} = ${ instrValue.operation } ${printPlace(instrValue.value)}`; break; } case 'StartMemoize': { value = `StartMemoize deps=${ instrValue.deps?.map(dep => printManualMemoDependency(dep, false)) ?? '(none)' }`; break; } case 'FinishMemoize': { value = `FinishMemoize decl=${printPlace(instrValue.decl)}${instrValue.pruned ? ' pruned' : ''}`; break; } default: { assertExhaustive( instrValue, `Unexpected instruction kind '${ (instrValue as any as InstructionValue).kind }'`, ); } } return value; } function isMutable(range: MutableRange): boolean { return range.end > range.start + 1; } const DEBUG_MUTABLE_RANGES = false; function printMutableRange(identifier: Identifier): string { if (DEBUG_MUTABLE_RANGES) { // if debugging, print both the identifier and scope range if they differ const range = identifier.mutableRange; const scopeRange = identifier.scope?.range; if ( scopeRange != null && (scopeRange.start !== range.start || scopeRange.end !== range.end) ) { return `[${range.start}:${range.end}] scope=[${scopeRange.start}:${scopeRange.end}]`; } return isMutable(range) ? `[${range.start}:${range.end}]` : ''; } // in non-debug mode, prefer the scope range if it exists const range = identifier.scope?.range ?? identifier.mutableRange; return isMutable(range) ? `[${range.start}:${range.end}]` : ''; } export function printLValue(lval: LValue): string { let lvalue = `${printPlace(lval.place)}`; switch (lval.kind) { case InstructionKind.Let: { return `Let ${lvalue}`; } case InstructionKind.Const: { return `Const ${lvalue}$`; } case InstructionKind.Reassign: { return `Reassign ${lvalue}`; } case InstructionKind.Catch: { return `Catch ${lvalue}`; } case InstructionKind.HoistedConst: { return `HoistedConst ${lvalue}$`; } case InstructionKind.HoistedLet: { return `HoistedLet ${lvalue}$`; } case InstructionKind.Function: { return `Function ${lvalue}$`; } case InstructionKind.HoistedFunction: { return `HoistedFunction ${lvalue}$`; } default: { assertExhaustive(lval.kind, `Unexpected lvalue kind \`${lval.kind}\``); } } } export function printPattern(pattern: Pattern | Place | SpreadPattern): string { switch (pattern.kind) { case 'ArrayPattern': { return ( '[ ' + pattern.items .map(item => { if (item.kind === 'Hole') { return '<hole>'; } return printPattern(item); }) .join(', ') + ' ]' ); } case 'ObjectPattern': { return ( '{ ' + pattern.properties .map(item => { switch (item.kind) { case 'ObjectProperty': { return `${printObjectPropertyKey(item.key)}: ${printPattern( item.place, )}`; } case 'Spread': { return printPattern(item); } default: { assertExhaustive(item, 'Unexpected object property kind'); } } }) .join(', ') + ' }' ); } case 'Spread': { return `...${printPlace(pattern.place)}`; } case 'Identifier': { return printPlace(pattern); } default: { assertExhaustive( pattern, `Unexpected pattern kind \`${(pattern as any).kind}\``, ); } } } export function printPlace(place: Place): string { const items = [ place.effect, ' ', printIdentifier(place.identifier), printMutableRange(place.identifier), printType(place.identifier.type), place.reactive ? '{reactive}' : null, ]; return items.filter(x => x != null).join(''); } export function printIdentifier(id: Identifier): string { return `${printName(id.name)}\$${id.id}${printScope(id.scope)}`; } function printName(name: IdentifierName | null): string { if (name === null) { return ''; } return name.value; } function printScope(scope: ReactiveScope | null): string { return `${scope !== null ? `_@${scope.id}` : ''}`; } export function printManualMemoDependency( val: ManualMemoDependency, nameOnly: boolean, ): string { let rootStr; if (val.root.kind === 'Global') { rootStr = val.root.identifierName; } else { CompilerError.invariant(val.root.value.identifier.name?.kind === 'named', { reason: 'DepsValidation: expected named local variable in depslist', description: null, suggestions: null, details: [ { kind: 'error', loc: val.root.value.loc, message: null, }, ], }); rootStr = nameOnly ? val.root.value.identifier.name.value : printIdentifier(val.root.value.identifier); } return `${rootStr}${val.path.map(v => `${v.optional ? '?.' : '.'}${v.property}`).join('')}`; } export function printType(type: Type): string { if (type.kind === 'Type') return ''; // TODO(mofeiZ): add debugName for generated ids if (type.kind === 'Object' && type.shapeId != null) { return `:T${type.kind}<${type.shapeId}>`; } else if (type.kind === 'Function' && type.shapeId != null) { const returnType = printType(type.return); return `:T${type.kind}<${type.shapeId}>()${returnType !== '' ? `: ${returnType}` : ''}`; } else { return `:T${type.kind}`; } } export function printSourceLocation(loc: SourceLocation): string { if (typeof loc === 'symbol') { return 'generated'; } else { return `${loc.start.line}:${loc.start.column}:${loc.end.line}:${loc.end.column}`; } } export function printSourceLocationLine(loc: SourceLocation): string { if (typeof loc === 'symbol') { return 'generated'; } else { return `${loc.start.line}:${loc.end.line}`; } } export function printAliases(aliases: DisjointSet<Identifier>): string { const aliasSets = aliases.buildSets(); const items = []; for (const aliasSet of aliasSets) { items.push([...aliasSet].map(id => printIdentifier(id)).join(',')); } return items.join('\n'); } function getFunctionName( instrValue: ObjectMethod | FunctionExpression, defaultValue: string, ): string { switch (instrValue.kind) { case 'FunctionExpression': return instrValue.name ?? defaultValue; case 'ObjectMethod': return defaultValue; } } export function printAliasingEffect(effect: AliasingEffect): string { switch (effect.kind) { case 'Assign': { return `Assign ${printPlaceForAliasEffect(effect.into)} = ${printPlaceForAliasEffect(effect.from)}`; } case 'Alias': { return `Alias ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`; } case 'MaybeAlias': { return `MaybeAlias ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`; } case 'Capture': { return `Capture ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`; } case 'ImmutableCapture': { return `ImmutableCapture ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`; } case 'Create': { return `Create ${printPlaceForAliasEffect(effect.into)} = ${effect.value}`; } case 'CreateFrom': { return `Create ${printPlaceForAliasEffect(effect.into)} = kindOf(${printPlaceForAliasEffect(effect.from)})`; } case 'CreateFunction': { return `Function ${printPlaceForAliasEffect(effect.into)} = Function captures=[${effect.captures.map(printPlaceForAliasEffect).join(', ')}]`; } case 'Apply': { const receiverCallee = effect.receiver.identifier.id === effect.function.identifier.id ? printPlaceForAliasEffect(effect.receiver) : `${printPlaceForAliasEffect(effect.receiver)}.${printPlaceForAliasEffect(effect.function)}`; const args = effect.args .map(arg => { if (arg.kind === 'Identifier') { return printPlaceForAliasEffect(arg); } else if (arg.kind === 'Hole') { return ' '; } return `...${printPlaceForAliasEffect(arg.place)}`; }) .join(', '); let signature = ''; if (effect.signature != null) { if (effect.signature.aliasing != null) { signature = printAliasingSignature(effect.signature.aliasing); } else { signature = JSON.stringify(effect.signature, null, 2); } } return `Apply ${printPlaceForAliasEffect(effect.into)} = ${receiverCallee}(${args})${signature != '' ? '\n ' : ''}${signature}`; } case 'Freeze': { return `Freeze ${printPlaceForAliasEffect(effect.value)} ${effect.reason}`; } case 'Mutate': case 'MutateConditionally': case 'MutateTransitive': case 'MutateTransitiveConditionally': { return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}${effect.kind === 'Mutate' && effect.reason?.kind === 'AssignCurrentProperty' ? ' (assign `.current`)' : ''}`; } case 'MutateFrozen': { return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; } case 'MutateGlobal': { return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; } case 'Impure': { return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; } case 'Render': { return `Render ${printPlaceForAliasEffect(effect.place)}`; } default: { assertExhaustive(effect, `Unexpected kind '${(effect as any).kind}'`); } } } function printPlaceForAliasEffect(place: Place): string { return printIdentifier(place.identifier); } export function printAliasingSignature(signature: AliasingSignature): string { const tokens: Array<string> = ['function ']; if (signature.temporaries.length !== 0) { tokens.push('<'); tokens.push( signature.temporaries.map(temp => `$${temp.identifier.id}`).join(', '), ); tokens.push('>'); } tokens.push('('); tokens.push('this=$' + String(signature.receiver)); for (const param of signature.params) { tokens.push(', $' + String(param)); } if (signature.rest != null) { tokens.push(`, ...$${String(signature.rest)}`); } tokens.push('): '); tokens.push('$' + String(signature.returns) + ':'); for (const effect of signature.effects) { tokens.push('\n ' + printAliasingEffect(effect)); } return tokens.join(''); }