mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-04-18 00:21:29 +00:00
Add new REPL API JVM implementation
This commit is contained in:
committed by
Ilya Chernikov
parent
4c2c44b106
commit
d2fec96f38
@@ -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") {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ReplCompileResult, Evaluable?> {
|
||||
val compiled = compiler.compile(state, codeLine)
|
||||
return when (compiled) {
|
||||
@@ -96,7 +94,7 @@ open class GenericReplCompilingEvaluatorBase(
|
||||
}
|
||||
|
||||
class GenericReplCompilingEvaluator(
|
||||
compiler: ReplCompiler,
|
||||
compiler: ReplCompilerWithoutCheck,
|
||||
baseClasspath: Iterable<File>,
|
||||
baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
|
||||
fallbackScriptArgs: ScriptArgsWithTypes? = null,
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<*>,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
^
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<String>,
|
||||
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
|
||||
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
|
||||
limit: Int = 0
|
||||
): Sequence<ResultWithDiagnostics<EvaluationResult>> {
|
||||
val replCompilerProxy =
|
||||
KJvmReplCompilerImpl(defaultJvmScriptingHostConfiguration)
|
||||
val compilationState = replCompilerProxy.createReplCompilationState(compilationConfiguration)
|
||||
val compilationHistory = BasicReplStageHistory<ScriptDescriptor>()
|
||||
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<String>,
|
||||
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
|
||||
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
|
||||
limit: Int = 0
|
||||
): Sequence<ResultWithDiagnostics<EvaluatedSnippet>> {
|
||||
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<String>,
|
||||
expected: Sequence<ResultWithDiagnostics<Any?>>,
|
||||
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<String>,
|
||||
expected: Sequence<ResultWithDiagnostics<Any?>>,
|
||||
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<String>,
|
||||
expected: Sequence<Any?>,
|
||||
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<String>,
|
||||
expected: Sequence<Any?>,
|
||||
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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<JvmDependency>()
|
||||
?.flatMap { it.classpath }
|
||||
|
||||
@@ -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<ReplCodeAnalyzerBase> = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<CompiledSnippet, KJvmEvaluatedSnippet> {
|
||||
override var lastEvaluatedSnippet: LinkedSnippetImpl<KJvmEvaluatedSnippet>? = null
|
||||
private set
|
||||
|
||||
private val history = SnippetsHistory<KClass<*>?, Any?>()
|
||||
|
||||
override suspend fun eval(
|
||||
snippet: LinkedSnippet<out CompiledSnippet>,
|
||||
configuration: ScriptEvaluationConfiguration
|
||||
): ResultWithDiagnostics<LinkedSnippet<KJvmEvaluatedSnippet>> {
|
||||
|
||||
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
|
||||
@@ -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()
|
||||
|
||||
@@ -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<CompiledT, ResultT> = Pair<CompiledT, ResultT>
|
||||
typealias CompiledHistoryStorage<CompiledT, ResultT> = ArrayList<CompiledHistoryItem<CompiledT, ResultT>>
|
||||
typealias CompiledHistoryList<CompiledT, ResultT> = List<CompiledHistoryItem<CompiledT, ResultT>>
|
||||
|
||||
/*
|
||||
WARNING: Not thread safe, the caller is assumed to lock access.
|
||||
*/
|
||||
open class SnippetsHistory<CompiledT, ResultT>(startingHistory: CompiledHistoryList<CompiledT, ResultT> = emptyList()) : Serializable {
|
||||
protected val history: CompiledHistoryStorage<CompiledT, ResultT> = ArrayList(startingHistory)
|
||||
|
||||
fun add(line: CompiledT, value: ResultT) {
|
||||
history.add(line to value)
|
||||
}
|
||||
|
||||
fun lastItem(): CompiledHistoryItem<CompiledT, ResultT>? = history.lastOrNull()
|
||||
fun lastValue(): ResultT? = lastItem()?.second
|
||||
|
||||
val items: List<CompiledHistoryItem<CompiledT, ResultT>> = history
|
||||
|
||||
fun isEmpty(): Boolean = history.isEmpty()
|
||||
fun isNotEmpty(): Boolean = history.isNotEmpty()
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID: Long = 8328353001L
|
||||
}
|
||||
}
|
||||
@@ -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 <T> ResultWithDiagnostics<T>.isIncomplete() = this.reports.any { it.code == ScriptDiagnostic.incompleteCode }
|
||||
|
||||
fun <T> ResultWithDiagnostics<T>.isError() = this is ResultWithDiagnostics.Failure
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -159,6 +159,11 @@
|
||||
<artifactId>kotlin-scripting-jvm-host</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>kotlin-scripting-ide-services</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<!-- Compiler dependency required by scripting -->
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<AnalyzerT : ReplCodeAnalyzerBase> protected constructor(
|
||||
protected val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
|
||||
val initAnalyzer: (SharedScriptCompilationContext) -> AnalyzerT
|
||||
) : ReplCompiler<KJvmCompiledScript>, ScriptCompiler {
|
||||
val state = JvmReplCompilerState({ createReplCompilationState(it, initAnalyzer) })
|
||||
val history = JvmReplCompilerStageHistory(state)
|
||||
protected val scriptPriority = AtomicInteger()
|
||||
|
||||
override var lastCompiledSnippet: LinkedSnippetImpl<KJvmCompiledScript>? = null
|
||||
protected set
|
||||
|
||||
fun createReplCompilationState(
|
||||
scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||
initAnalyzer: (SharedScriptCompilationContext) -> AnalyzerT /* = { ReplCodeAnalyzer1(it.environment) } */
|
||||
): ReplCompilationState<AnalyzerT> {
|
||||
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<SourceCode>,
|
||||
configuration: ScriptCompilationConfiguration
|
||||
): ResultWithDiagnostics<LinkedSnippet<KJvmCompiledScript>> =
|
||||
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<CompiledScript> {
|
||||
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<AnalyzePreparationResult> =
|
||||
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<AnalyzerT : ReplCodeAnalyzerBase>(
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<Boolean> =
|
||||
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<ScriptDescriptor>
|
||||
): ResultWithDiagnostics<CompiledScript> =
|
||||
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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<ScriptDiagnostic>()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, ByteArray>()) { it.relativePath to it.asByteArray() }
|
||||
)
|
||||
|
||||
internal inline fun <T> withMessageCollectorAndDisposable(
|
||||
inline fun <T> withMessageCollectorAndDisposable(
|
||||
script: SourceCode? = null,
|
||||
parentMessageCollector: MessageCollector? = null,
|
||||
disposable: Disposable = Disposer.newDisposable(),
|
||||
@@ -57,7 +58,7 @@ internal inline fun <T> withMessageCollectorAndDisposable(
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <T> withMessageCollector(
|
||||
inline fun <T> withMessageCollector(
|
||||
script: SourceCode? = null,
|
||||
parentMessageCollector: MessageCollector? = null,
|
||||
body: (ScriptDiagnosticsMessageCollector) -> ResultWithDiagnostics<T>
|
||||
@@ -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<KtScript>()?.let {
|
||||
KJvmCompiledScript(
|
||||
containingKtFile.virtualFile?.path,
|
||||
containingKtFile.virtualFilePath,
|
||||
getScriptConfiguration(sourceFile),
|
||||
it.fqName.asString(),
|
||||
null,
|
||||
|
||||
@@ -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<ScriptDescriptor>(state.lock) {
|
||||
|
||||
override fun reset(): Iterable<ILineId> {
|
||||
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<ILineId> {
|
||||
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<ILineId>, removedAnalyzedLines: List<ReplCodeLine>) {
|
||||
|
||||
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<SourceCodeByReplLine>) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<ReplCodeLine> = replState.resetToLine(lineId)
|
||||
fun resetToLine(lineId: ILineId): List<SourceCodeByReplLine> = replState.resetToLine(lineId)
|
||||
|
||||
fun reset(): List<ReplCodeLine> = replState.reset()
|
||||
fun reset(): List<SourceCodeByReplLine> = 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<KtFile>, codeLine: ReplCodeLine): ReplLineAnalysisResult {
|
||||
fun analyzeReplLineWithImportedScripts(
|
||||
psiFile: KtFile,
|
||||
importedScripts: List<KtFile>,
|
||||
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<KtFile>, codeLine: ReplCodeLine): ReplLineAnalysisResult {
|
||||
private fun doAnalyze(linePsi: KtFile, importedScripts: List<KtFile>, 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<LineInfo.SuccessfulLine>()
|
||||
private val successfulLines = ResettableSnippetsHistory<LineInfo.SuccessfulLine>()
|
||||
private val submittedLines = hashMapOf<KtFile, LineInfo>()
|
||||
|
||||
fun resetToLine(lineId: ILineId): List<ReplCodeLine> {
|
||||
val removed = successfulLines.resetToLine(lineId.no)
|
||||
fun resetToLine(lineId: ILineId): List<SourceCodeByReplLine> {
|
||||
val removed = successfulLines.resetToLine(lineId)
|
||||
removed.forEach { submittedLines.remove(it.second.linePsi) }
|
||||
return removed.map { it.first }
|
||||
}
|
||||
|
||||
fun reset(): List<ReplCodeLine> {
|
||||
fun reset(): List<SourceCodeByReplLine> {
|
||||
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<ResultT> = List<CompiledHistoryItem<SourceCodeByReplLine, ResultT>>
|
||||
|
||||
@Deprecated("This functionality is left for backwards compatibility only", ReplaceWith("SnippetsHistory"))
|
||||
private class ResettableSnippetsHistory<ResultT>(startingHistory: CompiledHistoryList<ReplCodeAnalyzerBase.CompiledCode, ResultT> = emptyList()) :
|
||||
SnippetsHistory<ReplCodeAnalyzerBase.CompiledCode, ResultT>(startingHistory) {
|
||||
|
||||
fun resetToLine(line: ILineId): ReplSourceHistoryList<ResultT> {
|
||||
val removed = arrayListOf<Pair<SourceCodeByReplLine, ResultT>>()
|
||||
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<ResultT> {
|
||||
val removed = history.map { Pair(it.first.source, it.second) }
|
||||
history.clear()
|
||||
return removed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<CompilationT : JvmReplCompilerState.Compilation>(private val state: JvmReplCompilerState<CompilationT>) :
|
||||
BasicReplStageHistory<ScriptDescriptor>(state.lock)
|
||||
|
||||
fun checkSyntax(
|
||||
script: SourceCode,
|
||||
scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||
project: Project
|
||||
): ResultWithDiagnostics<Boolean>
|
||||
|
||||
|
||||
fun compileReplSnippet(
|
||||
compilationState: JvmReplCompilerState.Compilation,
|
||||
snippet: SourceCode,
|
||||
snippetId: ReplSnippetId,
|
||||
history: IReplStageHistory<ScriptDescriptor>
|
||||
): ResultWithDiagnostics<CompiledScript>
|
||||
}
|
||||
|
||||
class JvmReplCompilerStageHistory(private val state: JvmReplCompilerState) :
|
||||
BasicReplStageHistory<ScriptDescriptor>(state.lock) {
|
||||
|
||||
override fun reset(): Iterable<ILineId> {
|
||||
val removedCompiledLines = super.reset()
|
||||
val removedAnalyzedLines = state.compilation.analyzerEngine.reset()
|
||||
|
||||
checkConsistent(removedCompiledLines, removedAnalyzedLines)
|
||||
return removedCompiledLines
|
||||
}
|
||||
|
||||
override fun resetTo(id: ILineId): Iterable<ILineId> {
|
||||
val removedCompiledLines = super.resetTo(id)
|
||||
val removedAnalyzedLines = state.compilation.analyzerEngine.resetToLine(id)
|
||||
|
||||
checkConsistent(removedCompiledLines, removedAnalyzedLines)
|
||||
return removedCompiledLines
|
||||
}
|
||||
|
||||
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<ReplCodeLine>) {
|
||||
removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) ->
|
||||
if (removedCompiledLine != LineId(removedAnalyzedLine)) {
|
||||
throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class JvmReplCompilerState(
|
||||
val replCompilerProxy: KJvmReplCompilerProxy,
|
||||
class JvmReplCompilerState<CompilationT : JvmReplCompilerState.Compilation>(
|
||||
val createCompilation: (ScriptCompilationConfiguration) -> CompilationT,
|
||||
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
|
||||
) : IReplStageState<ScriptDescriptor> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ModuleDescriptor>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ModuleDescriptor>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<M
|
||||
.map { descriptorMap.getOrPut(it.libraryName) { getModuleDescriptorByLibrary(it, descriptorMap) } }
|
||||
}
|
||||
|
||||
fun createCompileResult(code: String) = createCompileResult(LineId(ReplCodeLine(0, 0, "")), code)
|
||||
fun createCompileResult(code: String) = createCompileResult(LineId(0, 0, 0), code)
|
||||
|
||||
fun createCompileResult(lineId: LineId, code: String): ReplCompileResult.CompiledClasses {
|
||||
return ReplCompileResult.CompiledClasses(
|
||||
@@ -219,7 +219,7 @@ class JsReplCompilationState(
|
||||
lock: ReentrantReadWriteLock,
|
||||
nameTables: NameTables,
|
||||
dependencies: List<ModuleDescriptor>,
|
||||
val replState: ReplCodeAnalyzer.ResettableAnalyzerState,
|
||||
val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState,
|
||||
val symbolTable: SymbolTable
|
||||
) : JsCompilationState(lock, nameTables, dependencies)
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
|
||||
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
|
||||
}
|
||||
@@ -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<SourceCodeCompletionVariant>, expected: Set<String>) {
|
||||
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<SourceCodeCompletionVariant>, expected: Set<String>) {
|
||||
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<Function0<Int>>(
|
||||
repl,
|
||||
"{ 1 + 2 }"
|
||||
)
|
||||
assertEvalResultIs<Function0<Int>>(
|
||||
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<T>(val x: T) { fun <R> map(f: (T) -> R): R = f(x) }".trimIndent()
|
||||
)
|
||||
)
|
||||
|
||||
// We expect that analysis time is not exponential
|
||||
for (i in 1..60) {
|
||||
repl.compileAndEval(
|
||||
SourceCodeTestImpl(
|
||||
i,
|
||||
"fun <T> Test<T>.map(f: (T) -> Double): List<Double> = listOf(f(this.x))"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun JvmTestRepl.compileAndEval(codeLine: SourceCode): Pair<ResultWithDiagnostics<LinkedSnippet<out CompiledSnippet>>, 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 <R> 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 <reified R> 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<KJvmCompiledScript>? {
|
||||
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 <T> ResultWithDiagnostics<T>.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
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -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<T>", "method")
|
||||
addCompletion("listOf()", "listOf()", "List<T>", "method")
|
||||
addCompletion("listOf(", "listOf(vararg T)", "List<T>", "method")
|
||||
addCompletion("listOfNotNull(", "listOfNotNull(T?)", "List<T>", "method")
|
||||
addCompletion("listOfNotNull(", "listOfNotNull(vararg T?)", "List<T>", "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<Run>()
|
||||
|
||||
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<RunRequest, ExpectedResult> {
|
||||
return RunRequest(cursor, code, _doCompile, _doComplete, _doErrorCheck) to _expected.collect()
|
||||
}
|
||||
|
||||
class Expected(private val run: Run) {
|
||||
private val variants = mutableListOf<SourceCodeCompletionVariant>()
|
||||
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<ScriptDiagnostic>()
|
||||
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<SourceCodeCompletionVariant>, val completionsCompType: ComparisonType,
|
||||
val errors: List<ScriptDiagnostic>, 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<RunRequest>
|
||||
): List<ResultWithDiagnostics<Pair<List<SourceCodeCompletionVariant>, List<ScriptDiagnostic>>>> {
|
||||
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 <T> checkLists(index: Int, checkName: String, expected: List<T>, actual: List<T>, 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<Pair<RunRequest, ExpectedResult>>
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<SimpleScript> {
|
||||
updateClasspath(classpathFromClass<SimpleScript>())
|
||||
}
|
||||
|
||||
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<ScriptWithBoth>())
|
||||
|
||||
providedProperties("providedString" to String::class)
|
||||
|
||||
implicitReceivers(ImplicitReceiverClass::class)
|
||||
}
|
||||
)
|
||||
|
||||
object ProvidedPropertiesConfiguration : ScriptCompilationConfiguration(
|
||||
{
|
||||
updateClasspath(classpathFromClass<ScriptWithProvidedProperties>())
|
||||
|
||||
providedProperties("providedString" to String::class)
|
||||
}
|
||||
)
|
||||
|
||||
object ImplicitReceiverConfiguration : ScriptCompilationConfiguration(
|
||||
{
|
||||
updateClasspath(classpathFromClass<ScriptWithImplicitReceiver>())
|
||||
|
||||
implicitReceivers(ImplicitReceiverClass::class)
|
||||
}
|
||||
)
|
||||
|
||||
class ImplicitReceiverClass(
|
||||
@Suppress("unused")
|
||||
val receiverString: String
|
||||
)
|
||||
|
||||
|
||||
inline fun <reified T : Any> createJvmCompilationConfigurationFromTemplate(
|
||||
hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
|
||||
noinline body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
|
||||
): ScriptCompilationConfiguration =
|
||||
createCompilationConfigurationFromTemplate(
|
||||
KotlinType(T::class),
|
||||
hostConfiguration,
|
||||
ScriptCompilationConfiguration::class,
|
||||
body
|
||||
)
|
||||
@@ -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<out CompiledSnippet>) = 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 <T> ResultWithDiagnostics<Iterable<T>>.toList() = this.valueOrNull()?.toList().orEmpty()
|
||||
|
||||
@JvmName("sequenceToList")
|
||||
fun <T> ResultWithDiagnostics<Sequence<T>>.toList() = this.valueOrNull()?.toList().orEmpty()
|
||||
41
plugins/scripting/scripting-ide-services/build.gradle.kts
Normal file
41
plugins/scripting/scripting-ide-services/build.gradle.kts
Normal file
@@ -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<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs += "-Xskip-metadata-version-check"
|
||||
freeCompilerArgs += "-Xallow-kotlin-package"
|
||||
}
|
||||
}
|
||||
|
||||
standardPublicJars()
|
||||
@@ -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<IdeLikeReplCodeAnalyzer>(hostConfiguration, {
|
||||
IdeLikeReplCodeAnalyzer(it.environment)
|
||||
}),
|
||||
ReplCompleter, ReplCodeAnalyzer {
|
||||
|
||||
override suspend fun complete(
|
||||
snippet: SourceCode,
|
||||
cursor: SourceCode.Position,
|
||||
configuration: ScriptCompilationConfiguration
|
||||
): ResultWithDiagnostics<ReplCompletionResult> =
|
||||
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<ScriptDiagnostic>.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<ReplAnalyzerResult> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<KtFile>,
|
||||
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<KtFile>): 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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<SourceCodeCompletionVariant> gen@{
|
||||
val element = getElementAt(cursor)
|
||||
|
||||
var descriptors: Collection<DeclarationDescriptor>? = 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<DeclarationDescriptor>)
|
||||
.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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <T : Any> getFrontendService(serviceClass: Class<T>): T {
|
||||
return provider.resolve(serviceClass)!!.getValue() as T
|
||||
}
|
||||
|
||||
override fun <T : Any> getIdeService(serviceClass: Class<T>): T {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun <T : Any> tryGetFrontendService(element: PsiElement, serviceClass: Class<T>): T? {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun <T : Any> getFrontendService(element: PsiElement, serviceClass: Class<T>): T {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun <T : Any> getFrontendService(moduleDescriptor: ModuleDescriptor, serviceClass: Class<T>): T {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun analyze(elements: Collection<KtElement>, bodyResolveMode: BodyResolveMode): BindingContext {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun analyzeWithAllCompilerChecks(elements: Collection<KtElement>): AnalysisResult {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun resolveToDescriptor(declaration: KtDeclaration, bodyResolveMode: BodyResolveMode): DeclarationDescriptor {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user