Skip to content

Add customizable names for definitions in quotes #7346

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 3 commits into from
Oct 8, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: what about displayName or debugName? showName feels more like a verb.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose show name because only the Expr.show will display it. Compiler debugging options as -Xprint will just print the annotations.

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)} }

}