From c4ac5f67b3d11f10d902267ac06910a778d755d5 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 1 Oct 2019 14:33:12 +0200 Subject: [PATCH 1/3] Add customizable names for definitions in quotes This allow naming val/def in quoted code with computed names --- docs/docs/reference/metaprogramming/macros.md | 32 +++++++++++++++++ library/src/scala/quoted/show/showName.scala | 24 +++++++++++++ .../src/scala/tasty/reflect/Printers.scala | 36 +++++++++++++------ tests/run-staging/quoted-show-name.check | 13 +++++++ tests/run-staging/quoted-show-name.scala | 21 +++++++++++ 5 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 library/src/scala/quoted/show/showName.scala create mode 100644 tests/run-staging/quoted-show-name.check create mode 100644 tests/run-staging/quoted-show-name.scala 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)} } + +} From 04625bd12ecd10a0e57bed9aaa1e2ae472a23f56 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 7 Oct 2019 08:29:57 +0200 Subject: [PATCH 2/3] Fix typos and improve description --- docs/docs/reference/metaprogramming/macros.md | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index 13fd42dd30ba..5da79c9a8b04 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -564,37 +564,52 @@ while (i < arr.length) { } sum ``` -### Sowing meaningful definition names in quotes +### Showing 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`. +may show several different `val y = ...`. + +For example +```scala +powerCode(16, '{7}).show +``` +will show +```scala +val y: scala.Double = 7 +val y: scala.Double = y.*(y) +val y: scala.Double = y.*(y) +val y: scala.Double = y.*(y) +val y: scala.Double = y.*(y) +y +``` +Even though there is no hygiene issue it may be hard to undestand the code. To overcome this inconvenience +each `y` can be assigned a meaningful 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 +`a{i}` where `{i}` is a known String, if `i == 3` then it would be named `a3`. 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 powerCodeD(n: Long, x: Expr[Double]))(given QuoteContext): Expr[Double] = '{ + val a1 = $x + ${ powerCodeD(n, 2, 'x1) } } -def powerCode(n: Long, i: Int, x: Expr[Double])(given QuoteContext): Expr[Double] = +def powerCodeD(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)} } + else if (n % 2 == 0) '{ @showName(${Expr("a" + i)}) val y = $x * $x; ${powerCodeD(n / 2, idx * 2, 'y)} } + else '{ $x * ${powerCodeD(n - 1, idx, x)} } ``` then ```scala -powerCode(16, '{7}).show +powerCodeD(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 +val a1: scala.Double = 7 +val a2: scala.Double = a1.*(a1) +val a4: scala.Double = a2.*(a2) +val a8: scala.Double = a4.*(a4) +val a16: scala.Double = a8.*(a8) +a16 ``` ### Find implicits within a macro From 8fb7a445ead053c0cecf9b1083e62d672e0a60cb Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 7 Oct 2019 13:59:13 +0200 Subject: [PATCH 3/3] Move showName to scala.internal --- docs/docs/reference/metaprogramming/macros.md | 47 ------------------- .../show => internal/quoted}/showName.scala | 2 +- .../src/scala/tasty/reflect/Printers.scala | 4 +- tests/run-staging/quoted-show-name.scala | 2 +- 4 files changed, 4 insertions(+), 51 deletions(-) rename library/src/scala/{quoted/show => internal/quoted}/showName.scala (94%) diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index 5da79c9a8b04..0ad19aa737fa 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -564,53 +564,6 @@ while (i < arr.length) { } sum ``` -### Showing 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 = ...`. - -For example -```scala -powerCode(16, '{7}).show -``` -will show -```scala -val y: scala.Double = 7 -val y: scala.Double = y.*(y) -val y: scala.Double = y.*(y) -val y: scala.Double = y.*(y) -val y: scala.Double = y.*(y) -y -``` -Even though there is no hygiene issue it may be hard to undestand the code. To overcome this inconvenience -each `y` can be assigned a meaningful 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 -`a{i}` where `{i}` is a known String, if `i == 3` then it would be named `a3`. - -The `powerCode` can be defined as follows using `showName` -```scala -def powerCodeD(n: Long, x: Expr[Double]))(given QuoteContext): Expr[Double] = '{ - val a1 = $x - ${ powerCodeD(n, 2, 'x1) } -} -def powerCodeD(n: Long, i: Int, x: Expr[Double])(given QuoteContext): Expr[Double] = - if (n == 0) '{1.0} - else if (n % 2 == 0) '{ @showName(${Expr("a" + i)}) val y = $x * $x; ${powerCodeD(n / 2, idx * 2, 'y)} } - else '{ $x * ${powerCodeD(n - 1, idx, x)} } -``` -then -```scala -powerCodeD(16, '{7}).show -``` -will show -```scala -val a1: scala.Double = 7 -val a2: scala.Double = a1.*(a1) -val a4: scala.Double = a2.*(a2) -val a8: scala.Double = a4.*(a4) -val a16: scala.Double = a8.*(a8) -a16 -``` ### Find implicits within a macro diff --git a/library/src/scala/quoted/show/showName.scala b/library/src/scala/internal/quoted/showName.scala similarity index 94% rename from library/src/scala/quoted/show/showName.scala rename to library/src/scala/internal/quoted/showName.scala index 220ec47cd988..af992823f5cc 100644 --- a/library/src/scala/quoted/show/showName.scala +++ b/library/src/scala/internal/quoted/showName.scala @@ -1,4 +1,4 @@ -package scala.quoted.show +package scala.internal.quoted /** Annotation used inside a quote to give a custom name to a definition. * The `name` argument must be a literal String. diff --git a/library/src/scala/tasty/reflect/Printers.scala b/library/src/scala/tasty/reflect/Printers.scala index b9ae97778d55..1c3ecf3ef832 100644 --- a/library/src/scala/tasty/reflect/Printers.scala +++ b/library/src/scala/tasty/reflect/Printers.scala @@ -1642,7 +1642,7 @@ trait Printers def printAnnotation(annot: Term)(given elideThis: Option[Symbol]): Buffer = { val Annotation(ref, args) = annot - if (annot.symbol.owner.fullName == "scala.quoted.show.showName") this + if (annot.symbol.owner.fullName == "scala.internal.quoted.showName") this else { this += "@" printTypeTree(ref) @@ -1818,7 +1818,7 @@ trait Printers } private def splicedName(sym: Symbol)(given ctx: Context): Option[String] = { - sym.annots.find(_.symbol.owner.fullName == "scala.quoted.show.showName").flatMap { + sym.annots.find(_.symbol.owner.fullName == "scala.internal.quoted.showName").flatMap { case Apply(_, Literal(Constant(c: String)) :: Nil) => Some(c) case Apply(_, Inlined(_, _, Literal(Constant(c: String))) :: Nil) => Some(c) case annot => None diff --git a/tests/run-staging/quoted-show-name.scala b/tests/run-staging/quoted-show-name.scala index 8592ebe0fe20..42a42872b5e6 100644 --- a/tests/run-staging/quoted-show-name.scala +++ b/tests/run-staging/quoted-show-name.scala @@ -1,5 +1,5 @@ import scala.quoted._ -import scala.quoted.show.showName +import scala.internal.quoted.showName import scala.quoted.staging._ import scala.reflect.ClassTag