Switch old IDE/CLI repls to the new infrastructure
should also fix #KT-5822
This commit is contained in:
@@ -18,7 +18,7 @@ package org.jetbrains.kotlin.cli.jvm.repl
|
|||||||
|
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import org.jetbrains.kotlin.cli.jvm.repl.LineResult.*
|
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
|
||||||
import org.jetbrains.kotlin.cli.jvm.repl.messages.unescapeLineBreaks
|
import org.jetbrains.kotlin.cli.jvm.repl.messages.unescapeLineBreaks
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
import org.jetbrains.kotlin.config.KotlinCompilerVersion
|
import org.jetbrains.kotlin.config.KotlinCompilerVersion
|
||||||
@@ -86,7 +86,7 @@ class ReplFromTerminal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val lineResult = eval(line)
|
val lineResult = eval(line)
|
||||||
if (lineResult is LineResult.Incomplete) {
|
if (lineResult is ReplEvalResult.Incomplete) {
|
||||||
return WhatNextAfterOneLine.INCOMPLETE
|
return WhatNextAfterOneLine.INCOMPLETE
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -94,20 +94,20 @@ class ReplFromTerminal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun eval(line: String): LineResult {
|
private fun eval(line: String): ReplEvalResult {
|
||||||
val lineResult = replInterpreter.eval(line)
|
val evalResult = replInterpreter.eval(line)
|
||||||
when (lineResult) {
|
when (evalResult) {
|
||||||
is ValueResult, UnitResult -> {
|
is ReplEvalResult.ValueResult, is ReplEvalResult.UnitResult -> {
|
||||||
writer.notifyCommandSuccess()
|
writer.notifyCommandSuccess()
|
||||||
if (lineResult is ValueResult) {
|
if (evalResult is ReplEvalResult.ValueResult) {
|
||||||
writer.outputCommandResult(lineResult.valueAsString)
|
writer.outputCommandResult(evalResult.value.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Incomplete -> writer.notifyIncomplete()
|
is ReplEvalResult.Error.Runtime -> writer.outputRuntimeError(evalResult.message)
|
||||||
is Error.CompileTime -> writer.outputCompileError(lineResult.errorText)
|
is ReplEvalResult.Error.CompileTime -> writer.outputRuntimeError(evalResult.message)
|
||||||
is Error.Runtime -> writer.outputRuntimeError(lineResult.errorText)
|
is ReplEvalResult.Incomplete -> writer.notifyIncomplete()
|
||||||
}
|
}
|
||||||
return lineResult
|
return evalResult
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
|
|||||||
@@ -16,143 +16,80 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.cli.jvm.repl
|
package org.jetbrains.kotlin.cli.jvm.repl
|
||||||
|
|
||||||
import com.google.common.base.Throwables
|
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.openapi.vfs.CharsetToolkit
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||||
import com.intellij.psi.PsiFileFactory
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||||
import com.intellij.psi.impl.PsiFileFactoryImpl
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
import com.intellij.testFramework.LightVirtualFile
|
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
|
||||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
import org.jetbrains.kotlin.cli.common.repl.*
|
||||||
import org.jetbrains.kotlin.cli.common.repl.ReplClassLoader
|
|
||||||
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
||||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
|
|
||||||
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
|
|
||||||
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
|
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
|
||||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
|
||||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import org.jetbrains.kotlin.psi.KtScript
|
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
class ReplInterpreter(
|
class ReplInterpreter(
|
||||||
disposable: Disposable,
|
disposable: Disposable,
|
||||||
private val configuration: CompilerConfiguration,
|
private val configuration: CompilerConfiguration,
|
||||||
private val replConfiguration: ReplConfiguration
|
private val replConfiguration: ReplConfiguration
|
||||||
): ReplConfiguration by replConfiguration {
|
): ReplConfiguration by replConfiguration {
|
||||||
private var lineNumber = 0
|
|
||||||
|
|
||||||
private val earlierLines = arrayListOf<EarlierLine>()
|
private val lineNumber = AtomicInteger()
|
||||||
|
|
||||||
private val previousIncompleteLines = arrayListOf<String>()
|
private val previousIncompleteLines = arrayListOf<String>()
|
||||||
private val classLoader: ReplClassLoader = run {
|
private val classLoader: ReplClassLoader = run {
|
||||||
val classpath = configuration.jvmClasspathRoots.map { it.toURI().toURL() }
|
val classpath = configuration.jvmClasspathRoots.map { it.toURI().toURL() }
|
||||||
ReplClassLoader(URLClassLoader(classpath.toTypedArray(), null))
|
ReplClassLoader(URLClassLoader(classpath.toTypedArray(), null))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val environment = run {
|
private val messageCollector = object : MessageCollector {
|
||||||
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, REPL_LINE_AS_SCRIPT_DEFINITION)
|
private var hasErrors = false
|
||||||
KotlinCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
private val messageRenderer = MessageRenderer.WITHOUT_PATHS
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
hasErrors = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) {
|
||||||
|
val msg = messageRenderer.render(severity, message, location)
|
||||||
|
with (replConfiguration.writer) {
|
||||||
|
when (severity) {
|
||||||
|
CompilerMessageSeverity.EXCEPTION -> sendInternalErrorReport(msg)
|
||||||
|
CompilerMessageSeverity.ERROR -> outputCompileError(msg)
|
||||||
|
CompilerMessageSeverity.STRONG_WARNING -> {} // TODO consider reporting this and two below
|
||||||
|
CompilerMessageSeverity.WARNING -> {}
|
||||||
|
CompilerMessageSeverity.INFO -> {}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasErrors(): Boolean = hasErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
|
// TODO: add script definition with project-based resolving for IDEA repl
|
||||||
private val analyzerEngine = ReplCodeAnalyzer(environment)
|
private val scriptCompiler: ReplCompiler by lazy { GenericReplCompiler(disposable, REPL_LINE_AS_SCRIPT_DEFINITION, configuration, messageCollector) }
|
||||||
|
private val scriptEvaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(scriptCompiler, configuration.jvmClasspathRoots, classLoader, null, ReplRepeatingMode.REPEAT_ANY_PREVIOUS) }
|
||||||
|
|
||||||
fun eval(line: String): LineResult {
|
private val evalState by lazy { scriptEvaluator.createState() }
|
||||||
++lineNumber
|
|
||||||
|
fun eval(line: String): ReplEvalResult {
|
||||||
|
|
||||||
val fullText = (previousIncompleteLines + line).joinToString(separator = "\n")
|
val fullText = (previousIncompleteLines + line).joinToString(separator = "\n")
|
||||||
|
|
||||||
val virtualFile =
|
|
||||||
LightVirtualFile("line$lineNumber${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, fullText).apply {
|
|
||||||
charset = CharsetToolkit.UTF8_CHARSET
|
|
||||||
}
|
|
||||||
val psiFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
|
|
||||||
?: error("Script file not analyzed at line $lineNumber: $fullText")
|
|
||||||
|
|
||||||
val errorHolder = createDiagnosticHolder()
|
|
||||||
|
|
||||||
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder)
|
|
||||||
|
|
||||||
if (syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof) {
|
|
||||||
return if (allowIncompleteLines) {
|
|
||||||
previousIncompleteLines.add(line)
|
|
||||||
LineResult.Incomplete
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
previousIncompleteLines.clear()
|
|
||||||
|
|
||||||
if (syntaxErrorReport.isHasErrors) {
|
|
||||||
return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
|
|
||||||
}
|
|
||||||
|
|
||||||
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, ReplCodeLine(lineNumber, 0, "fake line"))
|
|
||||||
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
|
|
||||||
val scriptDescriptor = when (analysisResult) {
|
|
||||||
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
|
|
||||||
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
|
|
||||||
else -> error("Unexpected result ${analysisResult::class.java}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val state = GenerationState(
|
|
||||||
psiFile.project,
|
|
||||||
ClassBuilderFactories.binaries(false),
|
|
||||||
analyzerEngine.module,
|
|
||||||
analyzerEngine.trace.bindingContext,
|
|
||||||
listOf(psiFile),
|
|
||||||
configuration)
|
|
||||||
|
|
||||||
compileScript(psiFile.script!!, earlierLines.map(EarlierLine::getScriptDescriptor), state, CompilationErrorHandler.THROW_EXCEPTION)
|
|
||||||
|
|
||||||
for (outputFile in state.factory.asList()) {
|
|
||||||
if (outputFile.relativePath.endsWith(".class")) {
|
|
||||||
classLoader.addClass(JvmClassName.byInternalName(outputFile.relativePath.replaceFirst("\\.class$".toRegex(), "")),
|
|
||||||
outputFile.asByteArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val scriptClass = classLoader.loadClass("Line$lineNumber")
|
|
||||||
|
|
||||||
val constructorParams = earlierLines.map(EarlierLine::getScriptClass).toTypedArray()
|
val evalRes = scriptEvaluator.compileAndEval(evalState, ReplCodeLine(lineNumber.getAndIncrement(), 0, fullText), null, object : InvokeWrapper {
|
||||||
val constructorArgs = earlierLines.map(EarlierLine::getScriptInstance).toTypedArray()
|
override fun <T> invoke(body: () -> T): T = executeUserCode { body() }
|
||||||
|
})
|
||||||
|
|
||||||
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
|
when {
|
||||||
val scriptInstance = try {
|
evalRes !is ReplEvalResult.Incomplete -> previousIncompleteLines.clear()
|
||||||
executeUserCode { scriptInstanceConstructor.newInstance(*constructorArgs) }
|
allowIncompleteLines -> previousIncompleteLines.add(line)
|
||||||
|
else -> return ReplEvalResult.Error.CompileTime("incomplete code")
|
||||||
}
|
}
|
||||||
catch (e: Throwable) {
|
return evalRes
|
||||||
// ignore everything in the stack trace until this constructor call
|
|
||||||
return LineResult.Error.Runtime(renderStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"))
|
|
||||||
}
|
|
||||||
|
|
||||||
val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
|
|
||||||
val rv: Any? = rvField.get(scriptInstance)
|
|
||||||
|
|
||||||
earlierLines.add(EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance))
|
|
||||||
|
|
||||||
if (!state.replSpecific.hasResult) {
|
|
||||||
return LineResult.UnitResult
|
|
||||||
}
|
|
||||||
val valueAsString: String = try {
|
|
||||||
executeUserCode { rv.toString() }
|
|
||||||
}
|
|
||||||
catch (e: Throwable) {
|
|
||||||
return LineResult.Error.Runtime(renderStackTrace(e, startFromMethodName = "java.lang.String.valueOf"))
|
|
||||||
}
|
|
||||||
return LineResult.ValueResult(valueAsString)
|
|
||||||
}
|
}
|
||||||
catch (e: Throwable) {
|
catch (e: Throwable) {
|
||||||
val writer = PrintWriter(System.err)
|
val writer = PrintWriter(System.err)
|
||||||
@@ -177,59 +114,8 @@ class ReplInterpreter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
|
||||||
private val REPL_LINE_AS_SCRIPT_DEFINITION = object : KotlinScriptDefinition(Any::class) {
|
private val REPL_LINE_AS_SCRIPT_DEFINITION = object : KotlinScriptDefinition(Any::class) {
|
||||||
override val name = "Kotlin REPL"
|
override val name = "Kotlin REPL"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderStackTrace(cause: Throwable, startFromMethodName: String): String {
|
|
||||||
val newTrace = arrayListOf<StackTraceElement>()
|
|
||||||
var skip = true
|
|
||||||
for (element in cause.stackTrace.reversed()) {
|
|
||||||
if ("${element.className}.${element.methodName}" == startFromMethodName) {
|
|
||||||
skip = false
|
|
||||||
}
|
|
||||||
if (!skip) {
|
|
||||||
newTrace.add(element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val resultingTrace = newTrace.reversed().dropLast(1)
|
|
||||||
|
|
||||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UsePropertyAccessSyntax")
|
|
||||||
(cause as java.lang.Throwable).setStackTrace(resultingTrace.toTypedArray())
|
|
||||||
|
|
||||||
return Throwables.getStackTraceAsString(cause)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun compileScript(
|
|
||||||
script: KtScript,
|
|
||||||
earlierScripts: List<ScriptDescriptor>,
|
|
||||||
state: GenerationState,
|
|
||||||
errorHandler: CompilationErrorHandler
|
|
||||||
) {
|
|
||||||
state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
|
|
||||||
state.replSpecific.earlierScriptsForReplInterpreter = earlierScripts.toList()
|
|
||||||
|
|
||||||
state.beforeCompile()
|
|
||||||
KotlinCodegenFacade.generatePackage(
|
|
||||||
state,
|
|
||||||
script.containingKtFile.packageFqName,
|
|
||||||
setOf(script.containingKtFile),
|
|
||||||
errorHandler
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class LineResult {
|
|
||||||
class ValueResult(val valueAsString: String): LineResult()
|
|
||||||
|
|
||||||
object UnitResult: LineResult()
|
|
||||||
object Incomplete : LineResult()
|
|
||||||
|
|
||||||
sealed class Error(val errorText: String): LineResult() {
|
|
||||||
class Runtime(errorText: String): Error(errorText)
|
|
||||||
class CompileTime(errorText: String): Error(errorText)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
>>> fun foo() = 765
|
>>> fun foo() = 765
|
||||||
>>> foo(1)
|
>>> foo(1)
|
||||||
error: too many arguments for public final fun foo(): Int defined in Line1
|
error: too many arguments for public final fun foo(): Int defined in Line_0
|
||||||
foo(1)
|
foo(1)
|
||||||
^
|
^
|
||||||
>>> foo()
|
>>> foo()
|
||||||
|
|||||||
+1
-1
@@ -6,4 +6,4 @@ java.lang.Exception: hi there
|
|||||||
>>> fun bar(): Nothing = throw AssertionError()
|
>>> fun bar(): Nothing = throw AssertionError()
|
||||||
>>> bar()
|
>>> bar()
|
||||||
java.lang.AssertionError
|
java.lang.AssertionError
|
||||||
at Line4.bar(Unknown Source)
|
at Line_3.bar(Unknown Source)
|
||||||
|
|||||||
+2
-6
@@ -1,9 +1,5 @@
|
|||||||
>>> class B { override fun toString(): String { return foo() } ; fun foo(): String { return error("message") } }
|
>>> class B { override fun toString(): String { return foo() } ; fun foo(): String { return error("message") } }
|
||||||
>>> B().toString()
|
>>> B().toString()
|
||||||
java.lang.IllegalStateException: message
|
java.lang.IllegalStateException: message
|
||||||
at Line1$B.foo(line1.kts:1)
|
at Line_0$B.foo(Line_0.kts:1)
|
||||||
at Line1$B.toString(line1.kts:1)
|
at Line_0$B.toString(Line_0.kts:1)
|
||||||
>>> B()
|
|
||||||
java.lang.IllegalStateException: message
|
|
||||||
at Line1$B.foo(line1.kts:1)
|
|
||||||
at Line1$B.toString(line1.kts:1)
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
package org.jetbrains.kotlin.repl
|
package org.jetbrains.kotlin.repl
|
||||||
|
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
|
||||||
import org.jetbrains.kotlin.cli.jvm.repl.ConsoleReplConfiguration
|
import org.jetbrains.kotlin.cli.jvm.repl.ConsoleReplConfiguration
|
||||||
import org.jetbrains.kotlin.cli.jvm.repl.LineResult
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.repl.ReplInterpreter
|
import org.jetbrains.kotlin.cli.jvm.repl.ReplInterpreter
|
||||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
@@ -38,6 +38,7 @@ private val INCOMPLETE_PATTERN = Pattern.compile("\\.\\.\\.( *)(.*)$")
|
|||||||
private val TRAILING_NEWLINE_REGEX = Regex("\n$")
|
private val TRAILING_NEWLINE_REGEX = Regex("\n$")
|
||||||
|
|
||||||
private val INCOMPLETE_LINE_MESSAGE = "incomplete line"
|
private val INCOMPLETE_LINE_MESSAGE = "incomplete line"
|
||||||
|
private val HISTORY_MISMATCH_LINE_MESSAGE = "history mismatch"
|
||||||
|
|
||||||
abstract class AbstractReplInterpreterTest : KtUsefulTestCase() {
|
abstract class AbstractReplInterpreterTest : KtUsefulTestCase() {
|
||||||
init {
|
init {
|
||||||
@@ -93,10 +94,11 @@ abstract class AbstractReplInterpreterTest : KtUsefulTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val actual = when (lineResult) {
|
val actual = when (lineResult) {
|
||||||
is LineResult.ValueResult -> lineResult.valueAsString
|
is ReplEvalResult.ValueResult -> lineResult.value.toString()
|
||||||
is LineResult.Error -> lineResult.errorText
|
is ReplEvalResult.Error -> lineResult.message
|
||||||
LineResult.Incomplete -> INCOMPLETE_LINE_MESSAGE
|
is ReplEvalResult.Incomplete -> INCOMPLETE_LINE_MESSAGE
|
||||||
LineResult.UnitResult -> ""
|
is ReplEvalResult.UnitResult -> ""
|
||||||
|
is ReplEvalResult.HistoryMismatch -> HISTORY_MISMATCH_LINE_MESSAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
|
|||||||
Reference in New Issue
Block a user