Skip to content

Define Expr methods in QuoteContext #10269

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
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
4 changes: 0 additions & 4 deletions compiler/src/dotty/tools/dotc/quoted/ExprImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ final class ExprImpl(val tree: tpd.Tree, val scopeId: Int) extends scala.quoted.
case _ => false
}

def unseal(using qctx: QuoteContext): qctx.reflect.Term =
checkScopeId(qctx.hashCode)
tree.asInstanceOf[qctx.reflect.Term]

def checkScopeId(expectedScopeId: Int): Unit =
if expectedScopeId != scopeId then
throw new ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
Expand Down
58 changes: 47 additions & 11 deletions compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,42 @@ object QuoteContextImpl {

class QuoteContextImpl private (ctx: Context) extends QuoteContext, QuoteUnpickler, QuoteMatching:

extension [T](self: scala.quoted.Expr[T]):
def show: String =
reflect.TreeMethodsImpl.show(self.asReflectTree)

def showAnsiColored: String =
reflect.TreeMethodsImpl.showAnsiColored(self.asReflectTree)

def matches(that: scala.quoted.Expr[Any]): Boolean =
treeMatch(self.asReflectTree, that.asReflectTree).nonEmpty

def asReflectTree: reflect.Term =
val expr = self.asInstanceOf[ExprImpl]
expr.checkScopeId(QuoteContextImpl.this.hashCode)
expr.tree

end extension

extension [X](self: scala.quoted.Expr[Any]):
/** Checks is the `quoted.Expr[?]` is valid expression of type `X` */
def isExprOf(using scala.quoted.Type[X]): Boolean =
reflect.TypeReprMethodsImpl.<:<(self.asReflectTree.tpe)(reflect.TypeRepr.of[X])

/** Convert this to an `quoted.Expr[X]` if this expression is a valid expression of type `X` or throws */
def asExprOf(using scala.quoted.Type[X]): scala.quoted.Expr[X] = {
if isExprOf[X] then
self.asInstanceOf[scala.quoted.Expr[X]]
else
throw Exception(
s"""Expr cast exception: ${self.show}
|of type: ${reflect.TypeReprMethodsImpl.show(self.asReflectTree.tpe)}
|did not conform to type: ${reflect.TypeReprMethodsImpl.show(reflect.TypeRepr.of[X])}
|""".stripMargin
)
}
end extension

object reflect extends scala.tasty.Reflection:

def rootContext: Context = ctx
Expand Down Expand Up @@ -72,15 +108,15 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, QuoteUnpickl
case _ => false
def asExpr: scala.quoted.Expr[Any] =
if self.isExpr then
new dotty.tools.dotc.quoted.ExprImpl(self, QuoteContextImpl.this.hashCode)
new ExprImpl(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](self: Tree)
def asExprOf(using tp: scala.quoted.Type[T]): scala.quoted.Expr[T] =
self.asExpr.asExprOf[T](using tp)(using QuoteContextImpl.this)
QuoteContextImpl.this.asExprOf[T](self.asExpr)(using tp)
end extension

end TreeMethodsImpl
Expand Down Expand Up @@ -316,11 +352,11 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, QuoteUnpickl
object TermMethodsImpl extends TermMethods:
extension (self: Term):
def seal: scala.quoted.Expr[Any] =
if self.isExpr then new dotty.tools.dotc.quoted.ExprImpl(self, QuoteContextImpl.this.hashCode)
if self.isExpr then new ExprImpl(self, QuoteContextImpl.this.hashCode)
else throw new Exception("Cannot seal a partially applied Term. Try eta-expanding the term first.")

def sealOpt: Option[scala.quoted.Expr[Any]] =
if self.isExpr then Some(new dotty.tools.dotc.quoted.ExprImpl(self, QuoteContextImpl.this.hashCode))
if self.isExpr then Some(new ExprImpl(self, QuoteContextImpl.this.hashCode))
else None

def tpe: TypeRepr = self.tpe
Expand Down Expand Up @@ -1003,7 +1039,7 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, QuoteUnpickl

object TypeTree extends TypeTreeModule:
def of[T <: AnyKind](using tp: scala.quoted.Type[T]): TypeTree =
tp.asInstanceOf[dotty.tools.dotc.quoted.TypeImpl].typeTree
tp.asInstanceOf[TypeImpl].typeTree
end TypeTree

object TypeTreeMethodsImpl extends TypeTreeMethods:
Expand Down Expand Up @@ -1572,7 +1608,7 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, QuoteUnpickl

object TypeRepr extends TypeReprModule:
def of[T <: AnyKind](using tp: scala.quoted.Type[T]): TypeRepr =
tp.asInstanceOf[dotty.tools.dotc.quoted.TypeImpl].typeTree.tpe
tp.asInstanceOf[TypeImpl].typeTree.tpe
def typeConstructorOf(clazz: Class[?]): TypeRepr =
if (clazz.isPrimitive)
if (clazz == classOf[Boolean]) dotc.core.Symbols.defn.BooleanType
Expand Down Expand Up @@ -1609,7 +1645,7 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, QuoteUnpickl
def seal: scala.quoted.Type[_] = self.asType

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

def =:=(that: TypeRepr): Boolean = self =:= that
def <:<(that: TypeRepr): Boolean = self <:< that
Expand Down Expand Up @@ -2624,16 +2660,16 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, QuoteUnpickl

def unpickleExpr[T](pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.QuoteContext) => scala.quoted.Expr[?]): scala.quoted.Expr[T] =
val tree = PickledQuotes.unpickleTerm(pickled, typeHole, termHole)(using reflect.rootContext)
new dotty.tools.dotc.quoted.ExprImpl(tree, hash).asInstanceOf[scala.quoted.Expr[T]]
new ExprImpl(tree, hash).asInstanceOf[scala.quoted.Expr[T]]

def unpickleType[T <: AnyKind](pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.QuoteContext) => scala.quoted.Expr[?]): scala.quoted.Type[T] =
val tree = PickledQuotes.unpickleTypeTree(pickled, typeHole, termHole)(using reflect.rootContext)
new dotty.tools.dotc.quoted.TypeImpl(tree, hash).asInstanceOf[scala.quoted.Type[T]]
new TypeImpl(tree, hash).asInstanceOf[scala.quoted.Type[T]]

object ExprMatch extends ExprMatchModule:
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutinee: scala.quoted.Expr[Any])(using pattern: scala.quoted.Expr[Any]): Option[Tup] =
val scrutineeTree = scrutinee.unseal(using QuoteContextImpl.this)
val patternTree = pattern.unseal(using QuoteContextImpl.this)
val scrutineeTree = QuoteContextImpl.this.asReflectTree(scrutinee)
val patternTree = QuoteContextImpl.this.asReflectTree(pattern)
treeMatch(scrutineeTree, patternTree).asInstanceOf[Option[Tup]]
end ExprMatch

Expand Down
63 changes: 1 addition & 62 deletions library/src-bootstrapped/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
@@ -1,71 +1,10 @@
package scala.quoted

/** Quoted expression of type `T` */
abstract class Expr[+T] private[scala] {

/** Show a source code like representation of this expression without syntax highlight */
def show(using qctx: QuoteContext): String = this.unseal.show

/** Shows the tree as fully typed source code colored with ANSI */
def showAnsiColored(using qctx: QuoteContext): String = this.unseal.showAnsiColored

/** Pattern matches `this` against `that`. Effectively performing a deep equality check.
* It does the equivalent of
* ```
* this match
* case '{...} => true // where the contents of the pattern are the contents of `that`
* case _ => false
* ```
*/
final def matches(that: Expr[Any])(using qctx: QuoteContext): Boolean =
val ExprMatch = qctx.asInstanceOf[scala.quoted.internal.QuoteMatching].ExprMatch
ExprMatch.unapply[EmptyTuple, EmptyTuple](this)(using that).nonEmpty

/** Checks is the `quoted.Expr[?]` is valid expression of type `X` */
def isExprOf[X](using tp: scala.quoted.Type[X])(using qctx: QuoteContext): Boolean =
this.unseal.tpe <:< qctx.reflect.TypeRepr.of[X]

/** 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] = {
if isExprOf[X] then
this.asInstanceOf[scala.quoted.Expr[X]]
else
throw Exception(
s"""Expr cast exception: ${this.show}
|of type: ${this.unseal.tpe.show}
|did not conform to type: ${qctx.reflect.TypeRepr.of[X].show}
|""".stripMargin
)
}

/** View this expression `quoted.Expr[T]` as a `Term` */
def unseal(using qctx: QuoteContext): qctx.reflect.Term

}
abstract class Expr[+T] private[scala]

object Expr {

extension [T](expr: Expr[T]):
/** Return the unlifted value of this expression.
*
* Returns `None` if the expression does not contain a value or contains side effects.
* Otherwise returns the `Some` of the value.
*/
def unlift(using qctx: QuoteContext, unlift: Unliftable[T]): Option[T] =
unlift.fromExpr(expr)

/** Return the unlifted value of this expression.
*
* Emits an error and throws if the expression does not contain a value or contains side effects.
* Otherwise returns the value.
*/
def unliftOrError(using qctx: QuoteContext, unlift: Unliftable[T]): T =
def reportError =
val msg = s"Expected a known value. \n\nThe value of: ${expr.show}\ncould not be unlifted using $unlift"
report.throwError(msg, expr)
unlift.fromExpr(expr).getOrElse(reportError)
end extension

/** `e.betaReduce` returns an expression that is functionally equivalent to `e`,
* however if `e` is of the form `((y1, ..., yn) => e2)(e1, ..., en)`
* then it optimizes this the top most call by returning the result of beta-reducing the application.
Expand Down
4 changes: 1 addition & 3 deletions library/src-non-bootstrapped/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
package scala.quoted

abstract class Expr[+T] private[scala]:
def unseal(using qctx: QuoteContext): qctx.reflect.Term
def asExprOf[X](using tp: scala.quoted.Type[X])(using qctx: QuoteContext): scala.quoted.Expr[X] = ???
abstract class Expr[+T] private[scala]
4 changes: 4 additions & 0 deletions library/src-non-bootstrapped/scala/quoted/Unliftable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package scala.quoted

trait Unliftable[T]:
def fromExpr(x: Expr[T]): QuoteContext ?=> Option[T]
5 changes: 0 additions & 5 deletions library/src-non-bootstrapped/scala/quoted/report.scala

This file was deleted.

53 changes: 53 additions & 0 deletions library/src/scala/quoted/QuoteContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,59 @@ package scala.quoted
*/
trait QuoteContext { self: internal.QuoteUnpickler & internal.QuoteMatching =>

// Extension methods for `Expr[T]`
extension [T](self: Expr[T]):
/** Show a source code like representation of this expression without syntax highlight */
def show: String

/** Shows the tree as fully typed source code colored with ANSI */
def showAnsiColored: String

/** Pattern matches `this` against `that`. Effectively performing a deep equality check.
* It does the equivalent of
* ```
* this match
* case '{...} => true // where the contents of the pattern are the contents of `that`
* case _ => false
* ```
*/
def matches(that: Expr[Any]): Boolean

/** Return the unlifted value of this expression.
*
* Returns `None` if the expression does not contain a value or contains side effects.
* Otherwise returns the `Some` of the value.
*/
def unlift(using unlift: Unliftable[T]): Option[T] =
unlift.fromExpr(self)(using QuoteContext.this)

/** Return the unlifted value of this expression.
*
* Emits an error and throws if the expression does not contain a value or contains side effects.
* Otherwise returns the value.
*/
def unliftOrError(using unlift: Unliftable[T]): T =
def reportError =
val msg = s"Expected a known value. \n\nThe value of: ${self.show}\ncould not be unlifted using $unlift"
report.throwError(msg, self)(using QuoteContext.this)
unlift.fromExpr(self)(using QuoteContext.this).getOrElse(reportError)

/** View this expression `quoted.Expr[T]` as a `Term` */
def unseal: reflect.Term = self.asReflectTree // TODO remove

/** View this expression `quoted.Expr[T]` as a `Term` */
def asReflectTree: reflect.Term
end extension

// Extension methods for `Expr[Any]` that take another explicit type parameter
extension [X](self: Expr[Any]):
/** Checks is the `quoted.Expr[?]` is valid expression of type `X` */
def isExprOf(using tp: scala.quoted.Type[X]): Boolean

/** Convert this to an `quoted.Expr[X]` if this expression is a valid expression of type `X` or throws */
def asExprOf(using tp: scala.quoted.Type[X]): scala.quoted.Expr[X]
end extension

/** Low-level Typed AST API metaprogramming API.
* This API does not have the static type guarantiees that `Expr` and `Type` provide.
*/
Expand Down
10 changes: 4 additions & 6 deletions tests/run-macros/tasty-tree-map/quoted_1.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import scala.quoted._

object Macros {

implicit inline def identityMaped[T](x: => T): T = ${ impl('x) }
object Macros:
implicit inline def identityMaped[T](x: => T): T = ${ MacrosImpl.impl('x) }

object MacrosImpl:
def impl[T: Type](x: Expr[T])(using qctx: QuoteContext) : Expr[T] = {
import qctx.reflect.{_, given} // FIXME: #8919
import qctx.reflect._
val identityMap = new TreeMap { }
val transformed = identityMap.transformTerm(x.unseal).asExprOf[T]
transformed
}

}