Skip to content

Implement liftables #3694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ class CompilationUnit(val source: SourceFile) {
object CompilationUnit {

/** Make a compilation unit for top class `clsd` with the contends of the `unpickled` */
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit =
mkCompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()), unpickled, forceTrees)

/** Make a compilation unit the given unpickled tree */
def mkCompilationUnit(source: SourceFile, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
assert(!unpickled.isEmpty, unpickled)
val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()))
val unit1 = new CompilationUnit(source)
unit1.tpdTree = unpickled
if (forceTrees)
force.traverse(unit1.tpdTree)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,18 @@ 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
*/
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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
20 changes: 20 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala
Original file line number Diff line number Diff line change
@@ -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)
}

}
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 7 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
Expand Down
129 changes: 129 additions & 0 deletions compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala
Original file line number Diff line number Diff line change
@@ -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

}
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/interpreter/RawExpr.scala
Original file line number Diff line number Diff line change
@@ -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})"
}
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/interpreter/RawQuoted.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dotty.tools.dotc.interpreter

import dotty.tools.dotc.ast.tpd

trait RawQuoted extends quoted.Quoted {
def tree: tpd.Tree
}
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/interpreter/RawType.scala
Original file line number Diff line number Diff line change
@@ -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})"
}
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprCompilationUnit.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dotty.tools.dotc.quoted

import dotty.tools.dotc.CompilationUnit
import dotty.tools.dotc.util.NoSource

import scala.quoted.Expr

/* Compilation unit containing the contents of a quoted expression */
class ExprCompilationUnit(val expr: Expr[_]) extends CompilationUnit(NoSource) {
override def toString = s"Expr($expr)"
}
36 changes: 36 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprCompiler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dotty.tools.dotc
package quoted

import dotty.tools.backend.jvm.GenBCode
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.{Mode, Phases}
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.transform.Pickler
import dotty.tools.io.VirtualDirectory

/** Compiler that takes the contents of a quoted expression `expr` and produces
* a class file with `class ' { def apply: Object = expr }`.
*/
class ExprCompiler(directory: VirtualDirectory) extends Compiler {

/** A GenBCode phase that outputs to a virtual directory */
private class ExprGenBCode extends GenBCode {
override def phaseName = "genBCode"
override def outputDir(implicit ctx: Context) = directory
}

override def phases: List[List[Phase]] = {
val backendPhases = super.phases.dropWhile {
case List(_: Pickler) => false
case _ => true
}.tail

List(new ExprFrontend(putInClass = true)) ::
Phases.replace(classOf[GenBCode], _ => new ExprGenBCode :: Nil, backendPhases)
}

override def newRun(implicit ctx: Context): ExprRun = {
reset()
new ExprRun(this, ctx.addMode(Mode.ReadPositions))
}
}
15 changes: 15 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprDecompiler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dotty.tools.dotc.quoted

import java.io.PrintStream

import dotty.tools.dotc.core.Phases.Phase

/** Compiler that takes the contents of a quoted expression `expr` and produces outputs
* the pretty printed code.
*/
class ExprDecompiler(out: PrintStream) extends ExprCompiler(null) {
override def phases: List[List[Phase]] = List(
List(new ExprFrontend(putInClass = false)), // Create class from Expr
List(new QuotePrinter(out)) // Print all loaded classes
)
}
55 changes: 55 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprFrontend.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package dotty.tools.dotc.quoted

import dotty.tools.dotc.CompilationUnit
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Flags._
import dotty.tools.dotc.core.Scopes._
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.typer.FrontEnd
import dotty.tools.dotc.util.Positions._
import dotty.tools.dotc.util.SourceFile
import dotty.tools.io._

import scala.quoted.Expr

/** Frontend that receives scala.quoted.Expr as input */
class ExprFrontend(putInClass: Boolean) extends FrontEnd {
import tpd._
import PickledQuotes.quotedToTree

override def isTyper = false

override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = {
units.map {
case exprUnit: ExprCompilationUnit =>
val tree =
if (putInClass) inClass(exprUnit.expr)
else quotedToTree(exprUnit.expr)
val source = new SourceFile("", Seq())
CompilationUnit.mkCompilationUnit(source, tree, forceTrees = true)
}
}

/** Places the contents of expr in a compilable tree for a class
* with the following format.
* `package __root__ { class ' { def apply: Any = <expr> } }`
*/
private def inClass(expr: Expr[_])(implicit ctx: Context): Tree = {
val pos = Position(0)
val assocFile = new PlainFile(Path("<quote>"))

val cls = ctx.newCompleteClassSymbol(defn.RootClass, nme.QUOTE.toTypeName, EmptyFlags,
defn.ObjectType :: Nil, newScope, coord = pos, assocFile = assocFile).entered.asClass
cls.enter(ctx.newDefaultConstructor(cls), EmptyScope)
val meth = ctx.newSymbol(cls, nme.apply, Method, ExprType(defn.AnyType), coord = pos).entered

val quoted = quotedToTree(expr)(ctx.withOwner(meth))

val run = DefDef(meth, quoted)
val classTree = ClassDef(cls, DefDef(cls.primaryConstructor.asTerm), run :: Nil)
PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], classTree :: Nil).withPos(pos)
}
}
13 changes: 13 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprRun.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dotty.tools.dotc
package quoted

import dotty.tools.dotc.core.Contexts._

import scala.quoted._

class ExprRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) {
def compileExpr(expr: Expr[_]): Unit = {
val units = new ExprCompilationUnit(expr) :: Nil
compileUnits(units)
}
}
Loading