diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index c3d9326f180c..f93320a7ff90 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -31,9 +31,13 @@ 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) force.traverse(unit1.tpdTree) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index a4847e644765..94ee7897a90e 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -38,7 +38,7 @@ class DottyUnpickler(bytes: Array[Byte]) extends ClassfileParser.Embedded { val unpickler = new TastyUnpickler(bytes) private val posUnpicklerOpt = unpickler.unpickle(new PositionsSectionUnpickler) - private val treeUnpickler = unpickler.unpickle(new TreeSectionUnpickler(posUnpicklerOpt)).get + private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt)).get /** Enter all toplevel classes and objects into their scopes * @param roots a set of SymDenotations that should be overwritten by unpickling @@ -46,6 +46,10 @@ class DottyUnpickler(bytes: Array[Byte]) extends ClassfileParser.Embedded { def enter(roots: Set[SymDenotation])(implicit ctx: Context): Unit = treeUnpickler.enterTopLevel(roots) + protected def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler]): TreeSectionUnpickler = { + new TreeSectionUnpickler(posUnpicklerOpt) + } + /** Only used if `-Yretain-trees` is set. */ private[this] var myBody: List[Tree] = _ /** The unpickled trees, and the source file they come from. */ diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index bde686b1522e..1d0ab3ac40e9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -67,7 +67,7 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) { printName(); printTrees() case REFINEDtype => printName(); printTree(); printTrees() - case RETURN => + case RETURN | HOLE => printNat(); printTrees() case METHODtype | POLYtype | TYPELAMBDAtype => printTree() diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala new file mode 100644 index 000000000000..57c3a1e2955a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala @@ -0,0 +1,20 @@ +package dotty.tools.dotc.core.tasty + +/** Utils for String representation of TASTY */ +object TastyString { + + /** Decode the TASTY String into TASTY bytes */ + def stringToTasty(str: String): Array[Byte] = { + val bytes = new Array[Byte](str.length) + for (i <- str.indices) bytes(i) = str.charAt(i).toByte + bytes + } + + /** Encode TASTY bytes into a TASTY String */ + def tastyToString(bytes: Array[Byte]): String = { + val chars = new Array[Char](bytes.length) + for (i <- bytes.indices) chars(i) = (bytes(i) & 0xff).toChar + new String(chars) + } + +} diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 87c3cbb554b7..e7babd50ca31 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -300,7 +300,7 @@ class TreePickler(pickler: TastyPickler) { pickleName(sym.name) pickleParams tpt match { - case templ: Template => pickleTree(tpt) + case _: Template | _: Hole => pickleTree(tpt) case _ if tpt.isType => pickleTpt(tpt) } pickleTreeUnlessEmpty(rhs) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 51b33d90fc0f..1bda0c6bb730 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -19,6 +19,9 @@ import scala.collection.{ mutable, immutable } import config.Printers.pickling import typer.Checking import config.Config +import dotty.tools.dotc.quoted.PickledQuotes +import dotty.tools.dotc.interpreter.RawExpr +import scala.quoted.Expr /** Unpickler for typed trees * @param reader the reader from which to unpickle @@ -1030,8 +1033,10 @@ class TreeUnpickler(reader: TastyReader, val idx = readNat() val args = until(end)(readTerm()) val splice = splices(idx) - if (args.isEmpty) splice.asInstanceOf[Tree] - else splice.asInstanceOf[Seq[Any] => Tree](args) + val expr = + if (args.isEmpty) splice.asInstanceOf[Expr[_]] + else splice.asInstanceOf[Seq[Any] => Expr[_]](args.map(arg => new RawExpr(arg))) + PickledQuotes.quotedToTree(expr) case _ => readPathTerm() } diff --git a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala new file mode 100644 index 000000000000..69352ad64190 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala @@ -0,0 +1,129 @@ +package dotty.tools.dotc +package interpreter + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.core.Constants._ +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Symbols._ + +import scala.reflect.ClassTag +import java.net.URLClassLoader + +/** Tree interpreter that can interpret + * * Literal constants + * * Calls to static methods + * * New objects with explicit `new` keyword + * * Quoted code + * + * The interpreter assumes that all calls in the trees are to code that was + * previously compiled and is present in the classpath of the current context. + */ +class Interpreter(implicit ctx: Context) { + import tpd._ + + private[this] val classLoader = { + val urls = ctx.settings.classpath.value.split(':').map(cp => java.nio.file.Paths.get(cp).toUri.toURL) + new URLClassLoader(urls, getClass.getClassLoader) + } + + /** Returns the interpreted result of interpreting the code represented by the tree. + * Return Some of the result or None if some error happen during the interpretation. + */ + def interpretTree[T](tree: Tree)(implicit ct: ClassTag[T]): Option[T] = { + try { + interpretTreeImpl(tree) match { + case obj: T => Some(obj) + case obj => + ctx.error(s"Interpreted tree returned a result of an unexpected type. Expected ${ct.runtimeClass} but was ${obj.getClass}", tree.pos) + throw new StopInterpretation + } + } catch { + case _: StopInterpretation => None + } + } + + /** Returns the interpreted result of interpreting the code represented by the tree. + * Returns the result of the interpreted tree. + * + * If some error is encountered while interpreting a ctx.error is emited and a StopInterpretation is thrown. + */ + private def interpretTreeImpl(tree: Tree): Object = { + try { + tree match { + case Apply(_, quote :: Nil) if tree.symbol eq defn.quoteMethod => + new RawExpr(quote) + case TypeApply(_, quote :: Nil) if tree.symbol eq defn.typeQuoteMethod => + new RawType(quote) + + case Literal(Constant(c)) => + c.asInstanceOf[AnyRef] + + case Apply(fn, args) if fn.symbol.isConstructor => + val cls = fn.symbol.owner + val clazz = classLoader.loadClass(cls.symbol.fullName.toString) + val paramClasses = paramsSig(fn.symbol) + val args1: List[Object] = args.map(arg => interpretTreeImpl(arg)) + clazz.getConstructor(paramClasses: _*).newInstance(args1: _*).asInstanceOf[Object] + + case Apply(fun, args) if fun.symbol.isStatic => + val clazz = classLoader.loadClass(fun.symbol.owner.companionModule.fullName.toString) + val paramClasses = paramsSig(fun.symbol) + val args1: List[Object] = args.map(arg => interpretTreeImpl(arg)) + val method = clazz.getMethod(fun.symbol.name.toString, paramClasses: _*) + method.invoke(null, args1: _*) + + case tree: RefTree if tree.symbol.isStatic => + val clazz = classLoader.loadClass(tree.symbol.owner.companionModule.fullName.toString) + val method = clazz.getMethod(tree.name.toString) + method.invoke(null) + + case tree: RefTree if tree.symbol.is(Module) => + ??? // TODO + + case Inlined(_, bindings, expansion) => + if (bindings.nonEmpty) ??? // TODO evaluate bindings and add environment + interpretTreeImpl(expansion) + case _ => + val msg = + if (tree.tpe.derivesFrom(defn.QuotedExprClass)) "Quote needs to be explicit or a call to a static method" + else "Value needs to be a explicit or a call to a static method" + ctx.error(msg, tree.pos) + throw new StopInterpretation + } + } catch { + case ex: NoSuchMethodException => + ctx.error("Could not find interpreted method in classpath: " + ex.getMessage, tree.pos) + throw new StopInterpretation + case ex: ClassNotFoundException => + ctx.error("Could not find interpreted class in classpath: " + ex.getMessage, tree.pos) + throw new StopInterpretation + case ex: RuntimeException => + ex.printStackTrace() + ctx.error("A runtime exception occurred while interpreting: " + ex.getMessage, tree.pos) + throw new StopInterpretation + } + } + + /** List of classes of the parameters of the signature of `sym` */ + private def paramsSig(sym: Symbol): List[Class[_]] = { + sym.signature.paramsSig.map { param => + val paramString = param.toString + if (paramString == defn.BooleanClass.showFullName) classOf[Boolean] + else if (paramString == defn.ByteClass.showFullName) classOf[Byte] + else if (paramString == defn.CharClass.showFullName) classOf[Char] + else if (paramString == defn.ShortClass.showFullName) classOf[Short] + else if (paramString == defn.IntClass.showFullName) classOf[Int] + else if (paramString == defn.LongClass.showFullName) classOf[Long] + else if (paramString == defn.DoubleClass.showFullName) classOf[Float] + else if (paramString == defn.DoubleClass.showFullName) classOf[Double] + else classLoader.loadClass(paramString) + } + } + + /** Exception that stops interpretation if some issue is found */ + private class StopInterpretation extends Exception + +} diff --git a/compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala b/compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala new file mode 100644 index 000000000000..d6296c0450c8 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala @@ -0,0 +1,7 @@ +package dotty.tools.dotc.interpreter + +import dotty.tools.dotc.ast.tpd + +class RawExpr(val tree: tpd.Tree) extends quoted.Expr[Any] with RawQuoted { + override def toString: String = s"RawExpr(${tree.toString})" +} diff --git a/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala b/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala new file mode 100644 index 000000000000..ec3c6843c2d1 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala @@ -0,0 +1,7 @@ +package dotty.tools.dotc.interpreter + +import dotty.tools.dotc.ast.tpd + +trait RawQuoted extends quoted.Quoted { + def tree: tpd.Tree +} diff --git a/compiler/src/dotty/tools/dotc/interpreter/RawType.scala b/compiler/src/dotty/tools/dotc/interpreter/RawType.scala new file mode 100644 index 000000000000..7492b53fbdfd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/interpreter/RawType.scala @@ -0,0 +1,7 @@ +package dotty.tools.dotc.interpreter + +import dotty.tools.dotc.ast.tpd + +class RawType(val tree: tpd.Tree) extends quoted.Type[Any] with RawQuoted { + override def toString: String = s"RawType(${tree.toString})" +} 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..03372afc39e3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/ExprCompiler.scala @@ -0,0 +1,36 @@ +package dotty.tools.dotc +package quoted + +import dotty.tools.backend.jvm.GenBCode +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.{Mode, Phases} +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.transform.Pickler +import dotty.tools.io.VirtualDirectory + +/** 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 { + + /** 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 def phases: List[List[Phase]] = { + val backendPhases = super.phases.dropWhile { + case List(_: Pickler) => false + case _ => true + }.tail + + List(new ExprFrontend(putInClass = true)) :: + Phases.replace(classOf[GenBCode], _ => new ExprGenBCode :: Nil, backendPhases) + } + + override def newRun(implicit ctx: Context): ExprRun = { + reset() + new ExprRun(this, ctx.addMode(Mode.ReadPositions)) + } +} 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/ExprFrontend.scala b/compiler/src/dotty/tools/dotc/quoted/ExprFrontend.scala new file mode 100644 index 000000000000..56c73679647a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/ExprFrontend.scala @@ -0,0 +1,55 @@ +package dotty.tools.dotc.quoted + +import dotty.tools.dotc.CompilationUnit +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Scopes._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.typer.FrontEnd +import dotty.tools.dotc.util.Positions._ +import dotty.tools.dotc.util.SourceFile +import dotty.tools.io._ + +import scala.quoted.Expr + +/** Frontend that receives scala.quoted.Expr as input */ +class ExprFrontend(putInClass: Boolean) extends FrontEnd { + import tpd._ + import PickledQuotes.quotedToTree + + 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 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 = 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) + } +} diff --git a/compiler/src/dotty/tools/dotc/quoted/ExprRun.scala b/compiler/src/dotty/tools/dotc/quoted/ExprRun.scala new file mode 100644 index 000000000000..9230ce877382 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/ExprRun.scala @@ -0,0 +1,13 @@ +package dotty.tools.dotc +package quoted + +import dotty.tools.dotc.core.Contexts._ + +import scala.quoted._ + +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/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala new file mode 100644 index 000000000000..538fa2235cac --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -0,0 +1,91 @@ +package dotty.tools.dotc.quoted + +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.config.Printers._ +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.tasty._ +import dotty.tools.dotc.interpreter.RawQuoted + +object PickledQuotes { + import tpd._ + + /** Pickle the quote into a TASTY string */ + def pickleQuote(tree: Tree)(implicit ctx: Context): String = { + if (ctx.reporter.hasErrors) "" + else { + val encapsulated = encapsulateQuote(tree) + val pickled = pickle(encapsulated) + TastyString.tastyToString(pickled) + } + } + + /** Transform the expression into it's fully spliced Tree */ + def quotedToTree(expr: quoted.Quoted)(implicit ctx: Context): Tree = expr match { + case expr: quoted.TastyQuoted => unpickleQuote(expr) + case expr: quoted.Liftable.PrimitiveExpr[_] => Literal(Constant(expr.value)) + case expr: RawQuoted => expr.tree + } + + /** Unpickle the tree contained in the TastyQuoted */ + private def unpickleQuote(expr: quoted.TastyQuoted)(implicit ctx: Context): Tree = { + val tastyBytes = TastyString.stringToTasty(expr.tasty) + val unpickled = unpickle(tastyBytes, expr.args) + unpickled match { // Expects `package _root_ { val ': Any = }` + case PackageDef(_, (vdef: ValDef) :: Nil) => vdef.rhs + } + } + + /** Encapsulate the tree in a top level `val` + * `` ==> `package _root_ { val ': Any = }` + * + * Note: Trees for types are also encapsulated this way to preserve the holes in the tree. + * Encapsulating the type of the tree in a `type ' = ` can potentially + * contain references to the outer environment. + */ + private def encapsulateQuote(tree: Tree)(implicit ctx: Context): Tree = { + val sym = ctx.newSymbol(ctx.owner, "'".toTermName, Synthetic, defn.AnyType, coord = tree.pos) + val quotedVal = ValDef(sym, tree).withPos(tree.pos) + PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], quotedVal :: Nil).withPos(tree.pos) + } + + // TASTY picklingtests/pos/quoteTest.scala + + /** Pickle tree into it's TASTY bytes s*/ + private def pickle(tree: Tree)(implicit ctx: Context): Array[Byte] = { + val pickler = new TastyPickler(defn.RootClass) + val treePkl = pickler.treePkl + treePkl.pickle(tree :: Nil) + treePkl.compactify() + pickler.addrOfTree = treePkl.buf.addrOfTree + pickler.addrOfSym = treePkl.addrOfSym + // if (tree.pos.exists) + // new PositionPickler(pickler, treePkl.buf.addrOfTree).picklePositions(tree :: Nil) + + // other pickle sections go here. + val pickled = pickler.assembleParts() + + if (pickling ne noPrinter) { + println(i"**** pickled quote of \n${tree.show}") + new TastyPrinter(pickled).printContents() + } + + pickled + } + + /** Unpickle TASTY bytes into it's tree */ + private def unpickle(bytes: Array[Byte], splices: Seq[Any])(implicit ctx: Context): Tree = { + val unpickler = new QuoteUnpickler(bytes, splices) + unpickler.enter(roots = Set(defn.RootPackage)) + val tree = unpickler.body.head + if (pickling ne noPrinter) { + println(i"**** unpickled quote for \n${tree.show}") + new TastyPrinter(bytes).printContents() + } + tree + } +} 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..d19899331eb7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala @@ -0,0 +1,58 @@ +package dotty.tools.dotc.quoted + +import dotty.tools.dotc.Driver +import dotty.tools.dotc.core.Contexts.Context +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/QuoteUnpickler.scala b/compiler/src/dotty/tools/dotc/quoted/QuoteUnpickler.scala new file mode 100644 index 000000000000..563e9b9510f6 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/QuoteUnpickler.scala @@ -0,0 +1,23 @@ +package dotty.tools.dotc.quoted + +import dotty.tools.dotc.core.tasty._ + +object QuoteUnpickler { + class QuotedTreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], splices: Seq[Any]) + extends DottyUnpickler.TreeSectionUnpickler(posUnpickler) { + override def unpickle(reader: TastyReader, nameAtRef: TastyUnpickler.NameTable) = + new TreeUnpickler(reader, nameAtRef, posUnpickler, splices) + } +} + +/** A class for unpickling quoted Tasty trees and symbols. + * @param bytes the bytearray containing the Tasty file from which we unpickle + * @param splices splices that will fill the holes in the quote + */ +class QuoteUnpickler(bytes: Array[Byte], splices: Seq[Any]) extends DottyUnpickler(bytes) { + import DottyUnpickler._ + import QuoteUnpickler._ + + protected override def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler]): TreeSectionUnpickler = + new QuotedTreeSectionUnpickler(posUnpicklerOpt, splices) +} 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..6b63acd7c076 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/Runners.scala @@ -0,0 +1,10 @@ +package dotty.tools.dotc.quoted + +import scala.quoted.Expr +import scala.runtime.quoted._ + +/** Default runners for quoted expressions */ +object Runners { + implicit def runner[T]: Runner[T] = (expr: Expr[T]) => new QuoteDriver().run(expr) + implicit def show[T]: Show[T] = (expr: Expr[T]) => new QuoteDriver().show(expr) +} diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 49decaf67949..e3fc30daa6b8 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -13,7 +13,11 @@ import tasty.TreePickler.Hole import MegaPhase.MiniPhase import SymUtils._ import NameKinds.OuterSelectName + import scala.collection.mutable +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.quoted.PickledQuotes + /** Translates quoted terms and types to `unpickle` method calls. * Checks that the phase consistency principle (PCP) holds. @@ -29,17 +33,6 @@ class ReifyQuotes extends MacroTransform { protected def newTransformer(implicit ctx: Context): Transformer = new Reifier(inQuote = false, null, 0, new LevelInfo) - /** Serialize `tree`. Embedded splices are represented as nodes of the form - * - * Select(qual, sym) - * - * where `sym` is either `defn.QuotedExpr_~` or `defn.QuotedType_~`. For any splice, - * the `qual` part should not be pickled, since it will be added separately later - * as a splice. - */ - def pickleTree(tree: Tree, isType: Boolean)(implicit ctx: Context): String = - tree.show // TODO: replace with TASTY - private class LevelInfo { /** A map from locally defined symbols to the staging levels of their definitions */ val levelOf = new mutable.HashMap[Symbol, Int] @@ -276,7 +269,7 @@ class ReifyQuotes extends MacroTransform { ref(if (isType) defn.Unpickler_unpickleType else defn.Unpickler_unpickleExpr) .appliedToType(if (isType) body1.tpe else body1.tpe.widen) .appliedTo( - Literal(Constant(pickleTree(body1, isType))), + Literal(Constant(PickledQuotes.pickleQuote(body1))), SeqLiteral(splices, TypeTree(defn.AnyType))) } }.withPos(quote.pos) @@ -336,7 +329,8 @@ class ReifyQuotes extends MacroTransform { case Inlined(call, bindings, expansion @ Select(body, name)) if expansion.symbol.isSplice => // To maintain phase consistency, convert inlined expressions of the form // `{ bindings; ~expansion }` to `~{ bindings; expansion }` - transform(cpy.Select(expansion)(cpy.Inlined(tree)(call, bindings, body), name)) + if (level == 0) transform(Splicer.splice(cpy.Inlined(tree)(call, bindings, body))) + else transform(cpy.Select(expansion)(cpy.Inlined(tree)(call, bindings, body), name)) case _: Import => tree case tree: DefDef if tree.symbol.is(Macro) && level == 0 => diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala new file mode 100644 index 000000000000..d21fe3d8b7cb --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -0,0 +1,35 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.interpreter._ +import dotty.tools.dotc.quoted.PickledQuotes + +import scala.quoted + +/** Utility class to slice quoted expressions */ +object Splicer { + import tpd._ + + /** Splice the Tree for a Quoted expression. `~'(xyz)` becomes `xyz` + * and for `~xyz` the tree of `xyz` is interpreted for which the + * resulting expression is return as a `Tree` + */ + def splice(tree: Tree)(implicit ctx: Context): Tree = tree match { + case Apply(quote, quoted :: Nil) if quote.symbol == defn.quoteMethod => quoted + case TypeApply(quote, quoted :: Nil) if quote.symbol == defn.typeQuoteMethod => quoted + case tree: RefTree => reflectiveSplice(tree) + case tree: Apply => reflectiveSplice(tree) + case tree: Inlined => reflectiveSplice(tree) + } + + /** 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) + } + +} diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2648c2d183d3..6b9e6b141d28 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1074,6 +1074,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def typedQuote(tree: untpd.Quote, pt: Type)(implicit ctx: Context): Tree = track("typedQuote") { + ctx.compilationUnit.containsQuotesOrSplices = true val untpd.Quote(body) = tree val isType = body.isType val resultClass = if (isType) defn.QuotedTypeClass else defn.QuotedExprClass 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 e79121c7fe6e..7d20d5b9ba16 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -194,7 +194,10 @@ 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.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/CompilerTest.scala b/compiler/test/dotty/tools/dotc/CompilerTest.scala index 259e1487c1a7..2a3b3253742e 100644 --- a/compiler/test/dotty/tools/dotc/CompilerTest.scala +++ b/compiler/test/dotty/tools/dotc/CompilerTest.scala @@ -109,8 +109,12 @@ abstract class CompilerTest { if (runTest) log(s"WARNING: run tests can only be run by partest, JUnit just verifies compilation: $prefix$dirName") val (filePaths, javaFilePaths, normArgs, expErrors) = computeFilePathsAndExpErrors - compileWithJavac(javaFilePaths, Array.empty) // javac needs to run first on dotty-library - compileArgs(javaFilePaths ++ filePaths ++ normArgs, expErrors) + if (filePaths.exists(_.endsWith("_1.scala"))) { + log(s"WARNING: separate compilation test can not be run as legacy test: $prefix$dirName") + } else { + compileWithJavac(javaFilePaths, Array.empty) // javac needs to run first on dotty-library + compileArgs(javaFilePaths ++ filePaths ++ normArgs, expErrors) + } } def runDir(prefix: String, dirName: String, args: List[String] = Nil) (implicit defaultOptions: List[String]): Unit = 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/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index ef6652782dc7..3d65d5c458b5 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -1,8 +1,11 @@ package scala.quoted -class Expr[T] extends Quoted { - def unary_~ : T = ??? - def run: T = ??? +import scala.runtime.quoted.{Runner, Show} + +trait Expr[T] extends Quoted { + 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: Show[T]): String = runner.run(this) } object Expr { diff --git a/library/src/scala/quoted/Liftable.scala b/library/src/scala/quoted/Liftable.scala index 9e64a7c1ed8e..dc1df108373a 100644 --- a/library/src/scala/quoted/Liftable.scala +++ b/library/src/scala/quoted/Liftable.scala @@ -13,6 +13,23 @@ abstract class Liftable[T] { * gives an alternative implementation using just the basic staging system. */ object Liftable { - implicit def IntIsLiftable: Liftable[Int] = ??? - implicit def BooleanIsLiftable: Liftable[Boolean] = ??? + + sealed abstract class PrimitiveExpr[T] extends Expr[T] { + def value: T + } + + private class ValueExpr[T <: AnyVal](val value: T) extends PrimitiveExpr[T] + + implicit def BooleanIsLiftable: Liftable[Boolean] = (x: Boolean) => new ValueExpr(x) + implicit def ByteLiftable: Liftable[Byte] = (x: Byte) => new ValueExpr(x) + implicit def CharIsLiftable: Liftable[Char] = (x: Char) => new ValueExpr(x) + implicit def ShortIsLiftable: Liftable[Short] = (x: Short) => new ValueExpr(x) + implicit def IntIsLiftable: Liftable[Int] = (x: Int) => new ValueExpr(x) + implicit def LongIsLiftable: Liftable[Long] = (x: Long) => new ValueExpr(x) + implicit def FloatIsLiftable: Liftable[Float] = (x: Float) => new ValueExpr(x) + implicit def DoubleIsLiftable: Liftable[Double] = (x: Double) => new ValueExpr(x) + + private class StringExpr(val value: String) extends PrimitiveExpr[String] + + implicit def StringIsLiftable: Liftable[String] = (x: String) => new StringExpr(x) } diff --git a/library/src/scala/quoted/Quoted.scala b/library/src/scala/quoted/Quoted.scala index b425c0bfb874..537dd6221d51 100644 --- a/library/src/scala/quoted/Quoted.scala +++ b/library/src/scala/quoted/Quoted.scala @@ -1,6 +1,4 @@ package scala.quoted /** Common superclass of Expr and Type */ -class Quoted - - +abstract class Quoted diff --git a/library/src/scala/quoted/TastyExpr.scala b/library/src/scala/quoted/TastyExpr.scala new file mode 100644 index 000000000000..781bd6e03ba1 --- /dev/null +++ b/library/src/scala/quoted/TastyExpr.scala @@ -0,0 +1,6 @@ +package scala.quoted + +import scala.runtime.quoted.Unpickler.Pickled + +/** An Expr backed by a pickled TASTY tree */ +final case class TastyExpr[T](tasty: Pickled, args: Seq[Any]) extends Expr[T] with TastyQuoted diff --git a/library/src/scala/quoted/TastyQuoted.scala b/library/src/scala/quoted/TastyQuoted.scala new file mode 100644 index 000000000000..729fedd9a998 --- /dev/null +++ b/library/src/scala/quoted/TastyQuoted.scala @@ -0,0 +1,9 @@ +package scala.quoted + +import scala.runtime.quoted.Unpickler.Pickled + +/** A quote backed by a pickled TASTY tree */ +trait TastyQuoted extends Quoted { + def tasty: Pickled + def args: Seq[Any] +} diff --git a/library/src/scala/quoted/TastyType.scala b/library/src/scala/quoted/TastyType.scala new file mode 100644 index 000000000000..68c1833a9734 --- /dev/null +++ b/library/src/scala/quoted/TastyType.scala @@ -0,0 +1,6 @@ +package scala.quoted + +import scala.runtime.quoted.Unpickler.Pickled + +/** A Type backed by a pickled TASTY tree */ +final case class TastyType[T](tasty: Pickled, args: Seq[Any]) extends Type[T] with TastyQuoted diff --git a/library/src/scala/runtime/quoted/Runner.scala b/library/src/scala/runtime/quoted/Runner.scala new file mode 100644 index 000000000000..4f39c86adb78 --- /dev/null +++ b/library/src/scala/runtime/quoted/Runner.scala @@ -0,0 +1,9 @@ +package scala.runtime.quoted + +import scala.annotation.implicitNotFound +import scala.quoted.Expr + +@implicitNotFound("Could not find implicit Runner. Default runner can must be imported with `import dotty.tools.dotc.quoted.Runners._`") +trait Runner[T] { + def run(expr: Expr[T]): T +} diff --git a/library/src/scala/runtime/quoted/Show.scala b/library/src/scala/runtime/quoted/Show.scala new file mode 100644 index 000000000000..ec055a656277 --- /dev/null +++ b/library/src/scala/runtime/quoted/Show.scala @@ -0,0 +1,9 @@ +package scala.runtime.quoted + +import scala.annotation.implicitNotFound +import scala.quoted.Expr + +@implicitNotFound("Could not find implicit Show. Default runner can must be imported with `import dotty.tools.dotc.quoted.Runners._`") +trait Show[T] { + def run(expr: Expr[T]): String +} diff --git a/library/src/scala/runtime/quoted/Unpickler.scala b/library/src/scala/runtime/quoted/Unpickler.scala index 2073f7516f0c..ab24c9e7250f 100644 --- a/library/src/scala/runtime/quoted/Unpickler.scala +++ b/library/src/scala/runtime/quoted/Unpickler.scala @@ -13,10 +13,10 @@ object Unpickler { /** Unpickle `repr` which represents a pickled `Expr` tree, * replacing splice nodes with `args` */ - def unpickleExpr[T](repr: Pickled, args: Seq[Any]): Expr[T] = ??? + def unpickleExpr[T](repr: Pickled, args: Seq[Any]): Expr[T] = new TastyExpr[T](repr, args) /** Unpickle `repr` which represents a pickled `Type` tree, * replacing splice nodes with `args` */ - def unpickleType[T](repr: Pickled, args: Seq[Any]): Type[T] = ??? + def unpickleType[T](repr: Pickled, args: Seq[Any]): Type[T] = new TastyType[T](repr, args) } diff --git a/project/Build.scala b/project/Build.scala index d7a2e8995f66..f7c8801ca8b5 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,18 +639,19 @@ 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 args = args0.filter(arg => arg != "-repl" || arg != "-decompile") + val args = args0.filter(arg => arg != "-repl" && arg != "-decompile" && arg != "-with-compiler") val main = if (repl) "dotty.tools.repl.Main" else if (decompile) "dotty.tools.dotc.decompiler.Main" 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/neg/quoteTest.scala b/tests/neg/quote-0.scala similarity index 100% rename from tests/neg/quoteTest.scala rename to tests/neg/quote-0.scala diff --git a/tests/neg/quotedMacroOverride.scala b/tests/neg/quote-MacroOverride.scala similarity index 100% rename from tests/neg/quotedMacroOverride.scala rename to tests/neg/quote-MacroOverride.scala diff --git a/tests/neg/quote-spliceNonStaged.scala b/tests/neg/quote-spliceNonStaged.scala new file mode 100644 index 000000000000..b328aabac7b9 --- /dev/null +++ b/tests/neg/quote-spliceNonStaged.scala @@ -0,0 +1,7 @@ +package quotes +import scala.quoted._ + +object Quotes_1 { + def printHello: Expr[Unit] = '{ println("Hello") } + ~printHello // error +} diff --git a/tests/pending/pos/quotedSepComp/Macro_1.scala b/tests/pending/pos/quotedSepComp/Macro_1.scala deleted file mode 100644 index 205f63c937d8..000000000000 --- a/tests/pending/pos/quotedSepComp/Macro_1.scala +++ /dev/null @@ -1,5 +0,0 @@ -import scala.quoted._ -object Macros { - inline def assert(expr: => Boolean): Unit = ~ assertImpl('(expr)) - def assertImpl(expr: Expr[Boolean]) = '{ () } -} diff --git a/tests/pending/pos/quotedSepComp/Test_2.scala b/tests/pending/pos/quotedSepComp/Test_2.scala deleted file mode 100644 index 42a3748830a0..000000000000 --- a/tests/pending/pos/quotedSepComp/Test_2.scala +++ /dev/null @@ -1,5 +0,0 @@ -class Test { - import Macros._ - val x = 1 - assert(x != 0) -} diff --git a/tests/pos/quoted.scala b/tests/pos/quote-0.scala similarity index 94% rename from tests/pos/quoted.scala rename to tests/pos/quote-0.scala index 3155c353ca33..8372c6414cde 100644 --- a/tests/pos/quoted.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/quoteTest.scala b/tests/pos/quote-1.scala similarity index 99% rename from tests/pos/quoteTest.scala rename to tests/pos/quote-1.scala index e3671a2ea3e3..8d2a3ee243a8 100644 --- a/tests/pos/quoteTest.scala +++ b/tests/pos/quote-1.scala @@ -13,4 +13,3 @@ object Test { def g(es: Expr[String], t: Type[String]) = f('{ (~es + "!") :: Nil })('[List[~t]]) } - diff --git a/tests/pos/quote-assert/quoted_1.scala b/tests/pos/quote-assert/quoted_1.scala new file mode 100644 index 000000000000..7cb0e73f5bc4 --- /dev/null +++ b/tests/pos/quote-assert/quoted_1.scala @@ -0,0 +1,6 @@ +import scala.quoted._ + +object Macros { + def assertImpl(expr: Expr[Boolean]) = + '{ if !(~expr) then throw new AssertionError(s"failed assertion: ${~expr}") } +} diff --git a/tests/pos/quote-assert/quoted_2.scala b/tests/pos/quote-assert/quoted_2.scala new file mode 100644 index 000000000000..7d73cdc65a4a --- /dev/null +++ b/tests/pos/quote-assert/quoted_2.scala @@ -0,0 +1,19 @@ +import dotty.tools.dotc.quoted.Runners.runner +import scala.quoted._ +import Macros._ + +class Test { + + inline def assert(expr: => Boolean): Unit = + ~ assertImpl('(expr)) + + + val program = '{ + val x = 1 + assert(x != 0) + + ~assertImpl('(x != 0)) + } + + program.run +} diff --git a/tests/pos/liftable.scala b/tests/pos/quote-liftable.scala similarity index 81% rename from tests/pos/liftable.scala rename to tests/pos/quote-liftable.scala index 02023994d9e7..d3dd2ff57bf5 100644 --- a/tests/pos/liftable.scala +++ b/tests/pos/quote-liftable.scala @@ -24,5 +24,15 @@ object Test { } } + (true: Expr[Boolean]) + (1: Expr[Byte]) + ('a': Expr[Char]) + (1: Expr[Short]) + (1: Expr[Int]) + (1L: Expr[Long]) + (1.0f: Expr[Float]) + (1.0: Expr[Double]) + ("abc": Expr[String]) + val xs: Expr[List[Int]] = 1 :: 2 :: 3 :: Nil } diff --git a/tests/pos/stagedInterpreter.scala b/tests/pos/stagedInterpreter.scala deleted file mode 100644 index 2ba57be2b004..000000000000 --- a/tests/pos/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-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) + } +} diff --git a/tests/run/quote-and-splice.check b/tests/run/quote-and-splice.check new file mode 100644 index 000000000000..83cf59cca312 --- /dev/null +++ b/tests/run/quote-and-splice.check @@ -0,0 +1,8 @@ +3 +3 +4 +3 +1.0 +5.0 +25.0 +125.0 diff --git a/tests/run/quote-and-splice/Macros_1.scala b/tests/run/quote-and-splice/Macros_1.scala new file mode 100644 index 000000000000..e557bbf18bcd --- /dev/null +++ b/tests/run/quote-and-splice/Macros_1.scala @@ -0,0 +1,21 @@ +import scala.quoted._ + +object Macros { + + inline def macro1 = ~ macro1Impl + def macro1Impl = '(3) + + inline def macro2(inline p: Boolean) = ~ macro2Impl(p) + def macro2Impl(p: Boolean) = if (p) '(3) else '(4) + + inline def macro3(n: Int) = ~ macro3Impl('(n)) + def macro3Impl(p: Expr[Int]) = '{ 2 + ~p } + + inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) + + 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) } +} diff --git a/tests/run/quote-and-splice/Test_2.scala b/tests/run/quote-and-splice/Test_2.scala new file mode 100644 index 000000000000..a3e714a225c0 --- /dev/null +++ b/tests/run/quote-and-splice/Test_2.scala @@ -0,0 +1,15 @@ +object Test { + import Macros._ + + def main(args: Array[String]): Unit = { + println(macro1) + println(macro2(true)) + println(macro2(false)) + println(macro3(1)) + println(power(0, 5)) + println(power(1, 5)) + println(power(2, 5)) + println(power(3, 5)) + } + +} diff --git a/tests/run/quote-sep-comp.check b/tests/run/quote-sep-comp.check new file mode 100644 index 000000000000..da29283aaa47 --- /dev/null +++ b/tests/run/quote-sep-comp.check @@ -0,0 +1,2 @@ +true +false diff --git a/tests/run/quote-sep-comp/Macro_1.scala b/tests/run/quote-sep-comp/Macro_1.scala new file mode 100644 index 000000000000..c3ce7e2e1d53 --- /dev/null +++ b/tests/run/quote-sep-comp/Macro_1.scala @@ -0,0 +1,5 @@ +import scala.quoted._ +object Macros { + inline def assert2(expr: => Boolean): Unit = ~ assertImpl('(expr)) + def assertImpl(expr: Expr[Boolean]) = '{ println(~expr) } +} diff --git a/tests/run/quote-sep-comp/Test_2.scala b/tests/run/quote-sep-comp/Test_2.scala new file mode 100644 index 000000000000..805fb8a6b079 --- /dev/null +++ b/tests/run/quote-sep-comp/Test_2.scala @@ -0,0 +1,8 @@ +import Macros._ +object Test { + def main(args: Array[String]): Unit = { + val x = 1 + assert2(x != 0) + assert2(x == 0) + } +}