Introduce GetScriptingClassByClassLoader interface

It is needed to override default JVM behaviour
diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ImplicitsFromScriptResultTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ImplicitsFromScriptResultTest.kt
new file mode 100644
index 0000000..5c26519e
--- /dev/null
+++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ImplicitsFromScriptResultTest.kt
@@ -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()
+            }
+        }
+    }
+}
diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptingHostConfiguration.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptingHostConfiguration.kt
index f51f812..ee998d0 100644
--- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptingHostConfiguration.kt
+++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptingHostConfiguration.kt
@@ -45,7 +45,11 @@
         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 @@
         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
diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt
index 8e09af1..6d2ea8c 100644
--- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt
+++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt
@@ -352,8 +352,8 @@
     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 ->