From b9f219b9b49efc495887756f9fbb8976fa24c6e4 Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Wed, 1 May 2019 15:49:37 +0200 Subject: [PATCH] Fix #5895: REPL autocompletion crushes in certain cases The crashes happen due to the fact that in certain cases, certain trees get mispositioned during code completion. E.g. this happens with `opaque type T = Int` or `object Foo { type T = Int`. The tree spans are set incorrectly because repl doesn't set the sources of these trees correctly. Precisely, when typechecking on code completion, a virtual source is used. However, when parsing for autocompletion, a line source is used which is created by the parsing logic. This commit makes sure that REPL is consistent in its source choices when computing code completions. --- compiler/src/dotty/tools/repl/ParseResult.scala | 16 +++++++++------- compiler/src/dotty/tools/repl/ReplCompiler.scala | 2 +- .../test/dotty/tools/repl/TabcompleteTests.scala | 4 ++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index ae79438c594f..2cd1ea566bee 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -108,15 +108,15 @@ object ParseResult { @sharable private[this] val CommandExtract = """(:[\S]+)\s*(.*)""".r - private def parseStats(sourceCode: String)(implicit ctx: Context): List[untpd.Tree] = { + private def parseStats(implicit ctx: Context): List[untpd.Tree] = { val parser = new Parser(ctx.source) val stats = parser.blockStatSeq() parser.accept(Tokens.EOF) stats } - /** Extract a `ParseResult` from the string `sourceCode` */ - def apply(sourceCode: String)(implicit state: State): ParseResult = + def apply(source: SourceFile)(implicit state: State): ParseResult = { + val sourceCode = source.content().mkString sourceCode match { case "" => Newline case CommandExtract(cmd, arg) => cmd match { @@ -132,10 +132,8 @@ object ParseResult { case _ => implicit val ctx: Context = state.context - val source = SourceFile.virtual(str.REPL_SESSION_LINE + (state.objectIndex + 1), sourceCode) - val reporter = newStoreReporter - val stats = parseStats(sourceCode)(state.context.fresh.setReporter(reporter).withSource(source)) + val stats = parseStats(state.context.fresh.setReporter(reporter).withSource(source)) if (reporter.hasErrors) SyntaxErrors( @@ -145,6 +143,10 @@ object ParseResult { else Parsed(source, stats) } + } + + def apply(sourceCode: String)(implicit state: State): ParseResult = + apply(SourceFile.virtual(str.REPL_SESSION_LINE + (state.objectIndex + 1), sourceCode)) /** Check if the input is incomplete * @@ -160,7 +162,7 @@ object ParseResult { val localCtx = ctx.fresh.setSource(source).setReporter(reporter) var needsMore = false reporter.withIncompleteHandler((_, _) => needsMore = true) { - parseStats(sourceCode)(localCtx) + parseStats(localCtx) } !reporter.hasErrors && needsMore } diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index 4641633cd132..85e74700c1d8 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -241,7 +241,7 @@ class ReplCompiler extends Compiler { PackageDef(Ident(nme.EMPTY_PACKAGE), List(wrapper)) } - ParseResult(expr)(state) match { + ParseResult(sourceFile)(state) match { case Parsed(_, trees) => wrap(trees).result case SyntaxErrors(_, reported, trees) => diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 9b426559fdbb..7db068879a79 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -124,4 +124,8 @@ class TabcompleteTests extends ReplTest { val comp = tabComplete("???.") assertEquals(Nil, comp) } + + @Test def moduleCompletion = fromInitialState { implicit s => + assertEquals(List("Predef"), tabComplete("object Foo { type T = Pre")) + } }