REPL: call 'toString()' for returned values explicitly and correctly report exceptions

#KT-12389 Fixed
This commit is contained in:
Pavel V. Talanov
2016-05-31 18:33:53 +03:00
parent 8ac40d7401
commit 5d31d6d98d
8 changed files with 47 additions and 22 deletions
@@ -100,7 +100,7 @@ class ReplFromTerminal(
is ValueResult, UnitResult -> { is ValueResult, UnitResult -> {
writer.notifyCommandSuccess() writer.notifyCommandSuccess()
if (lineResult is ValueResult) { if (lineResult is ValueResult) {
writer.outputCommandResult(lineResult.value) writer.outputCommandResult(lineResult.valueAsString)
} }
} }
Incomplete -> writer.notifyIncomplete() Incomplete -> writer.notifyIncomplete()
@@ -132,25 +132,28 @@ class ReplInterpreter(
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams) val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
val scriptInstance = try { val scriptInstance = try {
onUserCodeExecuting(true) executeUserCode { scriptInstanceConstructor.newInstance(*constructorArgs) }
scriptInstanceConstructor.newInstance(*constructorArgs)
} }
catch (e: Throwable) { catch (e: Throwable) {
return LineResult.Error.Runtime(renderStackTrace(e.cause!!)) // ignore everything in the stack trace until this constructor call
} return LineResult.Error.Runtime(renderStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"))
finally {
onUserCodeExecuting(false)
} }
val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true } 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)) earlierLines.add(EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance))
if (!state.replSpecific.hasResult) { if (!state.replSpecific.hasResult) {
return LineResult.UnitResult 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) { catch (e: Throwable) {
val writer = PrintWriter(System.err) 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) { fun dumpClasses(out: PrintWriter) {
classLoader.dumpClasses(out) classLoader.dumpClasses(out)
} }
@@ -176,13 +189,11 @@ class ReplInterpreter(
override fun getScriptName(script: KtScript): Name = StandardScriptDefinition.getScriptName(script) 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>() val newTrace = arrayListOf<StackTraceElement>()
var skip = true var skip = true
for ((i, element) in cause.stackTrace.withIndex().reversed()) { for ((i, element) in cause.stackTrace.withIndex().reversed()) {
// All our code happens in the script constructor, and no reflection/native code happens in constructors. if ("${element.className}.${element.methodName}" == startFromMethodName) {
// So we ignore everything in the stack trace until the first constructor
if (element.methodName == "<init>") {
skip = false skip = false
} }
if (!skip) { 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) val resultingTrace = newTrace.reversed().dropLast(1)
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UsePropertyAccessSyntax") @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UsePropertyAccessSyntax")
@@ -220,7 +230,7 @@ class ReplInterpreter(
} }
sealed class LineResult { sealed class LineResult {
class ValueResult(val value: Any?): LineResult() class ValueResult(val valueAsString: String): LineResult()
object UnitResult: LineResult() object UnitResult: LineResult()
object Incomplete : 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 printlnWelcomeMessage(x: String) = println(x)
override fun printlnHelpMessage(x: String) = println(x) override fun printlnHelpMessage(x: String) = println(x)
override fun outputCompileError(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 outputRuntimeError(x: String) = println(x)
override fun notifyReadLineStart() {} override fun notifyReadLineStart() {}
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 printlnWelcomeMessage(x: String) = printlnWithEscaping(x, EscapeType.INITIAL_PROMPT)
override fun printlnHelpMessage(x: String) = printlnWithEscaping(x, EscapeType.HELP_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 notifyReadLineStart() = printlnWithEscaping("", EscapeType.READLINE_START)
override fun notifyReadLineEnd() = printlnWithEscaping("", EscapeType.READLINE_END) override fun notifyReadLineEnd() = printlnWithEscaping("", EscapeType.READLINE_END)
override fun notifyCommandSuccess() = printlnWithEscaping("", EscapeType.SUCCESS) 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 { interface ReplWriter {
fun printlnWelcomeMessage(x: String) fun printlnWelcomeMessage(x: String)
fun printlnHelpMessage(x: String) fun printlnHelpMessage(x: String)
fun outputCommandResult(x: Any?) fun outputCommandResult(x: String)
fun notifyReadLineStart() fun notifyReadLineStart()
fun notifyReadLineEnd() fun notifyReadLineEnd()
fun notifyIncomplete() fun notifyIncomplete()
+9
View File
@@ -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) { val actual = when (lineResult) {
is LineResult.ValueResult -> "${lineResult.value}" is LineResult.ValueResult -> lineResult.valueAsString
is LineResult.Error -> lineResult.errorText is LineResult.Error -> lineResult.errorText
LineResult.Incomplete -> INCOMPLETE_LINE_MESSAGE LineResult.Incomplete -> INCOMPLETE_LINE_MESSAGE
LineResult.UnitResult -> "" LineResult.UnitResult -> ""
@@ -59,6 +59,12 @@ public class ReplInterpreterTestGenerated extends AbstractReplInterpreterTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("exceptionInValueToString.repl")
public void testExceptionInValueToString() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/repl/exceptionInValueToString.repl");
doTest(fileName);
}
@TestMetadata("function.repl") @TestMetadata("function.repl")
public void testFunction() throws Exception { public void testFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/repl/function.repl"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/repl/function.repl");