Skip to content

Commit

Permalink
Add customizable names for definitions in quotes
Browse files Browse the repository at this point in the history
This allow naming val/def in quoted code with computed names
  • Loading branch information
nicolasstucki committed Oct 4, 2019
1 parent 736af36 commit c4ac5f6
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 10 deletions.
32 changes: 32 additions & 0 deletions docs/docs/reference/metaprogramming/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,38 @@ while (i < arr.length) {
}
sum
```
### Sowing meaningful definition names in quotes

In the `powerCode` example above there is a `'{ val y = $x * $x; ... }` which when printed
may show several different `val y = ...`. Even though there is no higene issue it may be hard
to read the code. To overcome this each `y` can be assigned a meeningful name using the
`scala.quoted.show.showName` annotation. For example `'{ @showName(${Expr("y" + i)}) val y = $x * $x; ... }`
will assign to each `y` a name `y{i}` where `{i}` is a known String, if `i == 3` then it would be named `x3`.

The `powerCode` can be defined as follows using `showName`
```scala
def powerCode(n: Long, x: Expr[Double]))(given QuoteContext): Expr[Double] = '{
val x1 = $x
${ powerCode(n, 2, 'x1) }
}
def powerCode(n: Long, i: Int, x: Expr[Double])(given QuoteContext): Expr[Double] =
if (n == 0) '{1.0}
else if (n % 2 == 0) '{ @showName(${Expr("x" + i)}) val y = $x * $x; ${powerCode(n / 2, idx * 2, 'y)} }
else '{ $x * ${powerCode(n - 1, idx, x)} }
```
then
```scala
powerCode(16, '{7}).show
```
will show
```scala
val x1: scala.Double = 7
val x2: scala.Double = x1.*(x1)
val x4: scala.Double = x2.*(x2)
val x8: scala.Double = x4.*(x4)
val x16: scala.Double = x8.*(x8)
x16
```

### Find implicits within a macro

Expand Down
24 changes: 24 additions & 0 deletions library/src/scala/quoted/show/showName.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package scala.quoted.show

/** Annotation used inside a quote to give a custom name to a definition.
* The `name` argument must be a literal String.
*
* Usage:
* ```scala
* def let(name: String)(value: Expr[Int])(in: Expr[Int] => Expr[Int]): Expr[Int] = '{
* @showName(${Expr(name)})
* val x = $value
* ${ in('x) }
* }
* ```
* then using it in
* ```scala
* let("myVal")('{4})(x => '{ $x + 1}).show
* ```
* will retuns the code
* ```scala
* val myVal = 4
* myVal + 1
* ```
*/
class showName(name: String) extends scala.annotation.Annotation
36 changes: 26 additions & 10 deletions library/src/scala/tasty/reflect/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,8 @@ trait Printers
if (vdef.symbol.flags.is(Flags.Mutable)) this += highlightKeyword("var ")
else this += highlightKeyword("val ")

this += highlightValDef(name) += ": "
val name1 = splicedName(vdef.symbol).getOrElse(name)
this += highlightValDef(name1) += ": "
printTypeTree(tpt)
rhs match {
case Some(tree) =>
Expand Down Expand Up @@ -714,7 +715,8 @@ trait Printers

printProtectedOrPrivate(ddef)

this += highlightKeyword("def ") += highlightValDef((if (isConstructor) "this" else name))
val name1: String = if (isConstructor) "this" else splicedName(ddef.symbol).getOrElse(name)
this += highlightKeyword("def ") += highlightValDef(name1)
printTargsDefs(targs.zip(targs))
val it = argss.iterator
while (it.hasNext)
Expand All @@ -734,8 +736,11 @@ trait Printers
case Ident("_") =>
this += "_"

case IsTerm(tree @ Ident(_)) =>
printType(tree.tpe)
case IsIdent(tree) =>
splicedName(tree.symbol) match {
case Some(name) => this += name
case _ => printType(tree.tpe)
}

case Select(qual, name) =>
printQualTree(qual)
Expand Down Expand Up @@ -1637,12 +1642,15 @@ trait Printers

def printAnnotation(annot: Term)(given elideThis: Option[Symbol]): Buffer = {
val Annotation(ref, args) = annot
this += "@"
printTypeTree(ref)
if (args.isEmpty)
this
else
inParens(printTrees(args, ", "))
if (annot.symbol.owner.fullName == "scala.quoted.show.showName") this
else {
this += "@"
printTypeTree(ref)
if (args.isEmpty)
this
else
inParens(printTrees(args, ", "))
}
}

def printDefAnnotations(definition: Definition)(given elideThis: Option[Symbol]): Buffer = {
Expand Down Expand Up @@ -1809,6 +1817,14 @@ trait Printers
private def escapedString(str: String): String = str flatMap escapedChar
}

private def splicedName(sym: Symbol)(given ctx: Context): Option[String] = {
sym.annots.find(_.symbol.owner.fullName == "scala.quoted.show.showName").flatMap {
case Apply(_, Literal(Constant(c: String)) :: Nil) => Some(c)
case Apply(_, Inlined(_, _, Literal(Constant(c: String))) :: Nil) => Some(c)
case annot => None
}
}

private object SpecialOp {
def unapply(arg: Tree)(given ctx: Context): Option[(String, List[Term])] = arg match {
case IsTerm(arg @ Apply(fn, args)) =>
Expand Down
13 changes: 13 additions & 0 deletions tests/run-staging/quoted-show-name.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
((x1: scala.Double) => x1.*({
val x2: scala.Double = x1.*(x1)
val x4: scala.Double = x2.*(x2)
x4.*({
val x8: scala.Double = x4.*(x4)
x8.*({
val x16: scala.Double = x8.*(x8)
val x32: scala.Double = x16.*(x16)
val x64: scala.Double = x32.*(x32)
x64
})
})
}))
21 changes: 21 additions & 0 deletions tests/run-staging/quoted-show-name.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import scala.quoted._
import scala.quoted.show.showName
import scala.quoted.staging._
import scala.reflect.ClassTag

object Test {
given Toolbox = Toolbox.make(getClass.getClassLoader)
def main(args: Array[String]): Unit = withQuoteContext {
println(powerCode(77).show)
}

def powerCode(n: Long)(given QuoteContext): Expr[Double => Double] =
'{ x1 => ${powerCode(n, 2, 'x1)} }

def powerCode(n: Long, idx: Int, x: Expr[Double])(given QuoteContext): Expr[Double] =
if (n == 0) '{1.0}
else if (n == 1) x
else if (n % 2 == 0) '{ @showName(${Expr("x" + idx)}) val y = $x * $x; ${powerCode(n / 2, idx * 2, '{y})} }
else '{ $x * ${powerCode(n - 1, idx, x)} }

}

0 comments on commit c4ac5f6

Please sign in to comment.