Skip to content

Implement Expr.{run|show} #3685

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jan 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import dotty.tools.dotc.ast.tpd.{ Tree, TreeTraverser }
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.SymDenotations.ClassDenotation
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.transform.SymUtils._

class CompilationUnit(val source: SourceFile) {

Expand All @@ -31,17 +32,29 @@ class CompilationUnit(val source: SourceFile) {
object CompilationUnit {

/** Make a compilation unit for top class `clsd` with the contends of the `unpickled` */
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit =
mkCompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()), unpickled, forceTrees)

/** Make a compilation unit the given unpickled tree */
def mkCompilationUnit(source: SourceFile, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
assert(!unpickled.isEmpty, unpickled)
val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()))
val unit1 = new CompilationUnit(source)
unit1.tpdTree = unpickled
if (forceTrees)
if (forceTrees) {
val force = new Force
force.traverse(unit1.tpdTree)
unit1.containsQuotesOrSplices = force.containsQuotes
}
unit1
}

/** Force the tree to be loaded */
private object force extends TreeTraverser {
def traverse(tree: Tree)(implicit ctx: Context): Unit = traverseChildren(tree)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why the change here is necessary. forceTrees is true only in the IDE,, right? But in the IDE we never get to ReifyQuotes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, force tree is also used when compiling from TASTY.
This covers the case when we recompile from TASTY a file with a quote and no splices.

private class Force extends TreeTraverser {
var containsQuotes = false
def traverse(tree: Tree)(implicit ctx: Context): Unit = {
if (tree.symbol.isQuote)
containsQuotes = true
traverseChildren(tree)
}
}
}
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprCompilationUnit.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dotty.tools.dotc.quoted

import dotty.tools.dotc.CompilationUnit
import dotty.tools.dotc.util.NoSource

import scala.quoted.Expr

/* Compilation unit containing the contents of a quoted expression */
class ExprCompilationUnit(val expr: Expr[_]) extends CompilationUnit(NoSource) {
override def toString = s"Expr($expr)"
}
95 changes: 95 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprCompiler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package dotty.tools.dotc
package quoted

import dotty.tools.backend.jvm.GenBCode
import dotty.tools.dotc.ast.tpd

import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Flags.{EmptyFlags, Method}
import dotty.tools.dotc.core.{Mode, Phases}
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.core.Scopes.{EmptyScope, newScope}
import dotty.tools.dotc.core.StdNames.nme
import dotty.tools.dotc.core.Symbols.defn
import dotty.tools.dotc.core.Types.ExprType
import dotty.tools.dotc.core.quoted.PickledQuotes
import dotty.tools.dotc.transform.ReifyQuotes
import dotty.tools.dotc.typer.FrontEnd
import dotty.tools.dotc.util.Positions.Position
import dotty.tools.dotc.util.SourceFile
import dotty.tools.io.{Path, PlainFile, VirtualDirectory}

import scala.quoted.Expr

/** Compiler that takes the contents of a quoted expression `expr` and produces
* a class file with `class ' { def apply: Object = expr }`.
*/
class ExprCompiler(directory: VirtualDirectory) extends Compiler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these are all small files, should ExprRun and ExprFrontEnd be made part of ExprCompiler?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Done.

import tpd._

/** A GenBCode phase that outputs to a virtual directory */
private class ExprGenBCode extends GenBCode {
override def phaseName = "genBCode"
override def outputDir(implicit ctx: Context) = directory
}

override protected def frontendPhases: List[List[Phase]] =
List(List(new ExprFrontend(putInClass = true)))

override protected def picklerPhases: List[List[Phase]] =
List(List(new ReifyQuotes))

override protected def backendPhases: List[List[Phase]] =
List(List(new ExprGenBCode))

override def newRun(implicit ctx: Context): ExprRun = {
reset()
new ExprRun(this, ctx.addMode(Mode.ReadPositions))
}

/** Frontend that receives scala.quoted.Expr as input */
class ExprFrontend(putInClass: Boolean) extends FrontEnd {
import tpd._

override def isTyper = false

override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = {
units.map {
case exprUnit: ExprCompilationUnit =>
val tree =
if (putInClass) inClass(exprUnit.expr)
else PickledQuotes.quotedToTree(exprUnit.expr)
val source = new SourceFile("", Seq())
CompilationUnit.mkCompilationUnit(source, tree, forceTrees = true)
}
}

/** Places the contents of expr in a compilable tree for a class
* with the following format.
* `package __root__ { class ' { def apply: Any = <expr> } }`
*/
private def inClass(expr: Expr[_])(implicit ctx: Context): Tree = {
val pos = Position(0)
val assocFile = new PlainFile(Path("<quote>"))

val cls = ctx.newCompleteClassSymbol(defn.RootClass, nme.QUOTE.toTypeName, EmptyFlags,
defn.ObjectType :: Nil, newScope, coord = pos, assocFile = assocFile).entered.asClass
cls.enter(ctx.newDefaultConstructor(cls), EmptyScope)
val meth = ctx.newSymbol(cls, nme.apply, Method, ExprType(defn.AnyType), coord = pos).entered

val quoted = PickledQuotes.quotedToTree(expr)(ctx.withOwner(meth))

val run = DefDef(meth, quoted)
val classTree = ClassDef(cls, DefDef(cls.primaryConstructor.asTerm), run :: Nil)
PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], classTree :: Nil).withPos(pos)
}
}

class ExprRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) {
def compileExpr(expr: Expr[_]): Unit = {
val units = new ExprCompilationUnit(expr) :: Nil
compileUnits(units)
}
}

}
15 changes: 15 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprDecompiler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dotty.tools.dotc.quoted

import java.io.PrintStream

import dotty.tools.dotc.core.Phases.Phase

/** Compiler that takes the contents of a quoted expression `expr` and produces outputs
* the pretty printed code.
*/
class ExprDecompiler(out: PrintStream) extends ExprCompiler(null) {
override def phases: List[List[Phase]] = List(
List(new ExprFrontend(putInClass = false)), // Create class from Expr
List(new QuotePrinter(out)) // Print all loaded classes
)
}
56 changes: 56 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package dotty.tools.dotc.quoted

import dotty.tools.dotc.Driver
import dotty.tools.dotc.core.Contexts.{Context, FreshContext}
import dotty.tools.dotc.core.StdNames._
import dotty.tools.io.VirtualDirectory
import dotty.tools.repl.AbstractFileClassLoader

import scala.quoted.Expr
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.nio.charset.StandardCharsets

class QuoteDriver extends Driver {

def run[T](expr: Expr[T]): T = {
val ctx: Context = initCtx.fresh
// TODO enable optimisation?
// ctx.settings.optimise.update(true)(ctx)

val outDir = new VirtualDirectory("(memory)", None)

new ExprCompiler(outDir).newRun(ctx).compileExpr(expr)

val classLoader = new AbstractFileClassLoader(outDir, this.getClass.getClassLoader)

val clazz = classLoader.loadClass(nme.QUOTE.toString)
val method = clazz.getMethod("apply")
val instance = clazz.newInstance()

method.invoke(instance).asInstanceOf[T]
}

def show(expr: Expr[_]): String = {
val ctx: Context = initCtx.fresh
ctx.settings.color.update("never")(ctx) // TODO support colored show
val baos = new ByteArrayOutputStream
var ps: PrintStream = null
try {
ps = new PrintStream(baos, true, "utf-8")

new ExprDecompiler(ps).newRun(ctx).compileExpr(expr)

new String(baos.toByteArray, StandardCharsets.UTF_8)
}
finally if (ps != null) ps.close()
}

override def initCtx: Context = {
val ictx = super.initCtx.fresh
val classpath = System.getProperty("java.class.path")
ictx.settings.classpath.update(classpath)(ictx)
ictx
}

}
17 changes: 17 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/QuotePrinter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dotty.tools.dotc.quoted

import java.io.PrintStream

import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Phases.Phase

/** Pretty prints the compilation unit to an output stream */
class QuotePrinter(out: PrintStream) extends Phase {

override def phaseName: String = "quotePrinter"

override def run(implicit ctx: Context): Unit = {
val unit = ctx.compilationUnit
out.print(unit.tpdTree.show)
}
}
30 changes: 30 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/Runners.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package dotty.tools.dotc.quoted

import dotty.tools.dotc.ast.Trees.Literal
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.printing.RefinedPrinter

import scala.quoted.Expr
import scala.quoted.Liftable.ConstantExpr
import scala.runtime.quoted._

/** Default runners for quoted expressions */
object Runners {

implicit def runner[T]: Runner[T] = new Runner[T] {

def run(expr: Expr[T]): T = expr match {
case expr: ConstantExpr[T] => expr.value
case _ => new QuoteDriver().run(expr)
}

def show(expr: Expr[T]): String = expr match {
case expr: ConstantExpr[T] =>
val ctx = new QuoteDriver().initCtx
ctx.settings.color.update("never")(ctx)
val printer = new RefinedPrinter(ctx)
printer.toText(Literal(Constant(expr.value))).mkString(Int.MaxValue, false)
case _ => new QuoteDriver().show(expr)
}
}
}
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
override def transform(tree: Tree)(implicit ctx: Context): Tree =
try tree match {
case tree: Ident if !tree.isType =>
handleMeta(tree.symbol)
tree.tpe match {
case tpe: ThisType => This(tpe.cls).withPos(tree.pos)
case _ => tree
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ object Splicer {
/** Splice the Tree for a Quoted expression which is constructed via a reflective call to the given method */
private def reflectiveSplice(tree: Tree)(implicit ctx: Context): Tree = {
val interpreter = new Interpreter
interpreter.interpretTree[quoted.Expr[_]](tree).map(PickledQuotes.quotedToTree(_)).getOrElse(tree)
interpreter.interpretTree[scala.quoted.Expr[_]](tree).map(PickledQuotes.quotedToTree(_)).getOrElse(tree)
}

}
8 changes: 8 additions & 0 deletions compiler/test/dotty/Jars.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ object Jars {
val dottyInterfaces: String = sys.env.get("DOTTY_INTERFACE")
.getOrElse(Properties.dottyInterfaces)

/** Scala asm Jar */
lazy val scalaAsm: String =
findJarFromRuntime("scala-asm-6.0.0-scala-1")

/** Dotty extras classpath from env or properties */
val dottyExtras: List[String] = sys.env.get("DOTTY_EXTRAS")
.map(_.split(":").toList).getOrElse(Properties.dottyExtras)
Expand All @@ -25,6 +29,10 @@ object Jars {
val dottyTestDeps: List[String] =
dottyLib :: dottyCompiler :: dottyInterfaces :: dottyExtras

/** Dotty runtime with compiler dependencies, used for quoted.Expr.run */
val dottyRunWithCompiler: List[String] =
dottyLib :: dottyCompiler :: dottyInterfaces :: scalaAsm :: Nil

def scalaLibrary: String = sys.env.get("DOTTY_SCALA_LIBRARY")
.getOrElse(findJarFromRuntime("scala-library-2."))

Expand Down
6 changes: 5 additions & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,11 @@ class CompilationTests extends ParallelTesting {
@Test def runAll: Unit = {
implicit val testGroup: TestGroup = TestGroup("runAll")
compileFilesInDir("../tests/run", defaultOptions) +
compileFilesInDir("../tests/run-no-optimise", defaultOptions)
compileFilesInDir("../tests/run-no-optimise", defaultOptions) +
compileFile("../tests/run-special/quote-run-constants.scala", defaultRunWithCompilerOptions) +
compileFile("../tests/run-special/quote-run.scala", defaultRunWithCompilerOptions) +
compileFile("../tests/run-special/quote-run-2.scala", defaultRunWithCompilerOptions) +
compileFile("../tests/run-special/quote-run-staged-interpreter.scala", defaultRunWithCompilerOptions)
}.checkRuns()

// Generic java signatures tests ---------------------------------------------
Expand Down
4 changes: 0 additions & 4 deletions compiler/test/dotty/tools/dotc/FromTastyTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ class FromTastyTests extends ParallelTesting {
"i3000.scala",
"i536.scala",
"i974.scala",
"quote-liftable.scala",
"quote-0.scala",
"quote-1.scala",
"quote-stagedInterpreter.scala",
"t1203a.scala",
"t2260.scala",
"t3612.scala", // Test never finishes
Expand Down
3 changes: 3 additions & 0 deletions compiler/test/dotty/tools/vulpix/ChildJVMMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public class ChildJVMMain {
static final String MessageEnd = "##THIS IS THE END FOR ME, GOODBYE##";

private static void runMain(String dir) throws Exception {
String jcp = System.getProperty("java.class.path");
System.setProperty("java.class.path", jcp == null ? dir : dir + ":" + jcp);

ArrayList<URL> cp = new ArrayList<>();
for (String path : dir.split(":"))
cp.add(new File(path).toURI().toURL());
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/vulpix/TestConfiguration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ object TestConfiguration {
val defaultUnoptimised = TestFlags(classPath, runClassPath, basicDefaultOptions)
val defaultOptimised = defaultUnoptimised and "-optimise"
val defaultOptions = defaultUnoptimised
val defaultRunWithCompilerOptions = defaultOptions.withRunClasspath(Jars.dottyRunWithCompiler.mkString(":"))
val allowDeepSubtypes = defaultOptions without "-Yno-deep-subtypes"
val allowDoubleBindings = defaultOptions without "-Yno-double-bindings"
val picklingOptions = defaultUnoptimised and (
Expand Down
2 changes: 2 additions & 0 deletions dist/bin/dotc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ source "$PROG_HOME/bin/common"

default_java_opts="-Xmx768m -Xms768m"
bootcp=true
withCompiler=true

CompilerMain=dotty.tools.dotc.Main
DecompilerMain=dotty.tools.dotc.decompiler.Main
Expand Down Expand Up @@ -89,6 +90,7 @@ case "$1" in
-nobootcp) unset bootcp && shift ;;
-colors) colors=true && shift ;;
-no-colors) unset colors && shift ;;
-with-compiler) jvm_cp_args="$PSEP$DOTTY_COMP" && shift ;;

# break out -D and -J options and add them to JAVA_OPTS as well
# so they reach the JVM in time to do some good. The -D options
Expand Down
8 changes: 8 additions & 0 deletions dist/bin/dotr
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ source "$PROG_HOME/bin/common"

declare -a residual_args
run_repl=false
with_compiler=false
CLASS_PATH=""

while [[ $# -gt 0 ]]; do
Expand All @@ -44,6 +45,10 @@ while [[ $# -gt 0 ]]; do
shift
shift
;;
-with-compiler)
with_compiler=true
shift
;;
-d)
DEBUG="$DEBUG_STR"
shift
Expand All @@ -69,5 +74,8 @@ else
else
cp_arg+="$PSEP$CLASS_PATH"
fi
if [ $with_compiler == true ]; then
cp_arg+="$PSEP$DOTTY_COMP$PSEP$DOTTY_INTF$PSEP$SCALA_ASM"
fi
eval exec "\"$JAVACMD\"" "$DEBUG" "-classpath \"$cp_arg\"" "${residual_args[@]}"
fi
Loading