Implement "legacy" REPL wrappers on top of the "new" scripting infrastructure

This commit is contained in:
Ilya Chernikov
2019-04-30 15:43:33 +02:00
parent 010ae5326d
commit 46915df56f
18 changed files with 631 additions and 127 deletions
@@ -96,5 +96,11 @@ open class AggregatedReplStageState<T1, T2>(val state1: IReplStageState<T1>, val
override fun getNextLineNo() = state1.getNextLineNo()
override val currentGeneration: Int get() = state1.currentGeneration
override fun dispose() {
state2.dispose()
state1.dispose()
super.dispose()
}
}
@@ -90,7 +90,8 @@ sealed class ReplCompileResult : Serializable {
val classes: List<CompiledClassData>,
val hasResult: Boolean,
val classpathAddendum: List<File>,
val type: String?
val type: String?,
val data: Any? // TODO: temporary; migration to new scripting infrastructure
) : ReplCompileResult() {
companion object { private val serialVersionUID: Long = 2L }
}
@@ -63,7 +63,6 @@ interface IReplStageState<T> {
else throw IllegalArgumentException("$this is not an expected instance of IReplStageState")
fun dispose() {
history.reset()
}
}
@@ -249,4 +249,10 @@ interface CompiledScript<out ScriptBase : Any> {
*/
val otherScripts: List<CompiledScript<*>>
get() = emptyList()
/**
* The name and the type of the script's result field, if any
*/
val resultField: Pair<String, KotlinType>?
get() = null
}
@@ -50,6 +50,13 @@ val ScriptEvaluationConfigurationKeys.providedProperties by PropertiesCollection
*/
val ScriptEvaluationConfigurationKeys.constructorArgs by PropertiesCollection.key<List<Any?>>()
/**
* If the script is a snippet in a REPL, this property expected to contain previous REPL snippets in historical order
* For the first snippet in a REPL an empty list should be passed explicitly
* An array of the previous snippets will be passed to the current snippet constructor
*/
val ScriptEvaluationConfigurationKeys.previousSnippets by PropertiesCollection.key<List<Any>>()
@Deprecated("use scriptsInstancesSharing flag instead", level = DeprecationLevel.ERROR)
val ScriptEvaluationConfigurationKeys.scriptsInstancesSharingMap by PropertiesCollection.key<MutableMap<KClass<*>, EvaluationResult>>()
@@ -101,6 +108,11 @@ sealed class ResultValue {
override fun toString(): String = "$name: $type = $value"
}
class UnitValue(val scriptInstance: Any) : ResultValue() {
override fun toString(): String = "Unit"
}
// TODO: obsolete it, use differently named value in the saving evaluators
object Unit : ResultValue()
}
@@ -75,6 +75,10 @@ open class PropertiesCollection(private val properties: Map<Key<*>, Any?> = empt
data[this] = v
}
fun <T> PropertiesCollection.Key<T>.put(v: T) {
data[this] = v
}
// generic for lists
operator fun <T> PropertiesCollection.Key<in List<T>>.invoke(vararg vals: T) {
@@ -10,7 +10,10 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollectorBasedReporter
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.common.repl.IReplStageHistory
import org.jetbrains.kotlin.cli.common.repl.LineId
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.scriptResultFieldName
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
@@ -32,7 +35,7 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
return ReplCompilationState(context)
}
fun checkSyntax(
override fun checkSyntax(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
project: Project
@@ -44,13 +47,18 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
override val messageCollector = messageCollector
}
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(ktFile, errorHolder)
if (syntaxErrorReport.isHasErrors) failure(messageCollector) else true.asSuccess()
when {
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> false.asSuccess(messageCollector.diagnostics)
syntaxErrorReport.isHasErrors -> failure(messageCollector)
else -> true.asSuccess()
}
}
fun compileReplSnippet(
override fun compileReplSnippet(
compilationState: JvmReplCompilerState.Compilation,
snippet: SourceCode,
snippetId: ReplSnippetId,
// TODO: replace history with some interface based on CompiledScript
history: IReplStageHistory<ScriptDescriptor>
): ResultWithDiagnostics<CompiledScript<*>> =
withMessageCollector(snippet) { messageCollector ->
@@ -108,7 +116,6 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
CompilationErrorHandler.THROW_EXCEPTION
)
val generatedClassname = makeScriptBaseName(codeLine)
history.push(LineId(codeLine), scriptDescriptor)
val compiledScript =
@@ -133,4 +140,3 @@ internal class ReplCompilationState(val context: SharedScriptCompilationContext)
internal fun makeReplCodeLine(id: ReplSnippetId, code: SourceCode): ReplCodeLine =
ReplCodeLine(id.no, id.generation, code.text)
@@ -62,10 +62,7 @@ internal fun createSharedCompilationContext(
scriptCompilationConfiguration, hostConfiguration, scriptCompilationState, messageCollector, ignoredOptionsReportingState
)
val environment =
if (System.getProperty("idea.is.unit.test") == "true") KotlinCoreEnvironment.createForTests(
disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES
)
else KotlinCoreEnvironment.createForProduction(
KotlinCoreEnvironment.createForProduction(
disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES
)
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.scripting.dependencies.ScriptsCompilationDependencies
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.*
@@ -71,7 +72,7 @@ internal inline fun <T> withMessageCollectorAndDisposable(
failure(messageCollector, ex.asDiagnostics(path = locationId))
} finally {
if (disposeOnSuccess || failed) {
disposable.dispose()
Disposer.dispose(disposable)
}
}
}
@@ -134,6 +135,7 @@ internal fun makeCompiledScript(
containingKtFile.virtualFile?.path,
getScriptConfiguration(sourceFile),
it.fqName.asString(),
null,
makeOtherScripts(it),
null
)
@@ -145,10 +147,18 @@ internal fun makeCompiledScript(
}
val module = makeCompiledModule(generationState)
val resultField = with(generationState.replSpecific) {
// TODO: pass it in the configuration instead
if (!hasResult || resultType == null || scriptResultFieldName == null) null
else scriptResultFieldName!! to KotlinType(DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(resultType!!))
}
return KJvmCompiledScript(
script.locationId,
getScriptConfiguration(ktScript.containingKtFile),
ktScript.fqName.asString(),
resultField,
makeOtherScripts(ktScript),
module
)
@@ -5,112 +5,25 @@
package kotlin.script.experimental.jvmhost.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.cli.common.repl.IReplStageHistory
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.scripting.repl.ReplCodeAnalyzer
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvmhost.impl.KJvmCompilerImpl
import kotlin.script.experimental.jvmhost.impl.withDefaults
class JvmReplCompiler(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
val replCompilerProxy: KJvmReplCompilerProxy = KJvmCompilerImpl(hostConfiguration.withDefaults())
) : ReplCompiler {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = JvmReplCompilerState(replCompilerProxy, lock)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
import kotlin.script.experimental.api.*
interface KJvmReplCompilerProxy {
fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation
}
class JvmReplCompilerStageHistory(private val state: JvmReplCompilerState) :
BasicReplStageHistory<ScriptDescriptor>(state.lock) {
fun checkSyntax(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
project: Project
): ResultWithDiagnostics<Boolean>
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.compilation.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.compilation.analyzerEngine.resetToLine(id)
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<ReplCodeLine>) {
removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) ->
if (removedCompiledLine != LineId(removedAnalyzedLine)) {
throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine")
}
}
}
}
class JvmReplCompilerState(
val replCompilerProxy: KJvmReplCompilerProxy,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<ScriptDescriptor> {
override val history = JvmReplCompilerStageHistory(this)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
override fun dispose() {
lock.write {
_compilation?.disposable?.dispose()
_compilation = null
super.dispose()
}
}
fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): Compilation = lock.write {
if (_compilation == null) {
initializeCompilation(scriptCompilationConfiguration)
}
_compilation!!
}
internal val compilation: Compilation
get() = _compilation ?: throw IllegalStateException("Compilation state is either not initializad or already destroyed")
private var _compilation: Compilation? = null
val isCompilationInitialized get() = _compilation != null
private fun initializeCompilation(scriptCompilationConfiguration: ScriptCompilationConfiguration) {
if (_compilation != null) throw IllegalStateException("Compilation state is already initialized")
_compilation = replCompilerProxy.createReplCompilationState(scriptCompilationConfiguration)
}
interface Compilation {
val disposable: Disposable
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration
val environment: KotlinCoreEnvironment
val analyzerEngine: ReplCodeAnalyzer
}
}
fun compileReplSnippet(
compilationState: JvmReplCompilerState.Compilation,
snippet: SourceCode,
snippetId: ReplSnippetId,
history: IReplStageHistory<ScriptDescriptor>
): ResultWithDiagnostics<CompiledScript<*>>
}
@@ -0,0 +1,156 @@
/*
* Copyright 2010-2019 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.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.scripting.repl.ReplCodeAnalyzer
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvmhost.impl.KJvmReplCompilerImpl
import kotlin.script.experimental.jvmhost.impl.withDefaults
/**
* REPL Compilation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package
*/
class JvmReplCompiler(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
val replCompilerProxy: KJvmReplCompilerProxy = KJvmReplCompilerImpl(hostConfiguration.withDefaults())
) : ReplCompiler {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = JvmReplCompilerState(replCompilerProxy, lock)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = state.lock.write {
val replCompilerState = state.asState(JvmReplCompilerState::class.java)
val compilation = replCompilerState.getCompilationState(scriptCompilationConfiguration)
val res =
replCompilerProxy.checkSyntax(
codeLine.toSourceCode(scriptCompilationConfiguration),
compilation.baseScriptCompilationConfiguration,
compilation.environment.project
)
when {
// TODO: implement diagnostics rendering
res is ResultWithDiagnostics.Success && res.value -> ReplCheckResult.Ok()
res is ResultWithDiagnostics.Success && !res.value -> ReplCheckResult.Incomplete()
else -> ReplCheckResult.Error(res.reports.joinToString("\n") { it.message })
}
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = state.lock.write {
val replCompilerState = state.asState(JvmReplCompilerState::class.java)
val compilation = replCompilerState.getCompilationState(scriptCompilationConfiguration)
val snippet = codeLine.toSourceCode(scriptCompilationConfiguration)
val snippetId = ReplSnippetIdImpl(codeLine.no, codeLine.generation, snippet)
when (val res = replCompilerProxy.compileReplSnippet(compilation, snippet, snippetId, replCompilerState.history)) {
is ResultWithDiagnostics.Success ->
ReplCompileResult.CompiledClasses(
LineId(codeLine),
replCompilerState.history.map { it.id },
snippet.name!!,
emptyList(),
res.value.resultField != null,
emptyList(),
res.value.resultField?.second?.typeName,
res.value
)
else -> ReplCompileResult.Error(res.reports.joinToString("\n") { it.message })
}
}
}
class JvmReplCompilerStageHistory(private val state: JvmReplCompilerState) :
BasicReplStageHistory<ScriptDescriptor>(state.lock) {
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.compilation.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.compilation.analyzerEngine.resetToLine(id)
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<ReplCodeLine>) {
removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) ->
if (removedCompiledLine != LineId(removedAnalyzedLine)) {
throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine")
}
}
}
}
class JvmReplCompilerState(
val replCompilerProxy: KJvmReplCompilerProxy,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<ScriptDescriptor> {
override val history = JvmReplCompilerStageHistory(this)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
override fun dispose() {
lock.write {
_compilation?.disposable?.let {
Disposer.dispose(it)
}
_compilation = null
super.dispose()
}
}
fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): Compilation = lock.write {
if (_compilation == null) {
initializeCompilation(scriptCompilationConfiguration)
}
_compilation!!
}
internal val compilation: Compilation
get() = _compilation ?: throw IllegalStateException("Compilation state is either not initializad or already destroyed")
private var _compilation: Compilation? = null
val isCompilationInitialized get() = _compilation != null
private fun initializeCompilation(scriptCompilationConfiguration: ScriptCompilationConfiguration) {
if (_compilation != null) throw IllegalStateException("Compilation state is already initialized")
_compilation = replCompilerProxy.createReplCompilationState(scriptCompilationConfiguration)
}
interface Compilation {
val disposable: Disposable
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration
val environment: KotlinCoreEnvironment
val analyzerEngine: ReplCodeAnalyzer
}
}
internal class SourceCodeFromReplCodeLine(
val codeLine: ReplCodeLine,
compilationConfiguration: ScriptCompilationConfiguration
) : SourceCode {
override val text: String get() = codeLine.code
override val name: String = "${makeScriptBaseName(codeLine)}.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}"
override val locationId: String? = null
}
internal fun ReplCodeLine.toSourceCode(compilationConfiguration: ScriptCompilationConfiguration): SourceCode =
SourceCodeFromReplCodeLine(this, compilationConfiguration)
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2019 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.repl
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.*
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.jvm.jvm
/**
* REPL Evaluation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package
*/
class JvmReplEvaluator(
val baseScriptEvaluationConfiguration: ScriptEvaluationConfiguration,
val scriptEvaluator: ScriptEvaluator = BasicJvmScriptEvaluator()
) : ReplEvaluator {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> =
JvmReplEvaluatorState(baseScriptEvaluationConfiguration, lock)
override fun eval(
state: IReplStageState<*>,
compileResult: ReplCompileResult.CompiledClasses,
scriptArgs: ScriptArgsWithTypes?,
invokeWrapper: InvokeWrapper?
): ReplEvalResult = state.lock.write {
val evalState = state.asState(JvmReplEvaluatorState::class.java)
val compiledScript = (compileResult.data as? KJvmCompiledScript<*>)
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compileResult.data}")
val lastSnippetInstance = evalState.history.peek()?.item
val currentConfiguration = ScriptEvaluationConfiguration(baseScriptEvaluationConfiguration) {
if (evalState.history.isNotEmpty()) {
previousSnippets.put(evalState.history.map { it.item })
}
if (lastSnippetInstance != null) {
jvm {
baseClassLoader(lastSnippetInstance::class.java.classLoader)
}
}
}
val res = runBlocking { scriptEvaluator(compiledScript, currentConfiguration) }
when (res) {
is ResultWithDiagnostics.Success -> when (val retVal = res.value.returnValue) {
is ResultValue.Value -> {
evalState.history.push(compileResult.lineId, retVal.scriptInstance)
// TODO: the latter check is temporary while the result is used to return the instance too
if (retVal.type.isNotBlank())
ReplEvalResult.ValueResult(retVal.name, retVal.value, retVal.type)
else
ReplEvalResult.UnitResult()
}
is ResultValue.UnitValue -> {
evalState.history.push(compileResult.lineId, retVal.scriptInstance)
ReplEvalResult.UnitResult()
}
else -> throw IllegalStateException("Expecting value with script instance, got $retVal")
}
else -> ReplEvalResult.Error.Runtime(res.reports.joinToString("\n") { it.message })
}
}
}
open class JvmReplEvaluatorState(
scriptEvaluationConfiguration: ScriptEvaluationConfiguration,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<Any> {
override val history: IReplStageHistory<Any> = BasicReplStageHistory(lock)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
val topClassLoader: ClassLoader = scriptEvaluationConfiguration[ScriptEvaluationConfiguration.jvm.baseClassLoader]!!
}
@@ -0,0 +1,175 @@
/*
* Copyright 2010-2019 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 com.intellij.openapi.application.ApplicationManager
import junit.framework.TestCase
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase.resetApplicationToNull
import java.io.Closeable
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.script.experimental.jvmhost.repl.JvmReplCompiler
import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator
// Adapted form GenericReplTest
class LegacyReplTest : KtUsefulTestCase() {
fun testReplBasics() {
LegacyTestRepl().use { repl ->
val res1 = repl.replCompiler.check(repl.state, ReplCodeLine(0, 0, "val x ="))
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete)
assertEvalResult(repl, "val l1 = listOf(1 + 2)\nl1.first()", 3)
assertEvalUnit(repl, "val x = 5")
assertEvalResult(repl, "x + 2", 7)
}
}
fun testReplErrors() {
LegacyTestRepl().use { repl ->
repl.compileAndEval(repl.nextCodeLine("val x = 10"))
val res = repl.compileAndEval(repl.nextCodeLine("java.util.fish"))
TestCase.assertTrue("Expected compile error", res.first is ReplCompileResult.Error)
val result = repl.compileAndEval(repl.nextCodeLine("x"))
assertEquals(res.second.toString(), 10, (result.second as? ReplEvalResult.ValueResult)?.value)
}
}
fun testReplCodeFormat() {
LegacyTestRepl().use { repl ->
val codeLine0 = ReplCodeLine(0, 0, "val l1 = 1\r\nl1\r\n")
val res0 = repl.replCompiler.check(repl.state, codeLine0)
val res0c = res0 as? ReplCheckResult.Ok
TestCase.assertNotNull("Unexpected compile result: $res0", res0c)
}
}
fun testRepPackage() {
LegacyTestRepl().use { repl ->
assertEvalResult(repl, "package mypackage\n\nval x = 1\nx+2", 3)
assertEvalResult(repl, "x+4", 5)
}
}
fun test256Evals() {
LegacyTestRepl().use { repl ->
repl.compileAndEval(ReplCodeLine(0, 0, "val x0 = 0"))
val evals = 256
for (i in 1..evals) {
repl.compileAndEval(ReplCodeLine(i, 0, "val x$i = x${i-1} + 1"))
}
val res = repl.compileAndEval(ReplCodeLine(evals + 1, 0, "x$evals"))
assertEquals(res.second.toString(), evals, (res.second as? ReplEvalResult.ValueResult)?.value)
}
}
fun testReplSlowdownKt22740() {
LegacyTestRepl().use { repl ->
repl.compileAndEval(ReplCodeLine(0, 0, "class Test<T>(val x: T) { fun <R> map(f: (T) -> R): R = f(x) }".trimIndent()))
// We expect that analysis time is not exponential
for (i in 1..60) {
repl.compileAndEval(ReplCodeLine(i, 0, "fun <T> Test<T>.map(f: (T) -> Double): List<Double> = listOf(f(this.x))"))
}
}
}
fun testReplResultFieldWithFunction() {
LegacyTestRepl().use { repl ->
assertEvalResultIs<Function0<Int>>(repl, "{ 1 + 2 }")
assertEvalResultIs<Function0<Int>>(repl, "res0")
assertEvalResult(repl, "res0()", 3)
}
}
fun testReplResultField() {
LegacyTestRepl().use { repl ->
assertEvalResult(repl, "5 * 4", 20)
assertEvalResult(repl, "res0 + 3", 23)
}
}
private fun assertEvalUnit(repl: LegacyTestRepl, line: String) {
val compiledClasses = checkCompile(repl, line)
val evalResult = repl.compiledEvaluator.eval(repl.state, compiledClasses!!)
val unitResult = evalResult as? ReplEvalResult.UnitResult
TestCase.assertNotNull("Unexpected eval result: $evalResult", unitResult)
}
private fun<R> assertEvalResult(repl: LegacyTestRepl, line: String, expectedResult: R) {
val compiledClasses = checkCompile(repl, line)
val evalResult = repl.compiledEvaluator.eval(repl.state, compiledClasses!!)
val valueResult = evalResult as? ReplEvalResult.ValueResult
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
TestCase.assertEquals(expectedResult, valueResult!!.value)
}
private inline fun<reified R> assertEvalResultIs(repl: LegacyTestRepl, line: String) {
val compiledClasses = checkCompile(repl, line)
val evalResult = repl.compiledEvaluator.eval(repl.state, compiledClasses!!)
val valueResult = evalResult as? ReplEvalResult.ValueResult
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
TestCase.assertTrue(valueResult!!.value is R)
}
private fun checkCompile(repl: LegacyTestRepl, line: String): ReplCompileResult.CompiledClasses? {
val codeLine = repl.nextCodeLine(line)
val compileResult = repl.replCompiler.compile(repl.state, codeLine)
val compiledClasses = compileResult as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $compileResult", compiledClasses)
return compiledClasses
}
}
internal class LegacyTestRepl : Closeable {
val application = ApplicationManager.getApplication()
val currentLineCounter = AtomicInteger()
fun nextCodeLine(code: String): ReplCodeLine = ReplCodeLine(currentLineCounter.getAndIncrement(), 0, code)
val replCompiler: JvmReplCompiler by lazy {
JvmReplCompiler(simpleScriptompilationConfiguration)
}
val compiledEvaluator: ReplEvaluator by lazy {
JvmReplEvaluator(simpleScriptEvaluationConfiguration)
}
val state by lazy {
val stateLock = ReentrantReadWriteLock()
AggregatedReplStageState(replCompiler.createState(stateLock), compiledEvaluator.createState(stateLock), stateLock)
}
override fun close() {
state.dispose()
resetApplicationToNull(application)
}
}
private fun LegacyTestRepl.compileAndEval(codeLine: ReplCodeLine): Pair<ReplCompileResult, ReplEvalResult?> {
val compRes = replCompiler.compile(state, codeLine)
val evalRes = (compRes as? ReplCompileResult.CompiledClasses)?.let {
compiledEvaluator.eval(state, it)
}
return compRes to evalRes
}
@@ -0,0 +1,112 @@
/*
* Copyright 2010-2019 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 org.jetbrains.kotlin.cli.common.repl.BasicReplStageHistory
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.junit.Assert
import org.junit.Test
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.*
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
import kotlin.script.experimental.jvmhost.impl.KJvmReplCompilerImpl
class ReplTest : TestCase() {
companion object {
const val TEST_DATA_DIR = "libraries/scripting/jvm-host/testData"
}
@Test
fun testCompileAndEval() {
val out = captureOut {
chechEvaluateInReplNoErrors(
simpleScriptompilationConfiguration,
simpleScriptEvaluationConfiguration,
sequenceOf(
"val x = 3",
"x + 4",
"println(\"x = \$x\")"
),
sequenceOf(null, 7, null)
)
}
Assert.assertEquals("x = 3", out)
}
fun evaluateInRepl(
compilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration,
snippets: Sequence<String>
): Sequence<ResultWithDiagnostics<EvaluationResult>> {
val replCompilerProxy = KJvmReplCompilerImpl(defaultJvmScriptingHostConfiguration)
val compilationState = replCompilerProxy.createReplCompilationState(compilationConfiguration)
val compilationHistory = BasicReplStageHistory<ScriptDescriptor>()
val replEvaluator = BasicJvmScriptEvaluator()
var currentEvalConfig = evaluationConfiguration
return snippets.mapIndexed { snippetNo, snippetText ->
val snippetSource = snippetText.toScriptSource("Line_$snippetNo.simplescript.kts")
val snippetId = ReplSnippetIdImpl(snippetNo, 0, snippetSource)
replCompilerProxy.compileReplSnippet(compilationState, snippetSource, snippetId, compilationHistory)
.onSuccess {
runBlocking {
replEvaluator(it, currentEvalConfig)
}
}
.onSuccess {
val snippetInstance = when (val retVal = it.returnValue) {
is ResultValue.Value -> retVal.scriptInstance
is ResultValue.UnitValue -> retVal.scriptInstance
else -> throw IllegalStateException("Expecting value with script instance, got $it")
}
currentEvalConfig = ScriptEvaluationConfiguration(currentEvalConfig) {
previousSnippets.append(snippetInstance)
jvm {
baseClassLoader(snippetInstance::class.java.classLoader)
}
}
it.asSuccess()
}
}
}
fun chechEvaluateInReplNoErrors(
compilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration,
snippets: Sequence<String>,
expected: Sequence<Any?>
) {
val expectedIter = expected.iterator()
evaluateInRepl(compilationConfiguration, evaluationConfiguration, snippets).forEachIndexed { index, res ->
when (res) {
is ResultWithDiagnostics.Failure -> Assert.fail("#$index: Expected result, got $res")
is ResultWithDiagnostics.Success -> {
val expectedVal = expectedIter.next()
val resVal = res.value.returnValue
if (resVal is ResultValue.Value && resVal.type.isNotBlank()) // TODO: the latter check is temporary while the result is used to return the instance too
Assert.assertEquals("#$index: Expected $expectedVal, got $resVal", expectedVal, resVal.value)
else
Assert.assertTrue("#$index: Expected $expectedVal, got Unit", expectedVal == null)
}
}
}
}
}
@KotlinScript(fileExtension = "simplescript.kts")
abstract class SimpleScript
val simpleScriptompilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScript> {
jvm {
dependenciesFromCurrentContext(wholeClasspath = true)
}
}
val simpleScriptEvaluationConfiguration = ScriptEvaluationConfiguration()
@@ -526,9 +526,9 @@ private class FileBasedScriptCache(val baseDir: File) : ScriptingCacheWithCounte
get() = _retrievedScripts
}
private fun captureOut(body: () -> Unit): String = captureOutAndErr(body).first
internal fun captureOut(body: () -> Unit): String = captureOutAndErr(body).first
private fun captureOutAndErr(body: () -> Unit): Pair<String, String> {
internal fun captureOutAndErr(body: () -> Unit): Pair<String, String> {
val outStream = ByteArrayOutputStream()
val errStream = ByteArrayOutputStream()
val prevOut = System.out
@@ -43,7 +43,15 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
?.resultOrNull()
?: configuration
scriptClass.evalWithConfigAndOtherScriptsResults(refinedEvalConfiguration, importedScriptsEvalResults).let {
val instance =
scriptClass.evalWithConfigAndOtherScriptsResults(refinedEvalConfiguration, importedScriptsEvalResults)
val resultValue = compiledScript.resultField?.let { (resultFieldName, resultType) ->
val resultField = scriptClass.java.getDeclaredField(resultFieldName).apply { isAccessible = true }
ResultValue.Value(resultFieldName, resultField.get(instance), resultType.typeName, instance)
} ?: ResultValue.Value("", instance, "", instance)
EvaluationResult(resultValue, refinedEvalConfiguration).let {
sharedScripts?.put(scriptClass, it)
ResultWithDiagnostics.Success(it)
}
@@ -61,9 +69,15 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
private fun KClass<*>.evalWithConfigAndOtherScriptsResults(
refinedEvalConfiguration: ScriptEvaluationConfiguration,
importedScriptsEvalResults: List<EvaluationResult>
): EvaluationResult {
): Any {
val args = ArrayList<Any?>()
refinedEvalConfiguration[ScriptEvaluationConfiguration.previousSnippets]?.let {
if (it.isNotEmpty()) {
args.add(it.toTypedArray())
}
}
refinedEvalConfiguration[ScriptEvaluationConfiguration.constructorArgs]?.let {
args.addAll(it)
}
@@ -81,10 +95,7 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
val ctor = java.constructors.single()
val instance = ctor.newInstance(*args.toArray())
return EvaluationResult(
ResultValue.Value("", instance, "", instance),
refinedEvalConfiguration
)
return instance
}
}
@@ -16,6 +16,7 @@ internal class KJvmCompiledScriptData(
var sourceLocationId: String?,
var compilationConfiguration: ScriptCompilationConfiguration,
var scriptClassFQName: String,
var resultField: Pair<String, KotlinType>?,
var otherScripts: List<CompiledScript<*>> = emptyList()
) : Serializable {
@@ -24,6 +25,7 @@ internal class KJvmCompiledScriptData(
outputStream.writeObject(sourceLocationId)
outputStream.writeObject(otherScripts)
outputStream.writeObject(scriptClassFQName)
outputStream.writeObject(resultField)
}
@Suppress("UNCHECKED_CAST")
@@ -32,11 +34,12 @@ internal class KJvmCompiledScriptData(
sourceLocationId = inputStream.readObject() as String?
otherScripts = inputStream.readObject() as List<CompiledScript<*>>
scriptClassFQName = inputStream.readObject() as String
resultField = inputStream.readObject() as Pair<String, KotlinType>?
}
companion object {
@JvmStatic
private val serialVersionUID = 3L
private val serialVersionUID = 4L
}
}
@@ -49,9 +52,13 @@ class KJvmCompiledScript<out ScriptBase : Any> internal constructor(
sourceLocationId: String?,
compilationConfiguration: ScriptCompilationConfiguration,
scriptClassFQName: String,
resultField: Pair<String, KotlinType>?,
otherScripts: List<CompiledScript<*>> = emptyList(),
compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept
): this(KJvmCompiledScriptData(sourceLocationId, compilationConfiguration, scriptClassFQName, otherScripts), compiledModule)
) : this(
KJvmCompiledScriptData(sourceLocationId, compilationConfiguration, scriptClassFQName, resultField, otherScripts),
compiledModule
)
override val sourceLocationId: String?
get() = data.sourceLocationId
@@ -65,6 +72,9 @@ class KJvmCompiledScript<out ScriptBase : Any> internal constructor(
val scriptClassFQName: String
get() = data.scriptClassFQName
override val resultField: Pair<String, KotlinType>?
get() = data.resultField
override suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?): ResultWithDiagnostics<KClass<*>> = try {
// ensuring proper defaults are used
val actualEvaluationConfiguration = scriptEvaluationConfiguration ?: ScriptEvaluationConfiguration()
@@ -168,7 +178,8 @@ fun KJvmCompiledScript<*>.toBytes(): ByteArray {
} finally {
try {
oos?.close()
} catch (e: IOException) {}
} catch (e: IOException) {
}
}
}
@@ -115,7 +115,8 @@ open class GenericReplCompiler(
classpathAddendum ?: emptyList(),
generationState.replSpecific.resultType?.let {
DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(it)
}
},
null
)
}
}