Skip to content

Move reflection inside QuoteContext #6723

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
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,12 @@ class Definitions {
@threadUnsafe lazy val QuotedExprType: TypeRef = ctx.requiredClassRef("scala.quoted.Expr")
def QuotedExprClass(implicit ctx: Context): ClassSymbol = QuotedExprType.symbol.asClass

@threadUnsafe lazy val QuoteContextType: TypeRef = ctx.requiredClassRef("scala.quoted.QuoteContext")
def QuoteContextClass(implicit ctx: Context): ClassSymbol = QuoteContextType.symbol.asClass

@threadUnsafe lazy val QuoteContextModule: TermSymbol = ctx.requiredModule("scala.quoted.QuoteContext")
@threadUnsafe lazy val QuoteContext_macroContext: TermSymbol = QuoteContextModule.requiredMethod("macroContext")

@threadUnsafe lazy val InternalQuotedModuleRef: TermRef = ctx.requiredModuleRef("scala.internal.Quoted")
def InternalQuotedModule: Symbol = InternalQuotedModuleRef.symbol
@threadUnsafe lazy val InternalQuoted_exprQuoteR: TermRef = InternalQuotedModule.requiredMethodRef("exprQuote")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
l == level ||
level == -1 && (
sym == defn.TastyReflection_macroContext ||
sym == defn.QuoteContext_macroContext ||
// here we assume that Splicer.canBeSpliced was true before going to level -1,
// this implies that all non-inline arguments are quoted and that the following two cases are checked
// on inline parameters or type parameters.
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ object Splicer {
args.toSeq

protected def interpretTastyContext()(implicit env: Env): Object = ReflectionImpl(ctx, pos)
protected def interpretQuoteContext()(implicit env: Env): Object =
new scala.quoted.QuoteContext(ReflectionImpl(ctx, pos))

protected def interpretStaticMethodCall(moduleClass: Symbol, fn: Symbol, args: => List[Object])(implicit env: Env): Object = {
val (inst, clazz) =
Expand Down Expand Up @@ -308,6 +310,7 @@ object Splicer {
protected def interpretLiteral(value: Any)(implicit env: Env): Result
protected def interpretVarargs(args: List[Result])(implicit env: Env): Result
protected def interpretTastyContext()(implicit env: Env): Result
protected def interpretQuoteContext()(implicit env: Env): Result
protected def interpretStaticMethodCall(module: Symbol, fn: Symbol, args: => List[Result])(implicit env: Env): Result
protected def interpretModuleAccess(fn: Symbol)(implicit env: Env): Result
protected def interpretNew(fn: Symbol, args: => List[Result])(implicit env: Env): Result
Expand All @@ -333,6 +336,9 @@ object Splicer {
case _ if tree.symbol == defn.TastyReflection_macroContext =>
interpretTastyContext()

case _ if tree.symbol == defn.QuoteContext_macroContext =>
interpretQuoteContext()

case Call(fn, args) =>
if (fn.symbol.isConstructor && fn.symbol.owner.owner.is(Package)) {
interpretNew(fn.symbol, args.map(interpretTree))
Expand Down
8 changes: 7 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,11 @@ trait Implicits { self: Typer =>
if (ctx.inInlineMethod || enclosingInlineds.nonEmpty) ref(defn.TastyReflection_macroContext)
else EmptyTree

lazy val synthesizedQuoteContext: SpecialHandler =
(formal, span) => implicit ctx =>
if (ctx.inInlineMethod || enclosingInlineds.nonEmpty) ref(defn.QuoteContext_macroContext)
else EmptyTree

lazy val synthesizedTupleFunction: SpecialHandler =
(formal, span) => implicit ctx => formal match {
case AppliedType(_, funArgs @ fun :: tupled :: Nil) =>
Expand Down Expand Up @@ -1033,9 +1038,10 @@ trait Implicits { self: Typer =>
mySpecialHandlers = List(
defn.ClassTagClass -> synthesizedClassTag,
defn.QuotedTypeClass -> synthesizedTypeTag,
defn.QuoteContextClass -> synthesizedQuoteContext,
defn.TastyReflectionClass -> synthesizedTastyContext,
defn.EqlClass -> synthesizedEq,
defn.TupledFunctionClass -> synthesizedTupleFunction,
defn.TupledFunctionClass -> synthesizedTupleFunction,
defn.ValueOfClass -> synthesizedValueOf,
defn.Mirror_ProductClass -> synthesizedProductMirror,
defn.Mirror_SumClass -> synthesizedSumMirror,
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1978,7 +1978,7 @@ class Typer extends Namer
fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToType(patType),
implicits =
ref(defn.InternalQuoted_exprQuoteR).appliedToType(shape.tpe).appliedTo(shape) ::
implicitArgTree(defn.TastyReflectionType, tree.span) :: Nil,
implicitArgTree(defn.QuoteContextType, tree.span) :: Nil,
patterns = splicePat :: Nil,
proto = pt)
}
Expand Down
28 changes: 14 additions & 14 deletions docs/docs/reference/metaprogramming/tasty-reflect.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ guarantees and may fail at macro expansion time, hence additional explicit
checks must be done.

To provide reflection capabilities in macros we need to add an implicit
parameter of type `scala.tasty.Reflection` and import it in the scope where it
parameter of type `scala.quoted.QuoteContext` and import `tasty._` from it in the scope where it
is used.

```scala
Expand All @@ -29,23 +29,23 @@ import scala.tasty._

inline def natConst(x: => Int): Int = ${natConstImpl('{x})}

def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = {
import reflection._
def natConstImpl(x: Expr[Int]) given (qctx: QuoteContext): Expr[Int] = {
import qctx.tasty._
...
}
```

### Sealing and Unsealing

`import reflection._` will provide an `unseal` extension method on `quoted.Expr`
and `quoted.Type` which returns a `reflection.Term` that represents the tree of
the expression and `reflection.TypeTree` that represents the tree of the type
`import qctx.tasty._` will provide an `unseal` extension method on `quoted.Expr`
and `quoted.Type` which returns a `qctx.tasty.Term` that represents the tree of
the expression and `qctx.tasty.TypeTree` that represents the tree of the type
respectively. It will also import all extractors and methods on TASTy Reflect
trees. For example the `Literal(_)` extractor used below.

```scala
def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = {
import reflection._
def natConstImpl(x: Expr[Int]) given (qctx: QuoteContext): Expr[Int] = {
import qctx.tasty._
val xTree: Term = x.unseal
xTree match {
case Term.Literal(Constant.Int(n)) =>
Expand All @@ -58,10 +58,10 @@ def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = {
}
```

To easily know which extractors are needed, the `reflection.Term.show` method
To easily know which extractors are needed, the `qctx.tasty.Term.show` method
returns the string representation of the extractors.

The method `reflection.Term.seal[T]` provides a way to go back to a
The method `qctx.tasty.Term.seal[T]` provides a way to go back to a
`quoted.Expr[Any]`. Note that the type is `Expr[Any]`. Consequently, the type
must be set explicitly with a checked `cast` call. If the type does not conform
to it an exception will be thrown. In the code above, we could have replaced
Expand All @@ -77,8 +77,8 @@ operation expression passed while calling the `macro` below.
```scala
inline def macro(param: => Boolean): Unit = ${ macroImpl('param) }

def macroImpl(param: Expr[Boolean])(implicit refl: Reflection): Expr[Unit] = {
import refl._
def macroImpl(param: Expr[Boolean]) given (qctx: QuoteContext): Expr[Unit] = {
import qctx.tasty._
import util._

param.unseal.underlyingArgument match {
Expand All @@ -99,8 +99,8 @@ such as the start line, the end line or even the source code at the expansion
point.

```scala
def macroImpl()(reflect: Reflection): Expr[Unit] = {
import reflect.{Position => _, _}
def macroImpl()(qctx: QuoteContext): Expr[Unit] = {
import qctx.tasty._
val pos = rootPosition

val path = pos.sourceFile.jpath.toString
Expand Down
8 changes: 3 additions & 5 deletions library/src-3.x/scala/internal/quoted/Matcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import scala.annotation.internal.sharable

import scala.quoted._
import scala.quoted.matching.Bind
import scala.tasty._

object Matcher {

Expand All @@ -27,12 +26,11 @@ object Matcher {
*
* @param scrutineeExpr `Expr[_]` on which we are pattern matching
* @param patternExpr `Expr[_]` containing the pattern tree
* @param reflection instance of the reflection API (implicitly provided by the macro)
* @param qctx the current QuoteContext
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Expr[Ti]``
*/
def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup] = {
// TODO improve performance
import reflection.{Bind => BindPattern, _}
def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], qctx: QuoteContext): Option[Tup] = {
import qctx.tasty.{Bind => BindPattern, _}
import Matching._

type Env = Set[(Symbol, Symbol)]
Expand Down
4 changes: 2 additions & 2 deletions library/src-3.x/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ package quoted {
* Given list of statements `s1 :: s2 :: ... :: Nil` and an expression `e` the resulting expression
* will be equivalent to `'{ $s1; $s2; ...; $e }`.
*/
def block[T](statements: List[Expr[_]], expr: Expr[T])(implicit refl: tasty.Reflection): Expr[T] = {
import refl._
def block[T](statements: List[Expr[_]], expr: Expr[T])(implicit qctx: QuoteContext): Expr[T] = {
import qctx.tasty._
Block(statements.map(_.unseal), expr.unseal).seal.asInstanceOf[Expr[T]]
}

Expand Down
6 changes: 2 additions & 4 deletions library/src-3.x/scala/quoted/matching/Bind.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package scala.quoted
package matching

import scala.tasty.Reflection // TODO do not depend on reflection directly

/** Bind of an Expr[T] used to know if some Expr[T] is a reference to the binding
*
* @param name string name of this binding
Expand All @@ -21,8 +19,8 @@ class Bind[T <: AnyKind] private[scala](val name: String, private[Bind] val id:

object Bind {

def unapply[T](expr: Expr[T])(implicit reflect: Reflection): Option[Bind[T]] = {
import reflect.{Bind => BindPattern, _}
def unapply[T](expr: Expr[T]) given (qctx: QuoteContext): Option[Bind[T]] = {
import qctx.tasty.{Bind => BindPattern, _}
expr.unseal match {
case IsIdent(ref) =>
val sym = ref.symbol
Expand Down
11 changes: 4 additions & 7 deletions library/src-3.x/scala/quoted/matching/Const.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package scala.quoted.matching

import scala.quoted.Expr

import scala.tasty.Reflection // TODO do not depend on reflection directly
package scala.quoted
package matching

/** Matches expressions containing literal constant values and extracts the value.
* It may match expressions of type Boolean, Byte, Short, Int, Long,
Expand All @@ -17,8 +14,8 @@ import scala.tasty.Reflection // TODO do not depend on reflection directly
*/
object Const {

def unapply[T](expr: Expr[T])(implicit reflect: Reflection): Option[T] = {
import reflect._
def unapply[T](expr: Expr[T]) given (qctx: QuoteContext): Option[T] = {
import qctx.tasty._
def rec(tree: Term): Option[T] = tree match {
case Literal(c) => Some(c.value.asInstanceOf[T])
case Block(Nil, e) => rec(e)
Expand Down
9 changes: 3 additions & 6 deletions library/src-3.x/scala/quoted/matching/ConstSeq.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package scala.quoted.matching

import scala.quoted.Expr

import scala.tasty.Reflection // TODO do not depend on reflection directly
package scala.quoted
package matching

/** Literal sequence of literal constant value expressions */
object ConstSeq {

/** Matches literal sequence of literal constant value expressions */
def unapply[T](expr: Expr[Seq[T]]) given Reflection: Option[Seq[T]] = expr match {
def unapply[T](expr: Expr[Seq[T]]) given (qctx: QuoteContext): Option[Seq[T]] = expr match {
case ExprSeq(elems) =>
elems.foldRight(Option(List.empty[T])) { (elem, acc) =>
(elem, acc) match {
Expand Down
11 changes: 4 additions & 7 deletions library/src-3.x/scala/quoted/matching/ExprSeq.scala
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package scala.quoted.matching

import scala.quoted.Expr

import scala.tasty.Reflection // TODO do not depend on reflection directly
package scala.quoted
package matching

/** Literal sequence of expressions */
object ExprSeq {

/** Matches a literal sequence of expressions */
def unapply[T](expr: Expr[Seq[T]])(implicit reflect: Reflection): Option[Seq[Expr[T]]] = {
import reflect._
def unapply[T](expr: Expr[Seq[T]]) given (qctx: QuoteContext): Option[Seq[Expr[T]]] = {
import qctx.tasty._
def rec(tree: Term): Option[Seq[Expr[T]]] = tree match {
case Typed(Repeated(elems, _), _) => Some(elems.map(x => x.seal.asInstanceOf[Expr[T]]))
case Block(Nil, e) => rec(e)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// ALWAYS KEEP THIS FILE IN src-bootstrapped, DO NOT MOVE TO src

package dotty.internal

import scala.quoted._
import scala.quoted.matching._
import scala.tasty.Reflection
import reflect._

object StringContextMacro {
Expand Down Expand Up @@ -58,7 +59,7 @@ object StringContextMacro {
* @return the String contained in the given Expr
* quotes an error if the given Expr does not contain a String
*/
private def literalToString(expression : Expr[String])(implicit reflect: Reflection) : String = expression match {
private def literalToString(expression : Expr[String]) given (ctx: QuoteContext) : String = expression match {
case Const(string : String) => string
case _ => QuoteError("Expected statically known literal", expression)
}
Expand All @@ -69,8 +70,7 @@ object StringContextMacro {
* @return a list of Expr containing Strings, each corresponding to one parts of the given StringContext
* quotes an error if the given Expr does not correspond to a StringContext
*/
def getPartsExprs(strCtxExpr : Expr[scala.StringContext])(implicit reflect : Reflection): List[Expr[String]] = {
import reflect._
def getPartsExprs(strCtxExpr : Expr[scala.StringContext]) given QuoteContext: List[Expr[String]] = {
strCtxExpr match {
case '{ StringContext(${ExprSeq(parts)}: _*) } => parts.toList
case '{ new StringContext(${ExprSeq(parts)}: _*) } => parts.toList
Expand All @@ -84,8 +84,8 @@ object StringContextMacro {
* @return a list of Expr containing arguments
* quotes an error if the given Expr does not contain a list of arguments
*/
def getArgsExprs(argsExpr: Expr[Seq[Any]])(implicit reflect: Reflection): List[Expr[Any]] = {
import reflect._
def getArgsExprs(argsExpr: Expr[Seq[Any]]) given (qctx: QuoteContext): List[Expr[Any]] = {
import qctx.tasty._
argsExpr.unseal.underlyingArgument match {
case Typed(Repeated(args, _), _) => args.map(_.seal)
case tree => QuoteError("Expected statically known argument list", argsExpr)
Expand All @@ -98,8 +98,8 @@ object StringContextMacro {
* @param args the Expr that holds the sequence of arguments to interpolate to the String in the correct format
* @return the Expr containing the formatted and interpolated String or an error/warning if the parameters are not correct
*/
private def interpolate(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Any]])(implicit reflect: Reflection): Expr[String] = {
import reflect._
private def interpolate(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Any]]) given (qctx: QuoteContext): Expr[String] = {
import qctx.tasty._
val sourceFile = strCtxExpr.unseal.pos.sourceFile

val partsExpr = getPartsExprs(strCtxExpr)
Expand All @@ -111,27 +111,27 @@ object StringContextMacro {
def partError(message : String, index : Int, offset : Int) : Unit = {
reported = true
val positionStart = partsExpr(index).unseal.pos.start + offset
reflect.error(message, sourceFile, positionStart, positionStart)
error(message, sourceFile, positionStart, positionStart)
}
def partWarning(message : String, index : Int, offset : Int) : Unit = {
reported = true
val positionStart = partsExpr(index).unseal.pos.start + offset
reflect.warning(message, sourceFile, positionStart, positionStart)
warning(message, sourceFile, positionStart, positionStart)
}

def argError(message : String, index : Int) : Unit = {
reported = true
reflect.error(message, args(index).unseal.pos)
error(message, args(index).unseal.pos)
}

def strCtxError(message : String) : Unit = {
reported = true
val positionStart = strCtxExpr.unseal.pos.start
reflect.error(message, sourceFile, positionStart, positionStart)
error(message, sourceFile, positionStart, positionStart)
}
def argsError(message : String) : Unit = {
reported = true
reflect.error(message, argsExpr.unseal.pos)
error(message, argsExpr.unseal.pos)
}

def hasReported() : Boolean = {
Expand All @@ -158,8 +158,8 @@ object StringContextMacro {
* @param reporter the reporter to return any error/warning when a problem is encountered
* @return the Expr containing the formatted and interpolated String or an error/warning report if the parameters are not correct
*/
def interpolate(partsExpr : List[Expr[String]], args : List[Expr[Any]], argsExpr: Expr[Seq[Any]], reporter : Reporter)(implicit reflect: Reflection) : Expr[String] = {
import reflect._
def interpolate(partsExpr : List[Expr[String]], args : List[Expr[Any]], argsExpr: Expr[Seq[Any]], reporter : Reporter) given (qctx: QuoteContext) : Expr[String] = {
import qctx.tasty._

/** Checks if the number of arguments are the same as the number of formatting strings
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package scala.testing

import scala.quoted._
import scala.tasty.Reflection

inline def typeChecks(inline code: String): Boolean = ${ typeChecksImpl(code) }

private def typeChecksImpl(code: String)(implicit reflect: Reflection): Expr[Boolean] = {
import reflect._
private def typeChecksImpl(code: String) given (qctx: QuoteContext): Expr[Boolean] = {
import qctx.tasty._
typing.typeChecks(code).toExpr
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// ALWAYS KEEP THIS FILE IN src-non-bootstrapped

package dotty.internal

import scala.quoted._
import scala.quoted.matching._
import scala.tasty.Reflection
import reflect._

object StringContextMacro {

/** Implementation of scala.StringContext.f used in Dotty */
inline def f(sc: => StringContext)(args: Any*): String =
scala.compiletime.error("Cannot expand f interpolator while bootstrapping the compiler")

}
Loading