diff --git a/compiler/src/dotty/tools/dotc/quoted/ExprImpl.scala b/compiler/src/dotty/tools/dotc/quoted/ExprImpl.scala index 291c8d0226f5..8d44e9ffde31 100644 --- a/compiler/src/dotty/tools/dotc/quoted/ExprImpl.scala +++ b/compiler/src/dotty/tools/dotc/quoted/ExprImpl.scala @@ -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(...)`") diff --git a/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala b/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala index 8836d5ab5250..21756a781b6c 100644 --- a/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala +++ b/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala @@ -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 @@ -72,7 +108,7 @@ 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) @@ -80,7 +116,7 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, QuoteUnpickl 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 @@ -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 @@ -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: @@ -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 @@ -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 @@ -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 diff --git a/library/src-bootstrapped/scala/quoted/Expr.scala b/library/src-bootstrapped/scala/quoted/Expr.scala index 55dd61a1580c..cf50547421aa 100644 --- a/library/src-bootstrapped/scala/quoted/Expr.scala +++ b/library/src-bootstrapped/scala/quoted/Expr.scala @@ -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. diff --git a/library/src-non-bootstrapped/scala/quoted/Expr.scala b/library/src-non-bootstrapped/scala/quoted/Expr.scala index 640503b1cbb2..bf2b8f025002 100644 --- a/library/src-non-bootstrapped/scala/quoted/Expr.scala +++ b/library/src-non-bootstrapped/scala/quoted/Expr.scala @@ -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] diff --git a/library/src-non-bootstrapped/scala/quoted/Unliftable.scala b/library/src-non-bootstrapped/scala/quoted/Unliftable.scala new file mode 100644 index 000000000000..8aaab0c9e79d --- /dev/null +++ b/library/src-non-bootstrapped/scala/quoted/Unliftable.scala @@ -0,0 +1,4 @@ +package scala.quoted + +trait Unliftable[T]: + def fromExpr(x: Expr[T]): QuoteContext ?=> Option[T] diff --git a/library/src-non-bootstrapped/scala/quoted/report.scala b/library/src-non-bootstrapped/scala/quoted/report.scala deleted file mode 100644 index d20990ba9f5a..000000000000 --- a/library/src-non-bootstrapped/scala/quoted/report.scala +++ /dev/null @@ -1,5 +0,0 @@ -package scala.quoted - -object report: - /** Throwable used to stop the expansion of a macro after an error was reported */ - class StopQuotedContext extends Throwable diff --git a/library/src/scala/quoted/QuoteContext.scala b/library/src/scala/quoted/QuoteContext.scala index dd6d5bd78b61..b349b8ab54f3 100644 --- a/library/src/scala/quoted/QuoteContext.scala +++ b/library/src/scala/quoted/QuoteContext.scala @@ -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. */ diff --git a/library/src-bootstrapped/scala/quoted/report.scala b/library/src/scala/quoted/report.scala similarity index 100% rename from library/src-bootstrapped/scala/quoted/report.scala rename to library/src/scala/quoted/report.scala diff --git a/tests/run-macros/tasty-tree-map/quoted_1.scala b/tests/run-macros/tasty-tree-map/quoted_1.scala index 4eda7353f0cc..9ae1fc130809 100644 --- a/tests/run-macros/tasty-tree-map/quoted_1.scala +++ b/tests/run-macros/tasty-tree-map/quoted_1.scala @@ -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 } - -}