Skip to content

Commit fa02c15

Browse files
committed
Merge pull request scala#3458 from densh/si/8173
SI-8173 add support for patterns like init :+ last to quasiquotes
2 parents 0c16bb0 + ffc3203 commit fa02c15

File tree

10 files changed

+164
-87
lines changed

10 files changed

+164
-87
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ abstract class Quasiquotes extends Parsers
1616

1717
lazy val (universe: Tree, args, parts, parse, reify, method) = c.macroApplication match {
1818
case Apply(build.SyntacticTypeApplied(Select(Select(Apply(Select(universe0, _), List(Apply(_, parts0))), interpolator0), method0), _), args0) =>
19-
debug(s"\nparse prefix:\nuniverse=$universe0\nparts=$parts0\ninterpolator=$interpolator0\nmethod=$method0\nargs=$args0\n")
19+
debug(s"parse prefix:\nuniverse=$universe0\nparts=$parts0\ninterpolator=$interpolator0\nmethod=$method0\nargs=$args0\n")
2020
val parts1 = parts0.map {
2121
case lit @ Literal(Constant(s: String)) => s -> lit.pos
2222
case part => c.abort(part.pos, "Quasiquotes can only be used with literal strings")
@@ -43,8 +43,8 @@ abstract class Quasiquotes extends Parsers
4343
lazy val universeTypes = new definitions.UniverseDependentTypes(universe)
4444

4545
def expandQuasiquote = {
46-
debug(s"\nmacro application:\n${c.macroApplication}\n")
47-
debug(s"\ncode to parse:\n$code\n")
46+
debug(s"macro application:\n${c.macroApplication}\n")
47+
debug(s"code to parse:\n$code\n")
4848
val tree = parse(code)
4949
debug(s"parsed:\n${showRaw(tree)}\n$tree\n")
5050
val reified = reify(tree)

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

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ trait Reifiers { self: Quasiquotes =>
162162
reifyBuildCall(nme.SyntacticNew, earlyDefs, parents, selfdef, body)
163163
case SyntacticDefDef(mods, name, tparams, build.ImplicitParams(vparamss, implparams), tpt, rhs) =>
164164
if (implparams.nonEmpty)
165-
mirrorBuildCall(nme.SyntacticDefDef, reify(mods), reify(name), reify(tparams),
165+
mirrorBuildCall(nme.SyntacticDefDef, reify(mods), reify(name), reify(tparams),
166166
reifyBuildCall(nme.ImplicitParams, vparamss, implparams), reify(tpt), reify(rhs))
167167
else
168168
reifyBuildCall(nme.SyntacticDefDef, mods, name, tparams, vparamss, tpt, rhs)
@@ -305,7 +305,7 @@ trait Reifiers { self: Quasiquotes =>
305305
* > reifyMultiCardinalityList(lst) { ... } { ... }
306306
* q"List($foo, $bar) ++ ${holeMap(qq$f3948f9s$1).tree}"
307307
*/
308-
def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree): Tree
308+
def reifyMultiCardinalityList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree
309309

310310
/** Reifies arbitrary list filling ..$x and ...$y holeMap when they are put
311311
* in the correct position. Fallbacks to regular reification for non-high cardinality
@@ -361,10 +361,10 @@ trait Reifiers { self: Quasiquotes =>
361361
}
362362

363363
class ApplyReifier extends Reifier(isReifyingExpressions = true) {
364-
def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree): Tree =
364+
def reifyMultiCardinalityList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree =
365365
if (xs.isEmpty) mkList(Nil)
366366
else {
367-
def reifyGroup(group: List[T]): Tree = group match {
367+
def reifyGroup(group: List[Any]): Tree = group match {
368368
case List(elem) if fill.isDefinedAt(elem) => fill(elem)
369369
case elems => mkList(elems.map(fallback))
370370
}
@@ -403,14 +403,26 @@ trait Reifiers { self: Quasiquotes =>
403403

404404
}
405405
class UnapplyReifier extends Reifier(isReifyingExpressions = false) {
406-
def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree): Tree = xs match {
407-
case init :+ last if fill.isDefinedAt(last) =>
408-
init.foldRight[Tree](fill(last)) { (el, rest) =>
409-
val cons = Select(Select(Select(Ident(nme.scala_), nme.collection), nme.immutable), nme.CONS)
410-
Apply(cons, List(fallback(el), rest))
411-
}
412-
case _ =>
413-
mkList(xs.map(fallback))
406+
private def collection = ScalaDot(nme.collection)
407+
private def collectionColonPlus = Select(collection, nme.COLONPLUS)
408+
private def collectionCons = Select(Select(collection, nme.immutable), nme.CONS)
409+
private def collectionNil = Select(Select(collection, nme.immutable), nme.Nil)
410+
// pq"$lhs :+ $rhs"
411+
private def append(lhs: Tree, rhs: Tree) = Apply(collectionColonPlus, lhs :: rhs :: Nil)
412+
// pq"$lhs :: $rhs"
413+
private def cons(lhs: Tree, rhs: Tree) = Apply(collectionCons, lhs :: rhs :: Nil)
414+
415+
def reifyMultiCardinalityList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree = {
416+
val grouped = group(xs) { (a, b) => !fill.isDefinedAt(a) && !fill.isDefinedAt(b) }
417+
def appended(lst: List[Any], init: Tree) = lst.foldLeft(init) { (l, r) => append(l, fallback(r)) }
418+
def prepended(lst: List[Any], init: Tree) = lst.foldRight(init) { (l, r) => cons(fallback(l), r) }
419+
grouped match {
420+
case init :: List(hole) :: last :: Nil if fill.isDefinedAt(hole) => appended(last, prepended(init, fill(hole)))
421+
case init :: List(hole) :: Nil if fill.isDefinedAt(hole) => prepended(init, fill(hole))
422+
case List(hole) :: last :: Nil if fill.isDefinedAt(hole) => appended(last, fill(hole))
423+
case List(hole) :: Nil if fill.isDefinedAt(hole) => fill(hole)
424+
case _ => prepended(xs, collectionNil)
425+
}
414426
}
415427

416428
override def reifyModifiers(m: Modifiers) =

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

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -822,31 +822,32 @@ trait StdNames {
822822
def newLazyValSlowComputeName(lzyValName: Name) = lzyValName append LAZY_SLOW_SUFFIX
823823

824824
// ASCII names for operators
825-
val ADD = encode("+")
826-
val AND = encode("&")
827-
val ASR = encode(">>")
828-
val CONS = encode("::")
829-
val DIV = encode("/")
830-
val EQ = encode("==")
831-
val EQL = encode("=")
832-
val GE = encode(">=")
833-
val GT = encode(">")
834-
val HASHHASH = encode("##")
835-
val LE = encode("<=")
836-
val LSL = encode("<<")
837-
val LSR = encode(">>>")
838-
val LT = encode("<")
839-
val MINUS = encode("-")
840-
val MOD = encode("%")
841-
val MUL = encode("*")
842-
val NE = encode("!=")
843-
val OR = encode("|")
844-
val PLUS = ADD // technically redundant, but ADD looks funny with MINUS
845-
val PLUSPLUS = encode("++")
846-
val SUB = MINUS // ... as does SUB with PLUS
847-
val XOR = encode("^")
848-
val ZAND = encode("&&")
849-
val ZOR = encode("||")
825+
val ADD = encode("+")
826+
val AND = encode("&")
827+
val ASR = encode(">>")
828+
val CONS = encode("::")
829+
val COLONPLUS = encode(":+")
830+
val DIV = encode("/")
831+
val EQ = encode("==")
832+
val EQL = encode("=")
833+
val GE = encode(">=")
834+
val GT = encode(">")
835+
val HASHHASH = encode("##")
836+
val LE = encode("<=")
837+
val LSL = encode("<<")
838+
val LSR = encode(">>>")
839+
val LT = encode("<")
840+
val MINUS = encode("-")
841+
val MOD = encode("%")
842+
val MUL = encode("*")
843+
val NE = encode("!=")
844+
val OR = encode("|")
845+
val PLUS = ADD // technically redundant, but ADD looks funny with MINUS
846+
val PLUSPLUS = encode("++")
847+
val SUB = MINUS // ... as does SUB with PLUS
848+
val XOR = encode("^")
849+
val ZAND = encode("&&")
850+
val ZOR = encode("||")
850851

851852
// unary operators
852853
val UNARY_~ = encode("unary_~")

test/files/scalacheck/quasiquotes/DefinitionConstructionProps.scala

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -229,13 +229,13 @@ trait MethodConstruction { self: QuasiquoteProperties =>
229229

230230
property("splice type name into annotation") = test {
231231
val name = TypeName("annot")
232-
assertSameAnnots(q"@$name def foo", List(annot(name)))
232+
assertSameAnnots(q"@$name def foo", List(q"new $name"))
233233
}
234234

235235
property("splice ident into annotation") = test {
236236
val name = TypeName("annot")
237237
val ident = Ident(name)
238-
assertSameAnnots(q"@$ident def foo", List(annot(name)))
238+
assertSameAnnots(q"@$ident def foo", List(q"new $name"))
239239
}
240240

241241
property("splice idents into annotation") = test {
@@ -245,36 +245,36 @@ trait MethodConstruction { self: QuasiquoteProperties =>
245245
}
246246

247247
property("splice constructor calls into annotation") = test {
248-
val ctorcalls = List(annot("a1"), annot("a2"))
248+
val ctorcalls = List(q"new a1", q"new a2")
249249
assertSameAnnots(q"@..$ctorcalls def foo", ctorcalls)
250250
}
251251

252252
property("splice multiple annotations (1)") = test {
253-
val annot1 = annot("a1")
254-
val annot2 = annot("a2")
253+
val annot1 = q"new a1"
254+
val annot2 = q"new a2"
255255
val res = q"@$annot1 @$annot2 def foo"
256256
assertSameAnnots(res, List(annot1, annot2))
257257
}
258258

259259
property("splice multiple annotations (2)") = test {
260-
val annot1 = annot("a1")
261-
val annots = List(annot("a2"), annot("a3"))
260+
val annot1 = q"new a1"
261+
val annots = List(q"new a2", q"new a3")
262262
val res = q"@$annot1 @..$annots def foo"
263263
assertSameAnnots(res, annot1 :: annots)
264264
}
265265

266266
property("splice annotations with arguments (1)") = test {
267-
val a = annot("a", List(q"x"))
267+
val a = q"new a(x)"
268268
assertSameAnnots(q"@$a def foo", q"@a(x) def foo")
269269
}
270270

271271
property("splice annotations with arguments (2)") = test {
272-
val a = newTypeName("a")
272+
val a = TypeName("a")
273273
assertSameAnnots(q"@$a(x) def foo", q"@a(x) def foo")
274274
}
275275

276276
property("splice annotations with arguments (3") = test {
277-
val a = Ident(newTypeName("a"))
277+
val a = Ident(TypeName("a"))
278278
assertSameAnnots(q"@$a(x) def foo", q"@a(x) def foo")
279279
}
280280

@@ -286,7 +286,7 @@ trait MethodConstruction { self: QuasiquoteProperties =>
286286
}
287287

288288
property("can't splice annotations with arguments specificed twice") = test {
289-
val a = annot("a", List(q"x"))
289+
val a = q"new a(x)"
290290
assertThrows[IllegalArgumentException] {
291291
q"@$a(y) def foo"
292292
}

test/files/scalacheck/quasiquotes/DefinitionDeconstructionProps.scala

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,28 @@ trait ModsDeconstruction { self: QuasiquoteProperties =>
125125
}
126126

127127
property("@..$annots def foo") = test {
128-
val a = annot("a")
129-
val b = annot("b")
128+
val a = q"new a"
129+
val b = q"new b"
130130
val q"@..$annots def foo" = q"@$a @$b def foo"
131131
annots List(a, b)
132132
}
133133

134134
property("@$annot @..$annots def foo") = test {
135-
val a = annot("a")
136-
val b = annot("b")
137-
val c = annot("c")
135+
val a = q"new a"
136+
val b = q"new b"
137+
val c = q"new c"
138138
val q"@$first @..$rest def foo" = q"@$a @$b @$c def foo"
139-
first a && rest List(b, c)
139+
assert(first a)
140+
assert(rest List(b, c))
141+
}
142+
143+
property("@..$anots @$annot def foo") = test {
144+
val a = q"new a"
145+
val b = q"new b"
146+
val c = q"new c"
147+
val q"@..$init @$last def foo" = q"@$a @$b @$c def foo"
148+
assert(init List(a, b))
149+
assert(last c)
140150
}
141151
}
142152

test/files/scalacheck/quasiquotes/ErrorProps.scala

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ object ErrorProps extends QuasiquoteProperties("errors") {
3232
q"@...$annots def foo"
3333
""")
3434

35-
property("@..$first @$rest def foo") = fails(
36-
"Can't extract with .. here",
37-
"""
38-
q"@a @b @c def foo" match { case q"@..$first @$rest def foo" => }
39-
""")
40-
4135
property("only literal string arguments") = fails(
4236
"Quasiquotes can only be used with literal strings",
4337
"""
@@ -140,12 +134,6 @@ object ErrorProps extends QuasiquoteProperties("errors") {
140134
q"$m1 $m2 def foo"
141135
""")
142136

143-
property("can't extract with .. card here") = fails(
144-
"Can't extract with .. here",
145-
"""
146-
val q"f(..$xs, $y)" = EmptyTree
147-
""")
148-
149137
property("can't extract mods with annots") = fails(
150138
"Can't extract modifiers together with annotations, consider extracting just modifiers",
151139
"""

test/files/scalacheck/quasiquotes/PatternDeconstructionProps.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,18 @@ object PatternDeconstructionProps extends QuasiquoteProperties("pattern deconstr
2222
pat0 pat && subpat0 subpat
2323
}
2424

25+
property("extract apply many") = forAll { (pat: Tree, subpats: List[Tree]) =>
26+
val pq"$pat0(..$subpats0)" = pq"$pat(..$subpats)"
27+
pat0 pat && subpats0 subpats
28+
}
29+
30+
property("extract apply last") = forAll { (pat: Tree, subpats: List[Tree], subpatlast: Tree) =>
31+
val pq"$pat0(..$subpats0, $subpatlast0)" = pq"$pat(..$subpats, $subpatlast)"
32+
pat0 pat && subpats0 subpats && subpatlast0 subpatlast
33+
}
34+
2535
property("extract casedef") = forAll { (pat: Tree, cond: Tree, body: Tree) =>
2636
val cq"$pat0 if $cond0 => $body0" = cq"$pat if $cond => $body"
2737
pat0 pat && cond0 cond && body0 body
2838
}
29-
}
39+
}

test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,5 @@ trait Helpers {
116116
}
117117
}
118118

119-
def annot(name: String): Tree = annot(TypeName(name), Nil)
120-
def annot(name: TypeName): Tree = annot(name, Nil)
121-
def annot(name: String, args: List[Tree]): Tree = annot(TypeName(name), args)
122-
def annot(name: TypeName, args: List[Tree]): Tree = q"new $name(..$args)"
123-
124119
val scalapkg = build.setSymbol(Ident(TermName("scala")), definitions.ScalaPackage)
125120
}

test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,34 @@ object TermDeconstructionProps extends QuasiquoteProperties("term deconstruction
2929
y1 x1 && y2 x2 && ys List(x3)
3030
}
3131

32+
property("f(y1, ..ys, yn)") = forAll { (x1: Tree, x2: Tree, x3: Tree, x4: Tree) =>
33+
val q"f($y1, ..$ys, $yn)" = q"f($x1, $x2, $x3, $x4)"
34+
y1 x1 && ys List(x2, x3) && yn x4
35+
}
36+
37+
property("f(..ys, y_{n-1}, y_n)") = forAll { (x1: Tree, x2: Tree, x3: Tree, x4: Tree) =>
38+
val q"f(..$ys, $yn1, $yn)" = q"f($x1, $x2, $x3, $x4)"
39+
ys List(x1, x2) && yn1 x3 && yn x4
40+
}
41+
3242
property("f(...xss)") = forAll { (x1: Tree, x2: Tree) =>
33-
val q"f(...$argss)" = q"f($x1)($x2)"
34-
argss List(List(x1), List(x2))
43+
val q"f(...$xss)" = q"f($x1)($x2)"
44+
xss List(List(x1), List(x2))
45+
}
46+
47+
property("f(...$xss)(..$last)") = forAll { (x1: Tree, x2: Tree, x3: Tree) =>
48+
val q"f(...$xss)(..$last)" = q"f($x1)($x2)($x3)"
49+
xss List(List(x1), List(x2)) && last List(x3)
50+
}
51+
52+
property("f(...$xss)(..$lastinit, $lastlast)") = forAll { (x1: Tree, x2: Tree, x3: Tree, x4: Tree) =>
53+
val q"f(...$xss)(..$lastinit, $lastlast)" = q"f($x1)($x2, $x3, $x4)"
54+
xss List(List(x1)) && lastinit List(x2, x3) && lastlast x4
3555
}
3656

3757
property("f(...xss) = f") = forAll { (x1: Tree, x2: Tree) =>
38-
val q"f(...$argss)" = q"f"
39-
argss List()
58+
val q"f(...$xss)" = q"f"
59+
xss List()
4060
}
4161

4262
property("deconstruct unit as tuple") = test {
@@ -51,19 +71,40 @@ object TermDeconstructionProps extends QuasiquoteProperties("term deconstruction
5171

5272
property("deconstruct tuple mixed") = test {
5373
val q"($first, ..$rest)" = q"(a, b, c)"
54-
assert(first q"a" && rest List(q"b", q"c"))
74+
assert(first q"a")
75+
assert(rest List(q"b", q"c"))
76+
}
77+
78+
property("deconstruct tuple last element") = test {
79+
val q"($first, ..$rest, $last)" = q"(a, b, c, d)"
80+
assert(first q"a")
81+
assert(rest List(q"b", q"c"))
82+
assert(last q"d")
5583
}
5684

5785
property("deconstruct cases") = test {
5886
val q"$x match { case ..$cases }" = q"x match { case 1 => case 2 => }"
59-
x q"x" && cases List(cq"1 =>", cq"2 =>")
87+
assert(x q"x")
88+
assert(cases List(cq"1 =>", cq"2 =>"))
89+
}
90+
91+
property("deconstruct splitting last case") = test {
92+
val q"$_ match { case ..$cases case $last }" = q"x match { case 1 => case 2 => case 3 => }"
93+
assert(cases List(cq"1 =>", cq"2 =>"))
94+
assert(last cq"3 =>")
6095
}
6196

6297
property("deconstruct block") = test {
6398
val q"{ ..$xs }" = q"{ x1; x2; x3 }"
6499
assert(xs List(q"x1", q"x2", q"x3"))
65100
}
66101

102+
property("deconstruct last element of a block") = test {
103+
val q"{ ..$xs; $x }" = q"x1; x2; x3; x4"
104+
assert(xs List(q"x1", q"x2", q"x3"))
105+
assert(x q"x4")
106+
}
107+
67108
property("exhaustive function matcher") = test {
68109
def matches(line: String) { val q"(..$args) => $body" = parse(line) }
69110
matches("() => bippy")

0 commit comments

Comments
 (0)