Skip to content

Add quoted.Expr.asExprOf and Reflection.Tree.{asExprOf, isExpr} #9514

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
merged 3 commits into from
Aug 10, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,10 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
override def unapply(x: Any): Option[Term] = x match
case _ if Unapply_TypeTest.unapply(x).isDefined => None
case _: tpd.PatternTree @unchecked => None
case x: tpd.SeqLiteral @unchecked => Some(x)
case x: tpd.Tree @unchecked if x.isTerm => Some(x)
case x: tpd.SeqLiteral @unchecked => Some(x)
case x: tpd.Inlined @unchecked => Some(x)
case x: tpd.NamedArg @unchecked => Some(x)
case _ => None
}

Expand Down
9 changes: 6 additions & 3 deletions library/src-bootstrapped/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@ abstract class Expr[+T] private[scala] {
!scala.internal.quoted.Expr.unapply[EmptyTuple, EmptyTuple](this)(using that, false, qctx).isEmpty

/** Checked cast to a `quoted.Expr[U]` */
def cast[U](using tp: scala.quoted.Type[U])(using qctx: QuoteContext): scala.quoted.Expr[U] = {
def cast[U](using tp: scala.quoted.Type[U])(using qctx: QuoteContext): scala.quoted.Expr[U] = asExprOf[U]

/** Convert this to an `quoted.Expr[X]` if this expression is a valid expression of type `X` or throws */
def asExprOf[X](using tp: scala.quoted.Type[X])(using qctx: QuoteContext): scala.quoted.Expr[X] = {
val tree = this.unseal
val expectedType = tp.unseal.tpe
if (tree.tpe <:< expectedType)
this.asInstanceOf[scala.quoted.Expr[U]]
this.asInstanceOf[scala.quoted.Expr[X]]
else
throw new scala.tasty.reflect.ExprCastError(
s"""Expr: ${tree.show}
Expand Down Expand Up @@ -178,7 +181,7 @@ object Expr {
/** Given a tuple of the form `(Expr[A1], ..., Expr[An])`, outputs a tuple `Expr[(A1, ..., An)]`. */
def ofTuple[T <: Tuple: Tuple.IsMappedBy[Expr]: Type](tup: T)(using qctx: QuoteContext): Expr[Tuple.InverseMap[T, Expr]] = {
val elems: Seq[Expr[Any]] = tup.asInstanceOf[Product].productIterator.toSeq.asInstanceOf[Seq[Expr[Any]]]
ofTupleFromSeq(elems).cast[Tuple.InverseMap[T, Expr]]
ofTupleFromSeq(elems).asExprOf[Tuple.InverseMap[T, Expr]]
}

/** Find an implicit of type `T` in the current scope given by `qctx`.
Expand Down
30 changes: 13 additions & 17 deletions library/src-bootstrapped/scala/quoted/util/ExprMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,22 +102,18 @@ trait ExprMap {
}

def transformTerm(tree: Term, tpe: Type)(using ctx: Context): Term =
tree match {
case _: Closure =>
tree
case _: Inlined =>
transformTermChildren(tree, tpe)
case _ =>
tree.tpe.widen match {
case _: MethodType | _: PolyType =>
transformTermChildren(tree, tpe)
case _ =>
type X
val expr = tree.seal.asInstanceOf[Expr[X]]
val t = tpe.seal.asInstanceOf[quoted.Type[X]]
transform(expr)(using qctx, t).unseal
}
}
tree match
case _: Closure =>
tree
case _: Inlined =>
transformTermChildren(tree, tpe)
case _ if tree.isExpr =>
type X
val expr = tree.seal.asInstanceOf[Expr[X]]
val t = tpe.seal.asInstanceOf[quoted.Type[X]]
transform(expr)(using qctx, t).unseal
case _ =>
transformTermChildren(tree, tpe)

def transformTypeTree(tree: TypeTree)(using ctx: Context): TypeTree = tree

Expand Down Expand Up @@ -155,7 +151,7 @@ trait ExprMap {
trees mapConserve (transformTypeCaseDef(_))

}
new MapChildren().transformTermChildren(e.unseal, tpe.unseal.tpe).seal.cast[T] // Cast will only fail if this implementation has a bug
new MapChildren().transformTermChildren(e.unseal, tpe.unseal.tpe).asExprOf[T]
}

}
1 change: 1 addition & 0 deletions library/src-non-bootstrapped/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ package scala.quoted

abstract class Expr[+T] private[scala]:
def unseal(using qctx: QuoteContext): qctx.tasty.Term
def asExprOf[X](using tp: scala.quoted.Type[X])(using qctx: QuoteContext): scala.quoted.Expr[X] = ???
31 changes: 25 additions & 6 deletions library/src/scala/tasty/Reflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,28 @@ class Reflection(private[scala] val internal: CompilerInterface) { self =>
/** Shows the tree as fully typed source code */
def showWith(syntaxHighlight: SyntaxHighlight)(using ctx: Context): String =
new SourceCodePrinter[self.type](self)(syntaxHighlight).showTree(tree)

/** Does this tree represent a valid expression? */
def isExpr(using ctx: Context): Boolean =
tree match
case tree: Term =>
tree.tpe.widen match
case _: MethodType | _: PolyType => false
case _ => true
case _ => false

Copy link
Contributor

Choose a reason for hiding this comment

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

The isExpr will cause confusion for meta-programmers: what's the relation between expression and Expr[T]? What is the relationship between expression and Term?

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 is isExpr indicates that this term is a valid Expr[Any], therefore asExprOf will succeed. The names are intentionally similar as the are about the same concept. Docs can improve.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe make the method private?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But users are intended to use it

end extension


/** Convert this tree to an `quoted.Expr[T]` if the tree is a valid expression or throws */
extension [T](tree: Tree)
def asExprOf(using scala.quoted.Type[T])(using QuoteContext): scala.quoted.Expr[T] =
if tree.isExpr then
new scala.internal.quoted.Expr(tree, internal.compilerId).asExprOf[T]
else tree match
case tree: Term => 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)

end Tree

given (using ctx: Context) as TypeTest[Tree, PackageClause] = internal.PackageClause_TypeTest
Expand Down Expand Up @@ -655,15 +676,13 @@ class Reflection(private[scala] val internal: CompilerInterface) { self =>

/** Convert `Term` to an `quoted.Expr[Any]` if the term is a valid expression or throws */
def seal(using ctx: Context): scala.quoted.Expr[Any] =
sealOpt.getOrElse {
throw new Exception("Cannot seal a partially applied Term. Try eta-expanding the term first.")
}
if self.isExpr then new scala.internal.quoted.Expr(self, internal.compilerId)
else throw new Exception("Cannot seal a partially applied Term. Try eta-expanding the term first.")

/** Convert `Term` to an `quoted.Expr[Any]` if the term is a valid expression */
def sealOpt(using ctx: Context): Option[scala.quoted.Expr[Any]] =
self.tpe.widen match
case _: MethodType | _: PolyType => None
case _ => Some(new scala.internal.quoted.Expr(self, internal.compilerId))
if self.isExpr then Some(new scala.internal.quoted.Expr(self, internal.compilerId))
else None

/** Type of this term */
def tpe(using ctx: Context): Type = internal.Term_tpe(self)
Expand Down