diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.kt index e84f5fd94c7..7e4281600d1 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.kt @@ -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() diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt index dd413fbca06..1487fe270f4 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt @@ -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}.")) } 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 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() 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 == "") { + if ("${element.className}.${element.methodName}" == startFromMethodName) { skip = false } if (!skip) { @@ -190,7 +201,6 @@ class ReplInterpreter( } } - // throw away last element which contains Line1.kts(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() diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplConsoleWriter.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplConsoleWriter.kt index db1963eb443..e5585bde644 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplConsoleWriter.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplConsoleWriter.kt @@ -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() {} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemOutWrapperForIde.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemOutWrapperForIde.kt index 5ae54106cf8..402266860bc 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemOutWrapperForIde.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemOutWrapperForIde.kt @@ -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) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplWriter.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplWriter.kt index b5060205613..ccefb6c1be2 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplWriter.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplWriter.kt @@ -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() diff --git a/compiler/testData/repl/exceptionInValueToString.repl b/compiler/testData/repl/exceptionInValueToString.repl new file mode 100644 index 00000000000..e63ee6d12d1 --- /dev/null +++ b/compiler/testData/repl/exceptionInValueToString.repl @@ -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) \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt b/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt index 3e13a7618bc..4c84245f78f 100644 --- a/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt @@ -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 -> "" diff --git a/compiler/tests/org/jetbrains/kotlin/repl/ReplInterpreterTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/repl/ReplInterpreterTestGenerated.java index 73b1a8b8eae..4cda6c55810 100644 --- a/compiler/tests/org/jetbrains/kotlin/repl/ReplInterpreterTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/repl/ReplInterpreterTestGenerated.java @@ -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");