Add new REPL API JVM implementation

This commit is contained in:
Ilya Muradyan
2020-02-28 19:45:20 +03:00
committed by Ilya Chernikov
parent 4c2c44b106
commit d2fec96f38
51 changed files with 2557 additions and 503 deletions

View File

@@ -601,6 +601,7 @@ tasks {
// dependsOn(":kotlin-scripting-jvm-host-test:embeddableTest")
dependsOn(":kotlin-scripting-jsr223-test:embeddableTest")
dependsOn(":kotlin-main-kts-test:test")
dependsOn(":kotlin-scripting-ide-services-test:test")
}
register("compilerTest") {

View File

@@ -24,16 +24,13 @@ import kotlin.concurrent.write
data class LineId(override val no: Int, override val generation: Int, private val codeHash: Int) : ILineId, Serializable {
constructor(codeLine: ReplCodeLine): this(codeLine.no, codeLine.generation, codeLine.code.hashCode())
override fun compareTo(other: ILineId): Int = (other as? LineId)?.let {
no.compareTo(it.no).takeIf { it != 0 }
?: generation.compareTo(it.generation).takeIf { it != 0 }
?: codeHash.compareTo(it.codeHash)
override fun compareTo(other: ILineId): Int = (other as? LineId)?.let { lineId ->
no.compareTo(lineId.no).takeIf { no -> no != 0 }
?: codeHash.compareTo(lineId.codeHash)
} ?: -1 // TODO: check if it doesn't break something
companion object {
private val serialVersionUID: Long = 8328353000L
private const val serialVersionUID: Long = 8328354000L
}
}

View File

@@ -21,7 +21,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
open class GenericReplCompilingEvaluatorBase(
val compiler: ReplCompiler,
val compiler: ReplCompilerWithoutCheck,
val evaluator: ReplEvaluator,
private val fallbackScriptArgs: ScriptArgsWithTypes? = null
) : ReplFullEvaluator {
@@ -46,7 +46,7 @@ open class GenericReplCompilingEvaluatorBase(
}
ReplEvalResult.Error.CompileTime(compiled.message, compiled.location)
}
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete()
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(compiled.message)
is ReplCompileResult.CompiledClasses -> {
val result = eval(state, compiled, scriptArgs, invokeWrapper)
when (result) {
@@ -75,8 +75,6 @@ open class GenericReplCompilingEvaluatorBase(
override fun eval(state: IReplStageState<*>, compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
evaluator.eval(state, compileResult, scriptArgs, invokeWrapper)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = compiler.check(state, codeLine)
override fun compileToEvaluable(state: IReplStageState<*>, codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes?): Pair<ReplCompileResult, Evaluable?> {
val compiled = compiler.compile(state, codeLine)
return when (compiled) {
@@ -96,7 +94,7 @@ open class GenericReplCompilingEvaluatorBase(
}
class GenericReplCompilingEvaluator(
compiler: ReplCompiler,
compiler: ReplCompilerWithoutCheck,
baseClasspath: Iterable<File>,
baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
fallbackScriptArgs: ScriptArgsWithTypes? = null,

View File

@@ -25,7 +25,7 @@ const val KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY = "kotlin.script.engine"
abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable {
protected abstract val replCompiler: ReplCompiler
protected abstract val replCompiler: ReplCompilerWithoutCheck
protected abstract val replEvaluator: ReplFullEvaluator
override fun eval(script: String, context: ScriptContext): Any? = compileAndEval(script, context)
@@ -72,7 +72,7 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
val result = replCompiler.compile(state, codeLine)
val compiled = when (result) {
is ReplCompileResult.Error -> throw ScriptException("Error${result.locationString()}: ${result.message}")
is ReplCompileResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplCompileResult.Incomplete -> throw ScriptException("Error: incomplete code; ${result.message}")
is ReplCompileResult.CompiledClasses -> result
}
return CompiledKotlinScript(this, codeLine, compiled)
@@ -103,7 +103,7 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
throw ScriptException(result.message, result.location.path, result.location.line, result.location.column)
else -> throw ScriptException(result.message)
}
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplEvalResult.Incomplete -> throw ScriptException("Error: incomplete code. ${result.message}")
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
}
}

View File

@@ -96,7 +96,7 @@ sealed class ReplCompileResult : Serializable {
companion object { private val serialVersionUID: Long = 2L }
}
class Incomplete : ReplCompileResult() {
class Incomplete(val message: String) : ReplCompileResult() {
companion object { private val serialVersionUID: Long = 1L }
}
@@ -110,7 +110,9 @@ sealed class ReplCompileResult : Serializable {
}
}
interface ReplCompiler : ReplCompileAction, ReplCheckAction, CreateReplStageStateAction
interface ReplCompilerWithoutCheck : ReplCompileAction, CreateReplStageStateAction
interface ReplCompiler : ReplCompilerWithoutCheck, ReplCheckAction
// --- eval
@@ -137,7 +139,7 @@ sealed class ReplEvalResult : Serializable {
companion object { private val serialVersionUID: Long = 1L }
}
class Incomplete : ReplEvalResult() {
class Incomplete(val message: String) : ReplEvalResult() {
companion object { private val serialVersionUID: Long = 1L }
}
@@ -175,7 +177,7 @@ interface ReplAtomicEvalAction {
invokeWrapper: InvokeWrapper? = null): ReplEvalResult
}
interface ReplAtomicEvaluator : ReplAtomicEvalAction, ReplCheckAction
interface ReplAtomicEvaluator : ReplAtomicEvalAction
interface ReplDelayedEvalAction {
fun compileToEvaluable(state: IReplStageState<*>,

View File

@@ -8,15 +8,12 @@ package org.jetbrains.kotlin.daemon.client.experimental
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.daemon.client.KotlinRemoteReplCompilerClient
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.CompileServiceClientSide
import org.jetbrains.kotlin.daemon.common.experimental.findCallbackServerSocket
import org.jetbrains.kotlin.daemon.common.ReportCategory
import org.jetbrains.kotlin.daemon.common.ReportSeverity
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import org.jetbrains.kotlin.daemon.client.RemoteReplCompilerState
// TODO: reduce number of ports used then SOCKET_ANY_FREE_PORT is passed (same problem with other calls)

View File

@@ -12,11 +12,11 @@ compiler/testData/multiplatform/regressions/kt28385/jvm.kt:5:6: error: expecting
sdax = {
^
compiler/testData/multiplatform/regressions/kt28385/jvm.kt:5:8: error: expecting a top level declaration
sdax = {
^
compiler/testData/multiplatform/regressions/kt28385/jvm.kt:5:8: error: function declaration must have a name
sdax = {
^
compiler/testData/multiplatform/regressions/kt28385/jvm.kt:3:1: error: property must be initialized
val dasda
^
compiler/testData/multiplatform/regressions/kt28385/jvm.kt:5:8: error: function declaration must have a name
sdax = {
^

View File

@@ -66,7 +66,7 @@ class KotlinJsr223JvmScriptEngine4Idea(
private val messageCollector = MyMessageCollector()
override val replCompiler: ReplCompiler by lazy {
override val replCompiler: ReplCompilerWithoutCheck by lazy {
KotlinRemoteReplCompilerClient(
daemon,
makeAutodeletingFlagFile("idea-jsr223-repl-session"),

View File

@@ -10,7 +10,6 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
import org.jetbrains.kotlin.cli.common.repl.ReplCompiler
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment

View File

@@ -21,8 +21,8 @@ import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator
class LegacyReplTest : TestCase() {
fun testReplBasics() {
LegacyTestRepl().use { repl ->
val res1 = repl.replCompiler.check(repl.state, ReplCodeLine(0, 0, "val x ="))
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete)
val res1 = repl.replCompiler.compile(repl.state, ReplCodeLine(0, 0, "val x ="))
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCompileResult.Incomplete)
assertEvalResult(repl, "val l1 = listOf(1 + 2)\nl1.first()", 3)
@@ -54,8 +54,8 @@ class LegacyReplTest : TestCase() {
fun testReplCodeFormat() {
LegacyTestRepl().use { repl ->
val codeLine0 = ReplCodeLine(0, 0, "val l1 = 1\r\nl1\r\n")
val res0 = repl.replCompiler.check(repl.state, codeLine0)
val res0c = res0 as? ReplCheckResult.Ok
val res0 = repl.replCompiler.compile(repl.state, codeLine0)
val res0c = res0 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res0", res0c)
}
}
@@ -125,7 +125,7 @@ internal class LegacyTestRepl : Closeable {
fun nextCodeLine(code: String): ReplCodeLine = ReplCodeLine(currentLineCounter.getAndIncrement(), 0, code)
val replCompiler: JvmReplCompiler by lazy {
JvmReplCompiler(simpleScriptCompilationConfiguration)
JvmReplCompiler(simpleScriptCompilationConfiguration, false)
}
val compiledEvaluator: ReplEvaluator by lazy {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -7,24 +7,16 @@ package kotlin.script.experimental.jvmhost.test
import junit.framework.TestCase
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.BasicReplStageHistory
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerImpl
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase
import org.junit.Assert
import org.junit.Test
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.BasicJvmReplEvaluator
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.jvm
class ReplTest : TestCase() {
companion object {
const val TEST_DATA_DIR = "libraries/scripting/jvm-host-test/testData"
}
@Test
fun testCompileAndEval() {
val out = captureOut {
@@ -156,103 +148,98 @@ class ReplTest : TestCase() {
limit = 100
)
}
}
fun evaluateInRepl(
snippets: Sequence<String>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
): Sequence<ResultWithDiagnostics<EvaluationResult>> {
val replCompilerProxy =
KJvmReplCompilerImpl(defaultJvmScriptingHostConfiguration)
val compilationState = replCompilerProxy.createReplCompilationState(compilationConfiguration)
val compilationHistory = BasicReplStageHistory<ScriptDescriptor>()
val replEvaluator = BasicJvmScriptEvaluator()
var currentEvalConfig = evaluationConfiguration ?: ScriptEvaluationConfiguration()
val snipetsLimited = if (limit == 0) snippets else snippets.take(limit)
return snipetsLimited.mapIndexed { snippetNo, snippetText ->
val snippetSource =
snippetText.toScriptSource("Line_$snippetNo.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}")
val snippetId = ReplSnippetIdImpl(snippetNo, 0, snippetSource)
replCompilerProxy.compileReplSnippet(compilationState, snippetSource, snippetId, compilationHistory)
.onSuccess {
runBlocking {
replEvaluator(it, currentEvalConfig)
}
}
.onSuccess {
val snippetClass = it.returnValue.scriptClass
currentEvalConfig = ScriptEvaluationConfiguration(currentEvalConfig) {
previousSnippets.append(it.returnValue.scriptInstance)
if (snippetClass != null) {
jvm {
baseClassLoader(snippetClass.java.classLoader)
}
companion object {
private fun evaluateInRepl(
snippets: Sequence<String>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
): Sequence<ResultWithDiagnostics<EvaluatedSnippet>> {
val replCompiler = KJvmReplCompilerBase.create(defaultJvmScriptingHostConfiguration)
val replEvaluator = BasicJvmReplEvaluator()
val currentEvalConfig = evaluationConfiguration ?: ScriptEvaluationConfiguration()
val snipetsLimited = if (limit == 0) snippets else snippets.take(limit)
return snipetsLimited.mapIndexed { snippetNo, snippetText ->
val snippetSource =
snippetText.toScriptSource("Line_$snippetNo.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}")
runBlocking { replCompiler.compile(snippetSource, compilationConfiguration) }
.onSuccess {
runBlocking { replEvaluator.eval(it, currentEvalConfig) }
}
}
it.asSuccess()
}
}
}
fun checkEvaluateInReplDiags(
snippets: Sequence<String>,
expected: Sequence<ResultWithDiagnostics<Any?>>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
) {
val expectedIter = (if (limit == 0) expected else expected.take(limit)).iterator()
evaluateInRepl(snippets, compilationConfiguration, evaluationConfiguration, limit).forEachIndexed { index, res ->
val expectedRes = expectedIter.next()
when {
res is ResultWithDiagnostics.Failure && expectedRes is ResultWithDiagnostics.Failure -> {
Assert.assertTrue(
"#$index: Expected $expectedRes, got $res. Messages are different",
res.reports.map { it.message } == expectedRes.reports.map { it.message }
)
Assert.assertTrue(
"#$index: Expected $expectedRes, got $res. Locations are different",
res.reports.map { it.location }.zip(expectedRes.reports.map { it.location }).all {
it.second == null || it.second == it.first
.onSuccess {
it.get().asSuccess()
}
)
}
res is ResultWithDiagnostics.Success && expectedRes is ResultWithDiagnostics.Success -> {
val expectedVal = expectedRes.value
when (val resVal = res.value.returnValue) {
is ResultValue.Value -> Assert.assertEquals(
"#$index: Expected $expectedVal, got $resVal",
expectedVal,
resVal.value
)
is ResultValue.Unit -> Assert.assertTrue("#$index: Expected $expectedVal, got Unit", expectedVal == null)
is ResultValue.Error -> Assert.assertTrue(
"#$index: Expected $expectedVal, got Error: ${resVal.error}",
expectedVal is Throwable && expectedVal.message == resVal.error.message
)
else -> Assert.assertTrue("#$index: Expected $expectedVal, got unknown result $resVal", expectedVal == null)
}
}
else -> {
Assert.fail("#$index: Expected $expectedRes, got $res")
}
}
}
if (expectedIter.hasNext()) {
Assert.fail("Expected ${expectedIter.next()} got end of results stream")
fun checkEvaluateInReplDiags(
snippets: Sequence<String>,
expected: Sequence<ResultWithDiagnostics<Any?>>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
) {
val expectedIter = (if (limit == 0) expected else expected.take(limit)).iterator()
evaluateInRepl(snippets, compilationConfiguration, evaluationConfiguration, limit).forEachIndexed { index, res ->
val expectedRes = expectedIter.next()
when {
res is ResultWithDiagnostics.Failure && expectedRes is ResultWithDiagnostics.Failure -> {
val resReports = res.reports.filter {
it.code != ScriptDiagnostic.incompleteCode
}
Assert.assertTrue(
"#$index: Expected $expectedRes, got $res. Messages are different",
resReports.map { it.message } == expectedRes.reports.map { it.message }
)
Assert.assertTrue(
"#$index: Expected $expectedRes, got $res. Locations are different",
resReports.map { it.location }.zip(expectedRes.reports.map { it.location }).all {
it.second == null || it.second == it.first
}
)
}
res is ResultWithDiagnostics.Success && expectedRes is ResultWithDiagnostics.Success -> {
val expectedVal = expectedRes.value
val resVal = res.value.result
when (resVal) {
is ResultValue.Value -> Assert.assertEquals(
"#$index: Expected $expectedVal, got $resVal",
expectedVal,
resVal.value
)
is ResultValue.Unit -> Assert.assertNull("#$index: Expected $expectedVal, got Unit", expectedVal)
is ResultValue.Error -> Assert.assertTrue(
"#$index: Expected $expectedVal, got Error: ${resVal.error}",
expectedVal is Throwable && expectedVal.message == resVal.error?.message
)
else -> Assert.assertTrue("#$index: Expected $expectedVal, got unknown result $resVal", expectedVal == null)
}
}
else -> {
Assert.fail("#$index: Expected $expectedRes, got $res")
}
}
}
if (expectedIter.hasNext()) {
Assert.fail("Expected ${expectedIter.next()} got end of results stream")
}
}
fun checkEvaluateInRepl(
snippets: Sequence<String>,
expected: Sequence<Any?>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
) = checkEvaluateInReplDiags(
snippets, expected.map { ResultWithDiagnostics.Success(it) }, compilationConfiguration, evaluationConfiguration, limit
)
class TestReceiver(
@Suppress("unused")
val prop1: Int = 3
)
}
}
fun checkEvaluateInRepl(
snippets: Sequence<String>,
expected: Sequence<Any?>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
) = checkEvaluateInReplDiags(
snippets, expected.map { ResultWithDiagnostics.Success(it) }, compilationConfiguration, evaluationConfiguration, limit
)
class TestReceiver(val prop1: Int = 3)

View File

@@ -13,6 +13,7 @@ import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.*
import kotlin.script.experimental.jvm.util.classpathFromClass
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
import kotlin.script.experimental.jvmhost.test.ReplTest.Companion.checkEvaluateInRepl
class ResolveDependenciesTest : TestCase() {

View File

@@ -21,7 +21,6 @@ import java.net.URLClassLoader
import java.nio.file.Files
import java.util.concurrent.TimeUnit
import java.util.jar.JarFile
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.BasicScriptingHost
import kotlin.script.experimental.host.FileBasedScriptSource
@@ -348,7 +347,7 @@ class ScriptingHostTest : TestCase() {
assertTrue(compiledScript is ResultWithDiagnostics.Success)
val jvmCompiledScript = compiledScript.valueOrNull()!! as KJvmCompiledScript
val jvmCompiledModule = jvmCompiledScript.compiledModule as KJvmCompiledModuleInMemoryImpl
val jvmCompiledModule = jvmCompiledScript.getCompiledModule() as KJvmCompiledModuleInMemoryImpl
val bytes = jvmCompiledModule.compilerOutputFiles["SavedScript.class"]!!
var classFileVersion: Int? = null

View File

@@ -6,6 +6,7 @@
package kotlin.script.experimental.jvmhost.jsr223
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.common.repl.ReplCompilerWithoutCheck
import java.util.concurrent.locks.ReentrantReadWriteLock
import javax.script.ScriptContext
import javax.script.ScriptEngineFactory
@@ -61,8 +62,8 @@ class KotlinJsr223ScriptEngineImpl(
}
}
override val replCompiler: ReplCompiler by lazy {
JvmReplCompiler(compilationConfiguration)
override val replCompiler: ReplCompilerWithoutCheck by lazy {
JvmReplCompiler(compilationConfiguration, true)
}
private val localEvaluator by lazy {

View File

@@ -27,8 +27,8 @@ open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvalua
try {
if (compiledScript !is KJvmCompiledScript)
return failure("Cannot generate classes: unsupported compiled script type $compiledScript")
val module = (compiledScript.compiledModule as? KJvmCompiledModuleInMemory)
?: return failure("Cannot generate classes: unsupported module type ${compiledScript.compiledModule}")
val module = (compiledScript.getCompiledModule() as? KJvmCompiledModuleInMemory)
?: return failure("Cannot generate classes: unsupported module type ${compiledScript.getCompiledModule()}")
for ((path, bytes) in module.compilerOutputFiles) {
File(outputDir, path).apply {
if (!parentFile.isDirectory) {
@@ -47,8 +47,8 @@ open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvalua
}
fun KJvmCompiledScript.saveToJar(outputJar: File) {
val module = (compiledModule as? KJvmCompiledModuleInMemory)
?: throw IllegalArgumentException("Unsupported module type $compiledModule")
val module = (getCompiledModule() as? KJvmCompiledModuleInMemory)
?: throw IllegalArgumentException("Unsupported module type ${getCompiledModule()}")
val dependenciesFromScript = compilationConfiguration[ScriptCompilationConfiguration.dependencies]
?.filterIsInstance<JvmDependency>()
?.flatMap { it.classpath }

View File

@@ -5,66 +5,96 @@
package kotlin.script.experimental.jvmhost.repl
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerImpl
import org.jetbrains.kotlin.cli.common.repl.ReplCompilerWithoutCheck
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerStageHistory
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerState
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.KJvmReplCompilerProxy
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.withDefaultsFrom
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.util.isIncomplete
/**
* REPL Compilation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package
*/
class JvmReplCompiler(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val allowReInit: Boolean = true,
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
val replCompilerProxy: KJvmReplCompilerProxy = KJvmReplCompilerImpl(
var replCompiler: KJvmReplCompilerBase<ReplCodeAnalyzerBase> = KJvmReplCompilerBase.create(
hostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
)
) : ReplCompiler {
) : ReplCompilerWithoutCheck {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = JvmReplCompilerState(replCompilerProxy, lock)
private val compilers = mutableListOf(replCompiler)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = state.lock.write {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> =
if (allowReInit) {
JvmReplCompilerState({ replCompiler.createReplCompilationState(it, replCompiler.initAnalyzer) }, lock)
} else {
replCompiler.state
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = replCompiler.state.lock.write {
val replCompilerState = state.asState(JvmReplCompilerState::class.java)
val compilation = replCompilerState.getCompilationState(scriptCompilationConfiguration)
val res =
replCompilerProxy.checkSyntax(
codeLine.toSourceCode(scriptCompilationConfiguration),
compilation.baseScriptCompilationConfiguration,
compilation.environment.project
)
when {
// TODO: implement diagnostics rendering
res is ResultWithDiagnostics.Success && res.value -> ReplCheckResult.Ok()
res is ResultWithDiagnostics.Success && !res.value -> ReplCheckResult.Incomplete()
else -> ReplCheckResult.Error(res.reports.joinToString("\n") { it.message })
val snippet = codeLine.toSourceCode(scriptCompilationConfiguration)
if (allowReInit) {
replCompiler = compilers.find { historiesEq(it.history, replCompilerState.history) } ?: {
compilers.push(
KJvmReplCompilerBase.create(
hostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
)
)
compilers.last()
}()
}
when (val res = runBlocking { replCompiler.compile(listOf(snippet), scriptCompilationConfiguration) }) {
is ResultWithDiagnostics.Success -> {
val lineId = LineId(codeLine.no, 0, snippet.hashCode())
replCompilerState.apply {
lock.write {
val compilerHistory = history as JvmReplCompilerStageHistory<*>
compilerHistory.push(lineId, replCompiler.history.last().item)
}
}
ReplCompileResult.CompiledClasses(
lineId,
replCompiler.history.map { it.id },
snippet.name!!,
emptyList(),
res.value.get().resultField != null,
emptyList(),
res.value.get().resultField?.second?.typeName,
res.value
)
}
else -> {
val message = res.reports.joinToString("\n") { it.message }
if (res.isIncomplete()) {
ReplCompileResult.Incomplete(message)
} else {
ReplCompileResult.Error(message)
}
}
}
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = state.lock.write {
val replCompilerState = state.asState(JvmReplCompilerState::class.java)
val compilation = replCompilerState.getCompilationState(scriptCompilationConfiguration)
val snippet = codeLine.toSourceCode(scriptCompilationConfiguration)
val snippetId = ReplSnippetIdImpl(codeLine.no, codeLine.generation, snippet)
when (val res = replCompilerProxy.compileReplSnippet(compilation, snippet, snippetId, replCompilerState.history)) {
is ResultWithDiagnostics.Success ->
ReplCompileResult.CompiledClasses(
LineId(codeLine),
replCompilerState.history.map { it.id },
snippet.name!!,
emptyList(),
res.value.resultField != null,
emptyList(),
res.value.resultField?.second?.typeName,
res.value
)
else -> ReplCompileResult.Error(res.reports.joinToString("\n") { it.message })
}
companion object {
fun historiesEq(history1: IReplStageHistory<*>, history2: IReplStageHistory<*>) =
history1.count() == history2.count() &&
history1.zip(history2).all {
val (it1, it2) = it
it1.item === it2.item
}
}
}

View File

@@ -7,6 +7,7 @@ package kotlin.script.experimental.jvmhost.repl
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.common.repl.ReplEvaluator
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.reflect.KClass
@@ -15,6 +16,7 @@ import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.util.LinkedSnippetImpl
/**
* REPL Evaluation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package
@@ -35,8 +37,12 @@ class JvmReplEvaluator(
): ReplEvalResult = state.lock.write {
val evalState = state.asState(JvmReplEvaluatorState::class.java)
val history = evalState.history as ReplStageHistoryWithReplace
val compiledScript = (compileResult.data as? KJvmCompiledScript)
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compileResult.data}")
val compiledScriptList = (compileResult.data as? LinkedSnippetImpl<*>)
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled list script: ${compileResult.data}")
val compiledScript = (compiledScriptList.get() as? KJvmCompiledScript)
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compiledScriptList.get()}")
val lastSnippetClass = history.peek()?.item?.first
val historyBeforeSnippet = history.previousItems(compileResult.lineId).map { it.second }.toList()

View File

@@ -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

View File

@@ -5,6 +5,7 @@
package kotlin.script.experimental.jvm.impl
import org.jetbrains.annotations.TestOnly
import java.io.*
import java.net.URL
import java.net.URLClassLoader
@@ -39,13 +40,13 @@ internal class KJvmCompiledScriptData(
companion object {
@JvmStatic
private val serialVersionUID = 4L
private val serialVersionUID = 5L
}
}
class KJvmCompiledScript internal constructor(
open class KJvmCompiledScript internal constructor(
internal var data: KJvmCompiledScriptData,
var compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept
internal var compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept
) : CompiledScript, Serializable {
constructor(
@@ -93,6 +94,9 @@ class KJvmCompiledScript internal constructor(
)
}
@TestOnly
fun getCompiledModule() = compiledModule
private fun writeObject(outputStream: ObjectOutputStream) {
outputStream.writeObject(data)
outputStream.writeObject(compiledModule)
@@ -177,7 +181,7 @@ fun KJvmCompiledScript.toBytes(): ByteArray {
oos = ObjectOutputStream(bos)
oos.writeObject(this)
oos.flush()
return bos.toByteArray()!!
return bos.toByteArray()
} finally {
try {
oos?.close()

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -159,6 +159,11 @@
<artifactId>kotlin-scripting-jvm-host</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>kotlin-scripting-ide-services</artifactId>
<version>${kotlin.version}</version>
</dependency>
<!-- Compiler dependency required by scripting -->
<dependency>
<groupId>${project.groupId}</groupId>

View File

@@ -1,6 +1,6 @@
package org.jetbrains.kotlin.mainKts.test
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.mainKts.test.TEST_DATA_ROOT
import org.jetbrains.kotlin.mainKts.test.captureOut
import org.junit.Assert
import org.junit.Test
import javax.script.ScriptEngineManager

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -52,14 +52,14 @@ import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvm.util.KotlinJars
import kotlin.script.experimental.jvm.withUpdatedClasspath
internal class SharedScriptCompilationContext(
class SharedScriptCompilationContext(
val disposable: Disposable?,
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration,
val environment: KotlinCoreEnvironment,
val ignoredOptionsReportingState: IgnoredOptionsReportingState
)
internal fun createIsolatedCompilationContext(
fun createIsolatedCompilationContext(
baseScriptCompilationConfiguration: ScriptCompilationConfiguration,
hostConfiguration: ScriptingHostConfiguration,
messageCollector: ScriptDiagnosticsMessageCollector,

View File

@@ -18,7 +18,7 @@ import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.asErrorDiagnostics
internal class ScriptDiagnosticsMessageCollector(private val parentMessageCollector: MessageCollector?) : MessageCollector {
class ScriptDiagnosticsMessageCollector(private val parentMessageCollector: MessageCollector?) : MessageCollector {
private val _diagnostics = arrayListOf<ScriptDiagnostic>()
@@ -78,17 +78,17 @@ private fun ScriptDiagnostic.Severity.toCompilerMessageSeverity(): CompilerMessa
ScriptDiagnostic.Severity.FATAL -> CompilerMessageSeverity.EXCEPTION
}
internal fun failure(
fun failure(
messageCollector: ScriptDiagnosticsMessageCollector, vararg diagnostics: ScriptDiagnostic
): ResultWithDiagnostics.Failure =
ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics)
internal fun failure(
fun failure(
script: SourceCode, messageCollector: ScriptDiagnosticsMessageCollector, message: String
): ResultWithDiagnostics.Failure =
failure(messageCollector, message.asErrorDiagnostics(path = script.locationId))
internal class IgnoredOptionsReportingState {
class IgnoredOptionsReportingState {
var currentArguments = K2JVMCompilerArguments()
}

View File

@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.scripting.compiler.plugin.dependencies.ScriptsCompil
import org.jetbrains.kotlin.scripting.resolve.ScriptLightVirtualFile
import org.jetbrains.kotlin.scripting.scriptFileName
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.Serializable
import java.util.*
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.FileBasedScriptSource
@@ -33,7 +34,7 @@ internal fun makeCompiledModule(generationState: GenerationState) =
.associateTo(sortedMapOf<String, ByteArray>()) { it.relativePath to it.asByteArray() }
)
internal inline fun <T> withMessageCollectorAndDisposable(
inline fun <T> withMessageCollectorAndDisposable(
script: SourceCode? = null,
parentMessageCollector: MessageCollector? = null,
disposable: Disposable = Disposer.newDisposable(),
@@ -57,7 +58,7 @@ internal inline fun <T> withMessageCollectorAndDisposable(
}
}
internal inline fun <T> withMessageCollector(
inline fun <T> withMessageCollector(
script: SourceCode? = null,
parentMessageCollector: MessageCollector? = null,
body: (ScriptDiagnosticsMessageCollector) -> ResultWithDiagnostics<T>
@@ -99,6 +100,16 @@ internal fun getScriptKtFile(
}
}
class SourceCodeImpl(file: KtFile) : SourceCode, Serializable {
override val text: String = file.text
override val name: String? = file.name
override val locationId: String? = file.virtualFilePath
companion object {
private const val serialVersionUID = 1L
}
}
internal fun makeCompiledScript(
generationState: GenerationState,
script: SourceCode,
@@ -122,7 +133,7 @@ internal fun makeCompiledScript(
sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.valueOrThrow()?.mapNotNull { sourceFile ->
sourceFile.declarations.firstIsInstanceOrNull<KtScript>()?.let {
KJvmCompiledScript(
containingKtFile.virtualFile?.path,
containingKtFile.virtualFilePath,
getScriptConfiguration(sourceFile),
it.fqName.asString(),
null,

View File

@@ -11,29 +11,35 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.messages.DiagnosticMessageHolder
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.dependencies.ScriptDependencies
class ReplCompilerStageHistory(private val state: GenericReplCompilerState) : BasicReplStageHistory<ScriptDescriptor>(state.lock) {
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.analyzerEngine.reset()
lock.write {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.analyzerEngine.resetToLine(id)
lock.write {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.analyzerEngine.resetToLine(id)
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
}
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<ReplCodeLine>) {
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<SourceCodeByReplLine>) {
removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) ->
if (removedCompiledLine != LineId(removedAnalyzedLine)) {
if (removedCompiledLine.no != removedAnalyzedLine.no) {
throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine")
}
}
@@ -59,7 +65,7 @@ class GenericReplCompilerState(environment: KotlinCoreEnvironment, override val
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
val analyzerEngine = ReplCodeAnalyzer(environment)
val analyzerEngine = ReplCodeAnalyzerBase(environment)
var lastDependencies: ScriptDependencies? = null
}

View File

@@ -59,7 +59,7 @@ open class GenericReplCompiler(
if (compilerState.lastLineState == null || compilerState.lastLineState!!.codeLine != codeLine) {
val res = checker.check(state, codeLine)
when (res) {
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete()
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete("Code is incomplete")
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location)
is ReplCheckResult.Ok -> {
} // continue
@@ -81,10 +81,10 @@ open class GenericReplCompiler(
val analysisResult = compilerState.analyzerEngine.analyzeReplLine(psiFile, codeLine)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> {
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.WithErrors -> {
return ReplCompileResult.Error(errorHolder.renderMessage())
}
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> {
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.Successful -> {
(analysisResult.scriptDescriptor as? ScriptDescriptor)
?: error("Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}")
}
@@ -108,12 +108,12 @@ open class GenericReplCompiler(
setOf(psiFile.script!!.containingKtFile)
)
compilerState.history.push(LineId(codeLine), scriptDescriptor)
compilerState.history.push(LineId(codeLine.no, 0, codeLine.hashCode()), scriptDescriptor)
val classes = generationState.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }
return ReplCompileResult.CompiledClasses(
LineId(codeLine),
LineId(codeLine.no, 0, codeLine.hashCode()),
compilerState.history.map { it.id },
scriptDescriptor.name.identifier,
classes,

View File

@@ -7,13 +7,12 @@
package org.jetbrains.kotlin.scripting.compiler.plugin.repl
import org.jetbrains.kotlin.cli.common.repl.CompiledReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ILineId
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplHistory
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
@@ -33,23 +32,30 @@ import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.jvm.util.CompiledHistoryItem
import kotlin.script.experimental.jvm.util.CompiledHistoryList
import kotlin.script.experimental.jvm.util.SnippetsHistory
class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
private val topDownAnalysisContext: TopDownAnalysisContext
private val topDownAnalyzer: LazyTopDownAnalyzer
private val resolveSession: ResolveSession
private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
private val replState = ResettableAnalyzerState()
open class ReplCodeAnalyzerBase(
environment: KotlinCoreEnvironment,
val trace: BindingTraceContext = NoScopeRecordCliBindingTrace()
) {
protected val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
protected val container: ComponentProvider
protected val topDownAnalysisContext: TopDownAnalysisContext
protected val topDownAnalyzer: LazyTopDownAnalyzer
protected val resolveSession: ResolveSession
protected val replState = ResettableAnalyzerState()
val module: ModuleDescriptorImpl
val trace: BindingTraceContext = NoScopeRecordCliBindingTrace()
init {
// Module source scope is empty because all binary classes are in the dependency module, and all source classes are guaranteed
// to be found via ResolveSession. The latter is true as long as light classes are not needed in REPL (which is currently true
// because no symbol declared in the REPL session can be used from Java)
val container = TopDownAnalyzerFacadeForJVM.createContainer(
container = TopDownAnalyzerFacadeForJVM.createContainer(
environment.project,
emptyList(),
trace,
@@ -71,7 +77,10 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
val scriptDescriptor: ClassDescriptorWithResolutionScopes?
val diagnostics: Diagnostics
data class Successful(override val scriptDescriptor: ClassDescriptorWithResolutionScopes, override val diagnostics: Diagnostics) :
data class Successful(
override val scriptDescriptor: ClassDescriptorWithResolutionScopes,
override val diagnostics: Diagnostics
) :
ReplLineAnalysisResult
data class WithErrors(override val diagnostics: Diagnostics) :
@@ -80,9 +89,9 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
}
fun resetToLine(lineId: ILineId): List<ReplCodeLine> = replState.resetToLine(lineId)
fun resetToLine(lineId: ILineId): List<SourceCodeByReplLine> = replState.resetToLine(lineId)
fun reset(): List<ReplCodeLine> = replState.reset()
fun reset(): List<SourceCodeByReplLine> = replState.reset()
fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
topDownAnalysisContext.scripts.clear()
@@ -90,31 +99,38 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
return doAnalyze(psiFile, emptyList(), codeLine)
return doAnalyze(psiFile, emptyList(), codeLine.toSourceCode())
}
fun analyzeReplLineWithImportedScripts(psiFile: KtFile, importedScripts: List<KtFile>, codeLine: ReplCodeLine): ReplLineAnalysisResult {
fun analyzeReplLineWithImportedScripts(
psiFile: KtFile,
importedScripts: List<KtFile>,
codeLine: SourceCode,
priority: Int
): ReplLineAnalysisResult {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, priority)
return doAnalyze(psiFile, importedScripts, codeLine)
return doAnalyze(psiFile, importedScripts, codeLine.addNo(priority))
}
private fun doAnalyze(linePsi: KtFile, importedScripts: List<KtFile>, codeLine: ReplCodeLine): ReplLineAnalysisResult {
private fun doAnalyze(linePsi: KtFile, importedScripts: List<KtFile>, codeLine: SourceCodeByReplLine): ReplLineAnalysisResult {
scriptDeclarationFactory.setDelegateFactory(
FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi) + importedScripts)
)
replState.submitLine(linePsi, codeLine)
replState.submitLine(linePsi)
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts)
val diagnostics = trace.bindingContext.diagnostics
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }
return if (hasErrors) {
replState.lineFailure(linePsi, codeLine)
ReplLineAnalysisResult.WithErrors(diagnostics)
replState.lineFailure(linePsi)
ReplLineAnalysisResult.WithErrors(
diagnostics
)
} else {
val scriptDescriptor = context.scripts[linePsi.script]!!
replState.lineSuccess(linePsi, codeLine, scriptDescriptor)
@@ -123,10 +139,9 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
diagnostics
)
}
}
private class ScriptMutableDeclarationProviderFactory : DeclarationProviderFactory {
protected class ScriptMutableDeclarationProviderFactory : DeclarationProviderFactory {
private lateinit var delegateFactory: DeclarationProviderFactory
private lateinit var rootPackageProvider: AdaptablePackageMemberDeclarationProvider
@@ -175,28 +190,31 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
}
data class CompiledCode(val className: String, val source: SourceCodeByReplLine)
// TODO: merge with org.jetbrains.kotlin.resolve.repl.ReplState when switching to new REPL infrastructure everywhere
// TODO: review its place in the extracted state infrastructure (now the analyzer itself is a part of the state)
class ResettableAnalyzerState {
private val successfulLines = ReplHistory<LineInfo.SuccessfulLine>()
private val successfulLines = ResettableSnippetsHistory<LineInfo.SuccessfulLine>()
private val submittedLines = hashMapOf<KtFile, LineInfo>()
fun resetToLine(lineId: ILineId): List<ReplCodeLine> {
val removed = successfulLines.resetToLine(lineId.no)
fun resetToLine(lineId: ILineId): List<SourceCodeByReplLine> {
val removed = successfulLines.resetToLine(lineId)
removed.forEach { submittedLines.remove(it.second.linePsi) }
return removed.map { it.first }
}
fun reset(): List<ReplCodeLine> {
fun reset(): List<SourceCodeByReplLine> {
submittedLines.clear()
return successfulLines.reset().map { it.first }
}
fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) {
val line = LineInfo.SubmittedLine(
ktFile,
successfulLines.lastValue()
)
fun submitLine(ktFile: KtFile) {
val line =
LineInfo.SubmittedLine(
ktFile,
successfulLines.lastValue()
)
submittedLines[ktFile] = line
ktFile.fileScopesCustomizer = object : FileScopesCustomizer {
override fun createFileScopes(fileScopeFactory: FileScopeFactory): FileScopes {
@@ -205,7 +223,7 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
}
fun lineSuccess(ktFile: KtFile, codeLine: ReplCodeLine, scriptDescriptor: ClassDescriptorWithResolutionScopes) {
fun lineSuccess(ktFile: KtFile, codeLine: SourceCodeByReplLine, scriptDescriptor: ClassDescriptorWithResolutionScopes) {
val successfulLine =
LineInfo.SuccessfulLine(
ktFile,
@@ -213,10 +231,15 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
scriptDescriptor
)
submittedLines[ktFile] = successfulLine
successfulLines.add(CompiledReplCodeLine(ktFile.name, codeLine), successfulLine)
successfulLines.add(
CompiledCode(
ktFile.name,
codeLine
), successfulLine
)
}
fun lineFailure(ktFile: KtFile, codeLine: ReplCodeLine) {
fun lineFailure(ktFile: KtFile) {
submittedLines[ktFile] =
LineInfo.FailedLine(
ktFile,
@@ -251,3 +274,34 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
}
}
fun ReplCodeLine.toSourceCode() = SourceCodeByReplLine(code, no)
internal fun SourceCode.addNo(no: Int) = SourceCodeByReplLine(text, no, name, locationId)
data class SourceCodeByReplLine(
override val text: String,
val no: Int,
override val name: String? = null,
override val locationId: String? = null
) : SourceCode
private typealias ReplSourceHistoryList<ResultT> = List<CompiledHistoryItem<SourceCodeByReplLine, ResultT>>
@Deprecated("This functionality is left for backwards compatibility only", ReplaceWith("SnippetsHistory"))
private class ResettableSnippetsHistory<ResultT>(startingHistory: CompiledHistoryList<ReplCodeAnalyzerBase.CompiledCode, ResultT> = emptyList()) :
SnippetsHistory<ReplCodeAnalyzerBase.CompiledCode, ResultT>(startingHistory) {
fun resetToLine(line: ILineId): ReplSourceHistoryList<ResultT> {
val removed = arrayListOf<Pair<SourceCodeByReplLine, ResultT>>()
while ((history.lastOrNull()?.first?.source?.no ?: -1) > line.no) {
removed.add(history.removeAt(history.size - 1).let { Pair(it.first.source, it.second) })
}
return removed.reversed()
}
fun reset(): ReplSourceHistoryList<ResultT> {
val removed = history.map { Pair(it.first.source, it.second) }
history.clear()
return removed
}
}

View File

@@ -76,7 +76,7 @@ class ReplInterpreter(
}
// TODO: add script definition with project-based resolving for IDEA repl
private val scriptCompiler: ReplCompiler by lazy {
private val scriptCompiler: ReplCompilerWithoutCheck by lazy {
GenericReplCompiler(
disposable,
REPL_LINE_AS_SCRIPT_DEFINITION,

View File

@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.scripting.compiler.plugin.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
@@ -15,54 +14,11 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.*
interface KJvmReplCompilerProxy {
fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation
class JvmReplCompilerStageHistory<CompilationT : JvmReplCompilerState.Compilation>(private val state: JvmReplCompilerState<CompilationT>) :
BasicReplStageHistory<ScriptDescriptor>(state.lock)
fun checkSyntax(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
project: Project
): ResultWithDiagnostics<Boolean>
fun compileReplSnippet(
compilationState: JvmReplCompilerState.Compilation,
snippet: SourceCode,
snippetId: ReplSnippetId,
history: IReplStageHistory<ScriptDescriptor>
): ResultWithDiagnostics<CompiledScript>
}
class JvmReplCompilerStageHistory(private val state: JvmReplCompilerState) :
BasicReplStageHistory<ScriptDescriptor>(state.lock) {
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.compilation.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.compilation.analyzerEngine.resetToLine(id)
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<ReplCodeLine>) {
removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) ->
if (removedCompiledLine != LineId(removedAnalyzedLine)) {
throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine")
}
}
}
}
class JvmReplCompilerState(
val replCompilerProxy: KJvmReplCompilerProxy,
class JvmReplCompilerState<CompilationT : JvmReplCompilerState.Compilation>(
val createCompilation: (ScriptCompilationConfiguration) -> CompilationT,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<ScriptDescriptor> {
@@ -80,29 +36,29 @@ class JvmReplCompilerState(
}
}
fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): Compilation = lock.write {
fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): CompilationT = lock.write {
if (_compilation == null) {
initializeCompilation(scriptCompilationConfiguration)
}
_compilation!!
}
internal val compilation: Compilation
internal val compilation: CompilationT
get() = _compilation ?: throw IllegalStateException("Compilation state is either not initializad or already destroyed")
private var _compilation: Compilation? = null
private var _compilation: CompilationT? = null
val isCompilationInitialized get() = _compilation != null
private fun initializeCompilation(scriptCompilationConfiguration: ScriptCompilationConfiguration) {
if (_compilation != null) throw IllegalStateException("Compilation state is already initialized")
_compilation = replCompilerProxy.createReplCompilationState(scriptCompilationConfiguration)
_compilation = createCompilation(scriptCompilationConfiguration)
}
interface Compilation {
val disposable: Disposable?
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration
val environment: KotlinCoreEnvironment
val analyzerEngine: ReplCodeAnalyzer
val analyzerEngine: ReplCodeAnalyzerBase
}
}

View File

@@ -25,7 +25,7 @@ import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.generateTypicalIrProviderList
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import org.jetbrains.kotlin.serialization.js.ModuleKind
import kotlin.script.experimental.api.valueOr
import kotlin.script.experimental.host.StringScriptSource
@@ -35,7 +35,7 @@ class JsCoreScriptingCompiler(
private val nameTables: NameTables,
private val symbolTable: SymbolTable,
private val dependencyDescriptors: List<ModuleDescriptor>,
private val replState: ReplCodeAnalyzer.ResettableAnalyzerState = ReplCodeAnalyzer.ResettableAnalyzerState()
private val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState = ReplCodeAnalyzerBase.ResettableAnalyzerState()
) {
fun compile(codeLine: ReplCodeLine): ReplCompileResult {
val snippet = codeLine.code
@@ -91,6 +91,6 @@ class JsCoreScriptingCompiler(
val code = generateJsCode(context, irModuleFragment, nameTables)
return createCompileResult(LineId(codeLine), code)
return createCompileResult(LineId(codeLine.no, 0, codeLine.hashCode()), code)
}
}

View File

@@ -11,26 +11,27 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.AbstractJsScriptlikeCodeAnalyser
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.toSourceCode
import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities
class JsReplCodeAnalyzer(
environment: KotlinCoreEnvironment,
dependencies: List<ModuleDescriptor>,
private val replState: ReplCodeAnalyzer.ResettableAnalyzerState
private val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState
) : AbstractJsScriptlikeCodeAnalyser(environment, dependencies) {
fun analyzeReplLine(linePsi: KtFile, codeLine: ReplCodeLine): AnalysisResult {
linePsi.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
replState.submitLine(linePsi, codeLine)
replState.submitLine(linePsi)
val result = analysisImpl(linePsi)
return if (result.isSuccess) {
replState.lineSuccess(linePsi, codeLine, result.script)
replState.lineSuccess(linePsi, codeLine.toSourceCode(), result.script)
AnalysisResult.success(result.bindingContext, result.moduleDescriptor)
} else {
replState.lineFailure(linePsi, codeLine)
replState.lineFailure(linePsi)
AnalysisResult.compilationError(result.bindingContext)
}
}

View File

@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import java.util.concurrent.locks.ReentrantReadWriteLock
// Used to compile REPL code lines
@@ -22,7 +22,7 @@ class JsReplCompiler(private val environment: KotlinCoreEnvironment) : ReplCompi
lock,
NameTables(emptyList()),
readLibrariesFromConfiguration(environment.configuration),
ReplCodeAnalyzer.ResettableAnalyzerState(),
ReplCodeAnalyzerBase.ResettableAnalyzerState(),
SymbolTable(IdSignatureDescriptor(JsManglerDesc))
)
}

View File

@@ -27,7 +27,7 @@ import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.library.resolver.TopologicalLibraryOrder
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.resolve.ScriptLightVirtualFile
import org.jetbrains.kotlin.util.Logger
@@ -132,7 +132,7 @@ fun readLibrariesFromConfiguration(configuration: CompilerConfiguration): List<M
.map { descriptorMap.getOrPut(it.libraryName) { getModuleDescriptorByLibrary(it, descriptorMap) } }
}
fun createCompileResult(code: String) = createCompileResult(LineId(ReplCodeLine(0, 0, "")), code)
fun createCompileResult(code: String) = createCompileResult(LineId(0, 0, 0), code)
fun createCompileResult(lineId: LineId, code: String): ReplCompileResult.CompiledClasses {
return ReplCompileResult.CompiledClasses(
@@ -219,7 +219,7 @@ class JsReplCompilationState(
lock: ReentrantReadWriteLock,
nameTables: NameTables,
dependencies: List<ModuleDescriptor>,
val replState: ReplCodeAnalyzer.ResettableAnalyzerState,
val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState,
val symbolTable: SymbolTable
) : JsCompilationState(lock, nameTables, dependencies)

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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
)
}
)

View File

@@ -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)
}
}
}
}
}

View File

@@ -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
)

View File

@@ -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()

View File

@@ -0,0 +1,41 @@
description = "Kotlin Scripting Compiler extension providing code completion and static analysis"
plugins {
kotlin("jvm")
id("jps-compatible")
}
jvmTarget = "1.8"
publish()
dependencies {
compile(project(":kotlin-script-runtime"))
compile(kotlinStdlib())
compileOnly(project(":idea:ide-common"))
compile(project(":kotlin-scripting-common"))
compile(project(":kotlin-scripting-jvm"))
compileOnly(project(":kotlin-scripting-compiler"))
compileOnly(project(":compiler:cli"))
compileOnly(project(":kotlin-reflect-api"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
publishedRuntime(project(":kotlin-compiler"))
publishedRuntime(project(":kotlin-scripting-compiler"))
publishedRuntime(project(":kotlin-reflect"))
publishedRuntime(commonDep("org.jetbrains.intellij.deps", "trove4j"))
}
sourceSets {
"main" { projectDefault() }
"test" { }
}
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
kotlinOptions {
freeCompilerArgs += "-Xskip-metadata-version-check"
freeCompilerArgs += "-Xallow-kotlin-package"
}
}
standardPublicJars()

View File

@@ -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()
}
}
}

View File

@@ -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
)
}
}

View File

@@ -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
)
}
}
}

View File

@@ -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()
}
}

View File

@@ -267,6 +267,9 @@ include ":kotlin-build-common",
":kotlin-scripting-compiler-embeddable",
":kotlin-scripting-compiler-impl",
":kotlin-scripting-compiler-impl-embeddable",
":kotlin-scripting-ide-services",
":kotlin-scripting-ide-services-test",
":kotlin-scripting-ide-services-embeddable",
":kotlin-scripting-dependencies",
":kotlin-scripting-dependencies-maven",
":kotlin-scripting-jsr223",
@@ -478,6 +481,9 @@ project(':kotlin-scripting-compiler').projectDir = "$rootDir/plugins/scripting/s
project(':kotlin-scripting-compiler-embeddable').projectDir = "$rootDir/plugins/scripting/scripting-compiler-embeddable" as File
project(':kotlin-scripting-compiler-impl').projectDir = "$rootDir/plugins/scripting/scripting-compiler-impl" as File
project(':kotlin-scripting-compiler-impl-embeddable').projectDir = "$rootDir/plugins/scripting/scripting-compiler-impl-embeddable" as File
project(':kotlin-scripting-ide-services').projectDir = "$rootDir/plugins/scripting/scripting-ide-services" as File
project(':kotlin-scripting-ide-services-test').projectDir = "$rootDir/plugins/scripting/scripting-ide-services-test" as File
project(':kotlin-scripting-ide-services-embeddable').projectDir = "$rootDir/plugins/scripting/scripting-ide-services-embeddable" as File
project(':kotlin-scripting-idea').projectDir = "$rootDir/plugins/scripting/scripting-idea" as File
project(':kotlin-main-kts').projectDir = "$rootDir/libraries/tools/kotlin-main-kts" as File
project(':kotlin-main-kts-test').projectDir = "$rootDir/libraries/tools/kotlin-main-kts-test" as File