Skip to content
This repository was archived by the owner on Apr 19, 2020. It is now read-only.

Commit 5de3f13

Browse files
committed
Merge pull request scala#41 from densh/topic/quasiquotes
Improved splicing and extraction of lists and annotations
2 parents c7a8172 + d3ed213 commit 5de3f13

File tree

12 files changed

+305
-53
lines changed

12 files changed

+305
-53
lines changed

src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala

+120-27
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,22 @@ trait Reifiers { self: Quasiquotes =>
4545
}
4646
}
4747

48+
object AnnotPlaceholder {
49+
50+
def unapply(tree: Tree): Option[(String, List[Tree])] = tree match {
51+
case Apply(Select(New(Placeholder(name)), nme.CONSTRUCTOR), args) => Some((name, args))
52+
case _ => None
53+
}
54+
}
55+
4856
override def reifyTree(tree: Tree): Tree = reifyBasicTree(tree)
57+
58+
/** Reifies modifiers with custom list reifier for the annotations.
59+
*/
60+
override def reifyModifiers(m: Modifiers) =
61+
mirrorFactoryCall(nme.Modifiers, mirrorBuildCall(nme.flagsFromBits, reify(m.flags)), reify(m.privateWithin), reifyAnnotsList(m.annotations))
62+
63+
def reifyAnnotsList(annots: List[Tree]): Tree = ???
4964
}
5065

5166
class ApplyReifier(universe: Tree, placeholders: Placeholders) extends Reifier(universe, placeholders) {
@@ -64,7 +79,11 @@ trait Reifiers { self: Quasiquotes =>
6479
case (card, iterable) if card > 0 && iterable <:< iterableType =>
6580
extractIterableN(card, iterable).map {
6681
case tpe if tpe <:< treeType =>
67-
Some((wrapIterableN(tree, card) { t => t }, iterableN(card, tpe)))
82+
if (iterable <:< listTreeType || iterable <:< listListTreeType) {
83+
Some(tree, iterable)
84+
} else {
85+
Some((wrapIterableN(tree, card) { t => t }, iterableN(card, tpe)))
86+
}
6887
case LiftableType(lift) =>
6988
Some((wrapIterableN(tree, card) { t => wrapLift(lift, t) }, iterableN(card, treeType)))
7089
case tpe =>
@@ -139,25 +158,83 @@ trait Reifiers { self: Quasiquotes =>
139158
super.reifyName(name)
140159
}
141160

142-
override def reifyList(xs: List[Any]): Tree =
143-
Select(
144-
mkList(xs.map {
145-
case Placeholder(CorrespondsTo(tree, tpe)) if tpe <:< iterableTreeType => tree
146-
case List(Placeholder(CorrespondsTo(tree, tpe))) if tpe <:< iterableIterableTreeType => tree
147-
case x @ _ => mkList(List(reify(x)))
148-
}),
149-
nme.flatten)
161+
/** Splits list into a list of groups where subsequent elements are condidered
162+
* similar by the corresponding function.
163+
*
164+
* For example:
165+
*
166+
* > group(List(1, 1, 0, 0, 1, 0)) { _ == _ }
167+
* List(List(1, 1), List(0, 0), List(1), List(0))
168+
*
169+
*/
170+
def group[T](lst: List[T])(similar: (T, T) => Boolean) = lst.foldLeft[List[List[T]]](List()) {
171+
case (Nil, el) => List(List(el))
172+
case (ll :+ (last @ (lastinit :+ lastel)), el) if similar(lastel, el) => ll :+ (last :+ el)
173+
case (ll, el) => ll :+ List(el)
174+
}
175+
176+
/** Reifies list filling all the valid placeholders.
177+
*
178+
* Reification of non-trivial list is done in two steps:
179+
* 1. split the list into groups where every placeholder is always
180+
* put in a group of it's own and all subsquent non-placeholders are
181+
* grouped together; element is considered to be a placeholder if it's
182+
* in the domain of the fill function;
183+
* 2. fold the groups into a sequence of lists added together with ++ using
184+
* fill reification for placeholders and fallback reification for non-placeholders.
185+
*/
186+
def reifyListGeneric[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree): Tree =
187+
xs match {
188+
case Nil => mkList(Nil)
189+
case _ =>
190+
def reifyGroup(group: List[T]): Tree = group match {
191+
case List(elem) if fill.isDefinedAt(elem) => fill(elem)
192+
case elems => mkList(elems.map(fallback))
193+
}
194+
val head :: tail = group(xs) { (a, b) => !fill.isDefinedAt(a) && !fill.isDefinedAt(b) }
195+
tail.foldLeft[Tree](reifyGroup(head)) { (tree, lst) => q"$tree ++ ${reifyGroup(lst)}" }
196+
}
197+
198+
/** Reifies arbitrary list filling ..$x and ...$y placeholders when they are put
199+
* in the correct position. Fallbacks to super.reifyList for non-placeholders.
200+
*/
201+
override def reifyList(xs: List[Any]): Tree = reifyListGeneric(xs) {
202+
case Placeholder(CorrespondsTo(tree, tpe)) if tpe <:< iterableTreeType => tree
203+
case List(Placeholder(CorrespondsTo(tree, tpe))) if tpe <:< iterableIterableTreeType => tree
204+
} {
205+
reify(_)
206+
}
207+
208+
/** Custom list reifier for annotations. It's required because they have different shape
209+
* and additional $u.build.annotationRepr wrapping is needed to ensure that user won't
210+
* splice a non-constructor call in this position.
211+
*/
212+
override def reifyAnnotsList(annots: List[Tree]): Tree = reifyListGeneric(annots) {
213+
case AnnotPlaceholder(CorrespondsTo(tree, tpe), args) if tpe <:< iterableTreeType =>
214+
val x: TermName = c.freshName()
215+
q"$tree.map { $x => $u.build.annotationRepr($x, ${reify(args)}) }"
216+
} {
217+
case AnnotPlaceholder(CorrespondsTo(tree, tpe), args) if tpe <:< treeType =>
218+
q"$u.build.annotationRepr($tree, ${reify(args)})"
219+
case other => reify(other)
220+
}
150221
}
151222

152223
class UnapplyReifier(universe: Tree, placeholders: Placeholders) extends Reifier(universe, placeholders) {
153224

225+
val u = universe
226+
227+
object CorrespondsTo {
228+
def unapply(name: String): Option[(Tree, Int)] =
229+
placeholders.get(name)
230+
}
231+
154232
override def reifyBasicTree(tree: Tree): Tree = tree match {
155233
case global.emptyValDef =>
156234
mirrorBuildCall("EmptyValDefLike")
157235
case global.pendingSuperCall =>
158236
mirrorBuildCall("PendingSuperCallLike")
159-
case Placeholder(name) =>
160-
val (tree, card) = placeholders(name.toString)
237+
case Placeholder(CorrespondsTo(tree, card)) =>
161238
if (card > 0)
162239
c.abort(tree.pos, s"Can't extract a part of the tree with '${fmtCard(card)}' cardinality in this position.")
163240
tree
@@ -180,27 +257,40 @@ trait Reifiers { self: Quasiquotes =>
180257
placeholders(name.toString)._1
181258
}
182259

183-
override def reifyModifiers(m: global.Modifiers) =
184-
mirrorFactoryCall(nme.Modifiers, mirrorBuildCall("FlagsAsBits", reify(m.flags)), reify(m.privateWithin), reify(m.annotations))
185-
186-
override def reifyList(xs: List[Any]): Tree = {
187-
val last = if (xs.length > 0) xs.last else EmptyTree
188-
last match {
189-
case Placeholder(name) if placeholders(name)._2 == 1 =>
190-
val bnd = placeholders(name.toString)._1
191-
xs.init.foldRight[Tree](bnd) { (el, rest) =>
192-
scalaFactoryCall("collection.immutable.$colon$colon", reify(el), rest)
193-
}
194-
case List(Placeholder(name)) if placeholders(name)._2 == 2 =>
195-
val bnd = placeholders(name.toString)._1
196-
xs.init.foldRight[Tree](bnd) { (el, rest) =>
197-
scalaFactoryCall("collection.immutable.$colon$colon", reify(el), rest)
260+
def reifyListGeneric(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree) =
261+
xs match {
262+
case init :+ last if fill.isDefinedAt(last) =>
263+
init.foldRight[Tree](fill(last)) { (el, rest) =>
264+
q"scala.collection.immutable.$$colon$$colon(${fallback(el)}, $rest)"
198265
}
199266
case _ =>
200-
super.reifyList(xs)
267+
mkList(xs.map(fallback))
201268
}
269+
270+
override def reifyList(xs: List[Any]): Tree = reifyListGeneric(xs) {
271+
case Placeholder(CorrespondsTo(tree, 1)) => tree
272+
case List(Placeholder(CorrespondsTo(tree, 2))) => tree
273+
} {
274+
reify _
275+
}
276+
277+
override def reifyAnnotsList(annots: List[Tree]): Tree = reifyListGeneric(annots) {
278+
case AnnotPlaceholder(CorrespondsTo(tree, 1), Nil) => tree
279+
} {
280+
case AnnotPlaceholder(CorrespondsTo(tree, 0), args) =>
281+
args match {
282+
case Nil => tree
283+
case _ => q"$u.Apply($u.Select($u.New($tree), $u.nme.CONSTRUCTOR), ${reify(args)})"
284+
}
285+
case other =>
286+
reify(other)
202287
}
203288

289+
override def reifyModifiers(m: global.Modifiers) =
290+
mirrorFactoryCall(nme.Modifiers, mirrorBuildCall("FlagsAsBits", reify(m.flags)),
291+
reify(m.privateWithin), reifyAnnotsList(m.annotations))
292+
293+
204294
override def mirrorSelect(name: String): Tree =
205295
Select(universe, TermName(name))
206296

@@ -224,6 +314,9 @@ trait Reifiers { self: Quasiquotes =>
224314
lazy val iterableType = appliedType(IterableClass.toType, List(AnyTpe))
225315
lazy val iterableTreeType = appliedType(iterableType, List(treeType))
226316
lazy val iterableIterableTreeType = appliedType(iterableType, List(iterableTreeType))
317+
lazy val listType = appliedType(ListClass.toType, List(AnyTpe))
318+
lazy val listTreeType = appliedType(listType, List(treeType))
319+
lazy val listListTreeType = appliedType(listType, List(listTreeType))
227320
lazy val optionTreeType = appliedType(OptionClass.toType, List(treeType))
228321
lazy val optionNameType = appliedType(OptionClass.toType, List(nameType))
229322
}

src/reflect/scala/reflect/api/BuildUtils.scala

+2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ private[reflect] trait BuildUtils { self: Universe =>
7575

7676
def setSymbol[T <: Tree](tree: T, sym: Symbol): T
7777

78+
def annotationRepr(tree: Tree, args: List[Tree]): Tree
79+
7880
val FlagsAsBits: FlagsAsBitsExtractor
7981

8082
trait FlagsAsBitsExtractor {

src/reflect/scala/reflect/api/Liftable.scala

+7
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,11 @@ object Liftable {
2323
implicit lazy val liftBoolean: Liftable[Boolean] = new LiftableConstant[Boolean]
2424
implicit lazy val liftString: Liftable[String] = new LiftableConstant[String]
2525
implicit lazy val liftUnit: Liftable[Unit] = new LiftableConstant[Unit]
26+
27+
implicit lazy val liftScalaSymbol: Liftable[scala.Symbol] = new Liftable[scala.Symbol] {
28+
def apply(universe: Universe, value: scala.Symbol): universe.Tree = {
29+
import universe._
30+
q"scala.Symbol(${value.name})"
31+
}
32+
}
2633
}

src/reflect/scala/reflect/api/StdLiftables.scala renamed to src/reflect/scala/reflect/api/StandardLiftables.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package scala.reflect
22
package api
33

4-
trait StdLiftables { self: Universe =>
4+
trait StandardLiftables { self: Universe =>
55

66
private def requireSameUniverse[T](universe: Universe, tp: String, value: T) =
77
require(universe eq self, s"Can't lift $tp ${showRaw(value)} from universe ${showRaw(universe)} using lift$tp defined for ${showRaw(self)}.")

src/reflect/scala/reflect/api/Universe.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ abstract class Universe extends Symbols
7070
with TagInterop
7171
with StandardDefinitions
7272
with StandardNames
73+
with StandardLiftables
7374
with BuildUtils
7475
with Mirrors
7576
with Printers
7677
with Importers
7778
with Quasiquotes
78-
with StdLiftables
7979
{
8080
/** Use `refiy` to produce the abstract syntax tree representing a given Scala expression.
8181
*

src/reflect/scala/reflect/internal/BuildUtils.scala

+9
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ trait BuildUtils { self: SymbolTable =>
6565

6666
def setSymbol[T <: Tree](tree: T, sym: Symbol): T = { tree.setSymbol(sym); tree }
6767

68+
def annotationRepr(tree: Tree, args: List[Tree]): Tree = tree match {
69+
case ident: Ident => Apply(self.Select(New(ident), nme.CONSTRUCTOR: TermName), args)
70+
case call @ Apply(Select(New(ident: Ident), nme.CONSTRUCTOR), _) =>
71+
if(args.nonEmpty)
72+
throw new IllegalArgumentException("Can't splice annotation that already contains args with extra args.")
73+
call
74+
case _ => throw new IllegalArgumentException("Tree ${showRaw(tree)} isn't a correct representation of annotation.")
75+
}
76+
6877
object FlagsAsBits extends FlagsAsBitsExtractor {
6978
def unapply(flags: Long): Option[Long] = Some(flags)
7079
}

src/reflect/scala/reflect/internal/StdNames.scala

-2
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,6 @@ trait StdNames {
312312
val REIFY_FREE_VALUE_SUFFIX: NameType = "$value"
313313
val REIFY_SYMDEF_PREFIX: NameType = "symdef$"
314314
val QUASIQUOTE_PREFIX: String = "$quasiquote$"
315-
val QUASIQUOTE_MATCHER_PACKAGE: NameType = "$quasiquote$"
316-
val QUASIQUOTE_MATCHER_NAME: NameType = "$matcher$"
317315
val MIXIN_CONSTRUCTOR: NameType = "$init$"
318316
val MODULE_INSTANCE_FIELD: NameType = NameTransformer.MODULE_INSTANCE_NAME // "MODULE$"
319317
val OUTER: NameType = "$outer"

test/files/scalacheck/quasiquotes/ErrorProps.scala

+23-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Arbitrary._
66
import scala.reflect.runtime.universe._
77
import Flag._
88

9-
object ErrorProps extends QuasiquoteProperties("errors") {
9+
object ErrorProps extends QuasiquoteProperties("errors") with AnnotationErrors {
1010

1111
// // This test fails due to bug in untyped macro expansion
1212
// property("deconstruction: can't use two '..' cardinalities in a row") = fails (
@@ -16,14 +16,14 @@ object ErrorProps extends QuasiquoteProperties("errors") {
1616
// val q"f(..$xs1, ..$xs2)" = xs
1717
// }
1818

19-
property("construction: can't splice (1)") = fails (
19+
property("can't splice with given cardinality") = fails (
2020
"Splicing of type List[reflect.runtime.universe.Ident] with '' cardinality isn't supported."
2121
) {
2222
val xs = List(q"x", q"x")
2323
q"$xs"
2424
}
2525

26-
property("construction: splice typename into typedef with default bounds") = fails (
26+
property("splice typename into typedef with default bounds") = fails (
2727
"Name expected but reflect.runtime.universe.TypeDef found."
2828
) {
2929
val T1 = TypeName("T1")
@@ -32,5 +32,24 @@ object ErrorProps extends QuasiquoteProperties("errors") {
3232
q"type $T1[$T2 >: _root_.scala.Any <: _root_.scala.Nothing] = $t"
3333
TypeDef(Modifiers(), T1, List(T2), t)
3434
}
35-
3635
}
36+
37+
trait AnnotationErrors extends AnnotationConstr { self: QuasiquoteProperties =>
38+
39+
property("can't splice annotations with '...' cardinality") = fails (
40+
"Can't splice trees with '...' cardinality in annotation position."
41+
) {
42+
val annots = List(List(q"Foo"))
43+
q"@...$annots def foo"
44+
}
45+
46+
// // This test fails due to bug in untyped macro expansion
47+
// property("@..$first @$rest def foo") = fails (
48+
// "Can't extract a part of the tree with '..' cardinality in this position."
49+
// ) {
50+
// val a = annot("a")
51+
// val b = annot("b")
52+
// val c = annot("c")
53+
// val q"@..$first @$rest def foo" = q"@$a @$b @$c def foo"
54+
// }
55+
}

0 commit comments

Comments
 (0)