Skip to content

Commit

Permalink
SI-8173 add support for patterns like init :+ last to quasiquotes
Browse files Browse the repository at this point in the history
Adds support for patterns like:

  val q"{ ..$init; $last }" = q"{ a; b; c }"
  // init == List(q"a", q"b")
  // last == q"c"

Which under the hood get compiled as `:+` patterns:

  SyntacticBlock(init :+ last)
  • Loading branch information
densh committed Feb 2, 2014
1 parent 1a3de95 commit ffc3203
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 87 deletions.
Expand Up @@ -16,7 +16,7 @@ abstract class Quasiquotes extends Parsers

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

def expandQuasiquote = {
debug(s"\nmacro application:\n${c.macroApplication}\n")
debug(s"\ncode to parse:\n$code\n")
debug(s"macro application:\n${c.macroApplication}\n")
debug(s"code to parse:\n$code\n")
val tree = parse(code)
debug(s"parsed:\n${showRaw(tree)}\n$tree\n")
val reified = reify(tree)
Expand Down
36 changes: 24 additions & 12 deletions src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala
Expand Up @@ -162,7 +162,7 @@ trait Reifiers { self: Quasiquotes =>
reifyBuildCall(nme.SyntacticNew, earlyDefs, parents, selfdef, body)
case SyntacticDefDef(mods, name, tparams, build.ImplicitParams(vparamss, implparams), tpt, rhs) =>
if (implparams.nonEmpty)
mirrorBuildCall(nme.SyntacticDefDef, reify(mods), reify(name), reify(tparams),
mirrorBuildCall(nme.SyntacticDefDef, reify(mods), reify(name), reify(tparams),
reifyBuildCall(nme.ImplicitParams, vparamss, implparams), reify(tpt), reify(rhs))
else
reifyBuildCall(nme.SyntacticDefDef, mods, name, tparams, vparamss, tpt, rhs)
Expand Down Expand Up @@ -305,7 +305,7 @@ trait Reifiers { self: Quasiquotes =>
* > reifyMultiCardinalityList(lst) { ... } { ... }
* q"List($foo, $bar) ++ ${holeMap(qq$f3948f9s$1).tree}"
*/
def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree): Tree
def reifyMultiCardinalityList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree

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

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

}
class UnapplyReifier extends Reifier(isReifyingExpressions = false) {
def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree): Tree = xs match {
case init :+ last if fill.isDefinedAt(last) =>
init.foldRight[Tree](fill(last)) { (el, rest) =>
val cons = Select(Select(Select(Ident(nme.scala_), nme.collection), nme.immutable), nme.CONS)
Apply(cons, List(fallback(el), rest))
}
case _ =>
mkList(xs.map(fallback))
private def collection = ScalaDot(nme.collection)
private def collectionColonPlus = Select(collection, nme.COLONPLUS)
private def collectionCons = Select(Select(collection, nme.immutable), nme.CONS)
private def collectionNil = Select(Select(collection, nme.immutable), nme.Nil)
// pq"$lhs :+ $rhs"
private def append(lhs: Tree, rhs: Tree) = Apply(collectionColonPlus, lhs :: rhs :: Nil)
// pq"$lhs :: $rhs"
private def cons(lhs: Tree, rhs: Tree) = Apply(collectionCons, lhs :: rhs :: Nil)

def reifyMultiCardinalityList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree = {
val grouped = group(xs) { (a, b) => !fill.isDefinedAt(a) && !fill.isDefinedAt(b) }
def appended(lst: List[Any], init: Tree) = lst.foldLeft(init) { (l, r) => append(l, fallback(r)) }
def prepended(lst: List[Any], init: Tree) = lst.foldRight(init) { (l, r) => cons(fallback(l), r) }
grouped match {
case init :: List(hole) :: last :: Nil if fill.isDefinedAt(hole) => appended(last, prepended(init, fill(hole)))
case init :: List(hole) :: Nil if fill.isDefinedAt(hole) => prepended(init, fill(hole))
case List(hole) :: last :: Nil if fill.isDefinedAt(hole) => appended(last, fill(hole))
case List(hole) :: Nil if fill.isDefinedAt(hole) => fill(hole)
case _ => prepended(xs, collectionNil)
}
}

override def reifyModifiers(m: Modifiers) =
Expand Down
51 changes: 26 additions & 25 deletions src/reflect/scala/reflect/internal/StdNames.scala
Expand Up @@ -822,31 +822,32 @@ trait StdNames {
def newLazyValSlowComputeName(lzyValName: Name) = lzyValName append LAZY_SLOW_SUFFIX

// ASCII names for operators
val ADD = encode("+")
val AND = encode("&")
val ASR = encode(">>")
val CONS = encode("::")
val DIV = encode("/")
val EQ = encode("==")
val EQL = encode("=")
val GE = encode(">=")
val GT = encode(">")
val HASHHASH = encode("##")
val LE = encode("<=")
val LSL = encode("<<")
val LSR = encode(">>>")
val LT = encode("<")
val MINUS = encode("-")
val MOD = encode("%")
val MUL = encode("*")
val NE = encode("!=")
val OR = encode("|")
val PLUS = ADD // technically redundant, but ADD looks funny with MINUS
val PLUSPLUS = encode("++")
val SUB = MINUS // ... as does SUB with PLUS
val XOR = encode("^")
val ZAND = encode("&&")
val ZOR = encode("||")
val ADD = encode("+")
val AND = encode("&")
val ASR = encode(">>")
val CONS = encode("::")
val COLONPLUS = encode(":+")
val DIV = encode("/")
val EQ = encode("==")
val EQL = encode("=")
val GE = encode(">=")
val GT = encode(">")
val HASHHASH = encode("##")
val LE = encode("<=")
val LSL = encode("<<")
val LSR = encode(">>>")
val LT = encode("<")
val MINUS = encode("-")
val MOD = encode("%")
val MUL = encode("*")
val NE = encode("!=")
val OR = encode("|")
val PLUS = ADD // technically redundant, but ADD looks funny with MINUS
val PLUSPLUS = encode("++")
val SUB = MINUS // ... as does SUB with PLUS
val XOR = encode("^")
val ZAND = encode("&&")
val ZOR = encode("||")

// unary operators
val UNARY_~ = encode("unary_~")
Expand Down
22 changes: 11 additions & 11 deletions test/files/scalacheck/quasiquotes/DefinitionConstructionProps.scala
Expand Up @@ -229,13 +229,13 @@ trait MethodConstruction { self: QuasiquoteProperties =>

property("splice type name into annotation") = test {
val name = TypeName("annot")
assertSameAnnots(q"@$name def foo", List(annot(name)))
assertSameAnnots(q"@$name def foo", List(q"new $name"))
}

property("splice ident into annotation") = test {
val name = TypeName("annot")
val ident = Ident(name)
assertSameAnnots(q"@$ident def foo", List(annot(name)))
assertSameAnnots(q"@$ident def foo", List(q"new $name"))
}

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

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

property("splice multiple annotations (1)") = test {
val annot1 = annot("a1")
val annot2 = annot("a2")
val annot1 = q"new a1"
val annot2 = q"new a2"
val res = q"@$annot1 @$annot2 def foo"
assertSameAnnots(res, List(annot1, annot2))
}

property("splice multiple annotations (2)") = test {
val annot1 = annot("a1")
val annots = List(annot("a2"), annot("a3"))
val annot1 = q"new a1"
val annots = List(q"new a2", q"new a3")
val res = q"@$annot1 @..$annots def foo"
assertSameAnnots(res, annot1 :: annots)
}

property("splice annotations with arguments (1)") = test {
val a = annot("a", List(q"x"))
val a = q"new a(x)"
assertSameAnnots(q"@$a def foo", q"@a(x) def foo")
}

property("splice annotations with arguments (2)") = test {
val a = newTypeName("a")
val a = TypeName("a")
assertSameAnnots(q"@$a(x) def foo", q"@a(x) def foo")
}

property("splice annotations with arguments (3") = test {
val a = Ident(newTypeName("a"))
val a = Ident(TypeName("a"))
assertSameAnnots(q"@$a(x) def foo", q"@a(x) def foo")
}

Expand All @@ -286,7 +286,7 @@ trait MethodConstruction { self: QuasiquoteProperties =>
}

property("can't splice annotations with arguments specificed twice") = test {
val a = annot("a", List(q"x"))
val a = q"new a(x)"
assertThrows[IllegalArgumentException] {
q"@$a(y) def foo"
}
Expand Down
Expand Up @@ -125,18 +125,28 @@ trait ModsDeconstruction { self: QuasiquoteProperties =>
}

property("@..$annots def foo") = test {
val a = annot("a")
val b = annot("b")
val a = q"new a"
val b = q"new b"
val q"@..$annots def foo" = q"@$a @$b def foo"
annots ≈ List(a, b)
}

property("@$annot @..$annots def foo") = test {
val a = annot("a")
val b = annot("b")
val c = annot("c")
val a = q"new a"
val b = q"new b"
val c = q"new c"
val q"@$first @..$rest def foo" = q"@$a @$b @$c def foo"
first ≈ a && rest ≈ List(b, c)
assert(first ≈ a)
assert(rest ≈ List(b, c))
}

property("@..$anots @$annot def foo") = test {
val a = q"new a"
val b = q"new b"
val c = q"new c"
val q"@..$init @$last def foo" = q"@$a @$b @$c def foo"
assert(init ≈ List(a, b))
assert(last ≈ c)
}
}

Expand Down
12 changes: 0 additions & 12 deletions test/files/scalacheck/quasiquotes/ErrorProps.scala
Expand Up @@ -32,12 +32,6 @@ object ErrorProps extends QuasiquoteProperties("errors") {
q"@...$annots def foo"
""")

property("@..$first @$rest def foo") = fails(
"Can't extract with .. here",
"""
q"@a @b @c def foo" match { case q"@..$first @$rest def foo" => }
""")

property("only literal string arguments") = fails(
"Quasiquotes can only be used with literal strings",
"""
Expand Down Expand Up @@ -140,12 +134,6 @@ object ErrorProps extends QuasiquoteProperties("errors") {
q"$m1 $m2 def foo"
""")

property("can't extract with .. card here") = fails(
"Can't extract with .. here",
"""
val q"f(..$xs, $y)" = EmptyTree
""")

property("can't extract mods with annots") = fails(
"Can't extract modifiers together with annotations, consider extracting just modifiers",
"""
Expand Down
Expand Up @@ -22,8 +22,18 @@ object PatternDeconstructionProps extends QuasiquoteProperties("pattern deconstr
pat0 ≈ pat && subpat0 ≈ subpat
}

property("extract apply many") = forAll { (pat: Tree, subpats: List[Tree]) =>
val pq"$pat0(..$subpats0)" = pq"$pat(..$subpats)"
pat0 ≈ pat && subpats0 ≈ subpats
}

property("extract apply last") = forAll { (pat: Tree, subpats: List[Tree], subpatlast: Tree) =>
val pq"$pat0(..$subpats0, $subpatlast0)" = pq"$pat(..$subpats, $subpatlast)"
pat0 ≈ pat && subpats0 ≈ subpats && subpatlast0 ≈ subpatlast
}

property("extract casedef") = forAll { (pat: Tree, cond: Tree, body: Tree) =>
val cq"$pat0 if $cond0 => $body0" = cq"$pat if $cond => $body"
pat0 ≈ pat && cond0 ≈ cond && body0 ≈ body
}
}
}
5 changes: 0 additions & 5 deletions test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala
Expand Up @@ -116,10 +116,5 @@ trait Helpers {
}
}

def annot(name: String): Tree = annot(TypeName(name), Nil)
def annot(name: TypeName): Tree = annot(name, Nil)
def annot(name: String, args: List[Tree]): Tree = annot(TypeName(name), args)
def annot(name: TypeName, args: List[Tree]): Tree = q"new $name(..$args)"

val scalapkg = build.setSymbol(Ident(TermName("scala")), definitions.ScalaPackage)
}
53 changes: 47 additions & 6 deletions test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala
Expand Up @@ -29,14 +29,34 @@ object TermDeconstructionProps extends QuasiquoteProperties("term deconstruction
y1 ≈ x1 && y2 ≈ x2 && ys ≈ List(x3)
}

property("f(y1, ..ys, yn)") = forAll { (x1: Tree, x2: Tree, x3: Tree, x4: Tree) =>
val q"f($y1, ..$ys, $yn)" = q"f($x1, $x2, $x3, $x4)"
y1 ≈ x1 && ys ≈ List(x2, x3) && yn ≈ x4
}

property("f(..ys, y_{n-1}, y_n)") = forAll { (x1: Tree, x2: Tree, x3: Tree, x4: Tree) =>
val q"f(..$ys, $yn1, $yn)" = q"f($x1, $x2, $x3, $x4)"
ys ≈ List(x1, x2) && yn1 ≈ x3 && yn ≈ x4
}

property("f(...xss)") = forAll { (x1: Tree, x2: Tree) =>
val q"f(...$argss)" = q"f($x1)($x2)"
argss ≈ List(List(x1), List(x2))
val q"f(...$xss)" = q"f($x1)($x2)"
xss ≈ List(List(x1), List(x2))
}

property("f(...$xss)(..$last)") = forAll { (x1: Tree, x2: Tree, x3: Tree) =>
val q"f(...$xss)(..$last)" = q"f($x1)($x2)($x3)"
xss ≈ List(List(x1), List(x2)) && last ≈ List(x3)
}

property("f(...$xss)(..$lastinit, $lastlast)") = forAll { (x1: Tree, x2: Tree, x3: Tree, x4: Tree) =>
val q"f(...$xss)(..$lastinit, $lastlast)" = q"f($x1)($x2, $x3, $x4)"
xss ≈ List(List(x1)) && lastinit ≈ List(x2, x3) && lastlast ≈ x4
}

property("f(...xss) = f") = forAll { (x1: Tree, x2: Tree) =>
val q"f(...$argss)" = q"f"
argssList()
val q"f(...$xss)" = q"f"
xssList()
}

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

property("deconstruct tuple mixed") = test {
val q"($first, ..$rest)" = q"(a, b, c)"
assert(first ≈ q"a" && rest ≈ List(q"b", q"c"))
assert(first ≈ q"a")
assert(rest ≈ List(q"b", q"c"))
}

property("deconstruct tuple last element") = test {
val q"($first, ..$rest, $last)" = q"(a, b, c, d)"
assert(first ≈ q"a")
assert(rest ≈ List(q"b", q"c"))
assert(last ≈ q"d")
}

property("deconstruct cases") = test {
val q"$x match { case ..$cases }" = q"x match { case 1 => case 2 => }"
x ≈ q"x" && cases ≈ List(cq"1 =>", cq"2 =>")
assert(x ≈ q"x")
assert(cases ≈ List(cq"1 =>", cq"2 =>"))
}

property("deconstruct splitting last case") = test {
val q"$_ match { case ..$cases case $last }" = q"x match { case 1 => case 2 => case 3 => }"
assert(cases ≈ List(cq"1 =>", cq"2 =>"))
assert(last ≈ cq"3 =>")
}

property("deconstruct block") = test {
val q"{ ..$xs }" = q"{ x1; x2; x3 }"
assert(xs ≈ List(q"x1", q"x2", q"x3"))
}

property("deconstruct last element of a block") = test {
val q"{ ..$xs; $x }" = q"x1; x2; x3; x4"
assert(xs ≈ List(q"x1", q"x2", q"x3"))
assert(x ≈ q"x4")
}

property("exhaustive function matcher") = test {
def matches(line: String) { val q"(..$args) => $body" = parse(line) }
matches("() => bippy")
Expand Down

0 comments on commit ffc3203

Please sign in to comment.