From a1a4b9847e3b3d1f63d5c04c57f561708d009a34 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Mar 2016 14:00:30 +0100 Subject: [PATCH 1/8] Initialize context in REPL This broke under the recent introduction of the JS backend, because now the context needs to be initialized before the platform can be selected. So invoking `doti` immediately gave an IllegalStateException. No big deal to fix, but it shows how sorely we are lacking REPL tests. --- src/dotty/tools/dotc/core/Contexts.scala | 2 +- src/dotty/tools/dotc/core/Definitions.scala | 2 +- src/dotty/tools/dotc/repl/CompilingInterpreter.scala | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 2fc958a49b3a..fd0cff94e157 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -544,7 +544,7 @@ object Contexts { */ def initialize()(implicit ctx: Context): Unit = { _platform = newPlatform - definitions.init + definitions.init() } def squashed(p: Phase): Phase = { diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 6f8a8f837ce0..d8c882d5c704 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -798,7 +798,7 @@ class Definitions { private[this] var _isInitialized = false private def isInitialized = _isInitialized - def init(implicit ctx: Context) = { + def init()(implicit ctx: Context) = { this.ctx = ctx if (!_isInitialized) { // force initialization of every symbol that is synthesized or hijacked by the compiler diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala index 7d1da141966c..dd7189ddf23f 100644 --- a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala +++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -60,6 +60,8 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit import ast.untpd._ import CompilingInterpreter._ + ictx.base.initialize()(ictx) + /** directory to save .class files to */ val virtualDirectory = if (ictx.settings.d.isDefault(ictx)) new VirtualDirectory("(memory)", None) From f08c741efb6b64d6df7bd02b5191617037f6be12 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Mar 2016 14:01:32 +0100 Subject: [PATCH 2/8] Fix handling of imports in REPL. They printed as raw trees which confused the REPL when wrapping subsequent liens with them. --- src/dotty/tools/dotc/repl/CompilingInterpreter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala index dd7189ddf23f..a7d3fbc1cc54 100644 --- a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala +++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -479,7 +479,7 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit addWrapper() if (handler.statement.isInstanceOf[Import]) - preamble.append(handler.statement.toString + ";\n") + preamble.append(handler.statement.show + ";\n") // give wildcard imports a import wrapper all to their own if (handler.importsWildcard) @@ -647,7 +647,7 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit private class ImportHandler(imp: Import) extends StatementHandler(imp) { override def resultExtractionCode(req: Request, code: PrintWriter): Unit = { - code.println("+ \"" + imp.toString + "\\n\"") + code.println("+ \"" + imp.show + "\\n\"") } def isWildcardSelector(tree: Tree) = tree match { From c938f00ad86dc526c46f30c6c8465552cc12b44b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Mar 2016 14:55:24 +0100 Subject: [PATCH 3/8] Get rid of indentCode It messed up parsing of multi-line strings and did not seem to have a purpose. Error messages print fine without it. --- .../tools/dotc/repl/CompilingInterpreter.scala | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala index a7d3fbc1cc54..bc898488dd74 100644 --- a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala +++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -177,7 +177,7 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit // if (prevRequests.isEmpty) // new Run(this) // initialize the compiler // (not sure this is needed) // parse - parse(indentCode(line)) match { + parse(line) match { case None => Interpreter.Incomplete case Some(Nil) => Interpreter.Error // parse error or empty input case Some(tree :: Nil) if tree.isTerm && !tree.isInstanceOf[Assign] => @@ -273,7 +273,7 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit // header for the wrapper object code.println("object " + objectName + " {") code.print(importsPreamble) - code.println(indentCode(toCompute)) + code.println(toCompute) handlers.foreach(_.extraCodeToEvaluate(this,code)) code.println(importsTrailer) //end the wrapper object @@ -736,20 +736,6 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit /** Clean up a string for output */ private def clean(str: String)(implicit ctx: Context) = truncPrintString(stripWrapperGunk(str)) - - /** Indent some code by the width of the scala> prompt. - * This way, compiler error messages read better. - */ - def indentCode(code: String) = { - val spaces = " " - - stringFrom(str => - for (line <- code.lines) { - str.print(spaces) - str.print(line + "\n") - str.flush() - }) - } } /** Utility methods for the Interpreter. */ From 57bde5b5c31b76c687649848bbe2207ebeb7a57d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Mar 2016 14:55:58 +0100 Subject: [PATCH 4/8] Get rid of prompt in Driver It's used only in Resident, where it should be defined. --- src/dotty/tools/dotc/Driver.scala | 2 -- src/dotty/tools/dotc/Resident.scala | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/Driver.scala b/src/dotty/tools/dotc/Driver.scala index 887274fa80c5..2e78854c1405 100644 --- a/src/dotty/tools/dotc/Driver.scala +++ b/src/dotty/tools/dotc/Driver.scala @@ -15,8 +15,6 @@ import scala.util.control.NonFatal */ abstract class Driver extends DotClass { - val prompt = "\ndotc> " - protected def newCompiler(implicit ctx: Context): Compiler protected def emptyReporter: Reporter = new StoreReporter(null) diff --git a/src/dotty/tools/dotc/Resident.scala b/src/dotty/tools/dotc/Resident.scala index 18bb2ff4fd4e..e1b62e4d0e29 100644 --- a/src/dotty/tools/dotc/Resident.scala +++ b/src/dotty/tools/dotc/Resident.scala @@ -31,6 +31,7 @@ class Resident extends Driver { private val quit = ":q" private val reset = ":reset" + private val prompt = "dotc> " private def getLine() = { Console.print(prompt) From e1fb19412c5dcc722e7df24e543aadf03a463c9a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Mar 2016 16:19:03 +0100 Subject: [PATCH 5/8] Add REPL tests --- test/dotc/tests.scala | 2 ++ test/test/CompilerTest.scala | 17 ++++++++++++++- test/test/TestREPL.scala | 40 ++++++++++++++++++++++++++++++++++++ tests/repl/import.check | 11 ++++++++++ tests/repl/multilines.check | 33 +++++++++++++++++++++++++++++ tests/repl/onePlusOne.check | 3 +++ 6 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 test/test/TestREPL.scala create mode 100644 tests/repl/import.check create mode 100644 tests/repl/multilines.check create mode 100644 tests/repl/onePlusOne.check diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 457116feb33f..062625c230f4 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -46,6 +46,7 @@ class tests extends CompilerTest { val negDir = testsDir + "neg/" val runDir = testsDir + "run/" val newDir = testsDir + "new/" + val replDir = testsDir + "repl/" val sourceDir = "./src/" val dottyDir = sourceDir + "dotty/" @@ -109,6 +110,7 @@ class tests extends CompilerTest { @Test def pos_859 = compileFile(posSpecialDir, "i859", scala2mode)(allowDeepSubtypes) @Test def new_all = compileFiles(newDir, twice) + @Test def repl_all = replFiles(replDir) @Test def neg_all = compileFiles(negDir, verbose = true, compileSubDirs = false) @Test def neg_typedIdents() = compileDir(negDir, "typedIdents") diff --git a/test/test/CompilerTest.scala b/test/test/CompilerTest.scala index ef2f719fcff1..1ca836133b54 100644 --- a/test/test/CompilerTest.scala +++ b/test/test/CompilerTest.scala @@ -5,12 +5,12 @@ import dotty.tools.dotc.{Main, Bench, Driver} import dotty.tools.dotc.reporting.Reporter import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.config.CompilerCommand +import dotty.tools.io.PlainFile import scala.collection.mutable.ListBuffer import scala.reflect.io.{ Path, Directory, File => SFile, AbstractFile } import scala.tools.partest.nest.{ FileManager, NestUI } import scala.annotation.tailrec import java.io.{ RandomAccessFile, File => JFile } -import dotty.tools.io.PlainFile import org.junit.Test @@ -205,7 +205,22 @@ abstract class CompilerTest { } } + def replFile(prefix: String, fileName: String): Unit = { + val path = s"$prefix$fileName" + val f = new PlainFile(path) + val repl = new TestREPL(new String(f.toCharArray)) + repl.process(Array[String]()) + repl.check() + } + def replFiles(path: String): Unit = { + val dir = Directory(path) + val fileNames = dir.files.toArray.map(_.jfile.getName).filter(_ endsWith ".check") + for (name <- fileNames) { + log(s"testing $path$name") + replFile(path, name) + } + } // ========== HELPERS ============= diff --git a/test/test/TestREPL.scala b/test/test/TestREPL.scala new file mode 100644 index 000000000000..a9978cdde48a --- /dev/null +++ b/test/test/TestREPL.scala @@ -0,0 +1,40 @@ +package test + +import dotty.tools.dotc.repl._ +import dotty.tools.dotc.core.Contexts.Context +import collection.mutable +import java.io.StringWriter + + +class TestREPL(script: String) extends REPL { + + private val prompt = "scala> " + private val continuationPrompt = " | " + + private val out = new StringWriter() + override val output = new NewLinePrintWriter(out) + + override def input(implicit ctx: Context) = new InteractiveReader { + val lines = script.lines + def readLine(prompt: String): String = { + val line = lines.next + if (line.startsWith(prompt) || line.startsWith(continuationPrompt)) { + output.println(line) + line.drop(prompt.length) + } + else readLine(prompt) + } + val interactive = false + } + + def check() = { + out.close() + val printed = out.toString + val transcript = printed.drop(printed.indexOf(prompt)) + if (transcript.toString != script) { + println("input differs from transcript:") + println(transcript) + assert(false) + } + } +} \ No newline at end of file diff --git a/tests/repl/import.check b/tests/repl/import.check new file mode 100644 index 000000000000..ccaa521903c0 --- /dev/null +++ b/tests/repl/import.check @@ -0,0 +1,11 @@ +scala> import collection.mutable._ +import collection.mutable._ +scala> val buf = new ListBuffer[Int] +buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() +scala> buf += 22 +res0: scala.collection.mutable.ListBuffer[Int] = ListBuffer(22) +scala> buf ++= List(1, 2, 3) +res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(22, 1, 2, 3) +scala> buf.toList +res2: scala.collection.immutable.List[Int] = List(22, 1, 2, 3) +scala> :quit diff --git a/tests/repl/multilines.check b/tests/repl/multilines.check new file mode 100644 index 000000000000..3bc32707ebd3 --- /dev/null +++ b/tests/repl/multilines.check @@ -0,0 +1,33 @@ +scala> val x = """alpha + | + | omega""" +x: String = +alpha + +omega +scala> val y = """abc + | |def + | |ghi + | """.stripMargin +y: String = +abc +def +ghi + +scala> val z = { + | def square(x: Int) = x * x + | val xs = List(1, 2, 3) + | square(xs) + | } +:8: error: type mismatch: + found : scala.collection.immutable.List[Int](xs) + required: Int + square(xs) + ^ +scala> val z = { + | def square(x: Int) = x * x + | val xs = List(1, 2, 3) + | xs.map(square) + | } +z: scala.collection.immutable.List[Int] = List(1, 4, 9) +scala> :quit diff --git a/tests/repl/onePlusOne.check b/tests/repl/onePlusOne.check new file mode 100644 index 000000000000..9db6e6817c99 --- /dev/null +++ b/tests/repl/onePlusOne.check @@ -0,0 +1,3 @@ +scala> 1+1 +res0: Int = 2 +scala> :quit From d8a7a59ef96127ca64f27e0cc2529d775b1fa9c9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Mar 2016 16:57:10 +0100 Subject: [PATCH 6/8] Move all overridable bits into Config class Central config class replaces mixture of parameters and fields. The fields were in part in the wrong class, where they could not easily be overridden. --- .../tools/dotc/repl/InterpreterLoop.scala | 52 +++++++++---------- src/dotty/tools/dotc/repl/REPL.scala | 34 +++++++----- test/test/TestREPL.scala | 29 +++++------ 3 files changed, 60 insertions(+), 55 deletions(-) diff --git a/src/dotty/tools/dotc/repl/InterpreterLoop.scala b/src/dotty/tools/dotc/repl/InterpreterLoop.scala index eedec3c8282d..4ac9602e763c 100644 --- a/src/dotty/tools/dotc/repl/InterpreterLoop.scala +++ b/src/dotty/tools/dotc/repl/InterpreterLoop.scala @@ -21,10 +21,10 @@ import scala.concurrent.ExecutionContext.Implicits.global * @author Lex Spoon * @author Martin Odersky */ -class InterpreterLoop( - compiler: Compiler, - private var in: InteractiveReader, - out: PrintWriter)(implicit ctx: Context) { +class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Context) { + import config._ + + private var in = input val interpreter = compiler.asInstanceOf[Interpreter] @@ -52,24 +52,20 @@ class InterpreterLoop( /** print a friendly help message */ def printHelp(): Unit = { printWelcome() - out.println("Type :load followed by a filename to load a Scala file.") - out.println("Type :replay to reset execution and replay all previous commands.") - out.println("Type :quit to exit the interpreter.") + output.println("Type :load followed by a filename to load a Scala file.") + output.println("Type :replay to reset execution and replay all previous commands.") + output.println("Type :quit to exit the interpreter.") } /** Print a welcome message */ def printWelcome(): Unit = { - out.println(s"Welcome to Scala$version " + " (" + + output.println(s"Welcome to Scala$version " + " (" + System.getProperty("java.vm.name") + ", Java " + System.getProperty("java.version") + ")." ) - out.println("Type in expressions to have them evaluated.") - out.println("Type :help for more information.") - out.flush() + output.println("Type in expressions to have them evaluated.") + output.println("Type :help for more information.") + output.flush() } - /** Prompt to print when awaiting input */ - val prompt = "scala> " - val continuationPrompt = " | " - val version = ".next (pre-alpha)" /** The first interpreted command always takes a couple of seconds @@ -92,7 +88,7 @@ class InterpreterLoop( val (keepGoing, finalLineOpt) = command(line) if (keepGoing) { finalLineOpt.foreach(addReplay) - out.flush() + output.flush() repl() } } @@ -103,16 +99,16 @@ class InterpreterLoop( new FileReader(filename) } catch { case _: IOException => - out.println("Error opening file: " + filename) + output.println("Error opening file: " + filename) return } val oldIn = in val oldReplay = replayCommandsRev try { val inFile = new BufferedReader(fileIn) - in = new SimpleReader(inFile, out, false) - out.println("Loading " + filename + "...") - out.flush + in = new SimpleReader(inFile, output, false) + output.println("Loading " + filename + "...") + output.flush repl() } finally { in = oldIn @@ -124,10 +120,10 @@ class InterpreterLoop( /** create a new interpreter and replay all commands so far */ def replay(): Unit = { for (cmd <- replayCommands) { - out.println("Replaying: " + cmd) - out.flush() // because maybe cmd will have its own output + output.println("Replaying: " + cmd) + output.flush() // because maybe cmd will have its own output command(cmd) - out.println + output.println } } @@ -138,12 +134,12 @@ class InterpreterLoop( def withFile(command: String)(action: String => Unit): Unit = { val spaceIdx = command.indexOf(' ') if (spaceIdx <= 0) { - out.println("That command requires a filename to be specified.") + output.println("That command requires a filename to be specified.") return } val filename = command.substring(spaceIdx).trim if (!new File(filename).exists) { - out.println("That file does not exist") + output.println("That file does not exist") return } action(filename) @@ -169,7 +165,7 @@ class InterpreterLoop( else if (line matches replayRegexp) replay() else if (line startsWith ":") - out.println("Unknown command. Type :help for help.") + output.println("Unknown command. Type :help for help.") else shouldReplay = interpretStartingWith(line) @@ -188,7 +184,7 @@ class InterpreterLoop( case Interpreter.Error => None case Interpreter.Incomplete => if (in.interactive && code.endsWith("\n\n")) { - out.println("You typed two blank lines. Starting a new command.") + output.println("You typed two blank lines. Starting a new command.") None } else { val nextLine = in.readLine(continuationPrompt) @@ -207,7 +203,7 @@ class InterpreterLoop( val cmd = ":load " + filename command(cmd) replayCommandsRev = cmd :: replayCommandsRev - out.println() + output.println() } case _ => } diff --git a/src/dotty/tools/dotc/repl/REPL.scala b/src/dotty/tools/dotc/repl/REPL.scala index 2d6a3c742227..e5ff2d3afd5e 100644 --- a/src/dotty/tools/dotc/repl/REPL.scala +++ b/src/dotty/tools/dotc/repl/REPL.scala @@ -23,27 +23,37 @@ import java.io.{BufferedReader, File, FileReader, PrintWriter} */ class REPL extends Driver { - /** The default input reader */ - def input(implicit ctx: Context): InteractiveReader = { - val emacsShell = System.getProperty("env.emacs", "") != "" - //println("emacsShell="+emacsShell) //debug - if (ctx.settings.Xnojline.value || emacsShell) new SimpleReader() - else InteractiveReader.createDefault() - } - - /** The defult output writer */ - def output: PrintWriter = new NewLinePrintWriter(new ConsoleWriter, true) + lazy val config = new REPL.Config override def newCompiler(implicit ctx: Context): Compiler = - new repl.CompilingInterpreter(output, ctx) + new repl.CompilingInterpreter(config.output, ctx) override def sourcesRequired = false override def doCompile(compiler: Compiler, fileNames: List[String])(implicit ctx: Context): Reporter = { if (fileNames.isEmpty) - new InterpreterLoop(compiler, input, output).run() + new InterpreterLoop(compiler, config).run() else ctx.error(s"don't now what to do with $fileNames%, %") ctx.reporter } } + +object REPL { + class Config { + val prompt = "scala> " + val continuationPrompt = " | " + val version = ".next (pre-alpha)" + + /** The default input reader */ + def input(implicit ctx: Context): InteractiveReader = { + val emacsShell = System.getProperty("env.emacs", "") != "" + //println("emacsShell="+emacsShell) //debug + if (ctx.settings.Xnojline.value || emacsShell) new SimpleReader() + else InteractiveReader.createDefault() + } + + /** The default output writer */ + def output: PrintWriter = new NewLinePrintWriter(new ConsoleWriter, true) + } +} diff --git a/test/test/TestREPL.scala b/test/test/TestREPL.scala index a9978cdde48a..30dad2b642c8 100644 --- a/test/test/TestREPL.scala +++ b/test/test/TestREPL.scala @@ -5,32 +5,31 @@ import dotty.tools.dotc.core.Contexts.Context import collection.mutable import java.io.StringWriter - class TestREPL(script: String) extends REPL { - private val prompt = "scala> " - private val continuationPrompt = " | " - private val out = new StringWriter() - override val output = new NewLinePrintWriter(out) - override def input(implicit ctx: Context) = new InteractiveReader { - val lines = script.lines - def readLine(prompt: String): String = { - val line = lines.next - if (line.startsWith(prompt) || line.startsWith(continuationPrompt)) { - output.println(line) - line.drop(prompt.length) + override lazy val config = new REPL.Config { + override val output = new NewLinePrintWriter(out) + + override def input(implicit ctx: Context) = new InteractiveReader { + val lines = script.lines + def readLine(prompt: String): String = { + val line = lines.next + if (line.startsWith(prompt) || line.startsWith(continuationPrompt)) { + output.println(line) + line.drop(prompt.length) + } + else readLine(prompt) } - else readLine(prompt) + val interactive = false } - val interactive = false } def check() = { out.close() val printed = out.toString - val transcript = printed.drop(printed.indexOf(prompt)) + val transcript = printed.drop(printed.indexOf(config.prompt)) if (transcript.toString != script) { println("input differs from transcript:") println(transcript) From a7a0543d4e98319e70acc44510071ce2576077b8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 18 Mar 2016 11:33:14 +0100 Subject: [PATCH 7/8] Add docs to TestREPL --- test/test/TestREPL.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test/TestREPL.scala b/test/test/TestREPL.scala index 30dad2b642c8..d01038c434f6 100644 --- a/test/test/TestREPL.scala +++ b/test/test/TestREPL.scala @@ -5,6 +5,14 @@ import dotty.tools.dotc.core.Contexts.Context import collection.mutable import java.io.StringWriter +/** A subclass of REPL used for testing. + * It takes a transcript of a REPL session in `script`. The transcript + * starts with the first input prompt `scala> ` and ends with `scala> :quit` and a newline. + * Invoking `process()` on the `TestREPL` runs all input lines and + * collects then interleaved with REPL output in a string writer `out`. + * Invoking `check()` checks that the collected output matches the original + * `script`. + */ class TestREPL(script: String) extends REPL { private val out = new StringWriter() From ef8c1968b2ea407c5b2ddca2fef00eb922e81f8e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 18 Mar 2016 11:33:34 +0100 Subject: [PATCH 8/8] Add test file --- tests/repl/imports.check | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/repl/imports.check diff --git a/tests/repl/imports.check b/tests/repl/imports.check new file mode 100644 index 000000000000..3fa10328348a --- /dev/null +++ b/tests/repl/imports.check @@ -0,0 +1,24 @@ +scala> import scala.collection.mutable +import scala.collection.mutable +scala> val buf = mutable.ListBuffer[Int]() +buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() +scala> object o { + | val xs = List(1, 2, 3) + | } +defined module o +scala> import o._ +import o._ +scala> buf += xs +:11: error: type mismatch: + found : scala.collection.immutable.List[Int](o.xs) + required: String +buf += xs + ^ +:11: error: type mismatch: + found : String + required: scala.collection.mutable.ListBuffer[Int] +buf += xs +^ +scala> buf ++= xs +res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) +scala> :quit