Support Java Suppliers as message arguments
This commit is contained in:
+34
-16
@@ -18,6 +18,7 @@ package com.bnorm.power
|
||||
|
||||
import com.bnorm.power.delegate.FunctionDelegate
|
||||
import com.bnorm.power.delegate.LambdaFunctionDelegate
|
||||
import com.bnorm.power.delegate.SamConversionLambdaFunctionDelegate
|
||||
import com.bnorm.power.delegate.SimpleFunctionDelegate
|
||||
import com.bnorm.power.diagram.IrTemporaryVariable
|
||||
import com.bnorm.power.diagram.Node
|
||||
@@ -26,18 +27,15 @@ import com.bnorm.power.diagram.buildTree
|
||||
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.IrElementTransformerVoidWithContext
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||
import org.jetbrains.kotlin.backend.common.ir.asInlinable
|
||||
import org.jetbrains.kotlin.backend.common.ir.inline
|
||||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
||||
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.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.asString
|
||||
import org.jetbrains.kotlin.ir.builders.irBlock
|
||||
import org.jetbrains.kotlin.ir.builders.irCall
|
||||
import org.jetbrains.kotlin.ir.builders.irString
|
||||
import org.jetbrains.kotlin.ir.builders.parent
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
@@ -46,20 +44,25 @@ import org.jetbrains.kotlin.ir.declarations.path
|
||||
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.IrFunctionExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation
|
||||
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
|
||||
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.IrTypeSystemContextImpl
|
||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
||||
import org.jetbrains.kotlin.ir.types.classifierOrNull
|
||||
import org.jetbrains.kotlin.ir.types.isBoolean
|
||||
import org.jetbrains.kotlin.ir.types.isSubtypeOf
|
||||
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
|
||||
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.util.patchDeclarationParents
|
||||
import org.jetbrains.kotlin.ir.util.parentClassOrNull
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
class PowerAssertCallTransformer(
|
||||
@@ -122,10 +125,6 @@ class PowerAssertCallTransformer(
|
||||
val symbol = currentScope!!.scope.scopeOwnerSymbol
|
||||
val builder = DeclarationIrBuilder(context, symbol, expression.startOffset, expression.endOffset)
|
||||
return builder.diagram(expression, delegate, messageArgument, roots)
|
||||
// .also { println(expression.dump()) }
|
||||
// .also { println(it.dump()) }
|
||||
// .also { println(expression.dumpKotlinLike()) }
|
||||
// .also { println(it.dumpKotlinLike()) }
|
||||
}
|
||||
|
||||
private fun DeclarationIrBuilder.diagram(
|
||||
@@ -163,12 +162,14 @@ class PowerAssertCallTransformer(
|
||||
return when {
|
||||
messageArgument is IrConst<*> -> messageArgument
|
||||
messageArgument is IrStringConcatenation -> messageArgument
|
||||
messageArgument != null -> irBlock {
|
||||
+messageArgument.deepCopyWithSymbols(parent)
|
||||
.asInlinable(this)
|
||||
.inline(parent)
|
||||
.patchDeclarationParents(scope.getLocalDeclarationParent())
|
||||
}.transform(ReturnableBlockTransformer(context, scope.scopeOwnerSymbol), null)
|
||||
messageArgument is IrFunctionExpression -> {
|
||||
val invoke = messageArgument.type.classOrNull!!.functions
|
||||
.filter { it.owner.name.toString() == "invoke" }
|
||||
.single()
|
||||
irCall(invoke).apply { dispatchReceiver = messageArgument }
|
||||
}
|
||||
// SAMs conversion can be unwrapped to IrFunctionExpression
|
||||
messageArgument is IrTypeOperatorCall -> buildMessagePrefix(messageArgument.argument, roots, original)
|
||||
// TODO what should the default message be?
|
||||
roots.size == 1 && original.getValueArgument(0)!!.type.isBoolean() -> irString("Assertion failed")
|
||||
else -> null
|
||||
@@ -179,7 +180,16 @@ class PowerAssertCallTransformer(
|
||||
val values = function.valueParameters
|
||||
if (values.isEmpty()) return emptyList()
|
||||
|
||||
return context.referenceFunctions(function.kotlinFqName)
|
||||
// Java static functions require searching by class
|
||||
val parentClassFunctions = (function.parentClassOrNull
|
||||
?.let { context.referenceClass(it.kotlinFqName) }
|
||||
?.functions ?: emptySequence())
|
||||
.filter { it.owner.kotlinFqName == function.kotlinFqName }
|
||||
.toList()
|
||||
val possible = (context.referenceFunctions(function.kotlinFqName) + parentClassFunctions)
|
||||
.distinct()
|
||||
|
||||
return possible
|
||||
.mapNotNull { overload ->
|
||||
val parameters = overload.owner.valueParameters
|
||||
if (parameters.size !in values.size..values.size + 1) return@mapNotNull null
|
||||
@@ -191,6 +201,8 @@ class PowerAssertCallTransformer(
|
||||
return@mapNotNull when {
|
||||
isStringSupertype(messageParameter.type) -> SimpleFunctionDelegate(overload)
|
||||
isStringFunction(messageParameter.type) -> LambdaFunctionDelegate(overload, messageParameter)
|
||||
isStringJavaSupplierFunction(messageParameter.type) ->
|
||||
SamConversionLambdaFunctionDelegate(overload, messageParameter)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -199,6 +211,12 @@ class PowerAssertCallTransformer(
|
||||
private fun isStringFunction(type: IrType): Boolean =
|
||||
type.isFunctionOrKFunction() && type is IrSimpleType && (type.arguments.size == 1 && isStringSupertype(type.arguments.first()))
|
||||
|
||||
private fun isStringJavaSupplierFunction(type: IrType): Boolean {
|
||||
val javaSupplier = context.referenceClass(FqName("java.util.function.Supplier"))
|
||||
return javaSupplier != null && type.isSubtypeOfClass(javaSupplier)
|
||||
&& type is IrSimpleType && (type.arguments.size == 1 && isStringSupertype(type.arguments.first()))
|
||||
}
|
||||
|
||||
private fun isStringSupertype(argument: IrTypeArgument): Boolean =
|
||||
argument is IrTypeProjection && isStringSupertype(argument.type)
|
||||
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.delegate
|
||||
|
||||
import com.bnorm.power.irLambda
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
import org.jetbrains.kotlin.ir.builders.irReturn
|
||||
import org.jetbrains.kotlin.ir.builders.typeOperator
|
||||
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
|
||||
class SamConversionLambdaFunctionDelegate(
|
||||
private val overload: IrSimpleFunctionSymbol,
|
||||
private val messageParameter: IrValueParameter
|
||||
) : FunctionDelegate {
|
||||
override val function = overload.owner
|
||||
|
||||
override fun buildCall(
|
||||
builder: IrBuilderWithScope,
|
||||
original: IrCall,
|
||||
arguments: List<IrExpression?>,
|
||||
message: IrExpression
|
||||
): IrExpression = with(builder) {
|
||||
val lambda = irLambda(context.irBuiltIns.stringType, messageParameter.type) {
|
||||
+irReturn(message)
|
||||
}
|
||||
val expression = typeOperator(messageParameter.type, lambda, IrTypeOperator.SAM_CONVERSION, messageParameter.type)
|
||||
irCallCopy(overload, original, arguments, expression)
|
||||
}
|
||||
}
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.
|
||||
*
|
||||
* Copied from https://github.com/JetBrains/kotlin/blob/1.4.20/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/ReturnableBlockLowering.kt
|
||||
*/
|
||||
|
||||
package com.bnorm.power.internal
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
|
||||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.lower.irComposite
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||
import org.jetbrains.kotlin.ir.transformStatement
|
||||
|
||||
// TODO Remove when inlining works correctly on Kotlin/JS and Kotlin/Native
|
||||
class ReturnableBlockTransformer(val context: IrGeneratorContext, val containerSymbol: IrSymbol? = null) : IrElementTransformerVoidWithContext() {
|
||||
private var labelCnt = 0
|
||||
private val returnMap = mutableMapOf<IrReturnableBlockSymbol, (IrReturn) -> IrExpression>()
|
||||
|
||||
override fun visitReturn(expression: IrReturn): IrExpression {
|
||||
expression.transformChildrenVoid()
|
||||
return returnMap[expression.returnTargetSymbol]?.invoke(expression) ?: expression
|
||||
}
|
||||
|
||||
override fun visitContainerExpression(expression: IrContainerExpression): IrExpression {
|
||||
if (expression !is IrReturnableBlock) return super.visitContainerExpression(expression)
|
||||
|
||||
val scopeSymbol = currentScope?.scope?.scopeOwnerSymbol ?: containerSymbol
|
||||
val builder = DeclarationIrBuilder(context, scopeSymbol!!)
|
||||
val variable by lazy {
|
||||
builder.scope.createTmpVariable(expression.type, "tmp\$ret\$${labelCnt++}", true)
|
||||
}
|
||||
|
||||
val loop by lazy {
|
||||
IrDoWhileLoopImpl(
|
||||
expression.startOffset,
|
||||
expression.endOffset,
|
||||
context.irBuiltIns.unitType,
|
||||
expression.origin
|
||||
).apply {
|
||||
label = "l\$ret\$${labelCnt++}"
|
||||
condition = builder.irBoolean(false)
|
||||
}
|
||||
}
|
||||
|
||||
var hasReturned = false
|
||||
|
||||
returnMap[expression.symbol] = { returnExpression ->
|
||||
hasReturned = true
|
||||
builder.irComposite(returnExpression) {
|
||||
+irSet(variable.symbol, returnExpression.value)
|
||||
+irBreak(loop)
|
||||
}
|
||||
}
|
||||
|
||||
val newStatements = expression.statements.mapIndexed { i, s ->
|
||||
if (i == expression.statements.lastIndex && s is IrReturn && s.returnTargetSymbol == expression.symbol) {
|
||||
s.transformChildrenVoid()
|
||||
if (!hasReturned) s.value else {
|
||||
builder.irSet(variable.symbol, s.value)
|
||||
}
|
||||
} else {
|
||||
s.transformStatement(this)
|
||||
}
|
||||
}
|
||||
|
||||
returnMap.remove(expression.symbol)
|
||||
|
||||
if (!hasReturned) {
|
||||
return IrCompositeImpl(
|
||||
expression.startOffset,
|
||||
expression.endOffset,
|
||||
expression.type,
|
||||
expression.origin,
|
||||
newStatements
|
||||
)
|
||||
} else {
|
||||
loop.body = IrBlockImpl(
|
||||
expression.startOffset,
|
||||
expression.endOffset,
|
||||
context.irBuiltIns.unitType,
|
||||
expression.origin,
|
||||
newStatements
|
||||
)
|
||||
|
||||
return builder.irComposite(expression, expression.origin) {
|
||||
+variable
|
||||
+loop
|
||||
+irGet(variable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -19,7 +19,7 @@ package com.bnorm.power
|
||||
import kotlin.test.Test
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
class CompilerTest {
|
||||
class AssertTest {
|
||||
@Test
|
||||
fun memberFunctions() {
|
||||
assertMessage(
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 kotlin.test.Test
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
class Junit5AssertionsTest {
|
||||
@Test
|
||||
fun `test JUnit5 Assertions#assertTrue transformation`() {
|
||||
assertMessage(
|
||||
"""
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
||||
fun main() {
|
||||
assertTrue(1 != 1)
|
||||
}""",
|
||||
"""
|
||||
Assertion failed
|
||||
assertTrue(1 != 1)
|
||||
|
|
||||
false ==> expected: <true> but was: <false>
|
||||
""".trimIndent(),
|
||||
PowerAssertComponentRegistrar(setOf(FqName("org.junit.jupiter.api.Assertions.assertTrue")))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test JUnit5 Assertions#assertTrue transformation with message`() {
|
||||
assertMessage(
|
||||
"""
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
||||
fun main() {
|
||||
assertTrue(1 != 1, "Message:")
|
||||
}""",
|
||||
"""
|
||||
Message:
|
||||
assertTrue(1 != 1, "Message:")
|
||||
|
|
||||
false ==> expected: <true> but was: <false>
|
||||
""".trimIndent(),
|
||||
PowerAssertComponentRegistrar(setOf(FqName("org.junit.jupiter.api.Assertions.assertTrue")))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test JUnit5 Assertions#assertTrue transformation with message supplier`() {
|
||||
assertMessage(
|
||||
"""
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
||||
fun main() {
|
||||
assertTrue(1 != 1) { "Message:" }
|
||||
}""",
|
||||
"""
|
||||
Message:
|
||||
assertTrue(1 != 1) { "Message:" }
|
||||
|
|
||||
false ==> expected: <true> but was: <false>
|
||||
""".trimIndent(),
|
||||
PowerAssertComponentRegistrar(setOf(FqName("org.junit.jupiter.api.Assertions.assertTrue")))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test JUnit5 Assertions#assertFalse transformation`() {
|
||||
assertMessage(
|
||||
"""
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
|
||||
fun main() {
|
||||
assertFalse(1 == 1)
|
||||
}""",
|
||||
"""
|
||||
Assertion failed
|
||||
assertFalse(1 == 1)
|
||||
|
|
||||
true ==> expected: <false> but was: <true>
|
||||
""".trimIndent(),
|
||||
PowerAssertComponentRegistrar(setOf(FqName("org.junit.jupiter.api.Assertions.assertFalse")))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test JUnit5 Assertions#assertFalse transformation with message`() {
|
||||
assertMessage(
|
||||
"""
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
|
||||
fun main() {
|
||||
assertFalse(1 == 1, "Message:")
|
||||
}""",
|
||||
"""
|
||||
Message:
|
||||
assertFalse(1 == 1, "Message:")
|
||||
|
|
||||
true ==> expected: <false> but was: <true>
|
||||
""".trimIndent(),
|
||||
PowerAssertComponentRegistrar(setOf(FqName("org.junit.jupiter.api.Assertions.assertFalse")))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test JUnit5 Assertions#assertFalse transformation with message supplier`() {
|
||||
assertMessage(
|
||||
"""
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
|
||||
fun main() {
|
||||
assertFalse(1 == 1) { "Message:" }
|
||||
}""",
|
||||
"""
|
||||
Message:
|
||||
assertFalse(1 == 1) { "Message:" }
|
||||
|
|
||||
true ==> expected: <false> but was: <true>
|
||||
""".trimIndent(),
|
||||
PowerAssertComponentRegistrar(setOf(FqName("org.junit.jupiter.api.Assertions.assertFalse")))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test JUnit5 Assertions#assertEquals transformation`() {
|
||||
assertMessage(
|
||||
"""
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
||||
fun main() {
|
||||
val greeting = "Hello"
|
||||
val name = "World"
|
||||
assertEquals(greeting, name)
|
||||
}""",
|
||||
"""
|
||||
assertEquals(greeting, name)
|
||||
| |
|
||||
| World
|
||||
Hello ==> expected: <Hello> but was: <World>
|
||||
""".trimIndent(),
|
||||
PowerAssertComponentRegistrar(setOf(FqName("org.junit.jupiter.api.Assertions.assertEquals")))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test JUnit5 Assertions#assertEquals transformation with message`() {
|
||||
assertMessage(
|
||||
"""
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
||||
fun main() {
|
||||
val greeting = "Hello"
|
||||
val name = "World"
|
||||
assertEquals(greeting, name, "Message:")
|
||||
}""",
|
||||
"""
|
||||
Message:
|
||||
assertEquals(greeting, name, "Message:")
|
||||
| |
|
||||
| World
|
||||
Hello ==> expected: <Hello> but was: <World>
|
||||
""".trimIndent(),
|
||||
PowerAssertComponentRegistrar(setOf(FqName("org.junit.jupiter.api.Assertions.assertEquals")))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test JUnit5 Assertions#assertEquals transformation with message supplier`() {
|
||||
assertMessage(
|
||||
"""
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
||||
fun main() {
|
||||
val greeting = "Hello"
|
||||
val name = "World"
|
||||
assertEquals(greeting, name) { "Message:" }
|
||||
}""",
|
||||
"""
|
||||
Message:
|
||||
assertEquals(greeting, name) { "Message:" }
|
||||
| |
|
||||
| World
|
||||
Hello ==> expected: <Hello> but was: <World>
|
||||
""".trimIndent(),
|
||||
PowerAssertComponentRegistrar(setOf(FqName("org.junit.jupiter.api.Assertions.assertEquals")))
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -19,7 +19,7 @@ package com.bnorm.power
|
||||
import kotlin.test.Test
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
class AssertLibraryTest {
|
||||
class KotlinTestAssertTest {
|
||||
@Test
|
||||
fun `test assertTrue transformation`() {
|
||||
assertMessage(
|
||||
Reference in New Issue
Block a user