From 86939bb7099a0770bd976f26ead8b5b28d71a65b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 14 Dec 2017 10:24:39 +0100 Subject: [PATCH 01/18] Avoid running sepparate compilation tests under legacy tests Those tests have never fully been supported, all files are compiled at the same time. --- compiler/test/dotty/tools/dotc/CompilerTest.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 = From f02156230fb02ee3a543422474a1b872819f2493 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 20 Dec 2017 07:33:45 +0100 Subject: [PATCH 02/18] Rename quote test files --- compiler/test/dotty/tools/dotc/FromTastyTests.scala | 8 ++++---- ...uotedMacroOverride.scala => quote-MacroOverride.scala} | 0 tests/pos/{quoted.scala => quote-0.scala} | 0 tests/pos/{quoteTest.scala => quote-1.scala} | 0 tests/pos/{liftable.scala => quote-liftable.scala} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename tests/neg/{quotedMacroOverride.scala => quote-MacroOverride.scala} (100%) rename tests/pos/{quoted.scala => quote-0.scala} (100%) rename tests/pos/{quoteTest.scala => quote-1.scala} (100%) rename tests/pos/{liftable.scala => quote-liftable.scala} (100%) diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index d08789d72a92..bf1f58b760f7 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -38,10 +38,10 @@ class FromTastyTests extends ParallelTesting { "i3000.scala", "i536.scala", "i974.scala", - "liftable.scala", - "quoteTest.scala", - "quoted.scala", - "stagedInterpreter.scala", + "quote-liftable.scala", + "quote-0.scala", + "quote-1.scala", + "quote-stagedInterpreter.scala", "superacc.scala", "t0231.scala", "t1203a.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/pos/quoted.scala b/tests/pos/quote-0.scala similarity index 100% rename from tests/pos/quoted.scala rename to tests/pos/quote-0.scala diff --git a/tests/pos/quoteTest.scala b/tests/pos/quote-1.scala similarity index 100% rename from tests/pos/quoteTest.scala rename to tests/pos/quote-1.scala diff --git a/tests/pos/liftable.scala b/tests/pos/quote-liftable.scala similarity index 100% rename from tests/pos/liftable.scala rename to tests/pos/quote-liftable.scala From ddb4449d9bc7a3f185ec26b12f6d7e6a85e38f4b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 5 Dec 2017 22:23:28 +0100 Subject: [PATCH 03/18] Add TASTY quote pickling for trees * Pickle/unpickle quotes * Add Splicer * Add tree interpreter for splicing staged expression * Add concrete implementations of Quoted --- .../dotc/core/tasty/DottyUnpickler.scala | 6 +- .../tools/dotc/core/tasty/TastyPrinter.scala | 2 +- .../tools/dotc/core/tasty/TastyString.scala | 20 +++ .../tools/dotc/core/tasty/TreePickler.scala | 2 +- .../tools/dotc/interpreter/Interpreter.scala | 129 ++++++++++++++++++ .../tools/dotc/interpreter/RawExpr.scala | 7 + .../tools/dotc/interpreter/RawQuoted.scala | 7 + .../tools/dotc/interpreter/RawType.scala | 7 + .../tools/dotc/quoted/PickledQuotes.scala | 95 +++++++++++++ .../tools/dotc/quoted/QuoteUnpickler.scala | 23 ++++ .../tools/dotc/transform/ReifyQuotes.scala | 20 +-- .../dotty/tools/dotc/transform/Splicer.scala | 35 +++++ .../src/dotty/tools/dotc/typer/Typer.scala | 1 + library/src/scala/quoted/Expr.scala | 4 +- library/src/scala/quoted/Liftable.scala | 4 +- library/src/scala/quoted/Quoted.scala | 4 +- library/src/scala/quoted/TastyExpr.scala | 6 + library/src/scala/quoted/TastyQuoted.scala | 9 ++ library/src/scala/quoted/TastyType.scala | 6 + library/src/scala/quoted/ValueExpr.scala | 3 + .../src/scala/runtime/quoted/Unpickler.scala | 4 +- tests/neg/{quoteTest.scala => quote-0.scala} | 0 tests/neg/quote-spliceNonStaged.scala | 7 + tests/pending/pos/quotedSepComp/Macro_1.scala | 5 - tests/pending/pos/quotedSepComp/Test_2.scala | 5 - tests/pos/quote-1.scala | 1 - tests/pos/quote-assert/quoted_1.scala | 6 + tests/pos/quote-assert/quoted_2.scala | 18 +++ ...er.scala => quote-stagedInterpreter.scala} | 0 tests/run/quote-and-splice.check | 6 + tests/run/quote-and-splice/Macros_1.scala | 21 +++ tests/run/quote-and-splice/Test_2.scala | 16 +++ tests/run/quote-sep-comp.check | 2 + tests/run/quote-sep-comp/Macro_1.scala | 5 + tests/run/quote-sep-comp/Test_2.scala | 8 ++ 35 files changed, 458 insertions(+), 36 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala create mode 100644 compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala create mode 100644 compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala create mode 100644 compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala create mode 100644 compiler/src/dotty/tools/dotc/interpreter/RawType.scala create mode 100644 compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala create mode 100644 compiler/src/dotty/tools/dotc/quoted/QuoteUnpickler.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/Splicer.scala create mode 100644 library/src/scala/quoted/TastyExpr.scala create mode 100644 library/src/scala/quoted/TastyQuoted.scala create mode 100644 library/src/scala/quoted/TastyType.scala create mode 100644 library/src/scala/quoted/ValueExpr.scala rename tests/neg/{quoteTest.scala => quote-0.scala} (100%) create mode 100644 tests/neg/quote-spliceNonStaged.scala delete mode 100644 tests/pending/pos/quotedSepComp/Macro_1.scala delete mode 100644 tests/pending/pos/quotedSepComp/Test_2.scala create mode 100644 tests/pos/quote-assert/quoted_1.scala create mode 100644 tests/pos/quote-assert/quoted_2.scala rename tests/pos/{stagedInterpreter.scala => quote-stagedInterpreter.scala} (100%) create mode 100644 tests/run/quote-and-splice.check create mode 100644 tests/run/quote-and-splice/Macros_1.scala create mode 100644 tests/run/quote-and-splice/Test_2.scala create mode 100644 tests/run/quote-sep-comp.check create mode 100644 tests/run/quote-sep-comp/Macro_1.scala create mode 100644 tests/run/quote-sep-comp/Test_2.scala 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/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/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala new file mode 100644 index 000000000000..6cae92689543 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -0,0 +1,95 @@ +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.ValueExpr[_] => 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 splices = expr.args.map { + case arg: quoted.Quoted => quotedToTree(arg) + case arg => arg + } + val unpickled = unpickle(tastyBytes, splices) + 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/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/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 a562e69acc00..f7ba50435094 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/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index ef6652782dc7..58241792ddc1 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -1,7 +1,7 @@ package scala.quoted -class Expr[T] extends Quoted { - def unary_~ : T = ??? +abstract class Expr[T] extends Quoted { + def unary_~ : T = throw new Error("~ should have been compiled away") def run: T = ??? } diff --git a/library/src/scala/quoted/Liftable.scala b/library/src/scala/quoted/Liftable.scala index 9e64a7c1ed8e..950855454e2e 100644 --- a/library/src/scala/quoted/Liftable.scala +++ b/library/src/scala/quoted/Liftable.scala @@ -13,6 +13,6 @@ 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] = ??? + implicit def IntIsLiftable: Liftable[Int] = (x: Int) => new ValueExpr(x) + implicit def BooleanIsLiftable: Liftable[Boolean] = (x: Boolean) => new ValueExpr(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/quoted/ValueExpr.scala b/library/src/scala/quoted/ValueExpr.scala new file mode 100644 index 000000000000..59246b3bde67 --- /dev/null +++ b/library/src/scala/quoted/ValueExpr.scala @@ -0,0 +1,3 @@ +package scala.quoted + +final class ValueExpr[T <: AnyVal](val value: T) extends Expr[T] 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/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/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/quote-1.scala b/tests/pos/quote-1.scala index e3671a2ea3e3..8d2a3ee243a8 100644 --- a/tests/pos/quote-1.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..1b6cde45c700 --- /dev/null +++ b/tests/pos/quote-assert/quoted_2.scala @@ -0,0 +1,18 @@ +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/stagedInterpreter.scala b/tests/pos/quote-stagedInterpreter.scala similarity index 100% rename from tests/pos/stagedInterpreter.scala rename to tests/pos/quote-stagedInterpreter.scala diff --git a/tests/run/quote-and-splice.check b/tests/run/quote-and-splice.check new file mode 100644 index 000000000000..a9adb2078029 --- /dev/null +++ b/tests/run/quote-and-splice.check @@ -0,0 +1,6 @@ +3 +3 +4 +3 +1.0 +5.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..1f68fe8b8c73 --- /dev/null +++ b/tests/run/quote-and-splice/Test_2.scala @@ -0,0 +1,16 @@ +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)) + // FIXME +// 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) + } +} From c40a022b92ed5cbf97df77d99c468a5b86024c4e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 21 Dec 2017 17:27:43 +0100 Subject: [PATCH 04/18] Fix bug while unpickling splices in quotes --- .../src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 9 +++++++-- compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala | 6 +----- tests/run/quote-and-splice.check | 2 ++ tests/run/quote-and-splice/Test_2.scala | 5 ++--- 4 files changed, 12 insertions(+), 10 deletions(-) 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/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 6cae92689543..1245dfdeabcb 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -34,11 +34,7 @@ object PickledQuotes { /** Unpickle the tree contained in the TastyQuoted */ private def unpickleQuote(expr: quoted.TastyQuoted)(implicit ctx: Context): Tree = { val tastyBytes = TastyString.stringToTasty(expr.tasty) - val splices = expr.args.map { - case arg: quoted.Quoted => quotedToTree(arg) - case arg => arg - } - val unpickled = unpickle(tastyBytes, splices) + val unpickled = unpickle(tastyBytes, expr.args) unpickled match { // Expects `package _root_ { val ': Any = }` case PackageDef(_, (vdef: ValDef) :: Nil) => vdef.rhs } diff --git a/tests/run/quote-and-splice.check b/tests/run/quote-and-splice.check index a9adb2078029..83cf59cca312 100644 --- a/tests/run/quote-and-splice.check +++ b/tests/run/quote-and-splice.check @@ -4,3 +4,5 @@ 3 1.0 5.0 +25.0 +125.0 diff --git a/tests/run/quote-and-splice/Test_2.scala b/tests/run/quote-and-splice/Test_2.scala index 1f68fe8b8c73..a3e714a225c0 100644 --- a/tests/run/quote-and-splice/Test_2.scala +++ b/tests/run/quote-and-splice/Test_2.scala @@ -8,9 +8,8 @@ object Test { println(macro3(1)) println(power(0, 5)) println(power(1, 5)) - // FIXME -// println(power(2, 5)) -// println(power(3, 5)) + println(power(2, 5)) + println(power(3, 5)) } } From c80ea20fd46a40a4eb06b8a85170626e66e16a49 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 22 Dec 2017 16:23:02 +0100 Subject: [PATCH 05/18] Implement primitive liftable --- library/src/scala/quoted/Liftable.scala | 8 +++++++- tests/pos/quote-liftable.scala | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/library/src/scala/quoted/Liftable.scala b/library/src/scala/quoted/Liftable.scala index 950855454e2e..69e586b1f90c 100644 --- a/library/src/scala/quoted/Liftable.scala +++ b/library/src/scala/quoted/Liftable.scala @@ -13,6 +13,12 @@ abstract class Liftable[T] { * gives an alternative implementation using just the basic staging system. */ object Liftable { - implicit def IntIsLiftable: Liftable[Int] = (x: Int) => new ValueExpr(x) 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) } diff --git a/tests/pos/quote-liftable.scala b/tests/pos/quote-liftable.scala index 02023994d9e7..07f688daff78 100644 --- a/tests/pos/quote-liftable.scala +++ b/tests/pos/quote-liftable.scala @@ -24,5 +24,14 @@ 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]) + val xs: Expr[List[Int]] = 1 :: 2 :: 3 :: Nil } From 1674cd8a83910835148c795179b2b87328dcc985 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 22 Dec 2017 16:50:08 +0100 Subject: [PATCH 06/18] Add String to Liftable expressions and abstract over all primitives --- .../src/dotty/tools/dotc/quoted/PickledQuotes.scala | 2 +- library/src/scala/quoted/Liftable.scala | 11 +++++++++++ library/src/scala/quoted/ValueExpr.scala | 3 --- tests/pos/quote-liftable.scala | 1 + 4 files changed, 13 insertions(+), 4 deletions(-) delete mode 100644 library/src/scala/quoted/ValueExpr.scala diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 1245dfdeabcb..538fa2235cac 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -27,7 +27,7 @@ object PickledQuotes { /** 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.ValueExpr[_] => Literal(Constant(expr.value)) + case expr: quoted.Liftable.PrimitiveExpr[_] => Literal(Constant(expr.value)) case expr: RawQuoted => expr.tree } diff --git a/library/src/scala/quoted/Liftable.scala b/library/src/scala/quoted/Liftable.scala index 69e586b1f90c..dc1df108373a 100644 --- a/library/src/scala/quoted/Liftable.scala +++ b/library/src/scala/quoted/Liftable.scala @@ -13,6 +13,13 @@ abstract class Liftable[T] { * gives an alternative implementation using just the basic staging system. */ object Liftable { + + 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) @@ -21,4 +28,8 @@ object Liftable { 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/ValueExpr.scala b/library/src/scala/quoted/ValueExpr.scala deleted file mode 100644 index 59246b3bde67..000000000000 --- a/library/src/scala/quoted/ValueExpr.scala +++ /dev/null @@ -1,3 +0,0 @@ -package scala.quoted - -final class ValueExpr[T <: AnyVal](val value: T) extends Expr[T] diff --git a/tests/pos/quote-liftable.scala b/tests/pos/quote-liftable.scala index 07f688daff78..d3dd2ff57bf5 100644 --- a/tests/pos/quote-liftable.scala +++ b/tests/pos/quote-liftable.scala @@ -32,6 +32,7 @@ object Test { (1L: Expr[Long]) (1.0f: Expr[Float]) (1.0: Expr[Double]) + ("abc": Expr[String]) val xs: Expr[List[Int]] = 1 :: 2 :: 3 :: Nil } From 10753bcee28a9c434f94cc4daba08104b5781efc Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 10:28:46 +0100 Subject: [PATCH 07/18] Re-enable valueTypeNameToJavaType --- .../src/dotty/tools/dotc/core/Definitions.scala | 8 ++++++-- .../dotty/tools/dotc/interpreter/Interpreter.scala | 14 ++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 19dab5bac91a..84c89f727255 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1047,7 +1047,7 @@ class Definitions { // private val unboxedTypeRef = mutable.Map[TypeName, TypeRef]() // private val javaTypeToValueTypeRef = mutable.Map[Class[_], TypeRef]() -// private val valueTypeNameToJavaType = mutable.Map[TypeName, Class[_]]() + private val valueTypeNamesToJavaType = mutable.Map[TypeName, Class[_]]() private def valueTypeRef(name: String, boxed: TypeRef, jtype: Class[_], enc: Int, tag: Name): TypeRef = { val vcls = ctx.requiredClassRef(name) @@ -1056,7 +1056,7 @@ class Definitions { typeTags(vcls.name) = tag // unboxedTypeRef(boxed.name) = vcls // javaTypeToValueTypeRef(jtype) = vcls -// valueTypeNameToJavaType(vcls.name) = jtype + valueTypeNamesToJavaType(vcls.name) = jtype vcls } @@ -1066,6 +1066,10 @@ class Definitions { /** The JVM tag for `tp` if it's a primitive, `java.lang.Object` otherwise. */ def typeTag(tp: Type)(implicit ctx: Context): Name = typeTags(scalaClassName(tp)) + /** The `Class[_]` of a primitive value type name */ + def valueTypeNameToJavaType(name: TypeName)(implicit ctx: Context): Option[Class[_]] = + valueTypeNamesToJavaType.get(if (name.firstPart eq nme.scala_) name.lastPart.toTypeName else name) + type PrimitiveClassEnc = Int val ByteEnc = 2 diff --git a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala index 69352ad64190..653b8943c835 100644 --- a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala @@ -110,16 +110,10 @@ class Interpreter(implicit ctx: Context) { /** 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) + defn.valueTypeNameToJavaType(param) match { + case Some(clazz) => clazz + case None => classLoader.loadClass(param.toString) + } } } From a15fc900fc39d924cb5ba113b2b9bd7f7b911e54 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 10:29:25 +0100 Subject: [PATCH 08/18] Disable spourious `containsQuotesOrSplices = true` --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f7ba50435094..a562e69acc00 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1074,7 +1074,6 @@ 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 From e4c2a336f792bb9db6236ea268ad8a2d803d9593 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 10:34:21 +0100 Subject: [PATCH 09/18] Rename QuoteUnpickler to TastyUnpickler --- compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala | 2 +- .../{QuoteUnpickler.scala => TastyUnpickler.scala} | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) rename compiler/src/dotty/tools/dotc/quoted/{QuoteUnpickler.scala => TastyUnpickler.scala} (75%) diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 538fa2235cac..bf48f0da5acf 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -79,7 +79,7 @@ object PickledQuotes { /** 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) + val unpickler = new dotty.tools.dotc.quoted.TastyUnpickler(bytes, splices) unpickler.enter(roots = Set(defn.RootPackage)) val tree = unpickler.body.head if (pickling ne noPrinter) { diff --git a/compiler/src/dotty/tools/dotc/quoted/QuoteUnpickler.scala b/compiler/src/dotty/tools/dotc/quoted/TastyUnpickler.scala similarity index 75% rename from compiler/src/dotty/tools/dotc/quoted/QuoteUnpickler.scala rename to compiler/src/dotty/tools/dotc/quoted/TastyUnpickler.scala index 563e9b9510f6..892aa332f569 100644 --- a/compiler/src/dotty/tools/dotc/quoted/QuoteUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/quoted/TastyUnpickler.scala @@ -1,11 +1,12 @@ package dotty.tools.dotc.quoted import dotty.tools.dotc.core.tasty._ +import dotty.tools.dotc.core.tasty.TastyUnpickler.NameTable -object QuoteUnpickler { +object TastyUnpickler { class QuotedTreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], splices: Seq[Any]) extends DottyUnpickler.TreeSectionUnpickler(posUnpickler) { - override def unpickle(reader: TastyReader, nameAtRef: TastyUnpickler.NameTable) = + override def unpickle(reader: TastyReader, nameAtRef: NameTable) = new TreeUnpickler(reader, nameAtRef, posUnpickler, splices) } } @@ -14,9 +15,9 @@ object QuoteUnpickler { * @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) { +class TastyUnpickler(bytes: Array[Byte], splices: Seq[Any]) extends DottyUnpickler(bytes) { import DottyUnpickler._ - import QuoteUnpickler._ + import TastyUnpickler._ protected override def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler]): TreeSectionUnpickler = new QuotedTreeSectionUnpickler(posUnpicklerOpt, splices) From 29ef05d473e00e852f50c36212c9d3b6c3fb5589 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 10:35:16 +0100 Subject: [PATCH 10/18] Add comments to `RawQuote`s --- compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala | 5 ++--- compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala | 5 +++++ compiler/src/dotty/tools/dotc/interpreter/RawType.scala | 5 ++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala b/compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala index d6296c0450c8..3bbb1240f850 100644 --- a/compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala +++ b/compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala @@ -2,6 +2,5 @@ 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})" -} +/** Expression `quoted.Expr[_]` for which its internal representation is its tree. */ +final class RawExpr(val tree: tpd.Tree) extends quoted.Expr[Any] with RawQuoted diff --git a/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala b/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala index ec3c6843c2d1..7f527e95a925 100644 --- a/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala +++ b/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala @@ -2,6 +2,11 @@ package dotty.tools.dotc.interpreter import dotty.tools.dotc.ast.tpd +/** Quoted `quoted.Quoted` for which its internal representation is its tree. + * - Used for trees that cannot be serialized, such as references to local symbols that will be spliced in. + * - Used for trees that do not need to be serialized to avoid the overhead of serialization/deserialization. + */ trait RawQuoted extends quoted.Quoted { def tree: tpd.Tree + override def toString: String = s"${this.getClass.getName}(${tree.toString})" } diff --git a/compiler/src/dotty/tools/dotc/interpreter/RawType.scala b/compiler/src/dotty/tools/dotc/interpreter/RawType.scala index 7492b53fbdfd..3182d186abbf 100644 --- a/compiler/src/dotty/tools/dotc/interpreter/RawType.scala +++ b/compiler/src/dotty/tools/dotc/interpreter/RawType.scala @@ -2,6 +2,5 @@ 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})" -} +/** Type `quoted.Type[_]` for which its internal representation is its type tree. */ +final class RawType(val tree: tpd.Tree) extends quoted.Type[Any] with RawQuoted From 65acf1cbf2b1eeef5b905d739a4c35c190113b55 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 10:36:15 +0100 Subject: [PATCH 11/18] Replace `PrimitiveExpr`s by the sinlge class `ConstantExpr` --- .../tools/dotc/quoted/PickledQuotes.scala | 4 +-- library/src/scala/quoted/Liftable.scala | 26 +++++++------------ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index bf48f0da5acf..76ec891e5e89 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -24,10 +24,10 @@ object PickledQuotes { } } - /** Transform the expression into it's fully spliced Tree */ + /** Transform the expression into its 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: quoted.Liftable.ConstantExpr[_] => Literal(Constant(expr.value)) case expr: RawQuoted => expr.tree } diff --git a/library/src/scala/quoted/Liftable.scala b/library/src/scala/quoted/Liftable.scala index dc1df108373a..f9fd08ef6a74 100644 --- a/library/src/scala/quoted/Liftable.scala +++ b/library/src/scala/quoted/Liftable.scala @@ -14,22 +14,16 @@ abstract class Liftable[T] { */ object Liftable { - sealed abstract class PrimitiveExpr[T] extends Expr[T] { - def value: T - } + final class ConstantExpr[T] private[Liftable](val value: T) extends Expr[T] - private class ValueExpr[T <: AnyVal](val value: T) extends PrimitiveExpr[T] + implicit def BooleanIsLiftable: Liftable[Boolean] = (x: Boolean) => new ConstantExpr(x) + implicit def ByteLiftable: Liftable[Byte] = (x: Byte) => new ConstantExpr(x) + implicit def CharIsLiftable: Liftable[Char] = (x: Char) => new ConstantExpr(x) + implicit def ShortIsLiftable: Liftable[Short] = (x: Short) => new ConstantExpr(x) + implicit def IntIsLiftable: Liftable[Int] = (x: Int) => new ConstantExpr(x) + implicit def LongIsLiftable: Liftable[Long] = (x: Long) => new ConstantExpr(x) + implicit def FloatIsLiftable: Liftable[Float] = (x: Float) => new ConstantExpr(x) + implicit def DoubleIsLiftable: Liftable[Double] = (x: Double) => new ConstantExpr(x) - 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) + implicit def StringIsLiftable: Liftable[String] = (x: String) => new ConstantExpr(x) } From 2ff30d68483a89d25516358b4c9ec5d424f55f20 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 10:39:04 +0100 Subject: [PATCH 12/18] Avoid printing encoded TASTY in `TastyExpr` and `TastyType` --- library/src/scala/quoted/TastyExpr.scala | 4 +++- library/src/scala/quoted/TastyType.scala | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/library/src/scala/quoted/TastyExpr.scala b/library/src/scala/quoted/TastyExpr.scala index 781bd6e03ba1..4dd5448da031 100644 --- a/library/src/scala/quoted/TastyExpr.scala +++ b/library/src/scala/quoted/TastyExpr.scala @@ -3,4 +3,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 +final case class TastyExpr[T](tasty: Pickled, args: Seq[Any]) extends Expr[T] with TastyQuoted { + override def toString(): String = s"TastyExpr(, $args)" +} diff --git a/library/src/scala/quoted/TastyType.scala b/library/src/scala/quoted/TastyType.scala index 68c1833a9734..b63b30745ace 100644 --- a/library/src/scala/quoted/TastyType.scala +++ b/library/src/scala/quoted/TastyType.scala @@ -3,4 +3,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 +final case class TastyType[T](tasty: Pickled, args: Seq[Any]) extends Type[T] with TastyQuoted { + override def toString(): String = s"TastyType(, $args)" +} From 2bd3b6f341df2a5eb2d5db0832795fa05968f6c9 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 11:03:45 +0100 Subject: [PATCH 13/18] Simplify `RawQuoted` instantiations --- .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 4 ++-- .../src/dotty/tools/dotc/interpreter/Interpreter.scala | 4 ++-- .../src/dotty/tools/dotc/interpreter/RawExpr.scala | 6 ------ .../src/dotty/tools/dotc/interpreter/RawQuoted.scala | 10 ++++++++++ .../src/dotty/tools/dotc/interpreter/RawType.scala | 6 ------ 5 files changed, 14 insertions(+), 16 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala delete mode 100644 compiler/src/dotty/tools/dotc/interpreter/RawType.scala diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 1bda0c6bb730..cd95113b21dc 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -20,7 +20,7 @@ import config.Printers.pickling import typer.Checking import config.Config import dotty.tools.dotc.quoted.PickledQuotes -import dotty.tools.dotc.interpreter.RawExpr +import dotty.tools.dotc.interpreter.RawQuoted import scala.quoted.Expr /** Unpickler for typed trees @@ -1035,7 +1035,7 @@ class TreeUnpickler(reader: TastyReader, val splice = splices(idx) val expr = if (args.isEmpty) splice.asInstanceOf[Expr[_]] - else splice.asInstanceOf[Seq[Any] => Expr[_]](args.map(arg => new RawExpr(arg))) + else splice.asInstanceOf[Seq[Any] => Expr[_]](args.map(RawQuoted.apply)) 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 index 653b8943c835..ac2af9a3050c 100644 --- a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala @@ -54,9 +54,9 @@ class Interpreter(implicit ctx: Context) { try { tree match { case Apply(_, quote :: Nil) if tree.symbol eq defn.quoteMethod => - new RawExpr(quote) + RawQuoted(quote) case TypeApply(_, quote :: Nil) if tree.symbol eq defn.typeQuoteMethod => - new RawType(quote) + RawQuoted(quote) case Literal(Constant(c)) => c.asInstanceOf[AnyRef] diff --git a/compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala b/compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala deleted file mode 100644 index 3bbb1240f850..000000000000 --- a/compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala +++ /dev/null @@ -1,6 +0,0 @@ -package dotty.tools.dotc.interpreter - -import dotty.tools.dotc.ast.tpd - -/** Expression `quoted.Expr[_]` for which its internal representation is its tree. */ -final class RawExpr(val tree: tpd.Tree) extends quoted.Expr[Any] with RawQuoted diff --git a/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala b/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala index 7f527e95a925..db669d0c5f5e 100644 --- a/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala +++ b/compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala @@ -10,3 +10,13 @@ trait RawQuoted extends quoted.Quoted { def tree: tpd.Tree override def toString: String = s"${this.getClass.getName}(${tree.toString})" } + +object RawQuoted { + + def apply(tree: tpd.Tree): RawQuoted = + if (tree.isTerm) new RawExpr(tree) + else new RawType(tree) + + private final class RawExpr(val tree: tpd.Tree) extends quoted.Expr[Any] with RawQuoted + private final class RawType(val tree: tpd.Tree) extends quoted.Type[Any] with RawQuoted +} diff --git a/compiler/src/dotty/tools/dotc/interpreter/RawType.scala b/compiler/src/dotty/tools/dotc/interpreter/RawType.scala deleted file mode 100644 index 3182d186abbf..000000000000 --- a/compiler/src/dotty/tools/dotc/interpreter/RawType.scala +++ /dev/null @@ -1,6 +0,0 @@ -package dotty.tools.dotc.interpreter - -import dotty.tools.dotc.ast.tpd - -/** Type `quoted.Type[_]` for which its internal representation is its type tree. */ -final class RawType(val tree: tpd.Tree) extends quoted.Type[Any] with RawQuoted From 990e3d1ed3620962f3280344415d83268e91979b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 11:21:27 +0100 Subject: [PATCH 14/18] Implement extractor for quoted trees --- .../tools/dotc/interpreter/Interpreter.scala | 10 +++------- .../src/dotty/tools/dotc/quoted/Quoted.scala | 20 +++++++++++++++++++ .../tools/dotc/transform/ReifyQuotes.scala | 7 +++---- .../dotty/tools/dotc/transform/Splicer.scala | 4 ++-- 4 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/quoted/Quoted.scala diff --git a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala index ac2af9a3050c..470a22d13def 100644 --- a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala @@ -8,7 +8,7 @@ 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 dotty.tools.dotc.quoted.Quoted import scala.reflect.ClassTag import java.net.URLClassLoader @@ -53,13 +53,9 @@ class Interpreter(implicit ctx: Context) { private def interpretTreeImpl(tree: Tree): Object = { try { tree match { - case Apply(_, quote :: Nil) if tree.symbol eq defn.quoteMethod => - RawQuoted(quote) - case TypeApply(_, quote :: Nil) if tree.symbol eq defn.typeQuoteMethod => - RawQuoted(quote) + case Quoted(quotedTree) => RawQuoted(quotedTree) - case Literal(Constant(c)) => - c.asInstanceOf[AnyRef] + case Literal(Constant(c)) => c.asInstanceOf[AnyRef] case Apply(fn, args) if fn.symbol.isConstructor => val cls = fn.symbol.owner diff --git a/compiler/src/dotty/tools/dotc/quoted/Quoted.scala b/compiler/src/dotty/tools/dotc/quoted/Quoted.scala new file mode 100644 index 000000000000..4d83b0f1db61 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/Quoted.scala @@ -0,0 +1,20 @@ +package dotty.tools.dotc.quoted + +import dotty.tools.dotc.ast.Trees.GenericApply +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Types.Type +import dotty.tools.dotc.transform.SymUtils._ + +/** Extractors for quotes */ +object Quoted { + + /** Extracts the content of a quoted tree. + * The result can be the contents of a term ot type quote, which + * will return a term or type tree respectively. + */ + def unapply(tree: tpd.Tree)(implicit ctx: Context): Option[tpd.Tree] = tree match { + case tree: GenericApply[Type] if tree.symbol.isQuote => Some(tree.args.head) + case _ => None + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index e3fc30daa6b8..d3c9da597821 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -17,6 +17,7 @@ import NameKinds.OuterSelectName import scala.collection.mutable import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.quoted.PickledQuotes +import dotty.tools.dotc.quoted.Quoted /** Translates quoted terms and types to `unpickle` method calls. @@ -316,10 +317,8 @@ class ReifyQuotes extends MacroTransform { enteredSyms = enteredSyms.tail } tree match { - case Apply(fn, arg :: Nil) if fn.symbol == defn.quoteMethod => - quotation(arg, tree) - case TypeApply(fn, arg :: Nil) if fn.symbol == defn.typeQuoteMethod => - quotation(arg, tree) + case Quoted(quotedTree) => + quotation(quotedTree, tree) case Select(body, _) if tree.symbol.isSplice => splice(body, tree) case Block(stats, _) => diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index d21fe3d8b7cb..69843afd0bf1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -7,6 +7,7 @@ import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.interpreter._ import dotty.tools.dotc.quoted.PickledQuotes +import dotty.tools.dotc.quoted.Quoted import scala.quoted @@ -19,8 +20,7 @@ object Splicer { * 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 Quoted(quotedTree) => quotedTree case tree: RefTree => reflectiveSplice(tree) case tree: Apply => reflectiveSplice(tree) case tree: Inlined => reflectiveSplice(tree) From 2aa05ef6aac8d9efec169408a4ec12fd71111214 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 12:40:42 +0100 Subject: [PATCH 15/18] Re-work Interpreter --- .../tools/dotc/interpreter/Interpreter.scala | 147 +++++++++++------- tests/run/quote-and-splice.check | 1 + tests/run/quote-and-splice/Macros_1.scala | 3 + tests/run/quote-and-splice/Test_2.scala | 1 + 4 files changed, 99 insertions(+), 53 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala index 470a22d13def..4a416420b431 100644 --- a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala @@ -1,16 +1,22 @@ package dotty.tools.dotc package interpreter +import java.io.{PrintWriter, StringWriter} + 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.Names._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.quoted.Quoted +import dotty.tools.dotc.util.Positions.Position + import scala.reflect.ClassTag import java.net.URLClassLoader +import java.lang.reflect.Constructor +import java.lang.reflect.Method /** Tree interpreter that can interpret * * Literal constants @@ -37,11 +43,14 @@ class Interpreter(implicit ctx: Context) { interpretTreeImpl(tree) match { case obj: T => Some(obj) case obj => + // TODO upgrade to a full type tag check or something similar ctx.error(s"Interpreted tree returned a result of an unexpected type. Expected ${ct.runtimeClass} but was ${obj.getClass}", tree.pos) - throw new StopInterpretation + None } } catch { - case _: StopInterpretation => None + case ex: StopInterpretation => + ctx.error(ex.msg, ex.pos) + None } } @@ -50,56 +59,88 @@ class Interpreter(implicit ctx: Context) { * * 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 Quoted(quotedTree) => RawQuoted(quotedTree) - - 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 + private def interpretTreeImpl(tree: Tree): Object = { // TODO add environment + implicit val pos: Position = tree.pos + tree match { + case Quoted(quotedTree) => RawQuoted(quotedTree) + + case Literal(Constant(c)) => c.asInstanceOf[AnyRef] + + case Apply(fn, args) if fn.symbol.isConstructor => + val clazz = loadClass(fn.symbol.owner.symbol.fullName) + val paramClasses = paramsSig(fn.symbol) + val interpretedArgs = args.map(arg => interpretTreeImpl(arg)) + val constructor = getConstructor(clazz, paramClasses) + interpreted(constructor.newInstance(interpretedArgs: _*)) + + case _: RefTree | _: Apply if tree.symbol.isStatic => + val clazz = loadClass(tree.symbol.owner.companionModule.fullName) + val paramClasses = paramsSig(tree.symbol) + + val interpretedArgs = Array.newBuilder[Object] + def interpretArgs(tree: Tree): Unit = tree match { + case Apply(fn, args) => + interpretArgs(fn) + args.foreach(arg => interpretedArgs += interpretTreeImpl(arg)) + case _ => + } + interpretArgs(tree) + + val method = getMethod(clazz, tree.symbol.name, paramClasses) + interpreted(method.invoke(null, interpretedArgs.result(): _*)) + + // case tree: RefTree if tree.symbol.is(Module) => // TODO + // case Block(stats, expr) => // TODO evaluate bindings add environment + // case ValDef(_, _, rhs) => // TODO evaluate bindings add environment + + case Inlined(_, bindings, expansion) => + assert(bindings.isEmpty) // TODO evaluate bindings and add environment + interpretTreeImpl(expansion) + case _ => + // TODO Add more precise descriptions of why it could not be interpreted. + // This should be done after the full interpreter is implemented. + throw new StopInterpretation(s"Could not interpret ${tree.show}", tree.pos) + } + } + + private def loadClass(name: Name)(implicit pos: Position): Class[_] = { + try classLoader.loadClass(name.toString) + catch { + case _: ClassNotFoundException => + val msg = s"Could not find interpreted class $name in classpath" + throw new StopInterpretation(msg, pos) + } + } + + private def getMethod(clazz: Class[_], name: Name, paramClasses: List[Class[_]])(implicit pos: Position): Method = { + try clazz.getMethod(name.toString, paramClasses: _*) + catch { + case _: NoSuchMethodException => + val msg = s"Could not find interpreted method ${clazz.getCanonicalName}.$name with parameters $paramClasses" + throw new StopInterpretation(msg, pos) + } + } + + private def getConstructor(clazz: Class[_], paramClasses: List[Class[_]])(implicit pos: Position): Constructor[Object] = { + try clazz.getConstructor(paramClasses: _*).asInstanceOf[Constructor[Object]] + catch { + case _: NoSuchMethodException => + val msg = s"Could not find interpreted constructor of ${clazz.getCanonicalName} with parameters $paramClasses" + throw new StopInterpretation(msg, pos) + } + } + + private def interpreted[T](thunk: => T)(implicit pos: Position): T = { + try thunk + catch { case ex: RuntimeException => - ex.printStackTrace() - ctx.error("A runtime exception occurred while interpreting: " + ex.getMessage, tree.pos) - throw new StopInterpretation + val sw = new StringWriter() + sw.write("A runtime exception occurred while interpreting") + sw.write(ex.getMessage) + sw.write("\n") + ex.printStackTrace(new PrintWriter(sw)) + sw.write("\n") + throw new StopInterpretation(sw.toString, pos) } } @@ -114,6 +155,6 @@ class Interpreter(implicit ctx: Context) { } /** Exception that stops interpretation if some issue is found */ - private class StopInterpretation extends Exception + private class StopInterpretation(val msg: String, val pos: Position) extends Exception } diff --git a/tests/run/quote-and-splice.check b/tests/run/quote-and-splice.check index 83cf59cca312..e0180c559a3c 100644 --- a/tests/run/quote-and-splice.check +++ b/tests/run/quote-and-splice.check @@ -2,6 +2,7 @@ 3 4 3 +5 1.0 5.0 25.0 diff --git a/tests/run/quote-and-splice/Macros_1.scala b/tests/run/quote-and-splice/Macros_1.scala index e557bbf18bcd..a448e7591cb5 100644 --- a/tests/run/quote-and-splice/Macros_1.scala +++ b/tests/run/quote-and-splice/Macros_1.scala @@ -11,6 +11,9 @@ object Macros { inline def macro3(n: Int) = ~ macro3Impl('(n)) def macro3Impl(p: Expr[Int]) = '{ 2 + ~p } + inline def macro4(i: Int)(j: Int) = ~ macro4Impl('(i))('(j)) + def macro4Impl(i: Expr[Int])(j: Expr[Int]) = '{ ~i + ~j } + inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) def powerCode(n: Int, x: Expr[Double]): Expr[Double] = diff --git a/tests/run/quote-and-splice/Test_2.scala b/tests/run/quote-and-splice/Test_2.scala index a3e714a225c0..bb64ab07b05f 100644 --- a/tests/run/quote-and-splice/Test_2.scala +++ b/tests/run/quote-and-splice/Test_2.scala @@ -6,6 +6,7 @@ object Test { println(macro2(true)) println(macro2(false)) println(macro3(1)) + println(macro4(2)(3)) println(power(0, 5)) println(power(1, 5)) println(power(2, 5)) From 8b94c86a1382f4799b5ed9c8951fe301a7a432ec Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 12:52:13 +0100 Subject: [PATCH 16/18] Move quote unpickling to the core package --- .../tools/dotc/{ => core}/quoted/PickledQuotes.scala | 6 +++--- .../src/dotty/tools/dotc/{ => core}/quoted/Quoted.scala | 2 +- .../tools/dotc/{ => core}/quoted/TastyUnpickler.scala | 2 +- .../src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- .../src/dotty/tools/dotc/interpreter/Interpreter.scala | 2 +- .../src/dotty/tools/dotc/transform/ReifyQuotes.scala | 3 +-- compiler/src/dotty/tools/dotc/transform/Splicer.scala | 9 ++------- 7 files changed, 10 insertions(+), 16 deletions(-) rename compiler/src/dotty/tools/dotc/{ => core}/quoted/PickledQuotes.scala (95%) rename compiler/src/dotty/tools/dotc/{ => core}/quoted/Quoted.scala (94%) rename compiler/src/dotty/tools/dotc/{ => core}/quoted/TastyUnpickler.scala (96%) diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala similarity index 95% rename from compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala rename to compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index 76ec891e5e89..464d9b661294 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -1,4 +1,4 @@ -package dotty.tools.dotc.quoted +package dotty.tools.dotc.core.quoted import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.tpd @@ -8,7 +8,7 @@ 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.core.tasty.{TastyPickler, TastyPrinter, TastyString} import dotty.tools.dotc.interpreter.RawQuoted object PickledQuotes { @@ -79,7 +79,7 @@ object PickledQuotes { /** Unpickle TASTY bytes into it's tree */ private def unpickle(bytes: Array[Byte], splices: Seq[Any])(implicit ctx: Context): Tree = { - val unpickler = new dotty.tools.dotc.quoted.TastyUnpickler(bytes, splices) + val unpickler = new TastyUnpickler(bytes, splices) unpickler.enter(roots = Set(defn.RootPackage)) val tree = unpickler.body.head if (pickling ne noPrinter) { diff --git a/compiler/src/dotty/tools/dotc/quoted/Quoted.scala b/compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala similarity index 94% rename from compiler/src/dotty/tools/dotc/quoted/Quoted.scala rename to compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala index 4d83b0f1db61..db2b6ecf50ff 100644 --- a/compiler/src/dotty/tools/dotc/quoted/Quoted.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala @@ -1,4 +1,4 @@ -package dotty.tools.dotc.quoted +package dotty.tools.dotc.core.quoted import dotty.tools.dotc.ast.Trees.GenericApply import dotty.tools.dotc.ast.tpd diff --git a/compiler/src/dotty/tools/dotc/quoted/TastyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/quoted/TastyUnpickler.scala similarity index 96% rename from compiler/src/dotty/tools/dotc/quoted/TastyUnpickler.scala rename to compiler/src/dotty/tools/dotc/core/quoted/TastyUnpickler.scala index 892aa332f569..d47294ad7d76 100644 --- a/compiler/src/dotty/tools/dotc/quoted/TastyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/TastyUnpickler.scala @@ -1,4 +1,4 @@ -package dotty.tools.dotc.quoted +package dotty.tools.dotc.core.quoted import dotty.tools.dotc.core.tasty._ import dotty.tools.dotc.core.tasty.TastyUnpickler.NameTable diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index cd95113b21dc..f07f1204ed18 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -19,7 +19,7 @@ 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.core.quoted.PickledQuotes import dotty.tools.dotc.interpreter.RawQuoted import scala.quoted.Expr diff --git a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala index 4a416420b431..ba1e6055de2e 100644 --- a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala @@ -10,7 +10,7 @@ import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Names._ import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.quoted.Quoted +import dotty.tools.dotc.core.quoted.Quoted import dotty.tools.dotc.util.Positions.Position import scala.reflect.ClassTag diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index d3c9da597821..202db64662b8 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -16,8 +16,7 @@ import NameKinds.OuterSelectName import scala.collection.mutable import dotty.tools.dotc.core.StdNames._ -import dotty.tools.dotc.quoted.PickledQuotes -import dotty.tools.dotc.quoted.Quoted +import dotty.tools.dotc.core.quoted._ /** Translates quoted terms and types to `unpickle` method calls. diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 69843afd0bf1..271c807a8ec1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -1,15 +1,10 @@ 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.core.Contexts._ +import dotty.tools.dotc.core.quoted._ import dotty.tools.dotc.interpreter._ -import dotty.tools.dotc.quoted.PickledQuotes -import dotty.tools.dotc.quoted.Quoted - -import scala.quoted /** Utility class to slice quoted expressions */ object Splicer { From 82d36bd7b58f80b2dddb3d1d0dfeef54a79ef249 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 13:26:21 +0100 Subject: [PATCH 17/18] Encapsulate quoted types in top level `type` --- .../dotc/core/quoted/PickledQuotes.scala | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index 464d9b661294..293c0c843426 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -1,7 +1,7 @@ package dotty.tools.dotc.core.quoted import dotty.tools.dotc.ast.Trees._ -import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.{tpd, untpd} import dotty.tools.dotc.config.Printers._ import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Contexts._ @@ -35,22 +35,28 @@ object PickledQuotes { 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 = }` + unpickled match { case PackageDef(_, (vdef: ValDef) :: Nil) => vdef.rhs + case PackageDef(_, (tdef: TypeDef) :: Nil) => tdef.rhs } } - /** Encapsulate the tree in a top level `val` + /** Encapsulate the tree in a top level `val` or `type` * `` ==> `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. + * or + * `` ==> `package _root_ { type ' = }` */ 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) + def encapsulatedTerm = { + val sym = ctx.newSymbol(ctx.owner, "'".toTermName, Synthetic, defn.AnyType, coord = tree.pos) + ValDef(sym, tree).withPos(tree.pos) + } + + def encapsulatedType = + untpd.TypeDef("'".toTypeName, tree).withPos(tree.pos).withType(defn.AnyType) + + val quoted = if (tree.isTerm) encapsulatedTerm else encapsulatedType + PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], quoted :: Nil).withPos(tree.pos) } // TASTY picklingtests/pos/quoteTest.scala From f86bcc55411329662f8930e8c74a145b51050099 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Dec 2017 14:43:18 +0100 Subject: [PATCH 18/18] Add environment to the interpreter --- .../tools/dotc/interpreter/Interpreter.scala | 54 ++++++++++++++----- tests/run/quote-and-splice.check | 1 + tests/run/quote-and-splice/Macros_1.scala | 3 ++ tests/run/quote-and-splice/Test_2.scala | 1 + 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala index ba1e6055de2e..b00552f9511c 100644 --- a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala @@ -30,6 +30,8 @@ import java.lang.reflect.Method class Interpreter(implicit ctx: Context) { import tpd._ + type Env = Map[Symbol, Object] + 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) @@ -40,7 +42,7 @@ class Interpreter(implicit ctx: Context) { */ def interpretTree[T](tree: Tree)(implicit ct: ClassTag[T]): Option[T] = { try { - interpretTreeImpl(tree) match { + interpretTreeImpl(tree, Map.empty) match { case obj: T => Some(obj) case obj => // TODO upgrade to a full type tag check or something similar @@ -57,31 +59,37 @@ class Interpreter(implicit ctx: Context) { /** 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. + * If some error is encountered while interpreting a ctx.error is emitted and a StopInterpretation is thrown. */ - private def interpretTreeImpl(tree: Tree): Object = { // TODO add environment + private def interpretTreeImpl(tree: Tree, env: Env): Object = { + ctx.debuglog( + s"""Interpreting: + |${tree.show} + |$env + """.stripMargin) + implicit val pos: Position = tree.pos + tree match { case Quoted(quotedTree) => RawQuoted(quotedTree) - case Literal(Constant(c)) => c.asInstanceOf[AnyRef] + case Literal(Constant(c)) => c.asInstanceOf[Object] case Apply(fn, args) if fn.symbol.isConstructor => val clazz = loadClass(fn.symbol.owner.symbol.fullName) val paramClasses = paramsSig(fn.symbol) - val interpretedArgs = args.map(arg => interpretTreeImpl(arg)) + val interpretedArgs = args.map(arg => interpretTreeImpl(arg, env)) val constructor = getConstructor(clazz, paramClasses) interpreted(constructor.newInstance(interpretedArgs: _*)) case _: RefTree | _: Apply if tree.symbol.isStatic => val clazz = loadClass(tree.symbol.owner.companionModule.fullName) val paramClasses = paramsSig(tree.symbol) - val interpretedArgs = Array.newBuilder[Object] def interpretArgs(tree: Tree): Unit = tree match { case Apply(fn, args) => interpretArgs(fn) - args.foreach(arg => interpretedArgs += interpretTreeImpl(arg)) + args.foreach(arg => interpretedArgs += interpretTreeImpl(arg, env)) case _ => } interpretArgs(tree) @@ -89,20 +97,38 @@ class Interpreter(implicit ctx: Context) { val method = getMethod(clazz, tree.symbol.name, paramClasses) interpreted(method.invoke(null, interpretedArgs.result(): _*)) - // case tree: RefTree if tree.symbol.is(Module) => // TODO - // case Block(stats, expr) => // TODO evaluate bindings add environment - // case ValDef(_, _, rhs) => // TODO evaluate bindings add environment + case tree: Ident if env.contains(tree.symbol) => + env(tree.symbol) + + case Block(stats, expr) => + val env2 = stats.foldLeft(env)((acc, x) => interpretStat(x, acc)) + interpretTreeImpl(expr, env2) + + case tree: NamedArg => + interpretTreeImpl(tree.arg, env) case Inlined(_, bindings, expansion) => - assert(bindings.isEmpty) // TODO evaluate bindings and add environment - interpretTreeImpl(expansion) + val env2 = bindings.foldLeft(env)((acc, x) => interpretStat(x, acc)) + interpretTreeImpl(expansion, env2) + case _ => // TODO Add more precise descriptions of why it could not be interpreted. // This should be done after the full interpreter is implemented. - throw new StopInterpretation(s"Could not interpret ${tree.show}", tree.pos) + throw new StopInterpretation(s"Could not interpret ${tree.show}\n${tree}", tree.pos) } } + /** Interprets the statement and returns the updated environment */ + private def interpretStat(stat: Tree, env: Env): Env = stat match { + case tree: ValDef => + val obj = interpretTreeImpl(tree.rhs, env) + env.updated(tree.symbol, obj) + + case _ => + interpretTreeImpl(stat, env) + env + } + private def loadClass(name: Name)(implicit pos: Position): Class[_] = { try classLoader.loadClass(name.toString) catch { @@ -135,7 +161,7 @@ class Interpreter(implicit ctx: Context) { catch { case ex: RuntimeException => val sw = new StringWriter() - sw.write("A runtime exception occurred while interpreting") + sw.write("A runtime exception occurred while interpreting\n") sw.write(ex.getMessage) sw.write("\n") ex.printStackTrace(new PrintWriter(sw)) diff --git a/tests/run/quote-and-splice.check b/tests/run/quote-and-splice.check index e0180c559a3c..c559bdbee84f 100644 --- a/tests/run/quote-and-splice.check +++ b/tests/run/quote-and-splice.check @@ -3,6 +3,7 @@ 4 3 5 +9 1.0 5.0 25.0 diff --git a/tests/run/quote-and-splice/Macros_1.scala b/tests/run/quote-and-splice/Macros_1.scala index a448e7591cb5..d8aa24abeeaa 100644 --- a/tests/run/quote-and-splice/Macros_1.scala +++ b/tests/run/quote-and-splice/Macros_1.scala @@ -14,6 +14,9 @@ object Macros { inline def macro4(i: Int)(j: Int) = ~ macro4Impl('(i))('(j)) def macro4Impl(i: Expr[Int])(j: Expr[Int]) = '{ ~i + ~j } + inline def macro5(i: Int, j: Int) = ~ macro5Impl(j = '(j), i = '(i)) + def macro5Impl(i: Expr[Int], j: Expr[Int]) = '{ ~i + ~j } + inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) def powerCode(n: Int, x: Expr[Double]): Expr[Double] = diff --git a/tests/run/quote-and-splice/Test_2.scala b/tests/run/quote-and-splice/Test_2.scala index bb64ab07b05f..962441add308 100644 --- a/tests/run/quote-and-splice/Test_2.scala +++ b/tests/run/quote-and-splice/Test_2.scala @@ -7,6 +7,7 @@ object Test { println(macro2(false)) println(macro3(1)) println(macro4(2)(3)) + println(macro5(4, 5)) println(power(0, 5)) println(power(1, 5)) println(power(2, 5))