diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-07-10 13:05:06 -0700 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-07-10 13:05:06 -0700 |
commit | 9def2ada8261ca14f0059c7d2b01e85830705e9b (patch) | |
tree | 3dd0ee2c58ab3ff94a99c32f89e6d15dd6950777 /src/compiler | |
parent | 59947119eec1cb49c46f9ddf930741d1fb554f61 (diff) | |
parent | 114d52b1f8be07a95125397879b170e8496c0c2e (diff) | |
download | scala-9def2ada8261ca14f0059c7d2b01e85830705e9b.tar.gz scala-9def2ada8261ca14f0059c7d2b01e85830705e9b.tar.bz2 scala-9def2ada8261ca14f0059c7d2b01e85830705e9b.zip |
Merge pull request #2714 from scalamacros/topic/quasiquotes
Quasiquotes
Diffstat (limited to 'src/compiler')
23 files changed, 1036 insertions, 236 deletions
diff --git a/src/compiler/scala/reflect/macros/compiler/Errors.scala b/src/compiler/scala/reflect/macros/compiler/Errors.scala index dd3142127e..a60a2c2306 100644 --- a/src/compiler/scala/reflect/macros/compiler/Errors.scala +++ b/src/compiler/scala/reflect/macros/compiler/Errors.scala @@ -60,8 +60,8 @@ trait Errors extends Traces { (rtpe, atpe) match { case _ if rtpe eq atpe => success() case (TypeRef(_, RepeatedParamClass, rtpe :: Nil), TypeRef(_, RepeatedParamClass, atpe :: Nil)) => check(rtpe, atpe) - case (ExprClassOf(_), TreeType()) => success() - case (TreeType(), ExprClassOf(_)) => success() + case (ExprClassOf(_), TreeType()) if rtpe.prefix =:= atpe.prefix => success() + case (SubtreeType(), ExprClassOf(_)) if rtpe.prefix =:= atpe.prefix => success() case _ => rtpe <:< atpe } } diff --git a/src/compiler/scala/reflect/reify/codegen/GenTrees.scala b/src/compiler/scala/reflect/reify/codegen/GenTrees.scala index 78bdf7e132..3507c2a173 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenTrees.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenTrees.scala @@ -42,12 +42,6 @@ trait GenTrees { // the second prototype reified external types, but avoided reifying local ones => this created an ugly irregularity // current approach is uniform and compact var rtree = tree match { - case global.EmptyTree => - reifyMirrorObject(EmptyTree) - case global.emptyValDef => - mirrorSelect(nme.emptyValDef) - case global.pendingSuperCall => - mirrorSelect(nme.pendingSuperCall) case FreeDef(_, _, _, _, _) => reifyNestedFreeDef(tree) case FreeRef(_, _) => @@ -56,12 +50,8 @@ trait GenTrees { reifyBoundTerm(tree) case BoundType(tree) => reifyBoundType(tree) - case Literal(const @ Constant(_)) => - mirrorCall(nme.Literal, reifyProduct(const)) - case Import(expr, selectors) => - mirrorCall(nme.Import, reify(expr), mkList(selectors map reifyProduct)) case _ => - reifyProduct(tree) + reifyTreeSyntactically(tree) } // usually we don't reify symbols/types, because they can be re-inferred during subsequent reflective compilation @@ -78,6 +68,21 @@ trait GenTrees { rtree } + def reifyTreeSyntactically(tree: Tree) = tree match { + case global.EmptyTree => + reifyMirrorObject(EmptyTree) + case global.emptyValDef => + mirrorSelect(nme.emptyValDef) + case global.pendingSuperCall => + mirrorSelect(nme.pendingSuperCall) + case Literal(const @ Constant(_)) => + mirrorCall(nme.Literal, reifyProduct(const)) + case Import(expr, selectors) => + mirrorCall(nme.Import, reify(expr), mkList(selectors map reifyProduct)) + case _ => + reifyProduct(tree) + } + def reifyModifiers(m: global.Modifiers) = mirrorFactoryCall(nme.Modifiers, mirrorBuildCall(nme.flagsFromBits, reify(m.flags)), reify(m.privateWithin), reify(m.annotations)) diff --git a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala index e0570d61f2..de9fec0df5 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala @@ -42,6 +42,9 @@ trait GenUtils { def mirrorBuildCall(name: TermName, args: Tree*): Tree = call("" + nme.UNIVERSE_BUILD_PREFIX + name, args: _*) + def reifyBuildCall(name: TermName, args: Any*) = + mirrorBuildCall(name, args map reify: _*) + def mirrorMirrorCall(name: TermName, args: Tree*): Tree = call("" + nme.MIRROR_PREFIX + name, args: _*) diff --git a/src/compiler/scala/reflect/reify/package.scala b/src/compiler/scala/reflect/reify/package.scala index d3cae3d123..30cfec8e2a 100644 --- a/src/compiler/scala/reflect/reify/package.scala +++ b/src/compiler/scala/reflect/reify/package.scala @@ -32,7 +32,7 @@ package object reify { // If we're in the constructor of an object or others don't have easy access to `this`, we have no good way to grab // the class of that object. Instead, we construct an anonymous class and grab his class file, assuming // this is enough to get the correct class loadeer for the class we *want* a mirror for, the object itself. - rClassTree orElse Apply(Select(treeBuilder.makeAnonymousNew(Nil), sn.GetClass), Nil) + rClassTree orElse Apply(Select(gen.mkAnonymousNew(Nil), sn.GetClass), Nil) } // JavaUniverse is defined in scala-reflect.jar, so we must be very careful in case someone reifies stuff having only scala-library.jar on the classpath val isJavaUniverse = JavaUniverseClass != NoSymbol && universe.tpe <:< JavaUniverseClass.toTypeConstructor diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 603f9af1b4..ea6543bb71 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -103,16 +103,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) typer.typed(mkCast(tree, pt)) } - /** Trees fresh from the oven, mostly for use by the parser. */ - object treeBuilder extends { - val global: Global.this.type = Global.this - } with TreeBuilder { - def freshName(prefix: String): Name = freshTermName(prefix) - def freshTermName(prefix: String): TermName = currentUnit.freshTermName(prefix) - def freshTypeName(prefix: String): TypeName = currentUnit.freshTypeName(prefix) - def o2p(offset: Int): Position = new OffsetPosition(currentUnit.source, offset) - def r2p(start: Int, mid: Int, end: Int): Position = rangePos(currentUnit.source, start, mid, end) - } + /** A spare instance of TreeBuilder left for backwards compatibility. */ + lazy val treeBuilder: TreeBuilder { val global: Global.this.type } = new syntaxAnalyzer.ParserTreeBuilder /** Fold constants */ object constfold extends { diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index c28a6ba337..ad1977b9aa 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -112,7 +112,6 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { else AppliedTypeTree(Ident(clazz), targs map TypeTree) )) } - def mkSuperInitCall: Select = Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR) def wildcardStar(tree: Tree) = atPos(tree.pos) { Typed(tree, Ident(tpnme.WILDCARD_STAR)) } @@ -255,4 +254,52 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { attrThis, If(cond, Block(syncBody: _*), EmptyTree)) :: stats: _*) + + /** Creates a tree representing new Object { stats }. + * To make sure an anonymous subclass of Object is created, + * if there are no stats, a () is added. + */ + def mkAnonymousNew(stats: List[Tree]): Tree = { + val stats1 = if (stats.isEmpty) List(Literal(Constant(()))) else stats + mkNew(Nil, emptyValDef, stats1, NoPosition, NoPosition) + } + + /** Create positioned tree representing an object creation <new parents { stats } + * @param npos the position of the new + * @param cpos the position of the anonymous class starting with parents + */ + def mkNew(parents: List[Tree], self: ValDef, stats: List[Tree], + npos: Position, cpos: Position): Tree = + if (parents.isEmpty) + mkNew(List(scalaAnyRefConstr), self, stats, npos, cpos) + else if (parents.tail.isEmpty && stats.isEmpty) { + // `Parsers.template` no longer differentiates tpts and their argss + // e.g. `C()` will be represented as a single tree Apply(Ident(C), Nil) + // instead of parents = Ident(C), argss = Nil as before + // this change works great for things that are actually templates + // but in this degenerate case we need to perform postprocessing + val app = treeInfo.dissectApplied(parents.head) + atPos(npos union cpos) { New(app.callee, app.argss) } + } else { + val x = tpnme.ANON_CLASS_NAME + atPos(npos union cpos) { + Block( + List( + atPos(cpos) { + ClassDef( + Modifiers(FINAL), x, Nil, + mkTemplate(parents, self, NoMods, ListOfNil, stats, cpos.focus)) + }), + atPos(npos) { + New( + Ident(x) setPos npos.focus, + Nil) + } + ) + } + } + + def mkSyntheticParam(pname: TermName) = + ValDef(Modifiers(PARAM | SYNTHETIC), pname, TypeTree(), EmptyTree) + } diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala index 41d89aa3b4..641ab9c279 100644 --- a/src/compiler/scala/tools/nsc/ast/Trees.scala +++ b/src/compiler/scala/tools/nsc/ast/Trees.scala @@ -54,77 +54,6 @@ trait Trees extends scala.reflect.internal.Trees { self: Global => case xs :: rest => rest.foldLeft(Apply(gen.mkSuperInitCall, xs): Tree)(Apply.apply) } - /** Generates a template with constructor corresponding to - * - * constrmods (vparams1_) ... (vparams_n) preSuper { presupers } - * extends superclass(args_1) ... (args_n) with mixins { self => body } - * - * This gets translated to - * - * extends superclass with mixins { self => - * presupers' // presupers without rhs - * vparamss // abstract fields corresponding to value parameters - * def <init>(vparamss) { - * presupers - * super.<init>(args) - * } - * body - * } - */ - def Template(parents: List[Tree], self: ValDef, constrMods: Modifiers, vparamss: List[List[ValDef]], body: List[Tree], superPos: Position): Template = { - /* Add constructor to template */ - - // create parameters for <init> as synthetic trees. - var vparamss1 = mmap(vparamss) { vd => - atPos(vd.pos.focus) { - val mods = Modifiers(vd.mods.flags & (IMPLICIT | DEFAULTPARAM | BYNAMEPARAM) | PARAM | PARAMACCESSOR) - ValDef(mods withAnnotations vd.mods.annotations, vd.name, vd.tpt.duplicate, vd.rhs.duplicate) - } - } - val (edefs, rest) = body span treeInfo.isEarlyDef - val (evdefs, etdefs) = edefs partition treeInfo.isEarlyValDef - val gvdefs = evdefs map { - case vdef @ ValDef(_, _, tpt, _) => - copyValDef(vdef)( - // atPos for the new tpt is necessary, since the original tpt might have no position - // (when missing type annotation for ValDef for example), so even though setOriginal modifies the - // position of TypeTree, it would still be NoPosition. That's what the author meant. - tpt = atPos(vdef.pos.focus)(TypeTree() setOriginal tpt setPos tpt.pos.focus), - rhs = EmptyTree - ) - } - val lvdefs = evdefs collect { case vdef: ValDef => copyValDef(vdef)(mods = vdef.mods | PRESUPER) } - - val constrs = { - if (constrMods hasFlag TRAIT) { - if (body forall treeInfo.isInterfaceMember) List() - else List( - atPos(wrappingPos(superPos, lvdefs)) ( - DefDef(NoMods, nme.MIXIN_CONSTRUCTOR, List(), ListOfNil, TypeTree(), Block(lvdefs, Literal(Constant(())))))) - } else { - // convert (implicit ... ) to ()(implicit ... ) if its the only parameter section - if (vparamss1.isEmpty || !vparamss1.head.isEmpty && vparamss1.head.head.mods.isImplicit) - vparamss1 = List() :: vparamss1 - val superCall = pendingSuperCall // we can't know in advance which of the parents will end up as a superclass - // this requires knowing which of the parents is a type macro and which is not - // and that's something that cannot be found out before typer - // (the type macros aren't in the trunk yet, but there is a plan for them to land there soon) - // this means that we don't know what will be the arguments of the super call - // therefore here we emit a dummy which gets populated when the template is named and typechecked - List( - // TODO: previously this was `wrappingPos(superPos, lvdefs ::: argss.flatten)` - // is it going to be a problem that we can no longer include the `argss`? - atPos(wrappingPos(superPos, lvdefs)) ( - DefDef(constrMods, nme.CONSTRUCTOR, List(), vparamss1, TypeTree(), Block(lvdefs ::: List(superCall), Literal(Constant(())))))) - } - } - constrs foreach (ensureNonOverlapping(_, parents ::: gvdefs, focus=false)) - // Field definitions for the class - remove defaults. - val fieldDefs = vparamss.flatten map (vd => copyValDef(vd)(mods = vd.mods &~ DEFAULTPARAM, rhs = EmptyTree)) - - Template(parents, self, gvdefs ::: fieldDefs ::: constrs ::: etdefs ::: rest) - } - /** Construct class definition with given class symbol, value parameters, * supercall arguments and template body. * @@ -143,9 +72,9 @@ trait Trees extends scala.reflect.internal.Trees { self: Global => ) ClassDef(sym, - Template(sym.info.parents map TypeTree, - if (sym.thisSym == sym || phase.erasedTypes) emptyValDef else ValDef(sym.thisSym), - constrMods, vparamss, body, superPos)) + gen.mkTemplate(sym.info.parents map TypeTree, + if (sym.thisSym == sym || phase.erasedTypes) emptyValDef else ValDef(sym.thisSym), + constrMods, vparamss, body, superPos)) } // --- subcomponents -------------------------------------------------- diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index ef5872986c..eb924a811b 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -26,13 +26,22 @@ import util.FreshNameCreator * the beginnings of a campaign against this latest incursion by Cutty * McPastington and his army of very similar soldiers. */ -trait ParsersCommon extends ScannersCommon { +trait ParsersCommon extends ScannersCommon { self => val global : Global import global._ def newLiteral(const: Any) = Literal(Constant(const)) def literalUnit = newLiteral(()) + class ParserTreeBuilder extends TreeBuilder { + val global: self.global.type = self.global + def freshName(prefix: String): Name = freshTermName(prefix) + def freshTermName(prefix: String): TermName = currentUnit.freshTermName(prefix) + def freshTypeName(prefix: String): TypeName = currentUnit.freshTypeName(prefix) + def o2p(offset: Int): Position = new OffsetPosition(currentUnit.source, offset) + def r2p(start: Int, mid: Int, end: Int): Position = rangePos(currentUnit.source, start, mid, end) + } + /** This is now an abstract class, only to work around the optimizer: * methods in traits are never inlined. */ @@ -147,6 +156,17 @@ self => def newScanner(): Scanner = new SourceFileScanner(source) + /** Scoping operator used to temporarily look into the future. + * Backs up scanner data before evaluating a block and restores it after. + */ + def lookingAhead[T](body: => T): T = { + val snapshot = (new ScannerData{}).copyFrom(in) + in.nextToken() + val res = body + in copyFrom snapshot + res + } + val in = newScanner() in.init() @@ -290,6 +310,7 @@ self => /** whether a non-continuable syntax error has been seen */ private var lastErrorOffset : Int = -1 + val treeBuilder = new ParserTreeBuilder import treeBuilder.{global => _, _} /** The types of the context bounds of type parameters of the surrounding class @@ -399,7 +420,7 @@ self => def mainParamType = AppliedTypeTree(Ident(tpnme.Array), List(Ident(tpnme.String))) def mainParameter = List(ValDef(Modifiers(Flags.PARAM), nme.argv, mainParamType, EmptyTree)) def mainSetArgv = List(ValDef(NoMods, nme.args, TypeTree(), Ident(nme.argv))) - def mainDef = DefDef(NoMods, nme.main, Nil, List(mainParameter), scalaDot(tpnme.Unit), Block(mainSetArgv, makeAnonymousNew(stmts))) + def mainDef = DefDef(NoMods, nme.main, Nil, List(mainParameter), scalaDot(tpnme.Unit), Block(mainSetArgv, gen.mkAnonymousNew(stmts))) // object Main def moduleName = newTermName(ScriptRunner scriptMain settings) @@ -604,6 +625,8 @@ self => case _ => false } + def isAnnotation: Boolean = in.token == AT + def isLocalModifier: Boolean = in.token match { case ABSTRACT | FINAL | SEALED | IMPLICIT | LAZY => true case _ => false @@ -731,7 +754,7 @@ self => } @inline final def commaSeparated[T](part: => T): List[T] = tokenSeparated(COMMA, sepFirst = false, part) @inline final def caseSeparated[T](part: => T): List[T] = tokenSeparated(CASE, sepFirst = true, part) - @inline final def readAnnots[T](part: => T): List[T] = tokenSeparated(AT, sepFirst = true, part) + def readAnnots(part: => Tree): List[Tree] = tokenSeparated(AT, sepFirst = true, part) /* --------- OPERAND/OPERATOR STACK --------------------------------------- */ @@ -1365,7 +1388,7 @@ self => } else { syntaxErrorOrIncomplete("`*' expected", skipIt = true) } - } else if (in.token == AT) { + } else if (isAnnotation) { t = (t /: annotations(skipNewLines = false))(makeAnnotated) } else { t = atPos(t.pos.startOrPoint, colonPos) { @@ -1501,7 +1524,7 @@ self => val pname = freshName("x$") in.nextToken() val id = atPos(start) (Ident(pname)) - val param = atPos(id.pos.focus){ makeSyntheticParam(pname.toTermName) } + val param = atPos(id.pos.focus){ gen.mkSyntheticParam(pname.toTermName) } placeholderParams = param :: placeholderParams id case LPAREN => @@ -1516,7 +1539,7 @@ self => val tstart = in.offset val (parents, self, stats) = template() val cpos = r2p(tstart, tstart, in.lastOffset max tstart) - makeNew(parents, self, stats, npos, cpos) + gen.mkNew(parents, self, stats, npos, cpos) case _ => syntaxErrorOrIncompleteAnd("illegal start of simple expression", skipIt = true)(errorTermTree) } @@ -1602,13 +1625,16 @@ self => */ def block(): Tree = makeBlock(blockStatSeq()) + def caseClause(): CaseDef = + atPos(in.offset)(makeCaseDef(pattern(), guard(), caseBlock())) + /** {{{ * CaseClauses ::= CaseClause {CaseClause} * CaseClause ::= case Pattern [Guard] `=>' Block * }}} */ def caseClauses(): List[CaseDef] = { - val cases = caseSeparated { atPos(in.offset)(makeCaseDef(pattern(), guard(), caseBlock())) } + val cases = caseSeparated { caseClause() } if (cases.isEmpty) // trigger error if there are no cases accept(CASE) @@ -2050,6 +2076,8 @@ self => /* -------- PARAMETERS ------------------------------------------- */ + def allowTypelessParams = false + /** {{{ * ParamClauses ::= {ParamClause} [[nl] `(' implicit Params `)'] * ParamClause ::= [nl] `(' [Params] `)' @@ -2086,7 +2114,7 @@ self => val name = ident() var bynamemod = 0 val tpt = - if (settings.YmethodInfer && !owner.isTypeName && in.token != COLON) { + if (((settings.YmethodInfer && !owner.isTypeName) || allowTypelessParams) && in.token != COLON) { TypeTree() } else { // XX-METHOD-INFER accept(COLON) @@ -2804,7 +2832,7 @@ self => if (inScalaRootPackage && ScalaValueClassNames.contains(name)) Template(parents0, self, anyvalConstructor :: body) else - Template(anyrefParents(), self, constrMods, vparamss, body, o2p(tstart)) + gen.mkTemplate(anyrefParents(), self, constrMods, vparamss, body, o2p(tstart)) } } @@ -2867,7 +2895,7 @@ self => case IMPORT => in.flushDoc importClause() - case x if x == AT || isTemplateIntro || isModifier => + case x if isAnnotation || isTemplateIntro || isModifier => joinComment(topLevelTmplDef :: Nil) case _ => if (isStatSep) Nil @@ -2923,11 +2951,11 @@ self => if (in.token == IMPORT) { in.flushDoc stats ++= importClause() + } else if (isDefIntro || isModifier || isAnnotation) { + stats ++= joinComment(nonLocalDefOrDcl) } else if (isExprIntro) { in.flushDoc stats += statement(InTemplate) - } else if (isDefIntro || isModifier || in.token == AT) { - stats ++= joinComment(nonLocalDefOrDcl) } else if (!isStatSep) { syntaxErrorOrIncomplete("illegal start of definition", skipIt = true) } @@ -3007,7 +3035,7 @@ self => stats += statement(InBlock) if (in.token != RBRACE && in.token != CASE) acceptStatSep() } - else if (isDefIntro || isLocalModifier || in.token == AT) { + else if (isDefIntro || isLocalModifier || isAnnotation) { if (in.token == IMPLICIT) { val start = in.skipToken() if (isIdent) stats += implicitClosure(start, InBlock) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 2dca39f7a3..03cdead472 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -5,7 +5,7 @@ package scala.tools.nsc package ast.parser -import scala.tools.nsc.util.CharArrayReader +import scala.tools.nsc.util.{ CharArrayReader, CharArrayReaderData } import scala.reflect.internal.util._ import scala.reflect.internal.Chars._ import Tokens._ @@ -71,17 +71,37 @@ trait Scanners extends ScannersCommon { /** the base of a number */ var base: Int = 0 - def copyFrom(td: TokenData) = { + def copyFrom(td: TokenData): this.type = { this.token = td.token this.offset = td.offset this.lastOffset = td.lastOffset this.name = td.name this.strVal = td.strVal this.base = td.base + this } } - abstract class Scanner extends CharArrayReader with TokenData with ScannerCommon { + /** An interface to most of mutable data in Scanner defined in TokenData + * and CharArrayReader (+ next, prev fields) with copyFrom functionality + * to backup/restore data (used by quasiquotes' lookingAhead). + */ + trait ScannerData extends TokenData with CharArrayReaderData { + /** we need one token lookahead and one token history + */ + val next: TokenData = new TokenData{} + val prev: TokenData = new TokenData{} + + def copyFrom(sd: ScannerData): this.type = { + this.next copyFrom sd.next + this.prev copyFrom sd.prev + super[CharArrayReaderData].copyFrom(sd) + super[TokenData].copyFrom(sd) + this + } + } + + abstract class Scanner extends CharArrayReader with TokenData with ScannerData with ScannerCommon { private def isDigit(c: Char) = java.lang.Character isDigit c private var openComments = 0 @@ -194,13 +214,6 @@ trait Scanners extends ScannersCommon { cbuf.clear() } - private class TokenData0 extends TokenData - - /** we need one token lookahead and one token history - */ - val next : TokenData = new TokenData0 - val prev : TokenData = new TokenData0 - /** a stack of tokens which indicates whether line-ends can be statement separators * also used for keeping track of nesting levels. * We keep track of the closing symbol of a region. This can be diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 0ef71fa1b5..666f19851d 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -190,50 +190,6 @@ abstract class TreeBuilder { } } - /** Creates a tree representing new Object { stats }. - * To make sure an anonymous subclass of Object is created, - * if there are no stats, a () is added. - */ - def makeAnonymousNew(stats: List[Tree]): Tree = { - val stats1 = if (stats.isEmpty) List(Literal(Constant(()))) else stats - makeNew(Nil, emptyValDef, stats1, NoPosition, NoPosition) - } - - /** Create positioned tree representing an object creation <new parents { stats } - * @param npos the position of the new - * @param cpos the position of the anonymous class starting with parents - */ - def makeNew(parents: List[Tree], self: ValDef, stats: List[Tree], - npos: Position, cpos: Position): Tree = - if (parents.isEmpty) - makeNew(List(scalaAnyRefConstr), self, stats, npos, cpos) - else if (parents.tail.isEmpty && stats.isEmpty) { - // `Parsers.template` no longer differentiates tpts and their argss - // e.g. `C()` will be represented as a single tree Apply(Ident(C), Nil) - // instead of parents = Ident(C), argss = Nil as before - // this change works great for things that are actually templates - // but in this degenerate case we need to perform postprocessing - val app = treeInfo.dissectApplied(parents.head) - atPos(npos union cpos) { New(app.callee, app.argss) } - } else { - val x = tpnme.ANON_CLASS_NAME - atPos(npos union cpos) { - Block( - List( - atPos(cpos) { - ClassDef( - Modifiers(FINAL), x, Nil, - Template(parents, self, NoMods, ListOfNil, stats, cpos.focus)) - }), - atPos(npos) { - New( - Ident(x) setPos npos.focus, - Nil) - } - ) - } - } - /** Create a tree representing an assignment <lhs = rhs> */ def makeAssign(lhs: Tree, rhs: Tree): Tree = lhs match { case Apply(fn, args) => @@ -303,9 +259,6 @@ abstract class TreeBuilder { def makeParam(pname: TermName, tpe: Tree) = ValDef(Modifiers(PARAM), pname, tpe, EmptyTree) - def makeSyntheticParam(pname: TermName) = - ValDef(Modifiers(PARAM | SYNTHETIC), pname, TypeTree(), EmptyTree) - def makeSyntheticTypeParam(pname: TypeName, bounds: Tree) = TypeDef(Modifiers(DEFERRED | SYNTHETIC), pname, Nil, bounds) @@ -467,7 +420,7 @@ abstract class TreeBuilder { val x = freshTermName(prefix) val id = Ident(x) val sel = if (checkExhaustive) id else gen.mkUnchecked(id) - Function(List(makeSyntheticParam(x)), Match(sel, cases)) + Function(List(gen.mkSyntheticParam(x)), Match(sel, cases)) } /** Create tree for case definition <case pat if guard => rhs> */ diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 321baba562..993f735c72 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -190,6 +190,7 @@ trait ScalaSettings extends AbsScalaSettings val Yreifydebug = BooleanSetting("-Yreify-debug", "Trace reification.") val Ytyperdebug = BooleanSetting("-Ytyper-debug", "Trace all type assignments.") val Ypatmatdebug = BooleanSetting("-Ypatmat-debug", "Trace pattern matching translation.") + val Yquasiquotedebug = BooleanSetting("-Yquasiquote-debug", "Trace quasiquote-related activities.") /** Groups of Settings. */ diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index a3ab948171..81f5545695 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -697,7 +697,7 @@ trait ContextErrors { protected def macroExpansionError(expandee: Tree, msg: String, pos: Position = NoPosition) = { def msgForLog = if (msg != null && (msg contains "exception during macro expansion")) msg.split(EOL).drop(1).headOption.getOrElse("?") else msg macroLogLite("macro expansion has failed: %s".format(msgForLog)) - if (msg != null) context.error(pos, msg) // issueTypeError(PosAndMsgTypeError(..)) won't work => swallows positions + if (msg != null) context.error(if (pos.isDefined) pos else expandee.pos, msg) // issueTypeError(PosAndMsgTypeError(..)) won't work => swallows positions setError(expandee) throw MacroExpansionException } @@ -741,7 +741,7 @@ trait ContextErrors { try { // [Eugene] is there a better way? // [Paul] See Exceptional.scala and Origins.scala. - val relevancyThreshold = realex.getStackTrace().indexWhere(_.getMethodName endsWith "macroExpand1") + val relevancyThreshold = realex.getStackTrace().indexWhere(_.getMethodName endsWith "macroExpandWithRuntime") if (relevancyThreshold == -1) None else { var relevantElements = realex.getStackTrace().take(relevancyThreshold + 1) @@ -782,13 +782,16 @@ trait ContextErrors { } def MacroExpansionHasInvalidTypeError(expandee: Tree, expanded: Any) = { + def isUnaffiliatedExpr = expanded.isInstanceOf[scala.reflect.api.Exprs#Expr[_]] + def isUnaffiliatedTree = expanded.isInstanceOf[scala.reflect.api.Trees#TreeApi] val expected = "expr or tree" - val isPathMismatch = expanded != null && expanded.isInstanceOf[scala.reflect.api.Exprs#Expr[_]] + val actual = if (isUnaffiliatedExpr) "an expr" else if (isUnaffiliatedTree) "a tree" else "unexpected" + val isPathMismatch = expanded != null && (isUnaffiliatedExpr || isUnaffiliatedTree) macroExpansionError(expandee, s"macro must return a compiler-specific $expected; returned value is " + ( if (expanded == null) "null" - else if (isPathMismatch) s" $expected, but it doesn't belong to this compiler" - else " of " + expanded.getClass + else if (isPathMismatch) s"$actual, but it doesn't belong to this compiler's universe" + else "of " + expanded.getClass )) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 86ba3d2164..6b9537e27d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -142,7 +142,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { case Literal(Constant(s: String)) => s case Literal(Constant(d: Double)) => d case Literal(Constant(b: Boolean)) => b - case Literal(Constant(i: Int)) => new Fingerprint(i) + case Literal(Constant(i: Int)) => Fingerprint(i) } def pickle(macroImplRef: Tree): Tree = { @@ -464,9 +464,9 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { /** Describes the role that the macro expandee is performing. */ - type MacroRole = String - final def APPLY_ROLE: MacroRole = "APPLY_ROLE" - private val roleNames = Map(APPLY_ROLE -> "apply") + type MacroRole = scala.tools.nsc.typechecker.MacroRole + final def APPLY_ROLE = MacroRole.Apply + final def UNAPPLY_ROLE = MacroRole.Unapply /** Performs macro expansion: * @@ -482,9 +482,10 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * ========= Macro expansion ========= * * First of all `macroExpandXXX`: - * 1) If necessary desugars the `expandee` to fit into `macroExpand1` + * 1) If necessary desugars the `expandee` to fit into the default expansion scheme + * that is understood by `macroExpandWithRuntime` / `macroExpandWithoutRuntime` * - * Then `macroExpand1`: + * Then `macroExpandWithRuntime`: * 2) Checks whether the expansion needs to be delayed * 3) Loads macro implementation using `macroMirror` * 4) Synthesizes invocation arguments for the macro implementation @@ -532,26 +533,41 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { def summary() = s"expander = $this, expandee = ${showDetailed(expandee)}, desugared = ${if (expandee == desugared) () else showDetailed(desugared)}" if (macroDebugVerbose) println(s"macroExpand: ${summary()}") assert(allowExpandee(expandee), summary()) + linkExpandeeAndDesugared(expandee, desugared, role) val start = if (Statistics.canEnable) Statistics.startTimer(macroExpandNanos) else null if (Statistics.canEnable) Statistics.incCounter(macroExpandCount) try { - linkExpandeeAndDesugared(expandee, desugared, role) - macroExpand1(typer, desugared) match { - case Success(expanded) => - if (allowExpanded(expanded)) { - // also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc - val expanded1 = try onSuccess(duplicateAndKeepPositions(expanded)) finally popMacroContext() - if (!hasMacroExpansionAttachment(expanded1)) linkExpandeeAndExpanded(expandee, expanded1) - if (allowResult(expanded1)) expanded1 else onFailure(expanded) - } else { - typer.TyperErrorGen.MacroInvalidExpansionError(expandee, roleNames(role), allowedExpansions) - onFailure(expanded) + withInfoLevel(nodePrinters.InfoLevel.Quiet) { // verbose printing might cause recursive macro expansions + if (expandee.symbol.isErroneous || (expandee exists (_.isErroneous))) { + val reason = if (expandee.symbol.isErroneous) "not found or incompatible macro implementation" else "erroneous arguments" + macroLogVerbose(s"cancelled macro expansion because of $reason: $expandee") + onFailure(typer.infer.setError(expandee)) + } else try { + val expanded = { + val runtime = macroRuntime(expandee.symbol) + if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) + else macroExpandWithoutRuntime(typer, expandee) + } + expanded match { + case Success(expanded) => + if (allowExpanded(expanded)) { + // also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc + val expanded1 = try onSuccess(duplicateAndKeepPositions(expanded)) finally popMacroContext() + if (!hasMacroExpansionAttachment(expanded1)) linkExpandeeAndExpanded(expandee, expanded1) + if (allowResult(expanded1)) expanded1 else onFailure(expanded) + } else { + typer.TyperErrorGen.MacroInvalidExpansionError(expandee, role.name, allowedExpansions) + onFailure(expanded) + } + case Fallback(fallback) => onFallback(fallback) + case Delayed(delayed) => onDelayed(delayed) + case Skipped(skipped) => onSkipped(skipped) + case Failure(failure) => onFailure(failure) } - case Fallback(fallback) => onFallback(fallback) - case Delayed(delayed) => onDelayed(delayed) - case Skipped(skipped) => onSkipped(skipped) - case Failure(failure) => onFailure(failure) + } catch { + case typer.TyperErrorGen.MacroExpansionException => onFailure(expandee) + } } } finally { if (Statistics.canEnable) Statistics.stopTimer(macroExpandNanos, start) @@ -622,8 +638,21 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { expander(expandee) } - /** Captures statuses of macro expansions performed by `macroExpand1'. + /** Expands a term macro used in unapply role as `u.Quasiquote(StringContext("", "")).q.unapply(x)` in `case q"$x" => ...`. + * @see MacroExpander */ + def macroExpandUnapply(typer: Typer, original: Tree, fun: Tree, unapply: Symbol, args: List[Tree], mode: Mode, pt: Type) = { + val expandee = treeCopy.Apply(original, gen.mkAttributedSelect(fun, unapply), args) + object expander extends TermMacroExpander(UNAPPLY_ROLE, typer, expandee, mode, pt) { + override def allowedExpansions: String = "unapply trees" + override def allowExpandee(expandee: Tree) = expandee.isInstanceOf[Apply] + private def unsupported(what: String) = abort("unapply macros currently don't support " + what) + override def onFallback(fallback: Tree) = unsupported("fallback") + override def onDelayed(delayed: Tree) = unsupported("advanced interaction with type inference") + } + expander(original) + } + private sealed abstract class MacroStatus(val result: Tree) private case class Success(expanded: Tree) extends MacroStatus(expanded) private case class Fallback(fallback: Tree) extends MacroStatus(fallback) { currentRun.seenMacroExpansionsFallingBack = true } @@ -632,28 +661,6 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { private case class Failure(failure: Tree) extends MacroStatus(failure) private def Delay(expanded: Tree) = Delayed(expanded) private def Skip(expanded: Tree) = Skipped(expanded) - private def Cancel(expandee: Tree) = Failure(expandee) - - /** Does the same as `macroExpand`, but without typechecking the expansion - * Meant for internal use within the macro infrastructure, don't use it elsewhere. - */ - private def macroExpand1(typer: Typer, expandee: Tree): MacroStatus = { - // verbose printing might cause recursive macro expansions, so I'm shutting it down here - withInfoLevel(nodePrinters.InfoLevel.Quiet) { - if (expandee.symbol.isErroneous || (expandee exists (_.isErroneous))) { - val reason = if (expandee.symbol.isErroneous) "not found or incompatible macro implementation" else "erroneous arguments" - macroLogVerbose(s"cancelled macro expansion because of $reason: $expandee") - Cancel(typer.infer.setError(expandee)) - } - else try { - val runtime = macroRuntime(expandee.symbol) - if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) - else macroExpandWithoutRuntime(typer, expandee) - } catch { - case typer.TyperErrorGen.MacroExpansionException => Failure(expandee) - } - } - } /** Expands a macro when a runtime (i.e. the macro implementation) can be successfully loaded * Meant for internal use within the macro infrastructure, don't use it elsewhere. @@ -804,7 +811,7 @@ object MacrosStats { val macroExpandNanos = Statistics.newSubTimer("time spent in macroExpand", typerNanos) } -class Fingerprint(val value: Int) extends AnyVal { +class Fingerprint private[Fingerprint](val value: Int) extends AnyVal { def paramPos = { assert(isTag, this); value } def isTag = value >= 0 def isOther = this == Other @@ -819,8 +826,18 @@ class Fingerprint(val value: Int) extends AnyVal { } object Fingerprint { + def apply(value: Int) = new Fingerprint(value) def Tagged(tparamPos: Int) = new Fingerprint(tparamPos) val Other = new Fingerprint(-1) val LiftedTyped = new Fingerprint(-2) val LiftedUntyped = new Fingerprint(-3) } + +class MacroRole private[MacroRole](val name: String) extends AnyVal { + override def toString = name +} + +object MacroRole { + val Apply = new MacroRole("apply") + val Unapply = new MacroRole("unapply") +} diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 0ae68d2ba1..1a9a30c2ad 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3380,8 +3380,9 @@ trait Typers extends Adaptations with Tags { if (!tree.isErrorTyped) setError(tree) else tree // @H change to setError(treeCopy.Apply(tree, fun, args)) - case otpe if mode.inPatternMode && unapplyMember(otpe).exists => - doTypedUnapply(tree, fun0, fun, args, mode, pt) + case ExtractorType(unapply) if mode.inPatternMode => + if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(this, tree, fun, unapply, args, mode, pt) + else doTypedUnapply(tree, fun0, fun, args, mode, pt) case _ => if (treeInfo.isMacroApplication(tree)) duplErrorTree(MacroTooManyArgumentListsError(tree, fun.symbol)) diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index af3f772f79..47c859bb5c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -51,6 +51,14 @@ trait Unapplies extends ast.TreeDSL case NoSymbol => tp member nme.unapplySeq case unapp => unapp } + + object ExtractorType { + def unapply(tp: Type): Option[Symbol] = { + val member = unapplyMember(tp) + if (member.exists) Some(member) else None + } + } + /** returns unapply member's parameter type. */ def unapplyParameterType(extractor: Symbol) = extractor.tpe.params match { case p :: Nil => p.tpe.typeSymbol @@ -142,7 +150,7 @@ trait Unapplies extends ast.TreeDSL ModuleDef( Modifiers(cdef.mods.flags & AccessFlags | SYNTHETIC, cdef.mods.privateWithin), cdef.name.toTermName, - Template(parents, emptyValDef, NoMods, Nil, body, cdef.impl.pos.focus)) + gen.mkTemplate(parents, emptyValDef, NoMods, Nil, body, cdef.impl.pos.focus)) } private val caseMods = Modifiers(SYNTHETIC | CASE) diff --git a/src/compiler/scala/tools/nsc/util/CharArrayReader.scala b/src/compiler/scala/tools/nsc/util/CharArrayReader.scala index 5c6f525c6f..f116e4af34 100644 --- a/src/compiler/scala/tools/nsc/util/CharArrayReader.scala +++ b/src/compiler/scala/tools/nsc/util/CharArrayReader.scala @@ -8,15 +8,7 @@ package util import scala.reflect.internal.Chars._ -abstract class CharArrayReader { self => - - val buf: Array[Char] - - def decodeUni: Boolean = true - - /** An error routine to call on bad unicode escapes \\uxxxx. */ - protected def error(offset: Int, msg: String): Unit - +trait CharArrayReaderData { /** the last read character */ var ch: Char = _ @@ -29,7 +21,26 @@ abstract class CharArrayReader { self => /** The start offset of the line before the current one */ var lastLineStartOffset: Int = 0 - private var lastUnicodeOffset = -1 + protected var lastUnicodeOffset = -1 + + def copyFrom(cd: CharArrayReaderData): this.type = { + this.ch = cd.ch + this.charOffset = cd.charOffset + this.lineStartOffset = cd.lineStartOffset + this.lastLineStartOffset = cd.lastLineStartOffset + this.lastUnicodeOffset = cd.lastUnicodeOffset + this + } +} + +abstract class CharArrayReader extends CharArrayReaderData { self => + + val buf: Array[Char] + + def decodeUni: Boolean = true + + /** An error routine to call on bad unicode escapes \\uxxxx. */ + protected def error(offset: Int, msg: String): Unit /** Is last character a unicode escape \\uxxxx? */ def isUnicodeEscape = charOffset == lastUnicodeOffset diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 5a0ff4f6db..ad1d4c896b 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.reflect.macros.compiler.DefaultMacroCompiler +import scala.tools.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. @@ -22,6 +22,8 @@ trait FastTrack { new { val c: c0.type = c0 } with Taggers private implicit def context2macroimplementations(c0: MacroContext): MacroImplementations { val c: c0.type } = new { val c: c0.type = c0 } with MacroImplementations + private implicit def context2quasiquote(c0: MacroContext): QuasiquoteImpls { val c: c0.type } = + new { val c: c0.type = c0 } with QuasiquoteImpls private def make(sym: Symbol)(pf: PartialFunction[Applied, MacroContext => Tree]) = sym -> new FastTrackEntry(pf) @@ -41,6 +43,8 @@ trait FastTrack { make( materializeTypeTag) { case Applied(_, ttag :: Nil, (u :: _) :: _) => _.materializeTypeTag(u, EmptyTree, ttag.tpe, concrete = true) }, make( ApiUniverseReify) { case Applied(_, ttag :: Nil, (expr :: _) :: _) => c => c.materializeExpr(c.prefix.tree, EmptyTree, expr) }, make( StringContext_f) { case Applied(Select(Apply(_, ps), _), _, args) => c => c.macro_StringInterpolation_f(ps, args.flatten, c.expandee.pos) }, - make(ReflectRuntimeCurrentMirror) { case _ => c => currentMirror(c).tree } + make(ReflectRuntimeCurrentMirror) { case _ => c => currentMirror(c).tree }, + make( QuasiquoteClass_api_apply) { case _ => _.expandQuasiquote }, + make(QuasiquoteClass_api_unapply) { case _ => _.expandQuasiquote } ) } diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index c53d10bd87..afaca3396c 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -217,7 +217,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => val moduledef = ModuleDef( obj, - Template( + gen.mkTemplate( List(TypeTree(ObjectTpe)), emptyValDef, NoMods, diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala b/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala new file mode 100644 index 0000000000..9d171d52d2 --- /dev/null +++ b/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala @@ -0,0 +1,187 @@ +package scala.tools.reflect +package quasiquotes + +import scala.collection.{immutable, mutable} +import scala.reflect.internal.Flags._ + +class Cardinality private[Cardinality](val value: Int) extends AnyVal { + def pred = { assert(value - 1 >= 0); new Cardinality(value - 1) } + def succ = new Cardinality(value + 1) + override def toString = if (value == 0) "no dots" else "." * (value + 1) +} + +object Cardinality { + val NoDot = new Cardinality(0) + val DotDot = new Cardinality(1) + val DotDotDot = new Cardinality(2) + object Dot { def unapply(card: Cardinality) = card != 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 Cardinality._ + import definitions._ + import universeTypes._ + + /** Location characterizes a kind of a non-terminal in Scala syntax where something is going to be spliced. + * A location is typically associated with a type of the things that can be spliced there. + * Associated type might be different from an actual tpe of a splicee due to lifting. + * This is the first pillar of modularity in the quasiquote reifier. + */ + sealed abstract class Location(val tpe: Type) + case object UnknownLocation extends Location(NoType) + case class TreeLocation(override val tpe: Type) extends Location(tpe) + case object NameLocation extends Location(nameType) + case object ModsLocation extends Location(modsType) + case object FlagsLocation extends Location(flagsType) + case object SymbolLocation extends Location(symbolType) + case class IterableLocation(card: Cardinality, sublocation: TreeLocation) extends Location(NoType) { + override val tpe = { + def loop(n: Cardinality, tpe: Type): Type = + if (n == NoDot) tpe + else appliedType(IterableClass.toType, List(loop(n.pred, tpe))) + loop(card, sublocation.tpe) + } + } + + /** Hole type describes location, cardinality and a pre-reification routine associated with a hole. + * An interesting thing about HoleType is that it can be completely inferred from the type of the splicee. + * This is the second pillar of modularity in the quasiquote reifier. + */ + case class HoleType(preprocessor: Tree => Tree, location: Location, cardinality: Cardinality) { + def makeHole(tree: Tree) = Hole(preprocessor(tree), location, cardinality) + } + object HoleType { + def unapply(tpe: Type): Option[HoleType] = tpe match { + case NativeType(holeTpe) => Some(holeTpe) + case LiftableType(holeTpe) => Some(holeTpe) + case IterableTreeType(holeTpe) => Some(holeTpe) + case IterableLiftableType(holeTpe) => Some(holeTpe) + case _ => None + } + + trait HoleTypeExtractor { + def unapply(tpe: Type): Option[HoleType] = { + for { + preprocessor <- this.preprocessor(tpe) + location <- this.location(tpe) + cardinality <- Some(this.cardinality(tpe)) + } yield HoleType(preprocessor, location, cardinality) + } + def preprocessor(tpe: Type): Option[Tree => Tree] + def location(tpe: Type): Option[Location] + def cardinality(tpe: Type): Cardinality = parseCardinality(tpe)._1 + + def lifter(tpe: Type): Option[Tree => Tree] = { + val lifterTpe = appliedType(LiftableClass.toType, List(tpe)) + val lifter = c.inferImplicitValue(lifterTpe, silent = true) + if (lifter != EmptyTree) Some(tree => { + val lifted = Apply(lifter, List(u, tree)) + val targetType = Select(u, tpnme.Tree) + atPos(tree.pos)(TypeApply(Select(lifted, nme.asInstanceOf_), List(targetType))) + }) else None + } + + def iterator(tpe: Type)(elementTransform: Tree => Tree): Option[Tree => Tree] = { + def reifyIterable(tree: Tree, n: Cardinality): Tree = { + def loop(tree: Tree, n: Cardinality) = + if (n == NoDot) elementTransform(tree) + else { + val x: TermName = c.freshName() + val wrapped = reifyIterable(Ident(x), n.pred) + val xToWrapped = Function(List(ValDef(Modifiers(PARAM), x, TypeTree(), EmptyTree)), wrapped) + Select(Apply(Select(tree, nme.map), List(xToWrapped)), nme.toList) + } + if (tree.tpe != null && (tree.tpe <:< listTreeType || tree.tpe <:< listListTreeType)) tree + else atPos(tree.pos)(loop(tree, n)) + } + val (card, elementTpe) = parseCardinality(tpe) + if (card != NoDot) Some(reifyIterable(_, card)) else None + } + } + + object NativeType extends HoleTypeExtractor { + def preprocessor(tpe: Type) = Some(identity) + def location(tpe: Type) = { + if (tpe <:< treeType) Some(TreeLocation(tpe)) + else if (tpe <:< nameType) Some(NameLocation) + else if (tpe <:< modsType) Some(ModsLocation) + else if (tpe <:< flagsType) Some(FlagsLocation) + else if (tpe <:< symbolType) Some(SymbolLocation) + else None + } + } + + object LiftableType extends HoleTypeExtractor { + def preprocessor(tpe: Type) = lifter(tpe) + def location(tpe: Type) = Some(TreeLocation(treeType)) + } + + object IterableTreeType extends HoleTypeExtractor { + def preprocessor(tpe: Type) = iterator(tpe)(identity) + def location(tpe: Type) = { + val (card, elementTpe) = parseCardinality(tpe) + if (card != NoDot && elementTpe <:< treeType) Some(IterableLocation(card, TreeLocation(elementTpe))) + else None + } + } + + object IterableLiftableType extends HoleTypeExtractor { + def preprocessor(tpe: Type) = { + val (_, elementTpe) = parseCardinality(tpe) + for { + lifter <- this.lifter(elementTpe) + iterator <- this.iterator(tpe)(lifter) + } yield iterator + } + def location(tpe: Type) = Some(IterableLocation(cardinality(tpe), TreeLocation(treeType))) + } + } + + /** Hole encapsulates information about splices in quasiquotes. + * It packs together a cardinality of a splice, a splicee (possibly preprocessed) + * and the description of the location in Scala syntax where the splicee can be spliced. + * This is the third pillar of modularity in the quasiquote reifier. + */ + case class Hole(tree: Tree, location: Location, cardinality: Cardinality) + + object Hole { + def apply(splicee: Tree, holeCard: Cardinality): Hole = { + if (splicee.tpe == null) return new Hole(splicee, UnknownLocation, holeCard) + val (spliceeCard, elementTpe) = parseCardinality(splicee.tpe) + def cantSplice() = { + val holeCardMsg = if (holeCard != NoDot) s" with $holeCard" else "" + val action = "splice " + splicee.tpe + holeCardMsg + val suggestCard = holeCard != spliceeCard || holeCard != NoDot + val spliceeCardMsg = if (holeCard != spliceeCard && spliceeCard != NoDot) s"using $spliceeCard" else "omitting the dots" + val cardSuggestion = if (suggestCard) spliceeCardMsg else "" + def canBeLifted(tpe: Type) = HoleType.LiftableType.unapply(tpe).nonEmpty + val suggestLifting = (holeCard == NoDot || spliceeCard != NoDot) && !(elementTpe <:< treeType) && !canBeLifted(elementTpe) + val liftedTpe = if (holeCard != NoDot) elementTpe else splicee.tpe + val liftSuggestion = if (suggestLifting) s"providing an implicit instance of Liftable[$liftedTpe]" else "" + val advice = List(cardSuggestion, liftSuggestion).filter(_ != "").mkString(" or ") + c.abort(splicee.pos, s"Can't $action, consider $advice") + } + val holeTpe = splicee.tpe match { + case _ if holeCard != spliceeCard => cantSplice() + case HoleType(holeTpe) => holeTpe + case _ => cantSplice() + } + holeTpe.makeHole(splicee) + } + } + + def parseCardinality(tpe: Type): (Cardinality, Type) = { + if (tpe != null && isIterableType(tpe)) { + val (card, innerTpe) = parseCardinality(tpe.typeArguments.head) + (card.succ, innerTpe) + } else (NoDot, tpe) + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala new file mode 100644 index 0000000000..9a6ba56c18 --- /dev/null +++ b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala @@ -0,0 +1,134 @@ +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} +import scala.collection.mutable.ListBuffer + +/** 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._ + + abstract class Parser extends { + val global: self.global.type = self.global + } with ScalaParser { + /** Wraps given code to obtain a desired parser mode. + * This way we can just re-use standard parser entry point. + */ + def wrapCode(code: String): String = + s"object wrapper { self => $EOL $code $EOL }" + + def unwrapTree(wrappedTree: Tree): Tree = { + val PackageDef(_, List(ModuleDef(_, _, Template(_, _, _ :: parsed)))) = wrappedTree + parsed match { + case tree :: Nil => tree + case stats :+ tree => Block(stats, tree) + } + } + + def parse(code: String): Tree = { + try { + val wrapped = wrapCode(code) + debug(s"wrapped code\n=${wrapped}\n") + val file = new BatchSourceFile(nme.QUASIQUOTE_FILE, wrapped) + val tree = new QuasiquoteParser(file).parse() + unwrapTree(tree) + } catch { + case mi: MalformedInput => c.abort(c.macroApplication.pos, s"syntax error: ${mi.msg}") + } + } + + class QuasiquoteParser(source0: SourceFile) extends SourceFileParser(source0) { + override val treeBuilder = new ParserTreeBuilder { + // q"(..$xs)" + override def makeTupleTerm(trees: List[Tree], flattenUnary: Boolean): Tree = + Apply(Ident(nme.QUASIQUOTE_TUPLE), trees) + + // tq"(..$xs)" + override def makeTupleType(trees: List[Tree], flattenUnary: Boolean): Tree = + AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), trees) + + // q"{ $x }" + override def makeBlock(stats: List[Tree]): Tree = stats match { + case (head @ Ident(name)) :: Nil if holeMap.contains(name) => Block(Nil, head) + case _ => super.makeBlock(stats) + } + } + import treeBuilder.{global => _, _} + + // q"def foo($x)" + override def allowTypelessParams = true + + // q"foo match { case $x }" + override def caseClause(): CaseDef = + if (isHole && lookingAhead { in.token == CASE || in.token == RBRACE || in.token == SEMI }) { + val c = makeCaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Ident(ident()))), EmptyTree, EmptyTree) + while (in.token == SEMI) in.nextToken() + c + } else + super.caseClause() + + def isHole = isIdent && holeMap.contains(in.name) + + 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 isDclIntro: Boolean = super.isDclIntro || (isHole && lookingAhead { isDclIntro }) + + // $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 { in.token == AT || isModifier || isDefIntro || isIdent} => + val ann = Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(in.name.toString)))) + in.nextToken() + ann :: readAnnots(annot) + case _ => + Nil + } + } + } + + object TermParser extends Parser + + object CaseParser extends Parser { + override def wrapCode(code: String) = super.wrapCode("something match { case " + code + " }") + + override def unwrapTree(wrappedTree: Tree): Tree = { + val Match(_, head :: tail) = super.unwrapTree(wrappedTree) + if (tail.nonEmpty) + c.abort(c.macroApplication.pos, "Can't parse more than one casedef, consider generating a match tree instead") + head + } + } + + object PatternParser extends Parser { + override def wrapCode(code: String) = super.wrapCode("something match { case " + code + " => }") + + override def unwrapTree(wrappedTree: Tree): Tree = { + val Match(_, List(CaseDef(pat, _, _))) = super.unwrapTree(wrappedTree) + pat + } + } + + object TypeParser extends Parser { + override def wrapCode(code: String) = super.wrapCode("type T = " + code) + + override def unwrapTree(wrappedTree: Tree): Tree = { + val TypeDef(_, _, _, rhs) = super.unwrapTree(wrappedTree) + rhs + } + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala new file mode 100644 index 0000000000..b680c25f76 --- /dev/null +++ b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala @@ -0,0 +1,123 @@ +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 Cardinality._ + + // Step 1: Transform Scala source with holes into vanilla Scala source + + lazy val holeMap = new HoleMap() + lazy val code = { + val sb = new StringBuilder() + val sessionSuffix = randomUUID().toString.replace("-", "").substring(0, 8) + "$" + + foreach2(args, parts.init) { (tree, p) => + val (part, cardinality) = parseDots(p) + val placeholderName = c.freshName(TermName(nme.QUASIQUOTE_PREFIX + sessionSuffix)) + sb.append(part) + sb.append(placeholderName) + holeMap(placeholderName) = Hole(tree, cardinality) + } + sb.append(parts.last) + + sb.toString + } + + class HoleMap { + private val underlying = mutable.ListMap[String, Hole]() + private val accessed = mutable.Set[String]() + def unused: Set[Name] = (underlying.keys.toSet -- accessed).map(TermName(_)) + def contains(key: Name) = underlying.contains(key.toString) + def apply(key: Name) = { + val s = key.toString + accessed += s + underlying(s) + } + def update(key: Name, hole: Hole) = { + underlying += key.toString -> hole + } + def get(key: Name) = { + val s = key.toString + accessed += s + underlying.get(s) + } + } + + // Step 2: Transform vanilla Scala AST into an AST with holes + + trait HolePlaceholder { + def matching: PartialFunction[Any, Name] + def unapply(scrutinee: Any): Option[(Tree, Location, Cardinality)] = { + val name = matching.lift(scrutinee) + name.flatMap { holeMap.get(_).map { case Hole(repr, loc, card) => (repr, loc, card) } } + } + } + + 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 + case ValDef(_, name, TypeTree(), EmptyTree) => name + } + } + + object ModsPlaceholder extends HolePlaceholder { + def matching = { + case Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(s: String)))) => TermName(s) + } + } + + object AnnotPlaceholder { + def unapply(tree: Tree): Option[(Tree, Location, Cardinality, List[Tree])] = tree match { + case Apply(Select(New(Placeholder(tree, loc, card)), nme.CONSTRUCTOR), args) => Some(tree, loc, card, args) + case _ => None + } + } + + object TuplePlaceholder { + def unapply(tree: Tree): Option[List[Tree]] = tree match { + case Apply(Ident(nme.QUASIQUOTE_TUPLE), args) => Some(args) + case _ => None + } + } + + object TupleTypePlaceholder { + def unapply(tree: Tree): Option[List[Tree]] = tree match { + case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) => Some(args) + case _ => None + } + } + + object SymbolPlaceholder { + def unapply(scrutinee: Any): Option[Tree] = scrutinee match { + case Placeholder(tree, SymbolLocation, _) => Some(tree) + case _ => None + } + } + + object CasePlaceholder { + def unapply(tree: Tree): Option[(Tree, Location, Cardinality)] = tree match { + case CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Placeholder(tree, location, card))), EmptyTree, EmptyTree) => Some((tree, location, card)) + case _ => None + } + } + + object ClassPlaceholder { + def unapply(tree: Tree): Option[Tree] = tree match { + case ClassDef(_, _, _, _) => Some(tree) + case _ => None + } + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala b/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala new file mode 100644 index 0000000000..fe954e0bfd --- /dev/null +++ b/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala @@ -0,0 +1,51 @@ +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) = c.macroApplication match { + case Apply(Select(Select(Apply(Select(universe0, _), List(Apply(_, parts0))), interpolator0), method0), args0) => + val parts1 = parts0.map { + case Literal(Constant(s: String)) => s + 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 other => global.abort(s"Unknown quasiquote flavor: $other") + } + (universe0, args0, parts1, parse0, reify0) + 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"\ncode to parse=\n$code\n") + val tree = parse(code) + debug(s"parsed tree\n=${tree}\n=${showRaw(tree)}\n") + val reified = reify(tree) + debug(s"reified tree\n=${reified}\n=${showRaw(reified)}\n") + reified + } +} diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala new file mode 100644 index 0000000000..ec113036a3 --- /dev/null +++ b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala @@ -0,0 +1,290 @@ +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.SyntacticClassDef + import global.treeInfo._ + import global.definitions._ + import Cardinality._ + import universeTypes._ + + abstract class Reifier extends { + val global: self.global.type = self.global + } with ReflectReifier { + val reifee = EmptyTree + val universe = self.universe + val mirror = EmptyTree + val concrete = false + lazy val typer = throw new UnsupportedOperationException + + def isReifyingExpressions: Boolean + def isReifyingPatterns: Boolean = !isReifyingExpressions + def action = if (isReifyingExpressions) "splice" else "extract" + def holesHaveTypes = isReifyingExpressions + + def reifyFillingHoles(tree: Tree): Tree = { + val reified = reifyTree(tree) + holeMap.unused.foreach { hole => + c.abort(holeMap(hole).tree.pos, s"Don't know how to $action here") + } + reified + } + + override def reifyTree(tree: Tree): Tree = { + val reified = + reifyTreePlaceholder(tree) orElse + reifyTreeSyntactically(tree) + //println(s"reified ${showRaw(tree)} as $reified") + reified + } + + def reifyTreePlaceholder(tree: Tree): Tree = tree match { + case Placeholder(tree, TreeLocation(_), _) if isReifyingExpressions => tree + case Placeholder(tree, _, NoDot) if isReifyingPatterns => tree + case Placeholder(tree, _, card @ Dot()) => c.abort(tree.pos, s"Can't $action with $card here") + case TuplePlaceholder(args) => reifyTuple(args) + case TupleTypePlaceholder(args) => reifyTupleType(args) + case CasePlaceholder(tree, location, _) => reifyCase(tree, location) + case ClassPlaceholder(tree) => reifyClass(tree) + case _ => EmptyTree + } + + override def reifyName(name: Name): Tree = name match { + case Placeholder(tree, location, _) => + if (holesHaveTypes && !(location.tpe <:< nameType)) c.abort(tree.pos, s"$nameType expected but ${location.tpe} found") + tree + case _ => + super.reifyName(name) + } + + def reifyCase(tree: Tree, location: Location) = { + if (holesHaveTypes && !(location.tpe <:< caseDefType)) c.abort(tree.pos, s"$caseDefType expected but ${location.tpe} found") + tree + } + + def reifyTuple(args: List[Tree]) = args match { + case Nil => reify(Literal(Constant(()))) + case List(hole @ Placeholder(_, _, NoDot)) => reify(hole) + case List(Placeholder(_, _, _)) => reifyBuildCall(nme.TupleN, args) + // in a case we only have one element tuple without + // any cardinality annotations this means that this is + // just an expression wrapped in parentheses + case List(other) => reify(other) + case _ => reifyBuildCall(nme.TupleN, args) + } + + def reifyTupleType(args: List[Tree]) = args match { + case Nil => reify(Select(Ident(nme.scala_), tpnme.Unit)) + case List(hole @ Placeholder(_, _, NoDot)) => reify(hole) + case List(Placeholder(_, _, _)) => reifyBuildCall(nme.TupleTypeN, args) + case List(other) => reify(other) + case _ => reifyBuildCall(nme.TupleTypeN, args) + } + + def reifyClass(tree: Tree) = { + val SyntacticClassDef(mods, name, tparams, constrmods, argss, parents, selfval, body) = tree + reifyBuildCall(nme.SyntacticClassDef, mods, name, tparams, constrmods, argss, parents, selfval, body) + } + + /** 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: + * + * reifyMultiCardinalityList(lst) { + * // first we define patterns that extract high-cardinality holeMap (currently ..) + * case Placeholder(CorrespondsTo(tree, tpe)) if tpe <:< iterableTreeType => 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) + * > reifyMultiCardinalityList(lst) { ... } { ... } + * q"List($foo, $bar) ++ ${holeMap(qq$f3948f9s$1).tree}" + */ + def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree): Tree + + /** Reifies arbitrary list filling ..$x and ...$y holeMap when they are put + * in the correct position. Fallbacks to regular reification for non-high cardinality + * elements. + */ + override def reifyList(xs: List[Any]): Tree = reifyMultiCardinalityList(xs) { + case Placeholder(tree, _, DotDot) => tree + case CasePlaceholder(tree, _, DotDot) => tree + case List(Placeholder(tree, _, DotDotDot)) => tree + } { + reify(_) + } + + def reifyAnnotList(annots: List[Tree]): Tree + + def ensureNoExplicitFlags(m: Modifiers, pos: Position) = + if ((m.flags & ExplicitFlags) != 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(universe, nme.build), name), args.toList) + } + + class ApplyReifier extends Reifier { + def isReifyingExpressions = true + + override def reifyTreeSyntactically(tree: Tree): Tree = tree match { + case Block(stats, p @ Placeholder(_, _, _)) => reifyBuildCall(nme.Block, stats :+ p) + case Apply(f, List(Placeholder(argss, _, DotDotDot))) => reifyCallWithArgss(f, argss) + case RefTree(qual, SymbolPlaceholder(tree)) => mirrorBuildCall(nme.RefTree, reify(qual), tree) + case _ => super.reifyTreeSyntactically(tree) + } + + def reifyCallWithArgss(f: Tree, argss: Tree) = { + val f1 = reifyTree(f) + val foldLeftF1 = Apply(TypeApply(Select(argss, nme.foldLeft), List(Select(u, tpnme.Tree))), List(f1)) + val uDotApply = Function( + List(gen.mkSyntheticParam(nme.x_1), gen.mkSyntheticParam(nme.x_2)), + Apply(Select(u, nme.Apply), List(Ident(nme.x_1), Ident(nme.x_2)))) + Apply(foldLeftF1, List(uDotApply)) + } + + override def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree): Tree = xs match { + case Nil => mkList(Nil) + case _ => + def reifyGroup(group: List[T]): 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 reifyAnnotList(annots: List[Tree]): Tree = reifyMultiCardinalityList(annots) { + case AnnotPlaceholder(tree, _, DotDot, args) => + val x: TermName = c.freshName() + val xToAnnotationCtor = Function( + List(ValDef(Modifiers(PARAM), x, TypeTree(), EmptyTree)), + mirrorBuildCall(nme.mkAnnotationCtor, Ident(x), reify(args))) + Apply(Select(tree, nme.map), List(xToAnnotationCtor)) + } { + case AnnotPlaceholder(tree, _: TreeLocation, _, args) => + mirrorBuildCall(nme.mkAnnotationCtor, tree, reify(args)) + case other => reify(other) + } + + override def reifyModifiers(m: Modifiers) = { + val (modsPlaceholders, annots) = m.annotations.partition { + case ModsPlaceholder(_, _, _) => true + case _ => false + } + val (mods, flags) = modsPlaceholders.map { + case ModsPlaceholder(tree, location, card) => (tree, location) + }.partition { case (tree, location) => + location match { + case ModsLocation => true + case FlagsLocation => false + case _ => c.abort(tree.pos, s"$flagsType or $modsType expected but ${tree.tpe} found") + } + } + mods match { + case (tree, _) :: Nil => + if (flags.nonEmpty) c.abort(flags(0)._1.pos, "Can't splice flags together with modifiers, consider merging flags into modifiers") + if (annots.nonEmpty) c.abort(tree.pos, "Can't splice modifiers together with annotations, consider merging annotations into modifiers") + ensureNoExplicitFlags(m, tree.pos) + tree + case _ :: (second, _) :: Nil => + c.abort(second.pos, "Can't splice multiple modifiers, consider merging them into a single modifiers instance") + case _ => + val baseFlags = reifyBuildCall(nme.flagsFromBits, m.flags) + val reifiedFlags = flags.foldLeft[Tree](baseFlags) { case (flag, (tree, _)) => Apply(Select(flag, nme.OR), List(tree)) } + mirrorFactoryCall(nme.Modifiers, reifiedFlags, reify(m.privateWithin), reifyAnnotList(annots)) + } + } + } + + class UnapplyReifier extends Reifier { + def isReifyingExpressions = false + + override def reifyTreeSyntactically(tree: Tree): Tree = tree match { + case treeInfo.Applied(fun, Nil, argss) if fun != tree && !tree.isInstanceOf[AppliedTypeTree] => + reifyBuildCall(nme.Applied, fun, argss) + case treeInfo.Applied(fun, targs, argss) if fun != tree & !tree.isInstanceOf[AppliedTypeTree] => + mirrorBuildCall(nme.Applied, reifyBuildCall(nme.TypeApplied, fun, targs), reifyList(argss)) + case _ => + super.reifyTreeSyntactically(tree) + } + + override def scalaFactoryCall(name: String, args: Tree*): Tree = + call("scala." + name, args: _*) + + override def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree) = xs match { + case init :+ last if fill.isDefinedAt(last) => + init.foldRight[Tree](fill(last)) { (el, rest) => + val cons = Select(Select(Select(Ident(nme.scala_), nme.collection), nme.immutable), nme.CONS) + Apply(cons, List(fallback(el), rest)) + } + case _ => + mkList(xs.map(fallback)) + } + + override def reifyAnnotList(annots: List[Tree]): Tree = reifyMultiCardinalityList(annots) { + case AnnotPlaceholder(tree, _, DotDot, Nil) => tree + } { + case AnnotPlaceholder(tree, _, NoDot, Nil) => tree + case AnnotPlaceholder(tree, _, NoDot, args) => + val selectCONSTRUCTOR = Apply(Select(u, nme.Select), List(Apply(Select(u, nme.New), List(tree)), Select(Select(u, nme.nmeNme), nme.nmeCONSTRUCTOR))) + Apply(Select(u, nme.Apply), List(selectCONSTRUCTOR, reify(args))) + case other => + reify(other) + } + + override def reifyModifiers(m: Modifiers) = { + val mods = m.annotations.collect { case ModsPlaceholder(tree, _, _) => tree } + mods match { + case tree :: Nil => + if (m.annotations.length != 1) c.abort(tree.pos, "Can't extract modifiers together with annotations, consider extracting just modifiers") + ensureNoExplicitFlags(m, tree.pos) + tree + case _ :: second :: rest => + c.abort(second.pos, "Can't extract multiple modifiers together, consider extracting a single modifiers instance") + case Nil => + mirrorFactoryCall(nme.Modifiers, reifyBuildCall(nme.FlagsAsBits, m.flags), + reify(m.privateWithin), reifyAnnotList(m.annotations)) + } + } + } +}
\ No newline at end of file |