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.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.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.KotlinCompilerVersion
|
||||
@@ -86,7 +86,7 @@ class ReplFromTerminal(
|
||||
}
|
||||
|
||||
val lineResult = eval(line)
|
||||
if (lineResult is LineResult.Incomplete) {
|
||||
if (lineResult is ReplEvalResult.Incomplete) {
|
||||
return WhatNextAfterOneLine.INCOMPLETE
|
||||
}
|
||||
else {
|
||||
@@ -94,20 +94,20 @@ class ReplFromTerminal(
|
||||
}
|
||||
}
|
||||
|
||||
private fun eval(line: String): LineResult {
|
||||
val lineResult = replInterpreter.eval(line)
|
||||
when (lineResult) {
|
||||
is ValueResult, UnitResult -> {
|
||||
private fun eval(line: String): ReplEvalResult {
|
||||
val evalResult = replInterpreter.eval(line)
|
||||
when (evalResult) {
|
||||
is ReplEvalResult.ValueResult, is ReplEvalResult.UnitResult -> {
|
||||
writer.notifyCommandSuccess()
|
||||
if (lineResult is ValueResult) {
|
||||
writer.outputCommandResult(lineResult.valueAsString)
|
||||
if (evalResult is ReplEvalResult.ValueResult) {
|
||||
writer.outputCommandResult(evalResult.value.toString())
|
||||
}
|
||||
}
|
||||
Incomplete -> writer.notifyIncomplete()
|
||||
is Error.CompileTime -> writer.outputCompileError(lineResult.errorText)
|
||||
is Error.Runtime -> writer.outputRuntimeError(lineResult.errorText)
|
||||
is ReplEvalResult.Error.Runtime -> writer.outputRuntimeError(evalResult.message)
|
||||
is ReplEvalResult.Error.CompileTime -> writer.outputRuntimeError(evalResult.message)
|
||||
is ReplEvalResult.Incomplete -> writer.notifyIncomplete()
|
||||
}
|
||||
return lineResult
|
||||
return evalResult
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
|
||||
@@ -16,143 +16,80 @@
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.repl
|
||||
|
||||
import com.google.common.base.Throwables
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.vfs.CharsetToolkit
|
||||
import com.intellij.psi.PsiFileFactory
|
||||
import com.intellij.psi.impl.PsiFileFactoryImpl
|
||||
import com.intellij.testFramework.LightVirtualFile
|
||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
||||
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.common.messages.CompilerMessageLocation
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
|
||||
import org.jetbrains.kotlin.cli.common.repl.*
|
||||
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.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 java.io.PrintWriter
|
||||
import java.net.URLClassLoader
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class ReplInterpreter(
|
||||
disposable: Disposable,
|
||||
private val configuration: CompilerConfiguration,
|
||||
private val replConfiguration: ReplConfiguration
|
||||
): ReplConfiguration by replConfiguration {
|
||||
private var lineNumber = 0
|
||||
|
||||
private val earlierLines = arrayListOf<EarlierLine>()
|
||||
private val lineNumber = AtomicInteger()
|
||||
|
||||
private val previousIncompleteLines = arrayListOf<String>()
|
||||
private val classLoader: ReplClassLoader = run {
|
||||
val classpath = configuration.jvmClasspathRoots.map { it.toURI().toURL() }
|
||||
ReplClassLoader(URLClassLoader(classpath.toTypedArray(), null))
|
||||
}
|
||||
|
||||
private val environment = run {
|
||||
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, REPL_LINE_AS_SCRIPT_DEFINITION)
|
||||
KotlinCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||
private val messageCollector = object : MessageCollector {
|
||||
private var hasErrors = false
|
||||
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
|
||||
private val analyzerEngine = ReplCodeAnalyzer(environment)
|
||||
// TODO: add script definition with project-based resolving for IDEA repl
|
||||
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 {
|
||||
++lineNumber
|
||||
private val evalState by lazy { scriptEvaluator.createState() }
|
||||
|
||||
fun eval(line: String): ReplEvalResult {
|
||||
|
||||
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 {
|
||||
val scriptClass = classLoader.loadClass("Line$lineNumber")
|
||||
|
||||
val constructorParams = earlierLines.map(EarlierLine::getScriptClass).toTypedArray()
|
||||
val constructorArgs = earlierLines.map(EarlierLine::getScriptInstance).toTypedArray()
|
||||
val evalRes = scriptEvaluator.compileAndEval(evalState, ReplCodeLine(lineNumber.getAndIncrement(), 0, fullText), null, object : InvokeWrapper {
|
||||
override fun <T> invoke(body: () -> T): T = executeUserCode { body() }
|
||||
})
|
||||
|
||||
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
|
||||
val scriptInstance = try {
|
||||
executeUserCode { scriptInstanceConstructor.newInstance(*constructorArgs) }
|
||||
when {
|
||||
evalRes !is ReplEvalResult.Incomplete -> previousIncompleteLines.clear()
|
||||
allowIncompleteLines -> previousIncompleteLines.add(line)
|
||||
else -> return ReplEvalResult.Error.CompileTime("incomplete code")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
// 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)
|
||||
return evalRes
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
val writer = PrintWriter(System.err)
|
||||
@@ -177,59 +114,8 @@ class ReplInterpreter(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
||||
private val REPL_LINE_AS_SCRIPT_DEFINITION = object : KotlinScriptDefinition(Any::class) {
|
||||
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
|
||||
>>> 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
-1
@@ -6,4 +6,4 @@ java.lang.Exception: hi there
|
||||
>>> fun bar(): Nothing = throw AssertionError()
|
||||
>>> bar()
|
||||
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") } }
|
||||
>>> B().toString()
|
||||
java.lang.IllegalStateException: message
|
||||
at Line1$B.foo(line1.kts:1)
|
||||
at Line1$B.toString(line1.kts:1)
|
||||
>>> B()
|
||||
java.lang.IllegalStateException: message
|
||||
at Line1$B.foo(line1.kts:1)
|
||||
at Line1$B.toString(line1.kts:1)
|
||||
at Line_0$B.foo(Line_0.kts:1)
|
||||
at Line_0$B.toString(Line_0.kts:1)
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
package org.jetbrains.kotlin.repl
|
||||
|
||||
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.LineResult
|
||||
import org.jetbrains.kotlin.cli.jvm.repl.ReplInterpreter
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||
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 INCOMPLETE_LINE_MESSAGE = "incomplete line"
|
||||
private val HISTORY_MISMATCH_LINE_MESSAGE = "history mismatch"
|
||||
|
||||
abstract class AbstractReplInterpreterTest : KtUsefulTestCase() {
|
||||
init {
|
||||
@@ -93,10 +94,11 @@ abstract class AbstractReplInterpreterTest : KtUsefulTestCase() {
|
||||
}
|
||||
|
||||
val actual = when (lineResult) {
|
||||
is LineResult.ValueResult -> lineResult.valueAsString
|
||||
is LineResult.Error -> lineResult.errorText
|
||||
LineResult.Incomplete -> INCOMPLETE_LINE_MESSAGE
|
||||
LineResult.UnitResult -> ""
|
||||
is ReplEvalResult.ValueResult -> lineResult.value.toString()
|
||||
is ReplEvalResult.Error -> lineResult.message
|
||||
is ReplEvalResult.Incomplete -> INCOMPLETE_LINE_MESSAGE
|
||||
is ReplEvalResult.UnitResult -> ""
|
||||
is ReplEvalResult.HistoryMismatch -> HISTORY_MISMATCH_LINE_MESSAGE
|
||||
}
|
||||
|
||||
Assert.assertEquals(
|
||||
|
||||
Reference in New Issue
Block a user