IrGenerationExtension for transforming assert calls
This commit is contained in:
+2
-2
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.3.50" apply false
|
||||
kotlin("jvm") version "1.3.60" apply false
|
||||
id("org.jetbrains.dokka") version "0.10.0" apply false
|
||||
id("nebula.release") version "13.1.1"
|
||||
}
|
||||
@@ -8,7 +8,7 @@ val release = tasks.findByPath(":release")
|
||||
release?.finalizedBy(project.getTasksByName("publish", true))
|
||||
|
||||
allprojects {
|
||||
group = "com.bnorm.assert.power"
|
||||
group = "com.bnorm.power"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
|
||||
@@ -10,9 +10,10 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.3.60")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.1.1")
|
||||
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.1.1")
|
||||
testImplementation(kotlin("test-junit"))
|
||||
testImplementation("com.github.tschuchortdev:kotlin-compile-testing:1.2.5")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
|
||||
import org.jetbrains.kotlin.backend.common.lower.at
|
||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.lower.irIfThen
|
||||
import org.jetbrains.kotlin.backend.common.lower.irNot
|
||||
import org.jetbrains.kotlin.backend.common.lower.irThrow
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.SourceRangeInfo
|
||||
import org.jetbrains.kotlin.ir.builders.irBlock
|
||||
import org.jetbrains.kotlin.ir.builders.irCall
|
||||
import org.jetbrains.kotlin.ir.builders.irConcat
|
||||
import org.jetbrains.kotlin.ir.builders.irGet
|
||||
import org.jetbrains.kotlin.ir.builders.irString
|
||||
import org.jetbrains.kotlin.ir.builders.irTemporary
|
||||
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.IrExpression
|
||||
import org.jetbrains.kotlin.ir.util.getPackageFragment
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
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 context: JvmBackendContext
|
||||
) : IrElementTransformerVoidWithContext(), FileLoweringPass {
|
||||
private lateinit var file: IrFile
|
||||
private lateinit var fileSource: String
|
||||
|
||||
private val constructor = this@PowerAssertCallTransformer.context.ir.symbols.assertionErrorConstructor
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
file = irFile
|
||||
fileSource = File(irFile.path).readText()
|
||||
|
||||
irFile.transformChildrenVoid()
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
val function = expression.symbol.owner
|
||||
if (!function.isAssert)
|
||||
return super.visitCall(expression)
|
||||
|
||||
val callSource = fileSource.substring(expression.startOffset, expression.endOffset)
|
||||
val callIndent = file.info(expression).startColumnNumber
|
||||
|
||||
context.createIrBuilder(expression.symbol).run {
|
||||
at(expression)
|
||||
|
||||
return irBlock {
|
||||
val assertCondition = expression.getValueArgument(0)!!
|
||||
// val argumentSource = fileSource.substring(assertCondition.startOffset, assertCondition.endOffset)
|
||||
val indent = file.info(assertCondition).startColumnNumber - callIndent
|
||||
|
||||
// println(buildString {
|
||||
// append(callSource).newline()
|
||||
// indent(indent).append("|").newline()
|
||||
// indent(indent).append(argumentSource)
|
||||
// })
|
||||
|
||||
// TODO transform tree of expressions and create irTemporary for each
|
||||
val temp = irTemporary(assertCondition)
|
||||
val throwError = irThrow(irCall(constructor).apply {
|
||||
|
||||
val message = irConcat().apply {
|
||||
addArgument(irString(buildString {
|
||||
append("Assertion failed:").newline()
|
||||
append(callSource).newline()
|
||||
indent(indent).append("|").newline()
|
||||
indent(indent)
|
||||
}))
|
||||
addArgument(irGet(temp))
|
||||
}
|
||||
putValueArgument(0, message)
|
||||
})
|
||||
+irIfThen(irNot(irGet(temp)), throwError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
val IrFunction.isAssert: Boolean
|
||||
get() = name.asString() == "assert" && getPackageFragment()?.fqName == KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME
|
||||
|
||||
fun IrFile.info(expression: IrElement): SourceRangeInfo {
|
||||
return fileEntry.getSourceRangeInfo(expression.startOffset, expression.endOffset)
|
||||
}
|
||||
|
||||
fun StringBuilder.indent(indentation: Int) = apply {
|
||||
repeat(indentation) {
|
||||
append(" ")
|
||||
}
|
||||
}
|
||||
|
||||
fun StringBuilder.newline() = apply {
|
||||
append("\n")
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.com.intellij.mock.MockProject
|
||||
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.SourceRangeInfo
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
|
||||
class PowerAssertComponentRegistrar : ComponentRegistrar {
|
||||
override fun registerProjectComponents(
|
||||
project: MockProject,
|
||||
configuration: CompilerConfiguration
|
||||
) {
|
||||
IrGenerationExtension.registerExtension(project, PowerAssertIrGenerationExtension())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.BackendContext
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
|
||||
class PowerAssertIrGenerationExtension : IrGenerationExtension {
|
||||
override fun generate(file: IrFile, backendContext: BackendContext, bindingContext: BindingContext) {
|
||||
if (backendContext is JvmBackendContext) {
|
||||
PowerAssertCallTransformer(backendContext).runOnFileInOrder(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 com.tschuchort.compiletesting.KotlinCompilation
|
||||
import com.tschuchort.compiletesting.SourceFile
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.junit.Test
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
class CompilerTest {
|
||||
@Test
|
||||
fun testMyCompilerPlugin() {
|
||||
assertMessage(
|
||||
"""
|
||||
fun main() {
|
||||
val hello = "Brian"
|
||||
assert(hello == "World")
|
||||
}""",
|
||||
"""
|
||||
Assertion failed:
|
||||
assert(hello == "World")
|
||||
|
|
||||
false
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun assertMessage(@Language("kotlin") source: String, message: String) {
|
||||
val result = KotlinCompilation().apply {
|
||||
sources = listOf(SourceFile.kotlin("main.kt", source))
|
||||
useIR = true
|
||||
messageOutputStream = System.out
|
||||
compilerPlugins = listOf(PowerAssertComponentRegistrar())
|
||||
inheritClassPath = true
|
||||
}.compile()
|
||||
|
||||
assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode)
|
||||
|
||||
val kClazz = result.classLoader.loadClass("MainKt")
|
||||
val main = kClazz.declaredMethods.single { it.name == "main" }
|
||||
try {
|
||||
try {
|
||||
main.invoke(null)
|
||||
} catch (t: InvocationTargetException) {
|
||||
throw t.cause!!
|
||||
}
|
||||
fail("should have thrown assertion")
|
||||
} catch (t: AssertionError) {
|
||||
assertEquals(message, t.message)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user