From bf1067a9fd016b23b691c9f1e4fa93b9f3129222 Mon Sep 17 00:00:00 2001 From: Brian Norman Date: Sat, 3 Apr 2021 12:29:39 -0500 Subject: [PATCH 1/6] Prepare for diagramming of generic expressions --- .../bnorm/power/PowerAssertCallTransformer.kt | 68 +++++--- .../com/bnorm/power/PowerAssertGenerator.kt | 104 ------------- .../bnorm/power/diagram/DiagramGenerator.kt | 147 ++++++++++++++++++ .../ExpressionTree.kt} | 78 ++++------ .../IrDiagram.kt} | 40 +++-- .../power/diagram/IrTemporaryVariable.kt | 33 ++++ 6 files changed, 274 insertions(+), 196 deletions(-) delete mode 100644 kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertGenerator.kt create mode 100644 kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt rename kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/{AssertTree.kt => diagram/ExpressionTree.kt} (73%) rename kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/{IrStackVariable.kt => diagram/IrDiagram.kt} (91%) create mode 100644 kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrTemporaryVariable.kt diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt index f133d38aa95..cbfecc8f280 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt @@ -16,15 +16,23 @@ package com.bnorm.power +import com.bnorm.power.diagram.IrTemporaryVariable +import com.bnorm.power.diagram.DiagramGenerator +import com.bnorm.power.diagram.buildTree +import com.bnorm.power.diagram.info +import com.bnorm.power.diagram.irDiagram +import com.bnorm.power.diagram.substring import com.bnorm.power.internal.ReturnableBlockTransformer -import java.io.File -import org.jetbrains.kotlin.backend.common.* +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.ir.asSimpleLambda import org.jetbrains.kotlin.backend.common.ir.inline import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder import org.jetbrains.kotlin.backend.common.lower.at -import org.jetbrains.kotlin.cli.common.messages.* +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope @@ -39,16 +47,30 @@ import org.jetbrains.kotlin.ir.builders.parent import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.path -import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin +import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl -import org.jetbrains.kotlin.ir.types.* -import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.types.IrSimpleType +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.IrTypeArgument +import org.jetbrains.kotlin.ir.types.IrTypeProjection +import org.jetbrains.kotlin.ir.types.getClass +import org.jetbrains.kotlin.ir.types.isBoolean +import org.jetbrains.kotlin.ir.types.isSubtypeOf +import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols +import org.jetbrains.kotlin.ir.util.functions +import org.jetbrains.kotlin.ir.util.isFunctionOrKFunction +import org.jetbrains.kotlin.ir.util.kotlinFqName import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.util.OperatorNameConventions +import java.io.File fun FileLoweringPass.runOnFileInOrder(irFile: IrFile) { irFile.acceptVoid(object : IrElementVisitorVoid { @@ -98,8 +120,7 @@ class PowerAssertCallTransformer( val messageArgument = if (function.valueParameters.size == 2) expression.getValueArgument(1) else null // If the tree does not contain any children, the expression is not transformable - val tree = buildAssertTree(assertionArgument) - val root = tree.children.singleOrNull() ?: run { + val root = buildTree(assertionArgument) ?: run { messageCollector.info(expression, "Expression is constant and will not be power-assert transformed") return super.visitCall(expression) } @@ -108,8 +129,8 @@ class PowerAssertCallTransformer( DeclarationIrBuilder(context, symbol).run { at(expression) - val generator = object : PowerAssertGenerator() { - override fun IrBuilderWithScope.buildAssertThrow(subStack: List): IrExpression { + val generator = object : DiagramGenerator() { + override fun IrBuilderWithScope.buildAssertThrow(variables: List): IrExpression { val lambda = messageArgument?.asSimpleLambda() val title = when { @@ -124,20 +145,24 @@ class PowerAssertCallTransformer( else -> irString("Assertion failed") } - return delegate.buildCall(this, expression, buildMessage(file, fileSource, title.deepCopyWithSymbols(parent), expression, subStack)) + val prefix = title.deepCopyWithSymbols(parent) + val diagram = irDiagram(file, fileSource, prefix, expression, variables) + return delegate.buildCall(this, expression, irFalse(), diagram) } } -// println(expression.dump()) -// println(tree.dump()) +// println(root.dump()) return generator.buildAssert(this, root) +// .also { println(expression.dump()) } // .also { println(it.dump()) } +// .also { println(expression.dumpKotlinLike()) } +// .also { println(it.dumpKotlinLike()) } } } private interface FunctionDelegate { - fun buildCall(builder: IrBuilderWithScope, original: IrCall, message: IrExpression): IrExpression + fun buildCall(builder: IrBuilderWithScope, original: IrCall, argument: IrExpression, message: IrExpression): IrExpression } private fun findDelegate(fqName: FqName): FunctionDelegate? { @@ -148,25 +173,26 @@ class PowerAssertCallTransformer( if (parameters.size != 2) return@mapNotNull null if (!parameters[0].type.isBoolean()) return@mapNotNull null + val messageParameter = parameters.last() return@mapNotNull when { - isStringSupertype(parameters[1].type) -> { + isStringSupertype(messageParameter.type) -> { object : FunctionDelegate { - override fun buildCall(builder: IrBuilderWithScope, original: IrCall, message: IrExpression): IrExpression = with(builder) { + override fun buildCall(builder: IrBuilderWithScope, original: IrCall, argument: IrExpression, message: IrExpression): IrExpression = with(builder) { irCall(overload, type = overload.owner.returnType).apply { dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent) extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent) for (i in 0 until original.typeArgumentsCount) { putTypeArgument(i, original.getTypeArgument(i)) } - putValueArgument(0, irFalse()) + putValueArgument(0, argument) putValueArgument(1, message) } } } } - isStringFunction(parameters[1].type) -> { + isStringFunction(messageParameter.type) -> { object : FunctionDelegate { - override fun buildCall(builder: IrBuilderWithScope, original: IrCall, message: IrExpression): IrExpression = with(builder) { + override fun buildCall(builder: IrBuilderWithScope, original: IrCall, argument: IrExpression, message: IrExpression): IrExpression = with(builder) { val scope = this val lambda = builder.context.irFactory.buildFun { name = Name.special("") @@ -180,14 +206,14 @@ class PowerAssertCallTransformer( } parent = scope.parent } - val expression = IrFunctionExpressionImpl(original.startOffset, original.endOffset, parameters[1].type, lambda, IrStatementOrigin.LAMBDA) + val expression = IrFunctionExpressionImpl(original.startOffset, original.endOffset, messageParameter.type, lambda, IrStatementOrigin.LAMBDA) irCall(overload, type = overload.owner.returnType).apply { dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent) extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent) for (i in 0 until original.typeArgumentsCount) { putTypeArgument(i, original.getTypeArgument(i)) } - putValueArgument(0, irFalse()) + putValueArgument(0, argument) putValueArgument(1, expression) } } diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertGenerator.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertGenerator.kt deleted file mode 100644 index cf94436bd66..00000000000 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertGenerator.kt +++ /dev/null @@ -1,104 +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 - -import org.jetbrains.kotlin.backend.common.lower.irIfThen -import org.jetbrains.kotlin.backend.common.lower.irNot -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.irGet -import org.jetbrains.kotlin.ir.builders.irTemporary -import org.jetbrains.kotlin.ir.builders.parent -import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.expressions.IrWhen -import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols -import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid - -abstract class PowerAssertGenerator { - abstract fun IrBuilderWithScope.buildAssertThrow(subStack: List): IrExpression - - fun buildAssert( - builder: IrBuilderWithScope, - root: Node - ): IrExpression { - return builder.irBlock { - buildAssert(root, mutableListOf()) { subStack -> - buildAssertThrow(subStack) - } - } - } - - private fun IrStatementsBuilder<*>.buildAssert( - node: Node, - stack: MutableList, - thenPart: IrStatementsBuilder<*>.(stack: MutableList) -> IrExpression - ) { - fun IrStatementsBuilder<*>.nest(children: List, index: Int, stack: MutableList) { - val child = children[index] - buildAssert(child, stack) { subStack -> - if (index + 1 == children.size) buildAssertThrow(subStack) - else irBlock { nest(children, index + 1, subStack) } - } - } - - when (node) { - is ExpressionNode -> { - +irIfNotThan(stack, node, thenPart) - } - is AndNode -> { - for (child in node.children) { - buildAssert(child, stack, thenPart) - } - } - is OrNode -> { - nest(node.children, 0, stack) - } - } - } - - private inline fun IrStatementsBuilder<*>.irIfNotThan( - stack: MutableList, - node: ExpressionNode, - thenPart: IrStatementsBuilder<*>.(subStack: MutableList) -> IrExpression - ): IrWhen { - val expressions = node.getExpressionsCopy(this.parent) - val stackTransformer = StackBuilder(this, stack, expressions) - val transformed = expressions.first().transform(stackTransformer, null) - return irIfThen(irNot(transformed), thenPart(stack.toMutableList())) - } - - class StackBuilder( - private val builder: IrStatementsBuilder<*>, - private val stack: MutableList, - private val transform: List - ) : IrElementTransformerVoid() { - - override fun visitExpression(expression: IrExpression): IrExpression { - return if (expression in transform) { - with(builder) { - val copy = expression.deepCopyWithSymbols(scope.getLocalDeclarationParent()) - val variable = irTemporary(super.visitExpression(expression)) - stack.add(IrStackVariable(variable, copy)) - irGet(variable) - } - } else { - super.visitExpression(expression) - } - } - } -} diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt new file mode 100644 index 00000000000..99334d20ab5 --- /dev/null +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt @@ -0,0 +1,147 @@ +/* + * 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.backend.common.lower.irIfThen +import org.jetbrains.kotlin.backend.common.lower.irNot +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.expressions.IrExpression +import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols + +abstract class DiagramGenerator { + abstract fun IrBuilderWithScope.buildAssertThrow(variables: List): IrExpression + + fun buildAssert( + builder: IrBuilderWithScope, + root: Node + ): IrExpression { + return builder.irBlock { + buildAssert(root, listOf()) { subStack -> + buildAssertThrow(subStack) + } + } + } + + private fun IrStatementsBuilder<*>.buildAssert( + node: Node, + variables: List, + thenPart: IrStatementsBuilder<*>.(List) -> IrExpression + ): List = when (node) { + is ExpressionNode -> add(node, variables, thenPart) + is AndNode -> chain(node, variables, thenPart) + is OrNode -> nest(node, 0, variables) + else -> TODO("Unknown node type=$node") + } + + /** + * TODO - this is how the following function should behave + * ``` + * val result = call(1 + 2 + 3) + * ``` + * Transformed to + * ``` + * val result = run { + * val tmp0 = 1 + 2 + * val tmp1 = tmp0 + 3 + * call(tmp1, ) + * } + * ``` + */ + private fun IrStatementsBuilder<*>.add( + node: ExpressionNode, + variables: List, + thenPart: IrStatementsBuilder<*>.(List) -> IrExpression + ): List { + 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) + + if (transformed.type == context.irBuiltIns.booleanType) { + // TODO irIfThenElse to support property nesting of ANDs & ORs + +irIfThen(irNot(transformed), thenPart(variables + transformer.variables)) + } else { + +thenPart(variables + transformer.variables) + } + + return transformer.variables + } + + /** + * TODO - this is how the following function should behave + * ``` + * val result = call(1 == 1 && 2 == 2) + * ``` + * Transformed to + * ``` + * val result = run { + * val tmp0 = 1 == 1 + * if (tmp0) { + * val tmp1 = 2 == 2 + * if (tmp1) call(true, ) + * else call(false, ) + * } + * else call(false, ) + * } + * ``` + */ + private fun IrStatementsBuilder<*>.chain( + node: AndNode, + variables: List, + thenPart: IrStatementsBuilder<*>.(List) -> IrExpression + ): List { + val newVariables = mutableListOf() + for (child in node.children) { + newVariables.addAll(buildAssert(child, variables + newVariables, thenPart)) + } + return newVariables + } + + /** + * TODO - this is how the following function should behave + * ``` + * val result = call(1 == 1 || 2 == 2) + * ``` + * Transformed to + * ``` + * val result = run { + * val tmp0 = 1 == 1 + * if (tmp0) call(true, ) + * else { + * val tmp1 = 2 == 2 + * if (tmp1) call(true, ) + * else call(false, ) + * } + * } + * ``` + */ + private fun IrStatementsBuilder<*>.nest( + node: OrNode, + index: Int, + variables: List + ): List { + val children = node.children + val child = children[index] + buildAssert(child, variables) { newVariables -> + if (index + 1 == children.size) buildAssertThrow(newVariables) + else irBlock { nest(node, index + 1, newVariables) } + } + return emptyList() + } +} diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/AssertTree.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/ExpressionTree.kt similarity index 73% rename from kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/AssertTree.kt rename to kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/ExpressionTree.kt index e4672e41f00..11efe60fb87 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/AssertTree.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/ExpressionTree.kt @@ -14,25 +14,31 @@ * limitations under the License. */ -package com.bnorm.power +package com.bnorm.power.diagram import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.IrContainerExpression import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.expressions.IrWhen -import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols +import org.jetbrains.kotlin.ir.util.dumpKotlinLike import org.jetbrains.kotlin.ir.visitors.IrElementVisitor -sealed class Node { - abstract val parent: Node? - val mutableChildren: MutableList = mutableListOf() - val children: List get() = mutableChildren +abstract class Node { + private val _children = mutableListOf() + val children: List get() = _children - protected fun dump(builder: StringBuilder, indent: Int) { + fun addChild(node: Node) { + _children.add(node) + } + + fun dump(): String = buildString { + dump(this, 0) + } + + private fun dump(builder: StringBuilder, indent: Int) { builder.append(" ".repeat(indent)).append(this).appendLine() for (child in children) { child.dump(builder, indent + 1) @@ -40,56 +46,30 @@ sealed class Node { } } -class AndNode(override val parent: Node) : Node() { - init { - parent.mutableChildren.add(this) - } - +class AndNode : Node() { override fun toString() = "AndNode" } -class OrNode(override val parent: Node) : Node() { - init { - parent.mutableChildren.add(this) - } - +class OrNode : Node() { override fun toString() = "OrNode" } -class ExpressionNode( - override val parent: Node -) : Node() { - init { - parent.mutableChildren.add(this) - } - - private val _expressions: MutableList = mutableListOf() +class ExpressionNode : Node() { + private val _expressions = mutableListOf() + val expressions: List = _expressions fun add(expression: IrExpression) { _expressions.add(expression) } - fun getExpressionsCopy(initialParent: IrDeclarationParent?): List { - // Return a copy of all the expression by creating a deep copy of the head - // expression and running back through the assertion tree builder - val headCopy = _expressions.first().deepCopyWithSymbols(initialParent) - return (buildAssertTree(headCopy).children.single() as ExpressionNode)._expressions - } - - override fun toString() = "ExpressionNode($_expressions)" + override fun toString() = "ExpressionNode(${_expressions.map { it.dumpKotlinLike() }})" } -class RootNode : Node() { - override val parent: Node? = null - - fun dump(): String = buildString { - dump(this, 0) +fun buildTree(expression: IrExpression): Node? { + class RootNode : Node() { + override fun toString() = "RootNode" } - override fun toString() = "RootNode" -} - -fun buildAssertTree(expression: IrExpression): RootNode { val tree = RootNode() expression.accept(object : IrElementVisitor { val INCREMENT_DECREMENT_OPERATORS = setOf( @@ -104,14 +84,14 @@ fun buildAssertTree(expression: IrExpression): RootNode { } override fun visitExpression(expression: IrExpression, data: Node) { - val node = data as? ExpressionNode ?: ExpressionNode(data) + val node = data as? ExpressionNode ?: ExpressionNode().also { data.addChild(it) } node.add(expression) expression.acceptChildren(this, node) } override fun visitContainerExpression(expression: IrContainerExpression, data: Node) { if (expression.origin in INCREMENT_DECREMENT_OPERATORS) { - val node = data as? ExpressionNode ?: ExpressionNode(data) + val node = data as? ExpressionNode ?: ExpressionNode().also { data.addChild(it) } node.add(expression) return // Skip the internals of increment/decrement operations } @@ -136,7 +116,7 @@ fun buildAssertTree(expression: IrExpression): RootNode { when (expression.origin) { IrStatementOrigin.ANDAND -> { // flatten `&&` expressions to be at the same level - val node = data as? AndNode ?: AndNode(data) + val node = data as? AndNode ?: AndNode().also { data.addChild(it) } require(expression.branches.size == 2) val thenBranch = expression.branches[0] @@ -157,7 +137,7 @@ fun buildAssertTree(expression: IrExpression): RootNode { } IrStatementOrigin.OROR -> { // flatten `||` expressions to be at the same level - val node = data as? OrNode ?: OrNode(data) + val node = data as? OrNode ?: OrNode().also { data.addChild(it) } require(expression.branches.size == 2) val thenBranchCondition = expression.branches[0].condition @@ -182,11 +162,11 @@ fun buildAssertTree(expression: IrExpression): RootNode { else -> { // Add as basic expression and terminate // TODO this has to be broken and not work in all cases... - ExpressionNode(data) + ExpressionNode().also { data.addChild(it) } } } } }, tree) - return tree + return tree.children.singleOrNull() } diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/IrStackVariable.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt similarity index 91% rename from kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/IrStackVariable.kt rename to kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt index cfe7b25ca63..6dbef3015d9 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/IrStackVariable.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.bnorm.power +package com.bnorm.power.diagram +import com.bnorm.power.irString import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.SourceRangeInfo import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope @@ -31,38 +32,26 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin -data class IrStackVariable( - val temporary: IrVariable, - val original: IrExpression -) - -data class ValueDisplay( - val value: IrVariable, - val indent: Int, - val row: Int, - val source: String -) - -fun IrBuilderWithScope.buildMessage( +fun IrBuilderWithScope.irDiagram( file: IrFile, fileSource: String, - title: IrExpression, - expression: IrExpression, - stack: List + prefix: IrExpression? = null, + original: IrExpression, + variables: List ): IrExpression { - val originalInfo = file.info(expression) + val originalInfo = file.info(original) val callIndent = originalInfo.startColumnNumber - val stackValues = stack.map { it.toValueDisplay(fileSource, callIndent, file, originalInfo) } + val stackValues = variables.map { it.toValueDisplay(fileSource, callIndent, file, originalInfo) } val valuesByRow = stackValues.groupBy { it.row } - val rows = fileSource.substring(expression) + val rows = fileSource.substring(original) .replace("\n" + " ".repeat(callIndent), "\n") // Remove additional indentation .split("\n") return irConcat().apply { - addArgument(title) + if (prefix != null) addArgument(prefix) for ((row, rowSource) in rows.withIndex()) { val rowValues = valuesByRow[row]?.let { values -> values.sortedBy { it.indent } } ?: emptyList() @@ -102,7 +91,14 @@ fun IrBuilderWithScope.buildMessage( } } -private fun IrStackVariable.toValueDisplay( +private data class ValueDisplay( + val value: IrVariable, + val indent: Int, + val row: Int, + val source: String +) + +private fun IrTemporaryVariable.toValueDisplay( fileSource: String, callIndent: Int, file: IrFile, diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrTemporaryVariable.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrTemporaryVariable.kt new file mode 100644 index 00000000000..84218e17816 --- /dev/null +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrTemporaryVariable.kt @@ -0,0 +1,33 @@ +package com.bnorm.power.diagram + +import org.jetbrains.kotlin.ir.builders.IrStatementsBuilder +import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.builders.irTemporary +import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid + +data class IrTemporaryVariable( + val temporary: IrVariable, + val original: IrExpression +) + +class IrTemporaryExtractionTransformer( + private val builder: IrStatementsBuilder<*>, + private val transform: Set +) : IrElementTransformerVoid() { + private val _variables = mutableListOf() + val variables: List = _variables + + override fun visitExpression(expression: IrExpression): IrExpression { + return if (expression in transform) { + val copy = expression.deepCopyWithSymbols(builder.scope.getLocalDeclarationParent()) + val variable = builder.irTemporary(super.visitExpression(expression)) + _variables.add(IrTemporaryVariable(variable, copy)) + builder.irGet(variable) + } else { + super.visitExpression(expression) + } + } +} From f8270b4cc997fff8850a1873d68f08ed19e8ddfd Mon Sep 17 00:00:00 2001 From: Brian Norman Date: Sat, 3 Apr 2021 17:20:52 -0500 Subject: [PATCH 2/6] Allow functions with a single argument of any type --- .../bnorm/power/PowerAssertCallTransformer.kt | 47 +++++---- .../bnorm/power/diagram/DiagramGenerator.kt | 98 ++++++++++--------- .../com/bnorm/power/diagram/IrDiagram.kt | 2 +- .../com/bnorm/power/AssertBooleanTest.kt | 45 +++++++++ .../com/bnorm/power/DebugFunctionTest.kt | 80 +++++++++++++++ sample/build.gradle.kts | 3 +- .../kotlin/com/bnorm/power/Debug.kt | 10 ++ .../kotlin/com/bnorm/power/PowerAssertTest.kt | 37 +++++++ 8 files changed, 257 insertions(+), 65 deletions(-) create mode 100644 kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/AssertBooleanTest.kt create mode 100644 kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt create mode 100644 sample/src/commonMain/kotlin/com/bnorm/power/Debug.kt diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt index cbfecc8f280..e6d2aca4d7c 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt @@ -35,17 +35,18 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.backend.js.utils.asString import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope import org.jetbrains.kotlin.ir.builders.declarations.buildFun import org.jetbrains.kotlin.ir.builders.irBlockBody import org.jetbrains.kotlin.ir.builders.irCall import org.jetbrains.kotlin.ir.builders.irCallOp -import org.jetbrains.kotlin.ir.builders.irFalse import org.jetbrains.kotlin.ir.builders.irReturn import org.jetbrains.kotlin.ir.builders.irString import org.jetbrains.kotlin.ir.builders.parent import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.path import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrConst @@ -53,10 +54,12 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.IrSimpleType import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrTypeArgument import org.jetbrains.kotlin.ir.types.IrTypeProjection +import org.jetbrains.kotlin.ir.types.classifierOrNull import org.jetbrains.kotlin.ir.types.getClass import org.jetbrains.kotlin.ir.types.isBoolean import org.jetbrains.kotlin.ir.types.isSubtypeOf @@ -99,23 +102,25 @@ class PowerAssertCallTransformer( .replace("\r\n", "\n") // https://youtrack.jetbrains.com/issue/KT-41888 irFile.transformChildrenVoid() +// println(irFile.dumpKotlinLike()) } override fun visitCall(expression: IrCall): IrExpression { - val fqName = expression.symbol.owner.kotlinFqName - if (functions.none { fqName == it }) + val function = expression.symbol.owner + val fqName = function.kotlinFqName + if (function.valueParameters.isEmpty() || functions.none { fqName == it }) return super.visitCall(expression) // Find a valid delegate function or do not translate - val delegate = findDelegate(fqName) ?: run { + val delegate = findDelegate(function) ?: run { + val valueType = function.valueParameters[0].type.asString() messageCollector.warn( expression, - "Unable to find overload for function $fqName callable as $fqName(Boolean, String) or $fqName(Boolean, () -> String) for power-assert transformation" + "Unable to find overload for function $fqName callable as $fqName($valueType, String) or $fqName($valueType, () -> String) for power-assert transformation" ) return super.visitCall(expression) } - val function = expression.symbol.owner val assertionArgument = expression.getValueArgument(0)!! val messageArgument = if (function.valueParameters.size == 2) expression.getValueArgument(1) else null @@ -130,7 +135,7 @@ class PowerAssertCallTransformer( at(expression) val generator = object : DiagramGenerator() { - override fun IrBuilderWithScope.buildAssertThrow(variables: List): IrExpression { + override fun IrBuilderWithScope.buildCall(argument: IrExpression, variables: List): IrExpression { val lambda = messageArgument?.asSimpleLambda() val title = when { @@ -142,18 +147,18 @@ class PowerAssertCallTransformer( irCallOp(invoke.symbol, invoke.returnType, messageArgument) } // TODO what should the default message be? - else -> irString("Assertion failed") + assertionArgument.type.isBoolean() -> irString("Assertion failed") + else -> null } - val prefix = title.deepCopyWithSymbols(parent) + val prefix = title?.deepCopyWithSymbols(parent) val diagram = irDiagram(file, fileSource, prefix, expression, variables) - return delegate.buildCall(this, expression, irFalse(), diagram) + return delegate.buildCall(this, expression, argument, diagram) } } // println(root.dump()) - - return generator.buildAssert(this, root) + return generator.buildExpression(this, root) // .also { println(expression.dump()) } // .also { println(it.dump()) } // .also { println(expression.dumpKotlinLike()) } @@ -165,20 +170,22 @@ class PowerAssertCallTransformer( fun buildCall(builder: IrBuilderWithScope, original: IrCall, argument: IrExpression, message: IrExpression): IrExpression } - private fun findDelegate(fqName: FqName): FunctionDelegate? { - return context.referenceFunctions(fqName) + private fun findDelegate(function: IrFunction): FunctionDelegate? { + if (function.valueParameters.isEmpty()) return null + + return context.referenceFunctions(function.kotlinFqName) .mapNotNull { overload -> // TODO allow other signatures than (Boolean, String) and (Boolean, () -> String) val parameters = overload.owner.valueParameters if (parameters.size != 2) return@mapNotNull null - if (!parameters[0].type.isBoolean()) return@mapNotNull null + if (!function.valueParameters[0].type.isAssignableTo(parameters[0].type)) return@mapNotNull null val messageParameter = parameters.last() return@mapNotNull when { isStringSupertype(messageParameter.type) -> { object : FunctionDelegate { override fun buildCall(builder: IrBuilderWithScope, original: IrCall, argument: IrExpression, message: IrExpression): IrExpression = with(builder) { - irCall(overload, type = overload.owner.returnType).apply { + irCall(overload, type = original.type).apply { dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent) extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent) for (i in 0 until original.typeArgumentsCount) { @@ -207,7 +214,7 @@ class PowerAssertCallTransformer( parent = scope.parent } val expression = IrFunctionExpressionImpl(original.startOffset, original.endOffset, messageParameter.type, lambda, IrStatementOrigin.LAMBDA) - irCall(overload, type = overload.owner.returnType).apply { + irCall(overload, type = original.type).apply { dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent) extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent) for (i in 0 until original.typeArgumentsCount) { @@ -236,6 +243,12 @@ class PowerAssertCallTransformer( private fun isStringSupertype(type: IrType): Boolean = context.irBuiltIns.stringType.isSubtypeOf(type, context.irBuiltIns) + private fun IrType.isAssignableTo(type: IrType): Boolean = + isSubtypeOf(type, context.irBuiltIns) || + ((type.classifierOrNull as? IrTypeParameterSymbol)?.owner?.superTypes?.all { + isSubtypeOf(it, context.irBuiltIns) + } ?: false) + private fun MessageCollector.info(expression: IrElement, message: String) { report(expression, CompilerMessageSeverity.INFO, message) } diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt index 99334d20ab5..f2ddcdda3da 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt @@ -16,45 +16,50 @@ package com.bnorm.power.diagram -import org.jetbrains.kotlin.backend.common.lower.irIfThen -import org.jetbrains.kotlin.backend.common.lower.irNot 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.buildAssertThrow(variables: List): IrExpression + abstract fun IrBuilderWithScope.buildCall( + argument: IrExpression, + variables: List + ): IrExpression - fun buildAssert( + fun buildExpression( builder: IrBuilderWithScope, root: Node ): IrExpression { return builder.irBlock { - buildAssert(root, listOf()) { subStack -> - buildAssertThrow(subStack) + buildExpression(root, listOf()) { argument, subStack -> + buildCall(argument, subStack) } } } - private fun IrStatementsBuilder<*>.buildAssert( + private fun IrStatementsBuilder<*>.buildExpression( node: Node, variables: List, - thenPart: IrStatementsBuilder<*>.(List) -> IrExpression - ): List = when (node) { - is ExpressionNode -> add(node, variables, thenPart) - is AndNode -> chain(node, variables, thenPart) - is OrNode -> nest(node, 0, variables) - else -> TODO("Unknown node type=$node") + call: IrStatementsBuilder<*>.(IrExpression, List) -> 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") + } } /** - * TODO - this is how the following function should behave * ``` * val result = call(1 + 2 + 3) * ``` - * Transformed to + * Transforms to * ``` * val result = run { * val tmp0 = 1 + 2 @@ -66,67 +71,63 @@ abstract class DiagramGenerator { private fun IrStatementsBuilder<*>.add( node: ExpressionNode, variables: List, - thenPart: IrStatementsBuilder<*>.(List) -> IrExpression - ): List { + call: IrStatementsBuilder<*>.(IrExpression, List) -> 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) - if (transformed.type == context.irBuiltIns.booleanType) { - // TODO irIfThenElse to support property nesting of ANDs & ORs - +irIfThen(irNot(transformed), thenPart(variables + transformer.variables)) - } else { - +thenPart(variables + transformer.variables) - } - - return transformer.variables + +call(transformed, variables + transformer.variables) } /** - * TODO - this is how the following function should behave * ``` * val result = call(1 == 1 && 2 == 2) * ``` - * Transformed to + * Transforms to * ``` * val result = run { * val tmp0 = 1 == 1 * if (tmp0) { * val tmp1 = 2 == 2 - * if (tmp1) call(true, ) - * else call(false, ) + * call(tmp1, ) * } * else call(false, ) * } * ``` */ - private fun IrStatementsBuilder<*>.chain( + private fun IrStatementsBuilder<*>.nest( node: AndNode, + index: Int, variables: List, - thenPart: IrStatementsBuilder<*>.(List) -> IrExpression - ): List { - val newVariables = mutableListOf() - for (child in node.children) { - newVariables.addAll(buildAssert(child, variables + newVariables, thenPart)) + call: IrStatementsBuilder<*>.(IrExpression, List) -> 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 + ) } - return newVariables } /** - * TODO - this is how the following function should behave * ``` * val result = call(1 == 1 || 2 == 2) * ``` - * Transformed to + * Transforms to * ``` * val result = run { * val tmp0 = 1 == 1 * if (tmp0) call(true, ) * else { * val tmp1 = 2 == 2 - * if (tmp1) call(true, ) - * else call(false, ) + * call(tmp1, ) * } * } * ``` @@ -134,14 +135,19 @@ abstract class DiagramGenerator { private fun IrStatementsBuilder<*>.nest( node: OrNode, index: Int, - variables: List - ): List { + variables: List, + call: IrStatementsBuilder<*>.(IrExpression, List) -> IrExpression + ) { val children = node.children val child = children[index] - buildAssert(child, variables) { newVariables -> - if (index + 1 == children.size) buildAssertThrow(newVariables) - else irBlock { nest(node, index + 1, newVariables) } + 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 + ) } - return emptyList() } } diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt index 6dbef3015d9..0e1dacfa322 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt @@ -59,7 +59,7 @@ fun IrBuilderWithScope.irDiagram( addArgument( irString { - appendLine() + if (row != 0 || prefix != null) appendLine() append(rowSource) if (indentations.isNotEmpty()) { appendLine() diff --git a/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/AssertBooleanTest.kt b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/AssertBooleanTest.kt new file mode 100644 index 00000000000..786287f45d0 --- /dev/null +++ b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/AssertBooleanTest.kt @@ -0,0 +1,45 @@ +package com.bnorm.power + +import org.jetbrains.kotlin.name.FqName +import org.junit.Test +import kotlin.test.assertEquals + +class AssertBooleanTest { + @Test + fun `test assertTrue transformation`() { + assertMessage( + """ +import kotlin.test.assertTrue + +fun main() { + assertTrue(1 != 1) +}""", + """ +Assertion failed +assertTrue(1 != 1) + | + false +""".trimIndent(), + PowerAssertComponentRegistrar(setOf(FqName("kotlin.test.assertTrue"))) + ) + } + + @Test + fun `test assertFalse transformation`() { + assertMessage( + """ +import kotlin.test.assertFalse + +fun main() { + assertFalse(1 == 1) +}""", + """ +Assertion failed +assertFalse(1 == 1) + | + true +""".trimIndent(), + PowerAssertComponentRegistrar(setOf(FqName("kotlin.test.assertFalse"))) + ) + } +} diff --git a/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt new file mode 100644 index 00000000000..1470322fe03 --- /dev/null +++ b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt @@ -0,0 +1,80 @@ +package com.bnorm.power + +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +import org.jetbrains.kotlin.name.FqName +import org.junit.Test +import java.io.ByteArrayOutputStream +import java.io.PrintStream +import java.lang.reflect.InvocationTargetException +import kotlin.test.assertEquals + +class DebugFunctionTest { + @Test + fun `debug function transformation`() { + val actual = executeMainDebug(""" + dbg(1 + 2 + 3) + """.trimIndent()) + assertEquals( + """ + dbg(1 + 2 + 3) + | | + | 6 + 3 + """.trimIndent(), + actual.trim() + ) + } + @Test + fun `debug function transformation with message`() { + val actual = executeMainDebug(""" + dbg(1 + 2 + 3, "Message:") + """.trimIndent()) + assertEquals( + """ + Message: + dbg(1 + 2 + 3, "Message:") + | | + | 6 + 3 + """.trimIndent(), + actual.trim() + ) + } +} + +fun executeMainDebug(mainBody: String): String { + val file = SourceFile.kotlin( + name = "main.kt", + contents = """ +fun dbg(value: T): T = value + +fun dbg(value: T, msg: String): T { + println(msg) + return value +} + +fun main() { + $mainBody +} +""", + trimIndent = false + ) + + val result = compile(listOf(file), PowerAssertComponentRegistrar(setOf(FqName("dbg")))) + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + + val kClazz = result.classLoader.loadClass("MainKt") + val main = kClazz.declaredMethods.single { it.name == "main" && it.parameterCount == 0 } + val prevOut = System.out + try { + val out = ByteArrayOutputStream() + System.setOut(PrintStream(out)) + main.invoke(null) + return out.toString(Charsets.UTF_8) + } catch (t: InvocationTargetException) { + throw t.cause!! + } finally { + System.setOut(prevOut) + } +} diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index f76fdb01494..3374efc02a5 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -62,6 +62,7 @@ configure { "kotlin.test.assertTrue", "kotlin.require", "com.bnorm.power.AssertScope.assert", - "com.bnorm.power.assert" + "com.bnorm.power.assert", + "com.bnorm.power.dbg" ) } diff --git a/sample/src/commonMain/kotlin/com/bnorm/power/Debug.kt b/sample/src/commonMain/kotlin/com/bnorm/power/Debug.kt new file mode 100644 index 00000000000..0e0c85d320e --- /dev/null +++ b/sample/src/commonMain/kotlin/com/bnorm/power/Debug.kt @@ -0,0 +1,10 @@ +package com.bnorm.power + +val debugLog = StringBuilder() + +fun dbg(value: T): T = value + +fun dbg(value: T, msg: String): T { + debugLog.appendLine(msg) + return value +} diff --git a/sample/src/commonTest/kotlin/com/bnorm/power/PowerAssertTest.kt b/sample/src/commonTest/kotlin/com/bnorm/power/PowerAssertTest.kt index 0d5d607acb5..8e374f9396e 100644 --- a/sample/src/commonTest/kotlin/com/bnorm/power/PowerAssertTest.kt +++ b/sample/src/commonTest/kotlin/com/bnorm/power/PowerAssertTest.kt @@ -16,6 +16,7 @@ package com.bnorm.power +import kotlin.test.AfterTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -23,6 +24,11 @@ import kotlin.test.assertTrue class PowerAssertTest { + @AfterTest + fun cleanup() { + debugLog.clear() + } + @Test fun assertTrue() { val error = assertFailsWith { assertTrue(Person.UNKNOWN.size == 1) } @@ -111,4 +117,35 @@ class PowerAssertTest { """.trimIndent(), ) } + + @Test + fun dbgTest() { + val name = "Jane" + val greeting = dbg("Hello, $name") + assert(greeting == "Hello, Jane") + assertEquals( + actual = debugLog.toString().trim(), + expected = """ + dbg("Hello, ${"$"}name") + | | + | Jane + Hello, Jane + """.trimIndent()) + } + + @Test + fun dbgMessageTest() { + val name = "Jane" + val greeting = dbg("Hello, $name", "Greeting:") + assert(greeting == "Hello, Jane") + assertEquals( + actual = debugLog.toString().trim(), + expected = """ + Greeting: + dbg("Hello, ${"$"}name", "Greeting:") + | | + | Jane + Hello, Jane + """.trimIndent()) + } } From 8ace9f05f59ef29fcdb17d419db9d30723a1017c Mon Sep 17 00:00:00 2001 From: Brian Norman Date: Sat, 3 Apr 2021 17:45:29 -0500 Subject: [PATCH 3/6] Clean up temporary variable nesting logic --- .../bnorm/power/PowerAssertCallTransformer.kt | 81 ++++++---- .../com/bnorm/power/diagram/DiagramBuilder.kt | 142 ++++++++++++++++ .../bnorm/power/diagram/DiagramGenerator.kt | 153 ------------------ .../com/bnorm/power/diagram/IrDiagram.kt | 2 +- 4 files changed, 191 insertions(+), 187 deletions(-) create mode 100644 kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramBuilder.kt delete mode 100644 kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt index e6d2aca4d7c..a8bfc67fc48 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt @@ -16,11 +16,10 @@ package com.bnorm.power -import com.bnorm.power.diagram.IrTemporaryVariable -import com.bnorm.power.diagram.DiagramGenerator +import com.bnorm.power.diagram.buildDiagramNesting import com.bnorm.power.diagram.buildTree 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.internal.ReturnableBlockTransformer import org.jetbrains.kotlin.backend.common.FileLoweringPass @@ -121,6 +120,7 @@ class PowerAssertCallTransformer( return super.visitCall(expression) } + // TODO - support more arguments by currying? val assertionArgument = expression.getValueArgument(0)!! 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") return super.visitCall(expression) } +// println(root.dump()) val symbol = currentScope!!.scope.scopeOwnerSymbol - DeclarationIrBuilder(context, symbol).run { - at(expression) - - val generator = object : DiagramGenerator() { - override fun IrBuilderWithScope.buildCall(argument: IrExpression, variables: List): IrExpression { - - val lambda = messageArgument?.asSimpleLambda() - val title = when { - messageArgument is IrConst<*> -> messageArgument - messageArgument is IrStringConcatenation -> messageArgument - lambda != null -> lambda.deepCopyWithSymbols(parent).inline(parent).transform(ReturnableBlockTransformer(context, symbol), null) - messageArgument != null -> { - 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) + val builder = DeclarationIrBuilder(context, symbol, expression.startOffset, expression.endOffset) + return builder.buildDiagramNesting(root) { argument, variables -> + val lambda = messageArgument?.asSimpleLambda() + val title = when { + messageArgument is IrConst<*> -> messageArgument + messageArgument is IrStringConcatenation -> messageArgument + lambda != null -> lambda.deepCopyWithSymbols(parent).inline(parent) + .transform(ReturnableBlockTransformer(context, symbol), null) + messageArgument != null -> { + 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 } -// println(root.dump()) - return generator.buildExpression(this, root) + val prefix = title?.deepCopyWithSymbols(parent) + val diagram = irDiagramString(file, fileSource, prefix, expression, variables) + delegate.buildCall(this, expression, argument, diagram) + } // .also { println(expression.dump()) } // .also { println(it.dump()) } // .also { println(expression.dumpKotlinLike()) } // .also { println(it.dumpKotlinLike()) } - } } 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? { @@ -184,7 +183,12 @@ class PowerAssertCallTransformer( return@mapNotNull when { isStringSupertype(messageParameter.type) -> { 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 { dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent) extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent) @@ -199,7 +203,12 @@ class PowerAssertCallTransformer( } isStringFunction(messageParameter.type) -> { 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 lambda = builder.context.irFactory.buildFun { name = Name.special("") @@ -213,7 +222,13 @@ class PowerAssertCallTransformer( } 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 { dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent) extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent) diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramBuilder.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramBuilder.kt new file mode 100644 index 00000000000..9b6f0c757e0 --- /dev/null +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramBuilder.kt @@ -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) -> IrExpression +): IrExpression { + return buildExpression(root, listOf()) { argument, subStack -> + call(argument, subStack) + } +} + +private fun IrBuilderWithScope.buildExpression( + node: Node, + variables: List, + call: IrBuilderWithScope.(IrExpression, List) -> 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, ) + * } + * ``` + */ +private fun IrBuilderWithScope.add( + node: ExpressionNode, + variables: List, + call: IrBuilderWithScope.(IrExpression, List) -> 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, ) + * } + * else call(false, ) + * } + * ``` + */ +private fun IrBuilderWithScope.nest( + node: AndNode, + index: Int, + variables: List, + call: IrBuilderWithScope.(IrExpression, List) -> 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, ) + * else { + * val tmp1 = 2 == 2 + * call(tmp1, ) + * } + * } + * ``` + */ +private fun IrBuilderWithScope.nest( + node: OrNode, + index: Int, + variables: List, + call: IrBuilderWithScope.(IrExpression, List) -> 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 + ) + } +} diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt deleted file mode 100644 index f2ddcdda3da..00000000000 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/DiagramGenerator.kt +++ /dev/null @@ -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 - ): 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, - call: IrStatementsBuilder<*>.(IrExpression, List) -> 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, ) - * } - * ``` - */ - private fun IrStatementsBuilder<*>.add( - node: ExpressionNode, - variables: List, - call: IrStatementsBuilder<*>.(IrExpression, List) -> 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, ) - * } - * else call(false, ) - * } - * ``` - */ - private fun IrStatementsBuilder<*>.nest( - node: AndNode, - index: Int, - variables: List, - call: IrStatementsBuilder<*>.(IrExpression, List) -> 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, ) - * else { - * val tmp1 = 2 == 2 - * call(tmp1, ) - * } - * } - * ``` - */ - private fun IrStatementsBuilder<*>.nest( - node: OrNode, - index: Int, - variables: List, - call: IrStatementsBuilder<*>.(IrExpression, List) -> 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 - ) - } - } -} diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt index 0e1dacfa322..4fd29c33c16 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/diagram/IrDiagram.kt @@ -32,7 +32,7 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin -fun IrBuilderWithScope.irDiagram( +fun IrBuilderWithScope.irDiagramString( file: IrFile, fileSource: String, prefix: IrExpression? = null, From 98b80f35cab331fe60452901d17e8f5c81ac459b Mon Sep 17 00:00:00 2001 From: Brian Norman Date: Sat, 3 Apr 2021 17:49:38 -0500 Subject: [PATCH 4/6] Simplify initial power assert transformer call --- .../bnorm/power/PowerAssertCallTransformer.kt | 35 ++----------------- .../power/PowerAssertIrGenerationExtension.kt | 8 ++++- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt index a8bfc67fc48..f3a5ebdb8fb 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt @@ -22,13 +22,11 @@ import com.bnorm.power.diagram.info import com.bnorm.power.diagram.irDiagramString import com.bnorm.power.diagram.substring import com.bnorm.power.internal.ReturnableBlockTransformer -import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.ir.asSimpleLambda import org.jetbrains.kotlin.backend.common.ir.inline import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder -import org.jetbrains.kotlin.backend.common.lower.at import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageCollector @@ -66,44 +64,17 @@ import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols import org.jetbrains.kotlin.ir.util.functions import org.jetbrains.kotlin.ir.util.isFunctionOrKFunction import org.jetbrains.kotlin.ir.util.kotlinFqName -import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid -import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid -import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.util.OperatorNameConventions -import java.io.File - -fun FileLoweringPass.runOnFileInOrder(irFile: IrFile) { - irFile.acceptVoid(object : IrElementVisitorVoid { - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitFile(declaration: IrFile) { - lower(declaration) - super.visitFile(declaration) - } - }) -} class PowerAssertCallTransformer( + private val file: IrFile, + private val fileSource: String, private val context: IrPluginContext, private val messageCollector: MessageCollector, private val functions: Set -) : IrElementTransformerVoidWithContext(), FileLoweringPass { - private lateinit var file: IrFile - private lateinit var fileSource: String - - override fun lower(irFile: IrFile) { - file = irFile - fileSource = File(irFile.path).readText() - .replace("\r\n", "\n") // https://youtrack.jetbrains.com/issue/KT-41888 - - irFile.transformChildrenVoid() -// println(irFile.dumpKotlinLike()) - } - +) : IrElementTransformerVoidWithContext() { override fun visitCall(expression: IrCall): IrExpression { val function = expression.symbol.owner val fqName = function.kotlinFqName diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertIrGenerationExtension.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertIrGenerationExtension.kt index 3d05f8679e0..c25af95f2c4 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertIrGenerationExtension.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertIrGenerationExtension.kt @@ -20,7 +20,9 @@ import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.declarations.path import org.jetbrains.kotlin.name.FqName +import java.io.File class PowerAssertIrGenerationExtension( private val messageCollector: MessageCollector, @@ -28,7 +30,11 @@ class PowerAssertIrGenerationExtension( ) : IrGenerationExtension { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { for (file in moduleFragment.files) { - PowerAssertCallTransformer(pluginContext, messageCollector, functions).runOnFileInOrder(file) + val fileSource = File(file.path).readText() + .replace("\r\n", "\n") // https://youtrack.jetbrains.com/issue/KT-41888 + + PowerAssertCallTransformer(file, fileSource, pluginContext, messageCollector, functions) + .visitFile(file) } } } From ce119c9d92d3ec43bba7519b65c888b009bf9dd6 Mon Sep 17 00:00:00 2001 From: Brian Norman Date: Sat, 3 Apr 2021 17:51:57 -0500 Subject: [PATCH 5/6] Fix Java 8 compilation error --- .../src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt index 1470322fe03..34edaa0d940 100644 --- a/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt +++ b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt @@ -71,7 +71,7 @@ fun main() { val out = ByteArrayOutputStream() System.setOut(PrintStream(out)) main.invoke(null) - return out.toString(Charsets.UTF_8) + return out.toString("UTF-8") } catch (t: InvocationTargetException) { throw t.cause!! } finally { From 8d73179c8927bf4b78f4d35e9bd331d5355407e1 Mon Sep 17 00:00:00 2001 From: Brian Norman Date: Sun, 4 Apr 2021 17:58:05 -0500 Subject: [PATCH 6/6] Fix up formatting issues --- .../bnorm/power/PowerAssertCallTransformer.kt | 10 ++--- .../com/bnorm/power/AssertBooleanTest.kt | 41 +++++++++---------- .../com/bnorm/power/DebugFunctionTest.kt | 12 ++++-- .../kotlin/com/bnorm/power/PowerAssertTest.kt | 6 ++- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt index f3a5ebdb8fb..e9367f5c037 100644 --- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt +++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt @@ -229,11 +229,11 @@ class PowerAssertCallTransformer( private fun isStringSupertype(type: IrType): Boolean = context.irBuiltIns.stringType.isSubtypeOf(type, context.irBuiltIns) - private fun IrType.isAssignableTo(type: IrType): Boolean = - isSubtypeOf(type, context.irBuiltIns) || - ((type.classifierOrNull as? IrTypeParameterSymbol)?.owner?.superTypes?.all { - isSubtypeOf(it, context.irBuiltIns) - } ?: false) + private fun IrType.isAssignableTo(type: IrType): Boolean { + if (isSubtypeOf(type, context.irBuiltIns)) return true + val superTypes = (type.classifierOrNull as? IrTypeParameterSymbol)?.owner?.superTypes + return superTypes != null && superTypes.all { isSubtypeOf(it, context.irBuiltIns) } + } private fun MessageCollector.info(expression: IrElement, message: String) { report(expression, CompilerMessageSeverity.INFO, message) diff --git a/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/AssertBooleanTest.kt b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/AssertBooleanTest.kt index 786287f45d0..ade3f26a4de 100644 --- a/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/AssertBooleanTest.kt +++ b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/AssertBooleanTest.kt @@ -2,24 +2,23 @@ package com.bnorm.power import org.jetbrains.kotlin.name.FqName import org.junit.Test -import kotlin.test.assertEquals class AssertBooleanTest { @Test fun `test assertTrue transformation`() { assertMessage( """ -import kotlin.test.assertTrue - -fun main() { - assertTrue(1 != 1) -}""", + import kotlin.test.assertTrue + + fun main() { + assertTrue(1 != 1) + }""", """ -Assertion failed -assertTrue(1 != 1) - | - false -""".trimIndent(), + Assertion failed + assertTrue(1 != 1) + | + false + """.trimIndent(), PowerAssertComponentRegistrar(setOf(FqName("kotlin.test.assertTrue"))) ) } @@ -28,17 +27,17 @@ assertTrue(1 != 1) fun `test assertFalse transformation`() { assertMessage( """ -import kotlin.test.assertFalse - -fun main() { - assertFalse(1 == 1) -}""", + import kotlin.test.assertFalse + + fun main() { + assertFalse(1 == 1) + }""", """ -Assertion failed -assertFalse(1 == 1) - | - true -""".trimIndent(), + Assertion failed + assertFalse(1 == 1) + | + true + """.trimIndent(), PowerAssertComponentRegistrar(setOf(FqName("kotlin.test.assertFalse"))) ) } diff --git a/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt index 34edaa0d940..b77dad7cb41 100644 --- a/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt +++ b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/DebugFunctionTest.kt @@ -12,9 +12,11 @@ import kotlin.test.assertEquals class DebugFunctionTest { @Test fun `debug function transformation`() { - val actual = executeMainDebug(""" + val actual = executeMainDebug( + """ dbg(1 + 2 + 3) - """.trimIndent()) + """.trimIndent() + ) assertEquals( """ dbg(1 + 2 + 3) @@ -27,9 +29,11 @@ class DebugFunctionTest { } @Test fun `debug function transformation with message`() { - val actual = executeMainDebug(""" + val actual = executeMainDebug( + """ dbg(1 + 2 + 3, "Message:") - """.trimIndent()) + """.trimIndent() + ) assertEquals( """ Message: diff --git a/sample/src/commonTest/kotlin/com/bnorm/power/PowerAssertTest.kt b/sample/src/commonTest/kotlin/com/bnorm/power/PowerAssertTest.kt index 8e374f9396e..c0231ff33de 100644 --- a/sample/src/commonTest/kotlin/com/bnorm/power/PowerAssertTest.kt +++ b/sample/src/commonTest/kotlin/com/bnorm/power/PowerAssertTest.kt @@ -130,7 +130,8 @@ class PowerAssertTest { | | | Jane Hello, Jane - """.trimIndent()) + """.trimIndent() + ) } @Test @@ -146,6 +147,7 @@ class PowerAssertTest { | | | Jane Hello, Jane - """.trimIndent()) + """.trimIndent() + ) } }