Skip to content

Commit 39e466d

Browse files
Merge pull request #3662 from dotty-staging/add-meta-with-tasty
Add TASTY pickling of quotes and implement `~` on quotes
2 parents 1a52057 + f86bcc5 commit 39e466d

39 files changed

+591
-50
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,7 +1047,7 @@ class Definitions {
10471047

10481048
// private val unboxedTypeRef = mutable.Map[TypeName, TypeRef]()
10491049
// private val javaTypeToValueTypeRef = mutable.Map[Class[_], TypeRef]()
1050-
// private val valueTypeNameToJavaType = mutable.Map[TypeName, Class[_]]()
1050+
private val valueTypeNamesToJavaType = mutable.Map[TypeName, Class[_]]()
10511051

10521052
private def valueTypeRef(name: String, boxed: TypeRef, jtype: Class[_], enc: Int, tag: Name): TypeRef = {
10531053
val vcls = ctx.requiredClassRef(name)
@@ -1056,7 +1056,7 @@ class Definitions {
10561056
typeTags(vcls.name) = tag
10571057
// unboxedTypeRef(boxed.name) = vcls
10581058
// javaTypeToValueTypeRef(jtype) = vcls
1059-
// valueTypeNameToJavaType(vcls.name) = jtype
1059+
valueTypeNamesToJavaType(vcls.name) = jtype
10601060
vcls
10611061
}
10621062

@@ -1066,6 +1066,10 @@ class Definitions {
10661066
/** The JVM tag for `tp` if it's a primitive, `java.lang.Object` otherwise. */
10671067
def typeTag(tp: Type)(implicit ctx: Context): Name = typeTags(scalaClassName(tp))
10681068

1069+
/** The `Class[_]` of a primitive value type name */
1070+
def valueTypeNameToJavaType(name: TypeName)(implicit ctx: Context): Option[Class[_]] =
1071+
valueTypeNamesToJavaType.get(if (name.firstPart eq nme.scala_) name.lastPart.toTypeName else name)
1072+
10691073
type PrimitiveClassEnc = Int
10701074

10711075
val ByteEnc = 2
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package dotty.tools.dotc.core.quoted
2+
3+
import dotty.tools.dotc.ast.Trees._
4+
import dotty.tools.dotc.ast.{tpd, untpd}
5+
import dotty.tools.dotc.config.Printers._
6+
import dotty.tools.dotc.core.Constants.Constant
7+
import dotty.tools.dotc.core.Contexts._
8+
import dotty.tools.dotc.core.Decorators._
9+
import dotty.tools.dotc.core.Flags._
10+
import dotty.tools.dotc.core.Symbols._
11+
import dotty.tools.dotc.core.tasty.{TastyPickler, TastyPrinter, TastyString}
12+
import dotty.tools.dotc.interpreter.RawQuoted
13+
14+
object PickledQuotes {
15+
import tpd._
16+
17+
/** Pickle the quote into a TASTY string */
18+
def pickleQuote(tree: Tree)(implicit ctx: Context): String = {
19+
if (ctx.reporter.hasErrors) "<error>"
20+
else {
21+
val encapsulated = encapsulateQuote(tree)
22+
val pickled = pickle(encapsulated)
23+
TastyString.tastyToString(pickled)
24+
}
25+
}
26+
27+
/** Transform the expression into its fully spliced Tree */
28+
def quotedToTree(expr: quoted.Quoted)(implicit ctx: Context): Tree = expr match {
29+
case expr: quoted.TastyQuoted => unpickleQuote(expr)
30+
case expr: quoted.Liftable.ConstantExpr[_] => Literal(Constant(expr.value))
31+
case expr: RawQuoted => expr.tree
32+
}
33+
34+
/** Unpickle the tree contained in the TastyQuoted */
35+
private def unpickleQuote(expr: quoted.TastyQuoted)(implicit ctx: Context): Tree = {
36+
val tastyBytes = TastyString.stringToTasty(expr.tasty)
37+
val unpickled = unpickle(tastyBytes, expr.args)
38+
unpickled match {
39+
case PackageDef(_, (vdef: ValDef) :: Nil) => vdef.rhs
40+
case PackageDef(_, (tdef: TypeDef) :: Nil) => tdef.rhs
41+
}
42+
}
43+
44+
/** Encapsulate the tree in a top level `val` or `type`
45+
* `<tree>` ==> `package _root_ { val ': Any = <tree> }`
46+
* or
47+
* `<type tree>` ==> `package _root_ { type ' = <tree tree> }`
48+
*/
49+
private def encapsulateQuote(tree: Tree)(implicit ctx: Context): Tree = {
50+
def encapsulatedTerm = {
51+
val sym = ctx.newSymbol(ctx.owner, "'".toTermName, Synthetic, defn.AnyType, coord = tree.pos)
52+
ValDef(sym, tree).withPos(tree.pos)
53+
}
54+
55+
def encapsulatedType =
56+
untpd.TypeDef("'".toTypeName, tree).withPos(tree.pos).withType(defn.AnyType)
57+
58+
val quoted = if (tree.isTerm) encapsulatedTerm else encapsulatedType
59+
PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], quoted :: Nil).withPos(tree.pos)
60+
}
61+
62+
// TASTY picklingtests/pos/quoteTest.scala
63+
64+
/** Pickle tree into it's TASTY bytes s*/
65+
private def pickle(tree: Tree)(implicit ctx: Context): Array[Byte] = {
66+
val pickler = new TastyPickler(defn.RootClass)
67+
val treePkl = pickler.treePkl
68+
treePkl.pickle(tree :: Nil)
69+
treePkl.compactify()
70+
pickler.addrOfTree = treePkl.buf.addrOfTree
71+
pickler.addrOfSym = treePkl.addrOfSym
72+
// if (tree.pos.exists)
73+
// new PositionPickler(pickler, treePkl.buf.addrOfTree).picklePositions(tree :: Nil)
74+
75+
// other pickle sections go here.
76+
val pickled = pickler.assembleParts()
77+
78+
if (pickling ne noPrinter) {
79+
println(i"**** pickled quote of \n${tree.show}")
80+
new TastyPrinter(pickled).printContents()
81+
}
82+
83+
pickled
84+
}
85+
86+
/** Unpickle TASTY bytes into it's tree */
87+
private def unpickle(bytes: Array[Byte], splices: Seq[Any])(implicit ctx: Context): Tree = {
88+
val unpickler = new TastyUnpickler(bytes, splices)
89+
unpickler.enter(roots = Set(defn.RootPackage))
90+
val tree = unpickler.body.head
91+
if (pickling ne noPrinter) {
92+
println(i"**** unpickled quote for \n${tree.show}")
93+
new TastyPrinter(bytes).printContents()
94+
}
95+
tree
96+
}
97+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package dotty.tools.dotc.core.quoted
2+
3+
import dotty.tools.dotc.ast.Trees.GenericApply
4+
import dotty.tools.dotc.ast.tpd
5+
import dotty.tools.dotc.core.Contexts.Context
6+
import dotty.tools.dotc.core.Types.Type
7+
import dotty.tools.dotc.transform.SymUtils._
8+
9+
/** Extractors for quotes */
10+
object Quoted {
11+
12+
/** Extracts the content of a quoted tree.
13+
* The result can be the contents of a term ot type quote, which
14+
* will return a term or type tree respectively.
15+
*/
16+
def unapply(tree: tpd.Tree)(implicit ctx: Context): Option[tpd.Tree] = tree match {
17+
case tree: GenericApply[Type] if tree.symbol.isQuote => Some(tree.args.head)
18+
case _ => None
19+
}
20+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dotty.tools.dotc.core.quoted
2+
3+
import dotty.tools.dotc.core.tasty._
4+
import dotty.tools.dotc.core.tasty.TastyUnpickler.NameTable
5+
6+
object TastyUnpickler {
7+
class QuotedTreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], splices: Seq[Any])
8+
extends DottyUnpickler.TreeSectionUnpickler(posUnpickler) {
9+
override def unpickle(reader: TastyReader, nameAtRef: NameTable) =
10+
new TreeUnpickler(reader, nameAtRef, posUnpickler, splices)
11+
}
12+
}
13+
14+
/** A class for unpickling quoted Tasty trees and symbols.
15+
* @param bytes the bytearray containing the Tasty file from which we unpickle
16+
* @param splices splices that will fill the holes in the quote
17+
*/
18+
class TastyUnpickler(bytes: Array[Byte], splices: Seq[Any]) extends DottyUnpickler(bytes) {
19+
import DottyUnpickler._
20+
import TastyUnpickler._
21+
22+
protected override def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler]): TreeSectionUnpickler =
23+
new QuotedTreeSectionUnpickler(posUnpicklerOpt, splices)
24+
}

compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,18 @@ class DottyUnpickler(bytes: Array[Byte]) extends ClassfileParser.Embedded {
3838

3939
val unpickler = new TastyUnpickler(bytes)
4040
private val posUnpicklerOpt = unpickler.unpickle(new PositionsSectionUnpickler)
41-
private val treeUnpickler = unpickler.unpickle(new TreeSectionUnpickler(posUnpicklerOpt)).get
41+
private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt)).get
4242

4343
/** Enter all toplevel classes and objects into their scopes
4444
* @param roots a set of SymDenotations that should be overwritten by unpickling
4545
*/
4646
def enter(roots: Set[SymDenotation])(implicit ctx: Context): Unit =
4747
treeUnpickler.enterTopLevel(roots)
4848

49+
protected def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler]): TreeSectionUnpickler = {
50+
new TreeSectionUnpickler(posUnpicklerOpt)
51+
}
52+
4953
/** Only used if `-Yretain-trees` is set. */
5054
private[this] var myBody: List[Tree] = _
5155
/** The unpickled trees, and the source file they come from. */

compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) {
6767
printName(); printTrees()
6868
case REFINEDtype =>
6969
printName(); printTree(); printTrees()
70-
case RETURN =>
70+
case RETURN | HOLE =>
7171
printNat(); printTrees()
7272
case METHODtype | POLYtype | TYPELAMBDAtype =>
7373
printTree()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package dotty.tools.dotc.core.tasty
2+
3+
/** Utils for String representation of TASTY */
4+
object TastyString {
5+
6+
/** Decode the TASTY String into TASTY bytes */
7+
def stringToTasty(str: String): Array[Byte] = {
8+
val bytes = new Array[Byte](str.length)
9+
for (i <- str.indices) bytes(i) = str.charAt(i).toByte
10+
bytes
11+
}
12+
13+
/** Encode TASTY bytes into a TASTY String */
14+
def tastyToString(bytes: Array[Byte]): String = {
15+
val chars = new Array[Char](bytes.length)
16+
for (i <- bytes.indices) chars(i) = (bytes(i) & 0xff).toChar
17+
new String(chars)
18+
}
19+
20+
}

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ class TreePickler(pickler: TastyPickler) {
300300
pickleName(sym.name)
301301
pickleParams
302302
tpt match {
303-
case templ: Template => pickleTree(tpt)
303+
case _: Template | _: Hole => pickleTree(tpt)
304304
case _ if tpt.isType => pickleTpt(tpt)
305305
}
306306
pickleTreeUnlessEmpty(rhs)

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ import scala.collection.{ mutable, immutable }
1919
import config.Printers.pickling
2020
import typer.Checking
2121
import config.Config
22+
import dotty.tools.dotc.core.quoted.PickledQuotes
23+
import dotty.tools.dotc.interpreter.RawQuoted
24+
import scala.quoted.Expr
2225

2326
/** Unpickler for typed trees
2427
* @param reader the reader from which to unpickle
@@ -1030,8 +1033,10 @@ class TreeUnpickler(reader: TastyReader,
10301033
val idx = readNat()
10311034
val args = until(end)(readTerm())
10321035
val splice = splices(idx)
1033-
if (args.isEmpty) splice.asInstanceOf[Tree]
1034-
else splice.asInstanceOf[Seq[Any] => Tree](args)
1036+
val expr =
1037+
if (args.isEmpty) splice.asInstanceOf[Expr[_]]
1038+
else splice.asInstanceOf[Seq[Any] => Expr[_]](args.map(RawQuoted.apply))
1039+
PickledQuotes.quotedToTree(expr)
10351040
case _ =>
10361041
readPathTerm()
10371042
}

0 commit comments

Comments
 (0)