REPL: call 'toString()' for returned values explicitly and correctly report exceptions
#KT-12389 Fixed
This commit is contained in:
@@ -100,7 +100,7 @@ class ReplFromTerminal(
|
||||
is ValueResult, UnitResult -> {
|
||||
writer.notifyCommandSuccess()
|
||||
if (lineResult is ValueResult) {
|
||||
writer.outputCommandResult(lineResult.value)
|
||||
writer.outputCommandResult(lineResult.valueAsString)
|
||||
}
|
||||
}
|
||||
Incomplete -> writer.notifyIncomplete()
|
||||
|
||||
@@ -132,25 +132,28 @@ class ReplInterpreter(
|
||||
|
||||
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
|
||||
val scriptInstance = try {
|
||||
onUserCodeExecuting(true)
|
||||
scriptInstanceConstructor.newInstance(*constructorArgs)
|
||||
executeUserCode { scriptInstanceConstructor.newInstance(*constructorArgs) }
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
return LineResult.Error.Runtime(renderStackTrace(e.cause!!))
|
||||
}
|
||||
finally {
|
||||
onUserCodeExecuting(false)
|
||||
// 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 = rvField.get(scriptInstance)
|
||||
val rv: Any? = rvField.get(scriptInstance)
|
||||
|
||||
earlierLines.add(EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance))
|
||||
|
||||
if (!state.replSpecific.hasResult) {
|
||||
return LineResult.UnitResult
|
||||
}
|
||||
return LineResult.ValueResult(rv)
|
||||
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) {
|
||||
val writer = PrintWriter(System.err)
|
||||
@@ -160,6 +163,16 @@ class ReplInterpreter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> executeUserCode(body: () -> T): T {
|
||||
try {
|
||||
onUserCodeExecuting(true)
|
||||
return body()
|
||||
}
|
||||
finally {
|
||||
onUserCodeExecuting(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun dumpClasses(out: PrintWriter) {
|
||||
classLoader.dumpClasses(out)
|
||||
}
|
||||
@@ -176,13 +189,11 @@ class ReplInterpreter(
|
||||
override fun getScriptName(script: KtScript): Name = StandardScriptDefinition.getScriptName(script)
|
||||
}
|
||||
|
||||
private fun renderStackTrace(cause: Throwable): String {
|
||||
private fun renderStackTrace(cause: Throwable, startFromMethodName: String): String {
|
||||
val newTrace = arrayListOf<StackTraceElement>()
|
||||
var skip = true
|
||||
for ((i, element) in cause.stackTrace.withIndex().reversed()) {
|
||||
// All our code happens in the script constructor, and no reflection/native code happens in constructors.
|
||||
// So we ignore everything in the stack trace until the first constructor
|
||||
if (element.methodName == "<init>") {
|
||||
if ("${element.className}.${element.methodName}" == startFromMethodName) {
|
||||
skip = false
|
||||
}
|
||||
if (!skip) {
|
||||
@@ -190,7 +201,6 @@ class ReplInterpreter(
|
||||
}
|
||||
}
|
||||
|
||||
// throw away last element which contains Line1.kts<init>(Unknown source)
|
||||
val resultingTrace = newTrace.reversed().dropLast(1)
|
||||
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UsePropertyAccessSyntax")
|
||||
@@ -220,7 +230,7 @@ class ReplInterpreter(
|
||||
}
|
||||
|
||||
sealed class LineResult {
|
||||
class ValueResult(val value: Any?): LineResult()
|
||||
class ValueResult(val valueAsString: String): LineResult()
|
||||
|
||||
object UnitResult: LineResult()
|
||||
object Incomplete : LineResult()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,7 @@ class ReplConsoleWriter : ReplWriter {
|
||||
override fun printlnWelcomeMessage(x: String) = println(x)
|
||||
override fun printlnHelpMessage(x: String) = println(x)
|
||||
override fun outputCompileError(x: String) = println(x)
|
||||
override fun outputCommandResult(x: Any?) = println(x)
|
||||
override fun outputCommandResult(x: String) = println(x)
|
||||
override fun outputRuntimeError(x: String) = println(x)
|
||||
|
||||
override fun notifyReadLineStart() {}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -60,7 +60,7 @@ class ReplSystemOutWrapperForIde(standardOut: PrintStream) : PrintStream(standar
|
||||
|
||||
override fun printlnWelcomeMessage(x: String) = printlnWithEscaping(x, EscapeType.INITIAL_PROMPT)
|
||||
override fun printlnHelpMessage(x: String) = printlnWithEscaping(x, EscapeType.HELP_PROMPT)
|
||||
override fun outputCommandResult(x: Any?) = printlnWithEscaping(x.toString(), EscapeType.REPL_RESULT)
|
||||
override fun outputCommandResult(x: String) = printlnWithEscaping(x, EscapeType.REPL_RESULT)
|
||||
override fun notifyReadLineStart() = printlnWithEscaping("", EscapeType.READLINE_START)
|
||||
override fun notifyReadLineEnd() = printlnWithEscaping("", EscapeType.READLINE_END)
|
||||
override fun notifyCommandSuccess() = printlnWithEscaping("", EscapeType.SUCCESS)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.cli.jvm.repl.messages
|
||||
interface ReplWriter {
|
||||
fun printlnWelcomeMessage(x: String)
|
||||
fun printlnHelpMessage(x: String)
|
||||
fun outputCommandResult(x: Any?)
|
||||
fun outputCommandResult(x: String)
|
||||
fun notifyReadLineStart()
|
||||
fun notifyReadLineEnd()
|
||||
fun notifyIncomplete()
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
>>> 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)
|
||||
@@ -93,7 +93,7 @@ abstract class AbstractReplInterpreterTest : KtUsefulTestCase() {
|
||||
}
|
||||
|
||||
val actual = when (lineResult) {
|
||||
is LineResult.ValueResult -> "${lineResult.value}"
|
||||
is LineResult.ValueResult -> lineResult.valueAsString
|
||||
is LineResult.Error -> lineResult.errorText
|
||||
LineResult.Incomplete -> INCOMPLETE_LINE_MESSAGE
|
||||
LineResult.UnitResult -> ""
|
||||
|
||||
@@ -59,6 +59,12 @@ public class ReplInterpreterTestGenerated extends AbstractReplInterpreterTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("exceptionInValueToString.repl")
|
||||
public void testExceptionInValueToString() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/repl/exceptionInValueToString.repl");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("function.repl")
|
||||
public void testFunction() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/repl/function.repl");
|
||||
|
||||
Reference in New Issue
Block a user