diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index f8a1f3df21b2..89e6a868eed0 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -4,6 +4,7 @@ package repl import dotc.reporting.diagnostic.MessageContainer import dotc.core.Contexts.Context import dotc.parsing.Parsers.Parser +import dotc.parsing.Tokens import dotc.util.SourceFile import dotc.ast.untpd import dotc.reporting._ @@ -97,6 +98,12 @@ object ParseResult { @sharable private[this] val CommandExtract = """(:[\S]+)\s*(.*)""".r + private def parseStats(parser: Parser): List[untpd.Tree] = { + val stats = parser.blockStatSeq() + parser.accept(Tokens.EOF) + stats + } + /** Extract a `ParseResult` from the string `sourceCode` */ def apply(sourceCode: String)(implicit ctx: Context): ParseResult = sourceCode match { @@ -114,7 +121,7 @@ object ParseResult { val source = new SourceFile("", sourceCode.toCharArray) val parser = new Parser(source) - val (_, stats) = parser.templateStatSeq() + val stats = parseStats(parser) if (ctx.reporter.hasErrors) { SyntaxErrors(sourceCode, @@ -140,7 +147,7 @@ object ParseResult { reporter.withIncompleteHandler(_ => _ => needsMore = true) { val source = new SourceFile("", sourceCode.toCharArray) val parser = new Parser(source)(ctx.fresh.setReporter(reporter)) - parser.templateStatSeq() + parseStats(parser) !reporter.hasErrors && needsMore } } diff --git a/compiler/src/dotty/tools/repl/Rendering.scala b/compiler/src/dotty/tools/repl/Rendering.scala index 15125320ef1e..5dff8890d336 100644 --- a/compiler/src/dotty/tools/repl/Rendering.scala +++ b/compiler/src/dotty/tools/repl/Rendering.scala @@ -1,7 +1,9 @@ package dotty.tools package repl -import java.lang.ClassLoader +import java.io.{ StringWriter, PrintWriter } +import java.lang.{ ClassLoader, ExceptionInInitializerError } +import java.lang.reflect.InvocationTargetException import scala.util.control.NonFatal @@ -70,10 +72,26 @@ private[repl] class Rendering(compiler: ReplCompiler, /** Render value definition result */ def renderVal(d: Denotation)(implicit ctx: Context): Option[String] = { val dcl = d.symbol.showUser - val resultValue = - if (d.symbol.is(Flags.Lazy)) Some("") - else valueOf(d.symbol) - resultValue.map(value => s"$dcl = $value") + try { + val resultValue = + if (d.symbol.is(Flags.Lazy)) Some("") + else valueOf(d.symbol) + + resultValue.map(value => s"$dcl = $value") + } + catch { case ex: InvocationTargetException => Some(renderError(ex)) } + } + + /** Render the stack trace of the underlying exception */ + private def renderError(ex: InvocationTargetException): String = { + val cause = ex.getCause match { + case ex: ExceptionInInitializerError => ex.getCause + case ex => ex + } + val sw = new StringWriter() + val pw = new PrintWriter(sw) + cause.printStackTrace(pw) + sw.toString } } diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index e1a649c1258c..495f1159e357 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -146,7 +146,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler { * } * ``` */ - def wrapped(defs: Definitions): untpd.PackageDef = { + def wrapped(defs: Definitions, sourceCode: String): untpd.PackageDef = { import untpd._ implicit val ctx: Context = defs.state.run.runContext @@ -156,7 +156,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler { List( ModuleDef(objectName(defs.state), tmpl) .withMods(new Modifiers(Module | Final)) - .withPos(Position(defs.stats.head.pos.start, defs.stats.last.pos.end)) + .withPos(Position(0, sourceCode.length)) ) } @@ -170,7 +170,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler { def createUnit(defs: Definitions, sourceCode: String): Result[CompilationUnit] = { val unit = new CompilationUnit(new SourceFile(objectName(defs.state).toString, sourceCode)) - unit.untpdTree = wrapped(defs) + unit.untpdTree = wrapped(defs, sourceCode) unit.result } @@ -238,7 +238,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler { PackageDef(Ident(nme.EMPTY_PACKAGE), TypeDef("EvaluateExpr".toTypeName, tmpl) .withMods(new Modifiers(Final)) - .withPos(Position(trees.head.pos.start, trees.last.pos.end)) :: Nil + .withPos(Position(0, expr.length)) :: Nil ) } diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 138f5b032f7c..6d445b28a2a7 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -186,7 +186,7 @@ class ReplDriver(settings: Array[String], private def interpret(res: ParseResult)(implicit state: State): State = res match { - case parsed: Parsed => + case parsed: Parsed if parsed.trees.nonEmpty => compile(parsed) .withHistory(parsed.sourceCode :: state.history) .newRun(compiler, rootCtx) @@ -195,9 +195,13 @@ class ReplDriver(settings: Array[String], displayErrors(errs) state.withHistory(src :: state.history) - case Newline | SigKill => state - case cmd: Command => interpretCommand(cmd) + + case SigKill => // TODO + state + + case _ => // new line, empty tree + state } /** Compile `parsed` trees and evolve `state` in accordance */ diff --git a/compiler/test-resources/repl/parsing b/compiler/test-resources/repl/parsing new file mode 100644 index 000000000000..2b3da45aa537 --- /dev/null +++ b/compiler/test-resources/repl/parsing @@ -0,0 +1,14 @@ +scala> ; +scala> ;; +scala> 1; 2 +val res0: Int = 1 +val res1: Int = 2 +scala> 1; +val res2: Int = 1 +scala> 1;; 2 +val res3: Int = 1 +val res4: Int = 2 +scala> } +1 | } + | ^ + | eof expected, but '}' found diff --git a/compiler/test/dotty/tools/repl/CompilerTests.scala b/compiler/test/dotty/tools/repl/CompilerTests.scala index 20f29e45829f..733372ad48d7 100644 --- a/compiler/test/dotty/tools/repl/CompilerTests.scala +++ b/compiler/test/dotty/tools/repl/CompilerTests.scala @@ -92,4 +92,27 @@ class ReplCompilerTests extends ReplTest { storedOutput() ) } + + @Test def i3305: Unit = { + fromInitialState { implicit s => + compile("null.toString") + storedOutput().startsWith("java.lang.NullPointerException") + } + + fromInitialState { implicit s => + compile("def foo: Int = 1 + foo; foo") + storedOutput().startsWith("def foo: Int\njava.lang.StackOverflowError") + } + + fromInitialState { implicit s => + compile("""throw new IllegalArgumentException("Hello")""") + storedOutput().startsWith("java.lang.IllegalArgumentException: Hello") + } + + // FIXME + // fromInitialState { implicit s => + // compile("val (x, y) = null") + // storedOutput().startsWith("scala.MatchError: null") + // } + } } diff --git a/compiler/test/dotty/tools/repl/ScriptedTests.scala b/compiler/test/dotty/tools/repl/ScriptedTests.scala index 7807c5c1dbdd..3b8262aa0666 100644 --- a/compiler/test/dotty/tools/repl/ScriptedTests.scala +++ b/compiler/test/dotty/tools/repl/ScriptedTests.scala @@ -11,7 +11,7 @@ import scala.io.Source import dotc.reporting.MessageRendering -/** Runs all tests contained in `/repl/test-resources/scripts` */ +/** Runs all tests contained in `compiler/test-resources/repl/` */ class ScriptedTests extends ReplTest with MessageRendering { private def scripts(path: String): Array[JFile] = { @@ -44,7 +44,7 @@ class ScriptedTests extends ReplTest with MessageRendering { def evaluate(state: State, input: String, prompt: String) = try { val nstate = run(input.drop(prompt.length))(state) - val out = input + "\n" + stripColor(storedOutput()) + val out = input + "\n" + storedOutput() (out, nstate) } catch { diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 84ef6cc130ca..c4e60a5458a7 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -52,4 +52,10 @@ class TabcompleteTests extends ReplTest { assert(tabComplete(src2).suggestions.nonEmpty) } } + + @Test def i3309: Unit = + fromInitialState { implicit s => + List("\"", "#", ")", "=", "'", "¨", "£", ".", ":", ",", ";", "@", "}", "[", "]") + .foreach(src => assertTrue(tabComplete(src).suggestions.isEmpty)) + } }