From fae2912a60b33f6866a87d30a4e698e433939dc7 Mon Sep 17 00:00:00 2001 From: Denys Shabalin Date: Fri, 28 Feb 2014 14:04:31 +0100 Subject: [PATCH] Fix block construction/deconstruction asymmetry 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. --- .../tools/reflect/quasiquotes/Reifiers.scala | 2 ++ .../reflect/internal/ReificationSupport.scala | 29 ++++++++++--------- .../scala/reflect/internal/TreeGen.scala | 2 +- .../quasiquotes/TermConstructionProps.scala | 19 ++++++++---- .../quasiquotes/TermDeconstructionProps.scala | 13 +++++++++ 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala index 4937bfc35dc3..481897d0ec7e 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala @@ -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) => diff --git a/src/reflect/scala/reflect/internal/ReificationSupport.scala b/src/reflect/scala/reflect/internal/ReificationSupport.scala index ea230a215bf8..7f345845feb0 100644 --- a/src/reflect/scala/reflect/internal/ReificationSupport.scala +++ b/src/reflect/scala/reflect/internal/ReificationSupport.scala @@ -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) => @@ -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]) = @@ -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 } } diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index 6011289bafb8..9066c73393d0 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -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) diff --git a/test/files/scalacheck/quasiquotes/TermConstructionProps.scala b/test/files/scalacheck/quasiquotes/TermConstructionProps.scala index 10ce1604b114..fd4d2e9c4b29 100644 --- a/test/files/scalacheck/quasiquotes/TermConstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/TermConstructionProps.scala @@ -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) @@ -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 { diff --git a/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala b/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala index c77946c2919e..e96d1186f758 100644 --- a/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala @@ -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; ()" + } + } }