From e42e9f893256578ae6c597267494b352206984c9 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 10 Nov 2020 18:29:57 +0100 Subject: [PATCH] Define Expr methods in QuoteContext This homogenizes the way Expr and Tree are implemented. All the logic is internally implemented by the implementation of QuoteContext. This implies that Expr does not need to implement any methods outside of the QuoteContext. --- .../dotty/tools/dotc/quoted/ExprImpl.scala | 4 -- .../tools/dotc/quoted/QuoteContextImpl.scala | 58 +++++++++++++---- .../src-bootstrapped/scala/quoted/Expr.scala | 63 +------------------ .../scala/quoted/Expr.scala | 4 +- .../scala/quoted/Unliftable.scala | 4 ++ .../scala/quoted/report.scala | 5 -- library/src/scala/quoted/QuoteContext.scala | 53 ++++++++++++++++ .../scala/quoted/report.scala | 0 .../run-macros/tasty-tree-map/quoted_1.scala | 10 ++- 9 files changed, 110 insertions(+), 91 deletions(-) create mode 100644 library/src-non-bootstrapped/scala/quoted/Unliftable.scala delete mode 100644 library/src-non-bootstrapped/scala/quoted/report.scala rename library/{src-bootstrapped => src}/scala/quoted/report.scala (100%) 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 } - -}