Skip to content

Refactor scala.quoted.matching #8479

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 9 commits into from
Mar 9, 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
28 changes: 12 additions & 16 deletions docs/docs/reference/metaprogramming/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,13 +565,13 @@ sum
### Find implicits within a macro

Similarly to the `summonFrom` construct, it is possible to make implicit search available
in a quote context. For this we simply provide `scala.quoted.matching.summonExpr`:
in a quote context. For this we simply provide `scala.quoted.Expr.summon`:

```scala
inline def setFor[T]: Set[T] = ${ setForExpr[T] }

def setForExpr[T: Type](using QuoteContext): Expr[Set[T]] = {
summonExpr[Ordering[T]] match {
Expr.summon[Ordering[T]] match {
case Some(ord) => '{ new TreeSet[T]()($ord) }
case _ => '{ new HashSet[T] }
}
Expand Down Expand Up @@ -614,23 +614,19 @@ In case all files are suspended due to cyclic dependencies the compilation will

It is possible to deconstruct or extract values out of `Expr` using pattern matching.

#### scala.quoted.matching
`scala.quoted` contains objects that can help extracting values from `Expr`.

`scala.quoted.matching` contains objects that can help extracting values from `Expr`.

* `scala.quoted.matching.Const`: matches an expression of a literal value and returns the value.
* `scala.quoted.matching.Value`: matches an expression of a value and returns the value.
* `scala.quoted.matching.ExprSeq`: matches an explicit sequence of expresions and returns them. These sequences are useful to get individual `Expr[T]` out of a varargs expression of type `Expr[Seq[T]]`.
* `scala.quoted.matching.ConstSeq`: matches an explicit sequence of literal values and returns them.
* `scala.quoted.matching.ValueSeq`: matches an explicit sequence of values and returns them.
* `scala.quoted.Const`/`scala.quoted.Consts`: matches an expression of a literal value (or list of values) and returns the value (or list of values).
* `scala.quoted.Value`/`scala.quoted.Values`: matches an expression of a value (or list of values) and returns the value (or list of values).
* `scala.quoted.Varargs`: matches an explicit sequence of expresions and returns them. These sequences are useful to get individual `Expr[T]` out of a varargs expression of type `Expr[Seq[T]]`.

These could be used in the following way to optimize any call to `sum` that has statically known values.
```scala
inline def sum(inline args: Int*): Int = ${ sumExpr('args) }
private def sumExpr(argsExpr: Expr[Seq[Int]])(using QuoteContext): Expr[Int] = argsExpr match {
case ConstSeq(args) => // args is of type Seq[Int]
case Varargs(Consts(args)) => // args is of type Seq[Int]
Expr(args.sum) // precompute result of sum
case ExprSeq(argExprs) => // argExprs is of type Seq[Expr[Int]]
case Varargs(argExprs) => // argExprs is of type Seq[Expr[Int]]
val staticSum: Int = argExprs.map {
case Const(arg) => arg
case _ => 0
Expand Down Expand Up @@ -666,12 +662,12 @@ private def optimizeExpr(body: Expr[Int])(using QuoteContext): Expr[Int] = body
// Match a call to sum with an argument $n of type Int. n will be the Expr[Int] representing the argument.
case '{ sum($n) } => n
// Match a call to sum and extracts all its args in an `Expr[Seq[Int]]`
case '{ sum(${ExprSeq(args)}: _*) } => sumExpr(args)
case '{ sum(${Varargs(args)}: _*) } => sumExpr(args)
case body => body
}
private def sumExpr(args1: Seq[Expr[Int]])(using QuoteContext): Expr[Int] = {
def flatSumArgs(arg: Expr[Int]): Seq[Expr[Int]] = arg match {
case '{ sum(${ExprSeq(subArgs)}: _*) } => subArgs.flatMap(flatSumArgs)
case '{ sum(${Varargs(subArgs)}: _*) } => subArgs.flatMap(flatSumArgs)
case arg => Seq(arg)
}
val args2 = args1.flatMap(flatSumArgs)
Expand Down Expand Up @@ -709,7 +705,7 @@ inline def (sc: StringContext).showMe(inline args: Any*): String = ${ showMeExpr

private def showMeExpr(sc: Expr[StringContext], argsExpr: Expr[Seq[Any]])(using qctx: QuoteContext): Expr[String] = {
argsExpr match {
case ExprSeq(argExprs) =>
case Varargs(argExprs) =>
val argShowedExprs = argExprs.map {
case '{ $arg: $tp } =>
val showTp = '[Show[$tp]]
Expand All @@ -718,7 +714,7 @@ private def showMeExpr(sc: Expr[StringContext], argsExpr: Expr[Seq[Any]])(using
case None => qctx.error(s"could not find implicit for ${showTp.show}", arg); '{???}
}
}
val newArgsExpr = Expr.ofSeq(argShowedExprs)
val newArgsExpr = Varargs(argShowedExprs)
'{ $sc.s($newArgsExpr: _*) }
case _ =>
// `new StringContext(...).showMeExpr(args: _*)` not an explicit `showMeExpr"..."`
Expand Down
13 changes: 7 additions & 6 deletions library/src-bootstrapped/dotty/internal/StringContextMacro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
package dotty.internal

import scala.quoted._
import scala.quoted.matching._
import reflect._

object StringContextMacro {

Expand Down Expand Up @@ -65,9 +63,12 @@ object StringContextMacro {

def notStatic =
qctx.throwError("Expected statically known String Context", strCtxExpr)
def splitParts(seq: Expr[Seq[String]]) = (seq, seq) match {
case (ExprSeq(p1), ConstSeq(p2)) => (p1.toList, p2.toList)
case (_, _) => notStatic
def splitParts(seq: Expr[Seq[String]]) = seq match {
case Varargs(p1) =>
p1 match
case Consts(p2) => (p1.toList, p2.toList)
case _ => notStatic
case _ => notStatic
}
val (partsExpr, parts) = strCtxExpr match {
case '{ StringContext($parts: _*) } => splitParts(parts)
Expand All @@ -76,7 +77,7 @@ object StringContextMacro {
}

val args = argsExpr match {
case ExprSeq(args) => args
case Varargs(args) => args
case _ => qctx.throwError("Expected statically known argument list", argsExpr)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
package dotty.internal

import scala.quoted._
import scala.quoted.matching._
import reflect._

object StringContextMacro {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
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,
* Float, Double, Char, String, ClassTag, scala.Symbol, Null and Unit.
*
* Usage:
* ```
* (x: Expr[B]) match {
* case Const(value: B) => ...
* }
* ```
*/
/** Literal constant values */
object Const {

/** Matches expressions containing literal constant values and extracts the value.
* It may match expressions of type Boolean, Byte, Short, Int, Long,
* Float, Double, Char, String, ClassTag, scala.Symbol, Null and Unit.
*
* Usage:
* ```
* (x: Expr[B]) match {
* case Const(value: B) => ...
* }
* ```
*/
def unapply[T](expr: Expr[T])(using qctx: QuoteContext): Option[T] = {
import qctx.tasty.{_, given _}
def rec(tree: Term): Option[T] = tree match {
Expand Down
26 changes: 26 additions & 0 deletions library/src/scala/quoted/Consts.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package scala.quoted

/** Literal constant values */
object Consts {

/** Matches literal sequence of literal constant value expressions and return a sequence of values.
*
* Usage:
* ```scala
* inline def sum(args: Int*): Int = ${ sumExpr('args) }
* def sumExpr(argsExpr: Expr[Seq[Int]])(usingusing QuoteContext): Expr[Int] = argsExpr match
* case Varargs(Consts(args)) =>
* // args: Seq[Int]
* ...
* }
* ```
*/
def unapply[T](exprs: Seq[Expr[T]])(using qctx: QuoteContext): Option[Seq[T]] =
exprs.foldRight(Option(List.empty[T])) { (elem, acc) =>
(elem, acc) match {
case (Const(value), Some(lst)) => Some(value :: lst)
case (_, _) => None
}
}

}
32 changes: 20 additions & 12 deletions library/src/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,17 @@ object Expr {
}

/** Lift a value into an expression containing the construction of that value */
def apply[T: Liftable](x: T)(using qctx: QuoteContext): Expr[T] = summon[Liftable[T]].toExpr(x)
def apply[T](x: T)(using qctx: QuoteContext, lift: Liftable[T]): Expr[T] = lift.toExpr(x)

/** Lifts this sequence of expressions into an expression of a sequence
*
* Transforms a sequence of expression
* `Seq(e1, e2, ...)` where `ei: Expr[T]`
* to an expression equivalent to
* `'{ Seq($e1, $e2, ...) }` typed as an `Expr[Seq[T]]`
*
* Usage:
* ```scala
* '{ List(${Expr.ofSeq(List(1, 2, 3))}: _*) } // equvalent to '{ List(1, 2, 3) }
* ```
*/
def ofSeq[T](xs: Seq[Expr[T]])(using tp: Type[T], qctx: QuoteContext): Expr[Seq[T]] = {
import qctx.tasty.{_, given _}
Repeated(xs.map[Term](_.unseal).toList, tp.unseal).seal.asInstanceOf[Expr[Seq[T]]]
}

def ofSeq[T](xs: Seq[Expr[T]])(using tp: Type[T], qctx: QuoteContext): Expr[Seq[T]] = Varargs(xs)

/** Lifts this list of expressions into an expression of a list
*
Expand All @@ -128,7 +120,7 @@ object Expr {
* `'{ List($e1, $e2, ...) }` typed as an `Expr[List[T]]`
*/
def ofList[T](xs: Seq[Expr[T]])(using Type[T], QuoteContext): Expr[List[T]] =
if (xs.isEmpty) '{ Nil } else '{ List(${ofSeq(xs)}: _*) }
if (xs.isEmpty) '{ Nil } else '{ List(${Varargs(xs)}: _*) }

/** Lifts this sequence of expressions into an expression of a tuple
*
Expand Down Expand Up @@ -186,7 +178,7 @@ object Expr {
case Seq('{ $x1: $t1 }, '{ $x2: $t2 }, '{ $x3: $t3 }, '{ $x4: $t4 }, '{ $x5: $t5 }, '{ $x6: $t6 }, '{ $x7: $t7 }, '{ $x8: $t8 }, '{ $x9: $t9 }, '{ $x10: $t10 }, '{ $x11: $t11 }, '{ $x12: $t12 }, '{ $x13: $t13 }, '{ $x14: $t14 }, '{ $x15: $t15 }, '{ $x16: $t16 }, '{ $x17: $t17 }, '{ $x18: $t18 }, '{ $x19: $t19 }, '{ $x20: $t20 }, '{ $x21: $t21 }, '{ $x22: $t22 }) =>
'{ Tuple22($x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15, $x16, $x17, $x18, $x19, $x20, $x21, $x22) }
case _ =>
'{ Tuple.fromIArray(IArray(${ofSeq(seq)}: _*)) }
'{ Tuple.fromIArray(IArray(${Varargs(seq)}: _*)) }
}
}

Expand All @@ -197,4 +189,20 @@ object Expr {
ofTuple(elems).cast[Tuple.InverseMap[T, Expr]]
}

/** Find an implicit of type `T` in the current scope given by `qctx`.
* Return `Some` containing the expression of the implicit or
* `None` if implicit resolution failed.
*
* @tparam T type of the implicit parameter
* @param tpe quoted type of the implicit parameter
* @param qctx current context
*/
def summon[T](using tpe: Type[T])(using qctx: QuoteContext): Option[Expr[T]] = {
import qctx.tasty.{_, given _}
searchImplicit(tpe.unseal.tpe) match {
case iss: ImplicitSearchSuccess => Some(iss.tree.seal.asInstanceOf[Expr[T]])
case isf: ImplicitSearchFailure => None
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
package scala.quoted
package matching

/** Lambda expression extractor */
object Lambda {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
package scala.quoted
package matching

/** Value expressions */
object Value {
Expand Down
14 changes: 6 additions & 8 deletions library/src/scala/quoted/ValueOfExpr.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package scala.quoted

import scala.quoted.matching._

/** A typeclass for types that can be turned from a `quoted.Expr[T]` to a `T` */
trait ValueOfExpr[T] {

Expand Down Expand Up @@ -29,7 +27,7 @@ object ValueOfExpr {

private class PrimitiveValueOfExpr[T <: Unit | Null | Int | Boolean | Byte | Short | Int | Long | Float | Double | Char | String] extends ValueOfExpr[T] {
/** Lift a quoted primitive value `'{ n }` into `n` */
def apply(x: Expr[T])(using qctx: QuoteContext): Option[T] = matching.Const.unapply(x)
def apply(x: Expr[T])(using qctx: QuoteContext): Option[T] = Const.unapply(x)
}

given Option_delegate[T](using Type[T], ValueOfExpr[T]) as ValueOfExpr[Option[T]] = new {
Expand All @@ -44,8 +42,8 @@ object ValueOfExpr {

given StringContext_delegate as ValueOfExpr[StringContext] = new {
def apply(x: Expr[StringContext])(using qctx: QuoteContext): Option[StringContext] = x match {
case '{ new StringContext(${ConstSeq(args)}: _*) } => Some(StringContext(args: _*))
case '{ StringContext(${ConstSeq(args)}: _*) } => Some(StringContext(args: _*))
case '{ new StringContext(${Varargs(Consts(args))}: _*) } => Some(StringContext(args: _*))
case '{ StringContext(${Varargs(Consts(args))}: _*) } => Some(StringContext(args: _*))
case _ => None
}
override def toString(): String = "scala.quoted.ValueOfExpr.Tuple1_delegate"
Expand Down Expand Up @@ -272,9 +270,9 @@ object ValueOfExpr {

given Seq_delegate[T](using Type[T], ValueOfExpr[T]) as ValueOfExpr[Seq[T]] = new {
def apply(x: Expr[Seq[T]])(using qctx: QuoteContext): Option[Seq[T]] = x match {
case ValueSeq(elems) => Some(elems)
case '{ scala.collection.Seq[T](${ValueSeq(elems)}: _*) } => Some(elems)
case '{ scala.collection.immutable.Seq[T](${ValueSeq(elems)}: _*) } => Some(elems)
case Varargs(Values(elems)) => Some(elems)
case '{ scala.collection.Seq[T](${Varargs(Values(elems))}: _*) } => Some(elems)
case '{ scala.collection.immutable.Seq[T](${Varargs(Values(elems))}: _*) } => Some(elems)
case _ => None
}
override def toString(): String = "scala.quoted.ValueOfExpr.Seq_delegate"
Expand Down
25 changes: 25 additions & 0 deletions library/src/scala/quoted/Values.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package scala.quoted

/** Value expressions */
object Values {

/** Matches literal sequence of literal constant value expressions and return a sequence of values.
*
* Usage:
* ```scala
* inline def sum(args: Int*): Int = ${ sumExpr('args) }
* def sumExpr(argsExpr: Expr[Seq[Int]])(using QuoteContext): Expr[Int] = argsExpr match
* case Varargs(Values(args)) =>
* // args: Seq[Int]
* ...
* }
* ```
*/
def unapply[T](exprs: Seq[Expr[T]])(using valueOf: ValueOfExpr[T], qctx: QuoteContext): Option[Seq[T]] =
exprs.foldRight(Option(List.empty[T])) { (elem, acc) =>
(elem, acc) match {
case (Value(value), Some(lst)) => Some(value :: lst)
case (_, _) => None
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
package scala.quoted
package matching

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

/** Lifts this sequence of expressions into an expression of a sequence
*
* Transforms a sequence of expression
* `Seq(e1, e2, ...)` where `ei: Expr[T]`
* to an expression equivalent to
* `'{ Seq($e1, $e2, ...) }` typed as an `Expr[Seq[T]]`
*
* Usage:
* ```scala
* '{ List(${Varargs(List(1, 2, 3))}: _*) } // equvalent to '{ List(1, 2, 3) }
* ```
*/
def apply[T](xs: Seq[Expr[T]])(using tp: Type[T], qctx: QuoteContext): Expr[Seq[T]] = {
import qctx.tasty.{_, given _}
Repeated(xs.map[Term](_.unseal).toList, tp.unseal).seal.asInstanceOf[Expr[Seq[T]]]
}

/** Matches a literal sequence of expressions and return a sequence of expressions.
*
* Usage:
* ```scala
* inline def sum(args: Int*): Int = ${ sumExpr('args) }
* def sumExpr(argsExpr: Expr[Seq[Int]])(using QuoteContext): Expr[Int] = argsExpr match
* case ExprSeq(argExprs) =>
* // argExprs: Seq[Expr[Int]]
* case Varargs(argVarargs) =>
* // argVarargs: Seq[Expr[Int]]
* ...
* }
* ```
Expand Down
16 changes: 6 additions & 10 deletions library/src/scala/quoted/matching/ConstSeq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,11 @@ object ConstSeq {
* }
* ```
*/
def unapply[T](expr: Expr[Seq[T]])(using qctx: QuoteContext): Option[Seq[T]] = expr match {
case ExprSeq(elems) =>
elems.foldRight(Option(List.empty[T])) { (elem, acc) =>
(elem, acc) match {
case (Const(value), Some(lst)) => Some(value :: lst)
case (_, _) => None
}
}
case _ => None
}
@deprecated("use scala.quoted.Varargs(scala.quoted.Const(_)) instead", "0.23.0")
def unapply[T](expr: Expr[Seq[T]])(using qctx: QuoteContext): Option[Seq[T]] =
import scala.quoted.Const
expr match
case Varargs(Consts(elems)) => Some(elems)
case _ => None

}
Loading