diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index 0ad19aa737fa..13fd42dd30ba 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -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 diff --git a/library/src/scala/quoted/show/showName.scala b/library/src/scala/quoted/show/showName.scala new file mode 100644 index 000000000000..220ec47cd988 --- /dev/null +++ b/library/src/scala/quoted/show/showName.scala @@ -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 diff --git a/library/src/scala/tasty/reflect/Printers.scala b/library/src/scala/tasty/reflect/Printers.scala index 760e90779710..b9ae97778d55 100644 --- a/library/src/scala/tasty/reflect/Printers.scala +++ b/library/src/scala/tasty/reflect/Printers.scala @@ -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) => @@ -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) @@ -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) @@ -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 = { @@ -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)) => diff --git a/tests/run-staging/quoted-show-name.check b/tests/run-staging/quoted-show-name.check new file mode 100644 index 000000000000..ae3f5e6928af --- /dev/null +++ b/tests/run-staging/quoted-show-name.check @@ -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 + }) + }) +})) diff --git a/tests/run-staging/quoted-show-name.scala b/tests/run-staging/quoted-show-name.scala new file mode 100644 index 000000000000..8592ebe0fe20 --- /dev/null +++ b/tests/run-staging/quoted-show-name.scala @@ -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)} } + +}