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-jvm-host-test:embeddableTest")
|
||||||
dependsOn(":kotlin-scripting-jsr223-test:embeddableTest")
|
dependsOn(":kotlin-scripting-jsr223-test:embeddableTest")
|
||||||
dependsOn(":kotlin-main-kts-test:test")
|
dependsOn(":kotlin-main-kts-test:test")
|
||||||
|
dependsOn(":kotlin-scripting-ide-services-test:test")
|
||||||
}
|
}
|
||||||
|
|
||||||
register("compilerTest") {
|
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 {
|
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 { lineId ->
|
||||||
|
no.compareTo(lineId.no).takeIf { no -> no != 0 }
|
||||||
override fun compareTo(other: ILineId): Int = (other as? LineId)?.let {
|
?: codeHash.compareTo(lineId.codeHash)
|
||||||
no.compareTo(it.no).takeIf { it != 0 }
|
|
||||||
?: generation.compareTo(it.generation).takeIf { it != 0 }
|
|
||||||
?: codeHash.compareTo(it.codeHash)
|
|
||||||
} ?: -1 // TODO: check if it doesn't break something
|
} ?: -1 // TODO: check if it doesn't break something
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val serialVersionUID: Long = 8328353000L
|
private const val serialVersionUID: Long = 8328354000L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-5
@@ -21,7 +21,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
|
|||||||
import kotlin.concurrent.write
|
import kotlin.concurrent.write
|
||||||
|
|
||||||
open class GenericReplCompilingEvaluatorBase(
|
open class GenericReplCompilingEvaluatorBase(
|
||||||
val compiler: ReplCompiler,
|
val compiler: ReplCompilerWithoutCheck,
|
||||||
val evaluator: ReplEvaluator,
|
val evaluator: ReplEvaluator,
|
||||||
private val fallbackScriptArgs: ScriptArgsWithTypes? = null
|
private val fallbackScriptArgs: ScriptArgsWithTypes? = null
|
||||||
) : ReplFullEvaluator {
|
) : ReplFullEvaluator {
|
||||||
@@ -46,7 +46,7 @@ open class GenericReplCompilingEvaluatorBase(
|
|||||||
}
|
}
|
||||||
ReplEvalResult.Error.CompileTime(compiled.message, compiled.location)
|
ReplEvalResult.Error.CompileTime(compiled.message, compiled.location)
|
||||||
}
|
}
|
||||||
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete()
|
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(compiled.message)
|
||||||
is ReplCompileResult.CompiledClasses -> {
|
is ReplCompileResult.CompiledClasses -> {
|
||||||
val result = eval(state, compiled, scriptArgs, invokeWrapper)
|
val result = eval(state, compiled, scriptArgs, invokeWrapper)
|
||||||
when (result) {
|
when (result) {
|
||||||
@@ -75,8 +75,6 @@ open class GenericReplCompilingEvaluatorBase(
|
|||||||
override fun eval(state: IReplStageState<*>, compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
|
override fun eval(state: IReplStageState<*>, compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
|
||||||
evaluator.eval(state, compileResult, scriptArgs, invokeWrapper)
|
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?> {
|
override fun compileToEvaluable(state: IReplStageState<*>, codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes?): Pair<ReplCompileResult, Evaluable?> {
|
||||||
val compiled = compiler.compile(state, codeLine)
|
val compiled = compiler.compile(state, codeLine)
|
||||||
return when (compiled) {
|
return when (compiled) {
|
||||||
@@ -96,7 +94,7 @@ open class GenericReplCompilingEvaluatorBase(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GenericReplCompilingEvaluator(
|
class GenericReplCompilingEvaluator(
|
||||||
compiler: ReplCompiler,
|
compiler: ReplCompilerWithoutCheck,
|
||||||
baseClasspath: Iterable<File>,
|
baseClasspath: Iterable<File>,
|
||||||
baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
|
baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
|
||||||
fallbackScriptArgs: ScriptArgsWithTypes? = null,
|
fallbackScriptArgs: ScriptArgsWithTypes? = null,
|
||||||
|
|||||||
+3
-3
@@ -25,7 +25,7 @@ const val KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY = "kotlin.script.engine"
|
|||||||
|
|
||||||
abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable {
|
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
|
protected abstract val replEvaluator: ReplFullEvaluator
|
||||||
|
|
||||||
override fun eval(script: String, context: ScriptContext): Any? = compileAndEval(script, context)
|
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 result = replCompiler.compile(state, codeLine)
|
||||||
val compiled = when (result) {
|
val compiled = when (result) {
|
||||||
is ReplCompileResult.Error -> throw ScriptException("Error${result.locationString()}: ${result.message}")
|
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
|
is ReplCompileResult.CompiledClasses -> result
|
||||||
}
|
}
|
||||||
return CompiledKotlinScript(this, codeLine, compiled)
|
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)
|
throw ScriptException(result.message, result.location.path, result.location.line, result.location.column)
|
||||||
else -> throw ScriptException(result.message)
|
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}")
|
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 }
|
companion object { private val serialVersionUID: Long = 2L }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Incomplete : ReplCompileResult() {
|
class Incomplete(val message: String) : ReplCompileResult() {
|
||||||
companion object { private val serialVersionUID: Long = 1L }
|
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
|
// --- eval
|
||||||
|
|
||||||
@@ -137,7 +139,7 @@ sealed class ReplEvalResult : Serializable {
|
|||||||
companion object { private val serialVersionUID: Long = 1L }
|
companion object { private val serialVersionUID: Long = 1L }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Incomplete : ReplEvalResult() {
|
class Incomplete(val message: String) : ReplEvalResult() {
|
||||||
companion object { private val serialVersionUID: Long = 1L }
|
companion object { private val serialVersionUID: Long = 1L }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +177,7 @@ interface ReplAtomicEvalAction {
|
|||||||
invokeWrapper: InvokeWrapper? = null): ReplEvalResult
|
invokeWrapper: InvokeWrapper? = null): ReplEvalResult
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReplAtomicEvaluator : ReplAtomicEvalAction, ReplCheckAction
|
interface ReplAtomicEvaluator : ReplAtomicEvalAction
|
||||||
|
|
||||||
interface ReplDelayedEvalAction {
|
interface ReplDelayedEvalAction {
|
||||||
fun compileToEvaluable(state: IReplStageState<*>,
|
fun compileToEvaluable(state: IReplStageState<*>,
|
||||||
|
|||||||
-3
@@ -8,15 +8,12 @@ package org.jetbrains.kotlin.daemon.client.experimental
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
import org.jetbrains.kotlin.cli.common.repl.*
|
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.*
|
||||||
import org.jetbrains.kotlin.daemon.common.CompileServiceClientSide
|
|
||||||
import org.jetbrains.kotlin.daemon.common.experimental.findCallbackServerSocket
|
import org.jetbrains.kotlin.daemon.common.experimental.findCallbackServerSocket
|
||||||
import org.jetbrains.kotlin.daemon.common.ReportCategory
|
import org.jetbrains.kotlin.daemon.common.ReportCategory
|
||||||
import org.jetbrains.kotlin.daemon.common.ReportSeverity
|
import org.jetbrains.kotlin.daemon.common.ReportSeverity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
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)
|
// 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 = {
|
sdax = {
|
||||||
^
|
^
|
||||||
compiler/testData/multiplatform/regressions/kt28385/jvm.kt:5:8: error: expecting a top level declaration
|
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 = {
|
sdax = {
|
||||||
^
|
^
|
||||||
compiler/testData/multiplatform/regressions/kt28385/jvm.kt:3:1: error: property must be initialized
|
compiler/testData/multiplatform/regressions/kt28385/jvm.kt:3:1: error: property must be initialized
|
||||||
val dasda
|
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()
|
private val messageCollector = MyMessageCollector()
|
||||||
|
|
||||||
override val replCompiler: ReplCompiler by lazy {
|
override val replCompiler: ReplCompilerWithoutCheck by lazy {
|
||||||
KotlinRemoteReplCompilerClient(
|
KotlinRemoteReplCompilerClient(
|
||||||
daemon,
|
daemon,
|
||||||
makeAutodeletingFlagFile("idea-jsr223-repl-session"),
|
makeAutodeletingFlagFile("idea-jsr223-repl-session"),
|
||||||
|
|||||||
-1
@@ -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.messages.MessageCollector
|
||||||
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
|
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
|
||||||
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
|
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.common.repl.ReplEvalResult
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
|
|||||||
+5
-5
@@ -21,8 +21,8 @@ import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator
|
|||||||
class LegacyReplTest : TestCase() {
|
class LegacyReplTest : TestCase() {
|
||||||
fun testReplBasics() {
|
fun testReplBasics() {
|
||||||
LegacyTestRepl().use { repl ->
|
LegacyTestRepl().use { repl ->
|
||||||
val res1 = repl.replCompiler.check(repl.state, ReplCodeLine(0, 0, "val x ="))
|
val res1 = repl.replCompiler.compile(repl.state, ReplCodeLine(0, 0, "val x ="))
|
||||||
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete)
|
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCompileResult.Incomplete)
|
||||||
|
|
||||||
assertEvalResult(repl, "val l1 = listOf(1 + 2)\nl1.first()", 3)
|
assertEvalResult(repl, "val l1 = listOf(1 + 2)\nl1.first()", 3)
|
||||||
|
|
||||||
@@ -54,8 +54,8 @@ class LegacyReplTest : TestCase() {
|
|||||||
fun testReplCodeFormat() {
|
fun testReplCodeFormat() {
|
||||||
LegacyTestRepl().use { repl ->
|
LegacyTestRepl().use { repl ->
|
||||||
val codeLine0 = ReplCodeLine(0, 0, "val l1 = 1\r\nl1\r\n")
|
val codeLine0 = ReplCodeLine(0, 0, "val l1 = 1\r\nl1\r\n")
|
||||||
val res0 = repl.replCompiler.check(repl.state, codeLine0)
|
val res0 = repl.replCompiler.compile(repl.state, codeLine0)
|
||||||
val res0c = res0 as? ReplCheckResult.Ok
|
val res0c = res0 as? ReplCompileResult.CompiledClasses
|
||||||
TestCase.assertNotNull("Unexpected compile result: $res0", res0c)
|
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)
|
fun nextCodeLine(code: String): ReplCodeLine = ReplCodeLine(currentLineCounter.getAndIncrement(), 0, code)
|
||||||
|
|
||||||
val replCompiler: JvmReplCompiler by lazy {
|
val replCompiler: JvmReplCompiler by lazy {
|
||||||
JvmReplCompiler(simpleScriptCompilationConfiguration)
|
JvmReplCompiler(simpleScriptCompilationConfiguration, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val compiledEvaluator: ReplEvaluator by lazy {
|
val compiledEvaluator: ReplEvaluator by lazy {
|
||||||
|
|||||||
+91
-104
@@ -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.
|
* 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 junit.framework.TestCase
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.jetbrains.kotlin.cli.common.repl.BasicReplStageHistory
|
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase
|
||||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
|
||||||
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerImpl
|
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.script.experimental.api.*
|
import kotlin.script.experimental.api.*
|
||||||
import kotlin.script.experimental.host.toScriptSource
|
import kotlin.script.experimental.host.toScriptSource
|
||||||
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
|
import kotlin.script.experimental.jvm.BasicJvmReplEvaluator
|
||||||
import kotlin.script.experimental.jvm.baseClassLoader
|
|
||||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
||||||
import kotlin.script.experimental.jvm.jvm
|
|
||||||
|
|
||||||
class ReplTest : TestCase() {
|
class ReplTest : TestCase() {
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TEST_DATA_DIR = "libraries/scripting/jvm-host-test/testData"
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCompileAndEval() {
|
fun testCompileAndEval() {
|
||||||
val out = captureOut {
|
val out = captureOut {
|
||||||
@@ -156,103 +148,98 @@ class ReplTest : TestCase() {
|
|||||||
limit = 100
|
limit = 100
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun evaluateInRepl(
|
companion object {
|
||||||
snippets: Sequence<String>,
|
private fun evaluateInRepl(
|
||||||
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
|
snippets: Sequence<String>,
|
||||||
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
|
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
|
||||||
limit: Int = 0
|
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
|
||||||
): Sequence<ResultWithDiagnostics<EvaluationResult>> {
|
limit: Int = 0
|
||||||
val replCompilerProxy =
|
): Sequence<ResultWithDiagnostics<EvaluatedSnippet>> {
|
||||||
KJvmReplCompilerImpl(defaultJvmScriptingHostConfiguration)
|
val replCompiler = KJvmReplCompilerBase.create(defaultJvmScriptingHostConfiguration)
|
||||||
val compilationState = replCompilerProxy.createReplCompilationState(compilationConfiguration)
|
val replEvaluator = BasicJvmReplEvaluator()
|
||||||
val compilationHistory = BasicReplStageHistory<ScriptDescriptor>()
|
val currentEvalConfig = evaluationConfiguration ?: ScriptEvaluationConfiguration()
|
||||||
val replEvaluator = BasicJvmScriptEvaluator()
|
val snipetsLimited = if (limit == 0) snippets else snippets.take(limit)
|
||||||
var currentEvalConfig = evaluationConfiguration ?: ScriptEvaluationConfiguration()
|
return snipetsLimited.mapIndexed { snippetNo, snippetText ->
|
||||||
val snipetsLimited = if (limit == 0) snippets else snippets.take(limit)
|
val snippetSource =
|
||||||
return snipetsLimited.mapIndexed { snippetNo, snippetText ->
|
snippetText.toScriptSource("Line_$snippetNo.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}")
|
||||||
val snippetSource =
|
runBlocking { replCompiler.compile(snippetSource, compilationConfiguration) }
|
||||||
snippetText.toScriptSource("Line_$snippetNo.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}")
|
.onSuccess {
|
||||||
val snippetId = ReplSnippetIdImpl(snippetNo, 0, snippetSource)
|
runBlocking { replEvaluator.eval(it, currentEvalConfig) }
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
.onSuccess {
|
||||||
it.asSuccess()
|
it.get().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
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
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()) {
|
fun checkEvaluateInReplDiags(
|
||||||
Assert.fail("Expected ${expectedIter.next()} got end of results stream")
|
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)
|
|
||||||
|
|||||||
+1
@@ -13,6 +13,7 @@ import kotlin.script.experimental.host.toScriptSource
|
|||||||
import kotlin.script.experimental.jvm.*
|
import kotlin.script.experimental.jvm.*
|
||||||
import kotlin.script.experimental.jvm.util.classpathFromClass
|
import kotlin.script.experimental.jvm.util.classpathFromClass
|
||||||
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
|
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
|
||||||
|
import kotlin.script.experimental.jvmhost.test.ReplTest.Companion.checkEvaluateInRepl
|
||||||
|
|
||||||
class ResolveDependenciesTest : TestCase() {
|
class ResolveDependenciesTest : TestCase() {
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -21,7 +21,6 @@ import java.net.URLClassLoader
|
|||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.jar.JarFile
|
import java.util.jar.JarFile
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.script.experimental.api.*
|
import kotlin.script.experimental.api.*
|
||||||
import kotlin.script.experimental.host.BasicScriptingHost
|
import kotlin.script.experimental.host.BasicScriptingHost
|
||||||
import kotlin.script.experimental.host.FileBasedScriptSource
|
import kotlin.script.experimental.host.FileBasedScriptSource
|
||||||
@@ -348,7 +347,7 @@ class ScriptingHostTest : TestCase() {
|
|||||||
assertTrue(compiledScript is ResultWithDiagnostics.Success)
|
assertTrue(compiledScript is ResultWithDiagnostics.Success)
|
||||||
|
|
||||||
val jvmCompiledScript = compiledScript.valueOrNull()!! as KJvmCompiledScript
|
val jvmCompiledScript = compiledScript.valueOrNull()!! as KJvmCompiledScript
|
||||||
val jvmCompiledModule = jvmCompiledScript.compiledModule as KJvmCompiledModuleInMemoryImpl
|
val jvmCompiledModule = jvmCompiledScript.getCompiledModule() as KJvmCompiledModuleInMemoryImpl
|
||||||
val bytes = jvmCompiledModule.compilerOutputFiles["SavedScript.class"]!!
|
val bytes = jvmCompiledModule.compilerOutputFiles["SavedScript.class"]!!
|
||||||
|
|
||||||
var classFileVersion: Int? = null
|
var classFileVersion: Int? = null
|
||||||
|
|||||||
+3
-2
@@ -6,6 +6,7 @@
|
|||||||
package kotlin.script.experimental.jvmhost.jsr223
|
package kotlin.script.experimental.jvmhost.jsr223
|
||||||
|
|
||||||
import org.jetbrains.kotlin.cli.common.repl.*
|
import org.jetbrains.kotlin.cli.common.repl.*
|
||||||
|
import org.jetbrains.kotlin.cli.common.repl.ReplCompilerWithoutCheck
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
import javax.script.ScriptContext
|
import javax.script.ScriptContext
|
||||||
import javax.script.ScriptEngineFactory
|
import javax.script.ScriptEngineFactory
|
||||||
@@ -61,8 +62,8 @@ class KotlinJsr223ScriptEngineImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val replCompiler: ReplCompiler by lazy {
|
override val replCompiler: ReplCompilerWithoutCheck by lazy {
|
||||||
JvmReplCompiler(compilationConfiguration)
|
JvmReplCompiler(compilationConfiguration, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val localEvaluator by lazy {
|
private val localEvaluator by lazy {
|
||||||
|
|||||||
+4
-4
@@ -27,8 +27,8 @@ open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvalua
|
|||||||
try {
|
try {
|
||||||
if (compiledScript !is KJvmCompiledScript)
|
if (compiledScript !is KJvmCompiledScript)
|
||||||
return failure("Cannot generate classes: unsupported compiled script type $compiledScript")
|
return failure("Cannot generate classes: unsupported compiled script type $compiledScript")
|
||||||
val module = (compiledScript.compiledModule as? KJvmCompiledModuleInMemory)
|
val module = (compiledScript.getCompiledModule() as? KJvmCompiledModuleInMemory)
|
||||||
?: return failure("Cannot generate classes: unsupported module type ${compiledScript.compiledModule}")
|
?: return failure("Cannot generate classes: unsupported module type ${compiledScript.getCompiledModule()}")
|
||||||
for ((path, bytes) in module.compilerOutputFiles) {
|
for ((path, bytes) in module.compilerOutputFiles) {
|
||||||
File(outputDir, path).apply {
|
File(outputDir, path).apply {
|
||||||
if (!parentFile.isDirectory) {
|
if (!parentFile.isDirectory) {
|
||||||
@@ -47,8 +47,8 @@ open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvalua
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun KJvmCompiledScript.saveToJar(outputJar: File) {
|
fun KJvmCompiledScript.saveToJar(outputJar: File) {
|
||||||
val module = (compiledModule as? KJvmCompiledModuleInMemory)
|
val module = (getCompiledModule() as? KJvmCompiledModuleInMemory)
|
||||||
?: throw IllegalArgumentException("Unsupported module type $compiledModule")
|
?: throw IllegalArgumentException("Unsupported module type ${getCompiledModule()}")
|
||||||
val dependenciesFromScript = compilationConfiguration[ScriptCompilationConfiguration.dependencies]
|
val dependenciesFromScript = compilationConfiguration[ScriptCompilationConfiguration.dependencies]
|
||||||
?.filterIsInstance<JvmDependency>()
|
?.filterIsInstance<JvmDependency>()
|
||||||
?.flatMap { it.classpath }
|
?.flatMap { it.classpath }
|
||||||
|
|||||||
+67
-37
@@ -5,66 +5,96 @@
|
|||||||
|
|
||||||
package kotlin.script.experimental.jvmhost.repl
|
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.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.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 java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
import kotlin.concurrent.write
|
import kotlin.concurrent.write
|
||||||
import kotlin.script.experimental.api.*
|
import kotlin.script.experimental.api.*
|
||||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||||
import kotlin.script.experimental.host.withDefaultsFrom
|
import kotlin.script.experimental.host.withDefaultsFrom
|
||||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
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
|
* REPL Compilation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package
|
||||||
*/
|
*/
|
||||||
class JvmReplCompiler(
|
class JvmReplCompiler(
|
||||||
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||||
|
val allowReInit: Boolean = true,
|
||||||
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
|
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
|
||||||
val replCompilerProxy: KJvmReplCompilerProxy = KJvmReplCompilerImpl(
|
var replCompiler: KJvmReplCompilerBase<ReplCodeAnalyzerBase> = KJvmReplCompilerBase.create(
|
||||||
hostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
|
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 replCompilerState = state.asState(JvmReplCompilerState::class.java)
|
||||||
val compilation = replCompilerState.getCompilationState(scriptCompilationConfiguration)
|
val snippet = codeLine.toSourceCode(scriptCompilationConfiguration)
|
||||||
val res =
|
|
||||||
replCompilerProxy.checkSyntax(
|
if (allowReInit) {
|
||||||
codeLine.toSourceCode(scriptCompilationConfiguration),
|
replCompiler = compilers.find { historiesEq(it.history, replCompilerState.history) } ?: {
|
||||||
compilation.baseScriptCompilationConfiguration,
|
compilers.push(
|
||||||
compilation.environment.project
|
KJvmReplCompilerBase.create(
|
||||||
)
|
hostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
|
||||||
when {
|
)
|
||||||
// TODO: implement diagnostics rendering
|
)
|
||||||
res is ResultWithDiagnostics.Success && res.value -> ReplCheckResult.Ok()
|
compilers.last()
|
||||||
res is ResultWithDiagnostics.Success && !res.value -> ReplCheckResult.Incomplete()
|
}()
|
||||||
else -> ReplCheckResult.Error(res.reports.joinToString("\n") { it.message })
|
}
|
||||||
|
|
||||||
|
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 {
|
companion object {
|
||||||
val replCompilerState = state.asState(JvmReplCompilerState::class.java)
|
fun historiesEq(history1: IReplStageHistory<*>, history2: IReplStageHistory<*>) =
|
||||||
val compilation = replCompilerState.getCompilationState(scriptCompilationConfiguration)
|
history1.count() == history2.count() &&
|
||||||
val snippet = codeLine.toSourceCode(scriptCompilationConfiguration)
|
history1.zip(history2).all {
|
||||||
val snippetId = ReplSnippetIdImpl(codeLine.no, codeLine.generation, snippet)
|
val (it1, it2) = it
|
||||||
when (val res = replCompilerProxy.compileReplSnippet(compilation, snippet, snippetId, replCompilerState.history)) {
|
it1.item === it2.item
|
||||||
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 })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -7,6 +7,7 @@ package kotlin.script.experimental.jvmhost.repl
|
|||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.jetbrains.kotlin.cli.common.repl.*
|
import org.jetbrains.kotlin.cli.common.repl.*
|
||||||
|
import org.jetbrains.kotlin.cli.common.repl.ReplEvaluator
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
import kotlin.concurrent.write
|
import kotlin.concurrent.write
|
||||||
import kotlin.reflect.KClass
|
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.baseClassLoader
|
||||||
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
|
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
|
||||||
import kotlin.script.experimental.jvm.jvm
|
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
|
* 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 {
|
): ReplEvalResult = state.lock.write {
|
||||||
val evalState = state.asState(JvmReplEvaluatorState::class.java)
|
val evalState = state.asState(JvmReplEvaluatorState::class.java)
|
||||||
val history = evalState.history as ReplStageHistoryWithReplace
|
val history = evalState.history as ReplStageHistoryWithReplace
|
||||||
val compiledScript = (compileResult.data as? KJvmCompiledScript)
|
val compiledScriptList = (compileResult.data as? LinkedSnippetImpl<*>)
|
||||||
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compileResult.data}")
|
?: 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 lastSnippetClass = history.peek()?.item?.first
|
||||||
val historyBeforeSnippet = history.previousItems(compileResult.lineId).map { it.second }.toList()
|
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
|
||||||
+8
-4
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package kotlin.script.experimental.jvm.impl
|
package kotlin.script.experimental.jvm.impl
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.TestOnly
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
@@ -39,13 +40,13 @@ internal class KJvmCompiledScriptData(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private val serialVersionUID = 4L
|
private val serialVersionUID = 5L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class KJvmCompiledScript internal constructor(
|
open class KJvmCompiledScript internal constructor(
|
||||||
internal var data: KJvmCompiledScriptData,
|
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 {
|
) : CompiledScript, Serializable {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -93,6 +94,9 @@ class KJvmCompiledScript internal constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestOnly
|
||||||
|
fun getCompiledModule() = compiledModule
|
||||||
|
|
||||||
private fun writeObject(outputStream: ObjectOutputStream) {
|
private fun writeObject(outputStream: ObjectOutputStream) {
|
||||||
outputStream.writeObject(data)
|
outputStream.writeObject(data)
|
||||||
outputStream.writeObject(compiledModule)
|
outputStream.writeObject(compiledModule)
|
||||||
@@ -177,7 +181,7 @@ fun KJvmCompiledScript.toBytes(): ByteArray {
|
|||||||
oos = ObjectOutputStream(bos)
|
oos = ObjectOutputStream(bos)
|
||||||
oos.writeObject(this)
|
oos.writeObject(this)
|
||||||
oos.flush()
|
oos.flush()
|
||||||
return bos.toByteArray()!!
|
return bos.toByteArray()
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
oos?.close()
|
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>
|
<artifactId>kotlin-scripting-jvm-host</artifactId>
|
||||||
<version>${kotlin.version}</version>
|
<version>${kotlin.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>${project.groupId}</groupId>
|
||||||
|
<artifactId>kotlin-scripting-ide-services</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
<!-- Compiler dependency required by scripting -->
|
<!-- Compiler dependency required by scripting -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
|
package org.jetbrains.kotlin.mainKts.test
|
||||||
|
|
||||||
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
|
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.Assert
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import javax.script.ScriptEngineManager
|
import javax.script.ScriptEngineManager
|
||||||
|
|||||||
+248
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
-186
@@ -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)
|
|
||||||
|
|
||||||
+2
-2
@@ -52,14 +52,14 @@ import kotlin.script.experimental.jvm.jvm
|
|||||||
import kotlin.script.experimental.jvm.util.KotlinJars
|
import kotlin.script.experimental.jvm.util.KotlinJars
|
||||||
import kotlin.script.experimental.jvm.withUpdatedClasspath
|
import kotlin.script.experimental.jvm.withUpdatedClasspath
|
||||||
|
|
||||||
internal class SharedScriptCompilationContext(
|
class SharedScriptCompilationContext(
|
||||||
val disposable: Disposable?,
|
val disposable: Disposable?,
|
||||||
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration,
|
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||||
val environment: KotlinCoreEnvironment,
|
val environment: KotlinCoreEnvironment,
|
||||||
val ignoredOptionsReportingState: IgnoredOptionsReportingState
|
val ignoredOptionsReportingState: IgnoredOptionsReportingState
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun createIsolatedCompilationContext(
|
fun createIsolatedCompilationContext(
|
||||||
baseScriptCompilationConfiguration: ScriptCompilationConfiguration,
|
baseScriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||||
hostConfiguration: ScriptingHostConfiguration,
|
hostConfiguration: ScriptingHostConfiguration,
|
||||||
messageCollector: ScriptDiagnosticsMessageCollector,
|
messageCollector: ScriptDiagnosticsMessageCollector,
|
||||||
|
|||||||
+4
-4
@@ -18,7 +18,7 @@ import kotlin.script.experimental.api.ScriptDiagnostic
|
|||||||
import kotlin.script.experimental.api.SourceCode
|
import kotlin.script.experimental.api.SourceCode
|
||||||
import kotlin.script.experimental.api.asErrorDiagnostics
|
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>()
|
private val _diagnostics = arrayListOf<ScriptDiagnostic>()
|
||||||
|
|
||||||
@@ -78,17 +78,17 @@ private fun ScriptDiagnostic.Severity.toCompilerMessageSeverity(): CompilerMessa
|
|||||||
ScriptDiagnostic.Severity.FATAL -> CompilerMessageSeverity.EXCEPTION
|
ScriptDiagnostic.Severity.FATAL -> CompilerMessageSeverity.EXCEPTION
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun failure(
|
fun failure(
|
||||||
messageCollector: ScriptDiagnosticsMessageCollector, vararg diagnostics: ScriptDiagnostic
|
messageCollector: ScriptDiagnosticsMessageCollector, vararg diagnostics: ScriptDiagnostic
|
||||||
): ResultWithDiagnostics.Failure =
|
): ResultWithDiagnostics.Failure =
|
||||||
ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics)
|
ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics)
|
||||||
|
|
||||||
internal fun failure(
|
fun failure(
|
||||||
script: SourceCode, messageCollector: ScriptDiagnosticsMessageCollector, message: String
|
script: SourceCode, messageCollector: ScriptDiagnosticsMessageCollector, message: String
|
||||||
): ResultWithDiagnostics.Failure =
|
): ResultWithDiagnostics.Failure =
|
||||||
failure(messageCollector, message.asErrorDiagnostics(path = script.locationId))
|
failure(messageCollector, message.asErrorDiagnostics(path = script.locationId))
|
||||||
|
|
||||||
internal class IgnoredOptionsReportingState {
|
class IgnoredOptionsReportingState {
|
||||||
var currentArguments = K2JVMCompilerArguments()
|
var currentArguments = K2JVMCompilerArguments()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-3
@@ -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.resolve.ScriptLightVirtualFile
|
||||||
import org.jetbrains.kotlin.scripting.scriptFileName
|
import org.jetbrains.kotlin.scripting.scriptFileName
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
import java.io.Serializable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.script.experimental.api.*
|
import kotlin.script.experimental.api.*
|
||||||
import kotlin.script.experimental.host.FileBasedScriptSource
|
import kotlin.script.experimental.host.FileBasedScriptSource
|
||||||
@@ -33,7 +34,7 @@ internal fun makeCompiledModule(generationState: GenerationState) =
|
|||||||
.associateTo(sortedMapOf<String, ByteArray>()) { it.relativePath to it.asByteArray() }
|
.associateTo(sortedMapOf<String, ByteArray>()) { it.relativePath to it.asByteArray() }
|
||||||
)
|
)
|
||||||
|
|
||||||
internal inline fun <T> withMessageCollectorAndDisposable(
|
inline fun <T> withMessageCollectorAndDisposable(
|
||||||
script: SourceCode? = null,
|
script: SourceCode? = null,
|
||||||
parentMessageCollector: MessageCollector? = null,
|
parentMessageCollector: MessageCollector? = null,
|
||||||
disposable: Disposable = Disposer.newDisposable(),
|
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,
|
script: SourceCode? = null,
|
||||||
parentMessageCollector: MessageCollector? = null,
|
parentMessageCollector: MessageCollector? = null,
|
||||||
body: (ScriptDiagnosticsMessageCollector) -> ResultWithDiagnostics<T>
|
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(
|
internal fun makeCompiledScript(
|
||||||
generationState: GenerationState,
|
generationState: GenerationState,
|
||||||
script: SourceCode,
|
script: SourceCode,
|
||||||
@@ -122,7 +133,7 @@ internal fun makeCompiledScript(
|
|||||||
sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.valueOrThrow()?.mapNotNull { sourceFile ->
|
sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.valueOrThrow()?.mapNotNull { sourceFile ->
|
||||||
sourceFile.declarations.firstIsInstanceOrNull<KtScript>()?.let {
|
sourceFile.declarations.firstIsInstanceOrNull<KtScript>()?.let {
|
||||||
KJvmCompiledScript(
|
KJvmCompiledScript(
|
||||||
containingKtFile.virtualFile?.path,
|
containingKtFile.virtualFilePath,
|
||||||
getScriptConfiguration(sourceFile),
|
getScriptConfiguration(sourceFile),
|
||||||
it.fqName.asString(),
|
it.fqName.asString(),
|
||||||
null,
|
null,
|
||||||
|
|||||||
+17
-11
@@ -11,29 +11,35 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
|||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.messages.DiagnosticMessageHolder
|
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.messages.DiagnosticMessageHolder
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
|
import kotlin.concurrent.write
|
||||||
import kotlin.script.experimental.dependencies.ScriptDependencies
|
import kotlin.script.experimental.dependencies.ScriptDependencies
|
||||||
|
|
||||||
class ReplCompilerStageHistory(private val state: GenericReplCompilerState) : BasicReplStageHistory<ScriptDescriptor>(state.lock) {
|
class ReplCompilerStageHistory(private val state: GenericReplCompilerState) : BasicReplStageHistory<ScriptDescriptor>(state.lock) {
|
||||||
|
|
||||||
override fun reset(): Iterable<ILineId> {
|
override fun reset(): Iterable<ILineId> {
|
||||||
val removedCompiledLines = super.reset()
|
lock.write {
|
||||||
val removedAnalyzedLines = state.analyzerEngine.reset()
|
val removedCompiledLines = super.reset()
|
||||||
|
val removedAnalyzedLines = state.analyzerEngine.reset()
|
||||||
|
|
||||||
checkConsistent(removedCompiledLines, removedAnalyzedLines)
|
checkConsistent(removedCompiledLines, removedAnalyzedLines)
|
||||||
return removedCompiledLines
|
return removedCompiledLines
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetTo(id: ILineId): Iterable<ILineId> {
|
override fun resetTo(id: ILineId): Iterable<ILineId> {
|
||||||
val removedCompiledLines = super.resetTo(id)
|
lock.write {
|
||||||
val removedAnalyzedLines = state.analyzerEngine.resetToLine(id)
|
val removedCompiledLines = super.resetTo(id)
|
||||||
|
val removedAnalyzedLines = state.analyzerEngine.resetToLine(id)
|
||||||
|
|
||||||
checkConsistent(removedCompiledLines, removedAnalyzedLines)
|
checkConsistent(removedCompiledLines, removedAnalyzedLines)
|
||||||
return removedCompiledLines
|
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) ->
|
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")
|
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()
|
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
|
||||||
|
|
||||||
val analyzerEngine = ReplCodeAnalyzer(environment)
|
val analyzerEngine = ReplCodeAnalyzerBase(environment)
|
||||||
|
|
||||||
var lastDependencies: ScriptDependencies? = null
|
var lastDependencies: ScriptDependencies? = null
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -59,7 +59,7 @@ open class GenericReplCompiler(
|
|||||||
if (compilerState.lastLineState == null || compilerState.lastLineState!!.codeLine != codeLine) {
|
if (compilerState.lastLineState == null || compilerState.lastLineState!!.codeLine != codeLine) {
|
||||||
val res = checker.check(state, codeLine)
|
val res = checker.check(state, codeLine)
|
||||||
when (res) {
|
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.Error -> return@compile ReplCompileResult.Error(res.message, res.location)
|
||||||
is ReplCheckResult.Ok -> {
|
is ReplCheckResult.Ok -> {
|
||||||
} // continue
|
} // continue
|
||||||
@@ -81,10 +81,10 @@ open class GenericReplCompiler(
|
|||||||
val analysisResult = compilerState.analyzerEngine.analyzeReplLine(psiFile, codeLine)
|
val analysisResult = compilerState.analyzerEngine.analyzeReplLine(psiFile, codeLine)
|
||||||
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
|
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
|
||||||
val scriptDescriptor = when (analysisResult) {
|
val scriptDescriptor = when (analysisResult) {
|
||||||
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> {
|
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.WithErrors -> {
|
||||||
return ReplCompileResult.Error(errorHolder.renderMessage())
|
return ReplCompileResult.Error(errorHolder.renderMessage())
|
||||||
}
|
}
|
||||||
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> {
|
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.Successful -> {
|
||||||
(analysisResult.scriptDescriptor as? ScriptDescriptor)
|
(analysisResult.scriptDescriptor as? ScriptDescriptor)
|
||||||
?: error("Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}")
|
?: error("Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}")
|
||||||
}
|
}
|
||||||
@@ -108,12 +108,12 @@ open class GenericReplCompiler(
|
|||||||
setOf(psiFile.script!!.containingKtFile)
|
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()) }
|
val classes = generationState.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }
|
||||||
|
|
||||||
return ReplCompileResult.CompiledClasses(
|
return ReplCompileResult.CompiledClasses(
|
||||||
LineId(codeLine),
|
LineId(codeLine.no, 0, codeLine.hashCode()),
|
||||||
compilerState.history.map { it.id },
|
compilerState.history.map { it.id },
|
||||||
scriptDescriptor.name.identifier,
|
scriptDescriptor.name.identifier,
|
||||||
classes,
|
classes,
|
||||||
|
|||||||
+90
-36
@@ -7,13 +7,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.scripting.compiler.plugin.repl
|
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.ILineId
|
||||||
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
|
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.KotlinCoreEnvironment
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
|
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
|
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
|
||||||
|
import org.jetbrains.kotlin.container.ComponentProvider
|
||||||
import org.jetbrains.kotlin.container.get
|
import org.jetbrains.kotlin.container.get
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
|
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
|
||||||
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
|
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.parentsWithSelf
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
|
import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
|
||||||
import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities
|
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) {
|
open class ReplCodeAnalyzerBase(
|
||||||
private val topDownAnalysisContext: TopDownAnalysisContext
|
environment: KotlinCoreEnvironment,
|
||||||
private val topDownAnalyzer: LazyTopDownAnalyzer
|
val trace: BindingTraceContext = NoScopeRecordCliBindingTrace()
|
||||||
private val resolveSession: ResolveSession
|
) {
|
||||||
private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
|
protected val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
|
||||||
private val replState = ResettableAnalyzerState()
|
|
||||||
|
protected val container: ComponentProvider
|
||||||
|
protected val topDownAnalysisContext: TopDownAnalysisContext
|
||||||
|
protected val topDownAnalyzer: LazyTopDownAnalyzer
|
||||||
|
protected val resolveSession: ResolveSession
|
||||||
|
protected val replState = ResettableAnalyzerState()
|
||||||
|
|
||||||
val module: ModuleDescriptorImpl
|
val module: ModuleDescriptorImpl
|
||||||
|
|
||||||
val trace: BindingTraceContext = NoScopeRecordCliBindingTrace()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Module source scope is empty because all binary classes are in the dependency module, and all source classes are guaranteed
|
// 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
|
// 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)
|
// because no symbol declared in the REPL session can be used from Java)
|
||||||
val container = TopDownAnalyzerFacadeForJVM.createContainer(
|
container = TopDownAnalyzerFacadeForJVM.createContainer(
|
||||||
environment.project,
|
environment.project,
|
||||||
emptyList(),
|
emptyList(),
|
||||||
trace,
|
trace,
|
||||||
@@ -71,7 +77,10 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
|
|||||||
val scriptDescriptor: ClassDescriptorWithResolutionScopes?
|
val scriptDescriptor: ClassDescriptorWithResolutionScopes?
|
||||||
val diagnostics: Diagnostics
|
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
|
ReplLineAnalysisResult
|
||||||
|
|
||||||
data class WithErrors(override val diagnostics: Diagnostics) :
|
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 {
|
fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
|
||||||
topDownAnalysisContext.scripts.clear()
|
topDownAnalysisContext.scripts.clear()
|
||||||
@@ -90,31 +99,38 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
|
|||||||
|
|
||||||
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
|
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()
|
topDownAnalysisContext.scripts.clear()
|
||||||
trace.clearDiagnostics()
|
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(
|
scriptDeclarationFactory.setDelegateFactory(
|
||||||
FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi) + importedScripts)
|
FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi) + importedScripts)
|
||||||
)
|
)
|
||||||
replState.submitLine(linePsi, codeLine)
|
replState.submitLine(linePsi)
|
||||||
|
|
||||||
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts)
|
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts)
|
||||||
|
|
||||||
val diagnostics = trace.bindingContext.diagnostics
|
val diagnostics = trace.bindingContext.diagnostics
|
||||||
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }
|
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }
|
||||||
return if (hasErrors) {
|
return if (hasErrors) {
|
||||||
replState.lineFailure(linePsi, codeLine)
|
replState.lineFailure(linePsi)
|
||||||
ReplLineAnalysisResult.WithErrors(diagnostics)
|
ReplLineAnalysisResult.WithErrors(
|
||||||
|
diagnostics
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
val scriptDescriptor = context.scripts[linePsi.script]!!
|
val scriptDescriptor = context.scripts[linePsi.script]!!
|
||||||
replState.lineSuccess(linePsi, codeLine, scriptDescriptor)
|
replState.lineSuccess(linePsi, codeLine, scriptDescriptor)
|
||||||
@@ -123,10 +139,9 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
|
|||||||
diagnostics
|
diagnostics
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScriptMutableDeclarationProviderFactory : DeclarationProviderFactory {
|
protected class ScriptMutableDeclarationProviderFactory : DeclarationProviderFactory {
|
||||||
private lateinit var delegateFactory: DeclarationProviderFactory
|
private lateinit var delegateFactory: DeclarationProviderFactory
|
||||||
private lateinit var rootPackageProvider: AdaptablePackageMemberDeclarationProvider
|
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: 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)
|
// TODO: review its place in the extracted state infrastructure (now the analyzer itself is a part of the state)
|
||||||
class ResettableAnalyzerState {
|
class ResettableAnalyzerState {
|
||||||
private val successfulLines = ReplHistory<LineInfo.SuccessfulLine>()
|
private val successfulLines = ResettableSnippetsHistory<LineInfo.SuccessfulLine>()
|
||||||
private val submittedLines = hashMapOf<KtFile, LineInfo>()
|
private val submittedLines = hashMapOf<KtFile, LineInfo>()
|
||||||
|
|
||||||
fun resetToLine(lineId: ILineId): List<ReplCodeLine> {
|
fun resetToLine(lineId: ILineId): List<SourceCodeByReplLine> {
|
||||||
val removed = successfulLines.resetToLine(lineId.no)
|
val removed = successfulLines.resetToLine(lineId)
|
||||||
removed.forEach { submittedLines.remove(it.second.linePsi) }
|
removed.forEach { submittedLines.remove(it.second.linePsi) }
|
||||||
return removed.map { it.first }
|
return removed.map { it.first }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset(): List<ReplCodeLine> {
|
fun reset(): List<SourceCodeByReplLine> {
|
||||||
submittedLines.clear()
|
submittedLines.clear()
|
||||||
return successfulLines.reset().map { it.first }
|
return successfulLines.reset().map { it.first }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) {
|
fun submitLine(ktFile: KtFile) {
|
||||||
val line = LineInfo.SubmittedLine(
|
val line =
|
||||||
ktFile,
|
LineInfo.SubmittedLine(
|
||||||
successfulLines.lastValue()
|
ktFile,
|
||||||
)
|
successfulLines.lastValue()
|
||||||
|
)
|
||||||
submittedLines[ktFile] = line
|
submittedLines[ktFile] = line
|
||||||
ktFile.fileScopesCustomizer = object : FileScopesCustomizer {
|
ktFile.fileScopesCustomizer = object : FileScopesCustomizer {
|
||||||
override fun createFileScopes(fileScopeFactory: FileScopeFactory): FileScopes {
|
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 =
|
val successfulLine =
|
||||||
LineInfo.SuccessfulLine(
|
LineInfo.SuccessfulLine(
|
||||||
ktFile,
|
ktFile,
|
||||||
@@ -213,10 +231,15 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
|
|||||||
scriptDescriptor
|
scriptDescriptor
|
||||||
)
|
)
|
||||||
submittedLines[ktFile] = successfulLine
|
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] =
|
submittedLines[ktFile] =
|
||||||
LineInfo.FailedLine(
|
LineInfo.FailedLine(
|
||||||
ktFile,
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ class ReplInterpreter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add script definition with project-based resolving for IDEA repl
|
// TODO: add script definition with project-based resolving for IDEA repl
|
||||||
private val scriptCompiler: ReplCompiler by lazy {
|
private val scriptCompiler: ReplCompilerWithoutCheck by lazy {
|
||||||
GenericReplCompiler(
|
GenericReplCompiler(
|
||||||
disposable,
|
disposable,
|
||||||
REPL_LINE_AS_SCRIPT_DEFINITION,
|
REPL_LINE_AS_SCRIPT_DEFINITION,
|
||||||
|
|||||||
+9
-53
@@ -6,7 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.scripting.compiler.plugin.repl
|
package org.jetbrains.kotlin.scripting.compiler.plugin.repl
|
||||||
|
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.util.Disposer
|
import com.intellij.openapi.util.Disposer
|
||||||
import org.jetbrains.kotlin.cli.common.repl.*
|
import org.jetbrains.kotlin.cli.common.repl.*
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
@@ -15,54 +14,11 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
|
|||||||
import kotlin.concurrent.write
|
import kotlin.concurrent.write
|
||||||
import kotlin.script.experimental.api.*
|
import kotlin.script.experimental.api.*
|
||||||
|
|
||||||
interface KJvmReplCompilerProxy {
|
class JvmReplCompilerStageHistory<CompilationT : JvmReplCompilerState.Compilation>(private val state: JvmReplCompilerState<CompilationT>) :
|
||||||
fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation
|
BasicReplStageHistory<ScriptDescriptor>(state.lock)
|
||||||
|
|
||||||
fun checkSyntax(
|
class JvmReplCompilerState<CompilationT : JvmReplCompilerState.Compilation>(
|
||||||
script: SourceCode,
|
val createCompilation: (ScriptCompilationConfiguration) -> CompilationT,
|
||||||
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,
|
|
||||||
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
|
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
|
||||||
) : IReplStageState<ScriptDescriptor> {
|
) : 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) {
|
if (_compilation == null) {
|
||||||
initializeCompilation(scriptCompilationConfiguration)
|
initializeCompilation(scriptCompilationConfiguration)
|
||||||
}
|
}
|
||||||
_compilation!!
|
_compilation!!
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val compilation: Compilation
|
internal val compilation: CompilationT
|
||||||
get() = _compilation ?: throw IllegalStateException("Compilation state is either not initializad or already destroyed")
|
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
|
val isCompilationInitialized get() = _compilation != null
|
||||||
|
|
||||||
private fun initializeCompilation(scriptCompilationConfiguration: ScriptCompilationConfiguration) {
|
private fun initializeCompilation(scriptCompilationConfiguration: ScriptCompilationConfiguration) {
|
||||||
if (_compilation != null) throw IllegalStateException("Compilation state is already initialized")
|
if (_compilation != null) throw IllegalStateException("Compilation state is already initialized")
|
||||||
_compilation = replCompilerProxy.createReplCompilationState(scriptCompilationConfiguration)
|
_compilation = createCompilation(scriptCompilationConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Compilation {
|
interface Compilation {
|
||||||
val disposable: Disposable?
|
val disposable: Disposable?
|
||||||
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration
|
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration
|
||||||
val environment: KotlinCoreEnvironment
|
val environment: KotlinCoreEnvironment
|
||||||
val analyzerEngine: ReplCodeAnalyzer
|
val analyzerEngine: ReplCodeAnalyzerBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -25,7 +25,7 @@ import org.jetbrains.kotlin.ir.util.SymbolTable
|
|||||||
import org.jetbrains.kotlin.ir.util.generateTypicalIrProviderList
|
import org.jetbrains.kotlin.ir.util.generateTypicalIrProviderList
|
||||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||||
import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator
|
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 org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||||
import kotlin.script.experimental.api.valueOr
|
import kotlin.script.experimental.api.valueOr
|
||||||
import kotlin.script.experimental.host.StringScriptSource
|
import kotlin.script.experimental.host.StringScriptSource
|
||||||
@@ -35,7 +35,7 @@ class JsCoreScriptingCompiler(
|
|||||||
private val nameTables: NameTables,
|
private val nameTables: NameTables,
|
||||||
private val symbolTable: SymbolTable,
|
private val symbolTable: SymbolTable,
|
||||||
private val dependencyDescriptors: List<ModuleDescriptor>,
|
private val dependencyDescriptors: List<ModuleDescriptor>,
|
||||||
private val replState: ReplCodeAnalyzer.ResettableAnalyzerState = ReplCodeAnalyzer.ResettableAnalyzerState()
|
private val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState = ReplCodeAnalyzerBase.ResettableAnalyzerState()
|
||||||
) {
|
) {
|
||||||
fun compile(codeLine: ReplCodeLine): ReplCompileResult {
|
fun compile(codeLine: ReplCodeLine): ReplCompileResult {
|
||||||
val snippet = codeLine.code
|
val snippet = codeLine.code
|
||||||
@@ -91,6 +91,6 @@ class JsCoreScriptingCompiler(
|
|||||||
|
|
||||||
val code = generateJsCode(context, irModuleFragment, nameTables)
|
val code = generateJsCode(context, irModuleFragment, nameTables)
|
||||||
|
|
||||||
return createCompileResult(LineId(codeLine), code)
|
return createCompileResult(LineId(codeLine.no, 0, codeLine.hashCode()), code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -11,26 +11,27 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
|||||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.AbstractJsScriptlikeCodeAnalyser
|
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
|
import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities
|
||||||
|
|
||||||
class JsReplCodeAnalyzer(
|
class JsReplCodeAnalyzer(
|
||||||
environment: KotlinCoreEnvironment,
|
environment: KotlinCoreEnvironment,
|
||||||
dependencies: List<ModuleDescriptor>,
|
dependencies: List<ModuleDescriptor>,
|
||||||
private val replState: ReplCodeAnalyzer.ResettableAnalyzerState
|
private val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState
|
||||||
) : AbstractJsScriptlikeCodeAnalyser(environment, dependencies) {
|
) : AbstractJsScriptlikeCodeAnalyser(environment, dependencies) {
|
||||||
|
|
||||||
fun analyzeReplLine(linePsi: KtFile, codeLine: ReplCodeLine): AnalysisResult {
|
fun analyzeReplLine(linePsi: KtFile, codeLine: ReplCodeLine): AnalysisResult {
|
||||||
linePsi.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
|
linePsi.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
|
||||||
replState.submitLine(linePsi, codeLine)
|
replState.submitLine(linePsi)
|
||||||
|
|
||||||
val result = analysisImpl(linePsi)
|
val result = analysisImpl(linePsi)
|
||||||
|
|
||||||
return if (result.isSuccess) {
|
return if (result.isSuccess) {
|
||||||
replState.lineSuccess(linePsi, codeLine, result.script)
|
replState.lineSuccess(linePsi, codeLine.toSourceCode(), result.script)
|
||||||
AnalysisResult.success(result.bindingContext, result.moduleDescriptor)
|
AnalysisResult.success(result.bindingContext, result.moduleDescriptor)
|
||||||
} else {
|
} else {
|
||||||
replState.lineFailure(linePsi, codeLine)
|
replState.lineFailure(linePsi)
|
||||||
AnalysisResult.compilationError(result.bindingContext)
|
AnalysisResult.compilationError(result.bindingContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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.lower.serialization.ir.JsManglerDesc
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
|
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
|
||||||
import org.jetbrains.kotlin.ir.util.SymbolTable
|
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
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
|
|
||||||
// Used to compile REPL code lines
|
// Used to compile REPL code lines
|
||||||
@@ -22,7 +22,7 @@ class JsReplCompiler(private val environment: KotlinCoreEnvironment) : ReplCompi
|
|||||||
lock,
|
lock,
|
||||||
NameTables(emptyList()),
|
NameTables(emptyList()),
|
||||||
readLibrariesFromConfiguration(environment.configuration),
|
readLibrariesFromConfiguration(environment.configuration),
|
||||||
ReplCodeAnalyzer.ResettableAnalyzerState(),
|
ReplCodeAnalyzerBase.ResettableAnalyzerState(),
|
||||||
SymbolTable(IdSignatureDescriptor(JsManglerDesc))
|
SymbolTable(IdSignatureDescriptor(JsManglerDesc))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -27,7 +27,7 @@ import org.jetbrains.kotlin.ir.util.SymbolTable
|
|||||||
import org.jetbrains.kotlin.library.resolver.TopologicalLibraryOrder
|
import org.jetbrains.kotlin.library.resolver.TopologicalLibraryOrder
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.KtScript
|
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.configuration.ScriptingConfigurationKeys
|
||||||
import org.jetbrains.kotlin.scripting.resolve.ScriptLightVirtualFile
|
import org.jetbrains.kotlin.scripting.resolve.ScriptLightVirtualFile
|
||||||
import org.jetbrains.kotlin.util.Logger
|
import org.jetbrains.kotlin.util.Logger
|
||||||
@@ -132,7 +132,7 @@ fun readLibrariesFromConfiguration(configuration: CompilerConfiguration): List<M
|
|||||||
.map { descriptorMap.getOrPut(it.libraryName) { getModuleDescriptorByLibrary(it, descriptorMap) } }
|
.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 {
|
fun createCompileResult(lineId: LineId, code: String): ReplCompileResult.CompiledClasses {
|
||||||
return ReplCompileResult.CompiledClasses(
|
return ReplCompileResult.CompiledClasses(
|
||||||
@@ -219,7 +219,7 @@ class JsReplCompilationState(
|
|||||||
lock: ReentrantReadWriteLock,
|
lock: ReentrantReadWriteLock,
|
||||||
nameTables: NameTables,
|
nameTables: NameTables,
|
||||||
dependencies: List<ModuleDescriptor>,
|
dependencies: List<ModuleDescriptor>,
|
||||||
val replState: ReplCodeAnalyzer.ResettableAnalyzerState,
|
val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState,
|
||||||
val symbolTable: SymbolTable
|
val symbolTable: SymbolTable
|
||||||
) : JsCompilationState(lock, nameTables, dependencies)
|
) : 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
|
||||||
|
}
|
||||||
+426
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
+379
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
+76
@@ -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
|
||||||
|
)
|
||||||
+60
@@ -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()
|
||||||
@@ -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()
|
||||||
+119
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+406
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+73
@@ -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-embeddable",
|
||||||
":kotlin-scripting-compiler-impl",
|
":kotlin-scripting-compiler-impl",
|
||||||
":kotlin-scripting-compiler-impl-embeddable",
|
":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",
|
||||||
":kotlin-scripting-dependencies-maven",
|
":kotlin-scripting-dependencies-maven",
|
||||||
":kotlin-scripting-jsr223",
|
":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-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').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-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-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').projectDir = "$rootDir/libraries/tools/kotlin-main-kts" as File
|
||||||
project(':kotlin-main-kts-test').projectDir = "$rootDir/libraries/tools/kotlin-main-kts-test" as File
|
project(':kotlin-main-kts-test').projectDir = "$rootDir/libraries/tools/kotlin-main-kts-test" as File
|
||||||
|
|||||||
Reference in New Issue
Block a user