diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index c3d9326f180c..6adb73d083af 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -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) { @@ -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) + private class Force extends TreeTraverser { + var containsQuotes = false + def traverse(tree: Tree)(implicit ctx: Context): Unit = { + if (tree.symbol.isQuote) + containsQuotes = true + traverseChildren(tree) + } } } diff --git a/compiler/src/dotty/tools/dotc/quoted/ExprCompilationUnit.scala b/compiler/src/dotty/tools/dotc/quoted/ExprCompilationUnit.scala new file mode 100644 index 000000000000..620ca7065f57 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/ExprCompilationUnit.scala @@ -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)" +} diff --git a/compiler/src/dotty/tools/dotc/quoted/ExprCompiler.scala b/compiler/src/dotty/tools/dotc/quoted/ExprCompiler.scala new file mode 100644 index 000000000000..24de4011e541 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/ExprCompiler.scala @@ -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 { + 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 = } }` + */ + private def inClass(expr: Expr[_])(implicit ctx: Context): Tree = { + val pos = Position(0) + val assocFile = new PlainFile(Path("")) + + 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) + } + } + +} diff --git a/compiler/src/dotty/tools/dotc/quoted/ExprDecompiler.scala b/compiler/src/dotty/tools/dotc/quoted/ExprDecompiler.scala new file mode 100644 index 000000000000..ea91109ec0a3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/ExprDecompiler.scala @@ -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 + ) +} diff --git a/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala b/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala new file mode 100644 index 000000000000..f79302494793 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala @@ -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 + } + +} diff --git a/compiler/src/dotty/tools/dotc/quoted/QuotePrinter.scala b/compiler/src/dotty/tools/dotc/quoted/QuotePrinter.scala new file mode 100644 index 000000000000..96ac9e8390e1 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/QuotePrinter.scala @@ -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) + } +} diff --git a/compiler/src/dotty/tools/dotc/quoted/Runners.scala b/compiler/src/dotty/tools/dotc/quoted/Runners.scala new file mode 100644 index 000000000000..d5f9b18f7dc5 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/Runners.scala @@ -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) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 46fb723e2391..3807a25b09c2 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 271c807a8ec1..6e05b9d85873 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -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) } } diff --git a/compiler/test/dotty/Jars.scala b/compiler/test/dotty/Jars.scala index dd06dc2a6fee..5294943d2554 100644 --- a/compiler/test/dotty/Jars.scala +++ b/compiler/test/dotty/Jars.scala @@ -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) @@ -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.")) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 82bb6ef523f1..87c3e13d2c39 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -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 --------------------------------------------- diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index db6ab211257f..547b418b9ce1 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -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 diff --git a/compiler/test/dotty/tools/vulpix/ChildJVMMain.java b/compiler/test/dotty/tools/vulpix/ChildJVMMain.java index 90b7958989e3..ce9aebf719e1 100644 --- a/compiler/test/dotty/tools/vulpix/ChildJVMMain.java +++ b/compiler/test/dotty/tools/vulpix/ChildJVMMain.java @@ -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 cp = new ArrayList<>(); for (String path : dir.split(":")) cp.add(new File(path).toURI().toURL()); diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index b45af357ec3d..845908be2e0f 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -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 ( diff --git a/dist/bin/dotc b/dist/bin/dotc index 0ada07560cfc..a91f70fcc06a 100755 --- a/dist/bin/dotc +++ b/dist/bin/dotc @@ -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 @@ -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 diff --git a/dist/bin/dotr b/dist/bin/dotr index bb3d06330fdb..34766208c84d 100755 --- a/dist/bin/dotr +++ b/dist/bin/dotr @@ -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 @@ -44,6 +45,10 @@ while [[ $# -gt 0 ]]; do shift shift ;; + -with-compiler) + with_compiler=true + shift + ;; -d) DEBUG="$DEBUG_STR" shift @@ -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 diff --git a/docs/docs/reference/symmetric-meta-programming.md b/docs/docs/reference/symmetric-meta-programming.md index 004315516cfd..c198a08c1fa5 100644 --- a/docs/docs/reference/symmetric-meta-programming.md +++ b/docs/docs/reference/symmetric-meta-programming.md @@ -400,7 +400,8 @@ to `T` but only `~` is subject to the PCP, whereas `run` is just a normal method abstract class Expr[T] { def unary_~: T - def run: T // run staged code + def run(implicit runner: Runner[T]): T // run staged code + def show(implicit runner: Runner[T]): String // show staged code } ### Limitations to Splicing diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index 58241792ddc1..07f2e6e91751 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -1,8 +1,11 @@ package scala.quoted +import scala.runtime.quoted.Runner + abstract class Expr[T] extends Quoted { - def unary_~ : T = throw new Error("~ should have been compiled away") - def run: T = ??? + final def unary_~ : T = throw new Error("~ should have been compiled away") + final def run(implicit runner: Runner[T]): T = runner.run(this) + final def show(implicit runner: Runner[T]): String = runner.show(this) } object Expr { diff --git a/library/src/scala/runtime/quoted/Runner.scala b/library/src/scala/runtime/quoted/Runner.scala new file mode 100644 index 000000000000..e78517bb4f28 --- /dev/null +++ b/library/src/scala/runtime/quoted/Runner.scala @@ -0,0 +1,10 @@ +package scala.runtime.quoted + +import scala.annotation.implicitNotFound +import scala.quoted.Expr + +@implicitNotFound("Could not find implicit Runner. Default runner can be imported with `import dotty.tools.dotc.quoted.Runners._`") +trait Runner[T] { + def run(expr: Expr[T]): T + def show(expr: Expr[T]): String +} diff --git a/project/Build.scala b/project/Build.scala index f5ebbd12e3da..ccc3d29cb700 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -571,22 +571,33 @@ object Build { val java: String = Process("which" :: "java" :: Nil).!! val attList = (dependencyClasspath in Runtime).value val _ = packageAll.value - val scalaLib = attList + + def findLib(name: String) = attList .map(_.data.getAbsolutePath) - .find(_.contains("scala-library")) + .find(_.contains(name)) .toList.mkString(":") + val scalaLib = findLib("scala-library") + val dottyLib = packageAll.value("dotty-library") + + def run(args: List[String]): Unit = { + val fullArgs = insertClasspathInArgs(args, s".:$dottyLib:$scalaLib") + s"$java ${fullArgs.mkString(" ")}".! + } + if (args.isEmpty) { println("Couldn't run `dotr` without args. Use `repl` to run the repl or add args to run the dotty application") } else if (java == "") { println("Couldn't find java executable on path, please install java to a default location") } else if (scalaLib == "") { println("Couldn't find scala-library on classpath, please run using script in bin dir instead") - } else { - val dottyLib = packageAll.value("dotty-library") - val fullArgs = insertClasspathInArgs(args, s".:$dottyLib:$scalaLib") - s"$java ${fullArgs.mkString(" ")}".! - } + } else if (args.contains("-with-compiler")) { + val args1 = args.filter(_ != "-with-compiler") + val asm = findLib("scala-asm") + val dottyCompiler = packageAll.value("dotty-compiler") + val dottyInterfaces = packageAll.value("dotty-interfaces") + run(insertClasspathInArgs(args1, s"$dottyCompiler:$dottyInterfaces:$asm")) + } else run(args) }, run := dotc.evaluated, dotc := runCompilerMain().evaluated, @@ -628,10 +639,12 @@ object Build { def runCompilerMain(repl: Boolean = false) = Def.inputTaskDyn { val dottyLib = packageAll.value("dotty-library") + lazy val dottyCompiler = packageAll.value("dotty-compiler") val args0: List[String] = spaceDelimited("").parsed.toList val decompile = args0.contains("-decompile") val debugFromTasty = args0.contains("-Ythrough-tasty") - val args = args0.filter(arg => arg != "-repl" && arg != "-decompile" && arg != "-Ythrough-tasty") + val args = args0.filter(arg => arg != "-repl" && arg != "-decompile" && + arg != "-with-compiler" && arg != "-Ythrough-tasty") val main = if (repl) "dotty.tools.repl.Main" @@ -639,9 +652,9 @@ object Build { else if (debugFromTasty) "dotty.tools.dotc.fromtasty.Debug" else "dotty.tools.dotc.Main" - val extraClasspath = - if (decompile && !args.contains("-classpath")) dottyLib + ":." - else dottyLib + var extraClasspath = dottyLib + if (decompile && !args.contains("-classpath")) extraClasspath += ":." + if (args0.contains("-with-compiler")) extraClasspath += s":$dottyCompiler" val fullArgs = main :: insertClasspathInArgs(args, extraClasspath) diff --git a/project/scripts/sbtTests b/project/scripts/sbtTests index 7d7e68ee77d9..17641aea4c56 100755 --- a/project/scripts/sbtTests +++ b/project/scripts/sbtTests @@ -56,3 +56,13 @@ else echo "failed output check" exit -1 fi + +echo "testing scala.quoted.Expr.run from sbt dotr" +./project/scripts/sbt ";dotc -classpath compiler/target/scala-2.12/classes tests/run-special/quote-run.scala; dotr -with-compiler Test" > sbtdot5.out +cat sbtdot5.out +if grep -e "val a: Int = 3" sbtdot5.out; then + echo "output ok" +else + echo "failed output check" + exit -1 +fi diff --git a/tests/pos/quote-0.scala b/tests/pos/quote-0.scala index 3155c353ca33..8372c6414cde 100644 --- a/tests/pos/quote-0.scala +++ b/tests/pos/quote-0.scala @@ -1,4 +1,5 @@ import scala.quoted._ +import dotty.tools.dotc.quoted.Runners._ class Test { diff --git a/tests/pos/quote-assert/quoted_2.scala b/tests/pos/quote-assert/quoted_2.scala index 1b6cde45c700..7d73cdc65a4a 100644 --- a/tests/pos/quote-assert/quoted_2.scala +++ b/tests/pos/quote-assert/quoted_2.scala @@ -1,3 +1,4 @@ +import dotty.tools.dotc.quoted.Runners.runner import scala.quoted._ import Macros._ diff --git a/tests/pos/quote-no-splices.scala b/tests/pos/quote-no-splices.scala new file mode 100644 index 000000000000..7f67c60492ce --- /dev/null +++ b/tests/pos/quote-no-splices.scala @@ -0,0 +1,9 @@ +class Foo { + def foo: Unit = { + val expr ='{ + val a = 3 + println("foo") + 2 + a + } + } +} diff --git a/tests/pos/quote-stagedInterpreter.scala b/tests/pos/quote-stagedInterpreter.scala deleted file mode 100644 index 2ba57be2b004..000000000000 --- a/tests/pos/quote-stagedInterpreter.scala +++ /dev/null @@ -1,35 +0,0 @@ -import scala.quoted._ - -enum Exp { - case Num(n: Int) - case Plus(e1: Exp, e2: Exp) - case Var(x: String) - case Let(x: String, e: Exp, in: Exp) -} - -object Test { - import Exp._ - - val keepLets = true - - val exp = Plus(Plus(Num(2), Var("x")), Num(4)) - - val letExp = Let("x", Num(3), exp) - - def compile(e: Exp, env: Map[String, Expr[Int]]): Expr[Int] = e match { - case Num(n) => n - case Plus(e1, e2) => '(~compile(e1, env) + ~compile(e2, env)) - case Var(x) => env(x) - case Let(x, e, body) => - if (keepLets) - '{ val y = ~compile(e, env); ~compile(body, env + (x -> '(y))) } - else - compile(body, env + (x -> compile(e, env))) - } - - val res1 = '{ (x: Int) => ~compile(exp, Map("x" -> '(x))) } - - val res2 = compile(letExp, Map()) - - res1.run -} diff --git a/tests/run-special/quote-run-2.check b/tests/run-special/quote-run-2.check new file mode 100644 index 000000000000..d9052f8c4d3d --- /dev/null +++ b/tests/run-special/quote-run-2.check @@ -0,0 +1,18 @@ +1.0 +5.0 +{ + { + val y: Double = 5.0.*(5.0) + y + } +} +{ + 5.0.*( + { + { + val y: Double = 5.0.*(5.0) + y + } + } + ) +} diff --git a/tests/run-special/quote-run-2.scala b/tests/run-special/quote-run-2.scala new file mode 100644 index 000000000000..8361539c1442 --- /dev/null +++ b/tests/run-special/quote-run-2.scala @@ -0,0 +1,19 @@ + +import dotty.tools.dotc.quoted.Runners._ + +import scala.quoted._ + +object Test { + def main(args: Array[String]): Unit = { + def powerCode(n: Int, x: Expr[Double]): Expr[Double] = + if (n == 0) '(1.0) + else if (n == 1) x + else if (n % 2 == 0) '{ { val y = ~x * ~x; ~powerCode(n / 2, '(y)) } } + else '{ ~x * ~powerCode(n - 1, x) } + + println(powerCode(0, '(5)).show) + println(powerCode(1, '(5)).show) + println(powerCode(2, '(5)).show) + println(powerCode(3, '(5)).show) + } +} diff --git a/tests/run-special/quote-run-constants.check b/tests/run-special/quote-run-constants.check new file mode 100644 index 000000000000..19284ac517a1 --- /dev/null +++ b/tests/run-special/quote-run-constants.check @@ -0,0 +1,28 @@ +true +a + + +" +' +\ +1 +2 +3 +4.0 +5.0 +xyz +====== +true +'a' +'\n' +'\"' +'\'' +'\\' +1 +2 +3L +4.0 +5.0 +"xyz" +"\n\\\"\'" +"abc\n xyz" diff --git a/tests/run-special/quote-run-constants.scala b/tests/run-special/quote-run-constants.scala new file mode 100644 index 000000000000..af9449767a4f --- /dev/null +++ b/tests/run-special/quote-run-constants.scala @@ -0,0 +1,42 @@ + +import dotty.tools.dotc.quoted.Runners._ + +import scala.quoted._ + +object Test { + def main(args: Array[String]): Unit = { + def run[T](expr: Expr[T]): Unit = println(expr.run) + def show[T](expr: Expr[T]): Unit = println(expr.show) + + run(true) + run('a') + run('\n') + run('"') + run('\'') + run('\\') + run(1) + run(2) + run(3L) + run(4.0f) + run(5.0d) + run("xyz") + + println("======") + + show(true) + show('a') + show('\n') + show('"') + show('\'') + show('\\') + show(1) + show(2) + show(3L) + show(4.0f) + show(5.0d) + show("xyz") + show("\n\\\"'") + show("""abc + xyz""") + } +} diff --git a/tests/run-special/quote-run-staged-interpreter.check b/tests/run-special/quote-run-staged-interpreter.check new file mode 100644 index 000000000000..bacb3969c81b --- /dev/null +++ b/tests/run-special/quote-run-staged-interpreter.check @@ -0,0 +1,21 @@ +{ + { + def $anonfun(x: Int): Int = + { + 2.+(x).+(4) + } + closure($anonfun) + } +} +6 +8 +9 +--- +2.+(3).+(4) +9 +--- +{ + val y: Int = 3 + 2.+(y).+(4) +} +9 diff --git a/tests/run-special/quote-run-staged-interpreter.scala b/tests/run-special/quote-run-staged-interpreter.scala new file mode 100644 index 000000000000..91429b7c86eb --- /dev/null +++ b/tests/run-special/quote-run-staged-interpreter.scala @@ -0,0 +1,55 @@ +import scala.quoted._ +import dotty.tools.dotc.quoted.Runners._ + +enum Exp { + case Num(n: Int) + case Plus(e1: Exp, e2: Exp) + case Var(x: String) + case Let(x: String, e: Exp, in: Exp) +} + +object Test { + import Exp._ + + def compile(e: Exp, env: Map[String, Expr[Int]], keepLets: Boolean): Expr[Int] = { + def compileImpl(e: Exp, env: Map[String, Expr[Int]]): Expr[Int] = e match { + case Num(n) => n + case Plus(e1, e2) => '(~compileImpl(e1, env) + ~compileImpl(e2, env)) + case Var(x) => env(x) + case Let(x, e, body) => + if (keepLets) + '{ val y = ~compileImpl(e, env); ~compileImpl(body, env + (x -> '(y))) } + else + compileImpl(body, env + (x -> compileImpl(e, env))) + } + compileImpl(e, env) + } + + + def main(args: Array[String]): Unit = { + val exp = Plus(Plus(Num(2), Var("x")), Num(4)) + val letExp = Let("x", Num(3), exp) + + val res1 = '{ (x: Int) => ~compile(exp, Map("x" -> '(x)), false) } + + + println(res1.show) + + val fn = res1.run + println(fn(0)) + println(fn(2)) + println(fn(3)) + + println("---") + + val res2 = compile(letExp, Map(), false) + println(res2.show) + println(res2.run) + + println("---") + + val res3 = compile(letExp, Map(), true) + println(res3.show) + println(res3.run) + } +} diff --git a/tests/run-special/quote-run.check b/tests/run-special/quote-run.check new file mode 100644 index 000000000000..fefd8facd664 --- /dev/null +++ b/tests/run-special/quote-run.check @@ -0,0 +1,16 @@ +foo +5 +foo +5 +{ + val a: Int = 3 + println("foo") + 2.+(a) +} + +lambda(4) +lambda(5) +Foo +false +Bar +class '$A$1 diff --git a/tests/run-special/quote-run.scala b/tests/run-special/quote-run.scala new file mode 100644 index 000000000000..ad8c4fe35f7b --- /dev/null +++ b/tests/run-special/quote-run.scala @@ -0,0 +1,43 @@ + +import dotty.tools.dotc.quoted.Runners._ + +import scala.quoted._ + +object Test { + def main(args: Array[String]): Unit = { + val expr = '{ + val a = 3 + println("foo") + 2 + a + } + println(expr.run) + println(expr.run) + println(expr.show) + + val lambdaExpr = '{ + (x: Int) => println("lambda(" + x + ")") + } + println() + + val lambda = lambdaExpr.run + lambda(4) + lambda(5) + + val classExpr = '{ + class A { + override def toString: String = "Foo" + } + new A + } + val classExpr2 = '{ + class A { + override def toString: String = "Bar" + } + new A + } + println(classExpr.run) + println(classExpr.run.getClass == classExpr.run.getClass) + println(classExpr2.run) + println(classExpr2.run.getClass) + } +}