diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompiledEvaluator.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompiledEvaluator.kt deleted file mode 100644 index e56465d2feb..00000000000 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompiledEvaluator.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2010-2016 JetBrains s.r.o. - * - * 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 org.jetbrains.kotlin.cli.common.repl - -import org.jetbrains.kotlin.resolve.jvm.JvmClassName -import java.io.File -import java.net.URLClassLoader -import java.util.concurrent.locks.ReentrantReadWriteLock -import kotlin.concurrent.read -import kotlin.concurrent.write - -open class GenericReplCompiledEvaluator(baseClasspath: Iterable, - baseClassloader: ClassLoader?, - val scriptArgs: Array? = null, - val scriptArgsTypes: Array>? = null -) : ReplCompiledEvaluator { - - private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath) - private val classLoaderLock = ReentrantReadWriteLock() - - // TODO: consider to expose it as a part of (evaluator, invoker) interface - private val evalStateLock = ReentrantReadWriteLock() - - private val compiledLoadedClassesHistory = ReplHistory() - - override fun eval(codeLine: ReplCodeLine, - history: List, - compiledClasses: List, - hasResult: Boolean, - classpathAddendum: List, - invokeWrapper: InvokeWrapper? - ): ReplEvalResult = evalStateLock.write { - checkAndUpdateReplHistoryCollection(compiledLoadedClassesHistory, history)?.let { - return@eval ReplEvalResult.HistoryMismatch(compiledLoadedClassesHistory.lines, it) - } - - var mainLineClassName: String? = null - - fun classNameFromPath(path: String) = JvmClassName.byInternalName(path.replaceFirst("\\.class$".toRegex(), "")) - - fun processCompiledClasses() { - val expectedClassName = "Line${codeLine.no}" - compiledClasses.filter { it.path.endsWith(".class") } - .forEach { - val className = classNameFromPath(it.path) - if (className.internalName == expectedClassName || className.internalName.endsWith("/$expectedClassName")) { - mainLineClassName = className.internalName.replace('/', '.') - } - classLoader.addClass(className, it.bytes) - } - } - - classLoaderLock.read { - if (classpathAddendum.isNotEmpty()) { - classLoaderLock.write { - classLoader = makeReplClassLoader(classLoader, classpathAddendum) - } - } - processCompiledClasses() - } - - fun compiledClassesNames() = compiledClasses.map { classNameFromPath(it.path).fqNameForClassNameWithoutDollars.asString() } - - val scriptClass = classLoaderLock.read { - try { - classLoader.loadClass(mainLineClassName!!) - } - catch (e: Throwable) { - return ReplEvalResult.Error.Runtime(compiledLoadedClassesHistory.lines, "Error loading class $mainLineClassName: known classes: ${compiledClassesNames()}", - e as? Exception) - } - } - - fun getConstructorParams(): Array> = - (compiledLoadedClassesHistory.values.map { it.klass.java } + - (scriptArgs?.mapIndexed { i, it -> scriptArgsTypes?.getOrNull(i) ?: it?.javaClass ?: Any::class.java } ?: emptyList()) - ).toTypedArray() - fun getConstructorArgs() = (compiledLoadedClassesHistory.values.map { it.instance } + scriptArgs.orEmpty()).toTypedArray() - - val constructorParams: Array> = getConstructorParams() - val constructorArgs: Array = getConstructorArgs() - - val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams) - val scriptInstance = - try { - invokeWrapper?.invoke { scriptInstanceConstructor.newInstance(*constructorArgs) } ?: scriptInstanceConstructor.newInstance(*constructorArgs) - } - catch (e: Throwable) { - // ignore everything in the stack trace until this constructor call - return ReplEvalResult.Error.Runtime(compiledLoadedClassesHistory.lines, renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}."), e as? Exception) - } - - compiledLoadedClassesHistory.add(codeLine, ClassWithInstance(scriptClass.kotlin, scriptInstance)) - - val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true } - val rv: Any? = rvField.get(scriptInstance) - - return if (hasResult) ReplEvalResult.ValueResult(compiledLoadedClassesHistory.lines, rv) else ReplEvalResult.UnitResult(compiledLoadedClassesHistory.lines) - } - - override val lastEvaluatedScript: ClassWithInstance? get() = evalStateLock.read { compiledLoadedClassesHistory.values.lastOrNull() } - - companion object { - private val SCRIPT_RESULT_FIELD_NAME = "\$\$result" - } -} - -private fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable) = - ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader)) - diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt new file mode 100644 index 00000000000..dfaa8bf2ee0 --- /dev/null +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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 org.jetbrains.kotlin.cli.common.repl + + +import java.io.File +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.write + +class GenericReplCompilingEvaluator(val compiler: ReplCompiler, + baseClasspath: Iterable, + baseClassloader: ClassLoader?, + protected val fallbackScriptArgs: ScriptArgsWithTypes? = null, + repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT, + protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplFullEvaluator { + val evaluator = GenericReplEvaluator(baseClasspath, baseClassloader, fallbackScriptArgs, repeatingMode, stateLock) + + override fun compileAndEval(codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, verifyHistory: List?, invokeWrapper: InvokeWrapper?): ReplEvalResult { + return stateLock.write { + @Suppress("DEPRECATION") + val compiled = compiler.compile(codeLine, verifyHistory) + when (compiled) { + is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(compiled.compiledHistory, compiled.message, compiled.location) + is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(compiled.compiledHistory, compiled.lineNo) + is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(compiled.compiledHistory) + is ReplCompileResult.CompiledClasses -> { + @Suppress("DEPRECATION") + val result = eval(compiled, scriptArgs, invokeWrapper) + when (result) { + is ReplEvalResult.Error, + is ReplEvalResult.HistoryMismatch, + is ReplEvalResult.Incomplete -> { + result.completedEvalHistory.lastOrNull()?.let { compiler.resetToLine(it) } + result + } + is ReplEvalResult.ValueResult, + is ReplEvalResult.UnitResult -> + result + else -> throw IllegalStateException("Unknown evaluator result type ${compiled}") + } + } + else -> throw IllegalStateException("Unknown compiler result type ${compiled}") + } + } + } + + override fun resetToLine(lineNumber: Int): List { + stateLock.write { + val removedCompiledLines = compiler.resetToLine(lineNumber) + val removedEvaluatorLines = evaluator.resetToLine(lineNumber) + + removedCompiledLines.zip(removedEvaluatorLines).forEach { + if (it.first != it.second) { + throw IllegalStateException("History mistmatch when resetting lines") + } + } + + return removedCompiledLines + } + } + + override fun resetToLine(line: ReplCodeLine): List = resetToLine(line.no) + + + override val lastEvaluatedScripts: List get() = evaluator.lastEvaluatedScripts + override val history: List get() = evaluator.history + override val currentClasspath: List get() = evaluator.currentClasspath + + override val compiledHistory: List get() = compiler.history + override val evaluatedHistory: List get() = evaluator.history + + override fun eval(compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult { + return evaluator.eval(compileResult, scriptArgs, invokeWrapper) + } + + override fun check(codeLine: ReplCodeLine): ReplCheckResult { + return compiler.check(codeLine) + } + + override fun compileToEvaluable(codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes?, verifyHistory: List?): Pair { + val compiled = compiler.compile(codeLine, verifyHistory) + return if (compiled is ReplCompileResult.CompiledClasses) { + Pair(compiled, DelayedEvaluation(compiled, stateLock, evaluator, defaultScriptArgs ?: fallbackScriptArgs)) + } + else { + Pair(compiled, null) + } + } + + class DelayedEvaluation(override val compiledCode: ReplCompileResult.CompiledClasses, + private val stateLock: ReentrantReadWriteLock, + private val evaluator: ReplEvaluator, + private val defaultScriptArgs: ScriptArgsWithTypes?) : Evaluable { + override fun eval(scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult { + return stateLock.write { evaluator.eval(compiledCode, scriptArgs ?: defaultScriptArgs, invokeWrapper) } + } + } +} \ No newline at end of file diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplEvaluator.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplEvaluator.kt new file mode 100644 index 00000000000..ff5e0e27565 --- /dev/null +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplEvaluator.kt @@ -0,0 +1,209 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * 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 org.jetbrains.kotlin.cli.common.repl + +import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import java.io.File +import java.net.URLClassLoader +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write + +open class GenericReplEvaluator(baseClasspath: Iterable, + baseClassloader: ClassLoader?, + protected val fallbackScriptArgs: ScriptArgsWithTypes? = null, + protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT, + protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplEvaluator { + + private val topClassLoader: ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath) + + private val evaluatedHistory = ReplHistory() + + override fun resetToLine(lineNumber: Int): List { + return stateLock.write { + evaluatedHistory.resetToLine(lineNumber) + }.map { it.first } + } + + override val history: List get() = stateLock.read { evaluatedHistory.copySources() } + + override val currentClasspath: List get() = stateLock.read { + evaluatedHistory.copyValues().lastOrNull()?.let { it.classLoader.listAllUrlsAsFiles() } + ?: topClassLoader.listAllUrlsAsFiles() + } + + private class HistoryActions(val effectiveHistory: List, + val verify: (compareHistory: SourceList?) -> Int?, + val addPlaceholder: (line: CompiledReplCodeLine, value: EvalClassWithInstanceAndLoader) -> Unit, + val removePlaceholder: (line: CompiledReplCodeLine) -> Boolean, + val addFinal: (line: CompiledReplCodeLine, value: EvalClassWithInstanceAndLoader) -> Unit, + val processClasses: (compileResult: ReplCompileResult.CompiledClasses) -> Pair>) + + private fun prependClassLoaderWithNewClasses(effectiveHistory: List, compileResult: ReplCompileResult.CompiledClasses): Pair> { + return stateLock.write { + var mainLineClassName: String? = null + val classLoader = makeReplClassLoader(effectiveHistory.lastOrNull()?.classLoader ?: topClassLoader, compileResult.classpathAddendum) + fun classNameFromPath(path: String) = JvmClassName.byInternalName(path.replaceFirst("\\.class$".toRegex(), "")) + fun compiledClassesNames() = compileResult.classes.map { classNameFromPath(it.path).fqNameForClassNameWithoutDollars.asString() } + val expectedClassName = compileResult.generatedClassname + compileResult.classes.filter { it.path.endsWith(".class") } + .forEach { + val className = classNameFromPath(it.path) + if (className.internalName == expectedClassName || className.internalName.endsWith("/$expectedClassName")) { + mainLineClassName = className.internalName.replace('/', '.') + } + classLoader.addClass(className, it.bytes) + } + + val scriptClass = try { + classLoader.loadClass(mainLineClassName!!) + } + catch (t: Throwable) { + throw Exception("Error loading class $mainLineClassName: known classes: ${compiledClassesNames()}", t) + } + Pair(classLoader, scriptClass) + } + } + + override fun eval(compileResult: ReplCompileResult.CompiledClasses, + scriptArgs: ScriptArgsWithTypes?, + invokeWrapper: InvokeWrapper?): ReplEvalResult { + stateLock.write { + val verifyHistory = compileResult.compiledHistory.dropLast(1) + val defaultHistoryActor = HistoryActions( + effectiveHistory = evaluatedHistory.copyValues(), + verify = { line -> evaluatedHistory.firstMismatchingHistory(line) }, + addPlaceholder = { line, value -> evaluatedHistory.add(line, value) }, + removePlaceholder = { line -> evaluatedHistory.removeLast(line) }, + addFinal = { line, value -> evaluatedHistory.add(line, value) }, + processClasses = { compiled -> + prependClassLoaderWithNewClasses(evaluatedHistory.copyValues(), compiled) + }) + + val historyActor: HistoryActions = when (repeatingMode) { + ReplRepeatingMode.NONE -> defaultHistoryActor + ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT -> { + val lastItem = evaluatedHistory.lastItem() + if (lastItem == null || lastItem.first.source != compileResult.compiledCodeLine.source) { + defaultHistoryActor + } + else { + val trimmedHistory = ReplHistory(evaluatedHistory.copyAll().dropLast(1)) + HistoryActions( + effectiveHistory = trimmedHistory.copyValues(), + verify = { trimmedHistory.firstMismatchingHistory(it) }, + addPlaceholder = { _, _ -> NO_ACTION() }, + removePlaceholder = { NO_ACTION_THAT_RETURNS(true) }, + addFinal = { line, value -> + evaluatedHistory.removeLast(line) + evaluatedHistory.add(line, value) + }, + processClasses = { _ -> + Pair(lastItem.second.classLoader, lastItem.second.klass.java) + }) + } + } + ReplRepeatingMode.REPEAT_ANY_PREVIOUS -> { + if (evaluatedHistory.isEmpty() || !evaluatedHistory.contains(compileResult.compiledCodeLine.source)) { + defaultHistoryActor + } + else { + val historyCopy = evaluatedHistory.copyAll() + val matchingItem = historyCopy.first { it.first.source == compileResult.compiledCodeLine.source } + val trimmedHistory = ReplHistory(evaluatedHistory.copyAll().takeWhile { it != matchingItem }) + HistoryActions( + effectiveHistory = trimmedHistory.copyValues(), + verify = { trimmedHistory.firstMismatchingHistory(it) }, + addPlaceholder = { _, _ -> NO_ACTION() }, + removePlaceholder = { NO_ACTION_THAT_RETURNS(true) }, + addFinal = { line, value -> + val extraLines = evaluatedHistory.resetToLine(line) + evaluatedHistory.removeLast(line) + evaluatedHistory.add(line, value) + extraLines.forEach { + evaluatedHistory.add(it.first, it.second) + } + }, + processClasses = { _ -> + Pair(matchingItem.second.classLoader, matchingItem.second.klass.java) + }) + } + } + } + + val firstMismatch = historyActor.verify(verifyHistory) + if (firstMismatch != null) { + return@eval ReplEvalResult.HistoryMismatch(evaluatedHistory.copySources(), firstMismatch) + } + + val (classLoader, scriptClass) = try { + historyActor.processClasses(compileResult) + } + catch (e: Exception) { + return@eval ReplEvalResult.Error.Runtime(evaluatedHistory.copySources(), + e.message!!, e) + } + + val currentScriptArgs = scriptArgs ?: fallbackScriptArgs + val useScriptArgs = currentScriptArgs?.scriptArgs + val useScriptArgsTypes = currentScriptArgs?.scriptArgsTypes?.map { it.java } + + val constructorParams: Array> = (historyActor.effectiveHistory.map { it.klass.java } + + (useScriptArgs?.mapIndexed { i, it -> useScriptArgsTypes?.getOrNull(i) ?: it?.javaClass ?: Any::class.java } ?: emptyList()) + ).toTypedArray() + val constructorArgs: Array = (historyActor.effectiveHistory.map { it.instance } + useScriptArgs.orEmpty()).toTypedArray() + + val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams) + + historyActor.addPlaceholder(compileResult.compiledCodeLine, EvalClassWithInstanceAndLoader(scriptClass.kotlin, null, classLoader, invokeWrapper)) + + val scriptInstance = + try { + if (invokeWrapper != null) invokeWrapper.invoke { scriptInstanceConstructor.newInstance(*constructorArgs) } + else scriptInstanceConstructor.newInstance(*constructorArgs) + } + catch (e: Throwable) { + historyActor.removePlaceholder(compileResult.compiledCodeLine) + + // ignore everything in the stack trace until this constructor call + return@eval ReplEvalResult.Error.Runtime(evaluatedHistory.copySources(), + renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}."), e as? Exception) + } + + historyActor.removePlaceholder(compileResult.compiledCodeLine) + historyActor.addFinal(compileResult.compiledCodeLine, EvalClassWithInstanceAndLoader(scriptClass.kotlin, scriptInstance, classLoader, invokeWrapper)) + + val resultField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true } + val resultValue: Any? = resultField.get(scriptInstance) + + return if (compileResult.hasResult) ReplEvalResult.ValueResult(evaluatedHistory.copySources(), resultValue) + else ReplEvalResult.UnitResult(evaluatedHistory.copySources()) + } + } + + override val lastEvaluatedScripts: List get() { + return stateLock.read { evaluatedHistory.copyAll() } + } + + companion object { + private val SCRIPT_RESULT_FIELD_NAME = "\$\$result" + } + + private fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable) = + ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader)) +} + diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmInvocableScriptEngine.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmInvocableScriptEngine.kt index 3c3d06a1d00..635284dbd13 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmInvocableScriptEngine.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmInvocableScriptEngine.kt @@ -17,60 +17,117 @@ package org.jetbrains.kotlin.cli.common.repl import org.jetbrains.kotlin.utils.tryCreateCallableMapping +import java.lang.reflect.Proxy import javax.script.Invocable import javax.script.ScriptException -import kotlin.reflect.* +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter import kotlin.reflect.full.functions import kotlin.reflect.full.safeCast @Suppress("unused") // used externally (kotlin.script.utils) interface KotlinJsr223JvmInvocableScriptEngine : Invocable { - val replScriptEvaluator: ReplEvaluatorBase + val replScriptEvaluator: ReplEvaluatorExposedInternalHistory - fun getInterface(klass: KClass): Any? { - val (_, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ") - return getInterface(instance, klass) - } - - fun getInterface(receiver: Any, klass: KClass): Any? { - return klass.safeCast(receiver) + private fun prioritizedHistory(receiverClass: KClass<*>?, receiverInstance: Any?): List { + return replScriptEvaluator.lastEvaluatedScripts.map { it.second }.filter { it.instance != null }.reversed().assertNotEmpty("no script ").let { history -> + if (receiverInstance != null) { + val receiverKlass = receiverClass ?: receiverInstance.javaClass.kotlin + val receiverInHistory = history.find { it.instance == receiverInstance } ?: + EvalClassWithInstanceAndLoader(receiverKlass, receiverInstance, receiverKlass.java.classLoader, history.first().invokeWrapper) + listOf(receiverInHistory) + history.filterNot { it == receiverInHistory } + } + else { + history + } + } } override fun invokeFunction(name: String?, vararg args: Any?): Any? { if (name == null) throw java.lang.NullPointerException("function name cannot be null") - val (klass, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ") - return invokeImpl(klass, instance, name, args, invokeWrapper = null) + return invokeImpl(prioritizedHistory(null, null), name, args) } override fun invokeMethod(thiz: Any?, name: String?, vararg args: Any?): Any? { if (name == null) throw java.lang.NullPointerException("method name cannot be null") if (thiz == null) throw IllegalArgumentException("cannot invoke method on the null object") - return invokeImpl(thiz.javaClass.kotlin, thiz, name, args, invokeWrapper = null) + return invokeImpl(prioritizedHistory(thiz.javaClass.kotlin, thiz), name, args) } + private fun invokeImpl(prioritizedCallOrder: List, name: String, args: Array): Any? { + // TODO: cache the method lookups? + + val (fn, mapping, invokeWrapper) = prioritizedCallOrder.asSequence().map { attempt -> + val candidates = attempt.klass.functions.filter { it.name == name } + candidates.findMapping(listOf(attempt.instance) + args)?.let { + Triple(it.first, it.second, attempt.invokeWrapper) + } + }.filterNotNull().firstOrNull() ?: throw NoSuchMethodException("no suitable function '$name' found") + + val res = try { + if (invokeWrapper != null) { + invokeWrapper.invoke { + fn.callBy(mapping) + } + } + else { + fn.callBy(mapping) + } + } + catch (e: Throwable) { + // ignore everything in the stack trace until this constructor call + throw ScriptException(renderReplStackTrace(e.cause!!, startFromMethodName = fn.name)) + } + return if (fn.returnType.classifier == Unit::class) Unit else res + } + + override fun getInterface(clasz: Class?): T? { - val (_, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ") - return getInterface(instance, clasz) + return proxyInterface(null, clasz) } override fun getInterface(thiz: Any?, clasz: Class?): T? { if (thiz == null) throw IllegalArgumentException("object cannot be null") + return proxyInterface(thiz, clasz) + } + + private fun proxyInterface(thiz: Any?, clasz: Class?): T? { + replScriptEvaluator.lastEvaluatedScripts.assertNotEmpty("no script") + val priority = prioritizedHistory(thiz?.javaClass?.kotlin, thiz) + if (clasz == null) throw IllegalArgumentException("class object cannot be null") if (!clasz.isInterface) throw IllegalArgumentException("expecting interface") - return clasz.kotlin.safeCast(thiz) + + // TODO: cache the method lookups? + + val proxy = Proxy.newProxyInstance(Thread.currentThread().contextClassLoader, arrayOf(clasz)) { _, method, args -> + invokeImpl(priority, method.name, args ?: emptyArray()) + } + return clasz.kotlin.safeCast(proxy) } } -private fun invokeImpl(receiverClass: KClass<*>, receiverInstance: Any, name: String, args: Array, invokeWrapper: InvokeWrapper?): Any? { +private fun invokeImpl(prioritizedCallOrder: List, name: String, args: Array): Any? { + // TODO: cache the method lookups? + + val (fn, mapping, invokeWrapper) = prioritizedCallOrder.asSequence().map { attempt -> + val candidates = attempt.klass.functions.filter { it.name == name } + candidates.findMapping(listOf(attempt.instance) + args)?.let { + Triple(it.first, it.second, attempt.invokeWrapper) + } + }.filterNotNull().firstOrNull() ?: throw NoSuchMethodException("no suitable function '$name' found") - val candidates = receiverClass.functions.filter { it.name == name } - val (fn, mapping) = candidates.findMapping(listOf(receiverInstance) + args) ?: - throw NoSuchMethodException("no suitable function '$name' found") val res = try { - invokeWrapper?.invoke { + if (invokeWrapper != null) { + invokeWrapper.invoke { + fn.callBy(mapping) + } + } + else { fn.callBy(mapping) - } ?: fn.callBy(mapping) + } } catch (e: Throwable) { // ignore everything in the stack trace until this constructor call diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt index 193cc5d2b53..2b6412b4597 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt @@ -18,25 +18,27 @@ package org.jetbrains.kotlin.cli.common.repl import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import java.io.Reader +import java.util.concurrent.atomic.AtomicInteger import javax.script.* val KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY = "kotlin.script.history" + // TODO consider additional error handling +@Suppress("UNCHECKED_CAST") val Bindings.kotlinScriptHistory: MutableList get() = getOrPut(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, { arrayListOf() }) as MutableList abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable { - private var lineCount = 0 + protected var codeLineNumber = AtomicInteger(0) - protected abstract val replCompiler: ReplCompiler + protected abstract val replCompiler: ReplCompileAction + protected abstract val replScriptEvaluator: ReplFullEvaluator - protected abstract val replEvaluator: ReplCompiledEvaluator + override fun eval(script: String, context: ScriptContext): Any? = compileAndEval(script, context) - override fun eval(script: String, context: ScriptContext): Any? = compile(script, context).eval(context) - - override fun eval(script: Reader, context: ScriptContext): Any? = compile(script.readText(), context).eval() + override fun eval(script: Reader, context: ScriptContext): Any? = compileAndEval(script.readText(), context) override fun compile(script: String): CompiledScript = compile(script, getContext()) @@ -46,42 +48,56 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn override fun getFactory(): ScriptEngineFactory = myFactory - open fun compile(script: String, context: ScriptContext): CompiledScript { - lineCount += 1 + fun nextCodeLine(code: String) = ReplCodeLine(codeLineNumber.incrementAndGet(), code) - val codeLine = ReplCodeLine(lineCount, script) + open fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = null + + open fun compileAndEval(script: String, context: ScriptContext): Any? { + val codeLine = nextCodeLine(script) + val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory + val result = replScriptEvaluator.compileAndEval(codeLine, scriptArgs = overrideScriptArgs(context), verifyHistory = history) + val ret = when (result) { + is ReplEvalResult.ValueResult -> result.value + is ReplEvalResult.UnitResult -> null + is ReplEvalResult.Error -> throw ScriptException(result.message) + is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code") + is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}") + } + context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, ArrayList(result.completedEvalHistory)) + return ret + } + + open fun compile(script: String, context: ScriptContext): CompiledScript { + val codeLine = nextCodeLine(script) val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory - val compileResult = replCompiler.compile(codeLine, history) - - val compiled = when (compileResult) { - is ReplCompileResult.Error -> throw ScriptException("Error${compileResult.locationString()}: ${compileResult.message}") + val result = replCompiler.compile(codeLine, history) + val compiled = when (result) { + is ReplCompileResult.Error -> throw ScriptException("Error${result.locationString()}: ${result.message}") is ReplCompileResult.Incomplete -> throw ScriptException("error: incomplete code") - is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${compileResult.lineNo}") - is ReplCompileResult.CompiledClasses -> compileResult + is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}") + is ReplCompileResult.CompiledClasses -> result } - + context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, ArrayList(result.compiledHistory)) return CompiledKotlinScript(this, codeLine, compiled) } open fun eval(compiledScript: CompiledKotlinScript, context: ScriptContext): Any? { - val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory - - val evalResult = try { - replEvaluator.eval(compiledScript.codeLine, history, compiledScript.compiledData.classes, compiledScript.compiledData.hasResult, compiledScript.compiledData.classpathAddendum) + val result = try { + replScriptEvaluator.eval(compiledScript.compiledData, scriptArgs = overrideScriptArgs(context)) } catch (e: Exception) { throw ScriptException(e) } - val ret = when (evalResult) { - is ReplEvalResult.ValueResult -> evalResult.value + val ret = when (result) { + is ReplEvalResult.ValueResult -> result.value is ReplEvalResult.UnitResult -> null - is ReplEvalResult.Error -> throw ScriptException(evalResult.message) + is ReplEvalResult.Error -> throw ScriptException(result.message) is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code") - is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${evalResult.lineNo}") + is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}") } - history.add(compiledScript.codeLine) + context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, ArrayList(result.completedEvalHistory)) return ret } diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt index d631f4d93b7..77d0e2577bf 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt @@ -28,105 +28,173 @@ data class ReplCodeLine(val no: Int, val code: String) : Serializable { } } -data class ClassWithInstance(val klass: KClass<*>, val instance: Any) - -// TODO: consider storing code hash where source is not needed +data class CompiledReplCodeLine(val className: String, val source: ReplCodeLine) : Serializable { + companion object { + private val serialVersionUID: Long = 8228307678L + } +} data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializable { override fun equals(other: Any?): Boolean = (other as? CompiledClassData)?.let { path == it.path && Arrays.equals(bytes, it.bytes) } ?: false override fun hashCode(): Int = path.hashCode() + Arrays.hashCode(bytes) + companion object { private val serialVersionUID: Long = 8228357578L } } -sealed class ReplCheckResult(val updatedHistory: List) : Serializable { - class Ok(updatedHistory: List) : ReplCheckResult(updatedHistory) - class Incomplete(updatedHistory: List) : ReplCheckResult(updatedHistory) - class Error(updatedHistory: List, - val message: String, - val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION - ) : ReplCheckResult(updatedHistory) - { + +interface ReplCheckAction { + fun check(codeLine: ReplCodeLine): ReplCheckResult +} + +sealed class ReplCheckResult : Serializable { + class Ok : ReplCheckResult() + + class Incomplete : ReplCheckResult() + + class Error(val message: String, + val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCheckResult() { override fun toString(): String = "Error(message = \"$message\"" } + companion object { - private val serialVersionUID: Long = 8228357578L + private val serialVersionUID: Long = 8228307678L } } -sealed class ReplCompileResult(val updatedHistory: List) : Serializable { - class CompiledClasses(updatedHistory: List, +interface ReplResettableCodeLine { + fun resetToLine(lineNumber: Int): List + + fun resetToLine(line: ReplCodeLine): List = resetToLine(line.no) +} + +interface ReplCodeLineHistory { + val history: List +} + +interface ReplCombinedHistory { + val compiledHistory: List + val evaluatedHistory: List +} + +interface ReplCompileAction { + fun compile(codeLine: ReplCodeLine, verifyHistory: List? = null): ReplCompileResult +} + +sealed class ReplCompileResult(val compiledHistory: List) : Serializable { + class CompiledClasses(compiledHistory: List, + val compiledCodeLine: CompiledReplCodeLine, + val generatedClassname: String, val classes: List, val hasResult: Boolean, - val classpathAddendum: List - ) : ReplCompileResult(updatedHistory) - class Incomplete(updatedHistory: List) : ReplCompileResult(updatedHistory) - class HistoryMismatch(updatedHistory: List, val lineNo: Int): ReplCompileResult(updatedHistory) - class Error(updatedHistory: List, + val classpathAddendum: List) : ReplCompileResult(compiledHistory) + + class Incomplete(compiledHistory: List) : ReplCompileResult(compiledHistory) + + class HistoryMismatch(compiledHistory: List, val lineNo: Int) : ReplCompileResult(compiledHistory) + + class Error(compiledHistory: List, val message: String, - val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION - ) : ReplCompileResult(updatedHistory) - { + val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCompileResult(compiledHistory) { override fun toString(): String = "Error(message = \"$message\"" } + companion object { - private val serialVersionUID: Long = 8228357578L + private val serialVersionUID: Long = 8228307678L } } -sealed class ReplEvalResult(val updatedHistory: List) : Serializable { - class ValueResult(updatedHistory: List, val value: Any?) : ReplEvalResult(updatedHistory) { +interface ReplCompiler : ReplResettableCodeLine, ReplCodeLineHistory, ReplCompileAction, ReplCheckAction + +typealias EvalHistoryType = Pair + +interface ReplEvaluatorExposedInternalHistory { + val lastEvaluatedScripts: List +} + +interface ReplClasspath { + val currentClasspath: List +} + +data class EvalClassWithInstanceAndLoader(val klass: KClass<*>, val instance: Any?, val classLoader: ClassLoader, val invokeWrapper: InvokeWrapper?) + +interface ReplEvalAction { + fun eval(compileResult: ReplCompileResult.CompiledClasses, + scriptArgs: ScriptArgsWithTypes? = null, + invokeWrapper: InvokeWrapper? = null): ReplEvalResult +} + +sealed class ReplEvalResult(val completedEvalHistory: List) : Serializable { + class ValueResult(completedEvalHistory: List, val value: Any?) : ReplEvalResult(completedEvalHistory) { override fun toString(): String = "Result: $value" } - class UnitResult(updatedHistory: List) : ReplEvalResult(updatedHistory) - class Incomplete(updatedHistory: List) : ReplEvalResult(updatedHistory) - class HistoryMismatch(updatedHistory: List, val lineNo: Int): ReplEvalResult(updatedHistory) - sealed class Error(updatedHistory: List, val message: String) : ReplEvalResult(updatedHistory) { - class Runtime(updatedHistory: List, message: String, val cause: Exception? = null) : Error(updatedHistory, message) - class CompileTime(updatedHistory: List, + + class UnitResult(completedEvalHistory: List) : ReplEvalResult(completedEvalHistory) + + class Incomplete(completedEvalHistory: List) : ReplEvalResult(completedEvalHistory) + + class HistoryMismatch(completedEvalHistory: List, val lineNo: Int) : ReplEvalResult(completedEvalHistory) + + sealed class Error(completedEvalHistory: List, val message: String) : ReplEvalResult(completedEvalHistory) { + class Runtime(completedEvalHistory: List, message: String, val cause: Exception? = null) : Error(completedEvalHistory, message) + + class CompileTime(completedEvalHistory: List, message: String, - val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION - ) : Error(updatedHistory, message) + val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(completedEvalHistory, message) + override fun toString(): String = "${this::class.simpleName}Error(message = \"$message\"" } + companion object { - private val serialVersionUID: Long = 8228357578L + private val serialVersionUID: Long = 8228307678L } } -interface ReplChecker { - fun check(codeLine: ReplCodeLine, history: List): ReplCheckResult +interface ReplEvaluator : ReplResettableCodeLine, ReplCodeLineHistory, ReplEvaluatorExposedInternalHistory, ReplEvalAction, ReplClasspath + +interface ReplAtomicEvalAction { + fun compileAndEval(codeLine: ReplCodeLine, + scriptArgs: ScriptArgsWithTypes? = null, + verifyHistory: List? = null, + invokeWrapper: InvokeWrapper? = null): ReplEvalResult } -interface ReplCompiler : ReplChecker { - fun compile(codeLine: ReplCodeLine, history: List): ReplCompileResult +interface ReplAtomicEvaluator : ReplResettableCodeLine, ReplCombinedHistory, ReplEvaluatorExposedInternalHistory, ReplAtomicEvalAction, ReplCheckAction, ReplClasspath + +interface ReplDelayedEvalAction { + fun compileToEvaluable(codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes? = null, verifyHistory: List?): Pair } -interface ReplEvaluatorBase { - val lastEvaluatedScript: ClassWithInstance? +interface Evaluable { + val compiledCode: ReplCompileResult.CompiledClasses + fun eval(scriptArgs: ScriptArgsWithTypes? = null, invokeWrapper: InvokeWrapper? = null): ReplEvalResult } -interface ReplCompiledEvaluator : ReplEvaluatorBase { +interface ReplFullEvaluator : ReplEvaluator, ReplAtomicEvaluator, ReplDelayedEvalAction, ReplCombinedHistory - fun eval(codeLine: ReplCodeLine, - history: List, - compiledClasses: List, - hasResult: Boolean, - classpathAddendum: List, - invokeWrapper: InvokeWrapper? = null - ): ReplEvalResult +/** + * Keep args and arg types together, so as a whole they are present or absent + */ +class ScriptArgsWithTypes(val scriptArgs: Array, val scriptArgsTypes: Array>) : Serializable { + companion object { + private val serialVersionUID: Long = 8529357500L + } } - -interface ReplEvaluator : ReplChecker, ReplEvaluatorBase { - - fun eval(codeLine: ReplCodeLine, - history: List, - invokeWrapper: InvokeWrapper? = null - ): ReplEvalResult +interface ScriptTemplateEmptyArgsProvider { + val defaultEmptyArgs: ScriptArgsWithTypes? } +class SimpleScriptTemplateEmptyArgsProvider(override val defaultEmptyArgs: ScriptArgsWithTypes? = null) : ScriptTemplateEmptyArgsProvider + +enum class ReplRepeatingMode { + NONE, + REPEAT_ONLY_MOST_RECENT, + REPEAT_ANY_PREVIOUS +} + + interface InvokeWrapper { - operator fun invoke(body: () -> T): T // e.g. for capturing io + operator fun invoke(body: () -> T): T // e.g. for capturing io } \ No newline at end of file diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplHistory.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplHistory.kt new file mode 100644 index 00000000000..10bebc7aa08 --- /dev/null +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplHistory.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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 org.jetbrains.kotlin.cli.common.repl + +import java.io.Serializable +import java.util.concurrent.ConcurrentLinkedDeque + + +typealias CompiledHistoryItem = Pair +typealias SourceHistoryItem = Pair + +typealias CompiledHistoryStorage = ConcurrentLinkedDeque> +typealias CompiledHistoryList = List> +typealias SourceHistoryList = List> +typealias SourceList = List + +/* + Not thread safe, the caller is assumed to lock access. Even though ConcurrentLinkedDeque is used, there are mutliple + actions that require locking. + */ +class ReplHistory(startingHistory: CompiledHistoryList = emptyList()) : Serializable { + private val history: CompiledHistoryStorage = ConcurrentLinkedDeque(startingHistory) + + fun isEmpty(): Boolean = history.isEmpty() + fun isNotEmpty(): Boolean = history.isNotEmpty() + + fun add(line: CompiledReplCodeLine, value: T) { + history.add(line to value) + } + + /* remove last line only if it is the line we think it is */ + fun removeLast(line: CompiledReplCodeLine): Boolean { + return if (history.peekLast().first == line) { + history.removeLast() + true + } + else { + false + } + } + + /* resets back to a previous line number and returns the lines removed */ + fun resetToLine(lineNumber: Int): SourceHistoryList { + val removed = arrayListOf>() + while ((history.peekLast()?.first?.source?.no ?: -1) > lineNumber) { + removed.add(history.removeLast().let { Pair(it.first.source, it.second) }) + } + return removed.reversed() + } + + fun resetToLine(line: ReplCodeLine): SourceHistoryList { + return resetToLine(line.no) + } + + fun resetToLine(line: CompiledReplCodeLine): CompiledHistoryList { + val removed = arrayListOf>() + while ((history.peekLast()?.first?.source?.no ?: -1) > line.source.no) { + removed.add(history.removeLast()) + } + return removed.reversed() + } + + fun contains(line: ReplCodeLine): Boolean = history.any { it.first.source == line } + fun contains(line: CompiledReplCodeLine): Boolean = history.any { it.first == line } + + fun lastItem(): CompiledHistoryItem? = history.peekLast() + fun lastCodeLine(): CompiledReplCodeLine? = lastItem()?.first + fun lastValue(): T? = lastItem()?.second + + fun checkHistoryIsInSync(compareHistory: SourceList?): Boolean { + return firstMismatchingHistory(compareHistory) == null + } + + // return from the compareHistory the first line that does not match or null + fun firstMismatchingHistory(compareHistory: SourceList?): Int? { + if (compareHistory == null) return null + + val firstMismatch = history.zip(compareHistory).firstOrNull { it.first.first.source != it.second }?.second?.no + if (compareHistory.size == history.size) return firstMismatch + if (compareHistory.size > history.size) return compareHistory[history.size].no + return history.toList()[compareHistory.size].first.source.no + } + + fun copySources(): SourceList = history.map { it.first.source } + fun copyValues(): List = history.map { it.second } + fun copyAll(): CompiledHistoryList = history.toList() + + companion object { + private val serialVersionUID: Long = 8328353000L + } +} diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/replUtil.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/replUtil.kt index bca4f6f9bbc..a505b3aa1eb 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/replUtil.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/replUtil.kt @@ -17,44 +17,11 @@ package org.jetbrains.kotlin.cli.common.repl import com.google.common.base.Throwables -import java.io.Serializable +import java.io.File +import java.net.URLClassLoader -// TODO: thread safety!! -data class ReplHistory( - val lines: MutableList = arrayListOf(), - val values: MutableList = arrayListOf() -) : Serializable -{ - init { assert(isValid()) } - fun isValid() = lines.size == values.size - - fun add(line: ReplCodeLine, value: T) { - lines.add(line) - values.add(value) - } - - fun trimAt(idx: Int) { - lines.dropLast(lines.size - idx) - values.dropLast(lines.size - idx) - } - - companion object { - private val serialVersionUID: Long = 8228357578L - } -} - -fun checkAndUpdateReplHistoryCollection(history: ReplHistory, linesHistory: Iterable): Int? { - assert(history.isValid()) - val linesHistoryIt = linesHistory.iterator() - var idx = 0 - while (linesHistoryIt.hasNext()) { - val curLine = linesHistoryIt.next() - if (history.lines[idx] != curLine) return curLine.no - idx += 1 - } - history.trimAt(idx) - return null -} +fun makeSriptBaseName(codeLine: ReplCodeLine, generation: Long) = + "Line_${codeLine.no}" + if (generation > 1) "_gen_${generation}" else "" fun renderReplStackTrace(cause: Throwable, startFromMethodName: String): String { val newTrace = arrayListOf() @@ -76,3 +43,22 @@ fun renderReplStackTrace(cause: Throwable, startFromMethodName: String): String return Throwables.getStackTraceAsString(cause) } +fun NO_ACTION(): Unit = Unit +fun NO_ACTION_THAT_RETURNS(v: T): T = v + + +internal fun ClassLoader.listAllUrlsAsFiles(): List { + val parents = generateSequence(this) { loader -> loader.parent }.filterIsInstance(URLClassLoader::class.java) + return parents.fold(emptyList()) { accum, loader -> + loader.listLocalUrlsAsFiles() + accum + }.distinct() +} + +internal fun URLClassLoader.listLocalUrlsAsFiles(): List { + return this.urLs.map { it.toString().removePrefix("file:") }.filterNotNull().map { File(it) } +} + +internal fun List.assertNotEmpty(error: String): List { + if (this.isEmpty()) throw IllegalStateException(error) + return this +} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt index a34414b51d2..8ebe861c283 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt @@ -17,43 +17,71 @@ package org.jetbrains.kotlin.cli.jvm.repl import com.intellij.openapi.Disposable -import com.intellij.openapi.diagnostic.Logger import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.repl.* import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.script.KotlinScriptDefinition +import java.io.File +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.locks.ReentrantReadWriteLock -private val logger = Logger.getInstance(GenericRepl::class.java) - -open class GenericRepl( +open class GenericRepl protected constructor( disposable: Disposable, scriptDefinition: KotlinScriptDefinition, compilerConfiguration: CompilerConfiguration, messageCollector: MessageCollector, baseClassloader: ClassLoader?, - scriptArgs: Array? = null, - scriptArgsTypes: Array>? = null -) : ReplEvaluator, GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector) { + protected val fallbackScriptArgs: ScriptArgsWithTypes?, + protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.NONE, + protected val stateLock: ReentrantReadWriteLock +) : ReplCompiler, ReplEvaluator, ReplAtomicEvaluator { + constructor ( + disposable: Disposable, + scriptDefinition: KotlinScriptDefinition, + compilerConfiguration: CompilerConfiguration, + messageCollector: MessageCollector, + baseClassloader: ClassLoader?, + fallbackScriptArgs: ScriptArgsWithTypes? = null + ) : this(disposable, scriptDefinition, compilerConfiguration, messageCollector, baseClassloader, fallbackScriptArgs, stateLock = ReentrantReadWriteLock()) - private val compiledEvaluator = GenericReplCompiledEvaluator(compilerConfiguration.jvmClasspathRoots, baseClassloader, scriptArgs, scriptArgsTypes) + protected val compiler: ReplCompiler by lazy { GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector, stateLock) } + protected val evaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(compiler, compilerConfiguration.jvmClasspathRoots, baseClassloader, fallbackScriptArgs, repeatingMode, stateLock) } + protected var codeLineNumber = AtomicInteger(0) - override val lastEvaluatedScript: ClassWithInstance? get() = compiledEvaluator.lastEvaluatedScript + override fun resetToLine(lineNumber: Int): List { + return evaluator.resetToLine(lineNumber) + } - @Synchronized - override fun eval(codeLine: ReplCodeLine, history: List, invokeWrapper: InvokeWrapper?): ReplEvalResult = - compileAndEval(this, compiledEvaluator, codeLine, history, invokeWrapper) -} + override fun resetToLine(line: ReplCodeLine): List { + return evaluator.resetToLine(line) + } + override val history: List get() = evaluator.history + override val compiledHistory: List get() = compiler.history + override val evaluatedHistory: List get() = evaluator.history -fun compileAndEval(replCompiler: ReplCompiler, replCompiledEvaluator: ReplCompiledEvaluator, codeLine: ReplCodeLine, history: List, invokeWrapper: InvokeWrapper?): ReplEvalResult = - replCompiler.compile(codeLine, history).let { - when (it) { - is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(it.updatedHistory) - is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(it.updatedHistory, it.lineNo) - is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(it.updatedHistory, it.message, it.location) - is ReplCompileResult.CompiledClasses -> replCompiledEvaluator.eval(codeLine, history, it.classes, it.hasResult, it.classpathAddendum, invokeWrapper) - } - } + override val currentClasspath: List + get() = evaluator.currentClasspath + override val lastEvaluatedScripts: List + get() = evaluator.lastEvaluatedScripts + override fun check(codeLine: ReplCodeLine): ReplCheckResult { + return compiler.check(codeLine) + } + + override fun compile(codeLine: ReplCodeLine, verifyHistory: List?): ReplCompileResult { + return compiler.compile(codeLine, verifyHistory) + } + + override fun eval(compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult { + return evaluator.eval(compileResult, scriptArgs, invokeWrapper) + } + + override fun compileAndEval(codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, verifyHistory: List?, invokeWrapper: InvokeWrapper?): ReplEvalResult { + return evaluator.compileAndEval(codeLine, scriptArgs, verifyHistory, invokeWrapper) + } + + fun nextCodeLine(code: String) = ReplCodeLine(codeLineNumber.incrementAndGet(), code) +} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/CliReplAnalyzerEngine.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplAnalyzer.kt similarity index 55% rename from compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/CliReplAnalyzerEngine.kt rename to compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplAnalyzer.kt index c228a591d1e..35f66d848f8 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/CliReplAnalyzerEngine.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplAnalyzer.kt @@ -14,9 +14,14 @@ * limitations under the License. */ +@file:Suppress("UNUSED_PARAMETER") + package org.jetbrains.kotlin.cli.jvm.repl -import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.cli.common.repl.CompiledReplCodeLine +import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine +import org.jetbrains.kotlin.cli.common.repl.ReplHistory +import org.jetbrains.kotlin.cli.common.repl.ReplResettableCodeLine import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment @@ -30,25 +35,36 @@ import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM -import org.jetbrains.kotlin.resolve.lazy.ResolveSession +import org.jetbrains.kotlin.resolve.lazy.* import org.jetbrains.kotlin.resolve.lazy.data.KtClassLikeInfo import org.jetbrains.kotlin.resolve.lazy.declarations.* -import org.jetbrains.kotlin.resolve.repl.ReplState +import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyScriptDescriptor +import org.jetbrains.kotlin.resolve.scopes.ImportingScope +import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf +import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes import org.jetbrains.kotlin.script.ScriptPriorities +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write -class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) { +class GenericReplAnalyzer(environment: KotlinCoreEnvironment, + protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplResettableCodeLine { private val topDownAnalysisContext: TopDownAnalysisContext private val topDownAnalyzer: LazyTopDownAnalyzer private val resolveSession: ResolveSession private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory + private val replState = ResetableReplState() + val module: ModuleDescriptorImpl - private val replState = ReplState() + get() = stateLock.read { field } + val trace: BindingTraceContext = CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace() + get() = stateLock.read { field } init { // Module source scope is empty because all binary classes are in the dependency module, and all source classes are guaranteed - // to be found via ResolveSession. The latter is true as long as light classes are not needed in REPL (which is currently true - // because no symbol declared in the REPL session can be used from Java) + // to be found via ResolveSession. The latter is true as long as light classes are not needed in NONE (which is currently true + // because no symbol declared in the NONE session can be used from Java) val container = TopDownAnalyzerFacadeForJVM.createContainer( environment.project, emptyList(), @@ -78,18 +94,26 @@ class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) { } } - fun analyzeReplLine(psiFile: KtFile, priority: Int): ReplLineAnalysisResult { - topDownAnalysisContext.scripts.clear() - trace.clearDiagnostics() - - psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, priority) - - return doAnalyze(psiFile) + override fun resetToLine(lineNumber: Int): List { + return stateLock.write { replState.resetToLine(lineNumber) } } - private fun doAnalyze(linePsi: KtFile): ReplLineAnalysisResult { + override fun resetToLine(line: ReplCodeLine): List = resetToLine(line.no) + + fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult { + return stateLock.write { + topDownAnalysisContext.scripts.clear() + trace.clearDiagnostics() + + psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no) + + doAnalyze(psiFile, codeLine) + } + } + + private fun doAnalyze(linePsi: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult { scriptDeclarationFactory.setDelegateFactory(FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi))) - replState.submitLine(linePsi) + replState.submitLine(linePsi, codeLine) val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi)) @@ -100,12 +124,12 @@ class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) { val diagnostics = trace.bindingContext.diagnostics val hasErrors = diagnostics.any { it.severity == Severity.ERROR } if (hasErrors) { - replState.lineFailure(linePsi) + replState.lineFailure(linePsi, codeLine) return ReplLineAnalysisResult.WithErrors(diagnostics) } else { val scriptDescriptor = context.scripts[linePsi.script]!! - replState.lineSuccess(linePsi, scriptDescriptor) + replState.lineSuccess(linePsi, codeLine, scriptDescriptor) return ReplLineAnalysisResult.Successful(scriptDescriptor, diagnostics) } @@ -153,4 +177,60 @@ class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) { } } } + + class ResetableReplState() { + private val successfulLines = ReplHistory() + private val submittedLines = hashMapOf() + + fun resetToLine(lineNumber: Int): List { + val removed = successfulLines.resetToLine(lineNumber) + removed.forEach { submittedLines.remove(it.second.linePsi) } + return removed.map { it.first } + } + + fun resetToLine(line: ReplCodeLine): List = resetToLine(line.no) + + fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) { + val line = LineInfo.SubmittedLine(ktFile, successfulLines.lastValue()) + submittedLines[ktFile] = line + ktFile.fileScopesCustomizer = object : FileScopesCustomizer { + override fun createFileScopes(fileScopeFactory: FileScopeFactory): FileScopes { + return lineInfo(ktFile)?.let { computeFileScopes(it, fileScopeFactory) } ?: fileScopeFactory.createScopesForFile(ktFile) + } + } + } + + fun lineSuccess(ktFile: KtFile, codeLine: ReplCodeLine, scriptDescriptor: LazyScriptDescriptor) { + val successfulLine = LineInfo.SuccessfulLine(ktFile, successfulLines.lastValue(), scriptDescriptor) + submittedLines[ktFile] = successfulLine + successfulLines.add(CompiledReplCodeLine(ktFile.name, codeLine), successfulLine) + } + + fun lineFailure(ktFile: KtFile, codeLine: ReplCodeLine) { + submittedLines[ktFile] = LineInfo.FailedLine(ktFile, successfulLines.lastValue()) + } + + private fun lineInfo(ktFile: KtFile) = submittedLines[ktFile] + + // use sealed? + private sealed class LineInfo { + abstract val linePsi: KtFile + abstract val parentLine: SuccessfulLine? + + class SubmittedLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?) : LineInfo() + class SuccessfulLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?, val lineDescriptor: LazyScriptDescriptor) : LineInfo() + class FailedLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?) : LineInfo() + } + + private fun computeFileScopes(lineInfo: LineInfo, fileScopeFactory: FileScopeFactory): FileScopes? { + // create scope that wraps previous line lexical scope and adds imports from this line + val lexicalScopeAfterLastLine = lineInfo.parentLine?.lineDescriptor?.scopeForInitializerResolution ?: return null + val lastLineImports = lexicalScopeAfterLastLine.parentsWithSelf.first { it is ImportingScope } as ImportingScope + val scopesForThisLine = fileScopeFactory.createScopesForFile(lineInfo.linePsi, lastLineImports) + val combinedLexicalScopes = lexicalScopeAfterLastLine.replaceImportingScopes(scopesForThisLine.importingScope) + return FileScopes(combinedLexicalScopes, scopesForThisLine.importingScope, scopesForThisLine.importResolver) + } + } } + + diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplChecker.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplChecker.kt index ae968dc613f..b9b97753302 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplChecker.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplChecker.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.cli.jvm.repl + import com.intellij.openapi.Disposable import com.intellij.openapi.vfs.CharsetToolkit import com.intellij.psi.PsiFileFactory @@ -25,8 +26,8 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult -import org.jetbrains.kotlin.cli.common.repl.ReplChecker import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine +import org.jetbrains.kotlin.cli.common.repl.makeSriptBaseName import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.repl.messages.DiagnosticMessageHolder @@ -37,14 +38,18 @@ import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.parsing.KotlinParserDefinition import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.script.KotlinScriptDefinition +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write open class GenericReplChecker( disposable: Disposable, val scriptDefinition: KotlinScriptDefinition, val compilerConfiguration: CompilerConfiguration, - messageCollector: MessageCollector -) : ReplChecker { - protected val environment = run { + messageCollector: MessageCollector, + protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock() +) { + internal val environment = run { compilerConfiguration.apply { add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition) put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector) @@ -53,41 +58,43 @@ open class GenericReplChecker( KotlinCoreEnvironment.createForProduction(disposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES) } - protected val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl + private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl // "line" - is the unit of evaluation here, could in fact consists of several character lines - protected class LineState( + internal class LineState( val codeLine: ReplCodeLine, val psiFile: KtFile, val errorHolder: DiagnosticMessageHolder) - protected var lineState: LineState? = null + private var _lineState: LineState? = null + + internal val lineState: LineState? get() = stateLock.read { _lineState } fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder() - @Synchronized - override fun check(codeLine: ReplCodeLine, history: List): ReplCheckResult { - val virtualFile = - LightVirtualFile("line${codeLine.no}${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, codeLine.code).apply { - charset = CharsetToolkit.UTF8_CHARSET - } - val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile? - ?: error("Script file not analyzed at line ${codeLine.no}: ${codeLine.code}") + fun check(codeLine: ReplCodeLine, generation: Long): ReplCheckResult { + stateLock.write { + val scriptFileName = makeSriptBaseName(codeLine, generation) + val virtualFile = + LightVirtualFile("${scriptFileName}${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, codeLine.code).apply { + charset = CharsetToolkit.UTF8_CHARSET + } + val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile? + ?: error("Script file not analyzed at line ${codeLine.no}: ${codeLine.code}") - val errorHolder = createDiagnosticHolder() + val errorHolder = createDiagnosticHolder() - val syntaxErrorReport = AnalyzerWithCompilerReport.Companion.reportSyntaxErrors(psiFile, errorHolder) + val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder) - if (!syntaxErrorReport.isHasErrors) { - lineState = LineState(codeLine, psiFile, errorHolder) - } + if (!syntaxErrorReport.isHasErrors) { + _lineState = LineState(codeLine, psiFile, errorHolder) + } - return when { - syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete(history) - syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(history, errorHolder.renderedDiagnostics) - else -> ReplCheckResult.Ok(history) + return when { + syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete() + syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(errorHolder.renderedDiagnostics) + else -> ReplCheckResult.Ok() + } } } } - - diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplCompiler.kt index ab914580553..563dbbaea6d 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplCompiler.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.cli.jvm.repl + import com.intellij.openapi.Disposable import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.cli.common.messages.MessageCollector @@ -29,78 +30,115 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.KotlinScriptExternalDependencies import java.io.File +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write -open class GenericReplCompiler( - disposable: Disposable, - scriptDefinition: KotlinScriptDefinition, - compilerConfiguration: CompilerConfiguration, - messageCollector: MessageCollector -) : ReplCompiler, GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector) { - private val analyzerEngine = CliReplAnalyzerEngine(environment) +open class GenericReplCompiler(disposable: Disposable, + protected val scriptDefinition: KotlinScriptDefinition, + protected val compilerConfiguration: CompilerConfiguration, + messageCollector: MessageCollector, + protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplCompiler { + private val checker = GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector, stateLock) + + private val analyzerEngine = GenericReplAnalyzer(checker.environment, stateLock) private var lastDependencies: KotlinScriptExternalDependencies? = null private val descriptorsHistory = ReplHistory() - @Synchronized - override fun compile(codeLine: ReplCodeLine, history: List): ReplCompileResult { - checkAndUpdateReplHistoryCollection(descriptorsHistory, history)?.let { - return@compile ReplCompileResult.HistoryMismatch(descriptorsHistory.lines, it) - } + private val generation = AtomicLong(1) - val (psiFile, errorHolder) = run { - if (lineState == null || lineState!!.codeLine != codeLine) { - val res = check(codeLine, history) - when (res) { - is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete(res.updatedHistory) - is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.updatedHistory, res.message, res.location) - is ReplCheckResult.Ok -> {} // continue + override fun resetToLine(lineNumber: Int): List { + return stateLock.write { + generation.incrementAndGet() + val removedCompiledLines = descriptorsHistory.resetToLine(lineNumber) + val removedAnalyzedLines = analyzerEngine.resetToLine(lineNumber) + + removedCompiledLines.zip(removedAnalyzedLines).forEach { + if (it.first.first != it.second) { + throw IllegalStateException("History mistmatch when resetting lines") } } - Pair(lineState!!.psiFile, lineState!!.errorHolder) + + removedCompiledLines + }.map { it.first } + } + + override val history: List get() = stateLock.read { descriptorsHistory.copySources() } + + override fun check(codeLine: ReplCodeLine): ReplCheckResult { + return checker.check(codeLine, generation.get()) + } + + override fun compile(codeLine: ReplCodeLine, verifyHistory: List?): ReplCompileResult { + stateLock.write { + val firstMismatch = descriptorsHistory.firstMismatchingHistory(verifyHistory) + if (firstMismatch != null) { + return@compile ReplCompileResult.HistoryMismatch(descriptorsHistory.copySources(), firstMismatch) + } + + val currentGeneration = generation.get() + + val (psiFile, errorHolder) = run { + if (checker.lineState == null || checker.lineState!!.codeLine != codeLine) { + val res = checker.check(codeLine, currentGeneration) + when (res) { + is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete(descriptorsHistory.copySources()) + is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(descriptorsHistory.copySources(), res.message, res.location) + is ReplCheckResult.Ok -> NO_ACTION() + } + } + Pair(checker.lineState!!.psiFile, checker.lineState!!.errorHolder) + } + + val newDependencies = scriptDefinition.getDependenciesFor(psiFile, checker.environment.project, lastDependencies) + var classpathAddendum: List? = null + if (lastDependencies != newDependencies) { + lastDependencies = newDependencies + classpathAddendum = newDependencies?.let { checker.environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) } + } + + val analysisResult = analyzerEngine.analyzeReplLine(psiFile, codeLine) + AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder) + val scriptDescriptor = when (analysisResult) { + is GenericReplAnalyzer.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(descriptorsHistory.copySources(), errorHolder.renderedDiagnostics) + is GenericReplAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor + else -> error("Unexpected result ${analysisResult.javaClass}") + } + + val state = GenerationState( + psiFile.project, + ClassBuilderFactories.binaries(false), + analyzerEngine.module, + analyzerEngine.trace.bindingContext, + listOf(psiFile), + compilerConfiguration + ) + state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME + state.replSpecific.earlierScriptsForReplInterpreter = descriptorsHistory.copyValues() + state.beforeCompile() + KotlinCodegenFacade.generatePackage( + state, + psiFile.script!!.getContainingKtFile().packageFqName, + setOf(psiFile.script!!.getContainingKtFile()), + org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION) + + val generatedClassname = makeSriptBaseName(codeLine, currentGeneration) + val compiledCodeLine = CompiledReplCodeLine(generatedClassname, codeLine) + descriptorsHistory.add(compiledCodeLine, scriptDescriptor) + + return ReplCompileResult.CompiledClasses(descriptorsHistory.copySources(), + compiledCodeLine, + generatedClassname, + state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }, + state.replSpecific.hasResult, + classpathAddendum ?: emptyList()) } - - val newDependencies = scriptDefinition.getDependenciesFor(psiFile, environment.project, lastDependencies) - var classpathAddendum: List? = null - if (lastDependencies != newDependencies) { - lastDependencies = newDependencies - classpathAddendum = newDependencies?.let { environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) } - } - - val analysisResult = analyzerEngine.analyzeReplLine(psiFile, codeLine.no) - AnalyzerWithCompilerReport.Companion.reportDiagnostics(analysisResult.diagnostics, errorHolder) - val scriptDescriptor = when (analysisResult) { - is CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(descriptorsHistory.lines, errorHolder.renderedDiagnostics) - is CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor - else -> error("Unexpected result ${analysisResult.javaClass}") - } - - val state = GenerationState( - psiFile.project, - ClassBuilderFactories.binaries(false), - analyzerEngine.module, - analyzerEngine.trace.bindingContext, - listOf(psiFile), - compilerConfiguration - ) - state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME - state.replSpecific.earlierScriptsForReplInterpreter = descriptorsHistory.values - state.beforeCompile() - KotlinCodegenFacade.generatePackage( - state, - psiFile.script!!.getContainingKtFile().packageFqName, - setOf(psiFile.script!!.getContainingKtFile()), - org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION) - - descriptorsHistory.add(codeLine, scriptDescriptor) - - return ReplCompileResult.CompiledClasses(descriptorsHistory.lines, - state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }, - state.replSpecific.hasResult, - classpathAddendum ?: emptyList()) } companion object { private val SCRIPT_RESULT_FIELD_NAME = "\$\$result" } -} +} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt index 7913b3c5d0c..6b863726e30 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt @@ -24,11 +24,10 @@ import com.intellij.psi.impl.PsiFileFactoryImpl import com.intellij.testFramework.LightVirtualFile import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.cli.common.repl.ReplClassLoader +import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots -import org.jetbrains.kotlin.cli.jvm.repl.CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful -import org.jetbrains.kotlin.cli.jvm.repl.CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors import org.jetbrains.kotlin.codegen.ClassBuilderFactories import org.jetbrains.kotlin.codegen.CompilationErrorHandler import org.jetbrains.kotlin.codegen.KotlinCodegenFacade @@ -65,7 +64,7 @@ class ReplInterpreter( } private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl - private val analyzerEngine = CliReplAnalyzerEngine(environment) + private val analyzerEngine = GenericReplAnalyzer(environment) fun eval(line: String): LineResult { ++lineNumber @@ -99,11 +98,11 @@ class ReplInterpreter( return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics) } - val analysisResult = analyzerEngine.analyzeReplLine(psiFile, lineNumber) + val analysisResult = analyzerEngine.analyzeReplLine(psiFile, ReplCodeLine(lineNumber, "fake line")) AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder) val scriptDescriptor = when (analysisResult) { - is WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics) - is Successful -> analysisResult.scriptDescriptor + is GenericReplAnalyzer.ReplLineAnalysisResult.WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics) + is GenericReplAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor else -> error("Unexpected result ${analysisResult.javaClass}") } diff --git a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt index fbc24cf3326..fe6516d4a33 100644 --- a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt +++ b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt @@ -35,15 +35,15 @@ open class KotlinRemoteReplClientBase( targetPlatform: CompileService.TargetPlatform, templateClasspath: List, templateClassName: String, - scriptArgs: Array? = null, - scriptArgsTypes: Array>? = null, + scriptArgs: Array? = null, + scriptArgsTypes: Array>? = null, compilerMessagesOutputStream: OutputStream = System.err, evalOutputStream: OutputStream? = null, evalErrorStream: OutputStream? = null, evalInputStream: InputStream? = null, port: Int = SOCKET_ANY_FREE_PORT, operationsTracer: RemoteOperationsTracer? = null -) : ReplChecker { +) { val sessionId = compileService.leaseReplSession( clientAliveFlagFile?.absolutePath, @@ -70,10 +70,6 @@ open class KotlinRemoteReplClientBase( } }) } - - override fun check(codeLine: ReplCodeLine, history: List): ReplCheckResult { - return compileService.remoteReplLineCheck(sessionId, codeLine, history).get() - } } class KotlinRemoteReplCompiler( @@ -99,10 +95,20 @@ class KotlinRemoteReplCompiler( evalInputStream = null, port = port, operationsTracer = operationsTracer -), ReplCompiler { +), ReplCompiler, ReplCheckAction { + override fun resetToLine(lineNumber: Int): List { + return emptyList() // TODO: not implemented, no current need + } - override fun compile(codeLine: ReplCodeLine, history: List): ReplCompileResult { - return compileService.remoteReplLineCompile(sessionId, codeLine, history).get() + override val history: List + get() = emptyList() // TODO: not implemented, no current need + + override fun check(codeLine: ReplCodeLine): ReplCheckResult { + return compileService.remoteReplLineCheck(sessionId, codeLine).get() + } + + override fun compile(codeLine: ReplCodeLine, verifyHistory: List?): ReplCompileResult { + return compileService.remoteReplLineCompile(sessionId, codeLine, verifyHistory).get() } } @@ -114,8 +120,8 @@ class KotlinRemoteReplEvaluator( targetPlatform: CompileService.TargetPlatform, templateClasspath: List, templateClassName: String, - scriptArgs: Array?, - scriptArgsTypes: Array>?, + scriptArgs: Array? = null, + scriptArgsTypes: Array>? = null, compilerMessagesOutputStream: OutputStream, evalOutputStream: OutputStream?, evalErrorStream: OutputStream?, @@ -137,12 +143,20 @@ class KotlinRemoteReplEvaluator( evalInputStream = evalInputStream, port = port, operationsTracer = operationsTracer -), ReplEvaluator { +), ReplAtomicEvalAction, ReplEvaluatorExposedInternalHistory, ReplCheckAction { - override val lastEvaluatedScript: ClassWithInstance? = null // not implemented, no need so far + override val lastEvaluatedScripts: List = emptyList() // not implemented, no need so far // TODO: invokeWrapper is ignored here, and in the daemon the session wrapper is used instead; So consider to make it per call (avoid performance penalties though) - override fun eval(codeLine: ReplCodeLine, history: List, invokeWrapper: InvokeWrapper?): ReplEvalResult { - return compileService.remoteReplLineEval(sessionId, codeLine, history).get() + // TODO: scriptArgs are ignored here, they should be passed through + override fun compileAndEval(codeLine: ReplCodeLine, + scriptArgs: ScriptArgsWithTypes?, + verifyHistory: List?, + invokeWrapper: InvokeWrapper?): ReplEvalResult { + return compileService.remoteReplLineEval(sessionId, codeLine, verifyHistory).get() + } + + override fun check(codeLine: ReplCodeLine): ReplCheckResult { + return compileService.remoteReplLineCheck(sessionId, codeLine).get() } } diff --git a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt index e2f76b715d1..aa7715d43af 100644 --- a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt +++ b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt @@ -16,8 +16,10 @@ package org.jetbrains.kotlin.daemon.common -import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments -import org.jetbrains.kotlin.cli.common.repl.* +import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult +import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine +import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult +import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult import java.io.File import java.io.Serializable import java.rmi.Remote @@ -148,8 +150,8 @@ interface CompileService : Remote { servicesFacade: CompilerCallbackServicesFacade, templateClasspath: List, templateClassName: String, - scriptArgs: Array?, - scriptArgsTypes: Array>?, + scriptArgs: Array?, + scriptArgsTypes: Array>?, compilerMessagesOutputStream: RemoteOutputStream, evalOutputStream: RemoteOutputStream?, evalErrorStream: RemoteOutputStream?, @@ -163,21 +165,20 @@ interface CompileService : Remote { @Throws(RemoteException::class) fun remoteReplLineCheck( sessionId: Int, - codeLine: ReplCodeLine, - history: List + codeLine: ReplCodeLine ): CallResult @Throws(RemoteException::class) fun remoteReplLineCompile( sessionId: Int, codeLine: ReplCodeLine, - history: List + history: List? ): CallResult @Throws(RemoteException::class) fun remoteReplLineEval( sessionId: Int, codeLine: ReplCodeLine, - history: List + history: List? ): CallResult } diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt index a241a432115..4d795e835e0 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt @@ -23,29 +23,36 @@ import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem import org.jetbrains.kotlin.cli.common.CLICompiler import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY -import org.jetbrains.kotlin.cli.common.arguments.* +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments import org.jetbrains.kotlin.cli.common.messages.* import org.jetbrains.kotlin.cli.common.modules.ModuleXmlParser -import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult -import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine -import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult -import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult +import org.jetbrains.kotlin.cli.common.repl.* import org.jetbrains.kotlin.cli.js.K2JSCompiler import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler -import org.jetbrains.kotlin.config.IncrementalCompilation import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.daemon.common.* -import org.jetbrains.kotlin.daemon.incremental.* -import org.jetbrains.kotlin.daemon.report.* +import org.jetbrains.kotlin.daemon.incremental.RemoteAnnotationsFileUpdater +import org.jetbrains.kotlin.daemon.incremental.RemoteArtifactChangesProvider +import org.jetbrains.kotlin.daemon.incremental.RemoteChangesRegostry +import org.jetbrains.kotlin.daemon.report.CompileServicesFacadeMessageCollector +import org.jetbrains.kotlin.daemon.report.DaemonMessageReporter +import org.jetbrains.kotlin.daemon.report.DaemonMessageReporterPrintStreamAdapter +import org.jetbrains.kotlin.daemon.report.RemoteICReporter import org.jetbrains.kotlin.incremental.* import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents import org.jetbrains.kotlin.modules.Module import org.jetbrains.kotlin.progress.CompilationCanceledStatus import org.jetbrains.kotlin.utils.addToStdlib.check import org.jetbrains.kotlin.utils.stackTraceStr -import java.io.* +import java.io.BufferedOutputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.PrintStream import java.rmi.NoSuchObjectException import java.rmi.registry.Registry import java.rmi.server.UnicastRemoteObject @@ -445,8 +452,8 @@ class CompileServiceImpl( servicesFacade: CompilerCallbackServicesFacade, templateClasspath: List, templateClassName: String, - scriptArgs: Array?, - scriptArgsTypes: Array>?, + scriptArgs: Array?, + scriptArgsTypes: Array>?, compilerMessagesOutputStream: RemoteOutputStream, evalOutputStream: RemoteOutputStream?, evalErrorStream: RemoteOutputStream?, @@ -457,7 +464,9 @@ class CompileServiceImpl( CompileService.CallResult.Error("Sorry, only JVM target platform is supported now") else { val disposable = Disposer.newDisposable() - val repl = KotlinJvmReplService(disposable, templateClasspath, templateClassName, scriptArgs, scriptArgsTypes, compilerMessagesOutputStream, operationsTracer) + val repl = KotlinJvmReplService(disposable, templateClasspath, templateClassName, + scriptArgs?.let { ScriptArgsWithTypes(it, scriptArgsTypes?.map { it.kotlin }?.toTypedArray() ?: emptyArray()) }, + compilerMessagesOutputStream, operationsTracer) val sessionId = state.sessions.leaseSession(ClientOrSessionProxy(aliveFlagPath, repl, disposable)) CompileService.CallResult.Good(sessionId) @@ -467,14 +476,14 @@ class CompileServiceImpl( // TODO: add more checks (e.g. is it a repl session) override fun releaseReplSession(sessionId: Int): CompileService.CallResult = releaseCompileSession(sessionId) - override fun remoteReplLineCheck(sessionId: Int, codeLine: ReplCodeLine, history: List): CompileService.CallResult = + override fun remoteReplLineCheck(sessionId: Int, codeLine: ReplCodeLine): CompileService.CallResult = ifAlive(minAliveness = Aliveness.Alive) { withValidRepl(sessionId) { - check(codeLine, history) + check(codeLine) } } - override fun remoteReplLineCompile(sessionId: Int, codeLine: ReplCodeLine, history: List): CompileService.CallResult = + override fun remoteReplLineCompile(sessionId: Int, codeLine: ReplCodeLine, history: List?): CompileService.CallResult = ifAlive(minAliveness = Aliveness.Alive) { withValidRepl(sessionId) { compile(codeLine, history) @@ -484,11 +493,11 @@ class CompileServiceImpl( override fun remoteReplLineEval( sessionId: Int, codeLine: ReplCodeLine, - history: List + history: List? ): CompileService.CallResult = ifAlive(minAliveness = Aliveness.Alive) { withValidRepl(sessionId) { - eval(codeLine, history) + compileAndEval(codeLine, verifyHistory = history) } } diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt index 91d61fe2e04..37d3bb2fa91 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt @@ -22,7 +22,6 @@ import org.jetbrains.kotlin.cli.common.repl.* import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots import org.jetbrains.kotlin.cli.jvm.repl.GenericReplCompiler -import org.jetbrains.kotlin.cli.jvm.repl.compileAndEval import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.daemon.common.DummyProfiler @@ -40,12 +39,10 @@ open class KotlinJvmReplService( disposable: Disposable, templateClasspath: List, templateClassName: String, - scriptArgs: Array?, - scriptArgsTypes: Array>?, + protected val fallbackScriptArgs: ScriptArgsWithTypes?, compilerOutputStreamProxy: RemoteOutputStream, - val operationsTracer: RemoteOperationsTracer? -) : ReplCompiler, ReplEvaluator { - + protected val operationsTracer: RemoteOperationsTracer? +) : ReplCompileAction, ReplAtomicEvalAction, ReplCheckAction, ReplEvaluatorExposedInternalHistory { protected val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerOutputStreamProxy, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)) protected class KeepFirstErrorMessageCollector(compilerMessagesStream: PrintStream) : MessageCollector { @@ -105,23 +102,24 @@ open class KotlinJvmReplService( private val scriptDef = makeScriptDefinition(templateClasspath, templateClassName) - private val replCompiler : GenericReplCompiler? by lazy { + private val replCompiler: ReplCompiler? by lazy { if (scriptDef == null) null else GenericReplCompiler(disposable, scriptDef, configuration, messageCollector) } - private val compiledEvaluator : GenericReplCompiledEvaluator by lazy { - GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null, scriptArgs, scriptArgsTypes) + private val replEvaluator: ReplFullEvaluator? by lazy { + replCompiler?.let { compiler -> + GenericReplCompilingEvaluator(compiler, configuration.jvmClasspathRoots, null, fallbackScriptArgs, ReplRepeatingMode.NONE) + } } - override val lastEvaluatedScript: ClassWithInstance? get() = compiledEvaluator.lastEvaluatedScript + override val lastEvaluatedScripts: List get() = replEvaluator?.lastEvaluatedScripts ?: emptyList() - override fun check(codeLine: ReplCodeLine, history: List): ReplCheckResult { + override fun check(codeLine: ReplCodeLine): ReplCheckResult { operationsTracer?.before("check") try { - return replCompiler?.check(codeLine, history) - ?: ReplCheckResult.Error(history, - messageCollector.firstErrorMessage ?: "Unknown error", + return replCompiler?.check(codeLine) + ?: ReplCheckResult.Error(messageCollector.firstErrorMessage ?: "Unknown error", messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) } finally { @@ -129,11 +127,11 @@ open class KotlinJvmReplService( } } - override fun compile(codeLine: ReplCodeLine, history: List): ReplCompileResult { + override fun compile(codeLine: ReplCodeLine, verifyHistory: List?): ReplCompileResult { operationsTracer?.before("compile") try { - return replCompiler?.compile(codeLine, history) - ?: ReplCompileResult.Error(history, + return replCompiler?.compile(codeLine, verifyHistory) + ?: ReplCompileResult.Error(verifyHistory ?: emptyList(), messageCollector.firstErrorMessage ?: "Unknown error", messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) } @@ -142,13 +140,13 @@ open class KotlinJvmReplService( } } - override fun eval(codeLine: ReplCodeLine, history: List, invokeWrapper: InvokeWrapper?): ReplEvalResult = synchronized(this) { + override fun compileAndEval(codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, verifyHistory: List?, invokeWrapper: InvokeWrapper?): ReplEvalResult { operationsTracer?.before("eval") try { - return replCompiler?.let { compileAndEval(it, compiledEvaluator, codeLine, history, invokeWrapper) } - ?: ReplEvalResult.Error.CompileTime(history, - messageCollector.firstErrorMessage ?: "Unknown error", - messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) + return replEvaluator?.compileAndEval(codeLine, scriptArgs ?: fallbackScriptArgs, verifyHistory, invokeWrapper) + ?: ReplEvalResult.Error.CompileTime(verifyHistory ?: replEvaluator?.history ?: emptyList(), + messageCollector.firstErrorMessage ?: "Unknown error", + messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) } finally { operationsTracer?.after("eval") diff --git a/compiler/tests/org/jetbrains/kotlin/cli/jvm/repl/GenericReplTest.kt b/compiler/tests/org/jetbrains/kotlin/cli/jvm/repl/GenericReplTest.kt index 28416fcc55b..402ac204989 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/jvm/repl/GenericReplTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/cli/jvm/repl/GenericReplTest.kt @@ -35,7 +35,6 @@ import java.io.File import java.net.URLClassLoader class GenericReplTest : TestCase() { - @Test fun testReplBasics() { @@ -45,7 +44,7 @@ class GenericReplTest : TestCase() { listOf(File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-runtime.jar")), "kotlin.script.templates.standard.ScriptTemplateWithArgs") - val res1 = repl.replCompiler?.check(ReplCodeLine(0, "val x ="), emptyList()) + val res1 = repl.replCompiler?.check(ReplCodeLine(0, "val x =")) TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete) val codeLine0 = ReplCodeLine(0, "val l1 = listOf(1 + 2)\nl1.first()") @@ -53,7 +52,7 @@ class GenericReplTest : TestCase() { val res2c = res2 as? ReplCompileResult.CompiledClasses TestCase.assertNotNull("Unexpected compile result: $res2", res2c) - val res21 = repl.compiledEvaluator.eval(codeLine0, emptyList(), res2c!!.classes, res2c.hasResult, res2c.classpathAddendum) + val res21 = repl.compiledEvaluator.eval(res2c!!) val res21e = res21 as? ReplEvalResult.ValueResult TestCase.assertNotNull("Unexpected eval result: $res21", res21e) TestCase.assertEquals(3, res21e!!.value) @@ -63,7 +62,7 @@ class GenericReplTest : TestCase() { val res3c = res3 as? ReplCompileResult.CompiledClasses TestCase.assertNotNull("Unexpected compile result: $res3", res3c) - val res31 = repl.compiledEvaluator.eval(codeLine1, listOf(codeLine0), res3c!!.classes, res3c.hasResult, res3c.classpathAddendum) + val res31 = repl.compiledEvaluator.eval(res3c!!) val res31e = res31 as? ReplEvalResult.UnitResult TestCase.assertNotNull("Unexpected eval result: $res31", res31e) @@ -75,7 +74,7 @@ class GenericReplTest : TestCase() { val res4c = res4 as? ReplCompileResult.CompiledClasses TestCase.assertNotNull("Unexpected compile result: $res4", res4c) - val res41 = repl.compiledEvaluator.eval(codeLine2, emptyList(), res4c!!.classes, res4c.hasResult, res4c.classpathAddendum) + val res41 = repl.compiledEvaluator.eval(res4c!!) val res41e = res41 as? ReplEvalResult.ValueResult TestCase.assertNotNull("Unexpected eval result: $res41", res41e) TestCase.assertEquals(7, res41e!!.value) @@ -97,7 +96,7 @@ class GenericReplTest : TestCase() { val res1c = res1 as? ReplCompileResult.CompiledClasses TestCase.assertNotNull("Unexpected compile result: $res1", res1c) - val res11 = repl.compiledEvaluator.eval(codeLine1, emptyList(), res1c!!.classes, res1c.hasResult, res1c.classpathAddendum) + val res11 = repl.compiledEvaluator.eval(res1c!!) val res11e = res11 as? ReplEvalResult.ValueResult TestCase.assertNotNull("Unexpected eval result: $res11", res11e) TestCase.assertEquals(3, res11e!!.value) @@ -107,7 +106,7 @@ class GenericReplTest : TestCase() { val res2c = res2 as? ReplCompileResult.CompiledClasses TestCase.assertNotNull("Unexpected compile result: $res2", res2c) - val res21 = repl.compiledEvaluator.eval(codeLine2, listOf(codeLine1), res2c!!.classes, res2c.hasResult, res2c.classpathAddendum) + val res21 = repl.compiledEvaluator.eval(res2c!!) val res21e = res21 as? ReplEvalResult.ValueResult TestCase.assertNotNull("Unexpected eval result: $res21", res21e) TestCase.assertEquals(5, res21e!!.value) @@ -121,6 +120,8 @@ internal class TestRepl( templateClasspath: List, templateClassName: String ) { + val emptyScriptArgs = ScriptArgsWithTypes(arrayOf(emptyArray()), arrayOf(Array::class)) + private val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK, *templateClasspath.toTypedArray()).apply { put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script") } @@ -137,8 +138,8 @@ internal class TestRepl( GenericReplCompiler(disposable, scriptDef, configuration, PrintingMessageCollector(System.out, MessageRenderer.WITHOUT_PATHS, false)) } - val compiledEvaluator : GenericReplCompiledEvaluator by lazy { - GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null, arrayOf(emptyArray())) + val compiledEvaluator: ReplEvaluator by lazy { + GenericReplEvaluator(configuration.jvmClasspathRoots, null, emptyScriptArgs, ReplRepeatingMode.NONE) } } diff --git a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt index ccb7c5cc01f..0693affc71c 100644 --- a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt @@ -486,7 +486,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { ScriptWithNoParam::class.qualifiedName!!, System.err) - val localEvaluator = GenericReplCompiledEvaluator(emptyList(), Thread.currentThread().contextClassLoader) + val localEvaluator = GenericReplEvaluator(emptyList(), Thread.currentThread().contextClassLoader) doReplTestWithLocalEval(repl, localEvaluator) } @@ -501,17 +501,16 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { "kotlin.script.templates.standard.ScriptTemplateWithArgs", System.err) - val localEvaluator = GenericReplCompiledEvaluator(emptyList(), - Thread.currentThread().contextClassLoader, - arrayOf(emptyArray())) + val localEvaluator = GenericReplEvaluator(emptyList(), Thread.currentThread().contextClassLoader, + ScriptArgsWithTypes(arrayOf(emptyArray()), arrayOf(Array::class))) doReplTestWithLocalEval(repl, localEvaluator) } } } - private fun doReplTestWithLocalEval(repl: KotlinRemoteReplCompiler, localEvaluator: GenericReplCompiledEvaluator) { - val res0 = repl.check(ReplCodeLine(0, "val x ="), emptyList()) + private fun doReplTestWithLocalEval(repl: KotlinRemoteReplCompiler, localEvaluator: ReplEvaluator) { + val res0 = repl.check(ReplCodeLine(0, "val x =")) TestCase.assertTrue("Unexpected check results: $res0", res0 is ReplCheckResult.Incomplete) val codeLine1 = ReplCodeLine(1, "val lst = listOf(1)\nval x = 5") @@ -519,7 +518,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val res1c = res1 as? ReplCompileResult.CompiledClasses TestCase.assertNotNull("Unexpected compile result: $res1", res1c) - val res11 = localEvaluator.eval(codeLine1, emptyList(), res1c!!.classes, res1c.hasResult, res1c.classpathAddendum) + val res11 = localEvaluator.eval(res1c!!) val res11e = res11 as? ReplEvalResult.UnitResult TestCase.assertNotNull("Unexpected eval result: $res11", res11e) @@ -531,7 +530,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val res2c = res2 as? ReplCompileResult.CompiledClasses TestCase.assertNotNull("Unexpected compile result: $res2", res2c) - val res21 = localEvaluator.eval(codeLine2, listOf(codeLine1), res2c!!.classes, res2c.hasResult, res2c.classpathAddendum) + val res21 = localEvaluator.eval(res2c!!) val res21e = res21 as? ReplEvalResult.ValueResult TestCase.assertNotNull("Unexpected eval result: $res21", res21e) TestCase.assertEquals(7, res21e!!.value) @@ -548,22 +547,23 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val repl = KotlinRemoteReplEvaluator(disposable, daemon!!, null, CompileService.TargetPlatform.JVM, listOf(File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-runtime.jar")), "kotlin.script.templates.standard.ScriptTemplateWithArgs", - arrayOf(emptyArray()), null, + arrayOf(emptyArray()), + arrayOf(Array::class.java), System.err, evalOut, evalErr, evalIn) - val res0 = repl.check(ReplCodeLine(0, "val x ="), emptyList()) + val res0 = repl.check(ReplCodeLine(0, "val x =")) TestCase.assertTrue("Unexpected check results: $res0", res0 is ReplCheckResult.Incomplete) val codeLine1 = ReplCodeLine(1, "val x = 5") - val res1 = repl.eval(codeLine1, emptyList()) + val res1 = repl.compileAndEval(codeLine1, verifyHistory = emptyList()) val res1e = res1 as? ReplEvalResult.UnitResult TestCase.assertNotNull("Unexpected eval result: $res1", res1e) val codeLine2 = ReplCodeLine(2, "x + 2") - val res2x = repl.eval(codeLine2, listOf(codeLine2)) + val res2x = repl.compileAndEval(codeLine2, verifyHistory = listOf(codeLine2)) TestCase.assertNotNull("Unexpected compile result: $res2x", res2x as? ReplEvalResult.HistoryMismatch) - val res2 = repl.eval(codeLine2, listOf(codeLine1)) + val res2 = repl.compileAndEval(codeLine2, verifyHistory = listOf(codeLine1)) val res2e = res2 as? ReplEvalResult.ValueResult TestCase.assertNotNull("Unexpected eval result: $res2", res2e) TestCase.assertEquals(7, res2e!!.value) @@ -592,8 +592,8 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { // use repl compiler for >> 1s, making sure that idle/unused timeouts are not firing for (attempts in 1..10) { - val codeLine1 = ReplCodeLine(1, "3 + 5") - val res1 = repl.compile(codeLine1, emptyList()) + val codeLine1 = ReplCodeLine(attempts, "3 + 5") + val res1 = repl.compile(codeLine1) val res1c = res1 as? ReplCompileResult.CompiledClasses TestCase.assertNotNull("Unexpected compile result: $res1", res1c) Thread.sleep(200) diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt index 1e1b51c7243..2eb250d2529 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt @@ -17,10 +17,7 @@ package org.jetbrains.kotlin.jsr223 import com.intellij.openapi.Disposable -import org.jetbrains.kotlin.cli.common.repl.GenericReplCompiledEvaluator -import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase -import org.jetbrains.kotlin.cli.common.repl.ReplCompiledEvaluator -import org.jetbrains.kotlin.cli.common.repl.ReplCompiler +import org.jetbrains.kotlin.cli.common.repl.* import org.jetbrains.kotlin.daemon.client.DaemonReportMessage import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient @@ -31,14 +28,15 @@ import java.io.File import javax.script.ScriptContext import javax.script.ScriptEngineFactory import javax.script.ScriptException +import kotlin.reflect.KClass class KotlinJsr223JvmScriptEngine4Idea( disposable: Disposable, factory: ScriptEngineFactory, templateClasspath: List, templateClassName: String, - getScriptArgs: (ScriptContext) -> Array?, - scriptArgsTypes: Array>? + private val getScriptArgs: (ScriptContext, Array>?) -> ScriptArgsWithTypes?, + private val scriptArgsTypes: Array>? ) : KotlinJsr223JvmScriptEngineBase(factory) { private val daemon by lazy { @@ -66,8 +64,10 @@ class KotlinJsr223JvmScriptEngine4Idea( } } - // TODO: bindings passing works only once on the first eval, subsequent setContext/setBindings call have no effect. Consider making it dynamic, but take history into account - val localEvaluator: ReplCompiledEvaluator by lazy { GenericReplCompiledEvaluator(templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext()), scriptArgsTypes) } + override fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = + getScriptArgs(getContext(), scriptArgsTypes) - override val replEvaluator: ReplCompiledEvaluator get() = localEvaluator + val localEvaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(replCompiler, templateClasspath, Thread.currentThread().contextClassLoader) } + + override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator } diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223StandardScriptEngineFactory4Idea.kt b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223StandardScriptEngineFactory4Idea.kt index 0c5d7a31826..6f765a4cb02 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223StandardScriptEngineFactory4Idea.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223StandardScriptEngineFactory4Idea.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.jsr223 import com.intellij.openapi.util.Disposer import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase +import org.jetbrains.kotlin.cli.common.repl.ScriptArgsWithTypes import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_JAVA_RUNTIME_JAR import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_JAVA_SCRIPT_RUNTIME_JAR @@ -37,8 +38,8 @@ class KotlinJsr223StandardScriptEngineFactory4Idea : KotlinJsr223JvmScriptEngine this, scriptCompilationClasspathFromContext(Thread.currentThread().contextClassLoader), "kotlin.script.templates.standard.ScriptTemplateWithBindings", - { ctx -> arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)) }, - arrayOf(Map::class.java) + { ctx, argTypes -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), argTypes ?: emptyArray()) }, + arrayOf(Map::class) ) } diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonCompileScriptEngine.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonCompileScriptEngine.kt index e383830a88d..1088b7b3385 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonCompileScriptEngine.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonCompileScriptEngine.kt @@ -28,6 +28,7 @@ import java.io.OutputStream import javax.script.ScriptContext import javax.script.ScriptEngineFactory import javax.script.ScriptException +import kotlin.reflect.KClass class KotlinJsr223JvmDaemonCompileScriptEngine( disposable: Disposable, @@ -35,8 +36,8 @@ class KotlinJsr223JvmDaemonCompileScriptEngine( compilerJar: File, templateClasspath: List, templateClassName: String, - getScriptArgs: (ScriptContext) -> Array?, - scriptArgsTypes: Array>?, + getScriptArgs: (ScriptContext, Array>?) -> ScriptArgsWithTypes?, + scriptArgsTypes: Array>?, compilerOut: OutputStream = System.err ) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine { @@ -56,20 +57,18 @@ class KotlinJsr223JvmDaemonCompileScriptEngine( } // TODO: bindings passing works only once on the first eval, subsequent setContext/setBindings call have no effect. Consider making it dynamic, but take history into account - val localEvaluator by lazy { GenericReplCompiledEvaluator(templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext()), scriptArgsTypes) } + val localEvaluator by lazy { GenericReplCompilingEvaluator(replCompiler, templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext(), scriptArgsTypes)) } - override val replEvaluator: ReplCompiledEvaluator get() = localEvaluator - override val replScriptEvaluator: ReplEvaluatorBase get() = localEvaluator -} + override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator + private fun connectToCompileService(compilerJar: File): CompileService { + val compilerId = CompilerId.makeCompilerId(compilerJar) + val daemonOptions = configureDaemonOptions() + val daemonJVMOptions = DaemonJVMOptions() -private fun connectToCompileService(compilerJar: File): CompileService { - val compilerId = CompilerId.makeCompilerId(compilerJar) - val daemonOptions = configureDaemonOptions() - val daemonJVMOptions = DaemonJVMOptions() + val daemonReportMessages = arrayListOf() - val daemonReportMessages = arrayListOf() - - return KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(null, daemonReportMessages), true, true) - ?: throw ScriptException("Unable to connect to repl server:" + daemonReportMessages.joinToString("\n ", prefix = "\n ") { "${it.category.name} ${it.message}" }) -} + return KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(null, daemonReportMessages), true, true) + ?: throw ScriptException("Unable to connect to repl server:" + daemonReportMessages.joinToString("\n ", prefix = "\n ") { "${it.category.name} ${it.message}" }) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmLocalScriptEngine.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmLocalScriptEngine.kt index f1bc6d671d3..417b63393af 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmLocalScriptEngine.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmLocalScriptEngine.kt @@ -31,14 +31,15 @@ import java.io.File import java.net.URLClassLoader import javax.script.ScriptContext import javax.script.ScriptEngineFactory +import kotlin.reflect.KClass class KotlinJsr223JvmLocalScriptEngine( disposable: Disposable, factory: ScriptEngineFactory, val templateClasspath: List, templateClassName: String, - getScriptArgs: (ScriptContext) -> Array?, - scriptArgsTypes: Array>? + getScriptArgs: (ScriptContext, Array>?) -> ScriptArgsWithTypes?, + scriptArgsTypes: Array>? ) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine { override val replCompiler: ReplCompiler by lazy { @@ -49,10 +50,9 @@ class KotlinJsr223JvmLocalScriptEngine( PrintingMessageCollector(System.out, MessageRenderer.WITHOUT_PATHS, false)) } // TODO: bindings passing works only once on the first eval, subsequent setContext/setBindings call have no effect. Consider making it dynamic, but take history into account - val localEvaluator by lazy { GenericReplCompiledEvaluator(templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext()), scriptArgsTypes) } + private val localEvaluator by lazy { GenericReplCompilingEvaluator(replCompiler, templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext(), scriptArgsTypes)) } - override val replEvaluator: ReplCompiledEvaluator get() = localEvaluator - override val replScriptEvaluator: ReplEvaluatorBase get() = localEvaluator + override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator private fun makeScriptDefinition(templateClasspath: List, templateClassName: String): KotlinScriptDefinition { val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader) diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt index a01b5d5e804..b8c94ca78bc 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt @@ -20,6 +20,7 @@ package org.jetbrains.kotlin.script.jsr223 import com.intellij.openapi.util.Disposer import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase +import org.jetbrains.kotlin.cli.common.repl.ScriptArgsWithTypes import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.script.util.classpathFromClass import org.jetbrains.kotlin.script.util.classpathFromClassloader @@ -40,8 +41,8 @@ class KotlinJsr223JvmLocalScriptEngineFactory : KotlinJsr223JvmScriptEngineFacto this, scriptCompilationClasspathFromContext(), "kotlin.script.templates.standard.ScriptTemplateWithBindings", - { ctx -> arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)) }, - arrayOf(Map::class.java) + { ctx, types -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), types ?: emptyArray()) }, + arrayOf(Map::class) ) } @@ -54,33 +55,15 @@ class KotlinJsr223JvmDaemonLocalEvalScriptEngineFactory : KotlinJsr223JvmScriptE kotlinCompilerJar, scriptCompilationClasspathFromContext(), "kotlin.script.templates.standard.ScriptTemplateWithBindings", - { ctx -> arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)) }, - arrayOf(Map::class.java) + { ctx, types -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), types ?: emptyArray()) }, + arrayOf(Map::class) ) } -private fun makeSerializableArgumentsForTemplateWithBindings(ctx: ScriptContext): Array { - val bindings = ctx.getBindings(ScriptContext.ENGINE_SCOPE) - val serializableBindings = linkedMapOf() - // TODO: consider deeper analysis and copying to serializable data if possible - serializableBindings.putAll(bindings) - return arrayOf(serializableBindings) -} private fun File.existsOrNull(): File? = existsAndCheckOrNull { true } private inline fun File.existsAndCheckOrNull(check: (File.() -> Boolean)): File? = if (exists() && check()) this else null -private val kotlinCompilerJar: File by lazy { - // highest prio - explicit property - System.getProperty("kotlin.compiler.jar")?.let(::File)?.existsOrNull() - // search classpath from context classloader and `java.class.path` property - ?: (classpathFromClass(Thread.currentThread().contextClassLoader, K2JVMCompiler::class) - ?: contextClasspath(KOTLIN_COMPILER_JAR, Thread.currentThread().contextClassLoader) - ?: classpathFromClasspathProperty() - )?.firstOrNull { it.matchMaybeVersionedFile(KOTLIN_COMPILER_JAR) } - ?: throw FileNotFoundException("Cannot find kotlin compiler jar, set kotlin.compiler.jar property to proper location") -} - private fun Iterable.anyOrNull(predicate: (T) -> Boolean) = if (any(predicate)) this else null private fun File.matchMaybeVersionedFile(baseName: String) = @@ -89,20 +72,30 @@ private fun File.matchMaybeVersionedFile(baseName: String) = name.startsWith(baseName.removeSuffix(".jar") + "-") private fun contextClasspath(keyName: String, classLoader: ClassLoader): List? = - ( classpathFromClassloader(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) } - ?: manifestClassPath(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) } + (classpathFromClassloader(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) } + ?: manifestClassPath(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) } )?.toList() private fun scriptCompilationClasspathFromContext(classLoader: ClassLoader = Thread.currentThread().contextClassLoader): List = - ( System.getProperty("kotlin.script.classpath")?.split(File.pathSeparator)?.map(::File) - ?: contextClasspath(KOTLIN_JAVA_RUNTIME_JAR, classLoader) - ?: listOf(kotlinRuntimeJar, kotlinScriptRuntimeJar) + (System.getProperty("kotlin.script.classpath")?.split(File.pathSeparator)?.map(::File) + ?: contextClasspath(KOTLIN_JAVA_RUNTIME_JAR, classLoader) + ?: listOf(kotlinRuntimeJar, kotlinScriptRuntimeJar) ) - .map { it?.canonicalFile } - .distinct() - .mapNotNull { it?.existsOrNull() } + .map { it?.canonicalFile } + .distinct() + .mapNotNull { it?.existsOrNull() } +private val kotlinCompilerJar: File by lazy { + // highest prio - explicit property + System.getProperty("kotlin.compiler.jar")?.let(::File)?.existsOrNull() + // search classpath from context classloader and `java.class.path` property + ?: (classpathFromClass(Thread.currentThread().contextClassLoader, K2JVMCompiler::class) + ?: contextClasspath(KOTLIN_COMPILER_JAR, Thread.currentThread().contextClassLoader) + ?: classpathFromClasspathProperty() + )?.firstOrNull { it.matchMaybeVersionedFile(KOTLIN_COMPILER_JAR) } + ?: throw FileNotFoundException("Cannot find kotlin compiler jar, set kotlin.compiler.jar property to proper location") +} private val kotlinRuntimeJar: File? by lazy { System.getProperty("kotlin.java.runtime.jar")?.let(::File)?.existsOrNull() @@ -117,3 +110,4 @@ private val kotlinScriptRuntimeJar: File? by lazy { } + diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt index a1e6c6e0ee5..6dbd71d6edf 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. + * Copyright 2010-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/basic.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/basic.kt index f8b32ba2104..577abdf000d 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/basic.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/basic.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. + * Copyright 2010-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/maven.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/maven.kt index 25b541f7842..4f48b110a26 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/maven.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/maven.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. + * Copyright 2010-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt index d549cab338d..021a85c80fd 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. + * Copyright 2010-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt b/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt index 6f326254b81..9464e5d76ec 100644 --- a/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt +++ b/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. + * Copyright 2010-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.