Skip to content

Commit

Permalink
Fix block construction/deconstruction asymmetry
Browse files Browse the repository at this point in the history
Deconstruction of blocks in case clauses uncovered asymmetry between
construction and deconstruction of blocks:

   tree match { case cq"$pat => ..$cases" => cq"$pat => ..$cases" }

Such an identity-like transformation used to produce an incorrect tree due
to the fact that zero-element block was mistakingly associated with
empty tree. Such association was used as a solution to block flatenning:

   val ab = q"a; b"
   q"..$ab; c"       // ==> q"a; b; c"

   val a = q"a"
   q"..$a; c"        // ==> q"a; c"

   val empty = q""
   q"..$empty; c"    // ==> q"c"

This commit changes meaning of zero-element block to a be a synthetic unit
instead. This is consistent with parsing of `{}`, cases, ifs and
non-abstract empty-bodied methods. A local tweak to block flattenning is
used to flatten empty tree as empty list instead.
  • Loading branch information
densh committed Feb 28, 2014
1 parent e17c055 commit fae2912
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 21 deletions.
2 changes: 2 additions & 0 deletions src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala
Expand Up @@ -202,6 +202,8 @@ trait Reifiers { self: Quasiquotes =>
// not to cause infinite recursion.
case block @ SyntacticBlock(stats) if block.isInstanceOf[Block] =>
reifyBuildCall(nme.SyntacticBlock, stats)
case SyntheticUnit() =>
reifyBuildCall(nme.SyntacticBlock, Nil)
case Try(block, catches, finalizer) =>
reifyBuildCall(nme.SyntacticTry, block, catches, finalizer)
case Match(selector, cases) =>
Expand Down
29 changes: 15 additions & 14 deletions src/reflect/scala/reflect/internal/ReificationSupport.scala
Expand Up @@ -94,7 +94,11 @@ trait ReificationSupport { self: SymbolTable =>

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

def toStats(tree: Tree): List[Tree] = SyntacticBlock.unapply(tree).get
def toStats(tree: Tree): List[Tree] = tree match {
case EmptyTree => Nil
case SyntacticBlock(stats) => stats
case _ => throw new IllegalArgumentException(s"can't flatten $tree")
}

def mkAnnotation(tree: Tree): Tree = tree match {
case SyntacticNew(Nil, SyntacticApplied(SyntacticTypeApplied(_, _), _) :: Nil, noSelfType, Nil) =>
Expand Down Expand Up @@ -239,7 +243,7 @@ trait ReificationSupport { self: SymbolTable =>
def unapply(templ: Template): Option[(List[Tree], ValDef, Modifiers, List[List[ValDef]], List[Tree], List[Tree])] = {
val Template(parents, selfType, _) = templ
val tbody = treeInfo.untypecheckedTemplBody(templ)

def result(ctorMods: Modifiers, vparamss: List[List[ValDef]], edefs: List[Tree], body: List[Tree]) =
Some((parents, selfType, ctorMods, vparamss, edefs, body))
def indexOfCtor(trees: List[Tree]) =
Expand Down Expand Up @@ -448,28 +452,25 @@ trait ReificationSupport { self: SymbolTable =>
* block as a list of elements rather than (stats, expr) pair
* it also:
*
* 1. Treats of q"" (empty tree) as zero-element block.
*
* 2. Strips trailing synthetic units which are inserted by the
* 1. Strips trailing synthetic units which are inserted by the
* compiler if the block ends with a definition rather
* than an expression.
* than an expression or is empty.
*
* 3. Matches non-block term trees and recognizes them as
* 2. Matches non-block term trees and recognizes them as
* single-element blocks for sake of consistency with
* compiler's default to treat single-element blocks with
* expressions as just expressions.
* expressions as just expressions. The only exception is q""
* which is not considered to be a block.
*/
object SyntacticBlock extends SyntacticBlockExtractor {
def apply(stats: List[Tree]): Tree =
if (stats.isEmpty) EmptyTree
else gen.mkBlock(stats)
def apply(stats: List[Tree]): Tree = gen.mkBlock(stats)

def unapply(tree: Tree): Option[List[Tree]] = tree match {
case bl @ self.Block(stats, SyntheticUnit()) => Some(treeInfo.untypecheckedBlockBody(bl))
case bl @ self.Block(stats, expr) => Some(treeInfo.untypecheckedBlockBody(bl) :+ expr)
case EmptyTree => Some(Nil)
case _ if tree.isTerm => Some(tree :: Nil)
case _ => None
case SyntheticUnit() => Some(Nil)
case _ if tree.isTerm && tree.nonEmpty => Some(tree :: Nil)
case _ => None
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/reflect/scala/reflect/internal/TreeGen.scala
Expand Up @@ -452,7 +452,7 @@ abstract class TreeGen {

/** Create block of statements `stats` */
def mkBlock(stats: List[Tree]): Tree =
if (stats.isEmpty) Literal(Constant(()))
if (stats.isEmpty) mkSyntheticUnit()
else if (!stats.last.isTerm) Block(stats, mkSyntheticUnit())
else if (stats.length == 1) stats.head
else Block(stats.init, stats.last)
Expand Down
19 changes: 13 additions & 6 deletions test/files/scalacheck/quasiquotes/TermConstructionProps.scala
Expand Up @@ -103,7 +103,7 @@ object TermConstructionProps extends QuasiquoteProperties("term construction") {

def blockInvariant(quote: Tree, trees: List[Tree]) =
quote ≈ (trees match {
case Nil => q""
case Nil => q"{}"
case _ :+ last if !last.isTerm => Block(trees, q"()")
case head :: Nil => head
case init :+ last => Block(init, last)
Expand Down Expand Up @@ -277,11 +277,18 @@ object TermConstructionProps extends QuasiquoteProperties("term construction") {
assert(stats ≈ List(q"def x = 2", q"()"))
}

property("empty-tree as block") = test {
val q"{ ..$stats1 }" = q" "
assert(stats1.isEmpty)
val stats2 = List.empty[Tree]
assert(q"{ ..$stats2 }"q"")
property("empty-tree is not a block") = test {
assertThrows[MatchError] {
val q"{ ..$stats1 }" = q" "
}
}

property("empty block is synthetic unit") = test {
val q"()" = q"{}"
val q"{..$stats}" = q"{}"
assert(stats.isEmpty)
assertEqAst(q"{..$stats}", "{}")
assertEqAst(q"{..$stats}", "()")
}

property("consistent variable order") = test {
Expand Down
13 changes: 13 additions & 0 deletions test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala
Expand Up @@ -186,4 +186,17 @@ object TermDeconstructionProps extends QuasiquoteProperties("term deconstruction
assert(init ≈ List(q"a", q"b"))
assert(last ≈ q"c")
}

property("si-8275 c") = test {
val cq"_ => ..$stats" = cq"_ =>"
assert(stats.isEmpty)
assertEqAst(q"{ case _ => ..$stats }", "{ case _ => }")
}

property("can't flatten type into block") = test {
assertThrows[IllegalArgumentException] {
val tpt = tq"List[Int]"
q"..$tpt; ()"
}
}
}

0 comments on commit fae2912

Please sign in to comment.