From d2fec96f38483368e5a5a2a17868112722a699cc Mon Sep 17 00:00:00 2001 From: Ilya Muradyan Date: Fri, 28 Feb 2020 19:45:20 +0300 Subject: [PATCH] Add new REPL API JVM implementation --- build.gradle.kts | 1 + .../kotlin/cli/common/repl/BasicReplState.kt | 11 +- .../repl/GenericReplCompilingEvaluator.kt | 8 +- .../repl/KotlinJsr223JvmScriptEngineBase.kt | 6 +- .../kotlin/cli/common/repl/ReplApi.kt | 10 +- .../KotlinRemoteReplCompilerClientAsync.kt | 3 - .../regressions/kt28385/output.txt | 6 +- .../KotlinJsr223JvmScriptEngine4Idea.kt | 2 +- .../repl/js/test/AbstractJsReplTest.kt | 1 - .../jvmhost/test/LegacyReplTest.kt | 10 +- .../experimental/jvmhost/test/ReplTest.kt | 195 ++++---- .../jvmhost/test/ResolveDependenciesTest.kt | 1 + .../jvmhost/test/ScriptingHostTest.kt | 3 +- .../jsr223/KotlinJsr223ScriptEngineImpl.kt | 5 +- .../experimental/jvmhost/jvmScriptSaving.kt | 8 +- .../jvmhost/repl/legacyReplCompilation.kt | 104 +++-- .../jvmhost/repl/legacyReplEvaluation.kt | 10 +- .../experimental/jvm/BasicJvmReplEvaluator.kt | 69 +++ .../jvm/impl/KJvmCompiledScript.kt | 12 +- .../experimental/jvm/util/SnippetsHistory.kt | 37 ++ .../jvm/util/diagnosticsHelpers.kt | 14 + .../experimental/jvm/util/sourceCodeUtils.kt | 46 ++ .../experimental/jvm/test/calcAbsoluteTest.kt | 40 ++ libraries/tools/kotlin-bom/pom.xml | 5 + .../kotlin/mainKts/test/mainKtsJsr223Test.kt | 4 +- .../plugin/impl/KJvmReplCompilerBase.kt | 248 ++++++++++ .../plugin/impl/KJvmReplCompilerImpl.kt | 186 -------- .../plugin/impl/compilationContext.kt | 4 +- .../compiler/plugin/impl/errorReporting.kt | 8 +- .../plugin/impl/jvmCompilationUtil.kt | 17 +- .../plugin/repl/GenericCompilerState.kt | 28 +- .../plugin/repl/GenericReplCompiler.kt | 10 +- .../compiler/plugin/repl/ReplCodeAnalyzer.kt | 126 ++++-- .../compiler/plugin/repl/ReplInterpreter.kt | 2 +- .../plugin/repl/jvmReplCompilation.kt | 62 +-- .../repl/js/JsCoreScriptingCompiler.kt | 6 +- .../scripting/repl/js/JsReplCodeAnalyzer.kt | 11 +- .../scripting/repl/js/JsReplCompiler.kt | 4 +- .../kotlin/scripting/repl/js/JsReplUtils.kt | 6 +- .../build.gradle.kts | 27 ++ .../build.gradle.kts | 50 ++ .../scripting/ide_services/JvmReplTest.kt | 426 ++++++++++++++++++ .../ReplCompletionAndErrorsAnalysisTest.kt | 379 ++++++++++++++++ .../test_util/testScriptDefinitions.kt | 76 ++++ .../ide_services/test_util/testUtil.kt | 60 +++ .../scripting-ide-services/build.gradle.kts | 41 ++ .../KJvmReplCompilerWithIdeServices.kt | 119 +++++ .../compiler/impl/IdeLikeReplCodeAnalyzer.kt | 68 +++ .../compiler/impl/KJvmReplCompleter.kt | 406 +++++++++++++++++ .../impl/KotlinResolutionFacadeForRepl.kt | 73 +++ settings.gradle | 6 + 51 files changed, 2557 insertions(+), 503 deletions(-) create mode 100644 libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmReplEvaluator.kt create mode 100644 libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/SnippetsHistory.kt create mode 100644 libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/diagnosticsHelpers.kt create mode 100644 libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/sourceCodeUtils.kt create mode 100644 libraries/scripting/jvm/test/kotlin/script/experimental/jvm/test/calcAbsoluteTest.kt create mode 100644 plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerBase.kt delete mode 100644 plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerImpl.kt create mode 100644 plugins/scripting/scripting-ide-services-embeddable/build.gradle.kts create mode 100644 plugins/scripting/scripting-ide-services-test/build.gradle.kts create mode 100644 plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt create mode 100644 plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt create mode 100644 plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testScriptDefinitions.kt create mode 100644 plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testUtil.kt create mode 100644 plugins/scripting/scripting-ide-services/build.gradle.kts create mode 100644 plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/KJvmReplCompilerWithIdeServices.kt create mode 100644 plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/IdeLikeReplCodeAnalyzer.kt create mode 100644 plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/KJvmReplCompleter.kt create mode 100644 plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/KotlinResolutionFacadeForRepl.kt diff --git a/build.gradle.kts b/build.gradle.kts index 270898234ee..1718c93353e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -601,6 +601,7 @@ tasks { // dependsOn(":kotlin-scripting-jvm-host-test:embeddableTest") dependsOn(":kotlin-scripting-jsr223-test:embeddableTest") dependsOn(":kotlin-main-kts-test:test") + dependsOn(":kotlin-scripting-ide-services-test:test") } register("compilerTest") { diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt index 7af3d692523..0636a3e7c3b 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt @@ -24,16 +24,13 @@ import kotlin.concurrent.write data class LineId(override val no: Int, override val generation: Int, private val codeHash: Int) : ILineId, Serializable { - constructor(codeLine: ReplCodeLine): this(codeLine.no, codeLine.generation, codeLine.code.hashCode()) - - override fun compareTo(other: ILineId): Int = (other as? LineId)?.let { - no.compareTo(it.no).takeIf { it != 0 } - ?: generation.compareTo(it.generation).takeIf { it != 0 } - ?: codeHash.compareTo(it.codeHash) + override fun compareTo(other: ILineId): Int = (other as? LineId)?.let { lineId -> + no.compareTo(lineId.no).takeIf { no -> no != 0 } + ?: codeHash.compareTo(lineId.codeHash) } ?: -1 // TODO: check if it doesn't break something companion object { - private val serialVersionUID: Long = 8328353000L + private const val serialVersionUID: Long = 8328354000L } } 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 index 825aa14f131..2309477bc50 100644 --- 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 @@ -21,7 +21,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.write open class GenericReplCompilingEvaluatorBase( - val compiler: ReplCompiler, + val compiler: ReplCompilerWithoutCheck, val evaluator: ReplEvaluator, private val fallbackScriptArgs: ScriptArgsWithTypes? = null ) : ReplFullEvaluator { @@ -46,7 +46,7 @@ open class GenericReplCompilingEvaluatorBase( } ReplEvalResult.Error.CompileTime(compiled.message, compiled.location) } - is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete() + is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(compiled.message) is ReplCompileResult.CompiledClasses -> { val result = eval(state, compiled, scriptArgs, invokeWrapper) when (result) { @@ -75,8 +75,6 @@ open class GenericReplCompilingEvaluatorBase( override fun eval(state: IReplStageState<*>, compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult = evaluator.eval(state, compileResult, scriptArgs, invokeWrapper) - override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = compiler.check(state, codeLine) - override fun compileToEvaluable(state: IReplStageState<*>, codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes?): Pair { val compiled = compiler.compile(state, codeLine) return when (compiled) { @@ -96,7 +94,7 @@ open class GenericReplCompilingEvaluatorBase( } class GenericReplCompilingEvaluator( - compiler: ReplCompiler, + compiler: ReplCompilerWithoutCheck, baseClasspath: Iterable, baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader, fallbackScriptArgs: ScriptArgsWithTypes? = null, 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 54f15ced818..93ccec6f34e 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 @@ -25,7 +25,7 @@ const val KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY = "kotlin.script.engine" abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable { - protected abstract val replCompiler: ReplCompiler + protected abstract val replCompiler: ReplCompilerWithoutCheck protected abstract val replEvaluator: ReplFullEvaluator override fun eval(script: String, context: ScriptContext): Any? = compileAndEval(script, context) @@ -72,7 +72,7 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn val result = replCompiler.compile(state, codeLine) val compiled = when (result) { is ReplCompileResult.Error -> throw ScriptException("Error${result.locationString()}: ${result.message}") - is ReplCompileResult.Incomplete -> throw ScriptException("error: incomplete code") + is ReplCompileResult.Incomplete -> throw ScriptException("Error: incomplete code; ${result.message}") is ReplCompileResult.CompiledClasses -> result } return CompiledKotlinScript(this, codeLine, compiled) @@ -103,7 +103,7 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn throw ScriptException(result.message, result.location.path, result.location.line, result.location.column) else -> throw ScriptException(result.message) } - is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code") + is ReplEvalResult.Incomplete -> throw ScriptException("Error: incomplete code. ${result.message}") is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}") } } 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 5eef5a9b892..e8030e9db61 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 @@ -96,7 +96,7 @@ sealed class ReplCompileResult : Serializable { companion object { private val serialVersionUID: Long = 2L } } - class Incomplete : ReplCompileResult() { + class Incomplete(val message: String) : ReplCompileResult() { companion object { private val serialVersionUID: Long = 1L } } @@ -110,7 +110,9 @@ sealed class ReplCompileResult : Serializable { } } -interface ReplCompiler : ReplCompileAction, ReplCheckAction, CreateReplStageStateAction +interface ReplCompilerWithoutCheck : ReplCompileAction, CreateReplStageStateAction + +interface ReplCompiler : ReplCompilerWithoutCheck, ReplCheckAction // --- eval @@ -137,7 +139,7 @@ sealed class ReplEvalResult : Serializable { companion object { private val serialVersionUID: Long = 1L } } - class Incomplete : ReplEvalResult() { + class Incomplete(val message: String) : ReplEvalResult() { companion object { private val serialVersionUID: Long = 1L } } @@ -175,7 +177,7 @@ interface ReplAtomicEvalAction { invokeWrapper: InvokeWrapper? = null): ReplEvalResult } -interface ReplAtomicEvaluator : ReplAtomicEvalAction, ReplCheckAction +interface ReplAtomicEvaluator : ReplAtomicEvalAction interface ReplDelayedEvalAction { fun compileToEvaluable(state: IReplStageState<*>, diff --git a/compiler/daemon/daemon-client-new/src/org/jetbrains/kotlin/daemon/client/experimental/KotlinRemoteReplCompilerClientAsync.kt b/compiler/daemon/daemon-client-new/src/org/jetbrains/kotlin/daemon/client/experimental/KotlinRemoteReplCompilerClientAsync.kt index 12b10c6c3b2..b17391bc5e5 100644 --- a/compiler/daemon/daemon-client-new/src/org/jetbrains/kotlin/daemon/client/experimental/KotlinRemoteReplCompilerClientAsync.kt +++ b/compiler/daemon/daemon-client-new/src/org/jetbrains/kotlin/daemon/client/experimental/KotlinRemoteReplCompilerClientAsync.kt @@ -8,15 +8,12 @@ package org.jetbrains.kotlin.daemon.client.experimental import kotlinx.coroutines.runBlocking import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.repl.* -import org.jetbrains.kotlin.daemon.client.KotlinRemoteReplCompilerClient import org.jetbrains.kotlin.daemon.common.* -import org.jetbrains.kotlin.daemon.common.CompileServiceClientSide import org.jetbrains.kotlin.daemon.common.experimental.findCallbackServerSocket import org.jetbrains.kotlin.daemon.common.ReportCategory import org.jetbrains.kotlin.daemon.common.ReportSeverity import java.io.File import java.util.concurrent.locks.ReentrantReadWriteLock -import org.jetbrains.kotlin.daemon.client.RemoteReplCompilerState // TODO: reduce number of ports used then SOCKET_ANY_FREE_PORT is passed (same problem with other calls) diff --git a/compiler/testData/multiplatform/regressions/kt28385/output.txt b/compiler/testData/multiplatform/regressions/kt28385/output.txt index 426962dee24..6d9721f5fde 100644 --- a/compiler/testData/multiplatform/regressions/kt28385/output.txt +++ b/compiler/testData/multiplatform/regressions/kt28385/output.txt @@ -12,11 +12,11 @@ compiler/testData/multiplatform/regressions/kt28385/jvm.kt:5:6: error: expecting sdax = { ^ compiler/testData/multiplatform/regressions/kt28385/jvm.kt:5:8: error: expecting a top level declaration -sdax = { - ^ -compiler/testData/multiplatform/regressions/kt28385/jvm.kt:5:8: error: function declaration must have a name sdax = { ^ compiler/testData/multiplatform/regressions/kt28385/jvm.kt:3:1: error: property must be initialized val dasda ^ +compiler/testData/multiplatform/regressions/kt28385/jvm.kt:5:8: error: function declaration must have a name +sdax = { + ^ 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 e14ff180fd0..3dcbe1a7ff0 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt @@ -66,7 +66,7 @@ class KotlinJsr223JvmScriptEngine4Idea( private val messageCollector = MyMessageCollector() - override val replCompiler: ReplCompiler by lazy { + override val replCompiler: ReplCompilerWithoutCheck by lazy { KotlinRemoteReplCompilerClient( daemon, makeAutodeletingFlagFile("idea-jsr223-repl-session"), diff --git a/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/AbstractJsReplTest.kt b/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/AbstractJsReplTest.kt index 6bee2b27287..9213a955bed 100644 --- a/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/AbstractJsReplTest.kt +++ b/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/AbstractJsReplTest.kt @@ -10,7 +10,6 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult -import org.jetbrains.kotlin.cli.common.repl.ReplCompiler import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/LegacyReplTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/LegacyReplTest.kt index 5cb11e1f30a..1955ef6531b 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/LegacyReplTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/LegacyReplTest.kt @@ -21,8 +21,8 @@ import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator class LegacyReplTest : TestCase() { 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) + val res1 = repl.replCompiler.compile(repl.state, ReplCodeLine(0, 0, "val x =")) + TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCompileResult.Incomplete) assertEvalResult(repl, "val l1 = listOf(1 + 2)\nl1.first()", 3) @@ -54,8 +54,8 @@ class LegacyReplTest : TestCase() { 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 + val res0 = repl.replCompiler.compile(repl.state, codeLine0) + val res0c = res0 as? ReplCompileResult.CompiledClasses TestCase.assertNotNull("Unexpected compile result: $res0", res0c) } } @@ -125,7 +125,7 @@ internal class LegacyTestRepl : Closeable { fun nextCodeLine(code: String): ReplCodeLine = ReplCodeLine(currentLineCounter.getAndIncrement(), 0, code) val replCompiler: JvmReplCompiler by lazy { - JvmReplCompiler(simpleScriptCompilationConfiguration) + JvmReplCompiler(simpleScriptCompilationConfiguration, false) } val compiledEvaluator: ReplEvaluator by lazy { diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt index 35e8d638b7e..0a31e67df96 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -7,24 +7,16 @@ 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.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerImpl +import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase import org.junit.Assert import org.junit.Test import kotlin.script.experimental.api.* import kotlin.script.experimental.host.toScriptSource -import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator -import kotlin.script.experimental.jvm.baseClassLoader +import kotlin.script.experimental.jvm.BasicJvmReplEvaluator import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration -import kotlin.script.experimental.jvm.jvm class ReplTest : TestCase() { - companion object { - const val TEST_DATA_DIR = "libraries/scripting/jvm-host-test/testData" - } - @Test fun testCompileAndEval() { val out = captureOut { @@ -156,103 +148,98 @@ class ReplTest : TestCase() { limit = 100 ) } -} -fun evaluateInRepl( - snippets: Sequence, - compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration, - evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration, - limit: Int = 0 -): Sequence> { - val replCompilerProxy = - KJvmReplCompilerImpl(defaultJvmScriptingHostConfiguration) - val compilationState = replCompilerProxy.createReplCompilationState(compilationConfiguration) - val compilationHistory = BasicReplStageHistory() - val replEvaluator = BasicJvmScriptEvaluator() - var currentEvalConfig = evaluationConfiguration ?: ScriptEvaluationConfiguration() - val snipetsLimited = if (limit == 0) snippets else snippets.take(limit) - return snipetsLimited.mapIndexed { snippetNo, snippetText -> - val snippetSource = - snippetText.toScriptSource("Line_$snippetNo.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}") - val snippetId = ReplSnippetIdImpl(snippetNo, 0, snippetSource) - replCompilerProxy.compileReplSnippet(compilationState, snippetSource, snippetId, compilationHistory) - .onSuccess { - runBlocking { - replEvaluator(it, currentEvalConfig) - } - } - .onSuccess { - val snippetClass = it.returnValue.scriptClass - currentEvalConfig = ScriptEvaluationConfiguration(currentEvalConfig) { - previousSnippets.append(it.returnValue.scriptInstance) - if (snippetClass != null) { - jvm { - baseClassLoader(snippetClass.java.classLoader) - } + companion object { + private fun evaluateInRepl( + snippets: Sequence, + compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration, + evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration, + limit: Int = 0 + ): Sequence> { + val replCompiler = KJvmReplCompilerBase.create(defaultJvmScriptingHostConfiguration) + val replEvaluator = BasicJvmReplEvaluator() + val currentEvalConfig = evaluationConfiguration ?: ScriptEvaluationConfiguration() + val snipetsLimited = if (limit == 0) snippets else snippets.take(limit) + return snipetsLimited.mapIndexed { snippetNo, snippetText -> + val snippetSource = + snippetText.toScriptSource("Line_$snippetNo.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}") + runBlocking { replCompiler.compile(snippetSource, compilationConfiguration) } + .onSuccess { + runBlocking { replEvaluator.eval(it, currentEvalConfig) } } - } - it.asSuccess() - } - } -} - -fun checkEvaluateInReplDiags( - snippets: Sequence, - expected: Sequence>, - compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration, - evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration, - limit: Int = 0 -) { - val expectedIter = (if (limit == 0) expected else expected.take(limit)).iterator() - evaluateInRepl(snippets, compilationConfiguration, evaluationConfiguration, limit).forEachIndexed { index, res -> - val expectedRes = expectedIter.next() - when { - res is ResultWithDiagnostics.Failure && expectedRes is ResultWithDiagnostics.Failure -> { - Assert.assertTrue( - "#$index: Expected $expectedRes, got $res. Messages are different", - res.reports.map { it.message } == expectedRes.reports.map { it.message } - ) - Assert.assertTrue( - "#$index: Expected $expectedRes, got $res. Locations are different", - res.reports.map { it.location }.zip(expectedRes.reports.map { it.location }).all { - it.second == null || it.second == it.first + .onSuccess { + it.get().asSuccess() } - ) - } - res is ResultWithDiagnostics.Success && expectedRes is ResultWithDiagnostics.Success -> { - val expectedVal = expectedRes.value - when (val resVal = res.value.returnValue) { - is ResultValue.Value -> Assert.assertEquals( - "#$index: Expected $expectedVal, got $resVal", - expectedVal, - resVal.value - ) - is ResultValue.Unit -> Assert.assertTrue("#$index: Expected $expectedVal, got Unit", expectedVal == null) - is ResultValue.Error -> Assert.assertTrue( - "#$index: Expected $expectedVal, got Error: ${resVal.error}", - expectedVal is Throwable && expectedVal.message == resVal.error.message - ) - else -> Assert.assertTrue("#$index: Expected $expectedVal, got unknown result $resVal", expectedVal == null) - } - } - else -> { - Assert.fail("#$index: Expected $expectedRes, got $res") } } - } - if (expectedIter.hasNext()) { - Assert.fail("Expected ${expectedIter.next()} got end of results stream") + + fun checkEvaluateInReplDiags( + snippets: Sequence, + expected: Sequence>, + compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration, + evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration, + limit: Int = 0 + ) { + val expectedIter = (if (limit == 0) expected else expected.take(limit)).iterator() + evaluateInRepl(snippets, compilationConfiguration, evaluationConfiguration, limit).forEachIndexed { index, res -> + val expectedRes = expectedIter.next() + when { + res is ResultWithDiagnostics.Failure && expectedRes is ResultWithDiagnostics.Failure -> { + + val resReports = res.reports.filter { + it.code != ScriptDiagnostic.incompleteCode + } + Assert.assertTrue( + "#$index: Expected $expectedRes, got $res. Messages are different", + resReports.map { it.message } == expectedRes.reports.map { it.message } + ) + Assert.assertTrue( + "#$index: Expected $expectedRes, got $res. Locations are different", + resReports.map { it.location }.zip(expectedRes.reports.map { it.location }).all { + it.second == null || it.second == it.first + } + ) + } + res is ResultWithDiagnostics.Success && expectedRes is ResultWithDiagnostics.Success -> { + val expectedVal = expectedRes.value + val resVal = res.value.result + when (resVal) { + is ResultValue.Value -> Assert.assertEquals( + "#$index: Expected $expectedVal, got $resVal", + expectedVal, + resVal.value + ) + is ResultValue.Unit -> Assert.assertNull("#$index: Expected $expectedVal, got Unit", expectedVal) + is ResultValue.Error -> Assert.assertTrue( + "#$index: Expected $expectedVal, got Error: ${resVal.error}", + expectedVal is Throwable && expectedVal.message == resVal.error?.message + ) + else -> Assert.assertTrue("#$index: Expected $expectedVal, got unknown result $resVal", expectedVal == null) + } + } + else -> { + Assert.fail("#$index: Expected $expectedRes, got $res") + } + } + } + if (expectedIter.hasNext()) { + Assert.fail("Expected ${expectedIter.next()} got end of results stream") + } + } + + fun checkEvaluateInRepl( + snippets: Sequence, + expected: Sequence, + compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration, + evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration, + limit: Int = 0 + ) = checkEvaluateInReplDiags( + snippets, expected.map { ResultWithDiagnostics.Success(it) }, compilationConfiguration, evaluationConfiguration, limit + ) + + class TestReceiver( + @Suppress("unused") + val prop1: Int = 3 + ) } } - -fun checkEvaluateInRepl( - snippets: Sequence, - expected: Sequence, - compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration, - evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration, - limit: Int = 0 -) = checkEvaluateInReplDiags( - snippets, expected.map { ResultWithDiagnostics.Success(it) }, compilationConfiguration, evaluationConfiguration, limit -) - -class TestReceiver(val prop1: Int = 3) diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ResolveDependenciesTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ResolveDependenciesTest.kt index 8d838304362..29343cdae17 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ResolveDependenciesTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ResolveDependenciesTest.kt @@ -13,6 +13,7 @@ import kotlin.script.experimental.host.toScriptSource import kotlin.script.experimental.jvm.* import kotlin.script.experimental.jvm.util.classpathFromClass import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost +import kotlin.script.experimental.jvmhost.test.ReplTest.Companion.checkEvaluateInRepl class ResolveDependenciesTest : TestCase() { diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt index 87e70f3776c..1f635f97052 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt @@ -21,7 +21,6 @@ import java.net.URLClassLoader import java.nio.file.Files import java.util.concurrent.TimeUnit import java.util.jar.JarFile -import kotlin.reflect.KClass import kotlin.script.experimental.api.* import kotlin.script.experimental.host.BasicScriptingHost import kotlin.script.experimental.host.FileBasedScriptSource @@ -348,7 +347,7 @@ class ScriptingHostTest : TestCase() { assertTrue(compiledScript is ResultWithDiagnostics.Success) val jvmCompiledScript = compiledScript.valueOrNull()!! as KJvmCompiledScript - val jvmCompiledModule = jvmCompiledScript.compiledModule as KJvmCompiledModuleInMemoryImpl + val jvmCompiledModule = jvmCompiledScript.getCompiledModule() as KJvmCompiledModuleInMemoryImpl val bytes = jvmCompiledModule.compilerOutputFiles["SavedScript.class"]!! var classFileVersion: Int? = null diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223ScriptEngineImpl.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223ScriptEngineImpl.kt index 6076281dfc9..ceeb94bb092 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223ScriptEngineImpl.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223ScriptEngineImpl.kt @@ -6,6 +6,7 @@ package kotlin.script.experimental.jvmhost.jsr223 import org.jetbrains.kotlin.cli.common.repl.* +import org.jetbrains.kotlin.cli.common.repl.ReplCompilerWithoutCheck import java.util.concurrent.locks.ReentrantReadWriteLock import javax.script.ScriptContext import javax.script.ScriptEngineFactory @@ -61,8 +62,8 @@ class KotlinJsr223ScriptEngineImpl( } } - override val replCompiler: ReplCompiler by lazy { - JvmReplCompiler(compilationConfiguration) + override val replCompiler: ReplCompilerWithoutCheck by lazy { + JvmReplCompiler(compilationConfiguration, true) } private val localEvaluator by lazy { diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt index e62c625540a..ce475fb62b3 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt @@ -27,8 +27,8 @@ open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvalua try { if (compiledScript !is KJvmCompiledScript) return failure("Cannot generate classes: unsupported compiled script type $compiledScript") - val module = (compiledScript.compiledModule as? KJvmCompiledModuleInMemory) - ?: return failure("Cannot generate classes: unsupported module type ${compiledScript.compiledModule}") + val module = (compiledScript.getCompiledModule() as? KJvmCompiledModuleInMemory) + ?: return failure("Cannot generate classes: unsupported module type ${compiledScript.getCompiledModule()}") for ((path, bytes) in module.compilerOutputFiles) { File(outputDir, path).apply { if (!parentFile.isDirectory) { @@ -47,8 +47,8 @@ open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvalua } fun KJvmCompiledScript.saveToJar(outputJar: File) { - val module = (compiledModule as? KJvmCompiledModuleInMemory) - ?: throw IllegalArgumentException("Unsupported module type $compiledModule") + val module = (getCompiledModule() as? KJvmCompiledModuleInMemory) + ?: throw IllegalArgumentException("Unsupported module type ${getCompiledModule()}") val dependenciesFromScript = compilationConfiguration[ScriptCompilationConfiguration.dependencies] ?.filterIsInstance() ?.flatMap { it.classpath } 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 index 8894076db82..3285f29d087 100644 --- 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 @@ -5,66 +5,96 @@ package kotlin.script.experimental.jvmhost.repl +import kotlinx.coroutines.runBlocking +import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.cli.common.repl.* -import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerImpl +import org.jetbrains.kotlin.cli.common.repl.ReplCompilerWithoutCheck +import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerStageHistory import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerState -import org.jetbrains.kotlin.scripting.compiler.plugin.repl.KJvmReplCompilerProxy +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase 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.host.withDefaultsFrom import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration +import kotlin.script.experimental.jvm.util.isIncomplete /** * REPL Compilation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package */ class JvmReplCompiler( val scriptCompilationConfiguration: ScriptCompilationConfiguration, + val allowReInit: Boolean = true, val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration, - val replCompilerProxy: KJvmReplCompilerProxy = KJvmReplCompilerImpl( + var replCompiler: KJvmReplCompilerBase = KJvmReplCompilerBase.create( hostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration) ) -) : ReplCompiler { +) : ReplCompilerWithoutCheck { - override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = JvmReplCompilerState(replCompilerProxy, lock) + private val compilers = mutableListOf(replCompiler) - override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = state.lock.write { + override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = + if (allowReInit) { + JvmReplCompilerState({ replCompiler.createReplCompilationState(it, replCompiler.initAnalyzer) }, lock) + } else { + replCompiler.state + } + + override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = replCompiler.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 }) + val snippet = codeLine.toSourceCode(scriptCompilationConfiguration) + + if (allowReInit) { + replCompiler = compilers.find { historiesEq(it.history, replCompilerState.history) } ?: { + compilers.push( + KJvmReplCompilerBase.create( + hostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration) + ) + ) + compilers.last() + }() + } + + when (val res = runBlocking { replCompiler.compile(listOf(snippet), scriptCompilationConfiguration) }) { + is ResultWithDiagnostics.Success -> { + val lineId = LineId(codeLine.no, 0, snippet.hashCode()) + replCompilerState.apply { + lock.write { + val compilerHistory = history as JvmReplCompilerStageHistory<*> + compilerHistory.push(lineId, replCompiler.history.last().item) + } + } + ReplCompileResult.CompiledClasses( + lineId, + replCompiler.history.map { it.id }, + snippet.name!!, + emptyList(), + res.value.get().resultField != null, + emptyList(), + res.value.get().resultField?.second?.typeName, + res.value + ) + } + else -> { + val message = res.reports.joinToString("\n") { it.message } + if (res.isIncomplete()) { + ReplCompileResult.Incomplete(message) + } else { + ReplCompileResult.Error(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 }) - } + companion object { + fun historiesEq(history1: IReplStageHistory<*>, history2: IReplStageHistory<*>) = + history1.count() == history2.count() && + history1.zip(history2).all { + val (it1, it2) = it + it1.item === it2.item + } } } 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 index 6c72e136709..0a2502c0b30 100644 --- 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 @@ -7,6 +7,7 @@ package kotlin.script.experimental.jvmhost.repl import kotlinx.coroutines.runBlocking import org.jetbrains.kotlin.cli.common.repl.* +import org.jetbrains.kotlin.cli.common.repl.ReplEvaluator import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.write import kotlin.reflect.KClass @@ -15,6 +16,7 @@ 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 +import kotlin.script.experimental.util.LinkedSnippetImpl /** * REPL Evaluation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package @@ -35,8 +37,12 @@ class JvmReplEvaluator( ): ReplEvalResult = state.lock.write { val evalState = state.asState(JvmReplEvaluatorState::class.java) val history = evalState.history as ReplStageHistoryWithReplace - val compiledScript = (compileResult.data as? KJvmCompiledScript) - ?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compileResult.data}") + val compiledScriptList = (compileResult.data as? LinkedSnippetImpl<*>) + ?: return ReplEvalResult.Error.CompileTime("Unable to access compiled list script: ${compileResult.data}") + + val compiledScript = (compiledScriptList.get() as? KJvmCompiledScript) + ?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compiledScriptList.get()}") + val lastSnippetClass = history.peek()?.item?.first val historyBeforeSnippet = history.previousItems(compileResult.lineId).map { it.second }.toList() diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmReplEvaluator.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmReplEvaluator.kt new file mode 100644 index 00000000000..d710cc87c77 --- /dev/null +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmReplEvaluator.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.script.experimental.jvm + +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +import kotlin.reflect.KClass +import kotlin.script.experimental.api.* +import kotlin.script.experimental.jvm.util.SnippetsHistory +import kotlin.script.experimental.util.LinkedSnippet +import kotlin.script.experimental.util.LinkedSnippetImpl +import kotlin.script.experimental.util.add + +class BasicJvmReplEvaluator(val scriptEvaluator: ScriptEvaluator = BasicJvmScriptEvaluator()) : + ReplEvaluator { + override var lastEvaluatedSnippet: LinkedSnippetImpl? = null + private set + + private val history = SnippetsHistory?, Any?>() + + override suspend fun eval( + snippet: LinkedSnippet, + configuration: ScriptEvaluationConfiguration + ): ResultWithDiagnostics> { + + val lastSnippetClass = history.lastItem()?.first + val historyBeforeSnippet = history.items.map { it.second } + val currentConfiguration = ScriptEvaluationConfiguration(configuration) { + if (historyBeforeSnippet.isNotEmpty()) { + previousSnippets.put(historyBeforeSnippet) + } + if (lastSnippetClass != null) { + jvm { + baseClassLoader(lastSnippetClass.java.classLoader) + } + } + } + + val snippetVal = snippet.get() + val newEvalRes = when (val res = scriptEvaluator(snippetVal, currentConfiguration)) { + is ResultWithDiagnostics.Success -> { + val retVal = res.value.returnValue + when (retVal) { + is ResultValue.Error -> history.add(retVal.scriptClass, null) + is ResultValue.Value, is ResultValue.Unit -> history.add(retVal.scriptClass, retVal.scriptInstance) + } + KJvmEvaluatedSnippet(snippetVal, currentConfiguration, retVal) + } + else -> + KJvmEvaluatedSnippet(snippetVal, currentConfiguration, ResultValue.NotEvaluated) + } + + val newNode = lastEvaluatedSnippet.add(newEvalRes) + lastEvaluatedSnippet = newNode + return newNode.asSuccess() + } +} + +class KJvmEvaluatedSnippet( + override val compiledSnippet: CompiledSnippet, + override val configuration: ScriptEvaluationConfiguration, + override val result: ResultValue +) : EvaluatedSnippet 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 357487f06f8..27461bbefe0 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 @@ -5,6 +5,7 @@ package kotlin.script.experimental.jvm.impl +import org.jetbrains.annotations.TestOnly import java.io.* import java.net.URL import java.net.URLClassLoader @@ -39,13 +40,13 @@ internal class KJvmCompiledScriptData( companion object { @JvmStatic - private val serialVersionUID = 4L + private val serialVersionUID = 5L } } -class KJvmCompiledScript internal constructor( +open class KJvmCompiledScript internal constructor( internal var data: KJvmCompiledScriptData, - var compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept + internal var compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept ) : CompiledScript, Serializable { constructor( @@ -93,6 +94,9 @@ class KJvmCompiledScript internal constructor( ) } + @TestOnly + fun getCompiledModule() = compiledModule + private fun writeObject(outputStream: ObjectOutputStream) { outputStream.writeObject(data) outputStream.writeObject(compiledModule) @@ -177,7 +181,7 @@ fun KJvmCompiledScript.toBytes(): ByteArray { oos = ObjectOutputStream(bos) oos.writeObject(this) oos.flush() - return bos.toByteArray()!! + return bos.toByteArray() } finally { try { oos?.close() diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/SnippetsHistory.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/SnippetsHistory.kt new file mode 100644 index 00000000000..535f7c8e4a8 --- /dev/null +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/SnippetsHistory.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.script.experimental.jvm.util + +import java.io.Serializable +import java.util.* + + +typealias CompiledHistoryItem = Pair +typealias CompiledHistoryStorage = ArrayList> +typealias CompiledHistoryList = List> + +/* + WARNING: Not thread safe, the caller is assumed to lock access. + */ +open class SnippetsHistory(startingHistory: CompiledHistoryList = emptyList()) : Serializable { + protected val history: CompiledHistoryStorage = ArrayList(startingHistory) + + fun add(line: CompiledT, value: ResultT) { + history.add(line to value) + } + + fun lastItem(): CompiledHistoryItem? = history.lastOrNull() + fun lastValue(): ResultT? = lastItem()?.second + + val items: List> = history + + fun isEmpty(): Boolean = history.isEmpty() + fun isNotEmpty(): Boolean = history.isNotEmpty() + + companion object { + private const val serialVersionUID: Long = 8328353001L + } +} diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/diagnosticsHelpers.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/diagnosticsHelpers.kt new file mode 100644 index 00000000000..108c3a16a2d --- /dev/null +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/diagnosticsHelpers.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.script.experimental.jvm.util + +import kotlin.script.experimental.api.ResultWithDiagnostics +import kotlin.script.experimental.api.ScriptDiagnostic + +fun ResultWithDiagnostics.isIncomplete() = this.reports.any { it.code == ScriptDiagnostic.incompleteCode } + +fun ResultWithDiagnostics.isError() = this is ResultWithDiagnostics.Failure + diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/sourceCodeUtils.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/sourceCodeUtils.kt new file mode 100644 index 00000000000..7be3b9beba4 --- /dev/null +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/sourceCodeUtils.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.script.experimental.jvm.util + +import java.io.Serializable +import kotlin.script.experimental.api.SourceCode + +data class AbsSourceCodePosition(val line: Int, val col: Int, val absolutePos: Int) : Serializable + +internal fun String.findNth(s: String, n: Int, start: Int = 0): Int { + if (n < 1) return -1 + + var i = start + + for (k in 1..n) { + i = indexOf(s, i) + if (i == -1) return -1 + i += s.length + } + + return i - s.length +} + +fun Int.toSourceCodePosition(code: SourceCode): SourceCode.Position { + val substr = code.text.substring(0, this) + val line = 1 + substr.count { it == '\n' } + val sep = code.text.determineSep() + val col = 1 + substr.length - substr.lastIndexOf(sep) - sep.length + return SourceCode.Position(line, col, this) +} + +fun String.determineSep() = if (indexOf("\r\n") == -1) "\n" else "\r\n" + +fun SourceCode.Position.calcAbsolute(code: SourceCode): Int { + if (absolutePos != null) + return absolutePos!! + + if (line == 1) + return col - 1 + + val sep = code.text.determineSep() + return code.text.findNth(sep, line - 1) + sep.length + col - 1 +} \ No newline at end of file diff --git a/libraries/scripting/jvm/test/kotlin/script/experimental/jvm/test/calcAbsoluteTest.kt b/libraries/scripting/jvm/test/kotlin/script/experimental/jvm/test/calcAbsoluteTest.kt new file mode 100644 index 00000000000..444f9212cb3 --- /dev/null +++ b/libraries/scripting/jvm/test/kotlin/script/experimental/jvm/test/calcAbsoluteTest.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.script.experimental.jvm.test + +import org.junit.Assert +import org.junit.Test +import kotlin.script.experimental.api.SourceCode +import kotlin.script.experimental.jvm.util.calcAbsolute +import kotlin.script.experimental.jvm.util.toSourceCodePosition + +class CalcAbsoluteTest { + + @Test + fun testMultiline() { + val source = """ + abcdefg + hij + klmnopqrst + covid19 + uv + """.trimIndent().toSource() + + val pos = SourceCode.Position(4, 6) + + val absPos = pos.calcAbsolute(source) + + Assert.assertEquals('1', source.text[absPos]) + Assert.assertEquals(17, 17.toSourceCodePosition(source).calcAbsolute(source)) + } + + fun String.toSource() = SourceCodeTestImpl(this) + + class SourceCodeTestImpl(override val text: String) : SourceCode { + override val name: String? = null + override val locationId: String? = null + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-bom/pom.xml b/libraries/tools/kotlin-bom/pom.xml index 46c9ffdeb47..0597e21d7ac 100644 --- a/libraries/tools/kotlin-bom/pom.xml +++ b/libraries/tools/kotlin-bom/pom.xml @@ -159,6 +159,11 @@ kotlin-scripting-jvm-host ${kotlin.version} + + ${project.groupId} + kotlin-scripting-ide-services + ${kotlin.version} + ${project.groupId} diff --git a/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsJsr223Test.kt b/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsJsr223Test.kt index e9c77b9ad9a..294b2434d41 100644 --- a/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsJsr223Test.kt +++ b/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsJsr223Test.kt @@ -1,6 +1,6 @@ +package org.jetbrains.kotlin.mainKts.test + import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback -import org.jetbrains.kotlin.mainKts.test.TEST_DATA_ROOT -import org.jetbrains.kotlin.mainKts.test.captureOut import org.junit.Assert import org.junit.Test import javax.script.ScriptEngineManager diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerBase.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerBase.kt new file mode 100644 index 00000000000..a4368de7b15 --- /dev/null +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerBase.kt @@ -0,0 +1,248 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.scripting.compiler.plugin.impl + + +import com.intellij.openapi.Disposable +import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback +import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorBasedReporter +import org.jetbrains.kotlin.cli.common.repl.LineId +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.codegen.ClassBuilderFactories +import org.jetbrains.kotlin.codegen.KotlinCodegenFacade +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.descriptors.ScriptDescriptor +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerStageHistory +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerState +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase +import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider +import java.util.concurrent.atomic.AtomicInteger +import kotlin.script.experimental.api.* +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration +import kotlin.script.experimental.jvm.impl.KJvmCompiledScript +import kotlin.script.experimental.util.LinkedSnippet +import kotlin.script.experimental.util.LinkedSnippetImpl +import kotlin.script.experimental.util.add + +open class KJvmReplCompilerBase protected constructor( + protected val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration, + val initAnalyzer: (SharedScriptCompilationContext) -> AnalyzerT +) : ReplCompiler, ScriptCompiler { + val state = JvmReplCompilerState({ createReplCompilationState(it, initAnalyzer) }) + val history = JvmReplCompilerStageHistory(state) + protected val scriptPriority = AtomicInteger() + + override var lastCompiledSnippet: LinkedSnippetImpl? = null + protected set + + fun createReplCompilationState( + scriptCompilationConfiguration: ScriptCompilationConfiguration, + initAnalyzer: (SharedScriptCompilationContext) -> AnalyzerT /* = { ReplCodeAnalyzer1(it.environment) } */ + ): ReplCompilationState { + val context = withMessageCollectorAndDisposable(disposeOnSuccess = false) { messageCollector, disposable -> + createIsolatedCompilationContext( + scriptCompilationConfiguration, + hostConfiguration, + messageCollector, + disposable + ).asSuccess() + }.valueOr { throw IllegalStateException("Unable to initialize repl compiler:\n ${it.reports.joinToString("\n ")}") } + return ReplCompilationState(context, initAnalyzer) + } + + override suspend fun compile( + snippets: Iterable, + configuration: ScriptCompilationConfiguration + ): ResultWithDiagnostics> = + snippets.map { snippet -> + withMessageCollector(snippet) { messageCollector -> + val initialConfiguration = configuration.refineBeforeParsing(snippet).valueOr { + return it + } + + val compilationState = state.getCompilationState(initialConfiguration) + + val (context, errorHolder, snippetKtFile) = prepareForAnalyze( + snippet, + messageCollector, + compilationState, + checkSyntaxErrors = true + ).valueOr { return@withMessageCollector it } + + val (sourceFiles, sourceDependencies) = collectRefinedSourcesAndUpdateEnvironment( + context, + snippetKtFile, + messageCollector + ) + + val firstFailure = sourceDependencies.firstOrNull { it.sourceDependencies is ResultWithDiagnostics.Failure } + ?.let { it.sourceDependencies as ResultWithDiagnostics.Failure } + + if (firstFailure != null) + return firstFailure + + if (history.isEmpty()) { + val updatedConfiguration = ScriptDependenciesProvider.getInstance(context.environment.project) + ?.getScriptConfiguration(snippetKtFile)?.configuration + ?: context.baseScriptCompilationConfiguration + registerPackageFragmentProvidersIfNeeded( + updatedConfiguration, + context.environment + ) + } + + val no = scriptPriority.getAndIncrement() + + val analysisResult = + compilationState.analyzerEngine.analyzeReplLineWithImportedScripts(snippetKtFile, sourceFiles.drop(1), snippet, no) + AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder) + + val scriptDescriptor = when (analysisResult) { + is ReplCodeAnalyzerBase.ReplLineAnalysisResult.WithErrors -> return failure( + messageCollector + ) + is ReplCodeAnalyzerBase.ReplLineAnalysisResult.Successful -> { + (analysisResult.scriptDescriptor as? ScriptDescriptor) + ?: return failure( + snippet, + messageCollector, + "Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}" + ) + } + else -> return failure( + snippet, + messageCollector, + "Unexpected result ${analysisResult::class.java}" + ) + } + + val generationState = GenerationState.Builder( + snippetKtFile.project, + ClassBuilderFactories.BINARIES, + compilationState.analyzerEngine.module, + compilationState.analyzerEngine.trace.bindingContext, + sourceFiles, + compilationState.environment.configuration + ).build().apply { + scriptSpecific.earlierScriptsForReplInterpreter = history.map { it.item } + beforeCompile() + } + KotlinCodegenFacade.generatePackage(generationState, snippetKtFile.script!!.containingKtFile.packageFqName, sourceFiles) + + history.push(LineId(no, 0, snippet.hashCode()), scriptDescriptor) + + val dependenciesProvider = ScriptDependenciesProvider.getInstance(context.environment.project) + val compiledScript = + makeCompiledScript( + generationState, + snippet, + sourceFiles.first(), + sourceDependencies + ) { ktFile -> + dependenciesProvider?.getScriptConfiguration(ktFile)?.configuration + ?: context.baseScriptCompilationConfiguration + } + + lastCompiledSnippet = lastCompiledSnippet.add(compiledScript) + + lastCompiledSnippet?.asSuccess(messageCollector.diagnostics) + ?: failure( + snippet, + messageCollector, + "last compiled snippet should not be null" + ) + } + }.last() + + override suspend fun invoke( + script: SourceCode, + scriptCompilationConfiguration: ScriptCompilationConfiguration + ): ResultWithDiagnostics { + return when (val res = compile(script, scriptCompilationConfiguration)) { + is ResultWithDiagnostics.Success -> res.value.get().asSuccess(res.reports) + is ResultWithDiagnostics.Failure -> res + } + } + + protected data class AnalyzePreparationResult( + val context: SharedScriptCompilationContext, + val errorHolder: MessageCollectorBasedReporter, + val snippetKtFile: KtFile + ) + + protected fun prepareForAnalyze( + snippet: SourceCode, + parentMessageCollector: MessageCollector, + compilationState: JvmReplCompilerState.Compilation, + checkSyntaxErrors: Boolean + ): ResultWithDiagnostics = + withMessageCollector( + snippet, + parentMessageCollector + ) { messageCollector -> + val context = + (compilationState as? ReplCompilationState<*>)?.context + ?: return failure( + snippet, messageCollector, "Internal error: unknown parameter passed as compilationState: $compilationState" + ) + + setIdeaIoUseFallback() + + val errorHolder = object : MessageCollectorBasedReporter { + override val messageCollector = messageCollector + } + + val snippetKtFile = + getScriptKtFile( + snippet, + context.baseScriptCompilationConfiguration, + context.environment.project, + messageCollector + ) + .valueOr { return it } + + if (checkSyntaxErrors) { + val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(snippetKtFile, errorHolder) + if (syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof) return failure( + messageCollector, ScriptDiagnostic(ScriptDiagnostic.incompleteCode, "Incomplete code") + ) + if (syntaxErrorReport.isHasErrors) return failure( + messageCollector + ) + } + + return AnalyzePreparationResult( + context, + errorHolder, + snippetKtFile + ).asSuccess() + } + + companion object { + fun create(hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration) = + KJvmReplCompilerBase(hostConfiguration) { + ReplCodeAnalyzerBase(it.environment) + } + } + +} + +class ReplCompilationState( + val context: SharedScriptCompilationContext, + val analyzerInit: (context: SharedScriptCompilationContext) -> AnalyzerT +) : JvmReplCompilerState.Compilation { + override val disposable: Disposable? get() = context.disposable + override val baseScriptCompilationConfiguration: ScriptCompilationConfiguration get() = context.baseScriptCompilationConfiguration + override val environment: KotlinCoreEnvironment get() = context.environment + override val analyzerEngine: AnalyzerT by lazy { + // ReplCodeAnalyzer1(context.environment) + analyzerInit(context) + } +} diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerImpl.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerImpl.kt deleted file mode 100644 index 8e026e3d61c..00000000000 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/KJvmReplCompilerImpl.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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 org.jetbrains.kotlin.scripting.compiler.plugin.impl - -import com.intellij.openapi.Disposable -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.IReplStageHistory -import org.jetbrains.kotlin.cli.common.repl.LineId -import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine -import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment -import org.jetbrains.kotlin.codegen.ClassBuilderFactories -import org.jetbrains.kotlin.codegen.KotlinCodegenFacade -import org.jetbrains.kotlin.codegen.state.GenerationState -import org.jetbrains.kotlin.descriptors.ScriptDescriptor -import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerState -import org.jetbrains.kotlin.scripting.compiler.plugin.repl.KJvmReplCompilerProxy -import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer -import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider -import kotlin.script.experimental.api.* -import kotlin.script.experimental.host.ScriptingHostConfiguration - -class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvmReplCompilerProxy { - - override fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation { - val context = withMessageCollectorAndDisposable(disposeOnSuccess = false) { messageCollector, disposable -> - createIsolatedCompilationContext( - scriptCompilationConfiguration, - hostConfiguration, - messageCollector, - disposable - ).asSuccess() - }.valueOr { throw IllegalStateException("Unable to initialize repl compiler:\n ${it.reports.joinToString("\n ")}") } - return ReplCompilationState(context) - } - - override fun checkSyntax( - script: SourceCode, - scriptCompilationConfiguration: ScriptCompilationConfiguration, - project: Project - ): ResultWithDiagnostics = - withMessageCollector(script) { messageCollector -> - val ktFile = getScriptKtFile( - script, - scriptCompilationConfiguration, - project, - messageCollector - ) - .valueOr { return it } - val errorHolder = object : MessageCollectorBasedReporter { - override val messageCollector = messageCollector - } - val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(ktFile, errorHolder) - when { - syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> false.asSuccess(messageCollector.diagnostics) - syntaxErrorReport.isHasErrors -> failure(messageCollector) - else -> true.asSuccess() - } - } - - 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 -> - - val context = (compilationState as? ReplCompilationState)?.context - ?: return failure( - snippet, messageCollector, "Internal error: unknown parameter passed as compilationState: $compilationState" - ) - - setIdeaIoUseFallback() - - // NOTE: converting between REPL entities from compiler and "new" scripting entities - // TODO: (big) move REPL API from compiler to the new scripting infrastructure and streamline ops - val codeLine = makeReplCodeLine(snippetId, snippet) - - val errorHolder = object : MessageCollectorBasedReporter { - override val messageCollector = messageCollector - } - - val snippetKtFile = - getScriptKtFile( - snippet, - context.baseScriptCompilationConfiguration, - context.environment.project, - messageCollector - ) - .valueOr { return it } - - val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(snippetKtFile, errorHolder) - if (syntaxErrorReport.isHasErrors) return failure(messageCollector) - - val (sourceFiles, sourceDependencies) = collectRefinedSourcesAndUpdateEnvironment( - context, - snippetKtFile, - messageCollector - ) - - val firstFailure = sourceDependencies.firstOrNull { it.sourceDependencies is ResultWithDiagnostics.Failure } - ?.let { it.sourceDependencies as ResultWithDiagnostics.Failure } - - if (firstFailure != null) - return firstFailure - - if (history.isEmpty()) { - val updatedConfiguration = ScriptDependenciesProvider.getInstance(context.environment.project) - ?.getScriptConfiguration(snippetKtFile)?.configuration - ?: context.baseScriptCompilationConfiguration - registerPackageFragmentProvidersIfNeeded(updatedConfiguration, context.environment) - } - - val analysisResult = - compilationState.analyzerEngine.analyzeReplLineWithImportedScripts(snippetKtFile, sourceFiles.drop(1), codeLine) - AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder) - - val scriptDescriptor = when (analysisResult) { - is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return failure( - messageCollector - ) - is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> { - (analysisResult.scriptDescriptor as? ScriptDescriptor) - ?: return failure( - snippet, - messageCollector, - "Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}" - ) - } - else -> return failure( - snippet, - messageCollector, - "Unexpected result ${analysisResult::class.java}" - ) - } - - val generationState = GenerationState.Builder( - snippetKtFile.project, - ClassBuilderFactories.BINARIES, - compilationState.analyzerEngine.module, - compilationState.analyzerEngine.trace.bindingContext, - sourceFiles, - compilationState.environment.configuration - ).build().apply { - scriptSpecific.earlierScriptsForReplInterpreter = history.map { it.item } - beforeCompile() - } - KotlinCodegenFacade.generatePackage(generationState, snippetKtFile.script!!.containingKtFile.packageFqName, sourceFiles) - - history.push(LineId(codeLine), scriptDescriptor) - - val dependenciesProvider = ScriptDependenciesProvider.getInstance(context.environment.project) - val compiledScript = - makeCompiledScript( - generationState, - snippet, - sourceFiles.first(), - sourceDependencies - ) { ktFile -> - dependenciesProvider?.getScriptConfiguration(ktFile)?.configuration - ?: context.baseScriptCompilationConfiguration - } - - compiledScript.asSuccess(messageCollector.diagnostics) - } -} - -internal class ReplCompilationState(val context: SharedScriptCompilationContext) : JvmReplCompilerState.Compilation { - override val disposable: Disposable? get() = context.disposable - override val baseScriptCompilationConfiguration: ScriptCompilationConfiguration get() = context.baseScriptCompilationConfiguration - override val environment: KotlinCoreEnvironment get() = context.environment - override val analyzerEngine: ReplCodeAnalyzer by lazy { - ReplCodeAnalyzer(context.environment) - } -} - -internal fun makeReplCodeLine(id: ReplSnippetId, code: SourceCode): ReplCodeLine = - ReplCodeLine(id.no, id.generation, code.text) - diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt index 2ec8f74b79e..d8d49ec3365 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt @@ -52,14 +52,14 @@ import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvm.util.KotlinJars import kotlin.script.experimental.jvm.withUpdatedClasspath -internal class SharedScriptCompilationContext( +class SharedScriptCompilationContext( val disposable: Disposable?, val baseScriptCompilationConfiguration: ScriptCompilationConfiguration, val environment: KotlinCoreEnvironment, val ignoredOptionsReportingState: IgnoredOptionsReportingState ) -internal fun createIsolatedCompilationContext( +fun createIsolatedCompilationContext( baseScriptCompilationConfiguration: ScriptCompilationConfiguration, hostConfiguration: ScriptingHostConfiguration, messageCollector: ScriptDiagnosticsMessageCollector, diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt index 27e724336e2..46dd7f7a1fa 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt @@ -18,7 +18,7 @@ import kotlin.script.experimental.api.ScriptDiagnostic import kotlin.script.experimental.api.SourceCode import kotlin.script.experimental.api.asErrorDiagnostics -internal class ScriptDiagnosticsMessageCollector(private val parentMessageCollector: MessageCollector?) : MessageCollector { +class ScriptDiagnosticsMessageCollector(private val parentMessageCollector: MessageCollector?) : MessageCollector { private val _diagnostics = arrayListOf() @@ -78,17 +78,17 @@ private fun ScriptDiagnostic.Severity.toCompilerMessageSeverity(): CompilerMessa ScriptDiagnostic.Severity.FATAL -> CompilerMessageSeverity.EXCEPTION } -internal fun failure( +fun failure( messageCollector: ScriptDiagnosticsMessageCollector, vararg diagnostics: ScriptDiagnostic ): ResultWithDiagnostics.Failure = ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics) -internal fun failure( +fun failure( script: SourceCode, messageCollector: ScriptDiagnosticsMessageCollector, message: String ): ResultWithDiagnostics.Failure = failure(messageCollector, message.asErrorDiagnostics(path = script.locationId)) -internal class IgnoredOptionsReportingState { +class IgnoredOptionsReportingState { var currentArguments = K2JVMCompilerArguments() } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/jvmCompilationUtil.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/jvmCompilationUtil.kt index 0871e02d332..61a668b1d23 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/jvmCompilationUtil.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/jvmCompilationUtil.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.scripting.compiler.plugin.dependencies.ScriptsCompil import org.jetbrains.kotlin.scripting.resolve.ScriptLightVirtualFile import org.jetbrains.kotlin.scripting.scriptFileName import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import java.io.Serializable import java.util.* import kotlin.script.experimental.api.* import kotlin.script.experimental.host.FileBasedScriptSource @@ -33,7 +34,7 @@ internal fun makeCompiledModule(generationState: GenerationState) = .associateTo(sortedMapOf()) { it.relativePath to it.asByteArray() } ) -internal inline fun withMessageCollectorAndDisposable( +inline fun withMessageCollectorAndDisposable( script: SourceCode? = null, parentMessageCollector: MessageCollector? = null, disposable: Disposable = Disposer.newDisposable(), @@ -57,7 +58,7 @@ internal inline fun withMessageCollectorAndDisposable( } } -internal inline fun withMessageCollector( +inline fun withMessageCollector( script: SourceCode? = null, parentMessageCollector: MessageCollector? = null, body: (ScriptDiagnosticsMessageCollector) -> ResultWithDiagnostics @@ -99,6 +100,16 @@ internal fun getScriptKtFile( } } +class SourceCodeImpl(file: KtFile) : SourceCode, Serializable { + override val text: String = file.text + override val name: String? = file.name + override val locationId: String? = file.virtualFilePath + + companion object { + private const val serialVersionUID = 1L + } +} + internal fun makeCompiledScript( generationState: GenerationState, script: SourceCode, @@ -122,7 +133,7 @@ internal fun makeCompiledScript( sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.valueOrThrow()?.mapNotNull { sourceFile -> sourceFile.declarations.firstIsInstanceOrNull()?.let { KJvmCompiledScript( - containingKtFile.virtualFile?.path, + containingKtFile.virtualFilePath, getScriptConfiguration(sourceFile), it.fqName.asString(), null, diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericCompilerState.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericCompilerState.kt index e76773af37e..0e888800544 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericCompilerState.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericCompilerState.kt @@ -11,29 +11,35 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.scripting.compiler.plugin.repl.messages.DiagnosticMessageHolder import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.write import kotlin.script.experimental.dependencies.ScriptDependencies class ReplCompilerStageHistory(private val state: GenericReplCompilerState) : BasicReplStageHistory(state.lock) { override fun reset(): Iterable { - val removedCompiledLines = super.reset() - val removedAnalyzedLines = state.analyzerEngine.reset() + lock.write { + val removedCompiledLines = super.reset() + val removedAnalyzedLines = state.analyzerEngine.reset() - checkConsistent(removedCompiledLines, removedAnalyzedLines) - return removedCompiledLines + checkConsistent(removedCompiledLines, removedAnalyzedLines) + return removedCompiledLines + } } override fun resetTo(id: ILineId): Iterable { - val removedCompiledLines = super.resetTo(id) - val removedAnalyzedLines = state.analyzerEngine.resetToLine(id) + lock.write { + val removedCompiledLines = super.resetTo(id) + val removedAnalyzedLines = state.analyzerEngine.resetToLine(id) - checkConsistent(removedCompiledLines, removedAnalyzedLines) - return removedCompiledLines + checkConsistent(removedCompiledLines, removedAnalyzedLines) + return removedCompiledLines + } } - private fun checkConsistent(removedCompiledLines: Iterable, removedAnalyzedLines: List) { + + private fun checkConsistent(removedCompiledLines: Iterable, removedAnalyzedLines: List) { removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) -> - if (removedCompiledLine != LineId(removedAnalyzedLine)) { + if (removedCompiledLine.no != removedAnalyzedLine.no) { throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine") } } @@ -59,7 +65,7 @@ class GenericReplCompilerState(environment: KotlinCoreEnvironment, override val override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get() - val analyzerEngine = ReplCodeAnalyzer(environment) + val analyzerEngine = ReplCodeAnalyzerBase(environment) var lastDependencies: ScriptDependencies? = null } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt index db73dc484ab..868f0ffb87a 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt @@ -59,7 +59,7 @@ open class GenericReplCompiler( if (compilerState.lastLineState == null || compilerState.lastLineState!!.codeLine != codeLine) { val res = checker.check(state, codeLine) when (res) { - is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete() + is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete("Code is incomplete") is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location) is ReplCheckResult.Ok -> { } // continue @@ -81,10 +81,10 @@ open class GenericReplCompiler( val analysisResult = compilerState.analyzerEngine.analyzeReplLine(psiFile, codeLine) AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder) val scriptDescriptor = when (analysisResult) { - is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> { + is ReplCodeAnalyzerBase.ReplLineAnalysisResult.WithErrors -> { return ReplCompileResult.Error(errorHolder.renderMessage()) } - is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> { + is ReplCodeAnalyzerBase.ReplLineAnalysisResult.Successful -> { (analysisResult.scriptDescriptor as? ScriptDescriptor) ?: error("Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}") } @@ -108,12 +108,12 @@ open class GenericReplCompiler( setOf(psiFile.script!!.containingKtFile) ) - compilerState.history.push(LineId(codeLine), scriptDescriptor) + compilerState.history.push(LineId(codeLine.no, 0, codeLine.hashCode()), scriptDescriptor) val classes = generationState.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) } return ReplCompileResult.CompiledClasses( - LineId(codeLine), + LineId(codeLine.no, 0, codeLine.hashCode()), compilerState.history.map { it.id }, scriptDescriptor.name.identifier, classes, diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplCodeAnalyzer.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplCodeAnalyzer.kt index df5779ff3ea..9cc41784e41 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplCodeAnalyzer.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplCodeAnalyzer.kt @@ -7,13 +7,12 @@ package org.jetbrains.kotlin.scripting.compiler.plugin.repl -import org.jetbrains.kotlin.cli.common.repl.CompiledReplCodeLine import org.jetbrains.kotlin.cli.common.repl.ILineId import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine -import org.jetbrains.kotlin.cli.common.repl.ReplHistory import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM +import org.jetbrains.kotlin.container.ComponentProvider import org.jetbrains.kotlin.container.get import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl @@ -33,23 +32,30 @@ 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.scripting.definitions.ScriptPriorities +import kotlin.script.experimental.api.SourceCode +import kotlin.script.experimental.jvm.util.CompiledHistoryItem +import kotlin.script.experimental.jvm.util.CompiledHistoryList +import kotlin.script.experimental.jvm.util.SnippetsHistory -class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) { - private val topDownAnalysisContext: TopDownAnalysisContext - private val topDownAnalyzer: LazyTopDownAnalyzer - private val resolveSession: ResolveSession - private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory - private val replState = ResettableAnalyzerState() +open class ReplCodeAnalyzerBase( + environment: KotlinCoreEnvironment, + val trace: BindingTraceContext = NoScopeRecordCliBindingTrace() +) { + protected val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory + + protected val container: ComponentProvider + protected val topDownAnalysisContext: TopDownAnalysisContext + protected val topDownAnalyzer: LazyTopDownAnalyzer + protected val resolveSession: ResolveSession + protected val replState = ResettableAnalyzerState() val module: ModuleDescriptorImpl - val trace: BindingTraceContext = NoScopeRecordCliBindingTrace() - 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) - val container = TopDownAnalyzerFacadeForJVM.createContainer( + container = TopDownAnalyzerFacadeForJVM.createContainer( environment.project, emptyList(), trace, @@ -71,7 +77,10 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) { val scriptDescriptor: ClassDescriptorWithResolutionScopes? val diagnostics: Diagnostics - data class Successful(override val scriptDescriptor: ClassDescriptorWithResolutionScopes, override val diagnostics: Diagnostics) : + data class Successful( + override val scriptDescriptor: ClassDescriptorWithResolutionScopes, + override val diagnostics: Diagnostics + ) : ReplLineAnalysisResult data class WithErrors(override val diagnostics: Diagnostics) : @@ -80,9 +89,9 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) { } } - fun resetToLine(lineId: ILineId): List = replState.resetToLine(lineId) + fun resetToLine(lineId: ILineId): List = replState.resetToLine(lineId) - fun reset(): List = replState.reset() + fun reset(): List = replState.reset() fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult { topDownAnalysisContext.scripts.clear() @@ -90,31 +99,38 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) { psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no) - return doAnalyze(psiFile, emptyList(), codeLine) + return doAnalyze(psiFile, emptyList(), codeLine.toSourceCode()) } - fun analyzeReplLineWithImportedScripts(psiFile: KtFile, importedScripts: List, codeLine: ReplCodeLine): ReplLineAnalysisResult { + fun analyzeReplLineWithImportedScripts( + psiFile: KtFile, + importedScripts: List, + codeLine: SourceCode, + priority: Int + ): ReplLineAnalysisResult { topDownAnalysisContext.scripts.clear() trace.clearDiagnostics() - psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no) + psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, priority) - return doAnalyze(psiFile, importedScripts, codeLine) + return doAnalyze(psiFile, importedScripts, codeLine.addNo(priority)) } - private fun doAnalyze(linePsi: KtFile, importedScripts: List, codeLine: ReplCodeLine): ReplLineAnalysisResult { + private fun doAnalyze(linePsi: KtFile, importedScripts: List, codeLine: SourceCodeByReplLine): ReplLineAnalysisResult { scriptDeclarationFactory.setDelegateFactory( FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi) + importedScripts) ) - replState.submitLine(linePsi, codeLine) + replState.submitLine(linePsi) val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts) val diagnostics = trace.bindingContext.diagnostics val hasErrors = diagnostics.any { it.severity == Severity.ERROR } return if (hasErrors) { - replState.lineFailure(linePsi, codeLine) - ReplLineAnalysisResult.WithErrors(diagnostics) + replState.lineFailure(linePsi) + ReplLineAnalysisResult.WithErrors( + diagnostics + ) } else { val scriptDescriptor = context.scripts[linePsi.script]!! replState.lineSuccess(linePsi, codeLine, scriptDescriptor) @@ -123,10 +139,9 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) { diagnostics ) } - } - private class ScriptMutableDeclarationProviderFactory : DeclarationProviderFactory { + protected class ScriptMutableDeclarationProviderFactory : DeclarationProviderFactory { private lateinit var delegateFactory: DeclarationProviderFactory private lateinit var rootPackageProvider: AdaptablePackageMemberDeclarationProvider @@ -175,28 +190,31 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) { } } + data class CompiledCode(val className: String, val source: SourceCodeByReplLine) + // TODO: merge with org.jetbrains.kotlin.resolve.repl.ReplState when switching to new REPL infrastructure everywhere // TODO: review its place in the extracted state infrastructure (now the analyzer itself is a part of the state) class ResettableAnalyzerState { - private val successfulLines = ReplHistory() + private val successfulLines = ResettableSnippetsHistory() private val submittedLines = hashMapOf() - fun resetToLine(lineId: ILineId): List { - val removed = successfulLines.resetToLine(lineId.no) + fun resetToLine(lineId: ILineId): List { + val removed = successfulLines.resetToLine(lineId) removed.forEach { submittedLines.remove(it.second.linePsi) } return removed.map { it.first } } - fun reset(): List { + fun reset(): List { submittedLines.clear() return successfulLines.reset().map { it.first } } - fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) { - val line = LineInfo.SubmittedLine( - ktFile, - successfulLines.lastValue() - ) + fun submitLine(ktFile: KtFile) { + val line = + LineInfo.SubmittedLine( + ktFile, + successfulLines.lastValue() + ) submittedLines[ktFile] = line ktFile.fileScopesCustomizer = object : FileScopesCustomizer { override fun createFileScopes(fileScopeFactory: FileScopeFactory): FileScopes { @@ -205,7 +223,7 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) { } } - fun lineSuccess(ktFile: KtFile, codeLine: ReplCodeLine, scriptDescriptor: ClassDescriptorWithResolutionScopes) { + fun lineSuccess(ktFile: KtFile, codeLine: SourceCodeByReplLine, scriptDescriptor: ClassDescriptorWithResolutionScopes) { val successfulLine = LineInfo.SuccessfulLine( ktFile, @@ -213,10 +231,15 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) { scriptDescriptor ) submittedLines[ktFile] = successfulLine - successfulLines.add(CompiledReplCodeLine(ktFile.name, codeLine), successfulLine) + successfulLines.add( + CompiledCode( + ktFile.name, + codeLine + ), successfulLine + ) } - fun lineFailure(ktFile: KtFile, codeLine: ReplCodeLine) { + fun lineFailure(ktFile: KtFile) { submittedLines[ktFile] = LineInfo.FailedLine( ktFile, @@ -251,3 +274,34 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) { } } } + +fun ReplCodeLine.toSourceCode() = SourceCodeByReplLine(code, no) +internal fun SourceCode.addNo(no: Int) = SourceCodeByReplLine(text, no, name, locationId) + +data class SourceCodeByReplLine( + override val text: String, + val no: Int, + override val name: String? = null, + override val locationId: String? = null +) : SourceCode + +private typealias ReplSourceHistoryList = List> + +@Deprecated("This functionality is left for backwards compatibility only", ReplaceWith("SnippetsHistory")) +private class ResettableSnippetsHistory(startingHistory: CompiledHistoryList = emptyList()) : + SnippetsHistory(startingHistory) { + + fun resetToLine(line: ILineId): ReplSourceHistoryList { + val removed = arrayListOf>() + while ((history.lastOrNull()?.first?.source?.no ?: -1) > line.no) { + removed.add(history.removeAt(history.size - 1).let { Pair(it.first.source, it.second) }) + } + return removed.reversed() + } + + fun reset(): ReplSourceHistoryList { + val removed = history.map { Pair(it.first.source, it.second) } + history.clear() + return removed + } +} diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplInterpreter.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplInterpreter.kt index 9bf26373eda..923950df763 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplInterpreter.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplInterpreter.kt @@ -76,7 +76,7 @@ class ReplInterpreter( } // TODO: add script definition with project-based resolving for IDEA repl - private val scriptCompiler: ReplCompiler by lazy { + private val scriptCompiler: ReplCompilerWithoutCheck by lazy { GenericReplCompiler( disposable, REPL_LINE_AS_SCRIPT_DEFINITION, diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/jvmReplCompilation.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/jvmReplCompilation.kt index 87ab9f63b54..6f98eb39620 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/jvmReplCompilation.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/jvmReplCompilation.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.scripting.compiler.plugin.repl import com.intellij.openapi.Disposable -import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import org.jetbrains.kotlin.cli.common.repl.* import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment @@ -15,54 +14,11 @@ import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.write 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 - - - fun compileReplSnippet( - compilationState: JvmReplCompilerState.Compilation, - snippet: SourceCode, - snippetId: ReplSnippetId, - history: IReplStageHistory - ): ResultWithDiagnostics -} - -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, +class JvmReplCompilerState( + val createCompilation: (ScriptCompilationConfiguration) -> CompilationT, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock() ) : IReplStageState { @@ -80,29 +36,29 @@ class JvmReplCompilerState( } } - fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): Compilation = lock.write { + fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): CompilationT = lock.write { if (_compilation == null) { initializeCompilation(scriptCompilationConfiguration) } _compilation!! } - internal val compilation: Compilation + internal val compilation: CompilationT get() = _compilation ?: throw IllegalStateException("Compilation state is either not initializad or already destroyed") - private var _compilation: Compilation? = null + private var _compilation: CompilationT? = 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) + _compilation = createCompilation(scriptCompilationConfiguration) } interface Compilation { val disposable: Disposable? val baseScriptCompilationConfiguration: ScriptCompilationConfiguration val environment: KotlinCoreEnvironment - val analyzerEngine: ReplCodeAnalyzer + val analyzerEngine: ReplCodeAnalyzerBase } } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsCoreScriptingCompiler.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsCoreScriptingCompiler.kt index 5fcc069d7dc..88ba2949846 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsCoreScriptingCompiler.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsCoreScriptingCompiler.kt @@ -25,7 +25,7 @@ import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.ir.util.generateTypicalIrProviderList import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator -import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase import org.jetbrains.kotlin.serialization.js.ModuleKind import kotlin.script.experimental.api.valueOr import kotlin.script.experimental.host.StringScriptSource @@ -35,7 +35,7 @@ class JsCoreScriptingCompiler( private val nameTables: NameTables, private val symbolTable: SymbolTable, private val dependencyDescriptors: List, - private val replState: ReplCodeAnalyzer.ResettableAnalyzerState = ReplCodeAnalyzer.ResettableAnalyzerState() + private val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState = ReplCodeAnalyzerBase.ResettableAnalyzerState() ) { fun compile(codeLine: ReplCodeLine): ReplCompileResult { val snippet = codeLine.code @@ -91,6 +91,6 @@ class JsCoreScriptingCompiler( val code = generateJsCode(context, irModuleFragment, nameTables) - return createCompileResult(LineId(codeLine), code) + return createCompileResult(LineId(codeLine.no, 0, codeLine.hashCode()), code) } } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplCodeAnalyzer.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplCodeAnalyzer.kt index 041a2579350..5b7b01ee554 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplCodeAnalyzer.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplCodeAnalyzer.kt @@ -11,26 +11,27 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.scripting.compiler.plugin.impl.AbstractJsScriptlikeCodeAnalyser -import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.toSourceCode import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities class JsReplCodeAnalyzer( environment: KotlinCoreEnvironment, dependencies: List, - private val replState: ReplCodeAnalyzer.ResettableAnalyzerState + private val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState ) : AbstractJsScriptlikeCodeAnalyser(environment, dependencies) { fun analyzeReplLine(linePsi: KtFile, codeLine: ReplCodeLine): AnalysisResult { linePsi.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no) - replState.submitLine(linePsi, codeLine) + replState.submitLine(linePsi) val result = analysisImpl(linePsi) return if (result.isSuccess) { - replState.lineSuccess(linePsi, codeLine, result.script) + replState.lineSuccess(linePsi, codeLine.toSourceCode(), result.script) AnalysisResult.success(result.bindingContext, result.moduleDescriptor) } else { - replState.lineFailure(linePsi, codeLine) + replState.lineFailure(linePsi) AnalysisResult.compilationError(result.bindingContext) } } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplCompiler.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplCompiler.kt index 3d55a39be6e..4a7325318dc 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplCompiler.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplCompiler.kt @@ -11,7 +11,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc import org.jetbrains.kotlin.ir.backend.js.utils.NameTables import org.jetbrains.kotlin.ir.util.SymbolTable -import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase import java.util.concurrent.locks.ReentrantReadWriteLock // Used to compile REPL code lines @@ -22,7 +22,7 @@ class JsReplCompiler(private val environment: KotlinCoreEnvironment) : ReplCompi lock, NameTables(emptyList()), readLibrariesFromConfiguration(environment.configuration), - ReplCodeAnalyzer.ResettableAnalyzerState(), + ReplCodeAnalyzerBase.ResettableAnalyzerState(), SymbolTable(IdSignatureDescriptor(JsManglerDesc)) ) } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplUtils.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplUtils.kt index 8bf781ca82c..37604efead2 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplUtils.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/repl/js/JsReplUtils.kt @@ -27,7 +27,7 @@ import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.library.resolver.TopologicalLibraryOrder import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtScript -import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys import org.jetbrains.kotlin.scripting.resolve.ScriptLightVirtualFile import org.jetbrains.kotlin.util.Logger @@ -132,7 +132,7 @@ fun readLibrariesFromConfiguration(configuration: CompilerConfiguration): List, - val replState: ReplCodeAnalyzer.ResettableAnalyzerState, + val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState, val symbolTable: SymbolTable ) : JsCompilationState(lock, nameTables, dependencies) diff --git a/plugins/scripting/scripting-ide-services-embeddable/build.gradle.kts b/plugins/scripting/scripting-ide-services-embeddable/build.gradle.kts new file mode 100644 index 00000000000..925b8a36922 --- /dev/null +++ b/plugins/scripting/scripting-ide-services-embeddable/build.gradle.kts @@ -0,0 +1,27 @@ +description = "Kotlin Scripting Compiler extension providing code completion and static analysis (for using in embeddable mode)" + +plugins { java } + +dependencies { + embedded(project(":kotlin-scripting-ide-services")) { isTransitive = false } + embedded(project(":idea:ide-common")) { isTransitive = false } + runtime(project(":kotlin-script-runtime")) + runtime(kotlinStdlib()) + runtime(project(":kotlin-scripting-common")) + runtime(project(":kotlin-scripting-jvm")) + runtime(project(":kotlin-compiler-embeddable")) + runtime(project(":kotlin-scripting-compiler-embeddable")) +} + +sourceSets { + "main" {} + "test" {} +} + +publish() + +noDefaultJar() + +runtimeJar(rewriteDefaultJarDepsToShadedCompiler()) +sourcesJar() +javadocJar() diff --git a/plugins/scripting/scripting-ide-services-test/build.gradle.kts b/plugins/scripting/scripting-ide-services-test/build.gradle.kts new file mode 100644 index 00000000000..df01199bffa --- /dev/null +++ b/plugins/scripting/scripting-ide-services-test/build.gradle.kts @@ -0,0 +1,50 @@ + +plugins { + kotlin("jvm") +} + +jvmTarget = "1.8" + +val allTestsRuntime by configurations.creating +val testCompile by configurations +testCompile.extendsFrom(allTestsRuntime) +val embeddableTestRuntime by configurations.creating { + extendsFrom(allTestsRuntime) +} + +dependencies { + allTestsRuntime(commonDep("junit")) + testCompile(project(":kotlin-scripting-ide-services")) + testCompile(project(":kotlin-scripting-compiler")) + testCompile(project(":compiler:cli-common")) + + testRuntimeOnly(project(":kotlin-compiler")) + testRuntimeOnly(commonDep("org.jetbrains.intellij.deps", "trove4j")) + testRuntimeOnly(project(":idea:ide-common")) { isTransitive = false } + + embeddableTestRuntime(project(":kotlin-scripting-ide-services-embeddable")) + embeddableTestRuntime(project(":kotlin-compiler-embeddable")) + embeddableTestRuntime(testSourceSet.output) +} + +sourceSets { + "main" {} + "test" { projectDefault() } +} + +tasks.withType> { + kotlinOptions.freeCompilerArgs += "-Xallow-kotlin-package" +} + +projectTest(parallel = true) { + dependsOn(":dist") + workingDir = rootDir +} + +// This doesn;t work now due to conflicts between embeddable compiler contents and intellij sdk modules +// To make it work, the dependencies to the intellij sdk should be eliminated +projectTest(taskName = "embeddableTest", parallel = true) { + workingDir = rootDir + dependsOn(embeddableTestRuntime) + classpath = embeddableTestRuntime +} diff --git a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt new file mode 100644 index 00000000000..b682b3c2f13 --- /dev/null +++ b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt @@ -0,0 +1,426 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.scripting.ide_services + +import junit.framework.TestCase +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.scripting.ide_services.test_util.JvmTestRepl +import org.jetbrains.kotlin.scripting.ide_services.test_util.SourceCodeTestImpl +import java.io.File +import kotlin.script.experimental.api.* +import kotlin.script.experimental.jvm.impl.KJvmCompiledScript +import kotlin.script.experimental.util.LinkedSnippet +import kotlin.script.experimental.util.get +import kotlin.script.experimental.jvm.util.isError +import kotlin.script.experimental.jvm.util.isIncomplete + +// Adapted form GenericReplTest + +// Artificial split into several testsuites, to speed up parallel testing +class JvmIdeServicesTest : TestCase() { + fun testReplBasics() { + JvmTestRepl() + .use { repl -> + val res1 = repl.compile( + SourceCodeTestImpl( + 0, + "val x =" + ) + ) + assertTrue("Unexpected check results: $res1", res1.isIncomplete()) + + assertEvalResult( + repl, + "val l1 = listOf(1 + 2)\nl1.first()", + 3 + ) + + assertEvalUnit( + repl, + "val x = 5" + ) + + assertEvalResult( + repl, + "x + 2", + 7 + ) + } + } + + fun testReplErrors() { + JvmTestRepl() + .use { repl -> + repl.compileAndEval(repl.nextCodeLine("val x = 10")) + + val res = repl.compileAndEval(repl.nextCodeLine("java.util.fish")) + assertTrue("Expected compile error", res.first.isError()) + + val result = repl.compileAndEval(repl.nextCodeLine("x")) + assertEquals(res.second.toString(), 10, (result.second?.result as ResultValue.Value?)?.value) + } + } + + fun testReplErrorsWithLocations() { + JvmTestRepl() + .use { repl -> + val (compileResult, evalResult) = repl.compileAndEval( + repl.nextCodeLine( + """ + val foobar = 78 + val foobaz = "dsdsda" + val ddd = ppp + val ooo = foobar + """.trimIndent() + ) + ) + + if (compileResult.isError() && evalResult == null) { + val errors = compileResult.getErrors() + val loc = errors.location + if (loc == null) { + fail("Location shouldn't be null") + } else { + assertEquals(3, loc.line) + assertEquals(11, loc.column) + assertEquals(3, loc.lineEnd) + assertEquals(14, loc.columnEnd) + } + } else { + fail("Result should be an error") + } + } + } + + fun testReplErrorsAndWarningsWithLocations() { + JvmTestRepl() + .use { repl -> + val (compileResult, evalResult) = repl.compileAndEval( + repl.nextCodeLine( + """ + fun f() { + val x = 3 + val y = ooo + return y + } + """.trimIndent() + ) + ) + if (compileResult.isError() && evalResult == null) { + val errors = compileResult.getErrors() + val loc = errors.location + if (loc == null) { + fail("Location shouldn't be null") + } else { + assertEquals(3, loc.line) + assertEquals(13, loc.column) + assertEquals(3, loc.lineEnd) + assertEquals(16, loc.columnEnd) + } + } else { + fail("Result should be an error") + } + } + } + + fun testReplSyntaxErrorsChecked() { + JvmTestRepl() + .use { repl -> + val res = repl.compileAndEval(repl.nextCodeLine("data class Q(val x: Int, val: String)")) + assertTrue("Expected compile error", res.first.isError()) + } + } + + private fun checkContains(actual: Sequence, expected: Set) { + val variants = actual.map { it.displayText }.toHashSet() + for (displayText in expected) { + if (!variants.contains(displayText)) { + fail("Element $displayText should be in $variants") + } + } + } + + private fun checkDoesntContain(actual: Sequence, expected: Set) { + val variants = actual.map { it.displayText }.toHashSet() + for (displayText in expected) { + if (variants.contains(displayText)) { + fail("Element $displayText should NOT be in $variants") + } + } + } + + fun testCompletion() = JvmTestRepl().use { repl -> + repl.compileAndEval( + repl.nextCodeLine( + """ + class AClass(val prop_x: Int) { + fun filter(xxx: (AClass).(AClass) -> Boolean): AClass { + return if(this.xxx(this)) + this + else + this + } + } + val AClass.prop_y: Int + get() = prop_x * prop_x + + val df = AClass(10) + val pro = "some string" + """.trimIndent() + ) + ) + + val codeLine1 = repl.nextCodeLine( + """ + df.filter{pr} + """.trimIndent() + ) + val completionList1 = repl.complete(codeLine1, 12) + checkContains(completionList1.valueOrThrow(), setOf("prop_x", "prop_y", "pro", "println(Double)")) + } + + fun testPackageCompletion() = JvmTestRepl().use { repl -> + val codeLine1 = repl.nextCodeLine( + """ + import java. + val xval = 3 + """.trimIndent() + ) + val completionList1 = repl.complete(codeLine1, 12) + checkContains(completionList1.valueOrThrow(), setOf("lang", "math")) + checkDoesntContain(completionList1.valueOrThrow(), setOf("xval")) + } + + fun testFileCompletion() = JvmTestRepl().use { repl -> + val codeLine1 = repl.nextCodeLine( + """ + val fname = " + """.trimIndent() + ) + val completionList1 = repl.complete(codeLine1, 13) + val files = File(".").listFiles()?.map { it.name } + assertFalse("There should be files in current dir", files.isNullOrEmpty()) + checkContains(completionList1.valueOrThrow(), files!!.toSet()) + } + + fun testReplCodeFormat() { + JvmTestRepl() + .use { repl -> + val codeLine0 = + SourceCodeTestImpl(0, "val l1 = 1\r\nl1\r\n") + val res = repl.compile(codeLine0) + + assertTrue("Unexpected compile result: $res", res is ResultWithDiagnostics.Success<*>) + } + } + + fun testRepPackage() { + JvmTestRepl() + .use { repl -> + assertEvalResult( + repl, + "package mypackage\n\nval x = 1\nx+2", + 3 + ) + + assertEvalResult( + repl, + "x+4", + 5 + ) + } + } + + fun testReplResultFieldWithFunction() { + JvmTestRepl() + .use { repl -> + assertEvalResultIs>( + repl, + "{ 1 + 2 }" + ) + assertEvalResultIs>( + repl, + "res0" + ) + assertEvalResult( + repl, + "res0()", + 3 + ) + } + } + + fun testReplResultField() { + JvmTestRepl() + .use { repl -> + assertEvalResult( + repl, + "5 * 4", + 20 + ) + assertEvalResult( + repl, + "res0 + 3", + 23 + ) + } + } +} + +// Artificial split into several testsuites, to speed up parallel testing +class LegacyReplTestLong1 : TestCase() { + + fun test256Evals() { + JvmTestRepl() + .use { repl -> + repl.compileAndEval( + SourceCodeTestImpl( + 0, + "val x0 = 0" + ) + ) + + val evals = 256 + for (i in 1..evals) { + repl.compileAndEval( + SourceCodeTestImpl( + i, + "val x$i = x${i - 1} + 1" + ) + ) + } + + val (_, evaluated) = repl.compileAndEval( + SourceCodeTestImpl( + evals + 1, + "x$evals" + ) + ) + assertEquals(evaluated.toString(), evals, (evaluated?.result as ResultValue.Value?)?.value) + } + } +} + +// Artificial split into several testsuites, to speed up parallel testing +class LegacyReplTestLong2 : TestCase() { + + fun testReplSlowdownKt22740() { + JvmTestRepl() + .use { repl -> + repl.compileAndEval( + SourceCodeTestImpl( + 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( + SourceCodeTestImpl( + i, + "fun Test.map(f: (T) -> Double): List = listOf(f(this.x))" + ) + ) + } + } + } +} + +private fun JvmTestRepl.compileAndEval(codeLine: SourceCode): Pair>, EvaluatedSnippet?> { + + val compRes = compile(codeLine) + + val evalRes = compRes.valueOrNull()?.let { + eval(it) + } + return compRes to evalRes?.valueOrNull().get() +} + +private fun assertEvalUnit( + repl: JvmTestRepl, + @Suppress("SameParameterValue") + line: String +) { + val compiledSnippet = + checkCompile(repl, line) + + val evalResult = repl.eval(compiledSnippet!!) + val valueResult = evalResult.valueOrNull().get() + + TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult) + TestCase.assertTrue(valueResult!!.result is ResultValue.Unit) +} + +private fun assertEvalResult(repl: JvmTestRepl, line: String, expectedResult: R) { + val compiledSnippet = + checkCompile(repl, line) + + val evalResult = repl.eval(compiledSnippet!!) + val valueResult = evalResult.valueOrNull().get() + + TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult) + TestCase.assertTrue(valueResult!!.result is ResultValue.Value) + TestCase.assertEquals(expectedResult, (valueResult.result as ResultValue.Value).value) +} + +private inline fun assertEvalResultIs(repl: JvmTestRepl, line: String) { + val compiledSnippet = + checkCompile(repl, line) + + val evalResult = repl.eval(compiledSnippet!!) + val valueResult = evalResult.valueOrNull().get() + + TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult) + TestCase.assertTrue(valueResult!!.result is ResultValue.Value) + TestCase.assertTrue((valueResult.result as ResultValue.Value).value is R) +} + +private fun checkCompile(repl: JvmTestRepl, line: String): LinkedSnippet? { + val codeLine = repl.nextCodeLine(line) + val compileResult = repl.compile(codeLine) + return compileResult.valueOrNull() +} + +private data class CompilationErrors( + val message: String, + val location: CompilerMessageLocation? +) + +private fun ResultWithDiagnostics.getErrors(): CompilationErrors = + CompilationErrors( + reports.joinToString("\n") { report -> + report.location?.let { loc -> + CompilerMessageLocation.create( + report.sourcePath, + loc.start.line, + loc.start.col, + loc.end?.line, + loc.end?.col, + null + )?.toString()?.let { + "$it " + } + }.orEmpty() + report.message + }, + reports.firstOrNull { + when (it.severity) { + ScriptDiagnostic.Severity.ERROR -> true + ScriptDiagnostic.Severity.FATAL -> true + else -> false + } + }?.let { + val loc = it.location ?: return@let null + CompilerMessageLocation.create( + it.sourcePath, + loc.start.line, + loc.start.col, + loc.end?.line, + loc.end?.col, + null + ) + } + ) diff --git a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt new file mode 100644 index 00000000000..203a14bbbe7 --- /dev/null +++ b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt @@ -0,0 +1,379 @@ +/* + * 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 org.jetbrains.kotlin.scripting.ide_services + +import junit.framework.TestCase +import kotlinx.coroutines.runBlocking +import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices +import org.jetbrains.kotlin.scripting.ide_services.test_util.SourceCodeTestImpl +import org.jetbrains.kotlin.scripting.ide_services.test_util.simpleScriptCompilationConfiguration +import org.jetbrains.kotlin.scripting.ide_services.test_util.toList +import org.junit.Assert +import org.junit.Test +import java.util.concurrent.atomic.AtomicInteger +import kotlin.script.experimental.api.* + +class ReplCompletionAndErrorsAnalysisTest : TestCase() { + @Test + fun testTrivial() = test { + run { + doCompile + code = """ + data class AClass(val memx: Int, val memy: String) + data class BClass(val memz: String, val mema: AClass) + val foobar = 42 + var foobaz = "string" + val v = BClass("KKK", AClass(5, "25")) + """.trimIndent() + } + + run { + code = "foo" + cursor = 3 + expect { + addCompletion("foobar", "foobar", "Int", "property") + addCompletion("foobaz", "foobaz", "String", "property") + } + } + + run { + code = "v.mema." + cursor = 7 + expect { + completionsMode(ComparisonType.INCLUDES) + addCompletion("memx", "memx", "Int", "property") + addCompletion("memy", "memy", "String", "property") + } + } + + run { + code = "listO" + cursor = 5 + expect { + addCompletion("listOf(", "listOf(T)", "List", "method") + addCompletion("listOf()", "listOf()", "List", "method") + addCompletion("listOf(", "listOf(vararg T)", "List", "method") + addCompletion("listOfNotNull(", "listOfNotNull(T?)", "List", "method") + addCompletion("listOfNotNull(", "listOfNotNull(vararg T?)", "List", "method") + } + } + } + + + @Test + fun testPackagesImport() = test { + run { + cursor = 17 + code = "import java.lang." + expect { + completionsMode(ComparisonType.INCLUDES) + addCompletion("Process", "Process", " (java.lang)", "class") + } + } + } + + @Test + fun testExtensionMethods() = test { + run { + doCompile + code = """ + class AClass(val c_prop_x: Int) { + fun filter(xxx: (AClass).() -> Boolean): AClass { + return this + } + } + val AClass.c_prop_y: Int + get() = c_prop_x * c_prop_x + + fun AClass.c_meth_z(v: Int) = v * c_prop_y + val df = AClass(10) + val c_zzz = "some string" + """.trimIndent() + } + + run { + code = "df.filter{ c_ }" + cursor = 13 + expect { + addCompletion("c_prop_x", "c_prop_x", "Int", "property") + addCompletion("c_zzz", "c_zzz", "String", "property") + addCompletion("c_prop_y", "c_prop_y", "Int", "property") + addCompletion("c_meth_z(", "c_meth_z(Int)", "Int", "method") + } + } + + run { + code = "df.fil" + cursor = 6 + expect { + addCompletion("filter { ", "filter(Line_1_simplescript.AClass.() -> ...", "Line_1_simplescript.AClass", "method") + } + } + } + + @Test + fun testBacktickedFields() = test { + run { + doCompile + code = """ + class AClass(val `c_prop x y z`: Int) + val df = AClass(33) + """.trimIndent() + } + + run { + code = "df.c_" + cursor = 5 + expect { + addCompletion("`c_prop x y z`", "`c_prop x y z`", "Int", "property") + } + } + } + + @Test + fun testListErrors() = test { + run { + doCompile + code = """ + data class AClass(val memx: Int, val memy: String) + data class BClass(val memz: String, val mema: AClass) + val foobar = 42 + var foobaz = "string" + val v = BClass("KKK", AClass(5, "25")) + """.trimIndent() + expect { + errorsMode(ComparisonType.EQUALS) + } + } + + run { + code = """ + val a = AClass("42", 3.14) + val b: Int = "str" + val c = foob + """.trimIndent() + expect { + addError(1, 16, 1, 20, "Type mismatch: inferred type is String but Int was expected", "ERROR") + addError(1, 22, 1, 26, "The floating-point literal does not conform to the expected type String", "ERROR") + addError(2, 14, 2, 19, "Type mismatch: inferred type is String but Int was expected", "ERROR") + addError(3, 9, 3, 13, "Unresolved reference: foob", "ERROR") + } + } + } + + @Test + fun testCompletionDuplication() = test { + for (i in 1..6) { + run { + if (i == 5) doCompile + if (i % 2 == 1) doErrorCheck + + val value = "a".repeat(i) + code = "val ddddd = \"$value\"" + cursor = 13 + i + } + } + + run { + code = "dd" + cursor = 2 + expect { + addCompletion("ddddd", "ddddd", "String", "property") + } + } + } + + private class TestConf { + private val runs = mutableListOf() + + fun run(setup: (Run).() -> Unit) { + val r = Run() + r.setup() + runs.add(r) + } + + fun collect() = runs.map { it.collect() } + + class Run { + private var _doCompile = false + val doCompile: Unit + get() { + _doCompile = true + } + + private var _doComplete = false + val doComplete: Unit + get() { + _doComplete = true + } + + private var _doErrorCheck = false + val doErrorCheck: Unit + get() { + _doErrorCheck = true + } + + var cursor: Int? = null + var code: String = "" + private var _expected: Expected = Expected(this) + + fun expect(setup: (Expected).() -> Unit) { + _expected = Expected(this) + _expected.setup() + } + + fun collect(): Pair { + return RunRequest(cursor, code, _doCompile, _doComplete, _doErrorCheck) to _expected.collect() + } + + class Expected(private val run: Run) { + private val variants = mutableListOf() + private var _completionsMode: ComparisonType = ComparisonType.DONT_CHECK + fun completionsMode(mode: ComparisonType) { + _completionsMode = mode + } + + fun addCompletion(text: String, displayText: String, tail: String, icon: String) { + if (_completionsMode == ComparisonType.DONT_CHECK) + _completionsMode = ComparisonType.EQUALS + run.doComplete + variants.add(SourceCodeCompletionVariant(text, displayText, tail, icon)) + } + + private val errors = mutableListOf() + private var _errorsMode: ComparisonType = ComparisonType.DONT_CHECK + fun errorsMode(mode: ComparisonType) { + _errorsMode = mode + } + + fun addError(startLine: Int, startCol: Int, endLine: Int, endCol: Int, message: String, severity: String) { + if (_errorsMode == ComparisonType.DONT_CHECK) + _errorsMode = ComparisonType.EQUALS + run.doErrorCheck + errors.add( + ScriptDiagnostic( + ScriptDiagnostic.unspecifiedError, + message, + ScriptDiagnostic.Severity.valueOf(severity), + location = SourceCode.Location( + SourceCode.Position(startLine, startCol), + SourceCode.Position(endLine, endCol) + ) + ) + ) + } + + fun collect(): ExpectedResult { + return ExpectedResult(variants, _completionsMode, errors, _errorsMode) + } + } + + } + } + + private fun test(setup: (TestConf).() -> Unit) { + val test = TestConf() + test.setup() + runBlocking { checkEvaluateInRepl(simpleScriptCompilationConfiguration, test.collect()) } + } + + enum class ComparisonType { + INCLUDES, EQUALS, DONT_CHECK + } + + data class RunRequest( + val cursor: Int?, + val code: String, + val doCompile: Boolean, + val doComplete: Boolean, + val doErrorCheck: Boolean + ) + + data class ExpectedResult( + val completions: List, val completionsCompType: ComparisonType, + val errors: List, val errorsCompType: ComparisonType + ) + + private val currentLineCounter = AtomicInteger() + + private fun nextCodeLine(code: String): SourceCode = + SourceCodeTestImpl( + currentLineCounter.getAndIncrement(), + code + ) + + private suspend fun evaluateInRepl( + compilationConfiguration: ScriptCompilationConfiguration, + snippets: List + ): List, List>>> { + val compiler = KJvmReplCompilerWithIdeServices() + return snippets.map { (cursor, snippetText, doComplile, doComplete, doErrorCheck) -> + val pos = SourceCode.Position(0, 0, cursor) + val codeLine = nextCodeLine(snippetText) + val completionRes = if (doComplete && cursor != null) { + val res = compiler.complete(codeLine, pos, compilationConfiguration) + res.toList().filter { it.tail != "keyword" } + } else { + emptyList() + } + + val errorsRes = if (doErrorCheck) { + val codeLineForErrorCheck = nextCodeLine(snippetText) + compiler.analyze(codeLineForErrorCheck, SourceCode.Position(0, 0), compilationConfiguration).toList() + } else { + emptyList() + } + + if (doComplile) { + val codeLineForCompilation = nextCodeLine(snippetText) + compiler.compile(codeLineForCompilation, compilationConfiguration) + } + + Pair(completionRes, errorsRes).asSuccess() + } + } + + private fun checkLists(index: Int, checkName: String, expected: List, actual: List, compType: ComparisonType) { + when (compType) { + ComparisonType.EQUALS -> Assert.assertEquals( + "#$index ($checkName): Expected $expected, got $actual", + expected, + actual + ) + ComparisonType.INCLUDES -> Assert.assertTrue( + "#$index ($checkName): Expected $actual to include $expected", + actual.containsAll(expected) + ) + ComparisonType.DONT_CHECK -> { + } + } + } + + private suspend fun checkEvaluateInRepl( + compilationConfiguration: ScriptCompilationConfiguration, + testData: List> + ) { + val (snippets, expected) = testData.unzip() + val expectedIter = expected.iterator() + evaluateInRepl(compilationConfiguration, snippets).forEachIndexed { index, res -> + when (res) { + is ResultWithDiagnostics.Failure -> Assert.fail("#$index: Expected result, got $res") + is ResultWithDiagnostics.Success -> { + val (expectedCompletions, completionsCompType, expectedErrors, errorsCompType) = expectedIter.next() + val (completionsRes, errorsRes) = res.value + + checkLists(index, "completions", expectedCompletions, completionsRes, completionsCompType) + val expectedErrorsWithPath = expectedErrors.map { + it.copy(sourcePath = errorsRes.firstOrNull()?.sourcePath) + } + checkLists(index, "errors", expectedErrorsWithPath, errorsRes, errorsCompType) + } + } + } + } + +} + diff --git a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testScriptDefinitions.kt b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testScriptDefinitions.kt new file mode 100644 index 00000000000..c82261a387e --- /dev/null +++ b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testScriptDefinitions.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.scripting.ide_services.test_util + +import kotlin.script.experimental.annotations.KotlinScript +import kotlin.script.experimental.api.* +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate +import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration +import kotlin.script.experimental.jvm.updateClasspath +import kotlin.script.experimental.jvm.util.classpathFromClass + +@KotlinScript(fileExtension = "simplescript.kts") +abstract class SimpleScript + +val simpleScriptCompilationConfiguration = + createJvmCompilationConfigurationFromTemplate { + updateClasspath(classpathFromClass()) + } + +val simpleScriptEvaluationConfiguration = ScriptEvaluationConfiguration() + +@KotlinScript(fileExtension = "withboth.kts", compilationConfiguration = ReceiverAndPropertiesConfiguration::class) +abstract class ScriptWithBoth + +@KotlinScript(fileExtension = "withproperties.kts", compilationConfiguration = ProvidedPropertiesConfiguration::class) +abstract class ScriptWithProvidedProperties + +@KotlinScript(fileExtension = "withreceiver.kts", compilationConfiguration = ImplicitReceiverConfiguration::class) +abstract class ScriptWithImplicitReceiver + +object ReceiverAndPropertiesConfiguration : ScriptCompilationConfiguration( + { + updateClasspath(classpathFromClass()) + + providedProperties("providedString" to String::class) + + implicitReceivers(ImplicitReceiverClass::class) + } +) + +object ProvidedPropertiesConfiguration : ScriptCompilationConfiguration( + { + updateClasspath(classpathFromClass()) + + providedProperties("providedString" to String::class) + } +) + +object ImplicitReceiverConfiguration : ScriptCompilationConfiguration( + { + updateClasspath(classpathFromClass()) + + implicitReceivers(ImplicitReceiverClass::class) + } +) + +class ImplicitReceiverClass( + @Suppress("unused") + val receiverString: String +) + + +inline fun createJvmCompilationConfigurationFromTemplate( + hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration, + noinline body: ScriptCompilationConfiguration.Builder.() -> Unit = {} +): ScriptCompilationConfiguration = + createCompilationConfigurationFromTemplate( + KotlinType(T::class), + hostConfiguration, + ScriptCompilationConfiguration::class, + body + ) \ No newline at end of file diff --git a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testUtil.kt b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testUtil.kt new file mode 100644 index 00000000000..7223a5255c9 --- /dev/null +++ b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testUtil.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.scripting.ide_services.test_util + +import kotlinx.coroutines.runBlocking +import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices +import java.io.Closeable +import java.util.concurrent.atomic.AtomicInteger +import kotlin.script.experimental.api.CompiledSnippet +import kotlin.script.experimental.api.ResultWithDiagnostics +import kotlin.script.experimental.api.SourceCode +import kotlin.script.experimental.api.valueOrNull +import kotlin.script.experimental.jvm.BasicJvmReplEvaluator +import kotlin.script.experimental.util.LinkedSnippet +import kotlin.script.experimental.jvm.util.toSourceCodePosition + +internal class JvmTestRepl : Closeable { + private val currentLineCounter = AtomicInteger(0) + + private val compileConfiguration = simpleScriptCompilationConfiguration + private val evalConfiguration = simpleScriptEvaluationConfiguration + + fun nextCodeLine(code: String): SourceCode = + SourceCodeTestImpl( + currentLineCounter.getAndIncrement(), + code + ) + + private val replCompiler: KJvmReplCompilerWithIdeServices by lazy { + KJvmReplCompilerWithIdeServices() + } + + private val compiledEvaluator: BasicJvmReplEvaluator by lazy { + BasicJvmReplEvaluator() + } + + fun compile(code: SourceCode) = runBlocking { replCompiler.compile(code, compileConfiguration) } + fun complete(code: SourceCode, cursor: Int) = runBlocking { replCompiler.complete(code, cursor.toSourceCodePosition(code), compileConfiguration) } + + fun eval(snippet: LinkedSnippet) = runBlocking { compiledEvaluator.eval(snippet, evalConfiguration) } + + override fun close() { + + } + +} + +internal class SourceCodeTestImpl(number: Int, override val text: String) : SourceCode { + override val name: String? = "Line_$number" + override val locationId: String? = "location_$number" +} + +@JvmName("iterableToList") +fun ResultWithDiagnostics>.toList() = this.valueOrNull()?.toList().orEmpty() + +@JvmName("sequenceToList") +fun ResultWithDiagnostics>.toList() = this.valueOrNull()?.toList().orEmpty() diff --git a/plugins/scripting/scripting-ide-services/build.gradle.kts b/plugins/scripting/scripting-ide-services/build.gradle.kts new file mode 100644 index 00000000000..312519a5a6a --- /dev/null +++ b/plugins/scripting/scripting-ide-services/build.gradle.kts @@ -0,0 +1,41 @@ + +description = "Kotlin Scripting Compiler extension providing code completion and static analysis" + +plugins { + kotlin("jvm") + id("jps-compatible") +} + +jvmTarget = "1.8" + +publish() + +dependencies { + compile(project(":kotlin-script-runtime")) + compile(kotlinStdlib()) + compileOnly(project(":idea:ide-common")) + compile(project(":kotlin-scripting-common")) + compile(project(":kotlin-scripting-jvm")) + compileOnly(project(":kotlin-scripting-compiler")) + compileOnly(project(":compiler:cli")) + compileOnly(project(":kotlin-reflect-api")) + compileOnly(intellijCoreDep()) { includeJars("intellij-core") } + publishedRuntime(project(":kotlin-compiler")) + publishedRuntime(project(":kotlin-scripting-compiler")) + publishedRuntime(project(":kotlin-reflect")) + publishedRuntime(commonDep("org.jetbrains.intellij.deps", "trove4j")) +} + +sourceSets { + "main" { projectDefault() } + "test" { } +} + +tasks.withType> { + kotlinOptions { + freeCompilerArgs += "-Xskip-metadata-version-check" + freeCompilerArgs += "-Xallow-kotlin-package" + } +} + +standardPublicJars() diff --git a/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/KJvmReplCompilerWithIdeServices.kt b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/KJvmReplCompilerWithIdeServices.kt new file mode 100644 index 00000000000..60e1fb4346d --- /dev/null +++ b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/KJvmReplCompilerWithIdeServices.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.scripting.ide_services.compiler + +import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport +import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase +import org.jetbrains.kotlin.scripting.compiler.plugin.impl.failure +import org.jetbrains.kotlin.scripting.compiler.plugin.impl.withMessageCollector +import org.jetbrains.kotlin.scripting.ide_services.compiler.impl.IdeLikeReplCodeAnalyzer +import org.jetbrains.kotlin.scripting.ide_services.compiler.impl.getKJvmCompletion +import org.jetbrains.kotlin.scripting.ide_services.compiler.impl.prepareCodeForCompletion +import kotlin.script.experimental.api.* +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration +import kotlin.script.experimental.jvm.util.calcAbsolute + +class KJvmReplCompilerWithIdeServices(hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration) : + KJvmReplCompilerBase(hostConfiguration, { + IdeLikeReplCodeAnalyzer(it.environment) + }), + ReplCompleter, ReplCodeAnalyzer { + + override suspend fun complete( + snippet: SourceCode, + cursor: SourceCode.Position, + configuration: ScriptCompilationConfiguration + ): ResultWithDiagnostics = + withMessageCollector(snippet) { messageCollector -> + val initialConfiguration = configuration.refineBeforeParsing(snippet).valueOr { + return it + } + + val cursorAbs = cursor.calcAbsolute(snippet) + + val newText = + prepareCodeForCompletion(snippet.text, cursorAbs) + val newSnippet = object : SourceCode { + override val text: String + get() = newText + override val name: String? + get() = snippet.name + override val locationId: String? + get() = snippet.locationId + + } + + val compilationState = state.getCompilationState(initialConfiguration) + + val (_, errorHolder, snippetKtFile) = prepareForAnalyze( + newSnippet, + messageCollector, + compilationState, + checkSyntaxErrors = false + ).valueOr { return@withMessageCollector it } + + val analysisResult = + compilationState.analyzerEngine.statelessAnalyzeWithImportedScripts(snippetKtFile, emptyList(), scriptPriority.get() + 1) + AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder) + + val (_, bindingContext, resolutionFacade, moduleDescriptor) = when (analysisResult) { + is IdeLikeReplCodeAnalyzer.ReplLineAnalysisResultWithStateless.Stateless -> { + analysisResult + } + else -> return failure( + newSnippet, + messageCollector, + "Unexpected result ${analysisResult::class.java}" + ) + } + + return getKJvmCompletion( + snippetKtFile, + bindingContext, + resolutionFacade, + moduleDescriptor, + cursorAbs + ).asSuccess(messageCollector.diagnostics) + } + + private fun List.toAnalyzeResult() = (filter { + when (it.severity) { + ScriptDiagnostic.Severity.FATAL, + ScriptDiagnostic.Severity.ERROR, + ScriptDiagnostic.Severity.WARNING + -> true + else -> false + } + }).asSequence() + + override suspend fun analyze( + snippet: SourceCode, + cursor: SourceCode.Position, + configuration: ScriptCompilationConfiguration + ): ResultWithDiagnostics { + return withMessageCollector(snippet) { messageCollector -> + val initialConfiguration = configuration.refineBeforeParsing(snippet).valueOr { + return it + } + + val compilationState = state.getCompilationState(initialConfiguration) + + val (_, errorHolder, snippetKtFile) = prepareForAnalyze( + snippet, + messageCollector, + compilationState, + checkSyntaxErrors = true + ).valueOr { return@withMessageCollector messageCollector.diagnostics.toAnalyzeResult().asSuccess() } + + val analysisResult = + compilationState.analyzerEngine.statelessAnalyzeWithImportedScripts(snippetKtFile, emptyList(), scriptPriority.get() + 1) + AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder) + + messageCollector.diagnostics.toAnalyzeResult().asSuccess() + } + } +} diff --git a/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/IdeLikeReplCodeAnalyzer.kt b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/IdeLikeReplCodeAnalyzer.kt new file mode 100644 index 00000000000..6099b6c2433 --- /dev/null +++ b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/IdeLikeReplCodeAnalyzer.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.scripting.ide_services.compiler.impl + +import org.jetbrains.kotlin.cli.jvm.compiler.CliBindingTrace +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.container.getService +import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics +import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory +import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase +import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities + +class IdeLikeReplCodeAnalyzer(private val environment: KotlinCoreEnvironment) : ReplCodeAnalyzerBase(environment, CliBindingTrace()) { + interface ReplLineAnalysisResultWithStateless : ReplLineAnalysisResult { + // Result of stateless analyse, which may be used for reporting errors + // without code generation + data class Stateless( + override val diagnostics: Diagnostics, + val bindingContext: BindingContext, + val resolutionFacade: KotlinResolutionFacadeForRepl, + val moduleDescriptor: ModuleDescriptor + ) : + ReplLineAnalysisResultWithStateless { + override val scriptDescriptor: ClassDescriptorWithResolutionScopes? get() = null + } + } + + fun statelessAnalyzeWithImportedScripts( + psiFile: KtFile, + importedScripts: List, + priority: Int + ): ReplLineAnalysisResultWithStateless { + topDownAnalysisContext.scripts.clear() + trace.clearDiagnostics() + + psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, priority) + + return doStatelessAnalyze(psiFile, importedScripts) + } + + private fun doStatelessAnalyze(linePsi: KtFile, importedScripts: List): ReplLineAnalysisResultWithStateless { + scriptDeclarationFactory.setDelegateFactory( + FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi) + importedScripts) + ) + replState.submitLine(linePsi) + + topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts) + + val moduleDescriptor = container.getService(ModuleDescriptor::class.java) + val resolutionFacade = + KotlinResolutionFacadeForRepl(environment, container) + val diagnostics = trace.bindingContext.diagnostics + return ReplLineAnalysisResultWithStateless.Stateless( + diagnostics, + trace.bindingContext, + resolutionFacade, + moduleDescriptor + ) + } + +} diff --git a/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/KJvmReplCompleter.kt b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/KJvmReplCompleter.kt new file mode 100644 index 00000000000..5a502dbb73b --- /dev/null +++ b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/KJvmReplCompleter.kt @@ -0,0 +1,406 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.scripting.ide_services.compiler.impl + +import com.intellij.psi.PsiElement +import com.intellij.psi.tree.TokenSet +import org.jetbrains.kotlin.backend.common.onlyIf +import org.jetbrains.kotlin.builtins.isFunctionType +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor +import org.jetbrains.kotlin.descriptors.impl.TypeParameterDescriptorImpl +import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper +import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.lexer.KtKeywordToken +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.renderer.ClassifierNamePolicy +import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.resolve.scopes.LexicalScope +import org.jetbrains.kotlin.resolve.scopes.MemberScope.Companion.ALL_NAME_FILTER +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.asFlexibleType +import org.jetbrains.kotlin.types.isFlexible +import java.io.File +import java.util.* +import kotlin.script.experimental.api.SourceCodeCompletionVariant + +fun getKJvmCompletion( + ktScript: KtFile, + bindingContext: BindingContext, + resolutionFacade: KotlinResolutionFacadeForRepl, + moduleDescriptor: ModuleDescriptor, + cursor: Int +) = KJvmReplCompleter(ktScript, bindingContext, resolutionFacade, moduleDescriptor, cursor).getCompletion() + +// Insert a constant string right after a cursor position to make this identifiable as a simple reference +// For example, code line +// import java. +// ^ +// is converted to +// import java.ABCDEF +// and it makes token after dot (for which reference variants are looked) discoverable in PSI +fun prepareCodeForCompletion(code: String, cursor: Int) = + code.substring(0, cursor) + KJvmReplCompleter.INSERTED_STRING + code.substring(cursor) + +private class KJvmReplCompleter( + private val ktScript: KtFile, + private val bindingContext: BindingContext, + private val resolutionFacade: KotlinResolutionFacadeForRepl, + private val moduleDescriptor: ModuleDescriptor, + private val cursor: Int +) { + + private fun getElementAt(cursorPos: Int): PsiElement? { + var element: PsiElement? = ktScript.findElementAt(cursorPos) + while (element !is KtExpression && element != null) { + element = element.parent + } + return element + } + + fun getCompletion() = sequence gen@{ + val element = getElementAt(cursor) + + var descriptors: Collection? = null + var isTipsManagerCompletion = true + var isSortNeeded = true + + if (element == null) + return@gen + + val simpleExpression = when { + element is KtSimpleNameExpression -> element + element.parent is KtSimpleNameExpression -> element.parent as KtSimpleNameExpression + else -> null + } + + if (simpleExpression != null) { + val inDescriptor: DeclarationDescriptor = simpleExpression.getResolutionScope(bindingContext, resolutionFacade).ownerDescriptor + val prefix = element.text.substring(0, cursor - element.startOffset) + isSortNeeded = false + descriptors = ReferenceVariantsHelper( + bindingContext, + resolutionFacade, + moduleDescriptor, + VisibilityFilter(inDescriptor) + ).getReferenceVariants( + simpleExpression, + DescriptorKindFilter.ALL, + { name: Name -> !name.isSpecial && name.identifier.startsWith(prefix) }, + filterOutJavaGettersAndSetters = true, + filterOutShadowed = false, // setting to true makes it slower up to 4 times + excludeNonInitializedVariable = true, + useReceiverType = null + ) + + } else if (element is KtStringTemplateExpression) { + if (element.hasInterpolation()) { + return@gen + } + + val stringVal = element.entries.joinToString("") { + val t = it.text + if (it.startOffset <= cursor && cursor <= it.endOffset) { + val s = cursor - it.startOffset + val e = s + INSERTED_STRING.length + t.substring(0, s) + t.substring(e) + } else t + } + + val separatorIndex = stringVal.lastIndexOfAny(charArrayOf('/', '\\')) + val dir = if (separatorIndex != -1) { + stringVal.substring(0, separatorIndex + 1) + } else { + "." + } + val namePrefix = stringVal.substring(separatorIndex + 1) + + val file = File(dir) + + file.listFiles { p, f -> p == file && f.startsWith(namePrefix, true) }?.forEach { + yield(SourceCodeCompletionVariant(it.name, it.name, "file", "file")) + } + + return@gen + + } else { + isTipsManagerCompletion = false + val resolutionScope: LexicalScope? + val parent = element.parent + + val qualifiedExpression = when { + element is KtQualifiedExpression -> { + isTipsManagerCompletion = true + element + } + parent is KtQualifiedExpression -> parent + else -> null + } + + if (qualifiedExpression != null) { + val receiverExpression = qualifiedExpression.receiverExpression + val expressionType = bindingContext.get( + BindingContext.EXPRESSION_TYPE_INFO, + receiverExpression + )?.type + if (expressionType != null) { + isSortNeeded = false + descriptors = ReferenceVariantsHelper( + bindingContext, + resolutionFacade, + moduleDescriptor, + { true } + ).getReferenceVariants( + receiverExpression, + CallTypeAndReceiver.DOT(receiverExpression), + DescriptorKindFilter.ALL, + ALL_NAME_FILTER + ) + } + } else { + resolutionScope = bindingContext.get( + BindingContext.LEXICAL_SCOPE, + element as KtExpression? + ) + descriptors = (resolutionScope?.getContributedDescriptors( + DescriptorKindFilter.ALL, + ALL_NAME_FILTER + ) + ?: return@gen) + } + } + + if (descriptors != null) { + val targetElement = if (isTipsManagerCompletion) element else element.parent + val prefixEnd = cursor - targetElement.startOffset + var prefix = targetElement.text.substring(0, prefixEnd) + + val cursorWithinElement = cursor - element.startOffset + val dotIndex = prefix.lastIndexOf('.', cursorWithinElement) + + prefix = if (dotIndex >= 0) { + prefix.substring(dotIndex + 1, cursorWithinElement) + } else { + prefix.substring(0, cursorWithinElement) + } + + if (descriptors !is ArrayList<*>) { + descriptors = ArrayList(descriptors) + } + + (descriptors as ArrayList) + .map { + val presentation = + getPresentation( + it + ) + Triple(it, presentation, (presentation.presentableText + presentation.tailText).toLowerCase()) + } + .onlyIf({ isSortNeeded }) { it.sortedBy { descTriple -> descTriple.third } } + .forEach { + val descriptor = it.first + val (rawName, presentableText, tailText, completionText) = it.second + if (rawName.startsWith(prefix)) { + val fullName: String = + formatName( + presentableText + ) + yield( + SourceCodeCompletionVariant( + completionText, + fullName, + tailText, + getIconFromDescriptor( + descriptor + ) + ) + ) + } + } + + yieldAll( + keywordsCompletionVariants( + KtTokens.KEYWORDS, + prefix + ) + ) + yieldAll( + keywordsCompletionVariants( + KtTokens.SOFT_KEYWORDS, + prefix + ) + ) + } + } + + private inner class VisibilityFilter( + private val inDescriptor: DeclarationDescriptor + ) : (DeclarationDescriptor) -> Boolean { + override fun invoke(descriptor: DeclarationDescriptor): Boolean { + if (descriptor is TypeParameterDescriptor && !isTypeParameterVisible(descriptor)) return false + + if (descriptor is DeclarationDescriptorWithVisibility) { + return try { + descriptor.visibility.isVisible(null, descriptor, inDescriptor) + } catch (e: IllegalStateException) { + true + } + } + + return true + } + + private fun isTypeParameterVisible(typeParameter: TypeParameterDescriptor): Boolean { + val owner = typeParameter.containingDeclaration + var parent: DeclarationDescriptor? = inDescriptor + while (parent != null) { + if (parent == owner) return true + if (parent is ClassDescriptor && !parent.isInner) return false + parent = parent.containingDeclaration + } + return true + } + } + + companion object { + const val INSERTED_STRING = "ABCDEF" + private const val NUMBER_OF_CHAR_IN_COMPLETION_NAME = 40 + + private fun keywordsCompletionVariants( + keywords: TokenSet, + prefix: String + ) = sequence { + keywords.types.forEach { + val token = (it as KtKeywordToken).value + if (token.startsWith(prefix)) yield( + SourceCodeCompletionVariant( + token, + token, + "keyword", + "keyword" + ) + ) + } + } + + private val RENDERER = + IdeDescriptorRenderers.SOURCE_CODE.withOptions { + this.classifierNamePolicy = + ClassifierNamePolicy.SHORT + this.typeNormalizer = + IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES + this.parameterNameRenderingPolicy = + ParameterNameRenderingPolicy.NONE + this.renderDefaultAnnotationArguments = false + this.typeNormalizer = lambda@{ kotlinType: KotlinType -> + if (kotlinType.isFlexible()) { + return@lambda kotlinType.asFlexibleType().upperBound + } + kotlinType + } + } + + private fun getIconFromDescriptor(descriptor: DeclarationDescriptor): String = when (descriptor) { + is FunctionDescriptor -> "method" + is PropertyDescriptor -> "property" + is LocalVariableDescriptor -> "property" + is ClassDescriptor -> "class" + is PackageFragmentDescriptor -> "package" + is PackageViewDescriptor -> "package" + is ValueParameterDescriptor -> "genericValue" + is TypeParameterDescriptorImpl -> "class" + else -> "" + } + + private fun formatName(builder: String, symbols: Int = NUMBER_OF_CHAR_IN_COMPLETION_NAME): String { + return if (builder.length > symbols) { + builder.substring(0, symbols) + "..." + } else builder + } + + data class DescriptorPresentation( + val rawName: String, + val presentableText: String, + val tailText: String, + val completionText: String + ) + + fun getPresentation(descriptor: DeclarationDescriptor): DescriptorPresentation { + val rawDescriptorName = descriptor.name.asString() + val descriptorName = rawDescriptorName.quoteIfNeeded() + var presentableText = descriptorName + var typeText = "" + var tailText = "" + var completionText = "" + if (descriptor is FunctionDescriptor) { + val returnType = descriptor.returnType + typeText = + if (returnType != null) RENDERER.renderType(returnType) else "" + presentableText += RENDERER.renderFunctionParameters( + descriptor + ) + val parameters = descriptor.valueParameters + if (parameters.size == 1 && parameters.first().type.isFunctionType) + completionText = "$descriptorName { " + + val extensionFunction = descriptor.extensionReceiverParameter != null + val containingDeclaration = descriptor.containingDeclaration + if (extensionFunction) { + tailText += " for " + RENDERER.renderType( + descriptor.extensionReceiverParameter!!.type + ) + tailText += " in " + DescriptorUtils.getFqName(containingDeclaration) + } + } else if (descriptor is VariableDescriptor) { + val outType = + descriptor.type + typeText = RENDERER.renderType(outType) + } else if (descriptor is ClassDescriptor) { + val declaredIn = descriptor.containingDeclaration + tailText = " (" + DescriptorUtils.getFqName(declaredIn) + ")" + } else { + typeText = RENDERER.render(descriptor) + } + tailText = if (typeText.isEmpty()) tailText else typeText + + if (completionText.isEmpty()) { + completionText = presentableText + var position = completionText.indexOf('(') + if (position != -1) { //If this is a string with a package after + if (completionText[position - 1] == ' ') { + position -= 2 + } + //if this is a method without args + if (completionText[position + 1] == ')') { + position++ + } + completionText = completionText.substring(0, position + 1) + } + position = completionText.indexOf(":") + if (position != -1) { + completionText = completionText.substring(0, position - 1) + } + } + + return DescriptorPresentation( + rawDescriptorName, + presentableText, + tailText, + completionText + ) + } + } +} diff --git a/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/KotlinResolutionFacadeForRepl.kt b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/KotlinResolutionFacadeForRepl.kt new file mode 100644 index 00000000000..4befc0fed64 --- /dev/null +++ b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/KotlinResolutionFacadeForRepl.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.scripting.ide_services.compiler.impl + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.container.ComponentProvider +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class KotlinResolutionFacadeForRepl( + private val environment: KotlinCoreEnvironment, + private val provider: ComponentProvider +) : + ResolutionFacade { + override val project: Project + get() = environment.project + + override fun analyze( + element: KtElement, + bodyResolveMode: BodyResolveMode + ): BindingContext { + throw UnsupportedOperationException() + } + + override val moduleDescriptor: ModuleDescriptor + get() { + throw UnsupportedOperationException() + } + + override fun getFrontendService(serviceClass: Class): T { + return provider.resolve(serviceClass)!!.getValue() as T + } + + override fun getIdeService(serviceClass: Class): T { + throw UnsupportedOperationException() + } + + override fun tryGetFrontendService(element: PsiElement, serviceClass: Class): T? { + throw UnsupportedOperationException() + } + + override fun getFrontendService(element: PsiElement, serviceClass: Class): T { + throw UnsupportedOperationException() + } + + override fun getFrontendService(moduleDescriptor: ModuleDescriptor, serviceClass: Class): T { + throw UnsupportedOperationException() + } + + override fun analyze(elements: Collection, bodyResolveMode: BodyResolveMode): BindingContext { + throw UnsupportedOperationException() + } + + override fun analyzeWithAllCompilerChecks(elements: Collection): AnalysisResult { + throw UnsupportedOperationException() + } + + override fun resolveToDescriptor(declaration: KtDeclaration, bodyResolveMode: BodyResolveMode): DeclarationDescriptor { + throw UnsupportedOperationException() + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index c065aa8f13b..f4fbeb92f18 100644 --- a/settings.gradle +++ b/settings.gradle @@ -267,6 +267,9 @@ include ":kotlin-build-common", ":kotlin-scripting-compiler-embeddable", ":kotlin-scripting-compiler-impl", ":kotlin-scripting-compiler-impl-embeddable", + ":kotlin-scripting-ide-services", + ":kotlin-scripting-ide-services-test", + ":kotlin-scripting-ide-services-embeddable", ":kotlin-scripting-dependencies", ":kotlin-scripting-dependencies-maven", ":kotlin-scripting-jsr223", @@ -478,6 +481,9 @@ project(':kotlin-scripting-compiler').projectDir = "$rootDir/plugins/scripting/s project(':kotlin-scripting-compiler-embeddable').projectDir = "$rootDir/plugins/scripting/scripting-compiler-embeddable" as File project(':kotlin-scripting-compiler-impl').projectDir = "$rootDir/plugins/scripting/scripting-compiler-impl" as File project(':kotlin-scripting-compiler-impl-embeddable').projectDir = "$rootDir/plugins/scripting/scripting-compiler-impl-embeddable" as File +project(':kotlin-scripting-ide-services').projectDir = "$rootDir/plugins/scripting/scripting-ide-services" as File +project(':kotlin-scripting-ide-services-test').projectDir = "$rootDir/plugins/scripting/scripting-ide-services-test" as File +project(':kotlin-scripting-ide-services-embeddable').projectDir = "$rootDir/plugins/scripting/scripting-ide-services-embeddable" as File project(':kotlin-scripting-idea').projectDir = "$rootDir/plugins/scripting/scripting-idea" as File project(':kotlin-main-kts').projectDir = "$rootDir/libraries/tools/kotlin-main-kts" as File project(':kotlin-main-kts-test').projectDir = "$rootDir/libraries/tools/kotlin-main-kts-test" as File