Merge pull request #24 from bnorm/kotlin-native-bugs
Fix bugs found on Kotlin/Native
This commit is contained in:
+26
-7
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.bnorm.power
|
package com.bnorm.power
|
||||||
|
|
||||||
|
import com.bnorm.power.internal.ReturnableBlockTransformer
|
||||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
|
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
|
||||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||||
@@ -23,6 +24,7 @@ import org.jetbrains.kotlin.backend.common.ir.asSimpleLambda
|
|||||||
import org.jetbrains.kotlin.backend.common.ir.inline
|
import org.jetbrains.kotlin.backend.common.ir.inline
|
||||||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
||||||
import org.jetbrains.kotlin.backend.common.lower.at
|
import org.jetbrains.kotlin.backend.common.lower.at
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.*
|
||||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||||
@@ -77,6 +79,7 @@ fun FileLoweringPass.runOnFileInOrder(irFile: IrFile) {
|
|||||||
|
|
||||||
class PowerAssertCallTransformer(
|
class PowerAssertCallTransformer(
|
||||||
private val context: IrPluginContext,
|
private val context: IrPluginContext,
|
||||||
|
private val messageCollector: MessageCollector,
|
||||||
private val functions: Set<FqName>
|
private val functions: Set<FqName>
|
||||||
) : IrElementTransformerVoidWithContext(), FileLoweringPass {
|
) : IrElementTransformerVoidWithContext(), FileLoweringPass {
|
||||||
private lateinit var file: IrFile
|
private lateinit var file: IrFile
|
||||||
@@ -95,9 +98,15 @@ class PowerAssertCallTransformer(
|
|||||||
if (functions.none { fqName == it })
|
if (functions.none { fqName == it })
|
||||||
return super.visitCall(expression)
|
return super.visitCall(expression)
|
||||||
|
|
||||||
|
// Find a valid delegate function or do not translate
|
||||||
val delegate = findDelegate(fqName) ?: run {
|
val delegate = findDelegate(fqName) ?: run {
|
||||||
// Find a valid delegate function or do not translate
|
val line = fileSource.substring(expression.startOffset).count { it == '\n' } + 1
|
||||||
// TODO log a warning
|
val location = CompilerMessageLocation.create(file.path, line, -1, null)
|
||||||
|
messageCollector.report(
|
||||||
|
CompilerMessageSeverity.WARNING,
|
||||||
|
"Unable to find overload for function $fqName callable as $fqName(Boolean, String) or $fqName(Boolean, () -> String) for power-assertion transformation",
|
||||||
|
location
|
||||||
|
)
|
||||||
return super.visitCall(expression)
|
return super.visitCall(expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +114,19 @@ class PowerAssertCallTransformer(
|
|||||||
val assertionArgument = expression.getValueArgument(0)!!
|
val assertionArgument = expression.getValueArgument(0)!!
|
||||||
val messageArgument = if (function.valueParameters.size == 2) expression.getValueArgument(1) else null
|
val messageArgument = if (function.valueParameters.size == 2) expression.getValueArgument(1) else null
|
||||||
|
|
||||||
|
// If the tree does not contain any children, the expression is not transformable
|
||||||
|
val tree = buildAssertTree(assertionArgument)
|
||||||
|
val root = tree.children.singleOrNull() ?: run {
|
||||||
|
val line = fileSource.substring(expression.startOffset).count { it == '\n' } + 1
|
||||||
|
val location = CompilerMessageLocation.create(file.path, line, -1, null)
|
||||||
|
messageCollector.report(
|
||||||
|
CompilerMessageSeverity.INFO,
|
||||||
|
"Expression is constant and will not be power-assertion transformed",
|
||||||
|
location
|
||||||
|
)
|
||||||
|
return super.visitCall(expression)
|
||||||
|
}
|
||||||
|
|
||||||
val symbol = currentScope!!.scope.scopeOwnerSymbol
|
val symbol = currentScope!!.scope.scopeOwnerSymbol
|
||||||
DeclarationIrBuilder(context, symbol).run {
|
DeclarationIrBuilder(context, symbol).run {
|
||||||
at(expression)
|
at(expression)
|
||||||
@@ -116,7 +138,7 @@ class PowerAssertCallTransformer(
|
|||||||
val title = when {
|
val title = when {
|
||||||
messageArgument is IrConst<*> -> messageArgument
|
messageArgument is IrConst<*> -> messageArgument
|
||||||
messageArgument is IrStringConcatenation -> messageArgument
|
messageArgument is IrStringConcatenation -> messageArgument
|
||||||
lambda != null -> lambda.inline(parent)
|
lambda != null -> lambda.inline(parent).transform(ReturnableBlockTransformer(context, symbol), null)
|
||||||
messageArgument != null -> {
|
messageArgument != null -> {
|
||||||
val invoke = messageArgument.type.getClass()!!.functions.single { it.name == OperatorNameConventions.INVOKE }
|
val invoke = messageArgument.type.getClass()!!.functions.single { it.name == OperatorNameConventions.INVOKE }
|
||||||
irCallOp(invoke.symbol, invoke.returnType, messageArgument)
|
irCallOp(invoke.symbol, invoke.returnType, messageArgument)
|
||||||
@@ -129,9 +151,6 @@ class PowerAssertCallTransformer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val tree = buildAssertTree(assertionArgument)
|
|
||||||
val root = tree.children.single()
|
|
||||||
|
|
||||||
// println(assertionArgument.dump())
|
// println(assertionArgument.dump())
|
||||||
// println(tree.dump())
|
// println(tree.dump())
|
||||||
|
|
||||||
@@ -147,7 +166,7 @@ class PowerAssertCallTransformer(
|
|||||||
private fun findDelegate(fqName: FqName): FunctionDelegate? {
|
private fun findDelegate(fqName: FqName): FunctionDelegate? {
|
||||||
return context.findOverloads(fqName)
|
return context.findOverloads(fqName)
|
||||||
.mapNotNull { overload ->
|
.mapNotNull { overload ->
|
||||||
// TODO allow other signatures than (Boolean, String) and (Boolean, () -> String))
|
// TODO allow other signatures than (Boolean, String) and (Boolean, () -> String)
|
||||||
val parameters = overload.owner.valueParameters
|
val parameters = overload.owner.valueParameters
|
||||||
if (parameters.size != 2) return@mapNotNull null
|
if (parameters.size != 2) return@mapNotNull null
|
||||||
if (!parameters[0].type.isBoolean()) return@mapNotNull null
|
if (!parameters[0].type.isBoolean()) return@mapNotNull null
|
||||||
|
|||||||
+5
-1
@@ -18,6 +18,8 @@ package com.bnorm.power
|
|||||||
|
|
||||||
import com.google.auto.service.AutoService
|
import com.google.auto.service.AutoService
|
||||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||||
|
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
import org.jetbrains.kotlin.com.intellij.mock.MockProject
|
import org.jetbrains.kotlin.com.intellij.mock.MockProject
|
||||||
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
@@ -38,7 +40,9 @@ class PowerAssertComponentRegistrar(
|
|||||||
) {
|
) {
|
||||||
val functions = configuration[KEY_FUNCTIONS]?.map { FqName(it) } ?: functions
|
val functions = configuration[KEY_FUNCTIONS]?.map { FqName(it) } ?: functions
|
||||||
if (functions.isEmpty()) return
|
if (functions.isEmpty()) return
|
||||||
IrGenerationExtension.registerExtension(project, PowerAssertIrGenerationExtension(functions.toSet()))
|
|
||||||
|
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
|
||||||
|
IrGenerationExtension.registerExtension(project, PowerAssertIrGenerationExtension(messageCollector, functions.toSet()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -18,15 +18,17 @@ package com.bnorm.power
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
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.IrModuleFragment
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
|
||||||
class PowerAssertIrGenerationExtension(
|
class PowerAssertIrGenerationExtension(
|
||||||
|
private val messageCollector: MessageCollector,
|
||||||
private val functions: Set<FqName>
|
private val functions: Set<FqName>
|
||||||
) : IrGenerationExtension {
|
) : IrGenerationExtension {
|
||||||
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
|
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
|
||||||
for (file in moduleFragment.files) {
|
for (file in moduleFragment.files) {
|
||||||
PowerAssertCallTransformer(pluginContext, functions).runOnFileInOrder(file)
|
PowerAssertCallTransformer(pluginContext, messageCollector, functions).runOnFileInOrder(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* 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.0/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
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
+irSetVar(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.irSetVar(variable.symbol, s.value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.transform(this, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -399,6 +399,20 @@ assert(a == 42)
|
|||||||
0
|
0
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun constantExpression() {
|
||||||
|
assertMessage(
|
||||||
|
"""
|
||||||
|
fun main() {
|
||||||
|
assert(true)
|
||||||
|
assert(false)
|
||||||
|
}""",
|
||||||
|
"""
|
||||||
|
Assertion failed
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertMessage(
|
fun assertMessage(
|
||||||
|
|||||||
@@ -57,5 +57,5 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configure<com.bnorm.power.PowerAssertGradleExtension> {
|
configure<com.bnorm.power.PowerAssertGradleExtension> {
|
||||||
functions = listOf("kotlin.test.assertTrue", "kotlin.require")
|
functions = listOf("kotlin.assert", "kotlin.test.assertTrue", "kotlin.require")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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 kotlin.test.Test
|
||||||
|
|
||||||
|
class JsPowerAssertTest {
|
||||||
|
@Test
|
||||||
|
fun assert() {
|
||||||
|
require(Person.UNKNOWN.size == 1) { "unknown persons: ${Person.UNKNOWN}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,6 @@ import kotlin.test.Test
|
|||||||
class JvmPowerAssertTest {
|
class JvmPowerAssertTest {
|
||||||
@Test
|
@Test
|
||||||
fun assert() {
|
fun assert() {
|
||||||
assert(Person.UNKNOWN.size == 1)
|
assert(Person.UNKNOWN.size == 1) { "unknown persons: ${Person.UNKNOWN}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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 kotlin.test.Test
|
||||||
|
|
||||||
|
class NativePowerAssertTest {
|
||||||
|
@Test
|
||||||
|
fun assert() {
|
||||||
|
assert(Person.UNKNOWN.size == 1) { "unknown persons: ${Person.UNKNOWN}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user