Skip to content

Encode pickled primitives directly using reflection #10232

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

Merged
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
25 changes: 15 additions & 10 deletions compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,18 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, scala.intern
case _: MethodType | _: PolyType => false
case _ => true
case _ => false
def asExpr: scala.quoted.Expr[Any] =
if self.isExpr then
new scala.internal.quoted.Expr(self, QuoteContextImpl.this.hashCode)
else self match
case TermTypeTest(self) => throw new Exception("Expected an expression. This is a partially applied Term. Try eta-expanding the term first.")
case _ => throw new Exception("Expected a Term but was: " + self)
end extension

extension [T](tree: Tree)
extension [T](self: Tree)
def asExprOf(using scala.quoted.Type[T])(using QuoteContext): scala.quoted.Expr[T] =
if tree.isExpr then
new scala.internal.quoted.Expr(tree, QuoteContextImpl.this.hashCode).asExprOf[T]
else tree match
case TermTypeTest(tree) => throw new Exception("Expected an expression. This is a partially applied Term. Try eta-expanding the term first.")
case _ => throw new Exception("Expected a Term but was: " + tree)
self.asExpr.asExprOf[T]
end extension

end TreeMethodsImpl

Expand Down Expand Up @@ -1564,7 +1567,7 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, scala.intern

type TypeRepr = dotc.core.Types.Type

object TypeRepr extends TypeModule:
object TypeRepr extends TypeReprModule:
def of[T <: AnyKind](using qtype: scala.quoted.Type[T]): TypeRepr =
qtype.asInstanceOf[scala.internal.quoted.Type[TypeTree]].typeTree.tpe
def typeConstructorOf(clazz: Class[?]): TypeRepr =
Expand All @@ -1589,7 +1592,7 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, scala.intern
dotc.core.Symbols.getClassIfDefined(clazz.getCanonicalName).typeRef
end TypeRepr

object TypeMethodsImpl extends TypeMethods:
object TypeReprMethodsImpl extends TypeReprMethods:
extension (self: TypeRepr):
def showExtractors: String =
Extractors.showType(using QuoteContextImpl.this)(self)
Expand All @@ -1600,7 +1603,9 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, scala.intern
def showAnsiColored: String =
SourceCode.showType(using QuoteContextImpl.this)(self)(SyntaxHighlight.ANSI)

def seal: scala.quoted.Type[_] =
def seal: scala.quoted.Type[_] = self.asType

def asType: scala.quoted.Type[?] =
new scala.internal.quoted.Type(Inferred(self), QuoteContextImpl.this.hashCode)

def =:=(that: TypeRepr): Boolean = self =:= that
Expand Down Expand Up @@ -1636,7 +1641,7 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, scala.intern
def appliedTo(targs: List[TypeRepr]): TypeRepr =
dotc.core.Types.decorateTypeApplications(self).appliedTo(targs)
end extension
end TypeMethodsImpl
end TypeReprMethodsImpl

type ConstantType = dotc.core.Types.ConstantType

Expand Down
59 changes: 54 additions & 5 deletions compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,41 @@ class ReifyQuotes extends MacroTransform {
}

private def pickledQuote(body: Tree, splices: List[Tree], originalTp: Type, isType: Boolean)(using Context) = {
/** Encode quote using Reflection.Literal
*
* Generate the code
* ```scala
* qctx => qctx.reflect.TreeMethods.asExpr(
* qctx.reflect.Literal.apply(x$1.reflect.Constant.<typeName>.apply(<literalValue>))
* ).asInstanceOf[scala.quoted.Expr[<body.type>]]
* ```
* this closure is always applied directly to the actual context and the BetaReduce phase removes it.
*/
def pickleAsLiteral(lit: Literal) = {
val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe)
val tpe = MethodType(defn.QuoteContextClass.typeRef :: Nil, exprType)
val meth = newSymbol(ctx.owner, UniqueName.fresh(nme.ANON_FUN), Synthetic | Method, tpe)
def mkConst(tss: List[List[Tree]]) = {
val reflect = tss.head.head.select("reflect".toTermName)
val typeName = body.tpe.typeSymbol.name
val literalValue =
if lit.const.tag == Constants.NullTag || lit.const.tag == Constants.UnitTag then Nil
else List(body)
val constant = reflect.select("Constant".toTermName).select(typeName.toTermName).select(nme.apply).appliedToArgs(literalValue)
val literal = reflect.select("Literal".toTermName).select(nme.apply).appliedTo(constant)
reflect.select("TreeMethods".toTermName).select("asExpr".toTermName).appliedTo(literal).asInstance(exprType)
}
Closure(meth, mkConst).withSpan(body.span)
}

def pickleAsValue(lit: Literal) = {
// TODO should all constants be pickled as Literals?
// Should examime the generated bytecode size to decide and performance
def liftedValue(lifter: Symbol) =
ref(lifter).appliedToType(originalTp).select(nme.toExpr).appliedTo(lit)
lit.const.tag match {
case Constants.NullTag => ref(defn.InternalQuotedExpr_null)
case Constants.UnitTag => ref(defn.InternalQuotedExpr_unit)
case Constants.NullTag => pickleAsLiteral(lit)
case Constants.UnitTag => pickleAsLiteral(lit)
case Constants.BooleanTag => liftedValue(defn.LiftableModule_BooleanLiftable)
case Constants.ByteTag => liftedValue(defn.LiftableModule_ByteLiftable)
case Constants.ShortTag => liftedValue(defn.LiftableModule_ShortLiftable)
Expand All @@ -170,14 +199,34 @@ class ReifyQuotes extends MacroTransform {
ref(unpickleMeth).appliedToType(originalTp).appliedTo(pickledQuote)
}

def taggedType(sym: Symbol) = ref(defn.InternalQuotedTypeModule).select(sym.name.toTermName)
/** Encode quote using Reflection.TypeRepr.typeConstructorOf
*
* Generate the code
* ```scala
* qctx => qctx.reflect.TypeReprMethods.asType(
* qctx.reflect.TypeRepr.typeConstructorOf(classOf[<type>]])
* ).asInstanceOf[scala.quoted.Type[<type>]]
* ```
* this closure is always applied directly to the actual context and the BetaReduce phase removes it.
*/
def taggedType() =
val typeType = defn.QuotedTypeClass.typeRef.appliedTo(body.tpe)
val classTree = TypeApply(ref(defn.Predef_classOf.termRef), body :: Nil)
val tpe = MethodType(defn.QuoteContextClass.typeRef :: Nil, typeType)
val meth = newSymbol(ctx.owner, UniqueName.fresh(nme.ANON_FUN), Synthetic | Method, tpe)
def mkConst(tss: List[List[Tree]]) = {
val reflect = tss.head.head.select("reflect".toTermName)
val typeRepr = reflect.select("TypeRepr".toTermName).select("typeConstructorOf".toTermName).appliedTo(classTree)
reflect.select("TypeReprMethods".toTermName).select("asType".toTermName).appliedTo(typeRepr).asInstance(typeType)
}
Closure(meth, mkConst).withSpan(body.span)

if (isType) {
if (splices.isEmpty && body.symbol.isPrimitiveValueClass) taggedType(body.symbol)
if (splices.isEmpty && body.symbol.isPrimitiveValueClass) taggedType()
else pickleAsTasty()
}
else getLiteral(body) match {
case Some(lit) => pickleAsLiteral(lit)
case Some(lit) => pickleAsValue(lit)
case _ => pickleAsTasty()
}
}
Expand Down
12 changes: 0 additions & 12 deletions library/src-bootstrapped/scala/internal/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,4 @@ object Expr {
qctx.asInstanceOf[QuoteContextInternal].exprMatch(scrutineeExpr, patternExpr).asInstanceOf[Option[Tup]]
}

/** Returns a null expresssion equivalent to `'{null}` */
def `null`: QuoteContext ?=> quoted.Expr[Null] = (using qctx) => {
import qctx.reflect._
Literal(Constant.Null()).seal.asInstanceOf[quoted.Expr[Null]]
}

/** Returns a unit expresssion equivalent to `'{}` or `'{()}` */
def Unit: QuoteContext ?=> quoted.Expr[Unit] = (using qctx) => {
import qctx.reflect._
Literal(Constant.Unit()).seal.asInstanceOf[quoted.Expr[Unit]]
}

}
30 changes: 0 additions & 30 deletions library/src-bootstrapped/scala/internal/quoted/Type.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,4 @@ object Type {
qctx.asInstanceOf[QuoteContextInternal].typeMatch(scrutineeType, patternType).asInstanceOf[Option[Tup]]
}


// TODO generalize following optimizations for all classes without parameters

def Unit: QuoteContext ?=> quoted.Type[Unit] =
qctx.reflect.TypeRepr.typeConstructorOf(classOf[Unit]).seal.asInstanceOf[quoted.Type[Unit]]

def Boolean: QuoteContext ?=> quoted.Type[Boolean] =
qctx.reflect.TypeRepr.typeConstructorOf(classOf[Boolean]).seal.asInstanceOf[quoted.Type[Boolean]]

def Byte: QuoteContext ?=> quoted.Type[Byte] =
qctx.reflect.TypeRepr.typeConstructorOf(classOf[Byte]).seal.asInstanceOf[quoted.Type[Byte]]

def Char: QuoteContext ?=> quoted.Type[Char] =
qctx.reflect.TypeRepr.typeConstructorOf(classOf[Char]).seal.asInstanceOf[quoted.Type[Char]]

def Short: QuoteContext ?=> quoted.Type[Short] =
qctx.reflect.TypeRepr.typeConstructorOf(classOf[Short]).seal.asInstanceOf[quoted.Type[Short]]

def Int: QuoteContext ?=> quoted.Type[Int] =
qctx.reflect.TypeRepr.typeConstructorOf(classOf[Int]).seal.asInstanceOf[quoted.Type[Int]]

def Long: QuoteContext ?=> quoted.Type[Long] =
qctx.reflect.TypeRepr.typeConstructorOf(classOf[Long]).seal.asInstanceOf[quoted.Type[Long]]

def Float: QuoteContext ?=> quoted.Type[Float] =
qctx.reflect.TypeRepr.typeConstructorOf(classOf[Float]).seal.asInstanceOf[quoted.Type[Float]]

def Double: QuoteContext ?=> quoted.Type[Double] =
qctx.reflect.TypeRepr.typeConstructorOf(classOf[Double]).seal.asInstanceOf[quoted.Type[Double]]

}
26 changes: 21 additions & 5 deletions library/src/scala/tasty/Reflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,15 @@ trait Reflection { reflection =>

/** Does this tree represent a valid expression? */
def isExpr: Boolean

/** Convert this tree to an `quoted.Expr[Any]` if the tree is a valid expression or throws */
def asExpr: scala.quoted.Expr[Any]
end extension

/** Convert this tree to an `quoted.Expr[T]` if the tree is a valid expression or throws */
extension [T](self: Tree)
// FIXME: remove QuoteContext from parameters
// TODO: Move Reflection inside QuoteContext as it can never be instantiated outside a QuoteContext
def asExprOf(using scala.quoted.Type[T])(using QuoteContext): scala.quoted.Expr[T]
}

Expand Down Expand Up @@ -1741,20 +1746,20 @@ trait Reflection { reflection =>
/** A type, type constructors, type bounds or NoPrefix */
type TypeRepr

val TypeRepr: TypeModule
val TypeRepr: TypeReprModule

trait TypeModule { this: TypeRepr.type =>
trait TypeReprModule { this: TypeRepr.type =>
/** Returns the type or kind (TypeRepr) of T */
def of[T <: AnyKind](using qtype: scala.quoted.Type[T]): TypeRepr

/** Returns the type constructor of the runtime (erased) class */
def typeConstructorOf(clazz: Class[?]): TypeRepr
}

given TypeMethods as TypeMethods = TypeMethodsImpl
protected val TypeMethodsImpl: TypeMethods
given TypeReprMethods as TypeReprMethods = TypeReprMethodsImpl
protected val TypeReprMethodsImpl: TypeReprMethods

trait TypeMethods {
trait TypeReprMethods {
extension (self: TypeRepr):

/** Shows the tree as extractors */
Expand All @@ -1769,6 +1774,17 @@ trait Reflection { reflection =>
/** Convert `TypeRepr` to an `quoted.Type[_]` */
def seal: scala.quoted.Type[_]

/** Convert this `TypeRepr` to an `Type[?]`
*
* Usage:
* ```
* typeRepr.asType match
* case '[$t] =>
* '{ val x: t = ... }
* ```
*/
def asType: scala.quoted.Type[?]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the difference with seal?

Copy link
Contributor Author

@nicolasstucki nicolasstucki Nov 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will replace seal. But that needs to be removed after #10207 is merged. In that PR we remove the unseal method from Type. I decided to do it this way to avoid conflicts with #10207.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the name asType is not as informative as seal, because the name Type in the context of TypeRepr is vague, it could refer to both TypeRepr and quoted.Type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next step is to find a way to inject this type in the context directly. Something like

typeRep.asTypeInContext { [T] =>>
   // Type[T] is given in this context
}

def asTypeInContext[U](body: [T] =>> (using Type[T]) => U): U = ...


/** Is `self` type the same as `that` type?
* This is the case iff `self <:< that` and `that <:< self`.
*/
Expand Down