aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dotty/tools/backend/jvm/DottyBackendInterface.scala4
-rw-r--r--src/dotty/tools/backend/jvm/GenBCode.scala5
-rw-r--r--src/dotty/tools/dotc/Bench.scala2
-rw-r--r--src/dotty/tools/dotc/Compiler.scala6
-rw-r--r--src/dotty/tools/dotc/Driver.scala4
-rw-r--r--src/dotty/tools/dotc/FromTasty.scala2
-rw-r--r--src/dotty/tools/dotc/Main.scala3
-rw-r--r--src/dotty/tools/dotc/Resident.scala2
-rw-r--r--src/dotty/tools/dotc/Run.scala3
-rw-r--r--src/dotty/tools/dotc/config/ScalaSettings.scala2
-rw-r--r--src/dotty/tools/dotc/core/Denotations.scala5
-rw-r--r--src/dotty/tools/dotc/core/Flags.scala1
-rw-r--r--src/dotty/tools/dotc/core/NameOps.scala7
-rw-r--r--src/dotty/tools/dotc/core/Phases.scala7
-rw-r--r--src/dotty/tools/dotc/core/SymDenotations.scala57
-rw-r--r--src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala31
-rw-r--r--src/dotty/tools/dotc/repl/CompilingInterpreter.scala811
-rw-r--r--src/dotty/tools/dotc/repl/ConsoleWriter.scala21
-rw-r--r--src/dotty/tools/dotc/repl/InteractiveReader.scala31
-rw-r--r--src/dotty/tools/dotc/repl/Interpreter.scala36
-rw-r--r--src/dotty/tools/dotc/repl/InterpreterLoop.scala228
-rw-r--r--src/dotty/tools/dotc/repl/Main.scala29
-rw-r--r--src/dotty/tools/dotc/repl/NewLinePrintWriter.scala11
-rw-r--r--src/dotty/tools/dotc/repl/REPL.scala49
-rw-r--r--src/dotty/tools/dotc/repl/SimpleReader.scala23
-rw-r--r--src/dotty/tools/dotc/transform/DropEmptyCompanions.scala98
-rw-r--r--src/dotty/tools/dotc/transform/Flatten.scala1
-rw-r--r--src/dotty/tools/dotc/transform/LazyVals.scala12
-rw-r--r--src/dotty/tools/dotc/transform/RestoreScopes.scala67
-rw-r--r--src/dotty/tools/dotc/typer/Namer.scala2
-rw-r--r--src/dotty/tools/dotc/typer/RefChecks.scala14
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala2
-rw-r--r--test/dotc/tests.scala1
-rw-r--r--tests/neg/valueClasses.scala10
-rw-r--r--tests/pending/run/t920.scala25
-rw-r--r--tests/run/t920.scala8
36 files changed, 1556 insertions, 64 deletions
diff --git a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala
index b6adba85a..5776cc8e2 100644
--- a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala
+++ b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala
@@ -32,7 +32,7 @@ import NameOps._
import StdNames.nme
import NameOps._
-class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{
+class DottyBackendInterface(outputDirectory: AbstractFile)(implicit ctx: Context) extends BackendInterface{
type Symbol = Symbols.Symbol
type Type = Types.Type
type Tree = tpd.Tree
@@ -734,7 +734,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{
def setter(clz: Symbol): Symbol = decorateSymbol(sym).setter
def moduleSuffix: String = "" // todo: validate that names already have $ suffix
- def outputDirectory: AbstractFile = new PlainDirectory(new Directory(new JFile(ctx.settings.d.value)))
+ def outputDirectory: AbstractFile = DottyBackendInterface.this.outputDirectory
def pos: Position = sym.pos
def throwsAnnotations: List[Symbol] = Nil
diff --git a/src/dotty/tools/backend/jvm/GenBCode.scala b/src/dotty/tools/backend/jvm/GenBCode.scala
index e8d196ce7..2d444d3be 100644
--- a/src/dotty/tools/backend/jvm/GenBCode.scala
+++ b/src/dotty/tools/backend/jvm/GenBCode.scala
@@ -29,6 +29,7 @@ import scala.tools.asm.tree._
import dotty.tools.dotc.util.{Positions, DotClass}
import tpd._
import StdNames._
+import scala.reflect.io.{Directory, PlainDirectory, AbstractFile}
import scala.tools.nsc.backend.jvm.opt.LocalOpt
@@ -37,9 +38,11 @@ class GenBCode extends Phase {
private val entryPoints = new mutable.HashSet[Symbol]()
def registerEntryPoint(sym: Symbol) = entryPoints += sym
+ def outputDir(implicit ctx: Context): AbstractFile =
+ new PlainDirectory(new Directory(new JFile(ctx.settings.d.value)))
def run(implicit ctx: Context): Unit = {
- new GenBCodePipeline(entryPoints.toList, new DottyBackendInterface()(ctx))(ctx).run(ctx.compilationUnit.tpdTree)
+ new GenBCodePipeline(entryPoints.toList, new DottyBackendInterface(outputDir)(ctx))(ctx).run(ctx.compilationUnit.tpdTree)
entryPoints.clear()
}
}
diff --git a/src/dotty/tools/dotc/Bench.scala b/src/dotty/tools/dotc/Bench.scala
index 47b5fd6dd..2fc38d78c 100644
--- a/src/dotty/tools/dotc/Bench.scala
+++ b/src/dotty/tools/dotc/Bench.scala
@@ -12,7 +12,7 @@ object Bench extends Driver {
@sharable private var numRuns = 1
- def newCompiler(): Compiler = new Compiler
+ def newCompiler(implicit ctx: Context): Compiler = new Compiler
private def ntimes(n: Int)(op: => Reporter): Reporter =
(emptyReporter /: (0 until n)) ((_, _) => op)
diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala
index 199657864..99b7bd880 100644
--- a/src/dotty/tools/dotc/Compiler.scala
+++ b/src/dotty/tools/dotc/Compiler.scala
@@ -74,13 +74,13 @@ class Compiler {
new CapturedVars, // capturedVars has a transformUnit: no phases should introduce local mutable vars here
new Constructors, // constructors changes decls in transformTemplate, no InfoTransformers should be added after it
new FunctionalInterfaces,
- new GetClass), // getClass transformation should be applied to specialized methods
+ new GetClass), // getClass transformation should be applied to specialized methods
List(new LambdaLift, // in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here
new ElimStaticThis,
new Flatten,
+ new DropEmptyCompanions,
new RestoreScopes),
- List(/*new PrivateToStatic,*/
- new ExpandPrivate,
+ List(new ExpandPrivate,
new CollectEntryPoints,
new LabelDefs),
List(new GenBCode)
diff --git a/src/dotty/tools/dotc/Driver.scala b/src/dotty/tools/dotc/Driver.scala
index 7f22fc774..3437b86fc 100644
--- a/src/dotty/tools/dotc/Driver.scala
+++ b/src/dotty/tools/dotc/Driver.scala
@@ -10,7 +10,7 @@ abstract class Driver extends DotClass {
val prompt = "\ndotc> "
- protected def newCompiler(): Compiler
+ protected def newCompiler(implicit ctx: Context): Compiler
protected def emptyReporter: Reporter = new StoreReporter(null)
@@ -90,7 +90,7 @@ abstract class Driver extends DotClass {
*/
def process(args: Array[String], rootCtx: Context): Reporter = {
val (fileNames, ctx) = setup(args, rootCtx)
- doCompile(newCompiler(), fileNames)(ctx)
+ doCompile(newCompiler(ctx), fileNames)(ctx)
}
def main(args: Array[String]): Unit = {
diff --git a/src/dotty/tools/dotc/FromTasty.scala b/src/dotty/tools/dotc/FromTasty.scala
index d8d8b8b1e..8f29c882c 100644
--- a/src/dotty/tools/dotc/FromTasty.scala
+++ b/src/dotty/tools/dotc/FromTasty.scala
@@ -30,7 +30,7 @@ import ast.tpd._
* scala dotty.tools.dotc.FromTasty -Xprint:front extMethods.T
*/
object FromTasty extends Driver {
- override def newCompiler(): Compiler = new TASTYCompiler
+ override def newCompiler(implicit ctx: Context): Compiler = new TASTYCompiler
class TASTYCompiler extends Compiler {
diff --git a/src/dotty/tools/dotc/Main.scala b/src/dotty/tools/dotc/Main.scala
index 699a57234..6c473d8bb 100644
--- a/src/dotty/tools/dotc/Main.scala
+++ b/src/dotty/tools/dotc/Main.scala
@@ -2,10 +2,9 @@ package dotty.tools
package dotc
import core.Contexts.Context
-import reporting.Reporter
/* To do:
*/
object Main extends Driver {
- override def newCompiler(): Compiler = new Compiler
+ override def newCompiler(implicit ctx: Context): Compiler = new Compiler
}
diff --git a/src/dotty/tools/dotc/Resident.scala b/src/dotty/tools/dotc/Resident.scala
index 3ae369f27..18bb2ff4f 100644
--- a/src/dotty/tools/dotc/Resident.scala
+++ b/src/dotty/tools/dotc/Resident.scala
@@ -25,7 +25,7 @@ class Resident extends Driver {
object residentCompiler extends Compiler
- override def newCompiler(): Compiler = ???
+ override def newCompiler(implicit ctx: Context): Compiler = ???
override def sourcesRequired = false
diff --git a/src/dotty/tools/dotc/Run.scala b/src/dotty/tools/dotc/Run.scala
index ba86e3e70..39fd42a64 100644
--- a/src/dotty/tools/dotc/Run.scala
+++ b/src/dotty/tools/dotc/Run.scala
@@ -82,6 +82,9 @@ class Run(comp: Compiler)(implicit ctx: Context) {
compileSources(List(new SourceFile(virtualFile)))
}
+ /** The context created for this run */
+ def runContext = ctx
+
/** Print summary; return # of errors encountered */
def printSummary(): Reporter = {
ctx.runInfo.printMaxConstraint()
diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala
index 035b20130..65bc9ba23 100644
--- a/src/dotty/tools/dotc/config/ScalaSettings.scala
+++ b/src/dotty/tools/dotc/config/ScalaSettings.scala
@@ -89,7 +89,7 @@ class ScalaSettings extends Settings.SettingGroup {
val showPhases = BooleanSetting("-Xshow-phases", "Print a synopsis of compiler phases.")
val sourceReader = StringSetting("-Xsource-reader", "classname", "Specify a custom method for reading source files.", "")
val XnoValueClasses = BooleanSetting("-Xno-value-classes", "Do not use value classes. Helps debugging.")
-
+ val XreplLineWidth = IntSetting("-Xrepl-line-width", "Maximial number of columns per line for REPL output", 390)
val XoldPatmat = BooleanSetting("-Xoldpatmat", "Use the pre-2.10 pattern matcher. Otherwise, the 'virtualizing' pattern matcher is used in 2.10.")
val XnoPatmatAnalysis = BooleanSetting("-Xno-patmat-analysis", "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation.")
val XfullLubs = BooleanSetting("-Xfull-lubs", "Retains pre 2.10 behavior of less aggressive truncation of least upper bounds.")
diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala
index 2af3f463d..b52c11201 100644
--- a/src/dotty/tools/dotc/core/Denotations.scala
+++ b/src/dotty/tools/dotc/core/Denotations.scala
@@ -590,7 +590,10 @@ object Denotations {
} while (d ne denot)
this
case _ =>
- if (coveredInterval.containsPhaseId(ctx.phaseId)) staleSymbolError
+ if (coveredInterval.containsPhaseId(ctx.phaseId)) {
+ if (ctx.debug) ctx.traceInvalid(this)
+ staleSymbolError
+ }
else NoDenotation
}
diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala
index 8c9db3a5c..02b341649 100644
--- a/src/dotty/tools/dotc/core/Flags.scala
+++ b/src/dotty/tools/dotc/core/Flags.scala
@@ -606,6 +606,7 @@ object Flags {
final val AbstractFinal = allOf(Abstract, Final)
final val AbstractSealed = allOf(Abstract, Sealed)
final val SyntheticArtifact = allOf(Synthetic, Artifact)
+ final val SyntheticModule = allOf(Synthetic, Module)
final val SyntheticTermParam = allOf(Synthetic, TermParam)
final val SyntheticTypeParam = allOf(Synthetic, TypeParam)
final val SyntheticCase = allOf(Synthetic, Case)
diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala
index 6c1930c9f..81240a9fc 100644
--- a/src/dotty/tools/dotc/core/NameOps.scala
+++ b/src/dotty/tools/dotc/core/NameOps.scala
@@ -82,6 +82,7 @@ object NameOps {
def isModuleVarName(name: Name): Boolean =
name.stripAnonNumberSuffix endsWith MODULE_VAR_SUFFIX
def isSelectorName = name.startsWith(" ") && name.tail.forall(_.isDigit)
+ def isLazyLocal = name.endsWith(nme.LAZY_LOCAL)
/** Is name a variable name? */
def isVariableName: Boolean = name.length > 0 && {
@@ -423,5 +424,11 @@ object NameOps {
case NO_NAME => primitivePostfixMethodName
case name => name
}
+
+ def lazyLocalName = name ++ nme.LAZY_LOCAL
+ def nonLazyName = {
+ assert(name.isLazyLocal)
+ name.dropRight(nme.LAZY_LOCAL.length)
+ }
}
}
diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala
index 970a9297a..83ac64d53 100644
--- a/src/dotty/tools/dotc/core/Phases.scala
+++ b/src/dotty/tools/dotc/core/Phases.scala
@@ -347,6 +347,13 @@ object Phases {
override def toString = phaseName
}
+ /** Replace all instances of `oldPhaseClass` in `current` phases
+ * by the result of `newPhases` applied to the old phase.
+ */
+ def replace(oldPhaseClass: Class[_ <: Phase], newPhases: Phase => List[Phase], current: List[List[Phase]]): List[List[Phase]] =
+ current.map(_.flatMap(phase =>
+ if (oldPhaseClass.isInstance(phase)) newPhases(phase) else phase :: Nil))
+
/** Dotty deviation: getClass yields Class[_], instead of [Class <: <type of receiver>].
* We can get back the old behavior using this decorator. We should also use the same
* trick for standard getClass.
diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala
index 4495b4096..705e1a4fa 100644
--- a/src/dotty/tools/dotc/core/SymDenotations.scala
+++ b/src/dotty/tools/dotc/core/SymDenotations.scala
@@ -45,19 +45,54 @@ trait SymDenotations { this: Context =>
if (denot.is(ValidForever) || denot.isRefinementClass) true
else {
val initial = denot.initial
- if (initial ne denot)
- ctx.withPhase(initial.validFor.firstPhaseId).stillValid(initial.asSymDenotation)
- else try {
- val owner = denot.owner.denot
- stillValid(owner) && (
- !owner.isClass
- || owner.isRefinementClass
- || (owner.unforcedDecls.lookupAll(denot.name) contains denot.symbol)
- || denot.isSelfSym)
- } catch {
- case ex: StaleSymbol => false
+ val firstPhaseId = initial.validFor.firstPhaseId.max(ctx.typerPhase.id)
+ if ((initial ne denot) || ctx.phaseId != firstPhaseId)
+ ctx.withPhase(firstPhaseId).stillValidInOwner(initial.asSymDenotation)
+ else
+ stillValidInOwner(denot)
+ }
+
+ private[SymDenotations] def stillValidInOwner(denot: SymDenotation): Boolean = try {
+ val owner = denot.owner.denot
+ stillValid(owner) && (
+ !owner.isClass
+ || owner.isRefinementClass
+ || (owner.unforcedDecls.lookupAll(denot.name) contains denot.symbol)
+ || denot.isSelfSym)
+ } catch {
+ case ex: StaleSymbol => false
+ }
+
+ /** Explain why symbol is invalid; used for debugging only */
+ def traceInvalid(denot: Denotation): Boolean = {
+ def show(d: Denotation) = s"$d#${d.symbol.id}"
+ def explain(msg: String) = {
+ println(s"${show(denot)} is invalid at ${this.period} because $msg")
+ false
+ }
+ denot match {
+ case denot: SymDenotation =>
+ def explainSym(msg: String) = explain(s"$msg\n defined = ${denot.definedPeriodsString}")
+ if (denot.is(ValidForever) || denot.isRefinementClass) true
+ else {
+ implicit val ctx: Context = this
+ val initial = denot.initial
+ if ((initial ne denot) || ctx.phaseId != initial.validFor.firstPhaseId) {
+ ctx.withPhase(initial.validFor.firstPhaseId).traceInvalid(initial.asSymDenotation)
+ } else try {
+ val owner = denot.owner.denot
+ if (!traceInvalid(owner)) explainSym("owner is invalid")
+ else if (!owner.isClass || owner.isRefinementClass || denot.isSelfSym) true
+ else if (owner.unforcedDecls.lookupAll(denot.name) contains denot.symbol) true
+ else explainSym(s"decls of ${show(owner)} are ${owner.unforcedDecls.lookupAll(denot.name).toList}, do not contain ${denot.symbol}")
+ } catch {
+ case ex: StaleSymbol => explainSym(s"$ex was thrown")
+ }
}
+ case _ =>
+ explain("denotation is not a SymDenotation")
}
+ }
}
object SymDenotations {
diff --git a/src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala b/src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala
new file mode 100644
index 000000000..a3a463717
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala
@@ -0,0 +1,31 @@
+package dotty.tools
+package dotc
+package repl
+
+import io.AbstractFile
+
+/**
+ * A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}.
+ *
+ * @author Lex Spoon
+ */
+class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader)
+extends ClassLoader(parent)
+{
+ override def findClass(name: String): Class[_] = {
+ var file: AbstractFile = root
+ val pathParts = name.split("[./]").toList
+ for (dirPart <- pathParts.init) {
+ file = file.lookupName(dirPart, true)
+ if (file == null) {
+ throw new ClassNotFoundException(name)
+ }
+ }
+ file = file.lookupName(pathParts.last+".class", false)
+ if (file == null) {
+ throw new ClassNotFoundException(name)
+ }
+ val bytes = file.toByteArray
+ defineClass(name, bytes, 0, bytes.length)
+ }
+}
diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
new file mode 100644
index 000000000..7d1da1419
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
@@ -0,0 +1,811 @@
+package dotty.tools
+package dotc
+package repl
+
+import java.io.{File, PrintWriter, StringWriter, Writer}
+import java.lang.{Class, ClassLoader}
+import java.net.{URL, URLClassLoader}
+
+import scala.collection.immutable.ListSet
+import scala.collection.mutable
+import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer}
+
+//import ast.parser.SyntaxAnalyzer
+import io.{PlainFile, VirtualDirectory}
+import scala.reflect.io.{PlainDirectory, Directory}
+import reporting.{ConsoleReporter, Reporter}
+import core.Flags
+import util.{SourceFile, NameTransformer}
+import io.ClassPath
+import ast.Trees._
+import parsing.Parsers._
+import core._
+import dotty.tools.backend.jvm.GenBCode
+import Symbols._, Types._, Contexts._, StdNames._, Names._, NameOps._
+import Decorators._
+import scala.util.control.NonFatal
+
+/** An interpreter for Scala code which is based on the `dotc` compiler.
+ *
+ * The overall approach is based on compiling the requested code and then
+ * using a Java classloader and Java reflection to run the code
+ * and access its results.
+ *
+ * In more detail, a single compiler instance is used
+ * to accumulate all successfully compiled or interpreted Scala code. To
+ * "interpret" a line of code, the compiler generates a fresh object that
+ * includes the line of code and which has public definition(s) to export
+ * all variables defined by that code. To extract the result of an
+ * interpreted line to show the user, a second "result object" is created
+ * which imports the variables exported by the above object and then
+ * exports a single definition named "result". To accommodate user expressions
+ * that read from variables or methods defined in previous statements, "import"
+ * statements are used.
+ *
+ * This interpreter shares the strengths and weaknesses of using the
+ * full compiler-to-Java. The main strength is that interpreted code
+ * behaves exactly as does compiled code, including running at full speed.
+ * The main weakness is that redefining classes and methods is not handled
+ * properly, because rebinding at the Java level is technically difficult.
+ *
+ * @author Moez A. Abdel-Gawad
+ * @author Lex Spoon
+ * @author Martin Odersky
+ *
+ * @param out The output to use for diagnostics
+ * @param ictx The context to use for initialization of the interpreter,
+ * needed to access the current classpath.
+ */
+class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler with Interpreter {
+ import ast.untpd._
+ import CompilingInterpreter._
+
+ /** directory to save .class files to */
+ val virtualDirectory =
+ if (ictx.settings.d.isDefault(ictx)) new VirtualDirectory("(memory)", None)
+ else new PlainDirectory(new Directory(new java.io.File(ictx.settings.d.value(ictx)))) // for now, to help debugging
+
+ /** A GenBCode phase that uses `virtualDirectory` for its output */
+ private class REPLGenBCode extends GenBCode {
+ override def outputDir(implicit ctx: Context) = virtualDirectory
+ }
+
+ /** Phases of this compiler use `REPLGenBCode` instead of `GenBCode`. */
+ override def phases = Phases.replace(
+ classOf[GenBCode], _ => new REPLGenBCode :: Nil, super.phases)
+
+ /** whether to print out result lines */
+ private var printResults: Boolean = true
+
+ /** Temporarily be quiet */
+ override def beQuietDuring[T](operation: => T): T = {
+ val wasPrinting = printResults
+ try {
+ printResults = false
+ operation
+ } finally {
+ printResults = wasPrinting
+ }
+ }
+
+ private def newReporter = new ConsoleReporter(Console.in, out) {
+ override def printMessage(msg: String) = {
+ out.print(/*clean*/(msg) + "\n")
+ // Suppress clean for now for compiler messages
+ // Otherwise we will completely delete all references to
+ // line$object$ module classes. The previous interpreter did not
+ // have the project because the module class was written without the final `$'
+ // and therefore escaped the purge. We can turn this back on once
+ // we drop the final `$' from module classes.
+ out.flush()
+ }
+ }
+
+ /** the previous requests this interpreter has processed */
+ private val prevRequests = new ArrayBuffer[Request]()
+
+ /** the compiler's classpath, as URL's */
+ val compilerClasspath: List[URL] = ictx.platform.classPath(ictx).asURLs
+
+ protected def parentClassLoader: ClassLoader = classOf[Interpreter].getClassLoader
+
+ /* A single class loader is used for all commands interpreted by this Interpreter.
+ It would also be possible to create a new class loader for each command
+ to interpret. The advantages of the current approach are:
+
+ - Expressions are only evaluated one time. This is especially
+ significant for I/O, e.g. "val x = Console.readLine"
+
+ The main disadvantage is:
+
+ - Objects, classes, and methods cannot be rebound. Instead, definitions
+ shadow the old ones, and old code objects refer to the old
+ definitions.
+ */
+ /** class loader used to load compiled code */
+ val classLoader: ClassLoader = {
+ val parent = new URLClassLoader(compilerClasspath.toArray, parentClassLoader)
+ new AbstractFileClassLoader(virtualDirectory, parent)
+ }
+
+ // Set the current Java "context" class loader to this interpreter's class loader
+ Thread.currentThread.setContextClassLoader(classLoader)
+
+ /** Parse a line into a sequence of trees. Returns None if the input is incomplete. */
+ private def parse(line: String)(implicit ctx: Context): Option[List[Tree]] = {
+ var justNeedsMore = false
+ val reporter = newReporter
+ reporter.withIncompleteHandler { _ => _ => justNeedsMore = true } {
+ // simple parse: just parse it, nothing else
+ def simpleParse(code: String)(implicit ctx: Context): List[Tree] = {
+ val source = new SourceFile("<console>", code.toCharArray())
+ val parser = new Parser(source)
+ val (selfDef, stats) = parser.templateStatSeq
+ stats
+ }
+ val trees = simpleParse(line)(ctx.fresh.setReporter(reporter))
+ if (reporter.hasErrors) {
+ Some(Nil) // the result did not parse, so stop
+ } else if (justNeedsMore) {
+ None
+ } else {
+ Some(trees)
+ }
+ }
+ }
+
+ /** Compile a SourceFile. Returns the root context of the run that compiled the file.
+ */
+ def compileSources(sources: List[SourceFile])(implicit ctx: Context): Context = {
+ val reporter = newReporter
+ val run = newRun(ctx.fresh.setReporter(reporter))
+ run.compileSources(sources)
+ run.runContext
+ }
+
+ /** Compile a string. Returns true if there are no
+ * compilation errors, or false otherwise.
+ */
+ def compileString(code: String)(implicit ctx: Context): Boolean = {
+ val runCtx = compileSources(List(new SourceFile("<script>", code.toCharArray)))
+ !runCtx.reporter.hasErrors
+ }
+
+ override def interpret(line: String)(implicit ctx: Context): Interpreter.Result = {
+ // if (prevRequests.isEmpty)
+ // new Run(this) // initialize the compiler // (not sure this is needed)
+ // parse
+ parse(indentCode(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] =>
+ interpret(s"val $newVarName =\n$line")
+ case Some(trees) =>
+ val req = new Request(line, newLineName)
+ if (!req.compile())
+ Interpreter.Error // an error happened during compilation, e.g. a type error
+ else {
+ val (interpreterResultString, succeeded) = req.loadAndRun()
+ if (printResults || !succeeded)
+ out.print(clean(interpreterResultString))
+ if (succeeded) {
+ prevRequests += req
+ Interpreter.Success
+ }
+ else Interpreter.Error
+ }
+ }
+ }
+
+ /** Trait collecting info about one of the statements of an interpreter request */
+ private trait StatementInfo {
+ /** The statement */
+ def statement: Tree
+
+ /** The names defined previously and referred to in the statement */
+ def usedNames: List[Name]
+
+ /** The names defined in the statement */
+ val boundNames: List[Name]
+
+ /** Statement is an import that contains a wildcard */
+ val importsWildcard: Boolean
+
+ /** The names imported by the statement (if it is an import clause) */
+ val importedNames: Seq[Name]
+
+ /** Statement defines an implicit calue or method */
+ val definesImplicit: Boolean
+ }
+
+ /** One line of code submitted by the user for interpretation */
+ private class Request(val line: String, val lineName: String)(implicit ctx: Context) {
+ private val trees = parse(line) match {
+ case Some(ts) => ts
+ case None => Nil
+ }
+
+ /** name to use for the object that will compute "line" */
+ private def objectName = lineName + INTERPRETER_WRAPPER_SUFFIX
+
+ /** name of the object that retrieves the result from the above object */
+ private def resultObjectName = "RequestResult$" + objectName
+
+ private def chooseHandler(stat: Tree): StatementHandler = stat match {
+ case stat: DefDef => new DefHandler(stat)
+ case stat: ValDef => new ValHandler(stat)
+ case stat @ Assign(Ident(_), _) => new AssignHandler(stat)
+ case stat: ModuleDef => new ModuleHandler(stat)
+ case stat: TypeDef if stat.isClassDef => new ClassHandler(stat)
+ case stat: TypeDef => new TypeAliasHandler(stat)
+ case stat: Import => new ImportHandler(stat)
+// case DocDef(_, documented) => chooseHandler(documented)
+ case stat => new GenericHandler(stat)
+ }
+
+ private val handlers: List[StatementHandler] = trees.map(chooseHandler)
+
+ /** all (public) names defined by these statements */
+ private val boundNames = ListSet(handlers.flatMap(_.boundNames): _*).toList
+
+ /** list of names used by this expression */
+ private val usedNames: List[Name] = handlers.flatMap(_.usedNames)
+
+ private val (importsPreamble, importsTrailer, accessPath) =
+ importsCode(usedNames.toSet)
+
+ /** Code to access a variable with the specified name */
+ private def fullPath(vname: String): String = s"$objectName$accessPath.`$vname`"
+
+ /** Code to access a variable with the specified name */
+ private def fullPath(vname: Name): String = fullPath(vname.toString)
+
+ /** the line of code to compute */
+ private def toCompute = line
+
+ /** generate the source code for the object that computes this request
+ * TODO Reformulate in a functional way
+ */
+ private def objectSourceCode: String =
+ stringFrom { code =>
+ // header for the wrapper object
+ code.println("object " + objectName + " {")
+ code.print(importsPreamble)
+ code.println(indentCode(toCompute))
+ handlers.foreach(_.extraCodeToEvaluate(this,code))
+ code.println(importsTrailer)
+ //end the wrapper object
+ code.println(";}")
+ }
+
+ /** Types of variables defined by this request. They are computed
+ after compilation of the main object */
+ private var typeOf: Map[Name, String] = _
+
+ /** generate source code for the object that retrieves the result
+ from objectSourceCode */
+ private def resultObjectSourceCode: String =
+ stringFrom(code => {
+ code.println("object " + resultObjectName)
+ code.println("{ val result: String = {")
+ code.println(objectName + accessPath + ";") // evaluate the object, to make sure its constructor is run
+ code.print("(\"\"") // print an initial empty string, so later code can
+ // uniformly be: + morestuff
+ handlers.foreach(_.resultExtractionCode(this, code))
+ code.println("\n)}")
+ code.println(";}")
+ })
+
+
+ /** Compile the object file. Returns whether the compilation succeeded.
+ * If all goes well, the "types" map is computed. */
+ def compile(): Boolean = {
+ val compileCtx = compileSources(
+ List(new SourceFile("<console>", objectSourceCode.toCharArray)))
+ !compileCtx.reporter.hasErrors && {
+ this.typeOf = findTypes(compileCtx)
+ val resultCtx = compileSources(
+ List(new SourceFile("<console>", resultObjectSourceCode.toCharArray)))
+ !resultCtx.reporter.hasErrors
+ }
+ }
+
+ /** Dig the types of all bound variables out of the compiler run.
+ * TODO: Change the interface so that we typecheck, and then transform
+ * directly. Treating the compiler as less of a blackbox will require
+ * much less magic here.
+ */
+ private def findTypes(implicit ctx: Context): Map[Name, String] = {
+ def valAndVarNames = handlers.flatMap(_.valAndVarNames)
+ def defNames = handlers.flatMap(_.defNames)
+
+ def getTypes(names: List[Name], nameMap: Name => Name): Map[Name, String] = {
+ /** the outermost wrapper object */
+ val outerResObjSym: Symbol =
+ defn.EmptyPackageClass.info.decl(objectName.toTermName).symbol
+
+ /** the innermost object inside the wrapper, found by
+ * following accessPath into the outer one. */
+ val resObjSym =
+ (accessPath.split("\\.")).foldLeft(outerResObjSym) { (sym,str) =>
+ if (str.isEmpty) sym
+ else
+ ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
+ sym.info.member(str.toTermName).symbol
+ }
+ }
+
+ names.foldLeft(Map.empty[Name,String]) { (map, name) =>
+ val rawType =
+ ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
+ resObjSym.info.member(name).info
+ }
+
+ // the types are all =>T; remove the =>
+ val cleanedType = rawType.widenExpr
+
+ map + (name ->
+ ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
+ cleanedType.show
+ })
+ }
+ }
+
+ val names1 = getTypes(valAndVarNames, n => n.toTermName.fieldName)
+ val names2 = getTypes(defNames, identity)
+ names1 ++ names2
+ }
+
+ /** load and run the code using reflection.
+ * @return A pair consisting of the run's result as a string, and
+ * a boolean indicating whether the run succeeded without throwing
+ * an exception.
+ */
+ def loadAndRun(): (String, Boolean) = {
+ val interpreterResultObject: Class[_] =
+ Class.forName(resultObjectName, true, classLoader)
+ val resultValMethod: java.lang.reflect.Method =
+ interpreterResultObject.getMethod("result")
+ try {
+ (resultValMethod.invoke(interpreterResultObject).toString, true)
+ } catch {
+ case NonFatal(ex) =>
+ def cause(ex: Throwable): Throwable =
+ if (ex.getCause eq null) ex else cause(ex.getCause)
+ val orig = cause(ex)
+ (stringFrom(str => orig.printStackTrace(str)), false)
+ }
+ }
+
+ /** Compute imports that allow definitions from previous
+ * requests to be visible in a new request. Returns
+ * three pieces of related code as strings:
+ *
+ * 1. A _preamble_: An initial code fragment that should go before
+ * the code of the new request.
+ *
+ * 2. A _trailer_: A code fragment that should go after the code
+ * of the new request.
+ *
+ * 3. An _access path_ which can be traversed to access
+ * any bindings inside code wrapped by #1 and #2 .
+ *
+ * The argument is a set of Names that need to be imported.
+ *
+ * Limitations: This method is not as precise as it could be.
+ * (1) It does not process wildcard imports to see what exactly
+ * they import.
+ * (2) If it imports any names from a request, it imports all
+ * of them, which is not really necessary.
+ * (3) It imports multiple same-named implicits, but only the
+ * last one imported is actually usable.
+ */
+ private def importsCode(wanted: Set[Name]): (String, String, String) = {
+ /** Narrow down the list of requests from which imports
+ * should be taken. Removes requests which cannot contribute
+ * useful imports for the specified set of wanted names.
+ */
+ def reqsToUse: List[(Request, StatementInfo)] = {
+ /** Loop through a list of StatementHandlers and select
+ * which ones to keep. 'wanted' is the set of
+ * names that need to be imported.
+ */
+ def select(reqs: List[(Request, StatementInfo)], wanted: Set[Name]): List[(Request, StatementInfo)] = {
+ reqs match {
+ case Nil => Nil
+
+ case (req, handler) :: rest =>
+ val keepit =
+ (handler.definesImplicit ||
+ handler.importsWildcard ||
+ handler.importedNames.exists(wanted.contains(_)) ||
+ handler.boundNames.exists(wanted.contains(_)))
+
+ val newWanted =
+ if (keepit) {
+ (wanted
+ ++ handler.usedNames
+ -- handler.boundNames
+ -- handler.importedNames)
+ } else {
+ wanted
+ }
+
+ val restToKeep = select(rest, newWanted)
+
+ if (keepit)
+ (req, handler) :: restToKeep
+ else
+ restToKeep
+ }
+ }
+
+ val rhpairs = for {
+ req <- prevRequests.toList.reverse
+ handler <- req.handlers
+ } yield (req, handler)
+
+ select(rhpairs, wanted).reverse
+ }
+
+ val preamble = new StringBuffer
+ val trailingBraces = new StringBuffer
+ val accessPath = new StringBuffer
+ val impname = INTERPRETER_IMPORT_WRAPPER
+ val currentImps = mutable.Set[Name]()
+
+ // add code for a new object to hold some imports
+ def addWrapper(): Unit = {
+ preamble.append("object " + impname + "{\n")
+ trailingBraces.append("}\n")
+ accessPath.append("." + impname)
+ currentImps.clear
+ }
+
+ addWrapper()
+
+ // loop through previous requests, adding imports
+ // for each one
+ for ((req, handler) <- reqsToUse) {
+ // If the user entered an import, then just use it
+
+ // add an import wrapping level if the import might
+ // conflict with some other import
+ if (handler.importsWildcard ||
+ currentImps.exists(handler.importedNames.contains))
+ if (!currentImps.isEmpty)
+ addWrapper()
+
+ if (handler.statement.isInstanceOf[Import])
+ preamble.append(handler.statement.toString + ";\n")
+
+ // give wildcard imports a import wrapper all to their own
+ if (handler.importsWildcard)
+ addWrapper()
+ else
+ currentImps ++= handler.importedNames
+
+ // For other requests, import each bound variable.
+ // import them explicitly instead of with _, so that
+ // ambiguity errors will not be generated. Also, quote
+ // the name of the variable, so that we don't need to
+ // handle quoting keywords separately.
+ for (imv <- handler.boundNames) {
+ if (currentImps.contains(imv))
+ addWrapper()
+ preamble.append("import ")
+ preamble.append(req.objectName + req.accessPath + ".`" + imv + "`;\n")
+ currentImps += imv
+ }
+ }
+
+ addWrapper() // Add one extra wrapper, to prevent warnings
+ // in the frequent case of redefining
+ // the value bound in the last interpreter
+ // request.
+
+ (preamble.toString, trailingBraces.toString, accessPath.toString)
+ }
+
+ // ------ Handlers ------------------------------------------
+
+ /** Class to handle one statement among all the statements included
+ * in a single interpreter request.
+ */
+ private sealed abstract class StatementHandler(val statement: Tree) extends StatementInfo {
+ val usedNames: List[Name] = {
+ val ivt = new UntypedTreeAccumulator[mutable.Set[Name]] {
+ override def apply(ns: mutable.Set[Name], tree: Tree)(implicit ctx: Context) =
+ tree match {
+ case Ident(name) => ns += name
+ case _ => foldOver(ns, tree)
+ }
+ }
+ ivt.foldOver(HashSet(), statement).toList
+ }
+ val boundNames: List[Name] = Nil
+ def valAndVarNames: List[Name] = Nil
+ def defNames: List[Name] = Nil
+ val importsWildcard = false
+ val importedNames: Seq[Name] = Nil
+ val definesImplicit = statement match {
+ case tree: MemberDef => tree.mods.is(Flags.Implicit)
+ case _ => false
+ }
+
+ def extraCodeToEvaluate(req: Request, code: PrintWriter) = {}
+ def resultExtractionCode(req: Request, code: PrintWriter) = {}
+ }
+
+ private class GenericHandler(statement: Tree) extends StatementHandler(statement)
+
+ private class ValHandler(statement: ValDef) extends StatementHandler(statement) {
+ override val boundNames = List(statement.name)
+ override def valAndVarNames = boundNames
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ val vname = statement.name
+ if (!statement.mods.is(Flags.AccessFlags) &&
+ !(isGeneratedVarName(vname.toString) &&
+ req.typeOf(vname.encode) == "Unit")) {
+ val prettyName = vname.decode
+ code.print(" + \"" + prettyName + ": " +
+ string2code(req.typeOf(vname)) +
+ " = \" + " +
+ " (if(" +
+ req.fullPath(vname) +
+ ".asInstanceOf[AnyRef] != null) " +
+ " ((if(" +
+ req.fullPath(vname) +
+ ".toString().contains('\\n')) " +
+ " \"\\n\" else \"\") + " +
+ req.fullPath(vname) + ".toString() + \"\\n\") else \"null\\n\") ")
+ }
+ }
+ }
+
+ private class DefHandler(defDef: DefDef) extends StatementHandler(defDef) {
+ override val boundNames = List(defDef.name)
+ override def defNames = boundNames
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ if (!defDef.mods.is(Flags.AccessFlags))
+ code.print("+\"" + string2code(defDef.name.toString) + ": " +
+ string2code(req.typeOf(defDef.name)) + "\\n\"")
+ }
+ }
+
+ private class AssignHandler(statement: Assign) extends StatementHandler(statement) {
+ val lhs = statement.lhs.asInstanceOf[Ident] // an unfortunate limitation
+
+ val helperName = newInternalVarName().toTermName
+ override val valAndVarNames = List(helperName)
+
+ override def extraCodeToEvaluate(req: Request, code: PrintWriter): Unit = {
+ code.println("val " + helperName + " = " + statement.lhs + ";")
+ }
+
+ /** Print out lhs instead of the generated varName */
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.print(" + \"" + lhs + ": " +
+ string2code(req.typeOf(helperName.encode)) +
+ " = \" + " +
+ string2code(req.fullPath(helperName))
+ + " + \"\\n\"")
+ }
+ }
+
+ private class ModuleHandler(module: ModuleDef) extends StatementHandler(module) {
+ override val boundNames = List(module.name)
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.println(" + \"defined module " +
+ string2code(module.name.toString)
+ + "\\n\"")
+ }
+ }
+
+ private class ClassHandler(classdef: TypeDef)
+ extends StatementHandler(classdef) {
+ override val boundNames =
+ List(classdef.name) :::
+ (if (classdef.mods.is(Flags.Case))
+ List(classdef.name.toTermName)
+ else
+ Nil)
+
+ // TODO: MemberDef.keyword does not include "trait";
+ // otherwise it could be used here
+ def keyword: String =
+ if (classdef.mods.is(Flags.Trait)) "trait" else "class"
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.print(
+ " + \"defined " +
+ keyword +
+ " " +
+ string2code(classdef.name.toString) +
+ "\\n\"")
+ }
+ }
+
+ private class TypeAliasHandler(typeDef: TypeDef)
+ extends StatementHandler(typeDef) {
+ override val boundNames =
+ if (!typeDef.mods.is(Flags.AccessFlags) && !typeDef.rhs.isInstanceOf[TypeBoundsTree])
+ List(typeDef.name)
+ else
+ Nil
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.println(" + \"defined type alias " +
+ string2code(typeDef.name.toString) + "\\n\"")
+ }
+ }
+
+ private class ImportHandler(imp: Import) extends StatementHandler(imp) {
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.println("+ \"" + imp.toString + "\\n\"")
+ }
+
+ def isWildcardSelector(tree: Tree) = tree match {
+ case Ident(nme.USCOREkw) => true
+ case _ => false
+ }
+
+ /** Whether this import includes a wildcard import */
+ override val importsWildcard = imp.selectors.exists(isWildcardSelector)
+
+ /** The individual names imported by this statement */
+ override val importedNames: Seq[Name] =
+ imp.selectors.filterNot(isWildcardSelector).flatMap {
+ case sel: RefTree => List(sel.name.toTypeName, sel.name.toTermName)
+ case _ => Nil
+ }
+ }
+
+ } // end Request
+
+ // ------- String handling ----------------------------------
+
+ /** next line number to use */
+ private var nextLineNo = 0
+
+ /** allocate a fresh line name */
+ private def newLineName = {
+ val num = nextLineNo
+ nextLineNo += 1
+ INTERPRETER_LINE_PREFIX + num
+ }
+
+ /** next result variable number to use */
+ private var nextVarNameNo = 0
+
+ /** allocate a fresh variable name */
+ private def newVarName = {
+ val num = nextVarNameNo
+ nextVarNameNo += 1
+ INTERPRETER_VAR_PREFIX + num
+ }
+
+ /** next internal variable number to use */
+ private var nextInternalVarNo = 0
+
+ /** allocate a fresh internal variable name */
+ private def newInternalVarName() = {
+ val num = nextVarNameNo
+ nextVarNameNo += 1
+ INTERPRETER_SYNTHVAR_PREFIX + num
+ }
+
+ /** Check if a name looks like it was generated by newVarName */
+ private def isGeneratedVarName(name: String): Boolean =
+ name.startsWith(INTERPRETER_VAR_PREFIX) && {
+ val suffix = name.drop(INTERPRETER_VAR_PREFIX.length)
+ suffix.forall(_.isDigit)
+ }
+
+ /** generate a string using a routine that wants to write on a stream */
+ private def stringFrom(writer: PrintWriter => Unit): String = {
+ val stringWriter = new StringWriter()
+ val stream = new NewLinePrintWriter(stringWriter)
+ writer(stream)
+ stream.close
+ stringWriter.toString
+ }
+
+ /** Truncate a string if it is longer than settings.maxPrintString */
+ private def truncPrintString(str: String)(implicit ctx: Context): String = {
+ val maxpr = ctx.settings.XreplLineWidth.value
+
+ if (maxpr <= 0)
+ return str
+
+ if (str.length <= maxpr)
+ return str
+
+ val trailer = "..."
+ if (maxpr >= trailer.length+1)
+ return str.substring(0, maxpr-3) + trailer
+
+ str.substring(0, maxpr)
+ }
+
+ /** 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. */
+object CompilingInterpreter {
+ val INTERPRETER_WRAPPER_SUFFIX = "$object"
+ val INTERPRETER_LINE_PREFIX = "line"
+ val INTERPRETER_VAR_PREFIX = "res"
+ val INTERPRETER_IMPORT_WRAPPER = "$iw"
+ val INTERPRETER_SYNTHVAR_PREFIX = "synthvar$"
+
+ /** Delete a directory tree recursively. Use with care!
+ */
+ private[repl] def deleteRecursively(path: File): Unit = {
+ path match {
+ case _ if !path.exists =>
+ ()
+ case _ if path.isDirectory =>
+ for (p <- path.listFiles)
+ deleteRecursively(p)
+ path.delete
+ case _ =>
+ path.delete
+ }
+ }
+
+ /** Heuristically strip interpreter wrapper prefixes
+ * from an interpreter output string.
+ */
+ def stripWrapperGunk(str: String): String = {
+ val wrapregex = "(line[0-9]+\\$object[$.])?(\\$iw[$.])*"
+ str.replaceAll(wrapregex, "")
+ }
+
+ /** Convert a string into code that can recreate the string.
+ * This requires replacing all special characters by escape
+ * codes. It does not add the surrounding " marks. */
+ def string2code(str: String): String = {
+ /** Convert a character to a backslash-u escape */
+ def char2uescape(c: Char): String = {
+ var rest = c.toInt
+ val buf = new StringBuilder
+ for (i <- 1 to 4) {
+ buf ++= (rest % 16).toHexString
+ rest = rest / 16
+ }
+ "\\" + "u" + buf.toString.reverse
+ }
+ val res = new StringBuilder
+ for (c <- str) {
+ if ("'\"\\" contains c) {
+ res += '\\'
+ res += c
+ } else if (!c.isControl) {
+ res += c
+ } else {
+ res ++= char2uescape(c)
+ }
+ }
+ res.toString
+ }
+}
diff --git a/src/dotty/tools/dotc/repl/ConsoleWriter.scala b/src/dotty/tools/dotc/repl/ConsoleWriter.scala
new file mode 100644
index 000000000..9387f366a
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/ConsoleWriter.scala
@@ -0,0 +1,21 @@
+package dotty.tools
+package dotc
+package repl
+import java.io.Writer
+
+/** A Writer that writes onto the Scala Console.
+ *
+ * @author Lex Spoon
+ * @version 1.0
+ */
+class ConsoleWriter extends Writer {
+ def close = flush
+
+ def flush = Console.flush
+
+ def write(cbuf: Array[Char], off: Int, len: Int): Unit =
+ if (len > 0)
+ write(new String(cbuf, off, len))
+
+ override def write(str: String): Unit = Console.print(str)
+}
diff --git a/src/dotty/tools/dotc/repl/InteractiveReader.scala b/src/dotty/tools/dotc/repl/InteractiveReader.scala
new file mode 100644
index 000000000..96c55ebd0
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/InteractiveReader.scala
@@ -0,0 +1,31 @@
+package dotty.tools
+package dotc
+package repl
+
+/** Reads lines from an input stream */
+trait InteractiveReader {
+ def readLine(prompt: String): String
+ val interactive: Boolean
+}
+
+/** TODO Enable jline support.
+ * The current Scala REPL know how to do this flexibly.
+ */
+object InteractiveReader {
+ /** Create an interactive reader. Uses JLine if the
+ * library is available, but otherwise uses a
+ * SimpleReader. */
+ def createDefault(): InteractiveReader = new SimpleReader()
+ /*
+ {
+ try {
+ new JLineReader
+ } catch {
+ case e =>
+ //out.println("jline is not available: " + e) //debug
+ new SimpleReader()
+ }
+ }
+*/
+
+}
diff --git a/src/dotty/tools/dotc/repl/Interpreter.scala b/src/dotty/tools/dotc/repl/Interpreter.scala
new file mode 100644
index 000000000..ea587a097
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/Interpreter.scala
@@ -0,0 +1,36 @@
+package dotty.tools
+package dotc
+package repl
+
+import core.Contexts.Context
+
+/** This object defines the type of interpreter results */
+object Interpreter {
+
+ /** A result from interpreting one line of input. */
+ abstract sealed class Result
+
+ /** The line was interpreted successfully. */
+ case object Success extends Result
+
+ /** The line was erroneous in some way. */
+ case object Error extends Result
+
+ /** The input was incomplete. The caller should request more input.
+ */
+ case object Incomplete extends Result
+}
+
+/** The exported functionality of the interpreter */
+trait Interpreter {
+ import Interpreter._
+
+ /** Interpret one line of input. All feedback, including parse errors
+ * and evaluation results, are printed via the context's reporter.
+ * reporter. Values defined are available for future interpreted strings.
+ */
+ def interpret(line: String)(implicit ctx: Context): Result
+
+ /** Suppress output during evaluation of `operation`. */
+ def beQuietDuring[T](operation: => T): T
+}
diff --git a/src/dotty/tools/dotc/repl/InterpreterLoop.scala b/src/dotty/tools/dotc/repl/InterpreterLoop.scala
new file mode 100644
index 000000000..eedec3c82
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/InterpreterLoop.scala
@@ -0,0 +1,228 @@
+package dotty.tools
+package dotc
+package repl
+
+import java.io.{BufferedReader, File, FileReader, PrintWriter}
+import java.io.IOException
+import java.lang.{ClassLoader, System}
+import scala.concurrent.{Future, Await}
+import scala.concurrent.duration.Duration
+import reporting.Reporter
+import core._
+import Contexts._
+import annotation.tailrec
+import scala.concurrent.ExecutionContext.Implicits.global
+
+/** The interactive shell. It provides a read-eval-print loop around
+ * the Interpreter class.
+ * After instantiation, clients should call the `run` method.
+ *
+ * @author Moez A. Abdel-Gawad
+ * @author Lex Spoon
+ * @author Martin Odersky
+ */
+class InterpreterLoop(
+ compiler: Compiler,
+ private var in: InteractiveReader,
+ out: PrintWriter)(implicit ctx: Context) {
+
+ val interpreter = compiler.asInstanceOf[Interpreter]
+
+ /** The context class loader at the time this object was created */
+ protected val originalClassLoader =
+ Thread.currentThread.getContextClassLoader
+
+ /** A reverse list of commands to replay if the user
+ * requests a :replay */
+ var replayCommandsRev: List[String] = Nil
+
+ /** A list of commands to replay if the user requests a :replay */
+ def replayCommands = replayCommandsRev.reverse
+
+ /** Record a command for replay should the user request a :replay */
+ def addReplay(cmd: String) =
+ replayCommandsRev = cmd :: replayCommandsRev
+
+ /** Close the interpreter */
+ def closeInterpreter()(implicit ctx: Context): Unit = {
+ ctx.reporter.flush()
+ Thread.currentThread.setContextClassLoader(originalClassLoader)
+ }
+
+ /** 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.")
+ }
+
+ /** Print a welcome message */
+ def printWelcome(): Unit = {
+ out.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()
+ }
+
+ /** 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
+ * due to classloading. To bridge the gap, we warm up the interpreter
+ * by letting it interpret a dummy line while waiting for the first
+ * line of input to be entered.
+ */
+ def firstLine(): String = {
+ val futLine = Future(in.readLine(prompt))
+ interpreter.beQuietDuring(
+ interpreter.interpret("val theAnswerToLifeInTheUniverseAndEverything = 21 * 2"))
+ Await.result(futLine, Duration.Inf)
+ }
+
+ /** The main read-eval-print loop for the interpreter. It calls
+ * `command()` for each line of input.
+ */
+ @tailrec final def repl(line: String = in.readLine(prompt)): Unit =
+ if (line != null) {
+ val (keepGoing, finalLineOpt) = command(line)
+ if (keepGoing) {
+ finalLineOpt.foreach(addReplay)
+ out.flush()
+ repl()
+ }
+ }
+
+ /** interpret all lines from a specified file */
+ def interpretAllFrom(filename: String): Unit = {
+ val fileIn = try {
+ new FileReader(filename)
+ } catch {
+ case _: IOException =>
+ out.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
+ repl()
+ } finally {
+ in = oldIn
+ replayCommandsRev = oldReplay
+ fileIn.close
+ }
+ }
+
+ /** 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
+ command(cmd)
+ out.println
+ }
+ }
+
+ /** Run one command submitted by the user. Three values are returned:
+ * (1) whether to keep running, (2) the line to record for replay,
+ * if any. */
+ def command(line: String): (Boolean, Option[String]) = {
+ 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.")
+ return
+ }
+ val filename = command.substring(spaceIdx).trim
+ if (!new File(filename).exists) {
+ out.println("That file does not exist")
+ return
+ }
+ action(filename)
+ }
+
+ val helpRegexp = ":h(e(l(p)?)?)?"
+ val quitRegexp = ":q(u(i(t)?)?)?"
+ val loadRegexp = ":l(o(a(d)?)?)?.*"
+ val replayRegexp = ":r(e(p(l(a(y)?)?)?)?)?.*"
+
+ var shouldReplay: Option[String] = None
+
+ if (line.matches(helpRegexp))
+ printHelp()
+ else if (line.matches(quitRegexp))
+ return (false, None)
+ else if (line.matches(loadRegexp)) {
+ withFile(line)(f => {
+ interpretAllFrom(f)
+ shouldReplay = Some(line)
+ })
+ }
+ else if (line matches replayRegexp)
+ replay()
+ else if (line startsWith ":")
+ out.println("Unknown command. Type :help for help.")
+ else
+ shouldReplay = interpretStartingWith(line)
+
+ (true, shouldReplay)
+ }
+
+ /** Interpret expressions starting with the first line.
+ * Read lines until a complete compilation unit is available
+ * or until a syntax error has been seen. If a full unit is
+ * read, go ahead and interpret it. Return the full string
+ * to be recorded for replay, if any.
+ */
+ def interpretStartingWith(code: String): Option[String] = {
+ interpreter.interpret(code) match {
+ case Interpreter.Success => Some(code)
+ 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.")
+ None
+ } else {
+ val nextLine = in.readLine(continuationPrompt)
+ if (nextLine == null)
+ None // end of file
+ else
+ interpretStartingWith(code + "\n" + nextLine)
+ }
+ }
+ }
+/*
+ def loadFiles(settings: Settings) {
+ settings match {
+ case settings: GenericRunnerSettings =>
+ for (filename <- settings.loadfiles.value) {
+ val cmd = ":load " + filename
+ command(cmd)
+ replayCommandsRev = cmd :: replayCommandsRev
+ out.println()
+ }
+ case _ =>
+ }
+ }
+*/
+ def run(): Reporter = {
+ // loadFiles(settings)
+ try {
+ if (!ctx.reporter.hasErrors) { // if there are already errors, no sense to continue
+ printWelcome()
+ repl(firstLine())
+ }
+ } finally {
+ closeInterpreter()
+ }
+ ctx.reporter
+ }
+}
diff --git a/src/dotty/tools/dotc/repl/Main.scala b/src/dotty/tools/dotc/repl/Main.scala
new file mode 100644
index 000000000..b2b92299e
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/Main.scala
@@ -0,0 +1,29 @@
+package dotty.tools
+package dotc
+package repl
+
+/* This REPL was adapted from an old (2008-ish) version of the Scala
+ * REPL. The original version from which the adaptation was done is found in:
+ *
+ * https://github.com/odersky/legacy-svn-scala/tree/spoon
+ *
+ * The reason this version was picked instead of a more current one is that
+ * the older version is much smaller, therefore easier to port. It is also
+ * considerably less intertwined with nsc than later versions.
+ *
+ * There are a number of TODOs:
+ *
+ * - re-enable jline support (urgent, easy, see TODO in InteractiveReader.scala)
+ * - figure out why we can launch REPL only with `java`, not with `scala`.
+ * - make a doti command (urgent, easy)
+ * - create or port REPL tests (urgent, intermediate)
+ * - copy improvements of current Scala REPL wrt to this version
+ * (somewhat urgent, intermediate)
+ * - re-enable bindSettings (not urgent, easy, see TODO in InterpreterLoop.scala)
+ * - make string generation more functional (not urgent, easy)
+ * - better handling of ^C (not urgent, intermediate)
+ * - syntax highlighting (not urgent, intermediate)
+ * - integrate with presentation compiler for command completion (not urgent, hard)
+ */
+/** The main entry point of the REPL */
+object Main extends REPL \ No newline at end of file
diff --git a/src/dotty/tools/dotc/repl/NewLinePrintWriter.scala b/src/dotty/tools/dotc/repl/NewLinePrintWriter.scala
new file mode 100644
index 000000000..8e36a0ae4
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/NewLinePrintWriter.scala
@@ -0,0 +1,11 @@
+package dotty.tools
+package dotc
+package repl
+import java.io.{Writer, PrintWriter}
+
+class NewLinePrintWriter(out: Writer, autoFlush: Boolean)
+extends PrintWriter(out, autoFlush) {
+ def this(out: Writer) = this(out, false)
+ override def println(): Unit = { print("\n"); flush() }
+}
+
diff --git a/src/dotty/tools/dotc/repl/REPL.scala b/src/dotty/tools/dotc/repl/REPL.scala
new file mode 100644
index 000000000..2d6a3c742
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/REPL.scala
@@ -0,0 +1,49 @@
+package dotty.tools
+package dotc
+package repl
+
+import core.Contexts.Context
+import reporting.Reporter
+import java.io.{BufferedReader, File, FileReader, PrintWriter}
+
+/** A compiler which stays resident between runs.
+ * Usage:
+ *
+ * > scala dotty.tools.dotc.Resident <options> <initial files>
+ *
+ * dotc> "more options and files to compile"
+ *
+ * ...
+ *
+ * dotc> :reset // reset all options to the ones passed on the command line
+ *
+ * ...
+ *
+ * dotc> :q // quit
+ */
+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)
+
+ override def newCompiler(implicit ctx: Context): Compiler =
+ new repl.CompilingInterpreter(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()
+ else
+ ctx.error(s"don't now what to do with $fileNames%, %")
+ ctx.reporter
+ }
+}
diff --git a/src/dotty/tools/dotc/repl/SimpleReader.scala b/src/dotty/tools/dotc/repl/SimpleReader.scala
new file mode 100644
index 000000000..9fd563382
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/SimpleReader.scala
@@ -0,0 +1,23 @@
+package dotty.tools
+package dotc
+package repl
+
+import java.io.{BufferedReader, PrintWriter}
+
+
+/** Reads using standard JDK API */
+class SimpleReader(
+ in: BufferedReader,
+ out: PrintWriter,
+ val interactive: Boolean)
+extends InteractiveReader {
+ def this() = this(Console.in, new PrintWriter(Console.out), true)
+
+ def readLine(prompt: String) = {
+ if (interactive) {
+ out.print(prompt)
+ out.flush()
+ }
+ in.readLine()
+ }
+}
diff --git a/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala b/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala
new file mode 100644
index 000000000..550e3348f
--- /dev/null
+++ b/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala
@@ -0,0 +1,98 @@
+package dotty.tools.dotc
+package transform
+
+import core._
+import DenotTransformers.SymTransformer
+import Phases.Phase
+import Contexts.Context
+import Flags._
+import Symbols._
+import SymDenotations.SymDenotation
+import ast.Trees._
+import collection.mutable
+import Decorators._
+import NameOps._
+import TreeTransforms.MiniPhaseTransform
+import dotty.tools.dotc.transform.TreeTransforms.TransformerInfo
+
+/** Remove companion objects that are empty
+ * Lots of constraints here:
+ * 1. It's impractical to place DropEmptyCompanions before lambda lift because dropped
+ * modules can be anywhere and have hard to trace references.
+ * 2. DropEmptyCompanions cannot be interleaved with LambdaLift or Flatten because
+ * they put things in liftedDefs sets which cause them to surface later. So
+ * removed modules resurface.
+ * 3. DropEmptyCompanions has to be before RestoreScopes.
+ * The solution to the constraints is to put DropEmptyCompanions between Flatten
+ * and RestoreScopes and to only start working once we are back on PackageDef
+ * level, so we know that all objects moved by LambdaLift and Flatten have arrived
+ * at their destination.
+ */
+class DropEmptyCompanions extends MiniPhaseTransform { thisTransform =>
+ import ast.tpd._
+ override def phaseName = "dropEmpty"
+ override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Flatten])
+
+ override def transformPackageDef(pdef: PackageDef)(implicit ctx: Context, info: TransformerInfo) = {
+
+ /** Is `tree` an empty companion object? */
+ def isEmptyCompanion(tree: Tree) = tree match {
+ case TypeDef(_, impl: Template) if tree.symbol.is(SyntheticModule) &&
+ tree.symbol.companionClass.exists &&
+ impl.body.forall(_.symbol.isPrimaryConstructor) =>
+ println(i"removing ${tree.symbol}")
+ true
+ case _ =>
+ false
+ }
+
+ val dropped = pdef.stats.filter(isEmptyCompanion).map(_.symbol).toSet
+
+ /** Symbol is a $lzy field representing a module */
+ def isLazyModuleVar(sym: Symbol) =
+ sym.name.isLazyLocal &&
+ sym.owner.info.decl(sym.name.asTermName.nonLazyName).symbol.is(Module)
+
+ /** Symbol should be dropped together with a dropped companion object.
+ * Such symbols are:
+ * - lzy fields pointing to modules,
+ * - vals and getters representing modules.
+ */
+ def symIsDropped(sym: Symbol): Boolean =
+ (sym.is(Module) || isLazyModuleVar(sym)) &&
+ dropped.contains(sym.info.resultType.typeSymbol)
+
+ /** Tree should be dropped because it (is associated with) an empty
+ * companion object. Such trees are
+ * - module classes of empty companion objects
+ * - definitions of lazy module variables or assignments to them.
+ * - vals and getters for empty companion objects
+ */
+ def toDrop(stat: Tree): Boolean = stat match {
+ case stat: TypeDef => dropped.contains(stat.symbol)
+ case stat: ValOrDefDef => symIsDropped(stat.symbol)
+ case stat: Assign => symIsDropped(stat.lhs.symbol)
+ case _ => false
+ }
+
+ def prune(tree: Tree): Tree = tree match {
+ case tree @ TypeDef(name, impl @ Template(constr, _, _, _)) =>
+ cpy.TypeDef(tree)(
+ rhs = cpy.Template(impl)(
+ constr = cpy.DefDef(constr)(rhs = pruneLocals(constr.rhs)),
+ body = pruneStats(impl.body)))
+ case _ =>
+ tree
+ }
+
+ def pruneStats(stats: List[Tree]) =
+ stats.filterConserve(!toDrop(_)).mapConserve(prune)
+
+ def pruneLocals(expr: Tree) = expr match {
+ case Block(stats, expr) => cpy.Block(expr)(pruneStats(stats), expr)
+ case _ => expr
+ }
+
+ cpy.PackageDef(pdef)(pdef.pid, pruneStats(pdef.stats))
+ }
+}
diff --git a/src/dotty/tools/dotc/transform/Flatten.scala b/src/dotty/tools/dotc/transform/Flatten.scala
index 9a047ef95..f0104e715 100644
--- a/src/dotty/tools/dotc/transform/Flatten.scala
+++ b/src/dotty/tools/dotc/transform/Flatten.scala
@@ -11,6 +11,7 @@ import collection.mutable
import TreeTransforms.MiniPhaseTransform
import dotty.tools.dotc.transform.TreeTransforms.TransformerInfo
+/** Lift nested classes to toplevel */
class Flatten extends MiniPhaseTransform with SymTransformer { thisTransform =>
import ast.tpd._
override def phaseName = "flatten"
diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala
index 1efc0826b..4a8ff83ca 100644
--- a/src/dotty/tools/dotc/transform/LazyVals.scala
+++ b/src/dotty/tools/dotc/transform/LazyVals.scala
@@ -63,8 +63,8 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer {
if (isField) {
if (sym.isVolatile ||
- (sym.is(Flags.Module) && !sym.is(Flags.Synthetic)))
- // module class is user-defined.
+ (sym.is(Flags.Module) && !sym.is(Flags.Synthetic)))
+ // module class is user-defined.
// Should be threadsafe, to mimic safety guaranteed by global object
transformMemberDefVolatile(tree)
else if (sym.is(Flags.Module)) { // synthetic module
@@ -101,7 +101,7 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer {
*/
def transformSyntheticModule(tree: ValOrDefDef)(implicit ctx: Context) = {
val sym = tree.symbol
- val holderSymbol = ctx.newSymbol(sym.owner, sym.asTerm.name ++ nme.LAZY_LOCAL,
+ val holderSymbol = ctx.newSymbol(sym.owner, sym.asTerm.name.lazyLocalName,
Flags.Synthetic, sym.info.widen.resultType).enteredAfter(this)
val field = ValDef(holderSymbol, tree.rhs.changeOwnerAfter(sym, holderSymbol, this))
val getter = DefDef(sym.asTerm, ref(holderSymbol))
@@ -114,7 +114,7 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer {
*/
def transformLocalDef(x: ValOrDefDef)(implicit ctx: Context) = {
val valueInitter = x.rhs
- val holderName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL).toTermName
+ val holderName = ctx.freshName(x.name.asTermName.lazyLocalName).toTermName
val initName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL_INIT).toTermName
val tpe = x.tpe.widen.resultType.widen
@@ -206,7 +206,7 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer {
val claz = x.symbol.owner.asClass
val tpe = x.tpe.widen.resultType.widen
assert(!(x.mods is Flags.Mutable))
- val containerName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL).toTermName
+ val containerName = ctx.freshName(x.name.asTermName.lazyLocalName).toTermName
val containerSymbol = ctx.newSymbol(claz, containerName,
x.symbol.flags &~ containerFlagsMask | containerFlags | Flags.Private,
tpe, coord = x.symbol.coord
@@ -367,7 +367,7 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer {
appendOffsetDefs += (companion.moduleClass -> new OffsetInfo(List(offsetTree), ord))
}
- val containerName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL).toTermName
+ val containerName = ctx.freshName(x.name.asTermName.lazyLocalName).toTermName
val containerSymbol = ctx.newSymbol(claz, containerName, (x.mods &~ containerFlagsMask | containerFlags).flags, tpe, coord = x.symbol.coord).enteredAfter(this)
val containerTree = ValDef(containerSymbol, defaultValue(tpe))
diff --git a/src/dotty/tools/dotc/transform/RestoreScopes.scala b/src/dotty/tools/dotc/transform/RestoreScopes.scala
index fe24186ac..41da05691 100644
--- a/src/dotty/tools/dotc/transform/RestoreScopes.scala
+++ b/src/dotty/tools/dotc/transform/RestoreScopes.scala
@@ -23,35 +23,46 @@ class RestoreScopes extends MiniPhaseTransform with IdentityDenotTransformer { t
import ast.tpd._
override def phaseName = "restoreScopes"
- override def transformTypeDef(tree: TypeDef)(implicit ctx: Context, info: TransformerInfo) = {
- val TypeDef(_, impl: Template) = tree
- //
- val restoredDecls = newScope
- for (stat <- impl.constr :: impl.body)
- if (stat.isInstanceOf[MemberDef] && stat.symbol.exists)
- restoredDecls.enter(stat.symbol)
+ /* Note: We need to wait until we see a package definition because
+ * DropEmptyConstructors changes template members when analyzing the
+ * enclosing package definitions. So by the time RestoreScopes gets to
+ * see a typedef or template, it still might be changed by DropEmptyConstructors.
+ */
+ override def transformPackageDef(pdef: PackageDef)(implicit ctx: Context, info: TransformerInfo) = {
+ pdef.stats.foreach(restoreScope)
+ pdef
+ }
+
+ private def restoreScope(tree: Tree)(implicit ctx: Context, info: TransformerInfo) = tree match {
+ case TypeDef(_, impl: Template) =>
+ val restoredDecls = newScope
+ for (stat <- impl.constr :: impl.body)
+ if (stat.isInstanceOf[MemberDef] && stat.symbol.exists)
+ restoredDecls.enter(stat.symbol)
// Enter class in enclosing package scope, in case it was an inner class before flatten.
// For top-level classes this does nothing.
- val cls = tree.symbol.asClass
- val pkg = cls.owner.asClass
-
- // Bring back companion links
- val companionClass = cls.info.decls.lookup(nme.COMPANION_CLASS_METHOD)
- val companionModule = cls.info.decls.lookup(nme.COMPANION_MODULE_METHOD)
-
- if (companionClass.exists) {
- restoredDecls.enter(companionClass)
- }
-
- if (companionModule.exists) {
- restoredDecls.enter(companionModule)
- }
-
- pkg.enter(cls)
- val cinfo = cls.classInfo
- tree.symbol.copySymDenotation(
- info = cinfo.derivedClassInfo( // Dotty deviation: Cannot expand cinfo inline without a type error
- decls = restoredDecls: Scope)).installAfter(thisTransform)
- tree
+ val cls = tree.symbol.asClass
+ val pkg = cls.owner.asClass
+
+ // Bring back companion links
+ val companionClass = cls.info.decls.lookup(nme.COMPANION_CLASS_METHOD)
+ val companionModule = cls.info.decls.lookup(nme.COMPANION_MODULE_METHOD)
+
+ if (companionClass.exists) {
+ restoredDecls.enter(companionClass)
+ }
+
+ if (companionModule.exists) {
+ restoredDecls.enter(companionModule)
+ }
+
+ pkg.enter(cls)
+ val cinfo = cls.classInfo
+ tree.symbol.copySymDenotation(
+ info = cinfo.derivedClassInfo( // Dotty deviation: Cannot expand cinfo inline without a type error
+ decls = restoredDecls: Scope)).installAfter(thisTransform)
+ tree
+ case tree => tree
}
}
+
diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala
index ecbec7d07..c8d2b3418 100644
--- a/src/dotty/tools/dotc/typer/Namer.scala
+++ b/src/dotty/tools/dotc/typer/Namer.scala
@@ -508,7 +508,7 @@ class Namer { typer: Typer =>
if (sym is Module) moduleValSig(sym)
else valOrDefDefSig(original, sym, Nil, Nil, identity)(localContext(sym).setNewScope)
case original: DefDef =>
- val typer1 = new Typer
+ val typer1 = ctx.typer.newLikeThis
nestedTyper(sym) = typer1
typer1.defDefSig(original, sym)(localContext(sym).setTyper(typer1))
case original: TypeDef =>
diff --git a/src/dotty/tools/dotc/typer/RefChecks.scala b/src/dotty/tools/dotc/typer/RefChecks.scala
index 067694bfd..bb411c6b5 100644
--- a/src/dotty/tools/dotc/typer/RefChecks.scala
+++ b/src/dotty/tools/dotc/typer/RefChecks.scala
@@ -708,7 +708,19 @@ object RefChecks {
if (clazz.is(Abstract))
ctx.error("`abstract' modifier cannot be used with value classes", clazz.pos)
if (!clazz.isStatic)
- ctx.error("value class cannot be an inner class", clazz.pos)
+ ctx.error(s"value class may not be a ${if (clazz.owner.isTerm) "local class" else "member of another class"}", clazz.pos)
+ else {
+ val clParamAccessors = clazz.asClass.paramAccessors.filter(sym => sym.isTerm && !sym.is(Method))
+ clParamAccessors match {
+ case List(param) =>
+ if (param.is(Mutable))
+ ctx.error("value class parameter must not be a var", param.pos)
+ if (param.is(PrivateLocal))
+ ctx.error("value class parameter must not be private[this]", param.pos)
+ case _ =>
+ ctx.error("value class needs to have exactly one val parameter", clazz.pos)
+ }
+ }
stats.foreach(checkValueClassMember)
}
}
diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala
index b65e3cd42..854bc2094 100644
--- a/src/dotty/tools/dotc/typer/Typer.scala
+++ b/src/dotty/tools/dotc/typer/Typer.scala
@@ -68,6 +68,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
*/
private var importedFromRoot: Set[Symbol] = Set()
+ def newLikeThis: Typer = new Typer
+
/** Attribute an identifier consisting of a simple name or wildcard
*
* @param tree The tree representing the identifier.
diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala
index 12b830738..70af32bd3 100644
--- a/test/dotc/tests.scala
+++ b/test/dotc/tests.scala
@@ -185,6 +185,7 @@ class tests extends CompilerTest {
@Test def neg_validateRefchecks = compileFile(negDir, "validate-refchecks", xerrors = 2)
@Test def neg_skolemize = compileFile(negDir, "skolemize", xerrors = 2)
@Test def neg_nested_bounds = compileFile(negDir, "nested_bounds", xerrors = 1)
+ @Test def neg_valueClasses = compileFile(negDir, "valueClasses", xerrors = 4)
@Test def run_all = runFiles(runDir)
diff --git a/tests/neg/valueClasses.scala b/tests/neg/valueClasses.scala
new file mode 100644
index 000000000..ae90ef63c
--- /dev/null
+++ b/tests/neg/valueClasses.scala
@@ -0,0 +1,10 @@
+class A1 {
+ class A2(x: Int) extends AnyVal // error: value class may not be a member of another class
+}
+class B1 {
+ def test = {
+ class B2(x: Int) extends AnyVal // error: value class may not be a local class
+ }
+}
+class C(private[this] val u: Int) extends AnyVal // error: value class parameter must not be private[this]
+class D(u: Int) extends AnyVal // error: value class parameter must not be private[this]
diff --git a/tests/pending/run/t920.scala b/tests/pending/run/t920.scala
new file mode 100644
index 000000000..a9874e1a8
--- /dev/null
+++ b/tests/pending/run/t920.scala
@@ -0,0 +1,25 @@
+object Test {
+ trait A;
+ trait Foo0 { def foo : A; }
+ trait Baz extends Foo0;
+ trait B extends A {
+ def initialize = {
+ trait Foo extends Test.Foo0 {
+ def foo : B.this.type = B.this;
+ }
+ class baz extends Baz with Foo {
+ override def toString = "baz"
+ }
+ Console.println(new baz);
+ }
+ }
+ object b extends B;
+ def main(args : Array[String]) : Unit = {
+ b.initialize;
+ }
+ class XYZ
+}
+
+
+
+
diff --git a/tests/run/t920.scala b/tests/run/t920.scala
index 6a7f122d5..d42388c4a 100644
--- a/tests/run/t920.scala
+++ b/tests/run/t920.scala
@@ -13,8 +13,12 @@ object Test {
Console.println(new baz);
}
}
- object b extends B;
+ object bb extends B;
def main(args : Array[String]) : Unit = {
- b.initialize;
+ bb.initialize;
}
}
+
+
+
+