diff --git a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index b6adba85a28b..5776cc8e234f 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 e8d196ce73c4..2d444d3beb9e 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 47b5fd6dd64f..2fc38d78c640 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/Driver.scala b/src/dotty/tools/dotc/Driver.scala index 7f22fc77465f..3437b86fcf9b 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 d8d8b8b1e13e..8f29c882c8f7 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 699a572349e2..6c473d8bbb44 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 3ae369f277cf..18bb2ff4fd4e 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 ba86e3e70de2..39fd42a64d72 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 035b20130fd7..65bc9ba23965 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 2af3f463dde6..b52c1120195e 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/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 970a9297ac2e..83ac64d536e9 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 <: ]. * 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 4495b40961af..705e1a4fa884 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 000000000000..a3a463717311 --- /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 000000000000..7d1da141966c --- /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("", 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("