From 9ba986e61151cb370ba519f568042776c9f303df Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Thu, 11 Sep 2014 11:27:49 +0200 Subject: moves the impl of quasiquotes to scala.reflect This brings consistency with scala.reflect.reify and scala.reflect.macros already existing in scala-compiler. To the contrast, scala.tools.reflect, the previous home of quasiquotes, is a grab bag of various stuff without any central theme. --- src/compiler/scala/reflect/quasiquotes/Holes.scala | 245 +++++++++++ .../scala/reflect/quasiquotes/Parsers.scala | 228 ++++++++++ .../scala/reflect/quasiquotes/Placeholders.scala | 201 +++++++++ .../scala/reflect/quasiquotes/Quasiquotes.scala | 60 +++ .../scala/reflect/quasiquotes/Reifiers.scala | 487 +++++++++++++++++++++ src/compiler/scala/tools/reflect/FastTrack.scala | 2 +- .../scala/tools/reflect/quasiquotes/Holes.scala | 245 ----------- .../scala/tools/reflect/quasiquotes/Parsers.scala | 228 ---------- .../tools/reflect/quasiquotes/Placeholders.scala | 201 --------- .../tools/reflect/quasiquotes/Quasiquotes.scala | 60 --- .../scala/tools/reflect/quasiquotes/Reifiers.scala | 487 --------------------- 11 files changed, 1222 insertions(+), 1222 deletions(-) create mode 100644 src/compiler/scala/reflect/quasiquotes/Holes.scala create mode 100644 src/compiler/scala/reflect/quasiquotes/Parsers.scala create mode 100644 src/compiler/scala/reflect/quasiquotes/Placeholders.scala create mode 100644 src/compiler/scala/reflect/quasiquotes/Quasiquotes.scala create mode 100644 src/compiler/scala/reflect/quasiquotes/Reifiers.scala delete mode 100644 src/compiler/scala/tools/reflect/quasiquotes/Holes.scala delete mode 100644 src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala delete mode 100644 src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala delete mode 100644 src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala delete mode 100644 src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala diff --git a/src/compiler/scala/reflect/quasiquotes/Holes.scala b/src/compiler/scala/reflect/quasiquotes/Holes.scala new file mode 100644 index 0000000000..38b05f9d4b --- /dev/null +++ b/src/compiler/scala/reflect/quasiquotes/Holes.scala @@ -0,0 +1,245 @@ +package scala.reflect +package quasiquotes + +import scala.collection.{immutable, mutable} +import scala.reflect.internal.Flags._ +import scala.reflect.macros.TypecheckException + +class Rank private[Rank](val value: Int) extends AnyVal { + def pred = { assert(value - 1 >= 0); new Rank(value - 1) } + def succ = new Rank(value + 1) + override def toString = if (value == 0) "no dots" else "." * (value + 1) +} + +object Rank { + val NoDot = new Rank(0) + val DotDot = new Rank(1) + val DotDotDot = new Rank(2) + object Dot { def unapply(rank: Rank) = rank != NoDot } + def parseDots(part: String) = { + if (part.endsWith("...")) (part.stripSuffix("..."), DotDotDot) + else if (part.endsWith("..")) (part.stripSuffix(".."), DotDot) + else (part, NoDot) + } +} + +/** Defines abstractions that provide support for splicing into Scala syntax. + */ +trait Holes { self: Quasiquotes => + import global._ + import Rank._ + import definitions._ + import universeTypes._ + + private lazy val IterableTParam = IterableClass.typeParams(0).asType.toType + private def inferParamImplicit(tfun: Type, targ: Type) = c.inferImplicitValue(appliedType(tfun, List(targ)), silent = true) + private def inferLiftable(tpe: Type): Tree = inferParamImplicit(liftableType, tpe) + private def inferUnliftable(tpe: Type): Tree = inferParamImplicit(unliftableType, tpe) + private def isLiftableType(tpe: Type) = inferLiftable(tpe) != EmptyTree + private def isNativeType(tpe: Type) = + (tpe <:< treeType) || (tpe <:< nameType) || (tpe <:< modsType) || + (tpe <:< flagsType) || (tpe <:< symbolType) + private def isBottomType(tpe: Type) = + tpe <:< NothingClass.tpe || tpe <:< NullClass.tpe + private def extractIterableTParam(tpe: Type) = + IterableTParam.asSeenFrom(tpe, IterableClass) + private def stripIterable(tpe: Type, limit: Rank = DotDotDot): (Rank, Type) = + if (limit == NoDot) (NoDot, tpe) + else if (tpe != null && !isIterableType(tpe)) (NoDot, tpe) + else if (isBottomType(tpe)) (NoDot, tpe) + else { + val targ = extractIterableTParam(tpe) + val (rank, innerTpe) = stripIterable(targ, limit.pred) + (rank.succ, innerTpe) + } + private def iterableTypeFromRank(n: Rank, tpe: Type): Type = { + if (n == NoDot) tpe + else appliedType(IterableClass.toType, List(iterableTypeFromRank(n.pred, tpe))) + } + + /** Hole encapsulates information about unquotees in quasiquotes. + * It packs together a rank, pre-reified tree representation + * (possibly preprocessed) and position. + */ + abstract class Hole { + val tree: Tree + val pos: Position + val rank: Rank + } + + object Hole { + def apply(rank: Rank, tree: Tree): Hole = + if (method != nme.unapply) new ApplyHole(rank, tree) + else new UnapplyHole(rank, tree) + def unapply(hole: Hole): Some[(Tree, Rank)] = Some((hole.tree, hole.rank)) + } + + class ApplyHole(annotatedRank: Rank, unquotee: Tree) extends Hole { + val (strippedTpe, tpe): (Type, Type) = { + val (strippedRank, strippedTpe) = stripIterable(unquotee.tpe, limit = annotatedRank) + if (isBottomType(strippedTpe)) cantSplice() + else if (isNativeType(strippedTpe)) { + if (strippedRank != NoDot && !(strippedTpe <:< treeType) && !isLiftableType(strippedTpe)) cantSplice() + else (strippedTpe, iterableTypeFromRank(annotatedRank, strippedTpe)) + } else if (isLiftableType(strippedTpe)) (strippedTpe, iterableTypeFromRank(annotatedRank, treeType)) + else cantSplice() + } + + val tree = { + def inner(itpe: Type)(tree: Tree) = + if (isNativeType(itpe)) tree + else if (isLiftableType(itpe)) lifted(itpe)(tree) + else global.abort("unreachable") + if (annotatedRank == NoDot) inner(strippedTpe)(unquotee) + else iterated(annotatedRank, unquotee, unquotee.tpe) + } + + val pos = unquotee.pos + + val rank = stripIterable(tpe)._1 + + private def cantSplice(): Nothing = { + val (iterableRank, iterableType) = stripIterable(unquotee.tpe) + val holeRankMsg = if (annotatedRank != NoDot) s" with $annotatedRank" else "" + val action = "unquote " + unquotee.tpe + holeRankMsg + val suggestRank = annotatedRank != iterableRank || annotatedRank != NoDot + val unquoteeRankMsg = if (annotatedRank != iterableRank && iterableRank != NoDot) s"using $iterableRank" else "omitting the dots" + val rankSuggestion = if (suggestRank) unquoteeRankMsg else "" + val suggestLifting = (annotatedRank == NoDot || iterableRank != NoDot) && !(iterableType <:< treeType) && !isLiftableType(iterableType) + val liftedTpe = if (annotatedRank != NoDot) iterableType else unquotee.tpe + val liftSuggestion = if (suggestLifting) s"providing an implicit instance of Liftable[$liftedTpe]" else "" + val advice = + if (isBottomType(iterableType)) "bottom type values often indicate programmer mistake" + else "consider " + List(rankSuggestion, liftSuggestion).filter(_ != "").mkString(" or ") + c.abort(unquotee.pos, s"Can't $action, $advice") + } + + private def lifted(tpe: Type)(tree: Tree): Tree = { + val lifter = inferLiftable(tpe) + assert(lifter != EmptyTree, s"couldnt find a liftable for $tpe") + val lifted = Apply(lifter, List(tree)) + atPos(tree.pos)(lifted) + } + + private def toStats(tree: Tree): Tree = + // q"$u.internal.reificationSupport.toStats($tree)" + Apply(Select(Select(Select(u, nme.internal), nme.reificationSupport), nme.toStats), tree :: Nil) + + private def toList(tree: Tree, tpe: Type): Tree = + if (isListType(tpe)) tree + else Select(tree, nme.toList) + + private def mapF(tree: Tree, f: Tree => Tree): Tree = + if (f(Ident(TermName("x"))) equalsStructure Ident(TermName("x"))) tree + else { + val x: TermName = c.freshName() + // q"$tree.map { $x => ${f(Ident(x))} }" + Apply(Select(tree, nme.map), + Function(ValDef(Modifiers(PARAM), x, TypeTree(), EmptyTree) :: Nil, + f(Ident(x))) :: Nil) + } + + private object IterableType { + def unapply(tpe: Type): Option[Type] = + if (isIterableType(tpe)) Some(extractIterableTParam(tpe)) else None + } + + private object LiftedType { + def unapply(tpe: Type): Option[Tree => Tree] = + if (tpe <:< treeType) Some(t => t) + else if (isLiftableType(tpe)) Some(lifted(tpe)(_)) + else None + } + + /** Map high-rank unquotee onto an expression that eveluates as a list of given rank. + * + * All possible combinations of representations are given in the table below: + * + * input output for T <: Tree output for T: Liftable + * + * ..${x: Iterable[T]} x.toList x.toList.map(lift) + * ..${x: T} toStats(x) toStats(lift(x)) + * + * ...${x: Iterable[Iterable[T]]} x.toList { _.toList } x.toList.map { _.toList.map(lift) } + * ...${x: Iterable[T]} x.toList.map { toStats(_) } x.toList.map { toStats(lift(_)) } + * ...${x: T} toStats(x).map { toStats(_) } toStats(lift(x)).map { toStats(_) } + * + * For optimization purposes `x.toList` is represented as just `x` if it is statically known that + * x is not just an Iterable[T] but a List[T]. Similarly no mapping is performed if mapping function is + * known to be an identity. + */ + private def iterated(rank: Rank, tree: Tree, tpe: Type): Tree = (rank, tpe) match { + case (DotDot, tpe @ IterableType(LiftedType(lift))) => mapF(toList(tree, tpe), lift) + case (DotDot, LiftedType(lift)) => toStats(lift(tree)) + case (DotDotDot, tpe @ IterableType(inner)) => mapF(toList(tree, tpe), t => iterated(DotDot, t, inner)) + case (DotDotDot, LiftedType(lift)) => mapF(toStats(lift(tree)), toStats) + case _ => global.abort("unreachable") + } + } + + class UnapplyHole(val rank: Rank, pat: Tree) extends Hole { + 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 (iterableRank, _) = stripIterable(tpe) + if (iterableRank.value < rank.value) + c.abort(pat.pos, s"Can't extract $tpe with $rank, consider using $iterableRank") + val (_, strippedTpe) = stripIterable(tpe, limit = rank) + if (strippedTpe <:< treeType) treeNoUnlift + else + unlifters.spawn(strippedTpe, rank).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 rank and unlifting of the values. + * In particular extraction of List[Tree] as List[T: Unliftable] requires + * helper extractors that would do the job: UnliftListElementwise[T]. Similarly + * List[List[Tree]] needs UnliftListOfListsElementwise[T]. + * + * See also "unlift list" tests in UnapplyProps.scala + */ + object unlifters { + private var records = List.empty[(Type, Rank)] + // Materialize unlift helper that does elementwise + // unlifting for corresponding rank and type. + def spawn(tpe: Type, rank: Rank): Option[Tree] = { + val unlifter = inferUnliftable(tpe) + if (unlifter == EmptyTree) None + else if (rank == NoDot) Some(unlifter) + else { + val idx = records.indexWhere { p => p._1 =:= tpe && p._2 == rank } + val resIdx = if (idx != -1) idx else { records +:= (tpe, rank); 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, rank), idx) => + val name = TermName(nme.QUASIQUOTE_UNLIFT_HELPER + idx) + val helperName = rank match { + case DotDot => nme.UnliftListElementwise + case DotDotDot => nme.UnliftListOfListsElementwise + } + val lifter = inferUnliftable(tpe) + assert(helperName.isTermName) + // q"val $name: $u.internal.reificationSupport.${helperName.toTypeName} = $u.internal.reificationSupport.$helperName($lifter)" + ValDef(NoMods, name, + AppliedTypeTree(Select(Select(Select(u, nme.internal), nme.reificationSupport), helperName.toTypeName), List(TypeTree(tpe))), + Apply(Select(Select(Select(u, nme.internal), nme.reificationSupport), helperName), lifter :: Nil)) + } + } +} diff --git a/src/compiler/scala/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/reflect/quasiquotes/Parsers.scala new file mode 100644 index 0000000000..007bac27da --- /dev/null +++ b/src/compiler/scala/reflect/quasiquotes/Parsers.scala @@ -0,0 +1,228 @@ +package scala.reflect +package quasiquotes + +import scala.tools.nsc.ast.parser.{Parsers => ScalaParser} +import scala.tools.nsc.ast.parser.Tokens._ +import scala.compat.Platform.EOL +import scala.reflect.internal.util.{BatchSourceFile, SourceFile, FreshNameCreator} +import scala.collection.mutable.ListBuffer +import scala.util.Try + +/** Builds upon the vanilla Scala parser and teams up together with Placeholders.scala to emulate holes. + * A principled solution to splicing into Scala syntax would be a parser that natively supports holes. + * Unfortunately, that's outside of our reach in Scala 2.11, so we have to emulate. + */ +trait Parsers { self: Quasiquotes => + import global.{Try => _, _} + import build.implodePatDefs + + abstract class Parser extends { + val global: self.global.type = self.global + } with ScalaParser { + def parse(code: String): Tree = { + try { + val file = new BatchSourceFile(nme.QUASIQUOTE_FILE, code) + val parser = new QuasiquoteParser(file) + parser.checkNoEscapingPlaceholders { parser.parseRule(entryPoint) } + } catch { + case mi: MalformedInput => c.abort(correspondingPosition(mi.offset), mi.msg) + } + } + + def correspondingPosition(offset: Int): Position = { + val posMapList = posMap.toList + def containsOffset(start: Int, end: Int) = start <= offset && offset < end + def fallbackPosition = posMapList match { + case (pos1, (start1, end1)) :: _ if start1 > offset => pos1 + case _ :+ ((pos2, (start2, end2))) if end2 <= offset => pos2.withPoint(pos2.point + (end2 - start2)) + } + posMapList.sliding(2).collect { + case (pos1, (start1, end1)) :: _ if containsOffset(start1, end1) => (pos1, offset - start1) + case (pos1, (start1, end1)) :: (pos2, (start2, _)) :: _ if containsOffset(end1, start2) => (pos1, end1 - start1) + case _ :: (pos2, (start2, end2)) :: _ if containsOffset(start2, end2) => (pos2, offset - start2) + }.map { case (pos, offset) => + pos.withPoint(pos.point + offset) + }.toList.headOption.getOrElse(fallbackPosition) + } + + override def token2string(token: Int): String = token match { + case EOF => "end of quote" + case _ => super.token2string(token) + } + + def entryPoint: QuasiquoteParser => Tree + + class QuasiquoteParser(source0: SourceFile) extends SourceFileParser(source0) { parser => + def isHole: Boolean = isIdent && isHole(in.name) + + def isHole(name: Name): Boolean = holeMap.contains(name) + + override implicit lazy val fresh: FreshNameCreator = new FreshNameCreator(nme.QUASIQUOTE_PREFIX) + + override val treeBuilder = new ParserTreeBuilder { + override implicit def fresh: FreshNameCreator = parser.fresh + + // q"(..$xs)" + override def makeTupleTerm(trees: List[Tree]): Tree = TuplePlaceholder(trees) + + // tq"(..$xs)" + override def makeTupleType(trees: List[Tree]): Tree = TupleTypePlaceholder(trees) + + // q"{ $x }" + override def makeBlock(stats: List[Tree]): Tree = method match { + case nme.apply => + stats match { + // we don't want to eagerly flatten trees with placeholders as they + // might have to be wrapped into a block depending on their value + case (head @ Ident(name)) :: Nil if isHole(name) => Block(Nil, head) + case _ => gen.mkBlock(stats, doFlatten = true) + } + case nme.unapply => gen.mkBlock(stats, doFlatten = false) + case other => global.abort("unreachable") + } + + // tq"$a => $b" + override def makeFunctionTypeTree(argtpes: List[Tree], restpe: Tree): Tree = FunctionTypePlaceholder(argtpes, restpe) + + // make q"val (x: T) = rhs" be equivalent to q"val x: T = rhs" for sake of bug compatibility (SI-8211) + override def makePatDef(mods: Modifiers, pat: Tree, rhs: Tree) = pat match { + case TuplePlaceholder(inParensPat :: Nil) => super.makePatDef(mods, inParensPat, rhs) + case _ => super.makePatDef(mods, pat, rhs) + } + } + import treeBuilder.{global => _, unit => _, _} + + // q"def foo($x)" + override def param(owner: Name, implicitmod: Int, caseParam: Boolean): ValDef = + if (isHole && lookingAhead { in.token == COMMA || in.token == RPAREN }) { + ParamPlaceholder(implicitmod, ident()) + } else super.param(owner, implicitmod, caseParam) + + // q"($x) => ..." && q"class X { selfie => } + override def convertToParam(tree: Tree): ValDef = tree match { + case Ident(name) if isHole(name) => ParamPlaceholder(NoFlags, name) + case _ => super.convertToParam(tree) + } + + // q"foo match { case $x }" + override def caseClause(): CaseDef = + if (isHole && lookingAhead { in.token == CASE || in.token == RBRACE || in.token == SEMI }) { + val c = CasePlaceholder(ident()) + while (in.token == SEMI) in.nextToken() + c + } else + super.caseClause() + + override def caseBlock(): Tree = super.caseBlock() match { + case Block(Nil, expr) => expr + case other => other + } + + override def isAnnotation: Boolean = super.isAnnotation || (isHole && lookingAhead { isAnnotation }) + + override def isModifier: Boolean = super.isModifier || (isHole && lookingAhead { isModifier }) + + override def isLocalModifier: Boolean = super.isLocalModifier || (isHole && lookingAhead { isLocalModifier }) + + override def isTemplateIntro: Boolean = super.isTemplateIntro || (isHole && lookingAhead { isTemplateIntro }) + + override def isDefIntro: Boolean = super.isDefIntro || (isHole && lookingAhead { isDefIntro }) + + override def isDclIntro: Boolean = super.isDclIntro || (isHole && lookingAhead { isDclIntro }) + + override def isStatSep(token: Int) = token == EOF || super.isStatSep(token) + + override def expectedMsg(token: Int): String = + if (isHole) expectedMsgTemplate(token2string(token), "unquotee") + else super.expectedMsg(token) + + // $mods def foo + // $mods T + override def readAnnots(annot: => Tree): List[Tree] = in.token match { + case AT => + in.nextToken() + annot :: readAnnots(annot) + case _ if isHole && lookingAhead { isAnnotation || isModifier || isDefIntro || isIdent || isStatSep || in.token == LPAREN } => + val ann = ModsPlaceholder(in.name) + in.nextToken() + ann :: readAnnots(annot) + case _ => + Nil + } + + override def refineStat(): List[Tree] = + if (isHole && !isDclIntro) { + val result = RefineStatPlaceholder(in.name) :: Nil + in.nextToken() + result + } else super.refineStat() + + override def ensureEarlyDef(tree: Tree) = tree match { + case Ident(name: TermName) if isHole(name) => EarlyDefPlaceholder(name) + case _ => super.ensureEarlyDef(tree) + } + + override def isTypedParam(tree: Tree) = super.isTypedParam(tree) || (tree match { + case Ident(name) if isHole(name) => true + case _ => false + }) + + override def topStat = super.topStat.orElse { + case _ if isHole => + val stats = PackageStatPlaceholder(in.name) :: Nil + in.nextToken() + stats + } + + override def enumerator(isFirst: Boolean, allowNestedIf: Boolean = true) = + if (isHole && lookingAhead { in.token == EOF || in.token == RPAREN || isStatSep }) { + val res = ForEnumPlaceholder(in.name) :: Nil + in.nextToken() + res + } else super.enumerator(isFirst, allowNestedIf) + } + } + + /** Wrapper around tree parsed in q"..." quote. Needed to support ..$ splicing on top-level. */ + object Q { + def apply(tree: Tree): Block = Block(Nil, tree).updateAttachment(Q) + def unapply(tree: Tree): Option[Tree] = tree match { + case Block(Nil, contents) if tree.hasAttachment[Q.type] => Some(contents) + case _ => None + } + } + + object TermParser extends Parser { + def entryPoint = parser => Q(implodePatDefs(gen.mkTreeOrBlock(parser.templateOrTopStatSeq()))) + } + + object TypeParser extends Parser { + def entryPoint = { parser => + if (parser.in.token == EOF) + TypeTree() + else + parser.typ() + } + } + + object CaseParser extends Parser { + def entryPoint = parser => implodePatDefs(parser.caseClause()) + } + + object PatternParser extends Parser { + def entryPoint = { parser => + val pat = parser.noSeq.pattern() + gen.patvarTransformer.transform(pat) + } + } + + object ForEnumeratorParser extends Parser { + def entryPoint = { parser => + val enums = parser.enumerator(isFirst = false, allowNestedIf = false) + assert(enums.length == 1) + implodePatDefs(enums.head) + } + } + + object FreshName extends FreshNameExtractor(nme.QUASIQUOTE_PREFIX) +} diff --git a/src/compiler/scala/reflect/quasiquotes/Placeholders.scala b/src/compiler/scala/reflect/quasiquotes/Placeholders.scala new file mode 100644 index 0000000000..a5b42f8a1f --- /dev/null +++ b/src/compiler/scala/reflect/quasiquotes/Placeholders.scala @@ -0,0 +1,201 @@ +package scala.reflect +package quasiquotes + +import java.util.UUID.randomUUID +import scala.collection.{immutable, mutable} + +/** Emulates hole support (see Holes.scala) in the quasiquote parser (see Parsers.scala). + * A principled solution to splicing into Scala syntax would be a parser that natively supports holes. + * Unfortunately, that's outside of our reach in Scala 2.11, so we have to emulate. + * This trait stores knowledge of how to represent the holes as something understandable by the parser + * and how to recover holes from the results of parsing the produced representation. + */ +trait Placeholders { self: Quasiquotes => + import global._ + import Rank._ + import universeTypes._ + + // Step 1: Transform Scala source with holes into vanilla Scala source + + lazy val posMap = mutable.LinkedHashMap[Position, (Int, Int)]() + lazy val code = { + val sb = new StringBuilder() + val sessionSuffix = randomUUID().toString.replace("-", "").substring(0, 8) + "$" + + def appendPart(value: String, pos: Position) = { + val start = sb.length + sb.append(value) + val end = sb.length + posMap += pos -> ((start, end)) + } + + def appendHole(tree: Tree, rank: Rank) = { + val placeholderName = c.freshName(TermName(nme.QUASIQUOTE_PREFIX + sessionSuffix)) + sb.append(placeholderName) + val holeTree = + if (method != nme.unapply) tree + else Bind(placeholderName, tree) + holeMap(placeholderName) = Hole(rank, holeTree) + } + + val iargs = method match { + case nme.apply => args + case nme.unapply => internal.subpatterns(args.head).get + case _ => global.abort("unreachable") + } + + foreach2(iargs, parts.init) { case (tree, (p, pos)) => + val (part, rank) = parseDots(p) + appendPart(part, pos) + appendHole(tree, rank) + } + val (p, pos) = parts.last + appendPart(p, pos) + + sb.toString + } + + object holeMap { + private val underlying = mutable.LinkedHashMap.empty[String, Hole] + private val accessed = mutable.Set.empty[String] + def unused: Set[Name] = (underlying.keys.toSet -- accessed).map(TermName(_)) + def contains(key: Name): Boolean = underlying.contains(key.toString) + def apply(key: Name): Hole = { + val skey = key.toString + val value = underlying(skey) + accessed += skey + value + } + def update(key: Name, hole: Hole) = + underlying += key.toString -> hole + def get(key: Name): Option[Hole] = { + val skey = key.toString + underlying.get(skey).map { v => + accessed += skey + v + } + } + def keysIterator: Iterator[TermName] = underlying.keysIterator.map(TermName(_)) + } + + // Step 2: Transform vanilla Scala AST into an AST with holes + + trait HolePlaceholder { + def matching: PartialFunction[Any, Name] + def unapply(scrutinee: Any): Option[Hole] = { + val name = matching.lift(scrutinee) + name.flatMap { holeMap.get(_) } + } + } + + object Placeholder extends HolePlaceholder { + def matching = { + case name: Name => name + case Ident(name) => name + case Bind(name, Ident(nme.WILDCARD)) => name + case TypeDef(_, name, List(), TypeBoundsTree(EmptyTree, EmptyTree)) => name + } + } + + object ModsPlaceholder extends HolePlaceholder { + def apply(name: Name) = + Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(name.toString)))) + def matching = { + case Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(s: String)))) => TermName(s) + } + } + + object AnnotPlaceholder extends HolePlaceholder { + def matching = { + case Apply(Select(New(Ident(name)), nme.CONSTRUCTOR), Nil) => name + } + } + + object ParamPlaceholder extends HolePlaceholder { + def apply(flags: FlagSet, name: Name) = + ValDef(Modifiers(flags), nme.QUASIQUOTE_PARAM, Ident(name), EmptyTree) + def matching = { + case ValDef(_, nme.QUASIQUOTE_PARAM, Ident(name), EmptyTree) => name + } + } + + object TuplePlaceholder { + def apply(args: List[Tree]) = + Apply(Ident(nme.QUASIQUOTE_TUPLE), args) + def unapply(tree: Tree): Option[List[Tree]] = tree match { + case Apply(Ident(nme.QUASIQUOTE_TUPLE), args) => Some(args) + case _ => None + } + } + + object TupleTypePlaceholder { + def apply(args: List[Tree]) = + AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) + def unapply(tree: Tree): Option[List[Tree]] = tree match { + case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) => Some(args) + case _ => None + } + } + + object FunctionTypePlaceholder { + def apply(args: List[Tree], res: Tree) = + AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), args :+ res) + def unapply(tree: Tree): Option[(List[Tree], Tree)] = tree match { + case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), args :+ res) => Some((args, res)) + case _ => None + } + } + + object SymbolPlaceholder { + def unapply(scrutinee: Any): Option[Hole] = scrutinee match { + case Placeholder(hole: ApplyHole) if hole.tpe <:< symbolType => Some(hole) + case _ => None + } + } + + object CasePlaceholder { + def apply(name: Name) = + CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), Ident(name) :: Nil), EmptyTree, EmptyTree) + def unapply(tree: Tree): Option[Hole] = tree match { + case CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Placeholder(hole))), EmptyTree, EmptyTree) => Some(hole) + case _ => None + } + } + + object RefineStatPlaceholder { + def apply(name: Name) = + ValDef(NoMods, nme.QUASIQUOTE_REFINE_STAT, Ident(name), EmptyTree) + def unapply(tree: Tree): Option[Hole] = tree match { + case ValDef(_, nme.QUASIQUOTE_REFINE_STAT, Ident(Placeholder(hole)), _) => Some(hole) + case _ => None + } + } + + object EarlyDefPlaceholder { + def apply(name: Name) = + ValDef(Modifiers(Flag.PRESUPER), nme.QUASIQUOTE_EARLY_DEF, Ident(name), EmptyTree) + def unapply(tree: Tree): Option[Hole] = tree match { + case ValDef(_, nme.QUASIQUOTE_EARLY_DEF, Ident(Placeholder(hole)), _) => Some(hole) + case _ => None + } + } + + object PackageStatPlaceholder { + def apply(name: Name) = + ValDef(NoMods, nme.QUASIQUOTE_PACKAGE_STAT, Ident(name), EmptyTree) + def unapply(tree: Tree): Option[Hole] = tree match { + case ValDef(NoMods, nme.QUASIQUOTE_PACKAGE_STAT, Ident(Placeholder(hole)), EmptyTree) => Some(hole) + case _ => None + } + } + + object ForEnumPlaceholder { + def apply(name: Name) = + build.SyntacticValFrom(Bind(name, Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) + def unapply(tree: Tree): Option[Hole] = tree match { + case build.SyntacticValFrom(Bind(Placeholder(hole), Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) => + Some(hole) + case _ => None + } + } +} diff --git a/src/compiler/scala/reflect/quasiquotes/Quasiquotes.scala b/src/compiler/scala/reflect/quasiquotes/Quasiquotes.scala new file mode 100644 index 0000000000..72e6000e9f --- /dev/null +++ b/src/compiler/scala/reflect/quasiquotes/Quasiquotes.scala @@ -0,0 +1,60 @@ +package scala.reflect +package quasiquotes + +import scala.reflect.macros.runtime.Context + +abstract class Quasiquotes extends Parsers + with Holes + with Placeholders + with Reifiers { + val c: Context + val global: c.universe.type = c.universe + import c.universe._ + + def debug(msg: => String): Unit = + if (settings.Yquasiquotedebug.value) println(msg) + + 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"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") + } + val reify0 = method0 match { + case nme.apply => new ApplyReifier().reifyFillingHoles(_) + case nme.unapply => new UnapplyReifier().reifyFillingHoles(_) + case other => global.abort(s"Unknown quasiquote api method: $other") + } + val parse0 = interpolator0 match { + case nme.q => TermParser.parse(_) + case nme.tq => TypeParser.parse(_) + case nme.cq => CaseParser.parse(_) + case nme.pq => PatternParser.parse(_) + case nme.fq => ForEnumeratorParser.parse(_) + case other => global.abort(s"Unknown quasiquote flavor: $other") + } + (universe0, args0, parts1, parse0, reify0, method0) + case _ => + global.abort(s"Couldn't parse call prefix tree ${c.macroApplication}.") + } + + lazy val u = universe // shortcut + lazy val universeTypes = new definitions.UniverseDependentTypes(universe) + + def expandQuasiquote = { + 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) + def sreified = + reified + .toString + .replace("scala.reflect.runtime.`package`.universe.internal.reificationSupport.", "") + .replace("scala.reflect.runtime.`package`.universe.", "") + .replace("scala.collection.immutable.", "") + debug(s"reified tree:\n$sreified\n") + reified + } +} diff --git a/src/compiler/scala/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/reflect/quasiquotes/Reifiers.scala new file mode 100644 index 0000000000..07becdc3c6 --- /dev/null +++ b/src/compiler/scala/reflect/quasiquotes/Reifiers.scala @@ -0,0 +1,487 @@ +package scala.reflect +package quasiquotes + +import java.lang.UnsupportedOperationException +import scala.reflect.reify.{Reifier => ReflectReifier} +import scala.reflect.internal.Flags._ + +trait Reifiers { self: Quasiquotes => + import global._ + import global.build._ + import global.treeInfo._ + import global.definitions._ + import Rank._ + import universeTypes._ + + abstract class Reifier(val isReifyingExpressions: Boolean) extends { + val global: self.global.type = self.global + val universe = self.universe + val reifee = EmptyTree + val mirror = EmptyTree + val concrete = false + } with ReflectReifier { + lazy val typer = throw new UnsupportedOperationException + + def isReifyingPatterns: Boolean = !isReifyingExpressions + def action = if (isReifyingExpressions) "unquote" else "extract" + def holesHaveTypes = isReifyingExpressions + + /** Map that stores freshly generated names linked to the corresponding names in the reified tree. + * This information is used to reify names created by calls to freshTermName and freshTypeName. + */ + val nameMap = collection.mutable.HashMap.empty[Name, Set[TermName]].withDefault { _ => Set() } + + /** Wraps expressions into: + * a block which starts with a sequence of vals that correspond + * to fresh names that has to be created at evaluation of the quasiquote + * and ends with reified tree: + * + * { + * val name$1: universe.TermName = universe.build.freshTermName(prefix1) + * ... + * val name$N: universe.TermName = universe.build.freshTermName(prefixN) + * tree + * } + * + * Wraps patterns into: + * a call into anonymous class' unapply method required by unapply macro expansion: + * + * new { + * def unapply(tree) = tree match { + * case pattern if guard => Some(result) + * case _ => None + * } + * }.unapply() + * + * where pattern corresponds to reified tree and guard represents conjunction of equalities + * which check that pairs of names in nameMap.values are equal between each other. + */ + def wrap(tree: Tree) = + if (isReifyingExpressions) { + val freshdefs = nameMap.iterator.map { + case (origname, names) => + assert(names.size == 1) + val FreshName(prefix) = origname + val nameTypeName = if (origname.isTermName) tpnme.TermName else tpnme.TypeName + val freshName = if (origname.isTermName) nme.freshTermName else nme.freshTypeName + // q"val ${names.head}: $u.$nameTypeName = $u.internal.reificationSupport.$freshName($prefix)" + ValDef(NoMods, names.head, Select(u, nameTypeName), + Apply(Select(Select(Select(u, nme.internal), nme.reificationSupport), freshName), Literal(Constant(prefix)) :: Nil)) + }.toList + // q"..$freshdefs; $tree" + SyntacticBlock(freshdefs :+ tree) + } else { + val freevars = holeMap.keysIterator.map(Ident(_)).toList + 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)) + } + val guard = + nameMap.collect { case (_, nameset) if nameset.size >= 2 => + nameset.toList.sliding(2).map { case List(n1, n2) => + // q"$n1 == $n2" + Apply(Select(Ident(n1), nme.EQ), List(Ident(n2))) + } + }.flatten.reduceOption[Tree] { (l, r) => + // q"$l && $r" + Apply(Select(l, nme.ZAND), List(r)) + }.getOrElse { EmptyTree } + // 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) = { ..${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(), + SyntacticBlock(unlifters.preamble() :+ 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).pos, s"Don't know how to $action here") + } + wrap(reified) + } + + override def reifyTree(tree: Tree): Tree = + reifyTreePlaceholder(tree) orElse + reifyTreeSyntactically(tree) + + def reifyTreePlaceholder(tree: Tree): Tree = tree match { + case Placeholder(hole: ApplyHole) if hole.tpe <:< treeType => hole.tree + case Placeholder(Hole(tree, NoDot)) if isReifyingPatterns => tree + case Placeholder(hole @ Hole(_, rank @ Dot())) => c.abort(hole.pos, s"Can't $action with $rank here") + case TuplePlaceholder(args) => reifyTuple(args) + // Due to greediness of syntactic applied we need to pre-emptively peek inside. + // `rest` will always be non-empty due to the rule on top of this one. + case SyntacticApplied(id @ Ident(nme.QUASIQUOTE_TUPLE), first :: rest) => + mirrorBuildCall(nme.SyntacticApplied, reifyTreePlaceholder(Apply(id, first)), reify(rest)) + case TupleTypePlaceholder(args) => reifyTupleType(args) + case FunctionTypePlaceholder(argtpes, restpe) => reifyFunctionType(argtpes, restpe) + case CasePlaceholder(hole) => hole.tree + case RefineStatPlaceholder(hole) => reifyRefineStat(hole) + case EarlyDefPlaceholder(hole) => reifyEarlyDef(hole) + case PackageStatPlaceholder(hole) => reifyPackageStat(hole) + case ParamPlaceholder(hole) => hole.tree + // for enumerators are checked not during splicing but during + // desugaring of the for loop in SyntacticFor & SyntacticForYield + case ForEnumPlaceholder(hole) => hole.tree + case _ => EmptyTree + } + + override def reifyTreeSyntactically(tree: Tree) = tree match { + case RefTree(qual, SymbolPlaceholder(Hole(tree, _))) if isReifyingExpressions => + mirrorBuildCall(nme.mkRefTree, reify(qual), tree) + case This(SymbolPlaceholder(Hole(tree, _))) if isReifyingExpressions => + mirrorCall(nme.This, tree) + case SyntacticTraitDef(mods, name, tparams, earlyDefs, parents, selfdef, body) => + reifyBuildCall(nme.SyntacticTraitDef, mods, name, tparams, earlyDefs, parents, selfdef, body) + case SyntacticClassDef(mods, name, tparams, constrmods, vparamss, + earlyDefs, parents, selfdef, body) => + mirrorBuildCall(nme.SyntacticClassDef, reify(mods), reify(name), reify(tparams), reify(constrmods), + reifyVparamss(vparamss), reify(earlyDefs), reify(parents), + reify(selfdef), reify(body)) + case SyntacticPackageObjectDef(name, earlyDefs, parents, selfdef, body) => + reifyBuildCall(nme.SyntacticPackageObjectDef, name, earlyDefs, parents, selfdef, body) + case SyntacticObjectDef(mods, name, earlyDefs, parents, selfdef, body) => + reifyBuildCall(nme.SyntacticObjectDef, mods, name, earlyDefs, parents, selfdef, body) + case SyntacticNew(earlyDefs, parents, selfdef, body) => + reifyBuildCall(nme.SyntacticNew, earlyDefs, parents, selfdef, body) + case SyntacticDefDef(mods, name, tparams, vparamss, tpt, rhs) => + mirrorBuildCall(nme.SyntacticDefDef, reify(mods), reify(name), reify(tparams), + reifyVparamss(vparamss), reify(tpt), reify(rhs)) + case SyntacticValDef(mods, name, tpt, rhs) if tree != noSelfType => + reifyBuildCall(nme.SyntacticValDef, mods, name, tpt, rhs) + case SyntacticVarDef(mods, name, tpt, rhs) => + reifyBuildCall(nme.SyntacticVarDef, mods, name, tpt, rhs) + case SyntacticValFrom(pat, rhs) => + reifyBuildCall(nme.SyntacticValFrom, pat, rhs) + case SyntacticValEq(pat, rhs) => + reifyBuildCall(nme.SyntacticValEq, pat, rhs) + case SyntacticFilter(cond) => + reifyBuildCall(nme.SyntacticFilter, cond) + case SyntacticFor(enums, body) => + reifyBuildCall(nme.SyntacticFor, enums, body) + case SyntacticForYield(enums, body) => + reifyBuildCall(nme.SyntacticForYield, enums, body) + case SyntacticAssign(lhs, rhs) => + reifyBuildCall(nme.SyntacticAssign, lhs, rhs) + case SyntacticApplied(fun, argss) if argss.nonEmpty => + reifyBuildCall(nme.SyntacticApplied, fun, argss) + case SyntacticTypeApplied(fun, targs) if targs.nonEmpty => + reifyBuildCall(nme.SyntacticTypeApplied, fun, targs) + case SyntacticAppliedType(tpt, targs) if targs.nonEmpty => + reifyBuildCall(nme.SyntacticAppliedType, tpt, targs) + case SyntacticFunction(args, body) => + reifyBuildCall(nme.SyntacticFunction, args, body) + case SyntacticEmptyTypeTree() => + reifyBuildCall(nme.SyntacticEmptyTypeTree) + case SyntacticImport(expr, selectors) => + reifyBuildCall(nme.SyntacticImport, expr, selectors) + case SyntacticPartialFunction(cases) => + reifyBuildCall(nme.SyntacticPartialFunction, cases) + case SyntacticMatch(scrutinee, cases) => + reifyBuildCall(nme.SyntacticMatch, scrutinee, cases) + case SyntacticTermIdent(name, isBackquoted) => + reifyBuildCall(nme.SyntacticTermIdent, name, isBackquoted) + case SyntacticTypeIdent(name) => + reifyBuildCall(nme.SyntacticTypeIdent, name) + case SyntacticCompoundType(parents, defns) => + reifyBuildCall(nme.SyntacticCompoundType, parents, defns) + case SyntacticSingletonType(ref) => + reifyBuildCall(nme.SyntacticSingletonType, ref) + case SyntacticTypeProjection(qual, name) => + reifyBuildCall(nme.SyntacticTypeProjection, qual, name) + case SyntacticAnnotatedType(tpt, annot) => + reifyBuildCall(nme.SyntacticAnnotatedType, tpt, annot) + case SyntacticExistentialType(tpt, where) => + reifyBuildCall(nme.SyntacticExistentialType, tpt, where) + case Q(tree) if fillListHole.isDefinedAt(tree) => + mirrorBuildCall(nme.SyntacticBlock, fillListHole(tree)) + case Q(other) => + reifyTree(other) + // Syntactic block always matches so we have to be careful + // 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 CaseDef(pat, guard, body) if fillListHole.isDefinedAt(body) => + mirrorCall(nme.CaseDef, reify(pat), reify(guard), mirrorBuildCall(nme.SyntacticBlock, fillListHole(body))) + // parser emits trees with scala package symbol to ensure + // that some names hygienically point to various scala package + // members; we need to preserve this symbol to preserve + // correctness of the trees produced by quasiquotes + case Select(id @ Ident(nme.scala_), name) if id.symbol == ScalaPackage => + reifyBuildCall(nme.ScalaDot, name) + case Select(qual, name) => + val ctor = if (name.isTypeName) nme.SyntacticSelectType else nme.SyntacticSelectTerm + reifyBuildCall(ctor, qual, name) + case _ => + super.reifyTreeSyntactically(tree) + } + + override def reifyName(name: Name): Tree = name match { + 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.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} + def result(n: Name) = if (isReifyingExpressions) Ident(n) else Bind(n, Ident(nme.WILDCARD)) + if (isReifyingPatterns) result(introduceName()) + else result(nameMap.get(name).map { _.head }.getOrElse { introduceName() }) + case _ => + super.reifyName(name) + } + + def reifyTuple(args: List[Tree]) = args match { + case Nil => reify(Literal(Constant(()))) + case List(hole @ Placeholder(Hole(_, NoDot))) => reify(hole) + case List(Placeholder(_)) => reifyBuildCall(nme.SyntacticTuple, args) + // in a case we only have one element tuple without + // any rank annotations this means that this is + // just an expression wrapped in parentheses + case List(other) => reify(other) + case _ => reifyBuildCall(nme.SyntacticTuple, args) + } + + def reifyTupleType(args: List[Tree]) = args match { + case Nil => reify(Select(Ident(nme.scala_), tpnme.Unit)) + case List(hole @ Placeholder(Hole(_, NoDot))) => reify(hole) + case List(Placeholder(_)) => reifyBuildCall(nme.SyntacticTupleType, args) + case List(other) => reify(other) + case _ => reifyBuildCall(nme.SyntacticTupleType, args) + } + + def reifyFunctionType(argtpes: List[Tree], restpe: Tree) = + reifyBuildCall(nme.SyntacticFunctionType, argtpes, restpe) + + def reifyConstructionCheck(name: TermName, hole: Hole) = hole match { + case _: UnapplyHole => hole.tree + case _: ApplyHole => mirrorBuildCall(name, hole.tree) + } + + def reifyRefineStat(hole: Hole) = reifyConstructionCheck(nme.mkRefineStat, hole) + + def reifyEarlyDef(hole: Hole) = reifyConstructionCheck(nme.mkEarlyDef, hole) + + def reifyAnnotation(hole: Hole) = reifyConstructionCheck(nme.mkAnnotation, hole) + + def reifyPackageStat(hole: Hole) = reifyConstructionCheck(nme.mkPackageStat, hole) + + def reifyVparamss(vparamss: List[List[ValDef]]) = { + val build.ImplicitParams(paramss, implparams) = vparamss + if (implparams.isEmpty) reify(paramss) + else reifyBuildCall(nme.ImplicitParams, paramss, implparams) + } + + /** Splits list into a list of groups where subsequent elements are considered + * similar by the corresponding function. + * + * Example: + * + * > group(List(1, 1, 0, 0, 1, 0)) { _ == _ } + * List(List(1, 1), List(0, 0), List(1), List(0)) + * + */ + def group[T](lst: List[T])(similar: (T, T) => Boolean) = lst.foldLeft[List[List[T]]](List()) { + case (Nil, el) => List(List(el)) + case (ll :+ (last @ (lastinit :+ lastel)), el) if similar(lastel, el) => ll :+ (last :+ el) + case (ll, el) => ll :+ List(el) + } + + /** Reifies list filling all the valid holeMap. + * + * Reification of non-trivial list is done in two steps: + * + * 1. split the list into groups where every placeholder is always + * put in a group of it's own and all subsquent non-holeMap are + * grouped together; element is considered to be a placeholder if it's + * in the domain of the fill function; + * + * 2. fold the groups into a sequence of lists added together with ++ using + * fill reification for holeMapĀ and fallback reification for non-holeMap. + * + * Example: + * + * reifyHighRankList(lst) { + * // first we define patterns that extract high-rank holeMap (currently ..) + * case Placeholder(IterableType(_, _)) => tree + * } { + * // in the end we define how single elements are reified, typically with default reify call + * reify(_) + * } + * + * Sample execution of previous concrete list reifier: + * + * > val lst = List(foo, bar, qq$f3948f9s$1) + * > reifyHighRankList(lst) { ... } { ... } + * q"List($foo, $bar) ++ ${holeMap(qq$f3948f9s$1).tree}" + */ + def reifyHighRankList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree + + val fillListHole: PartialFunction[Any, Tree] = { + case Placeholder(Hole(tree, DotDot)) => tree + case CasePlaceholder(Hole(tree, DotDot)) => tree + case RefineStatPlaceholder(h @ Hole(_, DotDot)) => reifyRefineStat(h) + case EarlyDefPlaceholder(h @ Hole(_, DotDot)) => reifyEarlyDef(h) + case PackageStatPlaceholder(h @ Hole(_, DotDot)) => reifyPackageStat(h) + case ForEnumPlaceholder(Hole(tree, DotDot)) => tree + case ParamPlaceholder(Hole(tree, DotDot)) => tree + case SyntacticPatDef(mods, pat, tpt, rhs) => + reifyBuildCall(nme.SyntacticPatDef, mods, pat, tpt, rhs) + case SyntacticValDef(mods, p @ Placeholder(h: ApplyHole), tpt, rhs) if h.tpe <:< treeType => + mirrorBuildCall(nme.SyntacticPatDef, reify(mods), h.tree, reify(tpt), reify(rhs)) + } + + val fillListOfListsHole: PartialFunction[Any, Tree] = { + case List(ParamPlaceholder(Hole(tree, DotDotDot))) => tree + case List(Placeholder(Hole(tree, DotDotDot))) => tree + } + + /** Reifies arbitrary list filling ..$x and ...$y holeMap when they are put + * in the correct position. Fallbacks to regular reification for zero rank + * elements. + */ + override def reifyList(xs: List[Any]): Tree = reifyHighRankList(xs)(fillListHole.orElse(fillListOfListsHole))(reify) + + def reifyAnnotList(annots: List[Tree]): Tree = reifyHighRankList(annots) { + case AnnotPlaceholder(h @ Hole(_, DotDot)) => reifyAnnotation(h) + } { + case AnnotPlaceholder(h: ApplyHole) if h.tpe <:< treeType => reifyAnnotation(h) + case AnnotPlaceholder(h: UnapplyHole) if h.rank == NoDot => reifyAnnotation(h) + case other => reify(other) + } + + // These are explicit flags except those that are used + // to overload the same tree for two different concepts: + // - MUTABLE that is used to override ValDef for vars + // - TRAIT that is used to override ClassDef for traits + val nonOverloadedExplicitFlags = ExplicitFlags & ~MUTABLE & ~TRAIT + + def ensureNoExplicitFlags(m: Modifiers, pos: Position) = { + // Traits automatically have ABSTRACT flag assigned to + // them so in that case it's not an explicit flag + val flags = if (m.isTrait) m.flags & ~ABSTRACT else m.flags + if ((flags & nonOverloadedExplicitFlags) != 0L) + c.abort(pos, s"Can't $action modifiers together with flags, consider merging flags into modifiers") + } + + override def mirrorSelect(name: String): Tree = + Select(universe, TermName(name)) + + override def mirrorCall(name: TermName, args: Tree*): Tree = + Apply(Select(universe, name), args.toList) + + override def mirrorBuildCall(name: TermName, args: Tree*): Tree = + Apply(Select(Select(Select(universe, nme.internal), nme.reificationSupport), name), args.toList) + + override def scalaFactoryCall(name: String, args: Tree*): Tree = + call("scala." + name, args: _*) + } + + class ApplyReifier extends Reifier(isReifyingExpressions = true) { + def reifyHighRankList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree = + if (xs.isEmpty) mkList(Nil) + else { + def reifyGroup(group: List[Any]): Tree = group match { + case List(elem) if fill.isDefinedAt(elem) => fill(elem) + case elems => mkList(elems.map(fallback)) + } + val head :: tail = group(xs) { (a, b) => !fill.isDefinedAt(a) && !fill.isDefinedAt(b) } + tail.foldLeft[Tree](reifyGroup(head)) { (tree, lst) => Apply(Select(tree, nme.PLUSPLUS), List(reifyGroup(lst))) } + } + + override def reifyModifiers(m: Modifiers) = + if (m == NoMods) super.reifyModifiers(m) + else { + val (modsPlaceholders, annots) = m.annotations.partition { + case ModsPlaceholder(_) => true + case _ => false + } + val (mods, flags) = modsPlaceholders.map { + case ModsPlaceholder(hole: ApplyHole) => hole + }.partition { hole => + if (hole.tpe <:< modsType) true + else if (hole.tpe <:< flagsType) false + else c.abort(hole.pos, s"$flagsType or $modsType expected but ${hole.tpe} found") + } + mods match { + case hole :: Nil => + if (flags.nonEmpty) c.abort(flags(0).pos, "Can't unquote flags together with modifiers, consider merging flags into modifiers") + if (annots.nonEmpty) c.abort(hole.pos, "Can't unquote modifiers together with annotations, consider merging annotations into modifiers") + ensureNoExplicitFlags(m, hole.pos) + hole.tree + case _ :: hole :: Nil => + c.abort(hole.pos, "Can't unquote multiple modifiers, consider merging them into a single modifiers instance") + case _ => + val baseFlags = reifyFlags(m.flags) + val reifiedFlags = flags.foldLeft[Tree](baseFlags) { case (flag, hole) => Apply(Select(flag, nme.OR), List(hole.tree)) } + mirrorFactoryCall(nme.Modifiers, reifiedFlags, reify(m.privateWithin), reifyAnnotList(annots)) + } + } + + } + class UnapplyReifier extends Reifier(isReifyingExpressions = false) { + 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 reifyHighRankList(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) = + if (m == NoMods) super.reifyModifiers(m) + else { + val mods = m.annotations.collect { case ModsPlaceholder(hole: UnapplyHole) => hole } + mods match { + 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.treeNoUnlift + case _ :: hole :: _ => + c.abort(hole.pos, "Can't extract multiple modifiers together, consider extracting a single modifiers instance") + case Nil => + mirrorFactoryCall(nme.Modifiers, reifyFlags(m.flags), reify(m.privateWithin), reifyAnnotList(m.annotations)) + } + } + } +} diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 64cf3d0847..8fed53c89f 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -5,7 +5,7 @@ import scala.reflect.reify.Taggers import scala.tools.nsc.typechecker.{ Analyzer, Macros } import scala.reflect.runtime.Macros.currentMirror import scala.reflect.api.Universe -import scala.tools.reflect.quasiquotes.{ Quasiquotes => QuasiquoteImpls } +import scala.reflect.quasiquotes.{ Quasiquotes => QuasiquoteImpls } /** Optimizes system macro expansions by hardwiring them directly to their implementations * bypassing standard reflective load and invoke to avoid the overhead of Java/Scala reflection. diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala b/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala deleted file mode 100644 index 68cc728eb3..0000000000 --- a/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala +++ /dev/null @@ -1,245 +0,0 @@ -package scala.tools.reflect -package quasiquotes - -import scala.collection.{immutable, mutable} -import scala.reflect.internal.Flags._ -import scala.reflect.macros.TypecheckException - -class Rank private[Rank](val value: Int) extends AnyVal { - def pred = { assert(value - 1 >= 0); new Rank(value - 1) } - def succ = new Rank(value + 1) - override def toString = if (value == 0) "no dots" else "." * (value + 1) -} - -object Rank { - val NoDot = new Rank(0) - val DotDot = new Rank(1) - val DotDotDot = new Rank(2) - object Dot { def unapply(rank: Rank) = rank != NoDot } - def parseDots(part: String) = { - if (part.endsWith("...")) (part.stripSuffix("..."), DotDotDot) - else if (part.endsWith("..")) (part.stripSuffix(".."), DotDot) - else (part, NoDot) - } -} - -/** Defines abstractions that provide support for splicing into Scala syntax. - */ -trait Holes { self: Quasiquotes => - import global._ - import Rank._ - import definitions._ - import universeTypes._ - - private lazy val IterableTParam = IterableClass.typeParams(0).asType.toType - private def inferParamImplicit(tfun: Type, targ: Type) = c.inferImplicitValue(appliedType(tfun, List(targ)), silent = true) - private def inferLiftable(tpe: Type): Tree = inferParamImplicit(liftableType, tpe) - private def inferUnliftable(tpe: Type): Tree = inferParamImplicit(unliftableType, tpe) - private def isLiftableType(tpe: Type) = inferLiftable(tpe) != EmptyTree - private def isNativeType(tpe: Type) = - (tpe <:< treeType) || (tpe <:< nameType) || (tpe <:< modsType) || - (tpe <:< flagsType) || (tpe <:< symbolType) - private def isBottomType(tpe: Type) = - tpe <:< NothingClass.tpe || tpe <:< NullClass.tpe - private def extractIterableTParam(tpe: Type) = - IterableTParam.asSeenFrom(tpe, IterableClass) - private def stripIterable(tpe: Type, limit: Rank = DotDotDot): (Rank, Type) = - if (limit == NoDot) (NoDot, tpe) - else if (tpe != null && !isIterableType(tpe)) (NoDot, tpe) - else if (isBottomType(tpe)) (NoDot, tpe) - else { - val targ = extractIterableTParam(tpe) - val (rank, innerTpe) = stripIterable(targ, limit.pred) - (rank.succ, innerTpe) - } - private def iterableTypeFromRank(n: Rank, tpe: Type): Type = { - if (n == NoDot) tpe - else appliedType(IterableClass.toType, List(iterableTypeFromRank(n.pred, tpe))) - } - - /** Hole encapsulates information about unquotees in quasiquotes. - * It packs together a rank, pre-reified tree representation - * (possibly preprocessed) and position. - */ - abstract class Hole { - val tree: Tree - val pos: Position - val rank: Rank - } - - object Hole { - def apply(rank: Rank, tree: Tree): Hole = - if (method != nme.unapply) new ApplyHole(rank, tree) - else new UnapplyHole(rank, tree) - def unapply(hole: Hole): Some[(Tree, Rank)] = Some((hole.tree, hole.rank)) - } - - class ApplyHole(annotatedRank: Rank, unquotee: Tree) extends Hole { - val (strippedTpe, tpe): (Type, Type) = { - val (strippedRank, strippedTpe) = stripIterable(unquotee.tpe, limit = annotatedRank) - if (isBottomType(strippedTpe)) cantSplice() - else if (isNativeType(strippedTpe)) { - if (strippedRank != NoDot && !(strippedTpe <:< treeType) && !isLiftableType(strippedTpe)) cantSplice() - else (strippedTpe, iterableTypeFromRank(annotatedRank, strippedTpe)) - } else if (isLiftableType(strippedTpe)) (strippedTpe, iterableTypeFromRank(annotatedRank, treeType)) - else cantSplice() - } - - val tree = { - def inner(itpe: Type)(tree: Tree) = - if (isNativeType(itpe)) tree - else if (isLiftableType(itpe)) lifted(itpe)(tree) - else global.abort("unreachable") - if (annotatedRank == NoDot) inner(strippedTpe)(unquotee) - else iterated(annotatedRank, unquotee, unquotee.tpe) - } - - val pos = unquotee.pos - - val rank = stripIterable(tpe)._1 - - private def cantSplice(): Nothing = { - val (iterableRank, iterableType) = stripIterable(unquotee.tpe) - val holeRankMsg = if (annotatedRank != NoDot) s" with $annotatedRank" else "" - val action = "unquote " + unquotee.tpe + holeRankMsg - val suggestRank = annotatedRank != iterableRank || annotatedRank != NoDot - val unquoteeRankMsg = if (annotatedRank != iterableRank && iterableRank != NoDot) s"using $iterableRank" else "omitting the dots" - val rankSuggestion = if (suggestRank) unquoteeRankMsg else "" - val suggestLifting = (annotatedRank == NoDot || iterableRank != NoDot) && !(iterableType <:< treeType) && !isLiftableType(iterableType) - val liftedTpe = if (annotatedRank != NoDot) iterableType else unquotee.tpe - val liftSuggestion = if (suggestLifting) s"providing an implicit instance of Liftable[$liftedTpe]" else "" - val advice = - if (isBottomType(iterableType)) "bottom type values often indicate programmer mistake" - else "consider " + List(rankSuggestion, liftSuggestion).filter(_ != "").mkString(" or ") - c.abort(unquotee.pos, s"Can't $action, $advice") - } - - private def lifted(tpe: Type)(tree: Tree): Tree = { - val lifter = inferLiftable(tpe) - assert(lifter != EmptyTree, s"couldnt find a liftable for $tpe") - val lifted = Apply(lifter, List(tree)) - atPos(tree.pos)(lifted) - } - - private def toStats(tree: Tree): Tree = - // q"$u.internal.reificationSupport.toStats($tree)" - Apply(Select(Select(Select(u, nme.internal), nme.reificationSupport), nme.toStats), tree :: Nil) - - private def toList(tree: Tree, tpe: Type): Tree = - if (isListType(tpe)) tree - else Select(tree, nme.toList) - - private def mapF(tree: Tree, f: Tree => Tree): Tree = - if (f(Ident(TermName("x"))) equalsStructure Ident(TermName("x"))) tree - else { - val x: TermName = c.freshName() - // q"$tree.map { $x => ${f(Ident(x))} }" - Apply(Select(tree, nme.map), - Function(ValDef(Modifiers(PARAM), x, TypeTree(), EmptyTree) :: Nil, - f(Ident(x))) :: Nil) - } - - private object IterableType { - def unapply(tpe: Type): Option[Type] = - if (isIterableType(tpe)) Some(extractIterableTParam(tpe)) else None - } - - private object LiftedType { - def unapply(tpe: Type): Option[Tree => Tree] = - if (tpe <:< treeType) Some(t => t) - else if (isLiftableType(tpe)) Some(lifted(tpe)(_)) - else None - } - - /** Map high-rank unquotee onto an expression that eveluates as a list of given rank. - * - * All possible combinations of representations are given in the table below: - * - * input output for T <: Tree output for T: Liftable - * - * ..${x: Iterable[T]} x.toList x.toList.map(lift) - * ..${x: T} toStats(x) toStats(lift(x)) - * - * ...${x: Iterable[Iterable[T]]} x.toList { _.toList } x.toList.map { _.toList.map(lift) } - * ...${x: Iterable[T]} x.toList.map { toStats(_) } x.toList.map { toStats(lift(_)) } - * ...${x: T} toStats(x).map { toStats(_) } toStats(lift(x)).map { toStats(_) } - * - * For optimization purposes `x.toList` is represented as just `x` if it is statically known that - * x is not just an Iterable[T] but a List[T]. Similarly no mapping is performed if mapping function is - * known to be an identity. - */ - private def iterated(rank: Rank, tree: Tree, tpe: Type): Tree = (rank, tpe) match { - case (DotDot, tpe @ IterableType(LiftedType(lift))) => mapF(toList(tree, tpe), lift) - case (DotDot, LiftedType(lift)) => toStats(lift(tree)) - case (DotDotDot, tpe @ IterableType(inner)) => mapF(toList(tree, tpe), t => iterated(DotDot, t, inner)) - case (DotDotDot, LiftedType(lift)) => mapF(toStats(lift(tree)), toStats) - case _ => global.abort("unreachable") - } - } - - class UnapplyHole(val rank: Rank, pat: Tree) extends Hole { - 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 (iterableRank, _) = stripIterable(tpe) - if (iterableRank.value < rank.value) - c.abort(pat.pos, s"Can't extract $tpe with $rank, consider using $iterableRank") - val (_, strippedTpe) = stripIterable(tpe, limit = rank) - if (strippedTpe <:< treeType) treeNoUnlift - else - unlifters.spawn(strippedTpe, rank).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 rank and unlifting of the values. - * In particular extraction of List[Tree] as List[T: Unliftable] requires - * helper extractors that would do the job: UnliftListElementwise[T]. Similarly - * List[List[Tree]] needs UnliftListOfListsElementwise[T]. - * - * See also "unlift list" tests in UnapplyProps.scala - */ - object unlifters { - private var records = List.empty[(Type, Rank)] - // Materialize unlift helper that does elementwise - // unlifting for corresponding rank and type. - def spawn(tpe: Type, rank: Rank): Option[Tree] = { - val unlifter = inferUnliftable(tpe) - if (unlifter == EmptyTree) None - else if (rank == NoDot) Some(unlifter) - else { - val idx = records.indexWhere { p => p._1 =:= tpe && p._2 == rank } - val resIdx = if (idx != -1) idx else { records +:= (tpe, rank); 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, rank), idx) => - val name = TermName(nme.QUASIQUOTE_UNLIFT_HELPER + idx) - val helperName = rank match { - case DotDot => nme.UnliftListElementwise - case DotDotDot => nme.UnliftListOfListsElementwise - } - val lifter = inferUnliftable(tpe) - assert(helperName.isTermName) - // q"val $name: $u.internal.reificationSupport.${helperName.toTypeName} = $u.internal.reificationSupport.$helperName($lifter)" - ValDef(NoMods, name, - AppliedTypeTree(Select(Select(Select(u, nme.internal), nme.reificationSupport), helperName.toTypeName), List(TypeTree(tpe))), - Apply(Select(Select(Select(u, nme.internal), nme.reificationSupport), helperName), lifter :: Nil)) - } - } -} diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala deleted file mode 100644 index 392b7fc881..0000000000 --- a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala +++ /dev/null @@ -1,228 +0,0 @@ -package scala.tools.reflect -package quasiquotes - -import scala.tools.nsc.ast.parser.{Parsers => ScalaParser} -import scala.tools.nsc.ast.parser.Tokens._ -import scala.compat.Platform.EOL -import scala.reflect.internal.util.{BatchSourceFile, SourceFile, FreshNameCreator} -import scala.collection.mutable.ListBuffer -import scala.util.Try - -/** Builds upon the vanilla Scala parser and teams up together with Placeholders.scala to emulate holes. - * A principled solution to splicing into Scala syntax would be a parser that natively supports holes. - * Unfortunately, that's outside of our reach in Scala 2.11, so we have to emulate. - */ -trait Parsers { self: Quasiquotes => - import global.{Try => _, _} - import build.implodePatDefs - - abstract class Parser extends { - val global: self.global.type = self.global - } with ScalaParser { - def parse(code: String): Tree = { - try { - val file = new BatchSourceFile(nme.QUASIQUOTE_FILE, code) - val parser = new QuasiquoteParser(file) - parser.checkNoEscapingPlaceholders { parser.parseRule(entryPoint) } - } catch { - case mi: MalformedInput => c.abort(correspondingPosition(mi.offset), mi.msg) - } - } - - def correspondingPosition(offset: Int): Position = { - val posMapList = posMap.toList - def containsOffset(start: Int, end: Int) = start <= offset && offset < end - def fallbackPosition = posMapList match { - case (pos1, (start1, end1)) :: _ if start1 > offset => pos1 - case _ :+ ((pos2, (start2, end2))) if end2 <= offset => pos2.withPoint(pos2.point + (end2 - start2)) - } - posMapList.sliding(2).collect { - case (pos1, (start1, end1)) :: _ if containsOffset(start1, end1) => (pos1, offset - start1) - case (pos1, (start1, end1)) :: (pos2, (start2, _)) :: _ if containsOffset(end1, start2) => (pos1, end1 - start1) - case _ :: (pos2, (start2, end2)) :: _ if containsOffset(start2, end2) => (pos2, offset - start2) - }.map { case (pos, offset) => - pos.withPoint(pos.point + offset) - }.toList.headOption.getOrElse(fallbackPosition) - } - - override def token2string(token: Int): String = token match { - case EOF => "end of quote" - case _ => super.token2string(token) - } - - def entryPoint: QuasiquoteParser => Tree - - class QuasiquoteParser(source0: SourceFile) extends SourceFileParser(source0) { parser => - def isHole: Boolean = isIdent && isHole(in.name) - - def isHole(name: Name): Boolean = holeMap.contains(name) - - override implicit lazy val fresh: FreshNameCreator = new FreshNameCreator(nme.QUASIQUOTE_PREFIX) - - override val treeBuilder = new ParserTreeBuilder { - override implicit def fresh: FreshNameCreator = parser.fresh - - // q"(..$xs)" - override def makeTupleTerm(trees: List[Tree]): Tree = TuplePlaceholder(trees) - - // tq"(..$xs)" - override def makeTupleType(trees: List[Tree]): Tree = TupleTypePlaceholder(trees) - - // q"{ $x }" - override def makeBlock(stats: List[Tree]): Tree = method match { - case nme.apply => - stats match { - // we don't want to eagerly flatten trees with placeholders as they - // might have to be wrapped into a block depending on their value - case (head @ Ident(name)) :: Nil if isHole(name) => Block(Nil, head) - case _ => gen.mkBlock(stats, doFlatten = true) - } - case nme.unapply => gen.mkBlock(stats, doFlatten = false) - case other => global.abort("unreachable") - } - - // tq"$a => $b" - override def makeFunctionTypeTree(argtpes: List[Tree], restpe: Tree): Tree = FunctionTypePlaceholder(argtpes, restpe) - - // make q"val (x: T) = rhs" be equivalent to q"val x: T = rhs" for sake of bug compatibility (SI-8211) - override def makePatDef(mods: Modifiers, pat: Tree, rhs: Tree) = pat match { - case TuplePlaceholder(inParensPat :: Nil) => super.makePatDef(mods, inParensPat, rhs) - case _ => super.makePatDef(mods, pat, rhs) - } - } - import treeBuilder.{global => _, unit => _, _} - - // q"def foo($x)" - override def param(owner: Name, implicitmod: Int, caseParam: Boolean): ValDef = - if (isHole && lookingAhead { in.token == COMMA || in.token == RPAREN }) { - ParamPlaceholder(implicitmod, ident()) - } else super.param(owner, implicitmod, caseParam) - - // q"($x) => ..." && q"class X { selfie => } - override def convertToParam(tree: Tree): ValDef = tree match { - case Ident(name) if isHole(name) => ParamPlaceholder(NoFlags, name) - case _ => super.convertToParam(tree) - } - - // q"foo match { case $x }" - override def caseClause(): CaseDef = - if (isHole && lookingAhead { in.token == CASE || in.token == RBRACE || in.token == SEMI }) { - val c = CasePlaceholder(ident()) - while (in.token == SEMI) in.nextToken() - c - } else - super.caseClause() - - override def caseBlock(): Tree = super.caseBlock() match { - case Block(Nil, expr) => expr - case other => other - } - - override def isAnnotation: Boolean = super.isAnnotation || (isHole && lookingAhead { isAnnotation }) - - override def isModifier: Boolean = super.isModifier || (isHole && lookingAhead { isModifier }) - - override def isLocalModifier: Boolean = super.isLocalModifier || (isHole && lookingAhead { isLocalModifier }) - - override def isTemplateIntro: Boolean = super.isTemplateIntro || (isHole && lookingAhead { isTemplateIntro }) - - override def isDefIntro: Boolean = super.isDefIntro || (isHole && lookingAhead { isDefIntro }) - - override def isDclIntro: Boolean = super.isDclIntro || (isHole && lookingAhead { isDclIntro }) - - override def isStatSep(token: Int) = token == EOF || super.isStatSep(token) - - override def expectedMsg(token: Int): String = - if (isHole) expectedMsgTemplate(token2string(token), "unquotee") - else super.expectedMsg(token) - - // $mods def foo - // $mods T - override def readAnnots(annot: => Tree): List[Tree] = in.token match { - case AT => - in.nextToken() - annot :: readAnnots(annot) - case _ if isHole && lookingAhead { isAnnotation || isModifier || isDefIntro || isIdent || isStatSep || in.token == LPAREN } => - val ann = ModsPlaceholder(in.name) - in.nextToken() - ann :: readAnnots(annot) - case _ => - Nil - } - - override def refineStat(): List[Tree] = - if (isHole && !isDclIntro) { - val result = RefineStatPlaceholder(in.name) :: Nil - in.nextToken() - result - } else super.refineStat() - - override def ensureEarlyDef(tree: Tree) = tree match { - case Ident(name: TermName) if isHole(name) => EarlyDefPlaceholder(name) - case _ => super.ensureEarlyDef(tree) - } - - override def isTypedParam(tree: Tree) = super.isTypedParam(tree) || (tree match { - case Ident(name) if isHole(name) => true - case _ => false - }) - - override def topStat = super.topStat.orElse { - case _ if isHole => - val stats = PackageStatPlaceholder(in.name) :: Nil - in.nextToken() - stats - } - - override def enumerator(isFirst: Boolean, allowNestedIf: Boolean = true) = - if (isHole && lookingAhead { in.token == EOF || in.token == RPAREN || isStatSep }) { - val res = ForEnumPlaceholder(in.name) :: Nil - in.nextToken() - res - } else super.enumerator(isFirst, allowNestedIf) - } - } - - /** Wrapper around tree parsed in q"..." quote. Needed to support ..$ splicing on top-level. */ - object Q { - def apply(tree: Tree): Block = Block(Nil, tree).updateAttachment(Q) - def unapply(tree: Tree): Option[Tree] = tree match { - case Block(Nil, contents) if tree.hasAttachment[Q.type] => Some(contents) - case _ => None - } - } - - object TermParser extends Parser { - def entryPoint = parser => Q(implodePatDefs(gen.mkTreeOrBlock(parser.templateOrTopStatSeq()))) - } - - object TypeParser extends Parser { - def entryPoint = { parser => - if (parser.in.token == EOF) - TypeTree() - else - parser.typ() - } - } - - object CaseParser extends Parser { - def entryPoint = parser => implodePatDefs(parser.caseClause()) - } - - object PatternParser extends Parser { - def entryPoint = { parser => - val pat = parser.noSeq.pattern() - gen.patvarTransformer.transform(pat) - } - } - - object ForEnumeratorParser extends Parser { - def entryPoint = { parser => - val enums = parser.enumerator(isFirst = false, allowNestedIf = false) - assert(enums.length == 1) - implodePatDefs(enums.head) - } - } - - object FreshName extends FreshNameExtractor(nme.QUASIQUOTE_PREFIX) -} diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala deleted file mode 100644 index b287971815..0000000000 --- a/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala +++ /dev/null @@ -1,201 +0,0 @@ -package scala.tools.reflect -package quasiquotes - -import java.util.UUID.randomUUID -import scala.collection.{immutable, mutable} - -/** Emulates hole support (see Holes.scala) in the quasiquote parser (see Parsers.scala). - * A principled solution to splicing into Scala syntax would be a parser that natively supports holes. - * Unfortunately, that's outside of our reach in Scala 2.11, so we have to emulate. - * This trait stores knowledge of how to represent the holes as something understandable by the parser - * and how to recover holes from the results of parsing the produced representation. - */ -trait Placeholders { self: Quasiquotes => - import global._ - import Rank._ - import universeTypes._ - - // Step 1: Transform Scala source with holes into vanilla Scala source - - lazy val posMap = mutable.LinkedHashMap[Position, (Int, Int)]() - lazy val code = { - val sb = new StringBuilder() - val sessionSuffix = randomUUID().toString.replace("-", "").substring(0, 8) + "$" - - def appendPart(value: String, pos: Position) = { - val start = sb.length - sb.append(value) - val end = sb.length - posMap += pos -> ((start, end)) - } - - def appendHole(tree: Tree, rank: Rank) = { - val placeholderName = c.freshName(TermName(nme.QUASIQUOTE_PREFIX + sessionSuffix)) - sb.append(placeholderName) - val holeTree = - if (method != nme.unapply) tree - else Bind(placeholderName, tree) - holeMap(placeholderName) = Hole(rank, holeTree) - } - - val iargs = method match { - case nme.apply => args - case nme.unapply => internal.subpatterns(args.head).get - case _ => global.abort("unreachable") - } - - foreach2(iargs, parts.init) { case (tree, (p, pos)) => - val (part, rank) = parseDots(p) - appendPart(part, pos) - appendHole(tree, rank) - } - val (p, pos) = parts.last - appendPart(p, pos) - - sb.toString - } - - object holeMap { - private val underlying = mutable.LinkedHashMap.empty[String, Hole] - private val accessed = mutable.Set.empty[String] - def unused: Set[Name] = (underlying.keys.toSet -- accessed).map(TermName(_)) - def contains(key: Name): Boolean = underlying.contains(key.toString) - def apply(key: Name): Hole = { - val skey = key.toString - val value = underlying(skey) - accessed += skey - value - } - def update(key: Name, hole: Hole) = - underlying += key.toString -> hole - def get(key: Name): Option[Hole] = { - val skey = key.toString - underlying.get(skey).map { v => - accessed += skey - v - } - } - def keysIterator: Iterator[TermName] = underlying.keysIterator.map(TermName(_)) - } - - // Step 2: Transform vanilla Scala AST into an AST with holes - - trait HolePlaceholder { - def matching: PartialFunction[Any, Name] - def unapply(scrutinee: Any): Option[Hole] = { - val name = matching.lift(scrutinee) - name.flatMap { holeMap.get(_) } - } - } - - object Placeholder extends HolePlaceholder { - def matching = { - case name: Name => name - case Ident(name) => name - case Bind(name, Ident(nme.WILDCARD)) => name - case TypeDef(_, name, List(), TypeBoundsTree(EmptyTree, EmptyTree)) => name - } - } - - object ModsPlaceholder extends HolePlaceholder { - def apply(name: Name) = - Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(name.toString)))) - def matching = { - case Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(s: String)))) => TermName(s) - } - } - - object AnnotPlaceholder extends HolePlaceholder { - def matching = { - case Apply(Select(New(Ident(name)), nme.CONSTRUCTOR), Nil) => name - } - } - - object ParamPlaceholder extends HolePlaceholder { - def apply(flags: FlagSet, name: Name) = - ValDef(Modifiers(flags), nme.QUASIQUOTE_PARAM, Ident(name), EmptyTree) - def matching = { - case ValDef(_, nme.QUASIQUOTE_PARAM, Ident(name), EmptyTree) => name - } - } - - object TuplePlaceholder { - def apply(args: List[Tree]) = - Apply(Ident(nme.QUASIQUOTE_TUPLE), args) - def unapply(tree: Tree): Option[List[Tree]] = tree match { - case Apply(Ident(nme.QUASIQUOTE_TUPLE), args) => Some(args) - case _ => None - } - } - - object TupleTypePlaceholder { - def apply(args: List[Tree]) = - AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) - def unapply(tree: Tree): Option[List[Tree]] = tree match { - case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) => Some(args) - case _ => None - } - } - - object FunctionTypePlaceholder { - def apply(args: List[Tree], res: Tree) = - AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), args :+ res) - def unapply(tree: Tree): Option[(List[Tree], Tree)] = tree match { - case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), args :+ res) => Some((args, res)) - case _ => None - } - } - - object SymbolPlaceholder { - def unapply(scrutinee: Any): Option[Hole] = scrutinee match { - case Placeholder(hole: ApplyHole) if hole.tpe <:< symbolType => Some(hole) - case _ => None - } - } - - object CasePlaceholder { - def apply(name: Name) = - CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), Ident(name) :: Nil), EmptyTree, EmptyTree) - def unapply(tree: Tree): Option[Hole] = tree match { - case CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Placeholder(hole))), EmptyTree, EmptyTree) => Some(hole) - case _ => None - } - } - - object RefineStatPlaceholder { - def apply(name: Name) = - ValDef(NoMods, nme.QUASIQUOTE_REFINE_STAT, Ident(name), EmptyTree) - def unapply(tree: Tree): Option[Hole] = tree match { - case ValDef(_, nme.QUASIQUOTE_REFINE_STAT, Ident(Placeholder(hole)), _) => Some(hole) - case _ => None - } - } - - object EarlyDefPlaceholder { - def apply(name: Name) = - ValDef(Modifiers(Flag.PRESUPER), nme.QUASIQUOTE_EARLY_DEF, Ident(name), EmptyTree) - def unapply(tree: Tree): Option[Hole] = tree match { - case ValDef(_, nme.QUASIQUOTE_EARLY_DEF, Ident(Placeholder(hole)), _) => Some(hole) - case _ => None - } - } - - object PackageStatPlaceholder { - def apply(name: Name) = - ValDef(NoMods, nme.QUASIQUOTE_PACKAGE_STAT, Ident(name), EmptyTree) - def unapply(tree: Tree): Option[Hole] = tree match { - case ValDef(NoMods, nme.QUASIQUOTE_PACKAGE_STAT, Ident(Placeholder(hole)), EmptyTree) => Some(hole) - case _ => None - } - } - - object ForEnumPlaceholder { - def apply(name: Name) = - build.SyntacticValFrom(Bind(name, Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) - def unapply(tree: Tree): Option[Hole] = tree match { - case build.SyntacticValFrom(Bind(Placeholder(hole), Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) => - Some(hole) - case _ => None - } - } -} diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala b/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala deleted file mode 100644 index b33069181c..0000000000 --- a/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala +++ /dev/null @@ -1,60 +0,0 @@ -package scala.tools.reflect -package quasiquotes - -import scala.reflect.macros.runtime.Context - -abstract class Quasiquotes extends Parsers - with Holes - with Placeholders - with Reifiers { - val c: Context - val global: c.universe.type = c.universe - import c.universe._ - - def debug(msg: => String): Unit = - if (settings.Yquasiquotedebug.value) println(msg) - - 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"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") - } - val reify0 = method0 match { - case nme.apply => new ApplyReifier().reifyFillingHoles(_) - case nme.unapply => new UnapplyReifier().reifyFillingHoles(_) - case other => global.abort(s"Unknown quasiquote api method: $other") - } - val parse0 = interpolator0 match { - case nme.q => TermParser.parse(_) - case nme.tq => TypeParser.parse(_) - case nme.cq => CaseParser.parse(_) - case nme.pq => PatternParser.parse(_) - case nme.fq => ForEnumeratorParser.parse(_) - case other => global.abort(s"Unknown quasiquote flavor: $other") - } - (universe0, args0, parts1, parse0, reify0, method0) - case _ => - global.abort(s"Couldn't parse call prefix tree ${c.macroApplication}.") - } - - lazy val u = universe // shortcut - lazy val universeTypes = new definitions.UniverseDependentTypes(universe) - - def expandQuasiquote = { - 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) - def sreified = - reified - .toString - .replace("scala.reflect.runtime.`package`.universe.internal.reificationSupport.", "") - .replace("scala.reflect.runtime.`package`.universe.", "") - .replace("scala.collection.immutable.", "") - debug(s"reified tree:\n$sreified\n") - reified - } -} diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala deleted file mode 100644 index 95113d5b00..0000000000 --- a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala +++ /dev/null @@ -1,487 +0,0 @@ -package scala.tools.reflect -package quasiquotes - -import java.lang.UnsupportedOperationException -import scala.reflect.reify.{Reifier => ReflectReifier} -import scala.reflect.internal.Flags._ - -trait Reifiers { self: Quasiquotes => - import global._ - import global.build._ - import global.treeInfo._ - import global.definitions._ - import Rank._ - import universeTypes._ - - abstract class Reifier(val isReifyingExpressions: Boolean) extends { - val global: self.global.type = self.global - val universe = self.universe - val reifee = EmptyTree - val mirror = EmptyTree - val concrete = false - } with ReflectReifier { - lazy val typer = throw new UnsupportedOperationException - - def isReifyingPatterns: Boolean = !isReifyingExpressions - def action = if (isReifyingExpressions) "unquote" else "extract" - def holesHaveTypes = isReifyingExpressions - - /** Map that stores freshly generated names linked to the corresponding names in the reified tree. - * This information is used to reify names created by calls to freshTermName and freshTypeName. - */ - val nameMap = collection.mutable.HashMap.empty[Name, Set[TermName]].withDefault { _ => Set() } - - /** Wraps expressions into: - * a block which starts with a sequence of vals that correspond - * to fresh names that has to be created at evaluation of the quasiquote - * and ends with reified tree: - * - * { - * val name$1: universe.TermName = universe.build.freshTermName(prefix1) - * ... - * val name$N: universe.TermName = universe.build.freshTermName(prefixN) - * tree - * } - * - * Wraps patterns into: - * a call into anonymous class' unapply method required by unapply macro expansion: - * - * new { - * def unapply(tree) = tree match { - * case pattern if guard => Some(result) - * case _ => None - * } - * }.unapply() - * - * where pattern corresponds to reified tree and guard represents conjunction of equalities - * which check that pairs of names in nameMap.values are equal between each other. - */ - def wrap(tree: Tree) = - if (isReifyingExpressions) { - val freshdefs = nameMap.iterator.map { - case (origname, names) => - assert(names.size == 1) - val FreshName(prefix) = origname - val nameTypeName = if (origname.isTermName) tpnme.TermName else tpnme.TypeName - val freshName = if (origname.isTermName) nme.freshTermName else nme.freshTypeName - // q"val ${names.head}: $u.$nameTypeName = $u.internal.reificationSupport.$freshName($prefix)" - ValDef(NoMods, names.head, Select(u, nameTypeName), - Apply(Select(Select(Select(u, nme.internal), nme.reificationSupport), freshName), Literal(Constant(prefix)) :: Nil)) - }.toList - // q"..$freshdefs; $tree" - SyntacticBlock(freshdefs :+ tree) - } else { - val freevars = holeMap.keysIterator.map(Ident(_)).toList - 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)) - } - val guard = - nameMap.collect { case (_, nameset) if nameset.size >= 2 => - nameset.toList.sliding(2).map { case List(n1, n2) => - // q"$n1 == $n2" - Apply(Select(Ident(n1), nme.EQ), List(Ident(n2))) - } - }.flatten.reduceOption[Tree] { (l, r) => - // q"$l && $r" - Apply(Select(l, nme.ZAND), List(r)) - }.getOrElse { EmptyTree } - // 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) = { ..${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(), - SyntacticBlock(unlifters.preamble() :+ 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).pos, s"Don't know how to $action here") - } - wrap(reified) - } - - override def reifyTree(tree: Tree): Tree = - reifyTreePlaceholder(tree) orElse - reifyTreeSyntactically(tree) - - def reifyTreePlaceholder(tree: Tree): Tree = tree match { - case Placeholder(hole: ApplyHole) if hole.tpe <:< treeType => hole.tree - case Placeholder(Hole(tree, NoDot)) if isReifyingPatterns => tree - case Placeholder(hole @ Hole(_, rank @ Dot())) => c.abort(hole.pos, s"Can't $action with $rank here") - case TuplePlaceholder(args) => reifyTuple(args) - // Due to greediness of syntactic applied we need to pre-emptively peek inside. - // `rest` will always be non-empty due to the rule on top of this one. - case SyntacticApplied(id @ Ident(nme.QUASIQUOTE_TUPLE), first :: rest) => - mirrorBuildCall(nme.SyntacticApplied, reifyTreePlaceholder(Apply(id, first)), reify(rest)) - case TupleTypePlaceholder(args) => reifyTupleType(args) - case FunctionTypePlaceholder(argtpes, restpe) => reifyFunctionType(argtpes, restpe) - case CasePlaceholder(hole) => hole.tree - case RefineStatPlaceholder(hole) => reifyRefineStat(hole) - case EarlyDefPlaceholder(hole) => reifyEarlyDef(hole) - case PackageStatPlaceholder(hole) => reifyPackageStat(hole) - case ParamPlaceholder(hole) => hole.tree - // for enumerators are checked not during splicing but during - // desugaring of the for loop in SyntacticFor & SyntacticForYield - case ForEnumPlaceholder(hole) => hole.tree - case _ => EmptyTree - } - - override def reifyTreeSyntactically(tree: Tree) = tree match { - case RefTree(qual, SymbolPlaceholder(Hole(tree, _))) if isReifyingExpressions => - mirrorBuildCall(nme.mkRefTree, reify(qual), tree) - case This(SymbolPlaceholder(Hole(tree, _))) if isReifyingExpressions => - mirrorCall(nme.This, tree) - case SyntacticTraitDef(mods, name, tparams, earlyDefs, parents, selfdef, body) => - reifyBuildCall(nme.SyntacticTraitDef, mods, name, tparams, earlyDefs, parents, selfdef, body) - case SyntacticClassDef(mods, name, tparams, constrmods, vparamss, - earlyDefs, parents, selfdef, body) => - mirrorBuildCall(nme.SyntacticClassDef, reify(mods), reify(name), reify(tparams), reify(constrmods), - reifyVparamss(vparamss), reify(earlyDefs), reify(parents), - reify(selfdef), reify(body)) - case SyntacticPackageObjectDef(name, earlyDefs, parents, selfdef, body) => - reifyBuildCall(nme.SyntacticPackageObjectDef, name, earlyDefs, parents, selfdef, body) - case SyntacticObjectDef(mods, name, earlyDefs, parents, selfdef, body) => - reifyBuildCall(nme.SyntacticObjectDef, mods, name, earlyDefs, parents, selfdef, body) - case SyntacticNew(earlyDefs, parents, selfdef, body) => - reifyBuildCall(nme.SyntacticNew, earlyDefs, parents, selfdef, body) - case SyntacticDefDef(mods, name, tparams, vparamss, tpt, rhs) => - mirrorBuildCall(nme.SyntacticDefDef, reify(mods), reify(name), reify(tparams), - reifyVparamss(vparamss), reify(tpt), reify(rhs)) - case SyntacticValDef(mods, name, tpt, rhs) if tree != noSelfType => - reifyBuildCall(nme.SyntacticValDef, mods, name, tpt, rhs) - case SyntacticVarDef(mods, name, tpt, rhs) => - reifyBuildCall(nme.SyntacticVarDef, mods, name, tpt, rhs) - case SyntacticValFrom(pat, rhs) => - reifyBuildCall(nme.SyntacticValFrom, pat, rhs) - case SyntacticValEq(pat, rhs) => - reifyBuildCall(nme.SyntacticValEq, pat, rhs) - case SyntacticFilter(cond) => - reifyBuildCall(nme.SyntacticFilter, cond) - case SyntacticFor(enums, body) => - reifyBuildCall(nme.SyntacticFor, enums, body) - case SyntacticForYield(enums, body) => - reifyBuildCall(nme.SyntacticForYield, enums, body) - case SyntacticAssign(lhs, rhs) => - reifyBuildCall(nme.SyntacticAssign, lhs, rhs) - case SyntacticApplied(fun, argss) if argss.nonEmpty => - reifyBuildCall(nme.SyntacticApplied, fun, argss) - case SyntacticTypeApplied(fun, targs) if targs.nonEmpty => - reifyBuildCall(nme.SyntacticTypeApplied, fun, targs) - case SyntacticAppliedType(tpt, targs) if targs.nonEmpty => - reifyBuildCall(nme.SyntacticAppliedType, tpt, targs) - case SyntacticFunction(args, body) => - reifyBuildCall(nme.SyntacticFunction, args, body) - case SyntacticEmptyTypeTree() => - reifyBuildCall(nme.SyntacticEmptyTypeTree) - case SyntacticImport(expr, selectors) => - reifyBuildCall(nme.SyntacticImport, expr, selectors) - case SyntacticPartialFunction(cases) => - reifyBuildCall(nme.SyntacticPartialFunction, cases) - case SyntacticMatch(scrutinee, cases) => - reifyBuildCall(nme.SyntacticMatch, scrutinee, cases) - case SyntacticTermIdent(name, isBackquoted) => - reifyBuildCall(nme.SyntacticTermIdent, name, isBackquoted) - case SyntacticTypeIdent(name) => - reifyBuildCall(nme.SyntacticTypeIdent, name) - case SyntacticCompoundType(parents, defns) => - reifyBuildCall(nme.SyntacticCompoundType, parents, defns) - case SyntacticSingletonType(ref) => - reifyBuildCall(nme.SyntacticSingletonType, ref) - case SyntacticTypeProjection(qual, name) => - reifyBuildCall(nme.SyntacticTypeProjection, qual, name) - case SyntacticAnnotatedType(tpt, annot) => - reifyBuildCall(nme.SyntacticAnnotatedType, tpt, annot) - case SyntacticExistentialType(tpt, where) => - reifyBuildCall(nme.SyntacticExistentialType, tpt, where) - case Q(tree) if fillListHole.isDefinedAt(tree) => - mirrorBuildCall(nme.SyntacticBlock, fillListHole(tree)) - case Q(other) => - reifyTree(other) - // Syntactic block always matches so we have to be careful - // 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 CaseDef(pat, guard, body) if fillListHole.isDefinedAt(body) => - mirrorCall(nme.CaseDef, reify(pat), reify(guard), mirrorBuildCall(nme.SyntacticBlock, fillListHole(body))) - // parser emits trees with scala package symbol to ensure - // that some names hygienically point to various scala package - // members; we need to preserve this symbol to preserve - // correctness of the trees produced by quasiquotes - case Select(id @ Ident(nme.scala_), name) if id.symbol == ScalaPackage => - reifyBuildCall(nme.ScalaDot, name) - case Select(qual, name) => - val ctor = if (name.isTypeName) nme.SyntacticSelectType else nme.SyntacticSelectTerm - reifyBuildCall(ctor, qual, name) - case _ => - super.reifyTreeSyntactically(tree) - } - - override def reifyName(name: Name): Tree = name match { - 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.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} - def result(n: Name) = if (isReifyingExpressions) Ident(n) else Bind(n, Ident(nme.WILDCARD)) - if (isReifyingPatterns) result(introduceName()) - else result(nameMap.get(name).map { _.head }.getOrElse { introduceName() }) - case _ => - super.reifyName(name) - } - - def reifyTuple(args: List[Tree]) = args match { - case Nil => reify(Literal(Constant(()))) - case List(hole @ Placeholder(Hole(_, NoDot))) => reify(hole) - case List(Placeholder(_)) => reifyBuildCall(nme.SyntacticTuple, args) - // in a case we only have one element tuple without - // any rank annotations this means that this is - // just an expression wrapped in parentheses - case List(other) => reify(other) - case _ => reifyBuildCall(nme.SyntacticTuple, args) - } - - def reifyTupleType(args: List[Tree]) = args match { - case Nil => reify(Select(Ident(nme.scala_), tpnme.Unit)) - case List(hole @ Placeholder(Hole(_, NoDot))) => reify(hole) - case List(Placeholder(_)) => reifyBuildCall(nme.SyntacticTupleType, args) - case List(other) => reify(other) - case _ => reifyBuildCall(nme.SyntacticTupleType, args) - } - - def reifyFunctionType(argtpes: List[Tree], restpe: Tree) = - reifyBuildCall(nme.SyntacticFunctionType, argtpes, restpe) - - def reifyConstructionCheck(name: TermName, hole: Hole) = hole match { - case _: UnapplyHole => hole.tree - case _: ApplyHole => mirrorBuildCall(name, hole.tree) - } - - def reifyRefineStat(hole: Hole) = reifyConstructionCheck(nme.mkRefineStat, hole) - - def reifyEarlyDef(hole: Hole) = reifyConstructionCheck(nme.mkEarlyDef, hole) - - def reifyAnnotation(hole: Hole) = reifyConstructionCheck(nme.mkAnnotation, hole) - - def reifyPackageStat(hole: Hole) = reifyConstructionCheck(nme.mkPackageStat, hole) - - def reifyVparamss(vparamss: List[List[ValDef]]) = { - val build.ImplicitParams(paramss, implparams) = vparamss - if (implparams.isEmpty) reify(paramss) - else reifyBuildCall(nme.ImplicitParams, paramss, implparams) - } - - /** Splits list into a list of groups where subsequent elements are considered - * similar by the corresponding function. - * - * Example: - * - * > group(List(1, 1, 0, 0, 1, 0)) { _ == _ } - * List(List(1, 1), List(0, 0), List(1), List(0)) - * - */ - def group[T](lst: List[T])(similar: (T, T) => Boolean) = lst.foldLeft[List[List[T]]](List()) { - case (Nil, el) => List(List(el)) - case (ll :+ (last @ (lastinit :+ lastel)), el) if similar(lastel, el) => ll :+ (last :+ el) - case (ll, el) => ll :+ List(el) - } - - /** Reifies list filling all the valid holeMap. - * - * Reification of non-trivial list is done in two steps: - * - * 1. split the list into groups where every placeholder is always - * put in a group of it's own and all subsquent non-holeMap are - * grouped together; element is considered to be a placeholder if it's - * in the domain of the fill function; - * - * 2. fold the groups into a sequence of lists added together with ++ using - * fill reification for holeMapĀ and fallback reification for non-holeMap. - * - * Example: - * - * reifyHighRankList(lst) { - * // first we define patterns that extract high-rank holeMap (currently ..) - * case Placeholder(IterableType(_, _)) => tree - * } { - * // in the end we define how single elements are reified, typically with default reify call - * reify(_) - * } - * - * Sample execution of previous concrete list reifier: - * - * > val lst = List(foo, bar, qq$f3948f9s$1) - * > reifyHighRankList(lst) { ... } { ... } - * q"List($foo, $bar) ++ ${holeMap(qq$f3948f9s$1).tree}" - */ - def reifyHighRankList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree - - val fillListHole: PartialFunction[Any, Tree] = { - case Placeholder(Hole(tree, DotDot)) => tree - case CasePlaceholder(Hole(tree, DotDot)) => tree - case RefineStatPlaceholder(h @ Hole(_, DotDot)) => reifyRefineStat(h) - case EarlyDefPlaceholder(h @ Hole(_, DotDot)) => reifyEarlyDef(h) - case PackageStatPlaceholder(h @ Hole(_, DotDot)) => reifyPackageStat(h) - case ForEnumPlaceholder(Hole(tree, DotDot)) => tree - case ParamPlaceholder(Hole(tree, DotDot)) => tree - case SyntacticPatDef(mods, pat, tpt, rhs) => - reifyBuildCall(nme.SyntacticPatDef, mods, pat, tpt, rhs) - case SyntacticValDef(mods, p @ Placeholder(h: ApplyHole), tpt, rhs) if h.tpe <:< treeType => - mirrorBuildCall(nme.SyntacticPatDef, reify(mods), h.tree, reify(tpt), reify(rhs)) - } - - val fillListOfListsHole: PartialFunction[Any, Tree] = { - case List(ParamPlaceholder(Hole(tree, DotDotDot))) => tree - case List(Placeholder(Hole(tree, DotDotDot))) => tree - } - - /** Reifies arbitrary list filling ..$x and ...$y holeMap when they are put - * in the correct position. Fallbacks to regular reification for zero rank - * elements. - */ - override def reifyList(xs: List[Any]): Tree = reifyHighRankList(xs)(fillListHole.orElse(fillListOfListsHole))(reify) - - def reifyAnnotList(annots: List[Tree]): Tree = reifyHighRankList(annots) { - case AnnotPlaceholder(h @ Hole(_, DotDot)) => reifyAnnotation(h) - } { - case AnnotPlaceholder(h: ApplyHole) if h.tpe <:< treeType => reifyAnnotation(h) - case AnnotPlaceholder(h: UnapplyHole) if h.rank == NoDot => reifyAnnotation(h) - case other => reify(other) - } - - // These are explicit flags except those that are used - // to overload the same tree for two different concepts: - // - MUTABLE that is used to override ValDef for vars - // - TRAIT that is used to override ClassDef for traits - val nonOverloadedExplicitFlags = ExplicitFlags & ~MUTABLE & ~TRAIT - - def ensureNoExplicitFlags(m: Modifiers, pos: Position) = { - // Traits automatically have ABSTRACT flag assigned to - // them so in that case it's not an explicit flag - val flags = if (m.isTrait) m.flags & ~ABSTRACT else m.flags - if ((flags & nonOverloadedExplicitFlags) != 0L) - c.abort(pos, s"Can't $action modifiers together with flags, consider merging flags into modifiers") - } - - override def mirrorSelect(name: String): Tree = - Select(universe, TermName(name)) - - override def mirrorCall(name: TermName, args: Tree*): Tree = - Apply(Select(universe, name), args.toList) - - override def mirrorBuildCall(name: TermName, args: Tree*): Tree = - Apply(Select(Select(Select(universe, nme.internal), nme.reificationSupport), name), args.toList) - - override def scalaFactoryCall(name: String, args: Tree*): Tree = - call("scala." + name, args: _*) - } - - class ApplyReifier extends Reifier(isReifyingExpressions = true) { - def reifyHighRankList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree = - if (xs.isEmpty) mkList(Nil) - else { - def reifyGroup(group: List[Any]): Tree = group match { - case List(elem) if fill.isDefinedAt(elem) => fill(elem) - case elems => mkList(elems.map(fallback)) - } - val head :: tail = group(xs) { (a, b) => !fill.isDefinedAt(a) && !fill.isDefinedAt(b) } - tail.foldLeft[Tree](reifyGroup(head)) { (tree, lst) => Apply(Select(tree, nme.PLUSPLUS), List(reifyGroup(lst))) } - } - - override def reifyModifiers(m: Modifiers) = - if (m == NoMods) super.reifyModifiers(m) - else { - val (modsPlaceholders, annots) = m.annotations.partition { - case ModsPlaceholder(_) => true - case _ => false - } - val (mods, flags) = modsPlaceholders.map { - case ModsPlaceholder(hole: ApplyHole) => hole - }.partition { hole => - if (hole.tpe <:< modsType) true - else if (hole.tpe <:< flagsType) false - else c.abort(hole.pos, s"$flagsType or $modsType expected but ${hole.tpe} found") - } - mods match { - case hole :: Nil => - if (flags.nonEmpty) c.abort(flags(0).pos, "Can't unquote flags together with modifiers, consider merging flags into modifiers") - if (annots.nonEmpty) c.abort(hole.pos, "Can't unquote modifiers together with annotations, consider merging annotations into modifiers") - ensureNoExplicitFlags(m, hole.pos) - hole.tree - case _ :: hole :: Nil => - c.abort(hole.pos, "Can't unquote multiple modifiers, consider merging them into a single modifiers instance") - case _ => - val baseFlags = reifyFlags(m.flags) - val reifiedFlags = flags.foldLeft[Tree](baseFlags) { case (flag, hole) => Apply(Select(flag, nme.OR), List(hole.tree)) } - mirrorFactoryCall(nme.Modifiers, reifiedFlags, reify(m.privateWithin), reifyAnnotList(annots)) - } - } - - } - class UnapplyReifier extends Reifier(isReifyingExpressions = false) { - 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 reifyHighRankList(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) = - if (m == NoMods) super.reifyModifiers(m) - else { - val mods = m.annotations.collect { case ModsPlaceholder(hole: UnapplyHole) => hole } - mods match { - 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.treeNoUnlift - case _ :: hole :: _ => - c.abort(hole.pos, "Can't extract multiple modifiers together, consider extracting a single modifiers instance") - case Nil => - mirrorFactoryCall(nme.Modifiers, reifyFlags(m.flags), reify(m.privateWithin), reifyAnnotList(m.annotations)) - } - } - } -} -- cgit v1.2.3