Clean up temporary variable nesting logic

This commit is contained in:
Brian Norman
2021-04-03 17:45:29 -05:00
parent f8270b4cc9
commit 8ace9f05f5
4 changed files with 191 additions and 187 deletions
@@ -16,11 +16,10 @@
package com.bnorm.power package com.bnorm.power
import com.bnorm.power.diagram.IrTemporaryVariable import com.bnorm.power.diagram.buildDiagramNesting
import com.bnorm.power.diagram.DiagramGenerator
import com.bnorm.power.diagram.buildTree import com.bnorm.power.diagram.buildTree
import com.bnorm.power.diagram.info import com.bnorm.power.diagram.info
import com.bnorm.power.diagram.irDiagram import com.bnorm.power.diagram.irDiagramString
import com.bnorm.power.diagram.substring import com.bnorm.power.diagram.substring
import com.bnorm.power.internal.ReturnableBlockTransformer import com.bnorm.power.internal.ReturnableBlockTransformer
import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.FileLoweringPass
@@ -121,6 +120,7 @@ class PowerAssertCallTransformer(
return super.visitCall(expression) return super.visitCall(expression)
} }
// TODO - support more arguments by currying?
val assertionArgument = expression.getValueArgument(0)!! val assertionArgument = expression.getValueArgument(0)!!
val messageArgument = if (function.valueParameters.size == 2) expression.getValueArgument(1) else null val messageArgument = if (function.valueParameters.size == 2) expression.getValueArgument(1) else null
@@ -129,45 +129,44 @@ class PowerAssertCallTransformer(
messageCollector.info(expression, "Expression is constant and will not be power-assert transformed") messageCollector.info(expression, "Expression is constant and will not be power-assert transformed")
return super.visitCall(expression) return super.visitCall(expression)
} }
// println(root.dump())
val symbol = currentScope!!.scope.scopeOwnerSymbol val symbol = currentScope!!.scope.scopeOwnerSymbol
DeclarationIrBuilder(context, symbol).run { val builder = DeclarationIrBuilder(context, symbol, expression.startOffset, expression.endOffset)
at(expression) return builder.buildDiagramNesting(root) { argument, variables ->
val lambda = messageArgument?.asSimpleLambda()
val generator = object : DiagramGenerator() { val title = when {
override fun IrBuilderWithScope.buildCall(argument: IrExpression, variables: List<IrTemporaryVariable>): IrExpression { messageArgument is IrConst<*> -> messageArgument
messageArgument is IrStringConcatenation -> messageArgument
val lambda = messageArgument?.asSimpleLambda() lambda != null -> lambda.deepCopyWithSymbols(parent).inline(parent)
val title = when { .transform(ReturnableBlockTransformer(context, symbol), null)
messageArgument is IrConst<*> -> messageArgument messageArgument != null -> {
messageArgument is IrStringConcatenation -> messageArgument val invoke =
lambda != null -> lambda.deepCopyWithSymbols(parent).inline(parent).transform(ReturnableBlockTransformer(context, symbol), null) messageArgument.type.getClass()!!.functions.single { it.name == OperatorNameConventions.INVOKE }
messageArgument != null -> { irCallOp(invoke.symbol, invoke.returnType, messageArgument)
val invoke = messageArgument.type.getClass()!!.functions.single { it.name == OperatorNameConventions.INVOKE }
irCallOp(invoke.symbol, invoke.returnType, messageArgument)
}
// TODO what should the default message be?
assertionArgument.type.isBoolean() -> irString("Assertion failed")
else -> null
}
val prefix = title?.deepCopyWithSymbols(parent)
val diagram = irDiagram(file, fileSource, prefix, expression, variables)
return delegate.buildCall(this, expression, argument, diagram)
} }
// TODO what should the default message be?
assertionArgument.type.isBoolean() -> irString("Assertion failed")
else -> null
} }
// println(root.dump()) val prefix = title?.deepCopyWithSymbols(parent)
return generator.buildExpression(this, root) val diagram = irDiagramString(file, fileSource, prefix, expression, variables)
delegate.buildCall(this, expression, argument, diagram)
}
// .also { println(expression.dump()) } // .also { println(expression.dump()) }
// .also { println(it.dump()) } // .also { println(it.dump()) }
// .also { println(expression.dumpKotlinLike()) } // .also { println(expression.dumpKotlinLike()) }
// .also { println(it.dumpKotlinLike()) } // .also { println(it.dumpKotlinLike()) }
}
} }
private interface FunctionDelegate { private interface FunctionDelegate {
fun buildCall(builder: IrBuilderWithScope, original: IrCall, argument: IrExpression, message: IrExpression): IrExpression fun buildCall(
builder: IrBuilderWithScope,
original: IrCall,
argument: IrExpression,
message: IrExpression
): IrExpression
} }
private fun findDelegate(function: IrFunction): FunctionDelegate? { private fun findDelegate(function: IrFunction): FunctionDelegate? {
@@ -184,7 +183,12 @@ class PowerAssertCallTransformer(
return@mapNotNull when { return@mapNotNull when {
isStringSupertype(messageParameter.type) -> { isStringSupertype(messageParameter.type) -> {
object : FunctionDelegate { object : FunctionDelegate {
override fun buildCall(builder: IrBuilderWithScope, original: IrCall, argument: IrExpression, message: IrExpression): IrExpression = with(builder) { override fun buildCall(
builder: IrBuilderWithScope,
original: IrCall,
argument: IrExpression,
message: IrExpression
): IrExpression = with(builder) {
irCall(overload, type = original.type).apply { irCall(overload, type = original.type).apply {
dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent) dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent)
extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent) extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent)
@@ -199,7 +203,12 @@ class PowerAssertCallTransformer(
} }
isStringFunction(messageParameter.type) -> { isStringFunction(messageParameter.type) -> {
object : FunctionDelegate { object : FunctionDelegate {
override fun buildCall(builder: IrBuilderWithScope, original: IrCall, argument: IrExpression, message: IrExpression): IrExpression = with(builder) { override fun buildCall(
builder: IrBuilderWithScope,
original: IrCall,
argument: IrExpression,
message: IrExpression
): IrExpression = with(builder) {
val scope = this val scope = this
val lambda = builder.context.irFactory.buildFun { val lambda = builder.context.irFactory.buildFun {
name = Name.special("<anonymous>") name = Name.special("<anonymous>")
@@ -213,7 +222,13 @@ class PowerAssertCallTransformer(
} }
parent = scope.parent parent = scope.parent
} }
val expression = IrFunctionExpressionImpl(original.startOffset, original.endOffset, messageParameter.type, lambda, IrStatementOrigin.LAMBDA) val expression = IrFunctionExpressionImpl(
original.startOffset,
original.endOffset,
messageParameter.type,
lambda,
IrStatementOrigin.LAMBDA
)
irCall(overload, type = original.type).apply { irCall(overload, type = original.type).apply {
dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent) dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent)
extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent) extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent)
@@ -0,0 +1,142 @@
/*
* Copyright (C) 2020 Brian Norman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.bnorm.power.diagram
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.irBlock
import org.jetbrains.kotlin.ir.builders.irFalse
import org.jetbrains.kotlin.ir.builders.irIfThenElse
import org.jetbrains.kotlin.ir.builders.irTrue
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
fun IrBuilderWithScope.buildDiagramNesting(
root: Node,
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression
): IrExpression {
return buildExpression(root, listOf()) { argument, subStack ->
call(argument, subStack)
}
}
private fun IrBuilderWithScope.buildExpression(
node: Node,
variables: List<IrTemporaryVariable>,
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression
): IrExpression = when (node) {
is ExpressionNode -> add(node, variables, call)
is AndNode -> nest(node, 0, variables, call)
is OrNode -> nest(node, 0, variables, call)
else -> TODO("Unknown node type=$node")
}
/**
* ```
* val result = call(1 + 2 + 3)
* ```
* Transforms to
* ```
* val result = run {
* val tmp0 = 1 + 2
* val tmp1 = tmp0 + 3
* call(tmp1, <diagram>)
* }
* ```
*/
private fun IrBuilderWithScope.add(
node: ExpressionNode,
variables: List<IrTemporaryVariable>,
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression
): IrExpression {
return irBlock {
val head = node.expressions.first().deepCopyWithSymbols(scope.getLocalDeclarationParent())
val expressions = (buildTree(head) as ExpressionNode).expressions
val transformer = IrTemporaryExtractionTransformer(this@irBlock, expressions.toSet())
val transformed = expressions.first().transform(transformer, null)
+call(transformed, variables + transformer.variables)
}
}
/**
* ```
* val result = call(1 == 1 && 2 == 2)
* ```
* Transforms to
* ```
* val result = run {
* val tmp0 = 1 == 1
* if (tmp0) {
* val tmp1 = 2 == 2
* call(tmp1, <diagram>)
* }
* else call(false, <diagram>)
* }
* ```
*/
private fun IrBuilderWithScope.nest(
node: AndNode,
index: Int,
variables: List<IrTemporaryVariable>,
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression
): IrExpression {
val children = node.children
val child = children[index]
return buildExpression(child, variables) { argument, newVariables ->
if (index + 1 == children.size) call(argument, newVariables) // last expression, result is false
else irIfThenElse(
context.irBuiltIns.anyType,
argument,
nest(node, index + 1, newVariables, call), // more expressions, continue nesting
call(irFalse(), newVariables), // short-circuit result to false
)
}
}
/**
* ```
* val result = call(1 == 1 || 2 == 2)
* ```
* Transforms to
* ```
* val result = run {
* val tmp0 = 1 == 1
* if (tmp0) call(true, <diagram>)
* else {
* val tmp1 = 2 == 2
* call(tmp1, <diagram>)
* }
* }
* ```
*/
private fun IrBuilderWithScope.nest(
node: OrNode,
index: Int,
variables: List<IrTemporaryVariable>,
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression
): IrExpression {
val children = node.children
val child = children[index]
return buildExpression(child, variables) { argument, newVariables ->
if (index + 1 == children.size) call(argument, newVariables) // last expression, result is false
else irIfThenElse(
context.irBuiltIns.anyType,
argument,
call(irTrue(), newVariables), // short-circuit result to true
nest(node, index + 1, newVariables, call), // more expressions, continue nesting
)
}
}
@@ -1,153 +0,0 @@
/*
* Copyright (C) 2020 Brian Norman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.bnorm.power.diagram
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.IrStatementsBuilder
import org.jetbrains.kotlin.ir.builders.irBlock
import org.jetbrains.kotlin.ir.builders.irFalse
import org.jetbrains.kotlin.ir.builders.irIfThenElse
import org.jetbrains.kotlin.ir.builders.irTrue
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
abstract class DiagramGenerator {
abstract fun IrBuilderWithScope.buildCall(
argument: IrExpression,
variables: List<IrTemporaryVariable>
): IrExpression
fun buildExpression(
builder: IrBuilderWithScope,
root: Node
): IrExpression {
return builder.irBlock {
buildExpression(root, listOf()) { argument, subStack ->
buildCall(argument, subStack)
}
}
}
private fun IrStatementsBuilder<*>.buildExpression(
node: Node,
variables: List<IrTemporaryVariable>,
call: IrStatementsBuilder<*>.(IrExpression, List<IrTemporaryVariable>) -> IrExpression
) {
when (node) {
is ExpressionNode -> add(node, variables, call)
is AndNode -> nest(node, 0, variables, call)
is OrNode -> nest(node, 0, variables, call)
else -> TODO("Unknown node type=$node")
}
}
/**
* ```
* val result = call(1 + 2 + 3)
* ```
* Transforms to
* ```
* val result = run {
* val tmp0 = 1 + 2
* val tmp1 = tmp0 + 3
* call(tmp1, <diagram>)
* }
* ```
*/
private fun IrStatementsBuilder<*>.add(
node: ExpressionNode,
variables: List<IrTemporaryVariable>,
call: IrStatementsBuilder<*>.(IrExpression, List<IrTemporaryVariable>) -> IrExpression
) {
val head = node.expressions.first().deepCopyWithSymbols(scope.getLocalDeclarationParent())
val expressions = (buildTree(head) as ExpressionNode).expressions
val transformer = IrTemporaryExtractionTransformer(this, expressions.toSet())
val transformed = expressions.first().transform(transformer, null)
+call(transformed, variables + transformer.variables)
}
/**
* ```
* val result = call(1 == 1 && 2 == 2)
* ```
* Transforms to
* ```
* val result = run {
* val tmp0 = 1 == 1
* if (tmp0) {
* val tmp1 = 2 == 2
* call(tmp1, <diagram>)
* }
* else call(false, <diagram>)
* }
* ```
*/
private fun IrStatementsBuilder<*>.nest(
node: AndNode,
index: Int,
variables: List<IrTemporaryVariable>,
call: IrStatementsBuilder<*>.(IrExpression, List<IrTemporaryVariable>) -> IrExpression
) {
val children = node.children
val child = children[index]
buildExpression(child, variables) { argument, newVariables ->
if (index + 1 == children.size) call(argument, newVariables) // last expression, result is false
else irIfThenElse(
context.irBuiltIns.anyType,
argument,
irBlock { nest(node, index + 1, newVariables, call) }, // more expressions, continue nesting
call(irFalse(), newVariables), // short-circuit result to false
)
}
}
/**
* ```
* val result = call(1 == 1 || 2 == 2)
* ```
* Transforms to
* ```
* val result = run {
* val tmp0 = 1 == 1
* if (tmp0) call(true, <diagram>)
* else {
* val tmp1 = 2 == 2
* call(tmp1, <diagram>)
* }
* }
* ```
*/
private fun IrStatementsBuilder<*>.nest(
node: OrNode,
index: Int,
variables: List<IrTemporaryVariable>,
call: IrStatementsBuilder<*>.(IrExpression, List<IrTemporaryVariable>) -> IrExpression
) {
val children = node.children
val child = children[index]
buildExpression(child, variables) { argument, newVariables ->
if (index + 1 == children.size) call(argument, newVariables) // last expression, result is false
else irIfThenElse(
context.irBuiltIns.anyType,
argument,
call(irTrue(), newVariables), // short-circuit result to true
irBlock { nest(node, index + 1, newVariables, call) }, // more expressions, continue nesting
)
}
}
}
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
fun IrBuilderWithScope.irDiagram( fun IrBuilderWithScope.irDiagramString(
file: IrFile, file: IrFile,
fileSource: String, fileSource: String,
prefix: IrExpression? = null, prefix: IrExpression? = null,