I’ve just migrated my project to 1.3.50, especially because its now mature scripting solution. In the same time, I performed other upgrades: moving JRE to AdeptJDK version 11 and Kotlin target version to 11, too.
After some painful dependency upgrade, my project builds and runs again. It uses several DSLs to read complex configurations. However, after switching to class version 55 (Java 11), I’ve run into the following exception:
Exception in thread “main” javax.script.ScriptException: Cannot inline bytecode > built with JVM target 11 into bytecode that is being built with JVM target 1.8. > Please specify proper ‘-jvm-target’ option
My DSL and scripts being complex, it took some time to narrow the problem source. With simple scripts (line 2+2
or Random(42)
the script evaluation worked fine. After commenting out different parts of the script, I found the problematic function. It allowed me to create a simple, complete example:
package hu.app
import javax.script.ScriptEngineManager
import kotlin.reflect.KClass
/**
* A class representing a type-value pair
*/
class TypedProperty<T : Any>(val type: KClass<T>, initialValue: T? = null) {
var value: T? = initialValue
companion object {
inline fun <reified T : Any> create(initialValue: T? = null) = TypedProperty(T::class, initialValue)
}
}
/**
* The root object the DSL creates. Body omitted for simplicity.
*/
class Building {
// Class body is omitted for simplicity
}
/**
* The simple DSL
*/
@DslMarker
annotation class MyDsl
@MyDsl
class PropertyBuilder constructor() {
@MyDsl
val properties: Map<String, TypedProperty<*>> = mutableMapOf()
/**
* An extension function for String allowing the creation of a freely typed, but typed properties.
* This function causes the problem!
*/
@MyDsl
inline infix fun <reified T : Any> String.set(value: T?) {
val p = (properties as MutableMap).getOrPut(this, { TypedProperty.create(value) })
if (T::class == p.type)
(p as TypedProperty<T>).apply { this.value = value }
else
throw RuntimeException("Property '$this' is reassigned with different type. Original: ${p.type.simpleName}, new: ${T::class.simpleName}")
}
}
// Root of the DSL
@MyDsl
fun building(id: String = "Unnamed", op: BuildingBuilder.() -> Unit) = BuildingBuilder(id).apply(op).build()
// An interface for DSL classes with property support
@MyDsl
interface HasPropertiesBuilder {
@MyDsl
val properties: PropertyBuilder
@MyDsl
fun properties(op: PropertyBuilder.() -> Unit) = properties.op()
}
// The builder of the root object: it has properties
class BuildingBuilder(val id: String) : HasPropertiesBuilder {
override val properties = PropertyBuilder()
// This function body is omitted for simplification
fun build() : Building = Building()
}
fun main() {
// Initialize the engine (new 1.3.50 engine is used)
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
// Evaluation
val res = engine.eval("""
import hu.pmi.autoparking.app.*
building {
properties {
"X" set 42
}
}
""".trimIndent()) as Building
}
In this code, there is an extension function in String to define a statement in the DSL, which creates a TypedProperty
object to store a class/value pair. This function has a reified type argument, therefore it has to be inline. This function causes the problem, and it is easy to check: the "X" set 42
line have to be commented and it is evaluated without any error.
The question is: how could I specify for the script engine, what class version should it compile into?
PS: A bonus question: When the application starts, it prints a warning:
ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory not found
It doesn’t has any impact on the execution: the script engine may be acquired and used without problem, but it may indicate some configuration mistake.