diff options
author | Den Shabalin <den.shabalin@gmail.com> | 2013-09-26 18:41:02 +0200 |
---|---|---|
committer | Den Shabalin <den.shabalin@gmail.com> | 2013-10-14 13:30:49 +0200 |
commit | debdd2f316934c417c7d9865ea0f2ea503e51b63 (patch) | |
tree | 2f3b2697034dd260035c13adda75b7482f4ddbdd | |
parent | 60603f2524bf7c06c6f73eefb1cc30b6df7c392d (diff) | |
download | scala-debdd2f316934c417c7d9865ea0f2ea503e51b63.tar.gz scala-debdd2f316934c417c7d9865ea0f2ea503e51b63.tar.bz2 scala-debdd2f316934c417c7d9865ea0f2ea503e51b63.zip |
use regular macro expansion logic for unapply quasiquotes
Previously due to limited support for expansion in apply position
quasiquotes had to use a compiler hook for deconstruction. Now with
recent changes in pattern matcher it's possible to remove that special
case.
7 files changed, 57 insertions, 15 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 7bf342f475..3cd937313f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3274,8 +3274,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // SI-7877 `isTerm` needed to exclude `class T[A] { def unapply(..) }; ... case T[X] =>` case HasUnapply(unapply) if mode.inPatternMode && fun.isTerm => - if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(this, tree, fun, unapply, args, mode, pt) - else doTypedUnapply(tree, fun0, fun, args, mode, pt) + doTypedUnapply(tree, fun0, fun, args, mode, pt) case _ => if (treeInfo.isMacroApplication(tree)) duplErrorTree(MacroTooManyArgumentListsError(tree, fun.symbol)) diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala b/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala index dd849f2bca..f92c9aa845 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala @@ -154,7 +154,7 @@ trait Holes { self: Quasiquotes => object Hole { def apply(splicee: Tree, holeCard: Cardinality): Hole = { - if (splicee.tpe == null) return new Hole(splicee, UnknownLocation, holeCard) + if (method == nme.unapply) return new Hole(splicee, UnknownLocation, holeCard) val (spliceeCard, elementTpe) = parseCardinality(splicee.tpe) def cantSplice() = { val holeCardMsg = if (holeCard != NoDot) s" with $holeCard" else "" diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala index d74350cad8..c31d1fcd12 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala @@ -32,10 +32,17 @@ trait Placeholders { self: Quasiquotes => def appendHole(tree: Tree, cardinality: Cardinality) = { val placeholderName = c.freshName(TermName(nme.QUASIQUOTE_PREFIX + sessionSuffix)) sb.append(placeholderName) - holeMap(placeholderName) = Hole(tree, cardinality) + val holeTree = if (method == nme.unapply) Bind(placeholderName, Ident(nme.WILDCARD)) else tree + holeMap(placeholderName) = Hole(holeTree, cardinality) } - foreach2(args, parts.init) { case (tree, (p, pos)) => + val iargs = method match { + case nme.apply => args + case nme.unapply => List.fill(parts.length - 1)(EmptyTree) + case _ => global.abort("unreachable") + } + + foreach2(iargs, parts.init) { case (tree, (p, pos)) => val (part, cardinality) = parseDots(p) appendPart(part, pos) appendHole(tree, cardinality) @@ -47,7 +54,7 @@ trait Placeholders { self: Quasiquotes => } class HoleMap { - private val underlying = mutable.ListMap[String, Hole]() + private var underlying = immutable.SortedMap[String, Hole]() private val accessed = mutable.Set[String]() def unused: Set[Name] = (underlying.keys.toSet -- accessed).map(TermName(_)) def contains(key: Name) = underlying.contains(key.toString) @@ -64,6 +71,7 @@ trait Placeholders { self: Quasiquotes => accessed += s underlying.get(s) } + def toList = underlying.toList } // Step 2: Transform vanilla Scala AST into an AST with holes diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala b/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala index 1305e25240..fb0ad3bbb0 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala @@ -14,8 +14,9 @@ abstract class Quasiquotes extends Parsers def debug(msg: String): Unit = if (settings.Yquasiquotedebug.value) println(msg) - lazy val (universe: Tree, args, parts, parse, reify) = c.macroApplication match { + lazy val (universe: Tree, args, parts, parse, reify, method) = c.macroApplication match { case Apply(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") 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") @@ -32,7 +33,7 @@ abstract class Quasiquotes extends Parsers case nme.pq => PatternParser.parse(_) case other => global.abort(s"Unknown quasiquote flavor: $other") } - (universe0, args0, parts1, parse0, reify0) + (universe0, args0, parts1, parse0, reify0, method0) case _ => global.abort(s"Couldn't parse call prefix tree ${c.macroApplication}.") } @@ -41,6 +42,7 @@ 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") val tree = parse(code) debug(s"parsed:\n${showRaw(tree)}\n$tree\n") diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala index d205ebece7..9f1a3ce257 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala @@ -7,7 +7,7 @@ import scala.reflect.internal.Flags._ trait Reifiers { self: Quasiquotes => import global._ - import global.build.{Select => _, Ident => _, _} + import global.build.{Select => _, Ident => _, TypeTree => _, _} import global.treeInfo._ import global.definitions._ import Cardinality._ @@ -27,12 +27,48 @@ trait Reifiers { self: Quasiquotes => def action = if (isReifyingExpressions) "splice" else "extract" def holesHaveTypes = isReifyingExpressions + def wrap(tree: Tree) = + if (isReifyingExpressions) tree + else { + val freevars = holeMap.toList.map { case (name, _) => Ident(name) } + val isVarPattern = tree match { case Bind(name, Ident(nme.WILDCARD)) => true case _ => false } + val cases = + if(isVarPattern) { + val Ident(name) :: Nil = freevars + // cq"$name: $treeType => $SomeModule($name)" :: Nil + CaseDef(Bind(name, Typed(Ident(nme.WILDCARD), TypeTree(treeType))), + EmptyTree, Apply(Ident(SomeModule), List(Ident(name)))) :: Nil + } else { + val (succ, fail) = freevars match { + case Nil => + // (q"true", q"false") + (Literal(Constant(true)), Literal(Constant(false))) + case head :: Nil => + // (q"$SomeModule($head)", q"$NoneModule") + (Apply(Ident(SomeModule), List(head)), Ident(NoneModule)) + case vars => + // (q"$SomeModule((..$vars))", q"$NoneModule") + (Apply(Ident(SomeModule), List(SyntacticTuple(vars))), Ident(NoneModule)) + } + // cq"$tree => $succ" :: cq"_ => $fail" :: Nil + CaseDef(tree, EmptyTree, succ) :: CaseDef(Ident(nme.WILDCARD), EmptyTree, fail) :: Nil + } + // q"new { def unapply(tree: $AnyClass) = tree match { case ..$cases } }.unapply(..$args)" + Apply( + Select( + SyntacticNew(Nil, Nil, noSelfType, List( + DefDef(NoMods, nme.unapply, Nil, List(List(ValDef(NoMods, nme.tree, TypeTree(AnyClass.toType), EmptyTree))), TypeTree(), + Match(Ident(nme.tree), cases)))), + nme.unapply), + args) + } + def reifyFillingHoles(tree: Tree): Tree = { val reified = reifyTree(tree) holeMap.unused.foreach { hole => c.abort(holeMap(hole).tree.pos, s"Don't know how to $action here") } - reified + wrap(reified) } override def reifyTree(tree: Tree): Tree = diff --git a/src/reflect/scala/reflect/api/Quasiquotes.scala b/src/reflect/scala/reflect/api/Quasiquotes.scala index 08d3274ca5..8e993af382 100644 --- a/src/reflect/scala/reflect/api/Quasiquotes.scala +++ b/src/reflect/scala/reflect/api/Quasiquotes.scala @@ -8,7 +8,7 @@ trait Quasiquotes { self: Universe => implicit class Quasiquote(ctx: StringContext) { protected trait api { def apply(args: Any*): Any = macro ??? - def unapply(subpatterns: Any*): Option[Any] = macro ??? + def unapply(scrutinee: Any): Any = macro ??? } object q extends api object tq extends api diff --git a/test/files/scalacheck/quasiquotes/ErrorProps.scala b/test/files/scalacheck/quasiquotes/ErrorProps.scala index b9e69e0e88..b0a7641577 100644 --- a/test/files/scalacheck/quasiquotes/ErrorProps.scala +++ b/test/files/scalacheck/quasiquotes/ErrorProps.scala @@ -41,10 +41,7 @@ object ErrorProps extends QuasiquoteProperties("errors") { property("@..$first @$rest def foo") = fails( "Can't extract with .. here", """ - val a = annot("a") - val b = annot("b") - val c = annot("c") - val q"@..$first @$rest def foo" = q"@$a @$b @$c def foo" + q"@a @b @c def foo" match { case q"@..$first @$rest def foo" => } """) property("only literal string arguments") = fails( |