Compare commits

...

2 Commits

Author SHA1 Message Date
Ilya Muradyan
106ea55afc Introduce GetScriptingClassByClassLoader interface
It is needed to override default JVM behaviour
2020-11-28 03:16:04 +03:00
Ilya Muradyan
977d170d11 Fix path for Windows in Fibonacci test 2020-11-28 03:16:04 +03:00
4 changed files with 162 additions and 5 deletions

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2010-2020 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.
*/
package kotlin.script.experimental.jvmhost.test
import junit.framework.TestCase
import kotlinx.coroutines.runBlocking
import java.io.BufferedOutputStream
import java.io.FileOutputStream
import java.nio.file.Files
import java.nio.file.Path
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.getScriptingClass
import kotlin.script.experimental.host.with
import kotlin.script.experimental.jvm.*
import kotlin.script.experimental.jvm.impl.KJvmCompiledModuleInMemory
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.jvmhost.JvmScriptCompiler
/**
* This test shows an ability of using KClasses loaded with classloaders
* other than default one, in the role of implicit receivers. For this reason,
* specific [GetScriptingClassByClassLoader] was implemented. Actually,
* as we use previously compiled snippets as implicits, we could achieve the same
* thing if we change the used compiler to one of the REPL ones. But if we are limited
* in our choice of compiler and only can tune the configuration, this is the only way.
*
* This test may be deleted or at least simplified when the option
* in [ScriptCompilationConfiguration] for saving previous classes in
* underlying module will be introduced.
*/
class ImplicitsFromScriptResultTest : TestCase() {
fun testImplicits() {
val host = CompilerHost()
val snippets = listOf(
"val xyz0 = 42",
"fun f() = xyz0",
"val finalRes = xyz0 + f()",
)
for (snippet in snippets) {
val res = host.compile(snippet)
assertTrue(res is ResultWithDiagnostics.Success)
}
}
}
fun interface PreviousScriptClassesProvider {
fun get(): List<KClass<*>>
}
class GetScriptClassForImplicits(
private val previousScriptClassesProvider: PreviousScriptClassesProvider
) : GetScriptingClassByClassLoader {
private val getScriptingClass = JvmGetScriptingClass()
private val lastClassLoader
get() = previousScriptClassesProvider.get().lastOrNull()?.java?.classLoader
override fun invoke(
classType: KotlinType,
contextClass: KClass<*>,
hostConfiguration: ScriptingHostConfiguration
): KClass<*> {
return getScriptingClass(classType, lastClassLoader ?: contextClass.java.classLoader, hostConfiguration)
}
override fun invoke(
classType: KotlinType,
contextClassLoader: ClassLoader?,
hostConfiguration: ScriptingHostConfiguration
): KClass<*> {
return getScriptingClass(classType, lastClassLoader ?: contextClassLoader, hostConfiguration)
}
}
class CompilerHost {
private var counter = 0
private val implicits = mutableListOf<KClass<*>>()
private val outputDir: Path = Files.createTempDirectory("kotlin-scripting-jvm")
private val classWriter = ClassWriter(outputDir)
init {
outputDir.toFile().deleteOnExit()
}
private val myHostConfiguration = defaultJvmScriptingHostConfiguration.with {
getScriptingClass(GetScriptClassForImplicits(::getImplicitsClasses))
}
private val compileConfiguration = ScriptCompilationConfiguration {
hostConfiguration(myHostConfiguration)
jvm {
dependencies(JvmDependency(outputDir.toFile()))
}
}
private val evaluationConfiguration = ScriptEvaluationConfiguration()
private val compiler = JvmScriptCompiler(myHostConfiguration)
private fun getImplicitsClasses(): List<KClass<*>> = implicits
fun compile(code: String): ResultWithDiagnostics<CompiledScript> {
val source = SourceCodeTestImpl(counter++, code)
val refinedConfig = compileConfiguration.with {
implicitReceivers(*implicits.toTypedArray())
}
val result = runBlocking { compiler.invoke(source, refinedConfig) }
val compiledScript = result.valueOrThrow() as KJvmCompiledScript
classWriter.writeCompiledSnippet(compiledScript)
val kClass = runBlocking { compiledScript.getClass(evaluationConfiguration) }.valueOrThrow()
implicits.add(kClass)
return result
}
private class SourceCodeTestImpl(number: Int, override val text: String) : SourceCode {
override val name: String = "Line_$number"
override val locationId: String = "location_$number"
}
}
class ClassWriter(private val outputDir: Path) {
fun writeCompiledSnippet(snippet: KJvmCompiledScript) {
val moduleInMemory = snippet.getCompiledModule() as KJvmCompiledModuleInMemory
moduleInMemory.compilerOutputFiles.forEach { (name, bytes) ->
if (name.endsWith(".class")) {
writeClass(bytes, outputDir.resolve(name))
}
}
}
private fun writeClass(classBytes: ByteArray, path: Path) {
FileOutputStream(path.toAbsolutePath().toString()).use { fos ->
BufferedOutputStream(fos).use { out ->
out.write(classBytes)
out.flush()
}
}
}
}

View File

@@ -45,7 +45,11 @@ val defaultJvmScriptingHostConfiguration
getScriptingClass(JvmGetScriptingClass())
}
class JvmGetScriptingClass : GetScriptingClass, Serializable {
interface GetScriptingClassByClassLoader : GetScriptingClass {
operator fun invoke(classType: KotlinType, contextClassLoader: ClassLoader?, hostConfiguration: ScriptingHostConfiguration): KClass<*>
}
class JvmGetScriptingClass : GetScriptingClassByClassLoader, Serializable {
@Transient
private var dependencies: List<ScriptDependency>? = null
@@ -64,7 +68,11 @@ class JvmGetScriptingClass : GetScriptingClass, Serializable {
invoke(classType, contextClass.java.classLoader, hostConfiguration)
@Synchronized
operator fun invoke(classType: KotlinType, contextClassLoader: ClassLoader?, hostConfiguration: ScriptingHostConfiguration): KClass<*> {
override operator fun invoke(
classType: KotlinType,
contextClassLoader: ClassLoader?,
hostConfiguration: ScriptingHostConfiguration
): KClass<*> {
// checking if class already loaded in the same context
val fromClass = classType.fromClass

View File

@@ -352,8 +352,8 @@ fun getScriptCollectedData(
val hostConfiguration =
compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] ?: defaultJvmScriptingHostConfiguration
val getScriptingClass = hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
val jvmGetScriptingClass = (getScriptingClass as? JvmGetScriptingClass)
?: throw IllegalArgumentException("Expecting JvmGetScriptingClass in the hostConfiguration[getScriptingClass], got $getScriptingClass")
val jvmGetScriptingClass = (getScriptingClass as? GetScriptingClassByClassLoader)
?: throw IllegalArgumentException("Expecting class implementing GetScriptingClassByClassLoader in the hostConfiguration[getScriptingClass], got $getScriptingClass")
val acceptedAnnotations =
compilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.flatMap {
it.annotations.mapNotNull { ann ->

View File

@@ -62,8 +62,9 @@ class CompileTimeFibonacciTest : TestCase() {
is ResultWithDiagnostics.Failure -> {
val error = result.reports.first()
val expectedFile = File("plugins/scripting/scripting-compiler/testData/compiler/compileTimeFibonacci/unsupported.fib.kts")
val expectedErrorMessage = """
(plugins/scripting/scripting-compiler/testData/compiler/compileTimeFibonacci/unsupported.fib.kts:3:1) Fibonacci of non-positive numbers like 0 are not supported
($expectedFile:3:1) Fibonacci of non-positive numbers like 0 are not supported
""".trimIndent()
Assert.assertEquals(expectedErrorMessage, error.message)
// TODO: the location is not in the diagnostics because the `MessageCollector` defined in KotlinTestUtils,