Skip to content

Commit d7deabf

Browse files
authored
Merge pull request #3685 from dotty-staging/implemet-quote-run
Implement Expr.{run|show}
2 parents 4cd1122 + 610ad9c commit d7deabf

33 files changed

+575
-60
lines changed

compiler/src/dotty/tools/dotc/CompilationUnit.scala

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import dotty.tools.dotc.ast.tpd.{ Tree, TreeTraverser }
88
import dotty.tools.dotc.core.Contexts.Context
99
import dotty.tools.dotc.core.SymDenotations.ClassDenotation
1010
import dotty.tools.dotc.core.Symbols._
11+
import dotty.tools.dotc.transform.SymUtils._
1112

1213
class CompilationUnit(val source: SourceFile) {
1314

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

3334
/** Make a compilation unit for top class `clsd` with the contends of the `unpickled` */
34-
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
35+
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit =
36+
mkCompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()), unpickled, forceTrees)
37+
38+
/** Make a compilation unit the given unpickled tree */
39+
def mkCompilationUnit(source: SourceFile, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
3540
assert(!unpickled.isEmpty, unpickled)
36-
val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()))
41+
val unit1 = new CompilationUnit(source)
3742
unit1.tpdTree = unpickled
38-
if (forceTrees)
43+
if (forceTrees) {
44+
val force = new Force
3945
force.traverse(unit1.tpdTree)
46+
unit1.containsQuotesOrSplices = force.containsQuotes
47+
}
4048
unit1
4149
}
4250

4351
/** Force the tree to be loaded */
44-
private object force extends TreeTraverser {
45-
def traverse(tree: Tree)(implicit ctx: Context): Unit = traverseChildren(tree)
52+
private class Force extends TreeTraverser {
53+
var containsQuotes = false
54+
def traverse(tree: Tree)(implicit ctx: Context): Unit = {
55+
if (tree.symbol.isQuote)
56+
containsQuotes = true
57+
traverseChildren(tree)
58+
}
4659
}
4760
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import dotty.tools.dotc.CompilationUnit
4+
import dotty.tools.dotc.util.NoSource
5+
6+
import scala.quoted.Expr
7+
8+
/* Compilation unit containing the contents of a quoted expression */
9+
class ExprCompilationUnit(val expr: Expr[_]) extends CompilationUnit(NoSource) {
10+
override def toString = s"Expr($expr)"
11+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package dotty.tools.dotc
2+
package quoted
3+
4+
import dotty.tools.backend.jvm.GenBCode
5+
import dotty.tools.dotc.ast.tpd
6+
7+
import dotty.tools.dotc.core.Contexts.Context
8+
import dotty.tools.dotc.core.Flags.{EmptyFlags, Method}
9+
import dotty.tools.dotc.core.{Mode, Phases}
10+
import dotty.tools.dotc.core.Phases.Phase
11+
import dotty.tools.dotc.core.Scopes.{EmptyScope, newScope}
12+
import dotty.tools.dotc.core.StdNames.nme
13+
import dotty.tools.dotc.core.Symbols.defn
14+
import dotty.tools.dotc.core.Types.ExprType
15+
import dotty.tools.dotc.core.quoted.PickledQuotes
16+
import dotty.tools.dotc.transform.ReifyQuotes
17+
import dotty.tools.dotc.typer.FrontEnd
18+
import dotty.tools.dotc.util.Positions.Position
19+
import dotty.tools.dotc.util.SourceFile
20+
import dotty.tools.io.{Path, PlainFile, VirtualDirectory}
21+
22+
import scala.quoted.Expr
23+
24+
/** Compiler that takes the contents of a quoted expression `expr` and produces
25+
* a class file with `class ' { def apply: Object = expr }`.
26+
*/
27+
class ExprCompiler(directory: VirtualDirectory) extends Compiler {
28+
import tpd._
29+
30+
/** A GenBCode phase that outputs to a virtual directory */
31+
private class ExprGenBCode extends GenBCode {
32+
override def phaseName = "genBCode"
33+
override def outputDir(implicit ctx: Context) = directory
34+
}
35+
36+
override protected def frontendPhases: List[List[Phase]] =
37+
List(List(new ExprFrontend(putInClass = true)))
38+
39+
override protected def picklerPhases: List[List[Phase]] =
40+
List(List(new ReifyQuotes))
41+
42+
override protected def backendPhases: List[List[Phase]] =
43+
List(List(new ExprGenBCode))
44+
45+
override def newRun(implicit ctx: Context): ExprRun = {
46+
reset()
47+
new ExprRun(this, ctx.addMode(Mode.ReadPositions))
48+
}
49+
50+
/** Frontend that receives scala.quoted.Expr as input */
51+
class ExprFrontend(putInClass: Boolean) extends FrontEnd {
52+
import tpd._
53+
54+
override def isTyper = false
55+
56+
override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = {
57+
units.map {
58+
case exprUnit: ExprCompilationUnit =>
59+
val tree =
60+
if (putInClass) inClass(exprUnit.expr)
61+
else PickledQuotes.quotedToTree(exprUnit.expr)
62+
val source = new SourceFile("", Seq())
63+
CompilationUnit.mkCompilationUnit(source, tree, forceTrees = true)
64+
}
65+
}
66+
67+
/** Places the contents of expr in a compilable tree for a class
68+
* with the following format.
69+
* `package __root__ { class ' { def apply: Any = <expr> } }`
70+
*/
71+
private def inClass(expr: Expr[_])(implicit ctx: Context): Tree = {
72+
val pos = Position(0)
73+
val assocFile = new PlainFile(Path("<quote>"))
74+
75+
val cls = ctx.newCompleteClassSymbol(defn.RootClass, nme.QUOTE.toTypeName, EmptyFlags,
76+
defn.ObjectType :: Nil, newScope, coord = pos, assocFile = assocFile).entered.asClass
77+
cls.enter(ctx.newDefaultConstructor(cls), EmptyScope)
78+
val meth = ctx.newSymbol(cls, nme.apply, Method, ExprType(defn.AnyType), coord = pos).entered
79+
80+
val quoted = PickledQuotes.quotedToTree(expr)(ctx.withOwner(meth))
81+
82+
val run = DefDef(meth, quoted)
83+
val classTree = ClassDef(cls, DefDef(cls.primaryConstructor.asTerm), run :: Nil)
84+
PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], classTree :: Nil).withPos(pos)
85+
}
86+
}
87+
88+
class ExprRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) {
89+
def compileExpr(expr: Expr[_]): Unit = {
90+
val units = new ExprCompilationUnit(expr) :: Nil
91+
compileUnits(units)
92+
}
93+
}
94+
95+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import java.io.PrintStream
4+
5+
import dotty.tools.dotc.core.Phases.Phase
6+
7+
/** Compiler that takes the contents of a quoted expression `expr` and produces outputs
8+
* the pretty printed code.
9+
*/
10+
class ExprDecompiler(out: PrintStream) extends ExprCompiler(null) {
11+
override def phases: List[List[Phase]] = List(
12+
List(new ExprFrontend(putInClass = false)), // Create class from Expr
13+
List(new QuotePrinter(out)) // Print all loaded classes
14+
)
15+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import dotty.tools.dotc.Driver
4+
import dotty.tools.dotc.core.Contexts.{Context, FreshContext}
5+
import dotty.tools.dotc.core.StdNames._
6+
import dotty.tools.io.VirtualDirectory
7+
import dotty.tools.repl.AbstractFileClassLoader
8+
9+
import scala.quoted.Expr
10+
import java.io.ByteArrayOutputStream
11+
import java.io.PrintStream
12+
import java.nio.charset.StandardCharsets
13+
14+
class QuoteDriver extends Driver {
15+
16+
def run[T](expr: Expr[T]): T = {
17+
val ctx: Context = initCtx.fresh
18+
// TODO enable optimisation?
19+
// ctx.settings.optimise.update(true)(ctx)
20+
21+
val outDir = new VirtualDirectory("(memory)", None)
22+
23+
new ExprCompiler(outDir).newRun(ctx).compileExpr(expr)
24+
25+
val classLoader = new AbstractFileClassLoader(outDir, this.getClass.getClassLoader)
26+
27+
val clazz = classLoader.loadClass(nme.QUOTE.toString)
28+
val method = clazz.getMethod("apply")
29+
val instance = clazz.newInstance()
30+
31+
method.invoke(instance).asInstanceOf[T]
32+
}
33+
34+
def show(expr: Expr[_]): String = {
35+
val ctx: Context = initCtx.fresh
36+
ctx.settings.color.update("never")(ctx) // TODO support colored show
37+
val baos = new ByteArrayOutputStream
38+
var ps: PrintStream = null
39+
try {
40+
ps = new PrintStream(baos, true, "utf-8")
41+
42+
new ExprDecompiler(ps).newRun(ctx).compileExpr(expr)
43+
44+
new String(baos.toByteArray, StandardCharsets.UTF_8)
45+
}
46+
finally if (ps != null) ps.close()
47+
}
48+
49+
override def initCtx: Context = {
50+
val ictx = super.initCtx.fresh
51+
val classpath = System.getProperty("java.class.path")
52+
ictx.settings.classpath.update(classpath)(ictx)
53+
ictx
54+
}
55+
56+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import java.io.PrintStream
4+
5+
import dotty.tools.dotc.core.Contexts._
6+
import dotty.tools.dotc.core.Phases.Phase
7+
8+
/** Pretty prints the compilation unit to an output stream */
9+
class QuotePrinter(out: PrintStream) extends Phase {
10+
11+
override def phaseName: String = "quotePrinter"
12+
13+
override def run(implicit ctx: Context): Unit = {
14+
val unit = ctx.compilationUnit
15+
out.print(unit.tpdTree.show)
16+
}
17+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import dotty.tools.dotc.ast.Trees.Literal
4+
import dotty.tools.dotc.core.Constants.Constant
5+
import dotty.tools.dotc.printing.RefinedPrinter
6+
7+
import scala.quoted.Expr
8+
import scala.quoted.Liftable.ConstantExpr
9+
import scala.runtime.quoted._
10+
11+
/** Default runners for quoted expressions */
12+
object Runners {
13+
14+
implicit def runner[T]: Runner[T] = new Runner[T] {
15+
16+
def run(expr: Expr[T]): T = expr match {
17+
case expr: ConstantExpr[T] => expr.value
18+
case _ => new QuoteDriver().run(expr)
19+
}
20+
21+
def show(expr: Expr[T]): String = expr match {
22+
case expr: ConstantExpr[T] =>
23+
val ctx = new QuoteDriver().initCtx
24+
ctx.settings.color.update("never")(ctx)
25+
val printer = new RefinedPrinter(ctx)
26+
printer.toText(Literal(Constant(expr.value))).mkString(Int.MaxValue, false)
27+
case _ => new QuoteDriver().show(expr)
28+
}
29+
}
30+
}

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
180180
override def transform(tree: Tree)(implicit ctx: Context): Tree =
181181
try tree match {
182182
case tree: Ident if !tree.isType =>
183+
handleMeta(tree.symbol)
183184
tree.tpe match {
184185
case tpe: ThisType => This(tpe.cls).withPos(tree.pos)
185186
case _ => tree

compiler/src/dotty/tools/dotc/transform/Splicer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ object Splicer {
2424
/** Splice the Tree for a Quoted expression which is constructed via a reflective call to the given method */
2525
private def reflectiveSplice(tree: Tree)(implicit ctx: Context): Tree = {
2626
val interpreter = new Interpreter
27-
interpreter.interpretTree[quoted.Expr[_]](tree).map(PickledQuotes.quotedToTree(_)).getOrElse(tree)
27+
interpreter.interpretTree[scala.quoted.Expr[_]](tree).map(PickledQuotes.quotedToTree(_)).getOrElse(tree)
2828
}
2929

3030
}

compiler/test/dotty/Jars.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ object Jars {
1414
val dottyInterfaces: String = sys.env.get("DOTTY_INTERFACE")
1515
.getOrElse(Properties.dottyInterfaces)
1616

17+
/** Scala asm Jar */
18+
lazy val scalaAsm: String =
19+
findJarFromRuntime("scala-asm-6.0.0-scala-1")
20+
1721
/** Dotty extras classpath from env or properties */
1822
val dottyExtras: List[String] = sys.env.get("DOTTY_EXTRAS")
1923
.map(_.split(":").toList).getOrElse(Properties.dottyExtras)
@@ -25,6 +29,10 @@ object Jars {
2529
val dottyTestDeps: List[String] =
2630
dottyLib :: dottyCompiler :: dottyInterfaces :: dottyExtras
2731

32+
/** Dotty runtime with compiler dependencies, used for quoted.Expr.run */
33+
val dottyRunWithCompiler: List[String] =
34+
dottyLib :: dottyCompiler :: dottyInterfaces :: scalaAsm :: Nil
35+
2836
def scalaLibrary: String = sys.env.get("DOTTY_SCALA_LIBRARY")
2937
.getOrElse(findJarFromRuntime("scala-library-2."))
3038

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,11 @@ class CompilationTests extends ParallelTesting {
197197
@Test def runAll: Unit = {
198198
implicit val testGroup: TestGroup = TestGroup("runAll")
199199
compileFilesInDir("../tests/run", defaultOptions) +
200-
compileFilesInDir("../tests/run-no-optimise", defaultOptions)
200+
compileFilesInDir("../tests/run-no-optimise", defaultOptions) +
201+
compileFile("../tests/run-special/quote-run-constants.scala", defaultRunWithCompilerOptions) +
202+
compileFile("../tests/run-special/quote-run.scala", defaultRunWithCompilerOptions) +
203+
compileFile("../tests/run-special/quote-run-2.scala", defaultRunWithCompilerOptions) +
204+
compileFile("../tests/run-special/quote-run-staged-interpreter.scala", defaultRunWithCompilerOptions)
201205
}.checkRuns()
202206

203207
// Generic java signatures tests ---------------------------------------------

compiler/test/dotty/tools/dotc/FromTastyTests.scala

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,6 @@ class FromTastyTests extends ParallelTesting {
3737
"i3000.scala",
3838
"i536.scala",
3939
"i974.scala",
40-
"quote-liftable.scala",
41-
"quote-0.scala",
42-
"quote-1.scala",
43-
"quote-stagedInterpreter.scala",
4440
"t1203a.scala",
4541
"t2260.scala",
4642
"t3612.scala", // Test never finishes

compiler/test/dotty/tools/vulpix/ChildJVMMain.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ public class ChildJVMMain {
1212
static final String MessageEnd = "##THIS IS THE END FOR ME, GOODBYE##";
1313

1414
private static void runMain(String dir) throws Exception {
15+
String jcp = System.getProperty("java.class.path");
16+
System.setProperty("java.class.path", jcp == null ? dir : dir + ":" + jcp);
17+
1518
ArrayList<URL> cp = new ArrayList<>();
1619
for (String path : dir.split(":"))
1720
cp.add(new File(path).toURI().toURL());

compiler/test/dotty/tools/vulpix/TestConfiguration.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ object TestConfiguration {
5151
val defaultUnoptimised = TestFlags(classPath, runClassPath, basicDefaultOptions)
5252
val defaultOptimised = defaultUnoptimised and "-optimise"
5353
val defaultOptions = defaultUnoptimised
54+
val defaultRunWithCompilerOptions = defaultOptions.withRunClasspath(Jars.dottyRunWithCompiler.mkString(":"))
5455
val allowDeepSubtypes = defaultOptions without "-Yno-deep-subtypes"
5556
val allowDoubleBindings = defaultOptions without "-Yno-double-bindings"
5657
val picklingOptions = defaultUnoptimised and (

dist/bin/dotc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ source "$PROG_HOME/bin/common"
2929

3030
default_java_opts="-Xmx768m -Xms768m"
3131
bootcp=true
32+
withCompiler=true
3233

3334
CompilerMain=dotty.tools.dotc.Main
3435
DecompilerMain=dotty.tools.dotc.decompiler.Main
@@ -89,6 +90,7 @@ case "$1" in
8990
-nobootcp) unset bootcp && shift ;;
9091
-colors) colors=true && shift ;;
9192
-no-colors) unset colors && shift ;;
93+
-with-compiler) jvm_cp_args="$PSEP$DOTTY_COMP" && shift ;;
9294

9395
# break out -D and -J options and add them to JAVA_OPTS as well
9496
# so they reach the JVM in time to do some good. The -D options

dist/bin/dotr

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ source "$PROG_HOME/bin/common"
3131

3232
declare -a residual_args
3333
run_repl=false
34+
with_compiler=false
3435
CLASS_PATH=""
3536

3637
while [[ $# -gt 0 ]]; do
@@ -44,6 +45,10 @@ while [[ $# -gt 0 ]]; do
4445
shift
4546
shift
4647
;;
48+
-with-compiler)
49+
with_compiler=true
50+
shift
51+
;;
4752
-d)
4853
DEBUG="$DEBUG_STR"
4954
shift
@@ -69,5 +74,8 @@ else
6974
else
7075
cp_arg+="$PSEP$CLASS_PATH"
7176
fi
77+
if [ $with_compiler == true ]; then
78+
cp_arg+="$PSEP$DOTTY_COMP$PSEP$DOTTY_INTF$PSEP$SCALA_ASM"
79+
fi
7280
eval exec "\"$JAVACMD\"" "$DEBUG" "-classpath \"$cp_arg\"" "${residual_args[@]}"
7381
fi

0 commit comments

Comments
 (0)