Skip to content

Commit 69efd50

Browse files
committed
Implement run/show for quoted expressions
1 parent d465c02 commit 69efd50

25 files changed

+385
-13
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ class CompilationUnit(val source: SourceFile) {
3131
object CompilationUnit {
3232

3333
/** 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 = {
34+
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit =
35+
mkCompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()), unpickled, forceTrees)
36+
37+
/** Make a compilation unit the given unpickled tree */
38+
def mkCompilationUnit(source: SourceFile, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
3539
assert(!unpickled.isEmpty, unpickled)
36-
val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()))
40+
val unit1 = new CompilationUnit(source)
3741
unit1.tpdTree = unpickled
3842
if (forceTrees)
3943
force.traverse(unit1.tpdTree)
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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package dotty.tools.dotc
2+
package quoted
3+
4+
import dotty.tools.backend.jvm.GenBCode
5+
import dotty.tools.dotc.core.Contexts.Context
6+
import dotty.tools.dotc.core.{Mode, Phases}
7+
import dotty.tools.dotc.core.Phases.Phase
8+
import dotty.tools.dotc.transform.Pickler
9+
import dotty.tools.io.VirtualDirectory
10+
11+
/** Compiler that takes the contents of a quoted expression `expr` and produces
12+
* a class file with `class ' { def apply: Object = expr }`.
13+
*/
14+
class ExprCompiler(directory: VirtualDirectory) extends Compiler {
15+
16+
/** A GenBCode phase that outputs to a virtual directory */
17+
private class ExprGenBCode extends GenBCode {
18+
override def phaseName = "genBCode"
19+
override def outputDir(implicit ctx: Context) = directory
20+
}
21+
22+
override def phases: List[List[Phase]] = {
23+
val backendPhases = super.phases.dropWhile {
24+
case List(_: Pickler) => false
25+
case _ => true
26+
}.tail
27+
28+
List(new ExprFrontend(putInClass = true)) ::
29+
Phases.replace(classOf[GenBCode], _ => new ExprGenBCode :: Nil, backendPhases)
30+
}
31+
32+
override def newRun(implicit ctx: Context): ExprRun = {
33+
reset()
34+
new ExprRun(this, ctx.addMode(Mode.ReadPositions))
35+
}
36+
}
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: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import dotty.tools.dotc.CompilationUnit
4+
import dotty.tools.dotc.ast.tpd
5+
import dotty.tools.dotc.core.Contexts.Context
6+
import dotty.tools.dotc.core.Flags._
7+
import dotty.tools.dotc.core.Scopes._
8+
import dotty.tools.dotc.core.StdNames._
9+
import dotty.tools.dotc.core.Symbols._
10+
import dotty.tools.dotc.core.Types._
11+
import dotty.tools.dotc.typer.FrontEnd
12+
import dotty.tools.dotc.util.Positions._
13+
import dotty.tools.dotc.util.SourceFile
14+
import dotty.tools.io._
15+
16+
import scala.quoted.Expr
17+
18+
/** Frontend that receives scala.quoted.Expr as input */
19+
class ExprFrontend(putInClass: Boolean) extends FrontEnd {
20+
import tpd._
21+
import PickledQuotes.quotedToTree
22+
23+
override def isTyper = false
24+
25+
override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = {
26+
units.map {
27+
case exprUnit: ExprCompilationUnit =>
28+
val tree =
29+
if (putInClass) inClass(exprUnit.expr)
30+
else quotedToTree(exprUnit.expr)
31+
val source = new SourceFile("", Seq())
32+
CompilationUnit.mkCompilationUnit(source, tree, forceTrees = true)
33+
}
34+
}
35+
36+
/** Places the contents of expr in a compilable tree for a class
37+
* with the following format.
38+
* `package __root__ { class ' { def apply: Any = <expr> } }`
39+
*/
40+
private def inClass(expr: Expr[_])(implicit ctx: Context): Tree = {
41+
val pos = Position(0)
42+
val assocFile = new PlainFile(Path("<quote>"))
43+
44+
val cls = ctx.newCompleteClassSymbol(defn.RootClass, nme.QUOTE.toTypeName, EmptyFlags,
45+
defn.ObjectType :: Nil, newScope, coord = pos, assocFile = assocFile).entered.asClass
46+
cls.enter(ctx.newDefaultConstructor(cls), EmptyScope)
47+
val meth = ctx.newSymbol(cls, nme.apply, Method, ExprType(defn.AnyType), coord = pos).entered
48+
49+
val quoted = quotedToTree(expr)(ctx.withOwner(meth))
50+
51+
val run = DefDef(meth, quoted)
52+
val classTree = ClassDef(cls, DefDef(cls.primaryConstructor.asTerm), run :: Nil)
53+
PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], classTree :: Nil).withPos(pos)
54+
}
55+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package dotty.tools.dotc
2+
package quoted
3+
4+
import dotty.tools.dotc.core.Contexts._
5+
6+
import scala.quoted._
7+
8+
class ExprRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) {
9+
def compileExpr(expr: Expr[_]): Unit = {
10+
val units = new ExprCompilationUnit(expr) :: Nil
11+
compileUnits(units)
12+
}
13+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import dotty.tools.dotc.Driver
4+
import dotty.tools.dotc.core.Contexts.Context
5+
import dotty.tools.dotc.core.StdNames._
6+
import dotty.tools.io.VirtualDirectory
7+
8+
import dotty.tools.repl.AbstractFileClassLoader
9+
10+
import scala.quoted.Expr
11+
12+
import java.io.ByteArrayOutputStream
13+
import java.io.PrintStream
14+
import java.nio.charset.StandardCharsets
15+
16+
class QuoteDriver extends Driver {
17+
18+
def run[T](expr: Expr[T]): T = {
19+
val ctx: Context = initCtx.fresh
20+
// TODO enable optimisation?
21+
// ctx.settings.optimise.update(true)(ctx)
22+
23+
val outDir = new VirtualDirectory("(memory)", None)
24+
25+
new ExprCompiler(outDir).newRun(ctx).compileExpr(expr)
26+
27+
val classLoader = new AbstractFileClassLoader(outDir, this.getClass.getClassLoader)
28+
29+
val clazz = classLoader.loadClass(nme.QUOTE.toString)
30+
val method = clazz.getMethod("apply")
31+
val instance = clazz.newInstance()
32+
33+
method.invoke(instance).asInstanceOf[T]
34+
}
35+
36+
def show(expr: Expr[_]): String = {
37+
val ctx: Context = initCtx.fresh
38+
ctx.settings.color.update("never")(ctx) // TODO support colored show
39+
val baos = new ByteArrayOutputStream
40+
var ps: PrintStream = null
41+
try {
42+
ps = new PrintStream(baos, true, "utf-8")
43+
44+
new ExprDecompiler(ps).newRun(ctx).compileExpr(expr)
45+
46+
new String(baos.toByteArray, StandardCharsets.UTF_8)
47+
}
48+
finally if (ps != null) ps.close()
49+
}
50+
51+
override def initCtx: Context = {
52+
val ictx = super.initCtx.fresh
53+
val compilerClasspath = System.getProperty("dotty.tools.dotc.classpath")
54+
assert(compilerClasspath ne null, "System property `dotty.tools.dotc.classpath` is not set.")
55+
val classpath = System.getProperty("java.class.path")
56+
val scalaLib = classpath.split(":").filter(_.contains("scala-library")).mkString(":")
57+
ictx.settings.classpath.update(compilerClasspath + ":" + scalaLib)(ictx)
58+
ictx
59+
}
60+
61+
}
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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import scala.quoted.Expr
4+
import scala.runtime.quoted._
5+
6+
/** Default runners for quoted expressions */
7+
object Runners {
8+
implicit def runner[T]: Runner[T] = (expr: Expr[T]) => new QuoteDriver().run(expr)
9+
implicit def show[T]: Show[T] = (expr: Expr[T]) => new QuoteDriver().show(expr)
10+
}

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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,9 @@ class CompilationTests extends ParallelTesting {
194194
@Test def runAll: Unit = {
195195
implicit val testGroup: TestGroup = TestGroup("runAll")
196196
compileFilesInDir("../tests/run", defaultOptions) +
197-
compileFilesInDir("../tests/run-no-optimise", defaultOptions)
197+
compileFilesInDir("../tests/run-no-optimise", defaultOptions) +
198+
compileFile("../tests/run-special/quote-run.scala", defaultRunWithCompilerOptions) +
199+
compileFile("../tests/run-special/quote-run-2.scala", defaultRunWithCompilerOptions)
198200
}.checkRuns()
199201

200202
// Generic java signatures tests ---------------------------------------------

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ 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+
System.setProperty("dotty.tools.dotc.classpath", dir);
1516
ArrayList<URL> cp = new ArrayList<>();
1617
for (String path : dir.split(":"))
1718
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/dotr

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ fi
3030
source "$PROG_HOME/bin/common"
3131

3232
declare -a residual_args
33+
declare -a system_properties
3334
run_repl=false
35+
with_compiler=false
3436
CLASS_PATH=""
3537

3638
while [[ $# -gt 0 ]]; do
@@ -44,6 +46,10 @@ while [[ $# -gt 0 ]]; do
4446
shift
4547
shift
4648
;;
49+
-with-compiler)
50+
with_compiler=true
51+
shift
52+
;;
4753
-d)
4854
DEBUG="$DEBUG_STR"
4955
shift
@@ -69,5 +75,9 @@ else
6975
else
7076
cp_arg+="$PSEP$CLASS_PATH"
7177
fi
72-
eval exec "\"$JAVACMD\"" "$DEBUG" "-classpath \"$cp_arg\"" "${residual_args[@]}"
78+
if [ $with_compiler == true ]; then
79+
cp_arg+="$PSEP$DOTTY_COMP$PSEP$DOTTY_INTF$PSEP$SCALA_ASM"
80+
system_properties+=("-Ddotty.tools.dotc.classpath=$DOTTY_COMP$PSEP$DOTTY_LIB$PSEP$DOTTY_INTF$PSEP$SCALA_ASM")
81+
fi
82+
eval exec "\"$JAVACMD\"" "$DEBUG" "-classpath \"$cp_arg\"" "${system_properties[@]}" "${residual_args[@]}"
7383
fi

library/src/scala/quoted/Expr.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package scala.quoted
22

3+
import scala.runtime.quoted.{Runner, Show}
4+
35
abstract class Expr[T] extends Quoted {
4-
def unary_~ : T = throw new Error("~ should have been compiled away")
5-
def run: T = ???
6+
final def unary_~ : T = throw new Error("~ should have been compiled away")
7+
final def run(implicit runner: Runner[T]): T = runner.run(this)
8+
final def show(implicit runner: Show[T]): String = runner.run(this)
69
}
710

811
object Expr {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package scala.runtime.quoted
2+
3+
import scala.annotation.implicitNotFound
4+
import scala.quoted.Expr
5+
6+
@implicitNotFound("Could not find implicit Runner. Default runner can must be imported with `import dotty.tools.dotc.quoted.Runners._`")
7+
trait Runner[T] {
8+
def run(expr: Expr[T]): T
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package scala.runtime.quoted
2+
3+
import scala.annotation.implicitNotFound
4+
import scala.quoted.Expr
5+
6+
@implicitNotFound("Could not find implicit Show. Default runner can must be imported with `import dotty.tools.dotc.quoted.Runners._`")
7+
trait Show[T] {
8+
def run(expr: Expr[T]): String
9+
}

project/Build.scala

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -571,22 +571,35 @@ object Build {
571571
val java: String = Process("which" :: "java" :: Nil).!!
572572
val attList = (dependencyClasspath in Runtime).value
573573
val _ = packageAll.value
574-
val scalaLib = attList
574+
575+
def findLib(name: String) = attList
575576
.map(_.data.getAbsolutePath)
576-
.find(_.contains("scala-library"))
577+
.find(_.contains(name))
577578
.toList.mkString(":")
578579

580+
val scalaLib = findLib("scala-library")
581+
val dottyLib = packageAll.value("dotty-library")
582+
583+
def run(args: List[String]): Unit = {
584+
val fullArgs = insertClasspathInArgs(args, s".:$dottyLib:$scalaLib")
585+
s"$java ${fullArgs.mkString(" ")}".!
586+
}
587+
579588
if (args.isEmpty) {
580589
println("Couldn't run `dotr` without args. Use `repl` to run the repl or add args to run the dotty application")
581590
} else if (java == "") {
582591
println("Couldn't find java executable on path, please install java to a default location")
583592
} else if (scalaLib == "") {
584593
println("Couldn't find scala-library on classpath, please run using script in bin dir instead")
585-
} else {
586-
val dottyLib = packageAll.value("dotty-library")
587-
val fullArgs = insertClasspathInArgs(args, s".:$dottyLib:$scalaLib")
588-
s"$java ${fullArgs.mkString(" ")}".!
589-
}
594+
} else if (args.contains("-with-compiler")) {
595+
val args1 = args.filter(_ != "-with-compiler")
596+
val asm = findLib("scala-asm")
597+
val dottyCompiler = packageAll.value("dotty-compiler")
598+
val dottyInterfaces = packageAll.value("dotty-interfaces")
599+
val deps = s"$dottyCompiler:$dottyInterfaces:$asm"
600+
val args2 = s"-Ddotty.tools.dotc.classpath=$deps:$dottyLib" :: insertClasspathInArgs(args1, deps)
601+
run(args2)
602+
} else run(args)
590603
},
591604
run := dotc.evaluated,
592605
dotc := runCompilerMain().evaluated,

0 commit comments

Comments
 (0)