From dcfc75a58f4640ee9589b635e90b865bdd1a3696 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Thu, 22 Aug 2019 11:38:10 +0300 Subject: [PATCH] [FIR] Add control flow graph and infrastructure for building it For some of language constructions cfg is dummy (e.g. for finally blocks or anonymous initializers) --- .../jetbrains/kotlin/fir/resolve/dfa/Stack.kt | 93 ++++ .../fir/resolve/dfa/cfg/ControlFlowGraph.kt | 159 ++++++ .../dfa/cfg/ControlFlowGraphBuilder.kt | 507 ++++++++++++++++++ .../dfa/cfg/ControlFlowGraphNodeBuilder.kt | 156 ++++++ .../dfa/cfg/ControlFlowGraphRenderer.kt | 163 ++++++ 5 files changed, 1078 insertions(+) create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/Stack.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphRenderer.kt diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/Stack.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/Stack.kt new file mode 100644 index 00000000000..03f6e9af4ad --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/Stack.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.resolve.dfa + +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode +import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.FirSymbolOwner + +interface Stack { + val size: Int + fun top(): T + fun pop(): T + fun push(value: T) +} + +fun stackOf(vararg values: T): Stack = StackImpl(*values, pushCallback = null, popCallback = null) +fun stackWithCallbacks(pushCallback: (T) -> Unit, popCallback: (T) -> Unit): Stack = + StackImpl(pushCallback = pushCallback, popCallback = popCallback) + +val Stack<*>.isEmpty: Boolean get() = size == 0 +val Stack<*>.isNotEmpty: Boolean get() = size != 0 +fun Stack.topOrNull(): T? = if (size == 0) null else top() + +private class StackImpl( + vararg values: T, + private val pushCallback: ((T) -> Unit)?, + private val popCallback: ((T) -> Unit)? +) : Stack { + private val stack = mutableListOf(*values) + + override fun top(): T = stack[stack.size - 1] + override fun pop(): T = stack.removeAt(stack.size - 1).also { element -> + popCallback?.let { it(element) } + } + + override fun push(value: T) { + stack.add(value) + pushCallback?.let { it(value) } + } + + override val size: Int get() = stack.size +} + +class NodeStorage>( + pushCallback: ((N) -> Unit)? = null, + popCallback: ((N) -> Unit)? = null +) : Stack { + private val stack: Stack = StackImpl(pushCallback = pushCallback, popCallback = popCallback) + private val map: MutableMap = mutableMapOf() + + override val size: Int get() = stack.size + + override fun top(): N = stack.top() + + override fun pop(): N = stack.pop().also { + map.remove(it.fir) + } + + override fun push(value: N) { + stack.push(value) + map[value.fir] = value + } + + operator fun get(key: T): N? { + return map[key] + } +} + +class SymbolBasedNodeStorage> : Stack where T : FirElement { + private val stack: Stack = stackOf() + private val map: MutableMap, N> = mutableMapOf() + + override val size: Int get() = stack.size + + override fun top(): N = stack.top() + + override fun pop(): N = stack.pop().also { + map.remove((it.fir as FirSymbolOwner<*>).symbol) + } + + override fun push(value: N) { + stack.push(value) + map[(value.fir as FirSymbolOwner<*>).symbol] = value + } + + operator fun get(key: FirBasedSymbol<*>): N? { + return map[key] + } +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt new file mode 100644 index 00000000000..87c3130d0b0 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt @@ -0,0 +1,159 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.resolve.dfa.cfg + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer +import org.jetbrains.kotlin.fir.declarations.FirFunction +import org.jetbrains.kotlin.fir.declarations.FirProperty +import org.jetbrains.kotlin.fir.expressions.* + +class ControlFlowGraph(val name: String) { + val nodes = mutableListOf>() + lateinit var enterNode: CFGNode<*> + lateinit var exitNode: CFGNode<*> +} + +sealed class CFGNode(val owner: ControlFlowGraph, val level: Int) { + init { + owner.nodes += this + } + + val previousNodes = mutableListOf>() + val followingNodes = mutableListOf>() + + abstract val fir: E + var isDead: Boolean = false +} + +val CFGNode<*>.usefulFollowingNodes: List> get() = if (isDead) followingNodes else followingNodes.filterNot { it.isDead } +val CFGNode<*>.usefulPreviousNodes: List> get() = if (isDead) previousNodes else previousNodes.filterNot { it.isDead } + +interface ReturnableNothingNode { + val returnsNothing: Boolean +} + +// ----------------------------------- Named function ----------------------------------- + +class FunctionEnterNode(owner: ControlFlowGraph, override val fir: FirFunction<*>, level: Int) : CFGNode>(owner, level) +class FunctionExitNode(owner: ControlFlowGraph, override val fir: FirFunction<*>, level: Int) : CFGNode>(owner, level) + +// ----------------------------------- Property ----------------------------------- + +class PropertyEnterNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int) : CFGNode(owner, level) +class PropertyExitNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int) : CFGNode(owner, level) + +// ----------------------------------- Init ----------------------------------- + +class InitBlockEnterNode(owner: ControlFlowGraph, override val fir: FirAnonymousInitializer, level: Int) : CFGNode(owner, level) +class InitBlockExitNode(owner: ControlFlowGraph, override val fir: FirAnonymousInitializer, level: Int) : CFGNode(owner, level) + +// ----------------------------------- Block ----------------------------------- + +class BlockEnterNode(owner: ControlFlowGraph, override val fir: FirBlock, level: Int) : CFGNode(owner, level) +class BlockExitNode(owner: ControlFlowGraph, override val fir: FirBlock, level: Int) : CFGNode(owner, level) + +// ----------------------------------- When ----------------------------------- + +class WhenEnterNode(owner: ControlFlowGraph, override val fir: FirWhenExpression, level: Int) : CFGNode(owner, level) +class WhenExitNode(owner: ControlFlowGraph, override val fir: FirWhenExpression, level: Int) : CFGNode(owner, level) +class WhenBranchConditionEnterNode(owner: ControlFlowGraph, override val fir: FirWhenBranch, level: Int) : CFGNode(owner, level) +class WhenBranchConditionExitNode(owner: ControlFlowGraph, override val fir: FirWhenBranch, level: Int) : CFGNode(owner, level) +class WhenBranchResultExitNode(owner: ControlFlowGraph, override val fir: FirWhenBranch, level: Int) : CFGNode(owner, level) + +// ----------------------------------- Loop ----------------------------------- + +class LoopEnterNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode(owner, level) +class LoopBlockEnterNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode(owner, level) +class LoopBlockExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode(owner, level) +class LoopConditionEnterNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode(owner, level) +class LoopConditionExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode(owner, level) +class LoopExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode(owner, level) + +// ----------------------------------- Try-catch-finally ----------------------------------- + +class TryExpressionEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode(owner, level) +class TryMainBlockEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode(owner, level) +class TryMainBlockExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode(owner, level) +class CatchClauseEnterNode(owner: ControlFlowGraph, override val fir: FirCatch, level: Int) : CFGNode(owner, level) +class CatchClauseExitNode(owner: ControlFlowGraph, override val fir: FirCatch, level: Int) : CFGNode(owner, level) +class FinallyBlockEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode(owner, level) +class FinallyBlockExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode(owner, level) +class FinallyProxyEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode(owner, level) +class FinallyProxyExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode(owner, level) +class TryExpressionExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode(owner, level) + +// ----------------------------------- Boolean operators ----------------------------------- + +abstract class AbstractBinaryExitNode(owner: ControlFlowGraph, level: Int) : CFGNode(owner, level) { + val leftOperandNode: CFGNode<*> get() = previousNodes[0] + val rightOperandNode: CFGNode<*> get() = previousNodes[1] +} + +class BinaryAndEnterNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int) : CFGNode(owner, level) +class BinaryAndExitNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int) : AbstractBinaryExitNode(owner, level) +class BinaryOrEnterNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int) : CFGNode(owner, level) + +// Exit left node pass after left argument in case if that argument is `false` +class BinaryOrExitLeftOperandNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int) : CFGNode(owner, level) +class BinaryOrExitNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int) : AbstractBinaryExitNode(owner, level) + +// ----------------------------------- Operator call ----------------------------------- + +class TypeOperatorCallNode(owner: ControlFlowGraph, override val fir: FirTypeOperatorCall, level: Int) : CFGNode(owner, level) +class OperatorCallNode(owner: ControlFlowGraph, override val fir: FirOperatorCall, level: Int) : AbstractBinaryExitNode(owner, level) + +// ----------------------------------- Jump ----------------------------------- + +class JumpNode(owner: ControlFlowGraph, override val fir: FirJump<*>, level: Int) : CFGNode>(owner, level) +class ConstExpressionNode(owner: ControlFlowGraph, override val fir: FirConstExpression<*>, level: Int) : CFGNode>(owner, level) + +// ----------------------------------- Resolvable call ----------------------------------- + +class QualifiedAccessNode( + owner: ControlFlowGraph, + override val fir: FirQualifiedAccessExpression, + override val returnsNothing: Boolean, + level: Int +) : CFGNode(owner, level), ReturnableNothingNode + +class FunctionCallNode( + owner: ControlFlowGraph, + override val fir: FirFunctionCall, + override val returnsNothing: Boolean, + level: Int +) : CFGNode(owner, level), ReturnableNothingNode + +class ThrowExceptionNode( + owner: ControlFlowGraph, + override val fir: FirThrowExpression, + level: Int +) : CFGNode(owner, level), ReturnableNothingNode { + override val returnsNothing: Boolean get() = true +} + +class StubNode(owner: ControlFlowGraph, level: Int) : CFGNode(owner, level) { + init { + isDead = true + } + + override val fir: FirStub get() = FirStub +} + +class VariableDeclarationNode(owner: ControlFlowGraph, override val fir: FirVariable<*>, level: Int) : CFGNode>(owner, level) +class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVariableAssignment, level: Int) : CFGNode(owner, level) + +// ----------------------------------- Other ----------------------------------- + +class AnnotationEnterNode(owner: ControlFlowGraph, override val fir: FirAnnotationCall, level: Int) : CFGNode(owner, level) +class AnnotationExitNode(owner: ControlFlowGraph, override val fir: FirAnnotationCall, level: Int) : CFGNode(owner, level) + +// ----------------------------------- Stub ----------------------------------- + +object FirStub : FirElement { + override val psi: PsiElement? get() = null +} diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt new file mode 100644 index 00000000000..a15588c8a6d --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt @@ -0,0 +1,507 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.resolve.dfa.cfg + +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.impl.FirAbstractPropertyAccessor +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.render +import org.jetbrains.kotlin.fir.resolve.dfa.* +import org.jetbrains.kotlin.fir.resolve.transformers.resultType +import org.jetbrains.kotlin.fir.types.isNothing + +class ControlFlowGraphBuilder : ControlFlowGraphNodeBuilder() { + private val graphs: Stack = stackOf(ControlFlowGraph("")) + override val graph: ControlFlowGraph get() = graphs.top() + + private val lexicalScopes: Stack>> = stackOf(stackOf()) + private val lastNodes: Stack> get() = lexicalScopes.top() + val lastNode: CFGNode<*> get() = lastNodes.top() + + private val exitNodes: Stack> = stackOf() + + private val functionExitNodes: NodeStorage, FunctionExitNode> = NodeStorage( + pushCallback = { exitNodes.push(it) }, + popCallback = { exitNodes.pop() } + ) + + private val whenExitNodes: NodeStorage = NodeStorage() + + private val loopEnterNodes: NodeStorage> = NodeStorage() + private val loopExitNodes: NodeStorage = NodeStorage() + + private val tryExitNodes: NodeStorage = NodeStorage() + private val catchNodeStorages: Stack> = stackOf() + private val catchNodeStorage: NodeStorage get() = catchNodeStorages.top() + + private val binaryAndExitNodes: Stack = stackOf() + private val binaryOrExitNodes: Stack = stackOf() + + private val topLevelVariableExitNodes: Stack = stackWithCallbacks( + pushCallback = { exitNodes.push(it) }, + popCallback = { exitNodes.pop() } + ) + + private val initBlockExitNodes: Stack = stackWithCallbacks( + pushCallback = { exitNodes.push(it) }, + popCallback = { exitNodes.pop() } + ) + + override var levelCounter: Int = 0 + + fun isTopLevel(): Boolean = graphs.size == 1 + + // ----------------------------------- Named function ----------------------------------- + + fun enterFunction(function: FirFunction<*>): FunctionEnterNode { + val name = when (function) { + is FirNamedFunction -> function.name.asString() + is FirAbstractPropertyAccessor -> if (function.isGetter) "" else "" + is FirAnonymousFunction -> "" // TODO: add check to lambda or fun + is FirConstructor -> function.name.asString() + else -> throw IllegalArgumentException("Unknown function: ${function.render()}") + } + graphs.push(ControlFlowGraph(name)) + functionExitNodes.push(createFunctionExitNode(function)) + lexicalScopes.push(stackOf()) + return createFunctionEnterNode(function).also { lastNodes.push(it) }.also { levelCounter++ } + } + + fun exitFunction(function: FirFunction<*>): Pair { + levelCounter-- + val exitNode = functionExitNodes.pop() + addEdge(lastNodes.pop(), exitNode) + lexicalScopes.pop() + return exitNode to graphs.pop() + } + + // ----------------------------------- Block ----------------------------------- + + fun enterBlock(block: FirBlock): BlockEnterNode { + return createBlockEnterNode(block).also { addNewSimpleNode(it) }.also { levelCounter++ } + } + + fun exitBlock(block: FirBlock): BlockExitNode { + levelCounter-- + return createBlockExitNode(block).also { addNewSimpleNode(it) } + } + + // ----------------------------------- Property ----------------------------------- + + fun enterProperty(property: FirProperty): PropertyEnterNode { + graphs.push(ControlFlowGraph("val ${property.name}")) + val enterNode = createPropertyEnterNode(property) + val exitNode = createPropertyExitNode(property) + topLevelVariableExitNodes.push(exitNode) + lexicalScopes.push(stackOf(enterNode)) + graph.enterNode = enterNode + graph.exitNode = exitNode + levelCounter++ + return enterNode + } + + fun exitProperty(property: FirProperty): Pair { + val topLevelVariableExitNode = topLevelVariableExitNodes.pop() + addNewSimpleNode(topLevelVariableExitNode) + levelCounter-- + lexicalScopes.pop() + return topLevelVariableExitNode to graphs.pop() + } + + // ----------------------------------- Operator call ----------------------------------- + + fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall): TypeOperatorCallNode { + return createTypeOperatorCallNode(typeOperatorCall).also { addNewSimpleNode(it) } + } + + fun exitOperatorCall(operatorCall: FirOperatorCall): OperatorCallNode { + return createOperatorCallNode(operatorCall).also { addNewSimpleNode(it) } + } + + // ----------------------------------- Jump ----------------------------------- + + fun exitJump(jump: FirJump<*>): JumpNode { + val node = createJumpNode(jump) + val nextNode = when (jump) { + is FirReturnExpression -> functionExitNodes[jump.target.labeledElement] + is FirContinueExpression -> loopEnterNodes[jump.target.labeledElement] + is FirBreakExpression -> loopExitNodes[jump.target.labeledElement] + else -> throw IllegalArgumentException("Unknown jump type: ${jump.render()}") + } + + addNodeWithJump(node, nextNode) + return node + } + + // ----------------------------------- When ----------------------------------- + + fun enterWhenExpression(whenExpression: FirWhenExpression): WhenEnterNode { + val node = createWhenEnterNode(whenExpression) + addNewSimpleNode(node) + whenExitNodes.push(createWhenExitNode(whenExpression)) + levelCounter++ + return node + } + + fun enterWhenBranchCondition(whenBranch: FirWhenBranch): WhenBranchConditionEnterNode { + return createWhenBranchConditionEnterNode(whenBranch).also { addNewSimpleNode(it) }.also { levelCounter++ } + } + + fun exitWhenBranchCondition(whenBranch: FirWhenBranch): WhenBranchConditionExitNode { + levelCounter-- + return createWhenBranchConditionExitNode(whenBranch).also { + addNewSimpleNode(it) + // put exit branch condition node twice so we can refer it after exit from when expression + lastNodes.push(it) + }.also { levelCounter++ } + } + + fun exitWhenBranchResult(whenBranch: FirWhenBranch): WhenBranchResultExitNode { + levelCounter-- + val node = createWhenBranchResultExitNode(whenBranch) + addEdge(lastNodes.pop(), node) + val whenExitNode = whenExitNodes.top() + addEdge(node, whenExitNode, propagateDeadness = false) + return node + } + + fun exitWhenExpression(whenExpression: FirWhenExpression): WhenExitNode { + levelCounter-- + // exit from last condition node still on stack + // we should remove it + require(lastNodes.pop() is WhenBranchConditionExitNode) + val whenExitNode = whenExitNodes.pop() + lastNodes.push(whenExitNode) + return whenExitNode + } + + // ----------------------------------- While Loop ----------------------------------- + + fun enterWhileLoop(loop: FirLoop): LoopConditionEnterNode { + addNewSimpleNode(createLoopEnterNode(loop)) + loopExitNodes.push(createLoopExitNode(loop)) + levelCounter++ + val node = createLoopConditionEnterNode(loop) + levelCounter++ + addNewSimpleNode(node) + // put conditional node twice so we can refer it after exit from loop block + lastNodes.push(node) + loopEnterNodes.push(node) + return node + } + + fun exitWhileLoopCondition(loop: FirLoop): LoopConditionExitNode { + levelCounter-- + val conditionExitNode = createLoopConditionExitNode(loop) + addNewSimpleNode(conditionExitNode) + // TODO: here we can check that condition is always true + addEdge(conditionExitNode, loopExitNodes.top()) + addNewSimpleNode(createLoopBlockEnterNode(loop)) + levelCounter++ + return conditionExitNode + } + + fun exitWhileLoop(loop: FirLoop): Pair { + loopEnterNodes.pop() + levelCounter-- + val loopBlockExitNode = createLoopBlockExitNode(loop) + addEdge(lastNodes.pop(), loopBlockExitNode) + if (lastNodes.isNotEmpty) { + val conditionEnterNode = lastNodes.pop() + require(conditionEnterNode is LoopConditionEnterNode) { loop.render() } + addEdge(loopBlockExitNode, conditionEnterNode, propagateDeadness = false) + } + val loopExitNode = loopExitNodes.pop() + lastNodes.push(loopExitNode) + levelCounter-- + return loopBlockExitNode to loopExitNode + } + + // ----------------------------------- Do while Loop ----------------------------------- + + fun enterDoWhileLoop(loop: FirLoop): LoopBlockEnterNode { + addNewSimpleNode(createLoopEnterNode(loop)) + loopExitNodes.push(createLoopExitNode(loop)) + levelCounter++ + val blockEnterNode = createLoopBlockEnterNode(loop) + addNewSimpleNode(blockEnterNode) + // put block enter node twice so we can refer it after exit from loop condition + lastNodes.push(blockEnterNode) + loopEnterNodes.push(blockEnterNode) + levelCounter++ + return blockEnterNode + } + + fun enterDoWhileLoopCondition(loop: FirLoop): Pair { + levelCounter-- + val blockExitNode = createLoopBlockExitNode(loop).also { addNewSimpleNode(it) } + val conditionEnterNode = createLoopConditionEnterNode(loop).also { addNewSimpleNode(it) } + levelCounter++ + return blockExitNode to conditionEnterNode + } + + fun exitDoWhileLoop(loop: FirLoop): LoopExitNode { + loopEnterNodes.pop() + levelCounter-- + val conditionExitNode = createLoopConditionExitNode(loop) + // TODO: here we can check that condition is always false + addEdge(lastNodes.pop(), conditionExitNode) + val blockEnterNode = lastNodes.pop() + require(blockEnterNode is LoopBlockEnterNode) + addEdge(conditionExitNode, blockEnterNode, propagateDeadness = false) + val loopExit = loopExitNodes.pop() + addEdge(conditionExitNode, loopExit) + lastNodes.push(loopExit) + levelCounter-- + return loopExit + } + + // ----------------------------------- Boolean operators ----------------------------------- + + fun enterBinaryAnd(binaryLogicExpression: FirBinaryLogicExpression): BinaryAndEnterNode { + assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.AND) + binaryAndExitNodes.push(createBinaryAndExitNode(binaryLogicExpression)) + return createBinaryAndEnterNode(binaryLogicExpression).also { addNewSimpleNode(it) }.also { levelCounter++ } + } + + fun exitLeftBinaryAndArgument(binaryLogicExpression: FirBinaryLogicExpression) { + assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.AND) + addEdge(lastNode, binaryAndExitNodes.top()) + } + + fun exitBinaryAnd(binaryLogicExpression: FirBinaryLogicExpression): BinaryAndExitNode { + levelCounter-- + assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.AND) + return binaryAndExitNodes.pop().also { addNewSimpleNode(it) } + } + + fun enterBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrEnterNode { + assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.OR) + binaryOrExitNodes.push(createBinaryOrExitNode(binaryLogicExpression)) + return createBinaryOrEnterNode(binaryLogicExpression).also { + addNewSimpleNode(it) + // put or enter node twice so we can refer it after exit from left argument + lastNodes.push(it) + }.also { levelCounter++ } + } + + fun exitLeftBinaryOrArgument(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitLeftOperandNode { + levelCounter-- + assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.OR) + val previousNode = lastNodes.pop() + addEdge(previousNode, binaryOrExitNodes.top()) + return createBinaryOrExitLeftOperandNode(binaryLogicExpression).also { + addEdge(previousNode, it) + lastNodes.push(it) + levelCounter++ + } + } + + fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitNode { + assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.OR) + levelCounter-- + return binaryOrExitNodes.pop().also { + addEdge(lastNodes.pop(), it) + addEdge(lastNodes.pop(), it) + lastNodes.push(it) + } + } + + // ----------------------------------- Try-catch-finally ----------------------------------- + + private val finallyEnterNodes: Stack = stackOf() + + fun enterTryExpression(tryExpression: FirTryExpression): TryMainBlockEnterNode { + catchNodeStorages.push(NodeStorage()) + addNewSimpleNode(createTryExpressionEnterNode(tryExpression)) + tryExitNodes.push(createTryExpressionExitNode(tryExpression)) + levelCounter++ + val tryNode = createTryMainBlockEnterNode(tryExpression) + addNewSimpleNode(tryNode) + addEdge(tryNode, exitNodes.top()) + + for (catch in tryExpression.catches) { + val catchNode = createCatchClauseEnterNode(catch) + catchNodeStorage.push(catchNode) + addEdge(tryNode, catchNode) + addEdge(catchNode, exitNodes.top()) + } + levelCounter++ + + if (tryExpression.finallyBlock != null) { + val finallyEnterNode = createFinallyBlockEnterNode(tryExpression) + addEdge(tryNode, finallyEnterNode) + finallyEnterNodes.push(finallyEnterNode) + } + + return tryNode + } + + fun exitTryMainBlock(tryExpression: FirTryExpression): TryMainBlockExitNode { + levelCounter-- + val node = createTryMainBlockExitNode(tryExpression) + addEdge(lastNodes.pop(), node) + addEdge(node, tryExitNodes.top()) + return node + } + + fun enterCatchClause(catch: FirCatch): CatchClauseEnterNode { + return catchNodeStorage[catch]!!.also { lastNodes.push(it) }.also { levelCounter++ } + } + + fun exitCatchClause(catch: FirCatch): CatchClauseExitNode { + levelCounter-- + return createCatchClauseExitNode(catch).also { + addEdge(lastNodes.pop(), it) + addEdge(it, tryExitNodes.top(), propagateDeadness = false) + } + } + + fun enterFinallyBlock(tryExpression: FirTryExpression): FinallyBlockEnterNode { + val enterNode = finallyEnterNodes.pop() + lastNodes.push(enterNode) + return enterNode + } + + fun exitFinallyBlock(tryExpression: FirTryExpression): FinallyBlockExitNode { + return createFinallyBlockExitNode(tryExpression).also { + addEdge(lastNodes.pop(), it) + addEdge(it, tryExitNodes.top()) + } + } + + fun exitTryExpression(tryExpression: FirTryExpression): TryExpressionExitNode { + levelCounter-- + catchNodeStorages.pop() + val node = tryExitNodes.pop() + node.markAsDeadIfNecessary() + lastNodes.push(node) + return node + } + + // ----------------------------------- Resolvable call ----------------------------------- + + fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression): QualifiedAccessNode { + val returnsNothing = qualifiedAccessExpression.resultType.isNothing + val node = createQualifiedAccessNode(qualifiedAccessExpression, returnsNothing) + if (returnsNothing) { + addNodeThatReturnsNothing(node) + } else { + addNewSimpleNode(node) + } + return node + } + + fun exitFunctionCall(functionCall: FirFunctionCall): FunctionCallNode { + val returnsNothing = functionCall.resultType.isNothing + val node = createFunctionCallNode(functionCall, returnsNothing) + if (returnsNothing) { + addNodeThatReturnsNothing(node) + } else { + addNewSimpleNode(node) + } + return node + } + + fun exitConstExpresion(constExpression: FirConstExpression<*>): ConstExpressionNode { + return createConstExpressionNode(constExpression).also { addNewSimpleNode(it) } + } + + fun exitVariableDeclaration(variable: FirVariable<*>): VariableDeclarationNode { + return createVariableDeclarationNode(variable).also { addNewSimpleNode(it) } + } + + fun exitVariableAssignment(assignment: FirVariableAssignment): VariableAssignmentNode { + return createVariableAssignmentNode(assignment).also { addNewSimpleNode(it) } + } + + fun exitThrowExceptionNode(throwExpression: FirThrowExpression): ThrowExceptionNode { + return createThrowExceptionNode(throwExpression).also { addNodeThatReturnsNothing(it) } + } + + // ----------------------------------- Annotations ----------------------------------- + + fun enterAnnotationCall(annotationCall: FirAnnotationCall): AnnotationEnterNode { + return createAnnotationEnterNode(annotationCall).also { + if (graphs.size > 1) { + addNewSimpleNode(it) + } else { + lastNodes.push(it) + } + } + } + + fun exitAnnotationCall(annotationCall: FirAnnotationCall): AnnotationExitNode { + return createAnnotationExitNode(annotationCall).also { + if (graphs.size > 1) { + addNewSimpleNode(it) + } else { + lastNodes.pop() + } + } + } + + // ----------------------------------- Block ----------------------------------- + + fun enterInitBlock(initBlock: FirAnonymousInitializer): InitBlockEnterNode { + val enterNode = createInitBlockEnterNode(initBlock).also { + lexicalScopes.push(stackOf(it)) + } + val exitNode = createInitBlockExitNode(initBlock) + initBlockExitNodes.push(exitNode) + levelCounter++ + return enterNode + } + + fun exitInitBlock(initBlock: FirAnonymousInitializer): InitBlockExitNode { + levelCounter-- + return initBlockExitNodes.pop().also { + addNewSimpleNode(it) + lexicalScopes.pop() + } + } + + // ------------------------------------------------------------------------------------------------------------------------- + + private fun CFGNode<*>.markAsDeadIfNecessary() { + isDead = previousNodes.all { it.isDead } + } + + private fun addNodeThatReturnsNothing(node: CFGNode<*>) { + /* + * `return` is temporary solution that is needed for init block + * it will be replaced after correct implementation of CFG for class initialization + */ + val exitNode: CFGNode<*> = exitNodes.top() + addNodeWithJump(node, exitNode) + } + + private fun addNodeWithJump(node: CFGNode<*>, targetNode: CFGNode<*>?) { + addEdge(lastNodes.pop(), node) + if (targetNode != null) { + addEdge(node, targetNode) + } + val stub = createStubNode() + addEdge(node, stub) + lastNodes.push(stub) + } + + private fun addNewSimpleNode(newNode: CFGNode<*>): CFGNode<*> { + val oldNode = lastNodes.pop() + addEdge(oldNode, newNode) + lastNodes.push(newNode) + return oldNode + } + + private fun addEdge(from: CFGNode<*>, to: CFGNode<*>, propagateDeadness: Boolean = true) { + if (propagateDeadness && from.isDead) { + to.isDead = true + } + from.followingNodes += to + to.previousNodes += from + } +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt new file mode 100644 index 00000000000..a36a91e62b8 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt @@ -0,0 +1,156 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.resolve.dfa.cfg + +import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer +import org.jetbrains.kotlin.fir.declarations.FirFunction +import org.jetbrains.kotlin.fir.declarations.FirProperty +import org.jetbrains.kotlin.fir.expressions.* + +abstract class ControlFlowGraphNodeBuilder { + protected abstract val graph: ControlFlowGraph + protected abstract var levelCounter: Int + + protected fun createStubNode(): StubNode = StubNode( + graph, + levelCounter + ) + + protected fun createLoopExitNode(fir: FirLoop): LoopExitNode = LoopExitNode(graph, fir, levelCounter) + + protected fun createLoopEnterNode(fir: FirLoop): LoopEnterNode = LoopEnterNode(graph, fir, levelCounter) + + protected fun createInitBlockExitNode(fir: FirAnonymousInitializer): InitBlockExitNode = + InitBlockExitNode(graph, fir, levelCounter) + + protected fun createInitBlockEnterNode(fir: FirAnonymousInitializer): InitBlockEnterNode = + InitBlockEnterNode(graph, fir, levelCounter) + + protected fun createTypeOperatorCallNode(fir: FirTypeOperatorCall): TypeOperatorCallNode = + TypeOperatorCallNode(graph, fir, levelCounter) + + protected fun createOperatorCallNode(fir: FirOperatorCall): OperatorCallNode = + OperatorCallNode(graph, fir, levelCounter) + + protected fun createWhenBranchConditionExitNode(fir: FirWhenBranch): WhenBranchConditionExitNode = + WhenBranchConditionExitNode(graph, fir, levelCounter) + + protected fun createJumpNode(fir: FirJump<*>): JumpNode = + JumpNode(graph, fir, levelCounter) + + protected fun createQualifiedAccessNode( + fir: FirQualifiedAccessExpression, + returnsNothing: Boolean + ): QualifiedAccessNode = QualifiedAccessNode(graph, fir, returnsNothing, levelCounter) + + protected fun createBlockEnterNode(fir: FirBlock): BlockEnterNode = BlockEnterNode(graph, fir, levelCounter) + + protected fun createBlockExitNode(fir: FirBlock): BlockExitNode = BlockExitNode(graph, fir, levelCounter) + + protected fun createPropertyExitNode(fir: FirProperty): PropertyExitNode = PropertyExitNode(graph, fir, levelCounter) + + protected fun createPropertyEnterNode(fir: FirProperty): PropertyEnterNode = PropertyEnterNode(graph, fir, levelCounter) + + protected fun createFunctionEnterNode(fir: FirFunction<*>): FunctionEnterNode = + FunctionEnterNode(graph, fir, levelCounter).also { + graph.enterNode = it + } + + protected fun createFunctionExitNode(fir: FirFunction<*>): FunctionExitNode = FunctionExitNode(graph, fir, levelCounter).also { + graph.exitNode = it + } + + protected fun createBinaryOrEnterNode(fir: FirBinaryLogicExpression): BinaryOrEnterNode = + BinaryOrEnterNode(graph, fir, levelCounter) + + protected fun createBinaryOrExitLeftOperandNode(fir: FirBinaryLogicExpression): BinaryOrExitLeftOperandNode = + BinaryOrExitLeftOperandNode(graph, fir, levelCounter) + + protected fun createBinaryOrExitNode(fir: FirBinaryLogicExpression): BinaryOrExitNode = + BinaryOrExitNode(graph, fir, levelCounter) + + protected fun createBinaryAndExitNode(fir: FirBinaryLogicExpression): BinaryAndExitNode = + BinaryAndExitNode(graph, fir, levelCounter) + + protected fun createBinaryAndEnterNode(fir: FirBinaryLogicExpression): BinaryAndEnterNode = + BinaryAndEnterNode(graph, fir, levelCounter) + + protected fun createWhenBranchConditionEnterNode(fir: FirWhenBranch): WhenBranchConditionEnterNode = + WhenBranchConditionEnterNode(graph, fir, levelCounter) + + protected fun createWhenEnterNode(fir: FirWhenExpression): WhenEnterNode = + WhenEnterNode(graph, fir, levelCounter) + + protected fun createWhenExitNode(fir: FirWhenExpression): WhenExitNode = + WhenExitNode(graph, fir, levelCounter) + + protected fun createWhenBranchResultExitNode(fir: FirWhenBranch): WhenBranchResultExitNode = + WhenBranchResultExitNode(graph, fir, levelCounter) + + protected fun createLoopConditionExitNode(fir: FirLoop): LoopConditionExitNode = + LoopConditionExitNode(graph, fir, levelCounter) + + protected fun createLoopConditionEnterNode(fir: FirLoop): LoopConditionEnterNode = + LoopConditionEnterNode(graph, fir, levelCounter) + + protected fun createLoopBlockEnterNode(fir: FirLoop): LoopBlockEnterNode = + LoopBlockEnterNode(graph, fir, levelCounter) + + protected fun createLoopBlockExitNode(fir: FirLoop): LoopBlockExitNode = + LoopBlockExitNode(graph, fir, levelCounter) + + protected fun createFunctionCallNode(fir: FirFunctionCall, returnsNothing: Boolean): FunctionCallNode = + FunctionCallNode(graph, fir, returnsNothing, levelCounter) + + protected fun createVariableAssignmentNode(fir: FirVariableAssignment): VariableAssignmentNode = + VariableAssignmentNode(graph, fir, levelCounter) + + protected fun createAnnotationExitNode(fir: FirAnnotationCall): AnnotationExitNode = + AnnotationExitNode(graph, fir, levelCounter) + + protected fun createAnnotationEnterNode(fir: FirAnnotationCall): AnnotationEnterNode = + AnnotationEnterNode(graph, fir, levelCounter) + + protected fun createVariableDeclarationNode(fir: FirVariable<*>): VariableDeclarationNode = + VariableDeclarationNode(graph, fir, levelCounter) + + protected fun createConstExpressionNode(fir: FirConstExpression<*>): ConstExpressionNode = + ConstExpressionNode(graph, fir, levelCounter) + + protected fun createThrowExceptionNode(fir: FirThrowExpression): ThrowExceptionNode = + ThrowExceptionNode(graph, fir, levelCounter) + + protected fun createFinallyProxyExitNode(fir: FirTryExpression): FinallyProxyExitNode = + FinallyProxyExitNode(graph, fir, levelCounter) + + protected fun createFinallyProxyEnterNode(fir: FirTryExpression): FinallyProxyEnterNode = + FinallyProxyEnterNode(graph, fir, levelCounter) + + protected fun createFinallyBlockExitNode(fir: FirTryExpression): FinallyBlockExitNode = + FinallyBlockExitNode(graph, fir, levelCounter) + + protected fun createFinallyBlockEnterNode(fir: FirTryExpression): FinallyBlockEnterNode = + FinallyBlockEnterNode(graph, fir, levelCounter) + + protected fun createCatchClauseExitNode(fir: FirCatch): CatchClauseExitNode = + CatchClauseExitNode(graph, fir, levelCounter) + + protected fun createTryMainBlockExitNode(fir: FirTryExpression): TryMainBlockExitNode = + TryMainBlockExitNode(graph, fir, levelCounter) + + protected fun createTryMainBlockEnterNode(fir: FirTryExpression): TryMainBlockEnterNode = + TryMainBlockEnterNode(graph, fir, levelCounter) + + protected fun createCatchClauseEnterNode(fir: FirCatch): CatchClauseEnterNode = + CatchClauseEnterNode(graph, fir, levelCounter) + + protected fun createTryExpressionEnterNode(fir: FirTryExpression): TryExpressionEnterNode = + TryExpressionEnterNode(graph, fir, levelCounter) + + protected fun createTryExpressionExitNode(fir: FirTryExpression): TryExpressionExitNode = + TryExpressionExitNode(graph, fir, levelCounter) + +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphRenderer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphRenderer.kt new file mode 100644 index 00000000000..f9d53838299 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphRenderer.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.resolve.dfa.cfg + +import org.jetbrains.kotlin.fir.FirRenderer +import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction +import org.jetbrains.kotlin.fir.declarations.FirConstructor +import org.jetbrains.kotlin.fir.declarations.FirFunction +import org.jetbrains.kotlin.fir.declarations.FirNamedFunction +import org.jetbrains.kotlin.fir.declarations.impl.FirAbstractPropertyAccessor +import org.jetbrains.kotlin.fir.declarations.impl.FirErrorFunction +import org.jetbrains.kotlin.fir.expressions.FirDoWhileLoop +import org.jetbrains.kotlin.fir.expressions.FirLoop +import org.jetbrains.kotlin.fir.expressions.FirWhileLoop +import org.jetbrains.kotlin.fir.expressions.impl.FirElseIfTrueCondition +import org.jetbrains.kotlin.fir.render +import org.jetbrains.kotlin.utils.DFS + +private const val INDENT = " " +private const val DEAD = "[DEAD]" + +fun ControlFlowGraph.renderToStringBuilder(builder: StringBuilder) { + val sortedNodes: List> = DFS.topologicalOrder( + nodes + ) { + val result = if (it !is WhenBranchConditionExitNode || it.followingNodes.size < 2) { + it.followingNodes + } else { + it.followingNodes.sortedBy { node -> if (node is BlockEnterNode) 1 else 0 } + } + result + } + + + val indices = sortedNodes.mapIndexed { i, node -> node to i }.toMap() + val notVisited = sortedNodes.toMutableSet() + val maxLineNumberSize = sortedNodes.size.toString().length + + fun List>.renderEdges(nodeIsDead: Boolean): String = map { + indices.getValue(it) to it.isDead + }.sortedBy { it.first }.joinToString(", ") { (index, isDead) -> + index.toString() + if (isDead && !nodeIsDead) DEAD else "" + } + + fun StringBuilder.renderNode(node: CFGNode<*>, index: Int) { + append(index.toString().padStart(maxLineNumberSize)) + append(": ") + append(INDENT.repeat(node.level)) + append(node.render()) + append(" -> ") + append(node.followingNodes.renderEdges(node.isDead)) + if (node.previousNodes.isNotEmpty()) { + append(" | <- ") + append(node.previousNodes.renderEdges(node.isDead)) + } + appendln() + } + + with(builder) { + sortedNodes.forEachIndexed { i, node -> + notVisited.remove(node) + renderNode(node, i) + } + + if (notVisited.isNotEmpty()) { + appendln("Not visited nodes:") + notVisited.forEach { node -> + renderNode(node, indices.getValue(node)) + } + } + + appendln() + } +} + +fun ControlFlowGraph.render(): String = buildString { renderToStringBuilder(this) } + +fun CFGNode<*>.render(): String = + buildString { + append( + when (this@render) { + is FunctionEnterNode -> "Enter function \"${fir.name()}\"" + is FunctionExitNode -> "Exit function \"${fir.name()}\"" + + is BlockEnterNode -> "Enter block" + is BlockExitNode -> "Exit block" + + is WhenEnterNode -> "Enter when" + is WhenBranchConditionEnterNode -> "Enter when branch condition ${if (fir.condition is FirElseIfTrueCondition) "\"else\"" else ""}" + is WhenBranchConditionExitNode -> "Exit when branch condition" + is WhenBranchResultExitNode -> "Exit when branch result" + is WhenExitNode -> "Exit when" + + is LoopEnterNode -> "Enter ${fir.type()} loop" + is LoopBlockEnterNode -> "Enter loop block" + is LoopBlockExitNode -> "Exit loop block" + is LoopConditionEnterNode -> "Enter loop condition" + is LoopConditionExitNode -> "Exit loop condition" + is LoopExitNode -> "Exit ${fir.type()}loop" + + is QualifiedAccessNode -> "Access variable ${fir.calleeReference.render()}" + is OperatorCallNode -> "Operator ${fir.operation.operator}" + is TypeOperatorCallNode -> "Type operator: \"${fir.psi?.text?.toString() ?: fir.render()}\"" + is JumpNode -> "Jump: ${fir.render()}" + is StubNode -> "Stub" + + is ConstExpressionNode -> "Const: ${fir.render()}" + is VariableDeclarationNode -> + "Variable declaration: ${buildString { FirRenderer(this).visitCallableDeclaration(fir)} }" + + is VariableAssignmentNode -> "Assignmenet: ${fir.lValue.render()}" + is FunctionCallNode -> "Function call: ${fir.render()}" + is ThrowExceptionNode -> "Throw: ${fir.render()}" + + is TryExpressionEnterNode -> "Try expression enter" + is TryMainBlockEnterNode -> "Try main block enter" + is TryMainBlockExitNode -> "Try main block exit" + is CatchClauseEnterNode -> "Catch enter" + is CatchClauseExitNode -> "Catch exit" + is FinallyBlockEnterNode -> "Enter finally" + is FinallyBlockExitNode -> "Exit finally" + is FinallyProxyEnterNode -> TODO() + is FinallyProxyExitNode -> TODO() + is TryExpressionExitNode -> "Try expression exit" + + is BinaryAndEnterNode -> "Enter &&" + is BinaryAndExitNode -> "Exit &&" + is BinaryOrEnterNode -> "Enter ||" + is BinaryOrExitLeftOperandNode -> "Exit left part of ||" + is BinaryOrExitNode -> "Exit ||" + + is PropertyEnterNode -> "Enter property" + is PropertyExitNode -> "Exit property" + is InitBlockEnterNode -> "Enter init block" + is InitBlockExitNode -> "Exit init block" + is AnnotationEnterNode -> "Enter annotation" + is AnnotationExitNode -> "Exit annotation" + + else -> TODO(this@render.toString()) + } + ) + if (isDead) { + append(DEAD) + } + } + +private fun FirFunction<*>.name(): String = when (this) { + is FirNamedFunction -> name.asString() + is FirAnonymousFunction -> "anonymousFunction" + is FirConstructor -> name.asString() + is FirAbstractPropertyAccessor -> if (isGetter) "getter" else "setter" + is FirErrorFunction -> "errorFunction" + else -> TODO(toString()) +} + +private fun FirLoop.type(): String = when (this) { + is FirWhileLoop -> "while" + is FirDoWhileLoop -> "do-while" + else -> throw IllegalArgumentException() +} \ No newline at end of file