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