summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
authorDen Shabalin <den.shabalin@gmail.com>2013-12-02 16:56:37 +0100
committerDen Shabalin <den.shabalin@gmail.com>2013-12-10 16:02:45 +0100
commit1188f95acf71486c4a75b9ad428a92e6e401f5cd (patch)
tree9eb308c7f20bbffc50d38886da2a8a91889368a9 /src/compiler
parent4c899ea34c01eebf1215abd579d11393cf20a487 (diff)
downloadscala-1188f95acf71486c4a75b9ad428a92e6e401f5cd.tar.gz
scala-1188f95acf71486c4a75b9ad428a92e6e401f5cd.tar.bz2
scala-1188f95acf71486c4a75b9ad428a92e6e401f5cd.zip
Introduce support for Unliftable for Quasiquotes
Unliftable is a type class similar to existing Liftable that lets users to extract custom data types out of trees with the help of straightforward type ascription syntax: val q“foo.bar(${baz: Baz})” = ... This will use Unliftable[Baz] to extract custom data type Baz out of a tree nested inside of the another tree. A simpler example would be extracting of constant values: val q”${x: Int} + ${y: Int}” = q”1 + 2”
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/scala/tools/reflect/quasiquotes/Holes.scala64
-rw-r--r--src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala8
2 files changed, 66 insertions, 6 deletions
diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala b/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala
index c90401e765..8457649bce 100644
--- a/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala
+++ b/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala
@@ -3,6 +3,7 @@ package quasiquotes
import scala.collection.{immutable, mutable}
import scala.reflect.internal.Flags._
+import scala.reflect.macros.TypecheckException
class Cardinality private[Cardinality](val value: Int) extends AnyVal {
def pred = { assert(value - 1 >= 0); new Cardinality(value - 1) }
@@ -33,6 +34,7 @@ trait Holes { self: Quasiquotes =>
protected lazy val IterableTParam = IterableClass.typeParams(0).asType.toType
protected def inferParamImplicit(tfun: Type, targ: Type) = c.inferImplicitValue(appliedType(tfun, List(targ)), silent = true)
protected def inferLiftable(tpe: Type): Tree = inferParamImplicit(liftableType, tpe)
+ protected def inferUnliftable(tpe: Type): Tree = inferParamImplicit(unliftableType, tpe)
protected def isLiftableType(tpe: Type) = inferLiftable(tpe) != EmptyTree
protected def isNativeType(tpe: Type) =
(tpe <:< treeType) || (tpe <:< nameType) || (tpe <:< modsType) ||
@@ -136,8 +138,66 @@ trait Holes { self: Quasiquotes =>
}
class UnapplyHole(val cardinality: Cardinality, pat: Tree) extends Hole {
- val (tree, pos) = pat match {
- case Bind(pname, inner) => (Bind(pname, Ident(nme.WILDCARD)), inner.pos)
+ val (placeholderName, pos, tptopt) = pat match {
+ case Bind(pname, inner @ Bind(_, Typed(Ident(nme.WILDCARD), tpt))) => (pname, inner.pos, Some(tpt))
+ case Bind(pname, inner @ Typed(Ident(nme.WILDCARD), tpt)) => (pname, inner.pos, Some(tpt))
+ case Bind(pname, inner) => (pname, inner.pos, None)
}
+ val treeNoUnlift = Bind(placeholderName, Ident(nme.WILDCARD))
+ lazy val tree =
+ tptopt.map { tpt =>
+ val TypeDef(_, _, _, typedTpt) =
+ try c.typeCheck(TypeDef(NoMods, TypeName("T"), Nil, tpt))
+ catch { case TypecheckException(pos, msg) => c.abort(pos.asInstanceOf[c.Position], msg) }
+ val tpe = typedTpt.tpe
+ val (iterableCard, _) = stripIterable(tpe)
+ if (iterableCard.value < cardinality.value)
+ c.abort(pat.pos, s"Can't extract $tpe with $cardinality, consider using $iterableCard")
+ val (_, strippedTpe) = stripIterable(tpe, limit = Some(cardinality))
+ if (strippedTpe <:< treeType) treeNoUnlift
+ else
+ unlifters.spawn(strippedTpe, cardinality).map {
+ Apply(_, treeNoUnlift :: Nil)
+ }.getOrElse {
+ c.abort(pat.pos, s"Can't find $unliftableType[$strippedTpe], consider providing it")
+ }
+ }.getOrElse { treeNoUnlift }
+ }
+
+ /** Full support for unliftable implies that it's possible to interleave
+ * deconstruction with higher cardinality and unlifting of the values.
+ * In particular extraction of List[Tree] as List[T: Unliftable] requires
+ * helper extractors that would do the job: UnliftHelper1[T]. Similarly
+ * List[List[Tree]] needs UnliftHelper2[T].
+ *
+ * See also "unlift list" tests in UnapplyProps.scala
+ */
+ object unlifters {
+ private var records = List.empty[(Type, Cardinality)]
+ // Request an UnliftHelperN[T] where n == card and T == tpe.
+ // If card == 0 then helper is not needed and plain instance
+ // of unliftable is returned.
+ def spawn(tpe: Type, card: Cardinality): Option[Tree] = {
+ val unlifter = inferUnliftable(tpe)
+ if (unlifter == EmptyTree) None
+ else if (card == NoDot) Some(unlifter)
+ else {
+ val idx = records.indexWhere { p => p._1 =:= tpe && p._2 == card }
+ val resIdx = if (idx != -1) idx else { records +:= (tpe, card); records.length - 1}
+ Some(Ident(TermName(nme.QUASIQUOTE_UNLIFT_HELPER + resIdx)))
+ }
+ }
+ // Returns a list of vals that will defined required unlifters
+ def preamble(): List[Tree] =
+ records.zipWithIndex.map { case ((tpe, card), idx) =>
+ val name = TermName(nme.QUASIQUOTE_UNLIFT_HELPER + idx)
+ val helperName = card match { case DotDot => nme.UnliftHelper1 case DotDotDot => nme.UnliftHelper2 }
+ val lifter = inferUnliftable(tpe)
+ assert(helperName.isTermName)
+ // q"val $name: $u.build.${helperName.toTypeName} = $u.build.$helperName($lifter)"
+ ValDef(NoMods, name,
+ AppliedTypeTree(Select(Select(u, nme.build), helperName.toTypeName), List(TypeTree(tpe))),
+ Apply(Select(Select(u, nme.build), helperName), lifter :: Nil))
+ }
}
}
diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala
index 6bd7663526..6d7aafe266 100644
--- a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala
+++ b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala
@@ -93,12 +93,12 @@ trait Reifiers { self: Quasiquotes =>
// cq"$tree if $guard => $succ" :: cq"_ => $fail" :: Nil
CaseDef(tree, guard, succ) :: CaseDef(Ident(nme.WILDCARD), EmptyTree, fail) :: Nil
}
- // q"new { def unapply(tree: $AnyClass) = tree match { case ..$cases } }.unapply(..$args)"
+ // q"new { def unapply(tree: $AnyClass) = { ..${unlifters.preamble()}; 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)))),
+ SyntacticBlock(unlifters.preamble() :+ Match(Ident(nme.tree), cases))))),
nme.unapply),
args)
}
@@ -198,7 +198,7 @@ trait Reifiers { self: Quasiquotes =>
case Placeholder(hole: ApplyHole) =>
if (!(hole.tpe <:< nameType)) c.abort(hole.pos, s"$nameType expected but ${hole.tpe} found")
hole.tree
- case Placeholder(hole: UnapplyHole) => hole.tree
+ case Placeholder(hole: UnapplyHole) => hole.treeNoUnlift
case FreshName(prefix) if prefix != nme.QUASIQUOTE_NAME_PREFIX =>
def fresh() = c.freshName[TermName](nme.QUASIQUOTE_NAME_PREFIX)
def introduceName() = { val n = fresh(); nameMap(name) += n; n}
@@ -401,7 +401,7 @@ trait Reifiers { self: Quasiquotes =>
case hole :: Nil =>
if (m.annotations.length != 1) c.abort(hole.pos, "Can't extract modifiers together with annotations, consider extracting just modifiers")
ensureNoExplicitFlags(m, hole.pos)
- hole.tree
+ hole.treeNoUnlift
case _ :: hole :: _ =>
c.abort(hole.pos, "Can't extract multiple modifiers together, consider extracting a single modifiers instance")
case Nil =>