diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-11-13 11:01:24 -0800 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-11-13 11:01:24 -0800 |
commit | 946b76ad8b31b1fd74e2f8e1972c4a9159ac690a (patch) | |
tree | 53f3f256ccd4dd84a89273a1e34cd2e956aaf6f7 | |
parent | 0e64a2e8254498155e243396e3e0fb184c86a563 (diff) | |
parent | 9484833838700e8db1b76081c32ee5e2bd9e8011 (diff) | |
download | scala-946b76ad8b31b1fd74e2f8e1972c4a9159ac690a.tar.gz scala-946b76ad8b31b1fd74e2f8e1972c4a9159ac690a.tar.bz2 scala-946b76ad8b31b1fd74e2f8e1972c4a9159ac690a.zip |
Merge pull request #3090 from densh/pull/undo-for
Add support for For loops to quasiquotes
33 files changed, 1046 insertions, 557 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala index 0020528c5b..6dda30b5e7 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala @@ -137,7 +137,7 @@ trait TreeDSL { def IF(tree: Tree) = new IfStart(tree, EmptyTree) def TRY(tree: Tree) = new TryStart(tree, Nil, EmptyTree) def BLOCK(xs: Tree*) = Block(xs.init.toList, xs.last) - def SOME(xs: Tree*) = Apply(SomeClass.companionSymbol, treeBuilder.makeTupleTerm(xs.toList, flattenUnary = true)) + def SOME(xs: Tree*) = Apply(SomeClass.companionSymbol, gen.mkTuple(xs.toList)) /** Typed trees from symbols. */ def REF(sym: Symbol) = gen.mkAttributedRef(sym) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 28b127698f..d4ac21a6b8 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -53,13 +53,6 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { NewFromConstructor(constructor, expr) } - // annotate the expression with @unchecked - def mkUnchecked(expr: Tree): Tree = atPos(expr.pos) { - // This can't be "Annotated(New(UncheckedClass), expr)" because annotations - // are very picky about things and it crashes the compiler with "unexpected new". - Annotated(New(scalaDot(UncheckedClass.name), Nil), expr) - } - // Builds a tree of the form "{ lhs = rhs ; lhs }" def mkAssignAndReturn(lhs: Symbol, rhs: Tree): Tree = { def lhsRef = if (lhs.owner.isClass) Select(This(lhs.owner), lhs) else Ident(lhs) @@ -263,7 +256,4 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { val stats1 = if (stats.isEmpty) List(Literal(Constant(()))) else stats mkNew(Nil, noSelfType, stats1, NoPosition, NoPosition) } - - def mkSyntheticParam(pname: TermName) = - ValDef(Modifiers(PARAM | SYNTHETIC), pname, TypeTree(), EmptyTree) } diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index cfa60cabc3..cd1869340a 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -861,7 +861,7 @@ self => atPos(start, in.skipToken()) { makeFunctionTypeTree(ts, typ()) } else { ts foreach checkNotByNameOrVarargs - val tuple = atPos(start) { makeTupleType(ts, flattenUnary = true) } + val tuple = atPos(start) { makeTupleType(ts) } infixTypeRest( compoundTypeRest( annotTypeRest( @@ -923,7 +923,7 @@ self => def simpleType(): Tree = { val start = in.offset simpleTypeRest(in.token match { - case LPAREN => atPos(start)(makeTupleType(inParens(types()), flattenUnary = true)) + case LPAREN => atPos(start)(makeTupleType(inParens(types()))) case USCORE => wildcardType(in.skipToken()) case _ => path(thisOK = false, typeOK = true) match { @@ -1394,9 +1394,9 @@ self => newLinesOpt() if (in.token == YIELD) { in.nextToken() - makeForYield(enums, expr()) + gen.mkFor(enums, gen.Yield(expr())) } else { - makeFor(enums, expr()) + gen.mkFor(enums, expr()) } } def adjustStart(tree: Tree) = @@ -1700,22 +1700,25 @@ self => * | val Pattern1 `=' Expr * }}} */ - def enumerators(): List[Enumerator] = { - val enums = new ListBuffer[Enumerator] - generator(enums, eqOK = false) + def enumerators(): List[Tree] = { + val enums = new ListBuffer[Tree] + enums ++= enumerator(isFirst = true) while (isStatSep) { in.nextToken() - if (in.token == IF) enums += makeFilter(in.offset, guard()) - else generator(enums, eqOK = true) + enums ++= enumerator(isFirst = false) } enums.toList } + def enumerator(isFirst: Boolean, allowNestedIf: Boolean = true): List[Tree] = + if (in.token == IF && !isFirst) makeFilter(in.offset, guard()) :: Nil + else generator(!isFirst, allowNestedIf) + /** {{{ * Generator ::= Pattern1 (`<-' | `=') Expr [Guard] * }}} */ - def generator(enums: ListBuffer[Enumerator], eqOK: Boolean) { + def generator(eqOK: Boolean, allowNestedIf: Boolean = true): List[Tree] = { val start = in.offset val hasVal = in.token == VAL if (hasVal) @@ -1733,13 +1736,22 @@ self => if (hasEq && eqOK) in.nextToken() else accept(LARROW) val rhs = expr() - enums += makeGenerator(r2p(start, point, in.lastOffset max start), pat, hasEq, rhs) - // why max above? IDE stress tests have shown that lastOffset could be less than start, + + def loop(): List[Tree] = + if (in.token != IF) Nil + else makeFilter(in.offset, guard()) :: loop() + + val tail = + if (allowNestedIf) loop() + else Nil + + // why max? IDE stress tests have shown that lastOffset could be less than start, // I guess this happens if instead if a for-expression we sit on a closing paren. - while (in.token == IF) enums += makeFilter(in.offset, guard()) + val genPos = r2p(start, point, in.lastOffset max start) + gen.mkGenerator(genPos, pat, hasEq, rhs) :: tail } - def makeFilter(start: Offset, tree: Tree) = Filter(r2p(start, tree.pos.point, tree.pos.end), tree) + def makeFilter(start: Offset, tree: Tree) = gen.Filter(tree).setPos(r2p(start, tree.pos.point, tree.pos.end)) /* -------- PATTERNS ------------------------------------------- */ @@ -2454,11 +2466,10 @@ self => EmptyTree } def mkDefs(p: Tree, tp: Tree, rhs: Tree): List[Tree] = { - val trees = - makePatDef(newmods, - if (tp.isEmpty) p - else Typed(p, tp) setPos (p.pos union tp.pos), - rhs) + val trees = { + val pat = if (tp.isEmpty) p else Typed(p, tp) setPos (p.pos union tp.pos) + gen.mkPatDef(newmods, pat, rhs) + } if (newmods.isDeferred) { trees match { case List(ValDef(_, _, _, EmptyTree)) => diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 28d5aefc2b..d88470bd5e 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -31,88 +31,6 @@ abstract class TreeBuilder { def convertToTypeName(t: Tree) = gen.convertToTypeName(t) - /** Convert all occurrences of (lower-case) variables in a pattern as follows: - * x becomes x @ _ - * x: T becomes x @ (_: T) - */ - object patvarTransformer extends Transformer { - override def transform(tree: Tree): Tree = tree match { - case Ident(name) if (treeInfo.isVarPattern(tree) && name != nme.WILDCARD) => - atPos(tree.pos)(Bind(name, atPos(tree.pos.focus) (Ident(nme.WILDCARD)))) - case Typed(id @ Ident(name), tpt) if (treeInfo.isVarPattern(id) && name != nme.WILDCARD) => - atPos(tree.pos.withPoint(id.pos.point)) { - Bind(name, atPos(tree.pos.withStart(tree.pos.point)) { - Typed(Ident(nme.WILDCARD), tpt) - }) - } - case Apply(fn @ Apply(_, _), args) => - treeCopy.Apply(tree, transform(fn), transformTrees(args)) - case Apply(fn, args) => - treeCopy.Apply(tree, fn, transformTrees(args)) - case Typed(expr, tpt) => - treeCopy.Typed(tree, transform(expr), tpt) - case Bind(name, body) => - treeCopy.Bind(tree, name, transform(body)) - case Alternative(_) | Star(_) => - super.transform(tree) - case _ => - tree - } - } - - /** Traverse pattern and collect all variable names with their types in buffer - * The variables keep their positions; whereas the pattern is converted to be - * synthetic for all nodes that contain a variable position. - */ - class GetVarTraverser extends Traverser { - val buf = new ListBuffer[(Name, Tree, Position)] - - def namePos(tree: Tree, name: Name): Position = - if (!tree.pos.isRange || name.containsName(nme.raw.DOLLAR)) tree.pos.focus - else { - val start = tree.pos.start - val end = start + name.decode.length - r2p(start, start, end) - } - - override def traverse(tree: Tree): Unit = { - def seenName(name: Name) = buf exists (_._1 == name) - def add(name: Name, t: Tree) = if (!seenName(name)) buf += ((name, t, namePos(tree, name))) - val bl = buf.length - - tree match { - case Bind(nme.WILDCARD, _) => - super.traverse(tree) - - case Bind(name, Typed(tree1, tpt)) => - val newTree = if (treeInfo.mayBeTypePat(tpt)) TypeTree() else tpt.duplicate - add(name, newTree) - traverse(tree1) - - case Bind(name, tree1) => - // can assume only name range as position, as otherwise might overlap - // with binds embedded in pattern tree1 - add(name, TypeTree()) - traverse(tree1) - - case _ => - super.traverse(tree) - } - if (buf.length > bl) - tree setPos tree.pos.makeTransparent - } - def apply(tree: Tree) = { - traverse(tree) - buf.toList - } - } - - /** Returns list of all pattern variables, possibly with their types, - * without duplicates - */ - private def getVariables(tree: Tree): List[(Name, Tree, Position)] = - new GetVarTraverser apply tree - def byNameApplication(tpe: Tree): Tree = AppliedTypeTree(rootScalaDot(tpnme.BYNAME_PARAM_CLASS_NAME), List(tpe)) def repeatedApplication(tpe: Tree): Tree = @@ -121,25 +39,12 @@ abstract class TreeBuilder { def makeImportSelector(name: Name, nameOffset: Int): ImportSelector = ImportSelector(name, nameOffset, name, nameOffset) - private def makeTuple(trees: List[Tree], isType: Boolean): Tree = { - val tupString = "Tuple" + trees.length - Apply(scalaDot(if (isType) newTypeName(tupString) else newTermName(tupString)), trees) - } - - def makeTupleTerm(trees: List[Tree], flattenUnary: Boolean): Tree = trees match { - case Nil => Literal(Constant(())) - case List(tree) if flattenUnary => tree - case _ => makeTuple(trees, isType = false) - } + def makeTupleTerm(elems: List[Tree]) = gen.mkTuple(elems) - def makeTupleType(trees: List[Tree], flattenUnary: Boolean): Tree = trees match { - case Nil => scalaUnitConstr - case List(tree) if flattenUnary => tree - case _ => AppliedTypeTree(scalaDot(newTypeName("Tuple" + trees.length)), trees) - } + def makeTupleType(elems: List[Tree]) = gen.mkTupleType(elems) def stripParens(t: Tree) = t match { - case Parens(ts) => atPos(t.pos) { makeTupleTerm(ts, flattenUnary = true) } + case Parens(ts) => atPos(t.pos) { makeTupleTerm(ts) } case _ => t } @@ -149,22 +54,6 @@ abstract class TreeBuilder { def makeSelfDef(name: TermName, tpt: Tree): ValDef = ValDef(Modifiers(PRIVATE), name, tpt, EmptyTree) - /** If tree is a variable pattern, return Some("its name and type"). - * Otherwise return none */ - private def matchVarPattern(tree: Tree): Option[(Name, Tree)] = { - def wildType(t: Tree): Option[Tree] = t match { - case Ident(x) if x.toTermName == nme.WILDCARD => Some(TypeTree()) - case Typed(Ident(x), tpt) if x.toTermName == nme.WILDCARD => Some(tpt) - case _ => None - } - tree match { - case Ident(name) => Some((name, TypeTree())) - case Bind(name, body) => wildType(body) map (x => (name, x)) - case Typed(Ident(name), tpt) => Some((name, tpt)) - case _ => None - } - } - /** Create tree representing (unencoded) binary operation expression or pattern. */ def makeBinop(isExpr: Boolean, left: Tree, op: TermName, right: Tree, opPos: Position): Tree = { def mkNamed(args: List[Tree]) = if (isExpr) args map treeInfo.assignmentToMaybeNamedArg else args @@ -214,173 +103,12 @@ abstract class TreeBuilder { /** Create block of statements `stats` */ def makeBlock(stats: List[Tree]): Tree = gen.mkBlock(stats) - def makeFilter(tree: Tree, condition: Tree, scrutineeName: String): Tree = { - val cases = List( - CaseDef(condition, EmptyTree, Literal(Constant(true))), - CaseDef(Ident(nme.WILDCARD), EmptyTree, Literal(Constant(false))) - ) - val matchTree = makeVisitor(cases, checkExhaustive = false, scrutineeName) - - atPos(tree.pos)(Apply(Select(tree, nme.withFilter), matchTree :: Nil)) - } - - /** Create tree for for-comprehension generator <val pat0 <- rhs0> */ - def makeGenerator(pos: Position, pat: Tree, valeq: Boolean, rhs: Tree): Enumerator = { - val pat1 = patvarTransformer.transform(pat) - val rhs1 = - if (valeq || treeInfo.isVarPatternDeep(pat)) rhs - else makeFilter(rhs, pat1.duplicate, nme.CHECK_IF_REFUTABLE_STRING) - - if (valeq) ValEq(pos, pat1, rhs1) - else ValFrom(pos, pat1, rhs1) - } - def makeParam(pname: TermName, tpe: Tree) = ValDef(Modifiers(PARAM), pname, tpe, EmptyTree) def makeSyntheticTypeParam(pname: TypeName, bounds: Tree) = TypeDef(Modifiers(DEFERRED | SYNTHETIC), pname, Nil, bounds) - abstract class Enumerator { def pos: Position } - case class ValFrom(pos: Position, pat: Tree, rhs: Tree) extends Enumerator - case class ValEq(pos: Position, pat: Tree, rhs: Tree) extends Enumerator - case class Filter(pos: Position, test: Tree) extends Enumerator - - /** Create tree for for-comprehension <for (enums) do body> or - * <for (enums) yield body> where mapName and flatMapName are chosen - * corresponding to whether this is a for-do or a for-yield. - * The creation performs the following rewrite rules: - * - * 1. - * - * for (P <- G) E ==> G.foreach (P => E) - * - * Here and in the following (P => E) is interpreted as the function (P => E) - * if P is a variable pattern and as the partial function { case P => E } otherwise. - * - * 2. - * - * for (P <- G) yield E ==> G.map (P => E) - * - * 3. - * - * for (P_1 <- G_1; P_2 <- G_2; ...) ... - * ==> - * G_1.flatMap (P_1 => for (P_2 <- G_2; ...) ...) - * - * 4. - * - * for (P <- G; E; ...) ... - * => - * for (P <- G.filter (P => E); ...) ... - * - * 5. For N < MaxTupleArity: - * - * for (P_1 <- G; P_2 = E_2; val P_N = E_N; ...) - * ==> - * for (TupleN(P_1, P_2, ... P_N) <- - * for (x_1 @ P_1 <- G) yield { - * val x_2 @ P_2 = E_2 - * ... - * val x_N & P_N = E_N - * TupleN(x_1, ..., x_N) - * } ...) - * - * If any of the P_i are variable patterns, the corresponding `x_i @ P_i' is not generated - * and the variable constituting P_i is used instead of x_i - * - * @param mapName The name to be used for maps (either map or foreach) - * @param flatMapName The name to be used for flatMaps (either flatMap or foreach) - * @param enums The enumerators in the for expression - * @param body The body of the for expression - */ - private def makeFor(mapName: TermName, flatMapName: TermName, enums: List[Enumerator], body: Tree): Tree = { - - /* make a closure pat => body. - * The closure is assigned a transparent position with the point at pos.point and - * the limits given by pat and body. - */ - def makeClosure(pos: Position, pat: Tree, body: Tree): Tree = { - def splitpos = wrappingPos(List(pat, body)).withPoint(pos.point).makeTransparent - matchVarPattern(pat) match { - case Some((name, tpt)) => - Function( - List(atPos(pat.pos) { ValDef(Modifiers(PARAM), name.toTermName, tpt, EmptyTree) }), - body) setPos splitpos - case None => - atPos(splitpos) { - makeVisitor(List(CaseDef(pat, EmptyTree, body)), checkExhaustive = false) - } - } - } - - /* Make an application qual.meth(pat => body) positioned at `pos`. - */ - def makeCombination(pos: Position, meth: TermName, qual: Tree, pat: Tree, body: Tree): Tree = - Apply(Select(qual, meth) setPos qual.pos, List(makeClosure(pos, pat, body))) setPos pos - - /* If `pat` is not yet a `Bind` wrap it in one with a fresh name */ - def makeBind(pat: Tree): Tree = pat match { - case Bind(_, _) => pat - case _ => Bind(freshTermName(), pat) setPos pat.pos - } - - /* A reference to the name bound in Bind `pat`. */ - def makeValue(pat: Tree): Tree = pat match { - case Bind(name, _) => Ident(name) setPos pat.pos.focus - } - - /* The position of the closure that starts with generator at position `genpos`. */ - def closurePos(genpos: Position) = { - val end = body.pos match { - case NoPosition => genpos.point - case bodypos => bodypos.end - } - r2p(genpos.start, genpos.point, end) - } - -// val result = - enums match { - case ValFrom(pos, pat, rhs) :: Nil => - makeCombination(closurePos(pos), mapName, rhs, pat, body) - case ValFrom(pos, pat, rhs) :: (rest @ (ValFrom(_, _, _) :: _)) => - makeCombination(closurePos(pos), flatMapName, rhs, pat, - makeFor(mapName, flatMapName, rest, body)) - case ValFrom(pos, pat, rhs) :: Filter(_, test) :: rest => - makeFor(mapName, flatMapName, - ValFrom(pos, pat, makeCombination(rhs.pos union test.pos, nme.withFilter, rhs, pat.duplicate, test)) :: rest, - body) - case ValFrom(pos, pat, rhs) :: rest => - val valeqs = rest.take(definitions.MaxTupleArity - 1).takeWhile(_.isInstanceOf[ValEq]) - assert(!valeqs.isEmpty) - val rest1 = rest.drop(valeqs.length) - val pats = valeqs map { case ValEq(_, pat, _) => pat } - val rhss = valeqs map { case ValEq(_, _, rhs) => rhs } - val defpat1 = makeBind(pat) - val defpats = pats map makeBind - val pdefs = (defpats, rhss).zipped flatMap makePatDef - val ids = (defpat1 :: defpats) map makeValue - val rhs1 = makeForYield( - List(ValFrom(pos, defpat1, rhs)), - Block(pdefs, atPos(wrappingPos(ids)) { makeTupleTerm(ids, flattenUnary = true) }) setPos wrappingPos(pdefs)) - val allpats = (pat :: pats) map (_.duplicate) - val vfrom1 = ValFrom(r2p(pos.start, pos.point, rhs1.pos.end), atPos(wrappingPos(allpats)) { makeTuple(allpats, isType = false) } , rhs1) - makeFor(mapName, flatMapName, vfrom1 :: rest1, body) - case _ => - EmptyTree //may happen for erroneous input - } -// println("made for "+result) -// result - } - - /** Create tree for for-do comprehension <for (enums) body> */ - def makeFor(enums: List[Enumerator], body: Tree): Tree = - makeFor(nme.foreach, nme.foreach, enums, body) - - /** Create tree for for-yield comprehension <for (enums) yield body> */ - def makeForYield(enums: List[Enumerator], body: Tree): Tree = - makeFor(nme.map, nme.flatMap, enums, body) - /** Create tree for a pattern alternative */ def makeAlternative(ts: List[Tree]): Tree = { def alternatives(t: Tree): List[Tree] = t match { @@ -390,21 +118,9 @@ abstract class TreeBuilder { Alternative(ts flatMap alternatives) } - /** Create visitor <x => x match cases> */ - def makeVisitor(cases: List[CaseDef], checkExhaustive: Boolean): Tree = - makeVisitor(cases, checkExhaustive, "x$") - - /** Create visitor <x => x match cases> */ - def makeVisitor(cases: List[CaseDef], checkExhaustive: Boolean, prefix: String): Tree = { - val x = freshTermName(prefix) - val id = Ident(x) - val sel = if (checkExhaustive) id else gen.mkUnchecked(id) - Function(List(gen.mkSyntheticParam(x)), Match(sel, cases)) - } - /** Create tree for case definition <case pat if guard => rhs> */ def makeCaseDef(pat: Tree, guard: Tree, rhs: Tree): CaseDef = - CaseDef(patvarTransformer.transform(pat), guard, rhs) + CaseDef(gen.patvarTransformer.transform(pat), guard, rhs) /** Creates tree representing: * { case x: Throwable => @@ -428,76 +144,6 @@ abstract class TreeBuilder { makeCaseDef(pat, EmptyTree, body) } - /** Create tree for pattern definition <val pat0 = rhs> */ - def makePatDef(pat: Tree, rhs: Tree): List[Tree] = - makePatDef(Modifiers(0), pat, rhs) - - /** Create tree for pattern definition <mods val pat0 = rhs> */ - def makePatDef(mods: Modifiers, pat: Tree, rhs: Tree): List[Tree] = matchVarPattern(pat) match { - case Some((name, tpt)) => - List(atPos(pat.pos union rhs.pos) { - ValDef(mods, name.toTermName, tpt, rhs) - }) - - case None => - // in case there is exactly one variable x_1 in pattern - // val/var p = e ==> val/var x_1 = e.match (case p => (x_1)) - // - // in case there are zero or more than one variables in pattern - // val/var p = e ==> private synthetic val t$ = e.match (case p => (x_1, ..., x_N)) - // val/var x_1 = t$._1 - // ... - // val/var x_N = t$._N - - val rhsUnchecked = gen.mkUnchecked(rhs) - - // TODO: clean this up -- there is too much information packked into makePatDef's `pat` argument - // when it's a simple identifier (case Some((name, tpt)) -- above), - // pat should have the type ascription that was specified by the user - // however, in `case None` (here), we must be careful not to generate illegal pattern trees (such as `(a, b): Tuple2[Int, String]`) - // i.e., this must hold: pat1 match { case Typed(expr, tp) => assert(expr.isInstanceOf[Ident]) case _ => } - // if we encounter such an erroneous pattern, we strip off the type ascription from pat and propagate the type information to rhs - val (pat1, rhs1) = patvarTransformer.transform(pat) match { - // move the Typed ascription to the rhs - case Typed(expr, tpt) if !expr.isInstanceOf[Ident] => - val rhsTypedUnchecked = - if (tpt.isEmpty) rhsUnchecked - else Typed(rhsUnchecked, tpt) setPos (rhs.pos union tpt.pos) - (expr, rhsTypedUnchecked) - case ok => - (ok, rhsUnchecked) - } - val vars = getVariables(pat1) - val matchExpr = atPos((pat1.pos union rhs.pos).makeTransparent) { - Match( - rhs1, - List( - atPos(pat1.pos) { - CaseDef(pat1, EmptyTree, makeTupleTerm(vars map (_._1) map Ident.apply, flattenUnary = true)) - } - )) - } - vars match { - case List((vname, tpt, pos)) => - List(atPos(pat.pos union pos union rhs.pos) { - ValDef(mods, vname.toTermName, tpt, matchExpr) - }) - case _ => - val tmp = freshTermName() - val firstDef = - atPos(matchExpr.pos) { - ValDef(Modifiers(PrivateLocal | SYNTHETIC | ARTIFACT | (mods.flags & LAZY)), - tmp, TypeTree(), matchExpr) - } - var cnt = 0 - val restDefs = for ((vname, tpt, pos) <- vars) yield atPos(pos) { - cnt += 1 - ValDef(mods, vname.toTermName, tpt, Select(Ident(tmp), newTermName("_" + cnt))) - } - firstDef :: restDefs - } - } - /** Create a tree representing the function type (argtpes) => restpe */ def makeFunctionTypeTree(argtpes: List[Tree], restpe: Tree): Tree = gen.mkFunctionTypeTree(argtpes, restpe) diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala index 0b5ade0b4c..3901184c25 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala @@ -63,11 +63,11 @@ trait Parsers { self: Quasiquotes => override implicit def fresh: FreshNameCreator = parser.fresh // q"(..$xs)" - override def makeTupleTerm(trees: List[Tree], flattenUnary: Boolean): Tree = + override def makeTupleTerm(trees: List[Tree]): Tree = Apply(Ident(nme.QUASIQUOTE_TUPLE), trees) // tq"(..$xs)" - override def makeTupleType(trees: List[Tree], flattenUnary: Boolean): Tree = + override def makeTupleType(trees: List[Tree]): Tree = AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), trees) // q"{ $x }" @@ -152,6 +152,13 @@ trait Parsers { self: Quasiquotes => in.nextToken() stats } + + override def enumerator(isFirst: Boolean, allowNestedIf: Boolean = true) = + if (isHole && lookingAhead { in.token == EOF || in.token == RPAREN || isStatSep }) { + val res = build.SyntacticValFrom(Bind(in.name, Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) :: Nil + in.nextToken() + res + } else super.enumerator(isFirst, allowNestedIf) } } @@ -170,15 +177,29 @@ trait Parsers { self: Quasiquotes => object PatternParser extends Parser { def entryPoint = { parser => val pat = parser.noSeq.pattern1() - parser.treeBuilder.patvarTransformer.transform(pat) + gen.patvarTransformer.transform(pat) + } + } + + object ForEnumeratorParser extends Parser { + def entryPoint = { parser => + val enums = parser.enumerator(isFirst = false, allowNestedIf = false) + assert(enums.length == 1) + enums.head } } + // Extractor that matches names which were generated by call to + // freshTermName or freshTypeName within quasiquotes. Such names + // have qq$some$random$prefix$0 shape where qq$ part is added + // by modified fresh name creator in QuasiquoteParser. object FreshName { def unapply(name: Name): Option[String] = - name.toString.split("\\$") match { - case Array(qq, left, right) if qq + "$" == nme.QUASIQUOTE_PREFIX && Try(right.toInt).isSuccess => - Some(left + "$") + name.toString.split("\\$").toSeq match { + case qq +: (middle :+ last) + if qq + "$" == nme.QUASIQUOTE_PREFIX + && Try(last.toInt).isSuccess && middle.nonEmpty => + Some(middle.mkString("", "$", "$")) case _ => None } diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala index c31d1fcd12..54be9123c7 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala @@ -161,4 +161,12 @@ trait Placeholders { self: Quasiquotes => case _ => None } } + + object ForEnumPlaceholder { + def unapply(tree: Tree): Option[(Tree, Location, Cardinality)] = tree match { + case build.SyntacticValFrom(Bind(Placeholder(tree, location, card), Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) => + Some((tree, location, card)) + 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 index f4d6b39d02..7d777ef7d5 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala @@ -31,6 +31,7 @@ abstract class Quasiquotes extends Parsers 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) diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala index 3d1ecf95b2..b28c85cfc2 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala @@ -127,6 +127,7 @@ trait Reifiers { self: Quasiquotes => case RefineStatPlaceholder(tree, _, _) => reifyRefineStat(tree) case EarlyDefPlaceholder(tree, _, _) => reifyEarlyDef(tree) case PackageStatPlaceholder(tree, _, _) => reifyPackageStat(tree) + case ForEnumPlaceholder(tree, _, _) => tree case _ => EmptyTree } @@ -148,6 +149,16 @@ trait Reifiers { self: Quasiquotes => 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, List(args)) @@ -275,7 +286,7 @@ trait Reifiers { self: Quasiquotes => case RefineStatPlaceholder(tree, _, DotDot) => reifyRefineStat(tree) case EarlyDefPlaceholder(tree, _, DotDot) => reifyEarlyDef(tree) case PackageStatPlaceholder(tree, _, DotDot) => reifyPackageStat(tree) - + case ForEnumPlaceholder(tree, _, DotDot) => tree case List(Placeholder(tree, _, DotDotDot)) => tree } { reify(_) diff --git a/src/reflect/scala/reflect/api/BuildUtils.scala b/src/reflect/scala/reflect/api/BuildUtils.scala index 28551b1dcd..cf05aefe72 100644 --- a/src/reflect/scala/reflect/api/BuildUtils.scala +++ b/src/reflect/scala/reflect/api/BuildUtils.scala @@ -223,5 +223,34 @@ private[reflect] trait BuildUtils { self: Universe => def apply(lhs: Tree, rhs: Tree): Tree def unapply(tree: Tree): Option[(Tree, Tree)] } + + val SyntacticValFrom: SyntacticValFromExtractor + + trait SyntacticValFromExtractor { + def apply(pat: Tree, rhs: Tree): Tree + def unapply(tree: Tree): Option[(Tree, Tree)] + } + + val SyntacticValEq: SyntacticValEqExtractor + + trait SyntacticValEqExtractor { + def apply(pat: Tree, rhs: Tree): Tree + def unapply(tree: Tree): Option[(Tree, Tree)] + } + + val SyntacticFilter: SyntacticFilterExtractor + + trait SyntacticFilterExtractor { + def apply(test: Tree): Tree + def unapply(tree: Tree): Option[(Tree)] + } + + val SyntacticFor: SyntacticForExtractor + val SyntacticForYield: SyntacticForExtractor + + trait SyntacticForExtractor { + def apply(enums: List[Tree], body: Tree): Tree + def unapply(tree: Tree): Option[(List[Tree], Tree)] + } } } diff --git a/src/reflect/scala/reflect/api/Quasiquotes.scala b/src/reflect/scala/reflect/api/Quasiquotes.scala index 3687ccba63..fcf8edcec7 100644 --- a/src/reflect/scala/reflect/api/Quasiquotes.scala +++ b/src/reflect/scala/reflect/api/Quasiquotes.scala @@ -14,5 +14,6 @@ trait Quasiquotes { self: Universe => object tq extends api object cq extends api object pq extends api + object fq extends api } } diff --git a/src/reflect/scala/reflect/internal/BuildUtils.scala b/src/reflect/scala/reflect/internal/BuildUtils.scala index fc6b26db3f..8fc1869dd2 100644 --- a/src/reflect/scala/reflect/internal/BuildUtils.scala +++ b/src/reflect/scala/reflect/internal/BuildUtils.scala @@ -9,7 +9,6 @@ trait BuildUtils { self: SymbolTable => import definitions.{TupleClass, FunctionClass, ScalaPackage, UnitClass} class BuildImpl extends BuildApi { - def selectType(owner: Symbol, name: String): TypeSymbol = select(owner, newTypeName(name)).asType @@ -19,7 +18,7 @@ trait BuildUtils { self: SymbolTable => else result } - private def select(owner: Symbol, name: Name): Symbol = { + protected def select(owner: Symbol, name: Name): Symbol = { val result = owner.info decl name if (result ne NoSymbol) result else @@ -135,7 +134,7 @@ trait BuildUtils { self: SymbolTable => def withFreshTypeName[T](prefix: String)(f: TypeName => T): T = f(freshTypeName(prefix)) - private implicit def fresh: FreshNameCreator = self.currentFreshNameCreator + protected implicit def fresh: FreshNameCreator = self.currentFreshNameCreator object FlagsRepr extends FlagsReprExtractor { def apply(bits: Long): FlagSet = bits @@ -175,7 +174,8 @@ trait BuildUtils { self: SymbolTable => } } - private object UnCtor { + // recover constructor contents generated by gen.mkTemplate + protected object UnCtor { def unapply(tree: Tree): Option[(Modifiers, List[List[ValDef]], List[Tree])] = tree match { case DefDef(mods, nme.MIXIN_CONSTRUCTOR, _, _, _, Block(lvdefs, _)) => Some((mods | Flag.TRAIT, Nil, lvdefs)) @@ -185,7 +185,8 @@ trait BuildUtils { self: SymbolTable => } } - private object UnMkTemplate { + // undo gen.mkTemplate + protected object UnMkTemplate { def unapply(templ: Template): Option[(List[Tree], ValDef, Modifiers, List[List[ValDef]], List[Tree], List[Tree])] = { val Template(parents, selfType, tbody) = templ def result(ctorMods: Modifiers, vparamss: List[List[ValDef]], edefs: List[Tree], body: List[Tree]) = @@ -297,8 +298,8 @@ trait BuildUtils { self: SymbolTable => } } - private trait ScalaMemberRef { - val symbols: Seq[Symbol] + // match references to `scala.$name` + protected class ScalaMemberRef(symbols: Seq[Symbol]) { def result(name: Name): Option[Symbol] = symbols.collect { case sym if sym.name == name => sym }.headOption def unapply(tree: Tree): Option[Symbol] = tree match { @@ -311,31 +312,23 @@ trait BuildUtils { self: SymbolTable => case _ => None } } - private object TupleClassRef extends ScalaMemberRef { - val symbols = TupleClass.seq - } - private object TupleCompanionRef extends ScalaMemberRef { - val symbols = TupleClass.seq.map { _.companionModule } - } - private object UnitClassRef extends ScalaMemberRef { - val symbols = Seq(UnitClass) - } - private object FunctionClassRef extends ScalaMemberRef { - val symbols = FunctionClass.seq - } + protected object TupleClassRef extends ScalaMemberRef(TupleClass.seq) + protected object TupleCompanionRef extends ScalaMemberRef(TupleClass.seq.map { _.companionModule }) + protected object UnitClassRef extends ScalaMemberRef(Seq(UnitClass)) + protected object FunctionClassRef extends ScalaMemberRef(FunctionClass.seq) object SyntacticTuple extends SyntacticTupleExtractor { - def apply(args: List[Tree]): Tree = args match { - case Nil => Literal(Constant(())) - case _ => - require(TupleClass(args.length).exists, s"Tuples with ${args.length} arity aren't supported") - self.Apply(TupleClass(args.length).companionModule, args: _*) + def apply(args: List[Tree]): Tree = { + require(args.isEmpty || TupleClass(args.length).exists, s"Tuples with ${args.length} arity aren't supported") + gen.mkTuple(args, flattenUnary = false) } def unapply(tree: Tree): Option[List[Tree]] = tree match { case Literal(Constant(())) => Some(Nil) - case Apply(TupleCompanionRef(sym), args) if sym == TupleClass(args.length).companionModule => + case Apply(MaybeTypeTreeOriginal(SyntacticTypeApplied(MaybeSelectApply(TupleCompanionRef(sym)), targs)), args) + if sym == TupleClass(args.length).companionModule + && (targs.isEmpty || targs.length == args.length) => Some(args) case _ => None @@ -343,17 +336,16 @@ trait BuildUtils { self: SymbolTable => } object SyntacticTupleType extends SyntacticTupleExtractor { - def apply(args: List[Tree]): Tree = args match { - case Nil => self.Select(self.Ident(nme.scala_), tpnme.Unit) - case _ => - require(TupleClass(args.length).exists, s"Tuples with ${args.length} arity aren't supported") - AppliedTypeTree(Ident(TupleClass(args.length)), args) + def apply(args: List[Tree]): Tree = { + require(args.isEmpty || TupleClass(args.length).exists, s"Tuples with ${args.length} arity aren't supported") + gen.mkTupleType(args, flattenUnary = false) } - def unapply(tree: Tree): Option[List[Tree]] = tree match { - case UnitClassRef(_) => + def unapply(tree: Tree): Option[List[Tree]] = tree match { + case MaybeTypeTreeOriginal(UnitClassRef(_)) => Some(Nil) - case AppliedTypeTree(TupleClassRef(sym), args) if sym == TupleClass(args.length) => + case MaybeTypeTreeOriginal(AppliedTypeTree(TupleClassRef(sym), args)) + if sym == TupleClass(args.length) => Some(args) case _ => None @@ -367,7 +359,8 @@ trait BuildUtils { self: SymbolTable => } def unapply(tree: Tree): Option[(List[Tree], Tree)] = tree match { - case AppliedTypeTree(FunctionClassRef(sym), args @ (argtpes :+ restpe)) if sym == FunctionClass(args.length - 1) => + case MaybeTypeTreeOriginal(AppliedTypeTree(FunctionClassRef(sym), args @ (argtpes :+ restpe))) + if sym == FunctionClass(args.length - 1) => Some((argtpes, restpe)) case _ => None } @@ -423,9 +416,7 @@ trait BuildUtils { self: SymbolTable => } } - trait SyntacticValDefBase extends SyntacticValDefExtractor { - val isMutable: Boolean - + protected class SyntacticValDefBase(isMutable: Boolean) extends SyntacticValDefExtractor { def apply(mods: Modifiers, name: TermName, tpt: Tree, rhs: Tree) = { val mods1 = if (isMutable) mods | MUTABLE else mods ValDef(mods1, name, tpt, rhs) @@ -438,9 +429,8 @@ trait BuildUtils { self: SymbolTable => None } } - - object SyntacticValDef extends SyntacticValDefBase { val isMutable = false } - object SyntacticVarDef extends SyntacticValDefBase { val isMutable = true } + object SyntacticValDef extends SyntacticValDefBase(isMutable = false) + object SyntacticVarDef extends SyntacticValDefBase(isMutable = true) object SyntacticAssign extends SyntacticAssignExtractor { def apply(lhs: Tree, rhs: Tree): Tree = gen.mkAssign(lhs, rhs) @@ -451,7 +441,238 @@ trait BuildUtils { self: SymbolTable => case _ => None } } + + object SyntacticValFrom extends SyntacticValFromExtractor { + def apply(pat: Tree, rhs: Tree): Tree = gen.ValFrom(pat, gen.mkCheckIfRefutable(pat, rhs)) + def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { + case gen.ValFrom(pat, UnCheckIfRefutable(pat1, rhs1)) if pat.equalsStructure(pat1) => + Some((pat, rhs1)) + case gen.ValFrom(pat, rhs) => + Some((pat, rhs)) + case _ => None + } + } + + object SyntacticValEq extends SyntacticValEqExtractor { + def apply(pat: Tree, rhs: Tree): Tree = gen.ValEq(pat, rhs) + def unapply(tree: Tree): Option[(Tree, Tree)] = gen.ValEq.unapply(tree) + } + + object SyntacticFilter extends SyntacticFilterExtractor { + def apply(tree: Tree): Tree = gen.Filter(tree) + def unapply(tree: Tree): Option[Tree] = gen.Filter.unapply(tree) + } + + // abstract over possible alternative representations of no type in valdef + protected object EmptyTypTree { + def unapply(tree: Tree): Boolean = tree match { + case EmptyTree => true + case tt: TypeTree if (tt.original == null || tt.original.isEmpty) => true + case _ => false + } + } + + // match a sequence of desugared `val $pat = $value` + protected object UnPatSeq { + def unapply(trees: List[Tree]): Option[List[(Tree, Tree)]] = trees match { + case Nil => Some(Nil) + // case q"$mods val ${_}: ${_} = ${MaybeUnchecked(value)} match { case $pat => (..$ids) }" :: tail + case ValDef(mods, _, _, Match(MaybeUnchecked(value), CaseDef(pat, EmptyTree, SyntacticTuple(ids)) :: Nil)) :: tail + if mods.hasFlag(SYNTHETIC) && mods.hasFlag(ARTIFACT) => + tail.drop(ids.length) match { + case UnPatSeq(rest) => Some((pat, value) :: rest) + case _ => None + } + // case q"${_} val $name1: ${_} = ${MaybeUnchecked(value)} match { case $pat => ${Ident(name2)} }" :: UnPatSeq(rest) + case ValDef(_, name1, _, Match(MaybeUnchecked(value), CaseDef(pat, EmptyTree, Ident(name2)) :: Nil)) :: UnPatSeq(rest) + if name1 == name2 => + Some((pat, value) :: rest) + // case q"${_} val $name: ${EmptyTypTree()} = $value" :: UnPatSeq(rest) => + case ValDef(_, name, EmptyTypTree(), value) :: UnPatSeq(rest) => + Some((Bind(name, self.Ident(nme.WILDCARD)), value) :: rest) + // case q"${_} val $name: $tpt = $value" :: UnPatSeq(rest) => + case ValDef(_, name, tpt, value) :: UnPatSeq(rest) => + Some((Bind(name, Typed(self.Ident(nme.WILDCARD), tpt)), value) :: rest) + case _ => None + } + } + + // match a sequence of desugared `val $pat = $value` with a tuple in the end + protected object UnPatSeqWithRes { + def unapply(tree: Tree): Option[(List[(Tree, Tree)], List[Tree])] = tree match { + case SyntacticBlock(UnPatSeq(trees) :+ SyntacticTuple(elems)) => Some((trees, elems)) + case _ => None + } + } + + // undo gen.mkSyntheticParam + protected object UnSyntheticParam { + def unapply(tree: Tree): Option[TermName] = tree match { + case ValDef(mods, name, _, EmptyTree) + if mods.hasFlag(SYNTHETIC) && mods.hasFlag(PARAM) => + Some(name) + case _ => None + } + } + + // undo gen.mkVisitor + protected object UnVisitor { + def unapply(tree: Tree): Option[(TermName, List[CaseDef])] = tree match { + case Function(UnSyntheticParam(x1) :: Nil, Match(MaybeUnchecked(Ident(x2)), cases)) + if x1 == x2 => + Some((x1, cases)) + case _ => None + } + } + + // undo gen.mkFor:makeClosure + protected object UnClosure { + def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { + case Function(ValDef(Modifiers(PARAM, _, _), name, tpt, EmptyTree) :: Nil, body) => + tpt match { + case EmptyTypTree() => Some((Bind(name, self.Ident(nme.WILDCARD)), body)) + case _ => Some((Bind(name, Typed(self.Ident(nme.WILDCARD), tpt)), body)) + } + case UnVisitor(_, CaseDef(pat, EmptyTree, body) :: Nil) => + Some((pat, body)) + case _ => None + } + } + + // match call to either withFilter or filter + protected object FilterCall { + def unapply(tree: Tree): Option[(Tree,Tree)] = tree match { + case Apply(Select(obj, nme.withFilter | nme.filter), arg :: Nil) => + Some(obj, arg) + case _ => None + } + } + + // transform a chain of withFilter calls into a sequence of for filters + protected object UnFilter { + def unapply(tree: Tree): Some[(Tree, List[Tree])] = tree match { + case UnCheckIfRefutable(_, _) => + Some((tree, Nil)) + case FilterCall(UnFilter(rhs, rest), UnClosure(_, test)) => + Some((rhs, rest :+ SyntacticFilter(test))) + case _ => + Some((tree, Nil)) + } + } + + // undo gen.mkCheckIfRefutable + protected object UnCheckIfRefutable { + def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { + case FilterCall(rhs, UnVisitor(name, + CaseDef(pat, EmptyTree, Literal(Constant(true))) :: + CaseDef(Ident(nme.WILDCARD), EmptyTree, Literal(Constant(false))) :: Nil)) + if name.toString.contains(nme.CHECK_IF_REFUTABLE_STRING) => + Some((pat, rhs)) + case _ => None + } + } + + // undo gen.mkFor:makeCombination accounting for possible extra implicit argument + protected class UnForCombination(name: TermName) { + def unapply(tree: Tree) = tree match { + case SyntacticApplied(SyntacticTypeApplied(sel @ Select(lhs, meth), _), (f :: Nil) :: Nil) + if name == meth && sel.hasAttachment[ForAttachment.type] => + Some(lhs, f) + case SyntacticApplied(SyntacticTypeApplied(sel @ Select(lhs, meth), _), (f :: Nil) :: _ :: Nil) + if name == meth && sel.hasAttachment[ForAttachment.type] => + Some(lhs, f) + case _ => None + } + } + protected object UnMap extends UnForCombination(nme.map) + protected object UnForeach extends UnForCombination(nme.foreach) + protected object UnFlatMap extends UnForCombination(nme.flatMap) + + // undo desugaring done in gen.mkFor + protected object UnFor { + def unapply(tree: Tree): Option[(List[Tree], Tree)] = { + val interm = tree match { + case UnFlatMap(UnFilter(rhs, filters), UnClosure(pat, UnFor(rest, body))) => + Some(((pat, rhs), filters ::: rest, body)) + case UnForeach(UnFilter(rhs, filters), UnClosure(pat, UnFor(rest, body))) => + Some(((pat, rhs), filters ::: rest, body)) + case UnMap(UnFilter(rhs, filters), UnClosure(pat, cbody)) => + Some(((pat, rhs), filters, gen.Yield(cbody))) + case UnForeach(UnFilter(rhs, filters), UnClosure(pat, cbody)) => + Some(((pat, rhs), filters, cbody)) + case _ => None + } + interm.flatMap { + case ((Bind(_, SyntacticTuple(_)) | SyntacticTuple(_), + UnFor(SyntacticValFrom(pat, rhs) :: innerRest, gen.Yield(UnPatSeqWithRes(pats, elems2)))), + outerRest, fbody) => + val valeqs = pats.map { case (pat, rhs) => SyntacticValEq(pat, rhs) } + Some((SyntacticValFrom(pat, rhs) :: innerRest ::: valeqs ::: outerRest, fbody)) + case ((pat, rhs), filters, body) => + Some((SyntacticValFrom(pat, rhs) :: filters, body)) + } + } + } + + // check that enumerators are valid + protected def mkEnumerators(enums: List[Tree]): List[Tree] = { + require(enums.nonEmpty, "enumerators can't be empty") + enums.head match { + case SyntacticValFrom(_, _) => + case t => throw new IllegalArgumentException(s"$t is not a valid fist enumerator of for loop") + } + enums.tail.foreach { + case SyntacticValEq(_, _) | SyntacticValFrom(_, _) | SyntacticFilter(_) => + case t => throw new IllegalArgumentException(s"$t is not a valid representation of a for loop enumerator") + } + enums + } + + object SyntacticFor extends SyntacticForExtractor { + def apply(enums: List[Tree], body: Tree): Tree = gen.mkFor(mkEnumerators(enums), body) + def unapply(tree: Tree) = tree match { + case UnFor(enums, gen.Yield(body)) => None + case UnFor(enums, body) => Some((enums, body)) + case _ => None + } + } + + object SyntacticForYield extends SyntacticForExtractor { + def apply(enums: List[Tree], body: Tree): Tree = gen.mkFor(mkEnumerators(enums), gen.Yield(body)) + def unapply(tree: Tree) = tree match { + case UnFor(enums, gen.Yield(body)) => Some((enums, body)) + case _ => None + } + } + + // use typetree's original instead of typetree itself + protected object MaybeTypeTreeOriginal { + def unapply(tree: Tree): Some[Tree] = tree match { + case tt: TypeTree => Some(tt.original) + case _ => Some(tree) + } + } + + // drop potential extra call to .apply + protected object MaybeSelectApply { + def unapply(tree: Tree): Some[Tree] = tree match { + case Select(f, nme.apply) => Some(f) + case other => Some(other) + } + } + + // drop potential @scala.unchecked annotation + protected object MaybeUnchecked { + def unapply(tree: Tree): Some[Tree] = tree match { + case Annotated(SyntacticNew(Nil, Apply(ScalaDot(tpnme.unchecked), Nil) :: Nil, noSelfType, Nil), annottee) => + Some(annottee) + case Typed(annottee, MaybeTypeTreeOriginal( + Annotated(SyntacticNew(Nil, Apply(ScalaDot(tpnme.unchecked), Nil) :: Nil, noSelfType, Nil), _))) => + Some(annottee) + case annottee => Some(annottee) + } + } } - val build: BuildApi = new BuildImpl + val build: BuildImpl = new BuildImpl } diff --git a/src/reflect/scala/reflect/internal/Importers.scala b/src/reflect/scala/reflect/internal/Importers.scala index 72c8ccfa62..cc6e55192f 100644 --- a/src/reflect/scala/reflect/internal/Importers.scala +++ b/src/reflect/scala/reflect/internal/Importers.scala @@ -8,6 +8,16 @@ import scala.ref.WeakReference // SI-6241: move importers to a mirror trait Importers extends api.Importers { to: SymbolTable => + /** Attachment that knows how to import itself into another universe. */ + trait ImportableAttachment { + def importAttachment(importer: Importer): this.type + } + + /** Attachment that doesn't contain any reflection artificats and can be imported as-is. */ + trait PlainAttachment extends ImportableAttachment { + def importAttachment(importer: Importer): this.type = this + } + def mkImporter(from0: api.Universe): Importer { val from: from0.type } = ( if (to eq from0) { new Importer { @@ -417,11 +427,15 @@ trait Importers extends api.Importers { to: SymbolTable => my.setPos(importPosition(their.pos)) } } + importAttachments(their.attachments.all).foreach { my.updateAttachment(_) } my } // ============== MISCELLANEOUS ============== + def importAttachments(attachments: Set[Any]): Set[Any] = + attachments.collect { case ia: ImportableAttachment => ia.importAttachment(this) } + def importAnnotationInfo(ann: from.AnnotationInfo): AnnotationInfo = { val atp1 = importType(ann.atp) val args1 = ann.args map importTree diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index 9eb66db01e..46f241643b 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -14,6 +14,7 @@ trait StdAttachments { def setAttachments(attachments: scala.reflect.macros.Attachments { type Pos = Position }): this.type = { rawatt = attachments; this } def updateAttachment[T: ClassTag](attachment: T): this.type = { rawatt = rawatt.update(attachment); this } def removeAttachment[T: ClassTag]: this.type = { rawatt = rawatt.remove[T]; this } + def hasAttachment[T: ClassTag]: Boolean = rawatt.contains[T] // cannot be final due to SynchronizedSymbols def pos: Position = rawatt.pos @@ -21,13 +22,17 @@ trait StdAttachments { def setPos(newpos: Position): this.type = { pos = newpos; this } } - /** When present, indicates that the host `Ident` has been created from a backquoted identifier. - */ - case object BackquotedIdentifierAttachment - /** Stores the trees that give rise to a refined type to be used in reification. * Unfortunately typed `CompoundTypeTree` is lacking essential info, and the reifier cannot use `CompoundTypeTree.tpe`. * Therefore we need this hack (see `Reshape.toPreTyperTypeTree` for a detailed explanation). */ case class CompoundTypeTreeOriginalAttachment(parents: List[Tree], stats: List[Tree]) + + /** When present, indicates that the host `Ident` has been created from a backquoted identifier. + */ + case object BackquotedIdentifierAttachment extends PlainAttachment + + /** Identifies trees are either result or intermidiate value of for loop desugaring. + */ + case object ForAttachment extends PlainAttachment } diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 02f22a16f6..c26e815df1 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -229,6 +229,7 @@ trait StdNames { final val Serializable: NameType = "Serializable" final val Singleton: NameType = "Singleton" final val Throwable: NameType = "Throwable" + final val unchecked: NameType = "unchecked" final val api: NameType = "api" final val Annotation: NameType = "Annotation" @@ -326,6 +327,7 @@ trait StdNames { val QUASIQUOTE_FILE: String = "<quasiquote>" val QUASIQUOTE_TUPLE: NameType = "$quasiquote$tuple$" val QUASIQUOTE_CASE: NameType = "$quasiquote$case$" + val QUASIQUOTE_FOR_ENUM: NameType = "$quasiquote$for$enum$" val MIXIN_CONSTRUCTOR: NameType = "$init$" val MODULE_INSTANCE_FIELD: NameType = NameTransformer.MODULE_INSTANCE_NAME // "MODULE$" val OUTER: NameType = "$outer" @@ -591,6 +593,9 @@ trait StdNames { val SyntacticBlock: NameType = "SyntacticBlock" val SyntacticClassDef: NameType = "SyntacticClassDef" val SyntacticDefDef: NameType = "SyntacticDefDef" + val SyntacticFilter: NameType = "SyntacticFilter" + val SyntacticFor: NameType = "SyntacticFor" + val SyntacticForYield: NameType = "SyntacticForYield" val SyntacticFunction: NameType = "SyntacticFunction" val SyntacticFunctionType: NameType = "SyntacticFunctionType" val SyntacticPackageObjectDef: NameType = "SyntacticPackageObjectDef" @@ -601,6 +606,8 @@ trait StdNames { val SyntacticTupleType: NameType = "SyntacticTupleType" val SyntacticTypeApplied: NameType = "SyntacticTypeApplied" val SyntacticValDef: NameType = "SyntacticValDef" + val SyntacticValEq: NameType = "SyntacticValEq" + val SyntacticValFrom: NameType = "SyntacticValFrom" val SyntacticVarDef: NameType = "SyntacticVarDef" val This: NameType = "This" val ThisType: NameType = "ThisType" @@ -744,7 +751,6 @@ trait StdNames { val typedProductIterator: NameType = "typedProductIterator" val TypeName: NameType = "TypeName" val typeTagToManifest: NameType = "typeTagToManifest" - val unapply: NameType = "unapply" val unapplySeq: NameType = "unapplySeq" val unbox: NameType = "unbox" @@ -763,6 +769,7 @@ trait StdNames { val tq: NameType = "tq" val cq: NameType = "cq" val pq: NameType = "pq" + val fq: NameType = "fq" // unencoded operators object raw { diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index cf7c729a6a..a0bd64f850 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -4,6 +4,7 @@ package internal import Flags._ import util._ +import scala.collection.mutable.ListBuffer abstract class TreeGen extends macros.TreeBuilder { val global: SymbolTable @@ -279,11 +280,23 @@ abstract class TreeGen extends macros.TreeBuilder { def mkNamedArg(lhs: Tree, rhs: Tree): Tree = atPos(rhs.pos)(AssignOrNamedArg(lhs, rhs)) /** Builds a tuple */ - def mkTuple(elems: List[Tree]): Tree = - if (elems.isEmpty) Literal(Constant(())) - else Apply( - Select(mkAttributedRef(TupleClass(elems.length).caseModule), nme.apply), - elems) + def mkTuple(elems: List[Tree], flattenUnary: Boolean = true): Tree = elems match { + case Nil => + Literal(Constant(())) + case tree :: Nil if flattenUnary => + tree + case _ => + Apply(scalaDot(TupleClass(elems.length).companionModule.name), elems) + } + + def mkTupleType(elems: List[Tree], flattenUnary: Boolean = true): Tree = elems match { + case Nil => + scalaDot(tpnme.Unit) + case List(tree) if flattenUnary => + tree + case _ => + AppliedTypeTree(scalaDot(TupleClass(elems.length).name), elems) + } // tree1 AND tree2 def mkAnd(tree1: Tree, tree2: Tree): Tree = @@ -299,7 +312,7 @@ abstract class TreeGen extends macros.TreeBuilder { } def mkSeqApply(arg: Tree): Apply = { - val factory = Select(gen.mkAttributedRef(SeqModule), nme.apply) + val factory = Select(mkAttributedRef(SeqModule), nme.apply) Apply(factory, List(arg)) } @@ -436,17 +449,15 @@ abstract class TreeGen extends macros.TreeBuilder { else Block(stats.init, stats.last) def mkTreeOrBlock(stats: List[Tree]) = stats match { - case Nil => EmptyTree + case Nil => EmptyTree case head :: Nil => head - case _ => gen.mkBlock(stats) + case _ => mkBlock(stats) } /** Create a tree representing an assignment <lhs = rhs> */ def mkAssign(lhs: Tree, rhs: Tree): Tree = lhs match { - case Apply(fn, args) => - Apply(atPos(fn.pos)(Select(fn, nme.update)), args :+ rhs) - case _ => - Assign(lhs, rhs) + case Apply(fn, args) => Apply(atPos(fn.pos)(Select(fn, nme.update)), args :+ rhs) + case _ => Assign(lhs, rhs) } def mkPackageObject(defn: ModuleDef, pidPos: Position = NoPosition, pkgPos: Position = NoPosition) = { @@ -454,4 +465,419 @@ abstract class TreeGen extends macros.TreeBuilder { val pid = atPos(pidPos)(Ident(defn.name)) atPos(pkgPos)(PackageDef(pid, module :: Nil)) } + + // Following objects represent encoding of for loop enumerators + // into the regular trees. Such representations are used for: + // + // - as intermediate value of enumerators inside of the parser + // right before the mkFor desugaring is being called + // + // - as intermediate value of enumerators obtained after + // re-sugaring of for loops through build.SyntacticFor + // and build.SyntacticForYield (which are used by quasiquotes) + // + // The encoding uses regular trees with ForAttachment that helps + // to reliably differentiate them from normal trees that can have + // similar shape. fq"$pat <- $rhs" for example is represented in + // the same way as "`<-`($pat, $rhs)"" but with added attachment to + // the `<-` identifier. + // + // The primary rationale behind such representation in favor of + // simple case classes is a wish to re-use the same representation + // between quasiquotes and parser without exposing compiler internals. + // Opaque tree encoding can be changed/adapted at any time without + // breaking end users code. + + /** Encode/decode fq"$pat <- $rhs" enumerator as q"`<-`($pat, $rhs)" */ + object ValFrom { + def apply(pat: Tree, rhs: Tree): Tree = + Apply(Ident(nme.LARROWkw).updateAttachment(ForAttachment), + List(pat, rhs)) + + def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { + case Apply(id @ Ident(nme.LARROWkw), List(pat, rhs)) + if id.hasAttachment[ForAttachment.type] => + Some((pat, rhs)) + case _ => None + } + } + + /** Encode/decode fq"$pat = $rhs" enumerator as q"$pat = $rhs" */ + object ValEq { + def apply(pat: Tree, rhs: Tree): Tree = + Assign(pat, rhs).updateAttachment(ForAttachment) + + def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { + case Assign(pat, rhs) + if tree.hasAttachment[ForAttachment.type] => + Some((pat, rhs)) + case _ => None + } + } + + /** Encode/decode fq"if $cond" enumerator as q"`if`($cond)" */ + object Filter { + def apply(tree: Tree) = + Apply(Ident(nme.IFkw).updateAttachment(ForAttachment), List(tree)) + + def unapply(tree: Tree): Option[Tree] = tree match { + case Apply(id @ Ident(nme.IFkw), List(cond)) + if id.hasAttachment[ForAttachment.type] => + Some((cond)) + case _ => None + } + } + + /** Encode/decode body of for yield loop as q"`yield`($tree)" */ + object Yield { + def apply(tree: Tree): Tree = + Apply(Ident(nme.YIELDkw).updateAttachment(ForAttachment), List(tree)) + + def unapply(tree: Tree): Option[Tree] = tree match { + case Apply(id @ Ident(nme.YIELDkw), List(tree)) + if id.hasAttachment[ForAttachment.type] => + Some(tree) + case _ => None + } + } + + /** Create tree for for-comprehension <for (enums) do body> or + * <for (enums) yield body> where mapName and flatMapName are chosen + * corresponding to whether this is a for-do or a for-yield. + * The creation performs the following rewrite rules: + * + * 1. + * + * for (P <- G) E ==> G.foreach (P => E) + * + * Here and in the following (P => E) is interpreted as the function (P => E) + * if P is a variable pattern and as the partial function { case P => E } otherwise. + * + * 2. + * + * for (P <- G) yield E ==> G.map (P => E) + * + * 3. + * + * for (P_1 <- G_1; P_2 <- G_2; ...) ... + * ==> + * G_1.flatMap (P_1 => for (P_2 <- G_2; ...) ...) + * + * 4. + * + * for (P <- G; E; ...) ... + * => + * for (P <- G.filter (P => E); ...) ... + * + * 5. For N < MaxTupleArity: + * + * for (P_1 <- G; P_2 = E_2; val P_N = E_N; ...) + * ==> + * for (TupleN(P_1, P_2, ... P_N) <- + * for (x_1 @ P_1 <- G) yield { + * val x_2 @ P_2 = E_2 + * ... + * val x_N & P_N = E_N + * TupleN(x_1, ..., x_N) + * } ...) + * + * If any of the P_i are variable patterns, the corresponding `x_i @ P_i' is not generated + * and the variable constituting P_i is used instead of x_i + * + * @param mapName The name to be used for maps (either map or foreach) + * @param flatMapName The name to be used for flatMaps (either flatMap or foreach) + * @param enums The enumerators in the for expression + * @param body The body of the for expression + */ + def mkFor(enums: List[Tree], sugarBody: Tree)(implicit fresh: FreshNameCreator): Tree = { + val (mapName, flatMapName, body) = sugarBody match { + case Yield(tree) => (nme.map, nme.flatMap, tree) + case _ => (nme.foreach, nme.foreach, sugarBody) + } + + /* make a closure pat => body. + * The closure is assigned a transparent position with the point at pos.point and + * the limits given by pat and body. + */ + def makeClosure(pos: Position, pat: Tree, body: Tree): Tree = { + def wrapped = wrappingPos(List(pat, body)) + def splitpos = (if (pos != NoPosition) wrapped.withPoint(pos.point) else pos).makeTransparent + matchVarPattern(pat) match { + case Some((name, tpt)) => + Function( + List(atPos(pat.pos) { ValDef(Modifiers(PARAM), name.toTermName, tpt, EmptyTree) }), + body) setPos splitpos + case None => + atPos(splitpos) { + mkVisitor(List(CaseDef(pat, EmptyTree, body)), checkExhaustive = false) + } + } + } + + /* Make an application qual.meth(pat => body) positioned at `pos`. + */ + def makeCombination(pos: Position, meth: TermName, qual: Tree, pat: Tree, body: Tree): Tree = + // ForAttachment on the method selection is used to differentiate + // result of for desugaring from a regular method call + Apply(Select(qual, meth) setPos qual.pos updateAttachment ForAttachment, + List(makeClosure(pos, pat, body))) setPos pos + + /* If `pat` is not yet a `Bind` wrap it in one with a fresh name */ + def makeBind(pat: Tree): Tree = pat match { + case Bind(_, _) => pat + case _ => Bind(freshTermName(), pat) setPos pat.pos + } + + /* A reference to the name bound in Bind `pat`. */ + def makeValue(pat: Tree): Tree = pat match { + case Bind(name, _) => Ident(name) setPos pat.pos.focus + } + + /* The position of the closure that starts with generator at position `genpos`. */ + def closurePos(genpos: Position) = + if (genpos == NoPosition) NoPosition + else { + val end = body.pos match { + case NoPosition => genpos.point + case bodypos => bodypos.end + } + rangePos(genpos.source, genpos.start, genpos.point, end) + } + + enums match { + case (t @ ValFrom(pat, rhs)) :: Nil => + makeCombination(closurePos(t.pos), mapName, rhs, pat, body) + case (t @ ValFrom(pat, rhs)) :: (rest @ (ValFrom(_, _) :: _)) => + makeCombination(closurePos(t.pos), flatMapName, rhs, pat, + mkFor(rest, sugarBody)) + case (t @ ValFrom(pat, rhs)) :: Filter(test) :: rest => + mkFor(ValFrom(pat, makeCombination(rhs.pos union test.pos, nme.withFilter, rhs, pat.duplicate, test)).setPos(t.pos) :: rest, sugarBody) + case (t @ ValFrom(pat, rhs)) :: rest => + val valeqs = rest.take(definitions.MaxTupleArity - 1).takeWhile { ValEq.unapply(_).nonEmpty } + assert(!valeqs.isEmpty) + val rest1 = rest.drop(valeqs.length) + val pats = valeqs map { case ValEq(pat, _) => pat } + val rhss = valeqs map { case ValEq(_, rhs) => rhs } + val defpat1 = makeBind(pat) + val defpats = pats map makeBind + val pdefs = (defpats, rhss).zipped flatMap mkPatDef + val ids = (defpat1 :: defpats) map makeValue + val rhs1 = mkFor( + List(ValFrom(defpat1, rhs).setPos(t.pos)), + Yield(Block(pdefs, atPos(wrappingPos(ids)) { mkTuple(ids) }) setPos wrappingPos(pdefs))) + val allpats = (pat :: pats) map (_.duplicate) + val pos1 = + if (t.pos == NoPosition) NoPosition + else rangePos(t.pos.source, t.pos.start, t.pos.point, rhs1.pos.end) + val vfrom1 = ValFrom(atPos(wrappingPos(allpats)) { mkTuple(allpats) }, rhs1).setPos(pos1) + mkFor(vfrom1 :: rest1, sugarBody) + case _ => + EmptyTree //may happen for erroneous input + + } + } + + /** Create tree for pattern definition <val pat0 = rhs> */ + def mkPatDef(pat: Tree, rhs: Tree)(implicit fresh: FreshNameCreator): List[Tree] = + mkPatDef(Modifiers(0), pat, rhs) + + /** Create tree for pattern definition <mods val pat0 = rhs> */ + def mkPatDef(mods: Modifiers, pat: Tree, rhs: Tree)(implicit fresh: FreshNameCreator): List[Tree] = matchVarPattern(pat) match { + case Some((name, tpt)) => + List(atPos(pat.pos union rhs.pos) { + ValDef(mods, name.toTermName, tpt, rhs) + }) + + case None => + // in case there is exactly one variable x_1 in pattern + // val/var p = e ==> val/var x_1 = e.match (case p => (x_1)) + // + // in case there are zero or more than one variables in pattern + // val/var p = e ==> private synthetic val t$ = e.match (case p => (x_1, ..., x_N)) + // val/var x_1 = t$._1 + // ... + // val/var x_N = t$._N + + val rhsUnchecked = mkUnchecked(rhs) + + // TODO: clean this up -- there is too much information packked into mkPatDef's `pat` argument + // when it's a simple identifier (case Some((name, tpt)) -- above), + // pat should have the type ascription that was specified by the user + // however, in `case None` (here), we must be careful not to generate illegal pattern trees (such as `(a, b): Tuple2[Int, String]`) + // i.e., this must hold: pat1 match { case Typed(expr, tp) => assert(expr.isInstanceOf[Ident]) case _ => } + // if we encounter such an erroneous pattern, we strip off the type ascription from pat and propagate the type information to rhs + val (pat1, rhs1) = patvarTransformer.transform(pat) match { + // move the Typed ascription to the rhs + case Typed(expr, tpt) if !expr.isInstanceOf[Ident] => + val rhsTypedUnchecked = + if (tpt.isEmpty) rhsUnchecked + else Typed(rhsUnchecked, tpt) setPos (rhs.pos union tpt.pos) + (expr, rhsTypedUnchecked) + case ok => + (ok, rhsUnchecked) + } + val vars = getVariables(pat1) + val matchExpr = atPos((pat1.pos union rhs.pos).makeTransparent) { + Match( + rhs1, + List( + atPos(pat1.pos) { + CaseDef(pat1, EmptyTree, mkTuple(vars map (_._1) map Ident.apply)) + } + )) + } + vars match { + case List((vname, tpt, pos)) => + List(atPos(pat.pos union pos union rhs.pos) { + ValDef(mods, vname.toTermName, tpt, matchExpr) + }) + case _ => + val tmp = freshTermName() + val firstDef = + atPos(matchExpr.pos) { + ValDef(Modifiers(PrivateLocal | SYNTHETIC | ARTIFACT | (mods.flags & LAZY)), + tmp, TypeTree(), matchExpr) + } + var cnt = 0 + val restDefs = for ((vname, tpt, pos) <- vars) yield atPos(pos) { + cnt += 1 + ValDef(mods, vname.toTermName, tpt, Select(Ident(tmp), newTermName("_" + cnt))) + } + firstDef :: restDefs + } + } + + /** Create tree for for-comprehension generator <val pat0 <- rhs0> */ + def mkGenerator(pos: Position, pat: Tree, valeq: Boolean, rhs: Tree)(implicit fresh: FreshNameCreator): Tree = { + val pat1 = patvarTransformer.transform(pat) + if (valeq) ValEq(pat1, rhs).setPos(pos) + else ValFrom(pat1, mkCheckIfRefutable(pat1, rhs)).setPos(pos) + } + + def mkCheckIfRefutable(pat: Tree, rhs: Tree)(implicit fresh: FreshNameCreator) = + if (treeInfo.isVarPatternDeep(pat)) rhs + else { + val cases = List( + CaseDef(pat.duplicate, EmptyTree, Literal(Constant(true))), + CaseDef(Ident(nme.WILDCARD), EmptyTree, Literal(Constant(false))) + ) + val visitor = mkVisitor(cases, checkExhaustive = false, nme.CHECK_IF_REFUTABLE_STRING) + atPos(rhs.pos)(Apply(Select(rhs, nme.withFilter), visitor :: Nil)) + } + + /** If tree is a variable pattern, return Some("its name and type"). + * Otherwise return none */ + private def matchVarPattern(tree: Tree): Option[(Name, Tree)] = { + def wildType(t: Tree): Option[Tree] = t match { + case Ident(x) if x.toTermName == nme.WILDCARD => Some(TypeTree()) + case Typed(Ident(x), tpt) if x.toTermName == nme.WILDCARD => Some(tpt) + case _ => None + } + tree match { + case Ident(name) => Some((name, TypeTree())) + case Bind(name, body) => wildType(body) map (x => (name, x)) + case Typed(Ident(name), tpt) => Some((name, tpt)) + case _ => None + } + } + + /** Create visitor <x => x match cases> */ + def mkVisitor(cases: List[CaseDef], checkExhaustive: Boolean, prefix: String = "x$")(implicit fresh: FreshNameCreator): Tree = { + val x = freshTermName(prefix) + val id = Ident(x) + val sel = if (checkExhaustive) id else mkUnchecked(id) + Function(List(mkSyntheticParam(x)), Match(sel, cases)) + } + + /** Traverse pattern and collect all variable names with their types in buffer + * The variables keep their positions; whereas the pattern is converted to be + * synthetic for all nodes that contain a variable position. + */ + class GetVarTraverser extends Traverser { + val buf = new ListBuffer[(Name, Tree, Position)] + + def namePos(tree: Tree, name: Name): Position = + if (!tree.pos.isRange || name.containsName(nme.raw.DOLLAR)) tree.pos.focus + else { + val start = tree.pos.start + val end = start + name.decode.length + rangePos(tree.pos.source, start, start, end) + } + + override def traverse(tree: Tree): Unit = { + def seenName(name: Name) = buf exists (_._1 == name) + def add(name: Name, t: Tree) = if (!seenName(name)) buf += ((name, t, namePos(tree, name))) + val bl = buf.length + + tree match { + case Bind(nme.WILDCARD, _) => + super.traverse(tree) + + case Bind(name, Typed(tree1, tpt)) => + val newTree = if (treeInfo.mayBeTypePat(tpt)) TypeTree() else tpt.duplicate + add(name, newTree) + traverse(tree1) + + case Bind(name, tree1) => + // can assume only name range as position, as otherwise might overlap + // with binds embedded in pattern tree1 + add(name, TypeTree()) + traverse(tree1) + + case _ => + super.traverse(tree) + } + if (buf.length > bl) + tree setPos tree.pos.makeTransparent + } + def apply(tree: Tree) = { + traverse(tree) + buf.toList + } + } + + /** Returns list of all pattern variables, possibly with their types, + * without duplicates + */ + private def getVariables(tree: Tree): List[(Name, Tree, Position)] = + new GetVarTraverser apply tree + + /** Convert all occurrences of (lower-case) variables in a pattern as follows: + * x becomes x @ _ + * x: T becomes x @ (_: T) + */ + object patvarTransformer extends Transformer { + override def transform(tree: Tree): Tree = tree match { + case Ident(name) if (treeInfo.isVarPattern(tree) && name != nme.WILDCARD) => + atPos(tree.pos)(Bind(name, atPos(tree.pos.focus) (Ident(nme.WILDCARD)))) + case Typed(id @ Ident(name), tpt) if (treeInfo.isVarPattern(id) && name != nme.WILDCARD) => + atPos(tree.pos.withPoint(id.pos.point)) { + Bind(name, atPos(tree.pos.withStart(tree.pos.point)) { + Typed(Ident(nme.WILDCARD), tpt) + }) + } + case Apply(fn @ Apply(_, _), args) => + treeCopy.Apply(tree, transform(fn), transformTrees(args)) + case Apply(fn, args) => + treeCopy.Apply(tree, fn, transformTrees(args)) + case Typed(expr, tpt) => + treeCopy.Typed(tree, transform(expr), tpt) + case Bind(name, body) => + treeCopy.Bind(tree, name, transform(body)) + case Alternative(_) | Star(_) => + super.transform(tree) + case _ => + tree + } + } + + // annotate the expression with @unchecked + def mkUnchecked(expr: Tree): Tree = atPos(expr.pos) { + // This can't be "Annotated(New(UncheckedClass), expr)" because annotations + // are very picky about things and it crashes the compiler with "unexpected new". + Annotated(New(scalaDot(tpnme.unchecked), Nil), expr) + } + + def mkSyntheticParam(pname: TermName) = + ValDef(Modifiers(PARAM | SYNTHETIC), pname, TypeTree(), EmptyTree) } diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 743c674eea..af0af8afe8 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -490,7 +490,7 @@ trait Trees extends api.Trees { case class Ident(name: Name) extends RefTree with IdentContextApi { def qualifier: Tree = EmptyTree - def isBackquoted = this.attachments.get[BackquotedIdentifierAttachment.type].isDefined + def isBackquoted = this.hasAttachment[BackquotedIdentifierAttachment.type] } object Ident extends IdentExtractor diff --git a/src/reflect/scala/reflect/macros/Attachments.scala b/src/reflect/scala/reflect/macros/Attachments.scala index c1ab269268..039e75fbee 100644 --- a/src/reflect/scala/reflect/macros/Attachments.scala +++ b/src/reflect/scala/reflect/macros/Attachments.scala @@ -41,6 +41,10 @@ abstract class Attachments { self => def get[T: ClassTag]: Option[T] = (all filter matchesTag[T]).headOption.asInstanceOf[Option[T]] + /** Check underlying payload contains an instance of type `T`. */ + def contains[T: ClassTag]: Boolean = + all exists matchesTag[T] + /** Creates a copy of this attachment with the payload slot of T added/updated with the provided value. * Replaces an existing payload of the same type, if exists. */ diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index 4d69a6673c..711456f6c7 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -53,8 +53,9 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.perRunCaches this.FixedMirrorTreeCreator this.FixedMirrorTypeCreator - this.BackquotedIdentifierAttachment this.CompoundTypeTreeOriginalAttachment + this.BackquotedIdentifierAttachment + this.ForAttachment this.noPrint this.typeDebug // inaccessible: this.maxFree diff --git a/test/files/scalacheck/quasiquotes/ArbitraryTreesAndNames.scala b/test/files/scalacheck/quasiquotes/ArbitraryTreesAndNames.scala index 4118d92076..c4b93dae48 100644 --- a/test/files/scalacheck/quasiquotes/ArbitraryTreesAndNames.scala +++ b/test/files/scalacheck/quasiquotes/ArbitraryTreesAndNames.scala @@ -1,11 +1,6 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.api._ -import scala.reflect.runtime.universe._ -import scala.reflect.runtime.universe.Flag._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.api.{Liftable, Universe} +import scala.reflect.runtime.universe._, Flag._ trait ArbitraryTreesAndNames { def smallList[T](size: Int, g: Gen[T]) = { diff --git a/test/files/scalacheck/quasiquotes/DefinitionConstructionProps.scala b/test/files/scalacheck/quasiquotes/DefinitionConstructionProps.scala index e8ddb4b72a..2ec679e78b 100644 --- a/test/files/scalacheck/quasiquotes/DefinitionConstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/DefinitionConstructionProps.scala @@ -1,11 +1,5 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.runtime.universe._ -import scala.reflect.runtime.universe.build.ScalaDot -import Flag._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._, build.ScalaDot object DefinitionConstructionProps extends QuasiquoteProperties("definition construction") diff --git a/test/files/scalacheck/quasiquotes/DefinitionDeconstructionProps.scala b/test/files/scalacheck/quasiquotes/DefinitionDeconstructionProps.scala index 993ef899b0..dbd26bf72a 100644 --- a/test/files/scalacheck/quasiquotes/DefinitionDeconstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/DefinitionDeconstructionProps.scala @@ -1,10 +1,5 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.runtime.universe._ -import Flag._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._ object DefinitionDeconstructionProps extends QuasiquoteProperties("definition deconstruction") diff --git a/test/files/scalacheck/quasiquotes/ErrorProps.scala b/test/files/scalacheck/quasiquotes/ErrorProps.scala index b0a7641577..cb46a60dbe 100644 --- a/test/files/scalacheck/quasiquotes/ErrorProps.scala +++ b/test/files/scalacheck/quasiquotes/ErrorProps.scala @@ -1,10 +1,4 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.runtime.universe._ -import Flag._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ object ErrorProps extends QuasiquoteProperties("errors") { property("can't extract two .. cardinalities in a row") = fails( diff --git a/test/files/scalacheck/quasiquotes/ForProps.scala b/test/files/scalacheck/quasiquotes/ForProps.scala new file mode 100644 index 0000000000..e71822aaea --- /dev/null +++ b/test/files/scalacheck/quasiquotes/ForProps.scala @@ -0,0 +1,70 @@ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._, build.{Ident => _, _} + +object ForProps extends QuasiquoteProperties("for") { + case class ForEnums(val value: List[Tree]) + + def genSimpleBind: Gen[Bind] = + for(name <- genTermName) + yield pq"$name @ _" + + def genForFilter: Gen[Tree] = + for(cond <- genIdent(genTermName)) + yield fq"if $cond" + + def genForFrom: Gen[Tree] = + for(lhs <- genSimpleBind; rhs <- genIdent(genTermName)) + yield fq"$lhs <- $rhs" + + def genForEq: Gen[Tree] = + for(lhs <- genSimpleBind; rhs <- genIdent(genTermName)) + yield fq"$lhs = $rhs" + + def genForEnums(size: Int): Gen[ForEnums] = + for(first <- genForFrom; rest <- listOfN(size, oneOf(genForFrom, genForFilter, genForEq))) + yield new ForEnums(first :: rest) + + implicit val arbForEnums: Arbitrary[ForEnums] = arbitrarySized(genForEnums) + + property("construct-reconstruct for") = forAll { (enums: ForEnums, body: Tree) => + val SyntacticFor(recoveredEnums, recoveredBody) = SyntacticFor(enums.value, body) + recoveredEnums ≈ enums.value && recoveredBody ≈ body + } + + property("construct-reconstruct for-yield") = forAll { (enums: ForEnums, body: Tree) => + val SyntacticForYield(recoveredEnums, recoveredBody) = SyntacticForYield(enums.value, body) + recoveredEnums ≈ enums.value && recoveredBody ≈ body + } + + val abcde = List(fq"a <-b", fq"if c", fq"d = e") + val foobarbaz = pq"foo @ Bar(baz)" + val fv = q"f(v)" + + property("construct/deconstruct for loop with fq") = test { + val for0 = q"for(..$abcde) $fv" + assertEqAst(for0, "for(a <- b; if c; d = e) f(v)") + val q"for(..$enums) $body" = for0 + assert(enums ≈ abcde) + assert(body ≈ fv) + } + + property("construct/deconstruct valfrom with fq") = test { + assert(fq"$foobarbaz <- $fv" ≈ fq"foo @ Bar(baz) <- f(v)") + val fq"$lhs <- $rhs" = fq"$foobarbaz <- $fv" + assert(lhs ≈ foobarbaz) + assert(rhs ≈ fv) + } + + property("construct/deconstruct valeq with fq") = test { + assert(fq"$foobarbaz = $fv" ≈ fq"foo @ Bar(baz) = f(v)") + val fq"$lhs = $rhs" = fq"$foobarbaz = $fv" + assert(lhs ≈ foobarbaz) + assert(rhs ≈ fv) + } + + property("construct/deconstruct filter with fq") = test { + assert(fq"if $fv" ≈ fq"if f(v)") + val fq"if $cond" = fq"if $fv" + assert(cond ≈ fv) + } +}
\ No newline at end of file diff --git a/test/files/scalacheck/quasiquotes/LiftableProps.scala b/test/files/scalacheck/quasiquotes/LiftableProps.scala index 510ab99068..1271e1accd 100644 --- a/test/files/scalacheck/quasiquotes/LiftableProps.scala +++ b/test/files/scalacheck/quasiquotes/LiftableProps.scala @@ -1,10 +1,5 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.runtime.universe._ -import Flag._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._ object LiftableProps extends QuasiquoteProperties("liftable") { property("splice byte") = test { diff --git a/test/files/scalacheck/quasiquotes/PatternConstructionProps.scala b/test/files/scalacheck/quasiquotes/PatternConstructionProps.scala index 504cb2a77d..582e915258 100644 --- a/test/files/scalacheck/quasiquotes/PatternConstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/PatternConstructionProps.scala @@ -1,10 +1,5 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.runtime.universe._ -import Flag._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._ object PatternConstructionProps extends QuasiquoteProperties("pattern construction") { property("splice bind") = forAll { (bind: Bind) => diff --git a/test/files/scalacheck/quasiquotes/PatternDeconstructionProps.scala b/test/files/scalacheck/quasiquotes/PatternDeconstructionProps.scala index f73fd29b22..cccf8095db 100644 --- a/test/files/scalacheck/quasiquotes/PatternDeconstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/PatternDeconstructionProps.scala @@ -1,11 +1,5 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.runtime.universe._ -import Flag._ -import definitions._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._ object PatternDeconstructionProps extends QuasiquoteProperties("pattern deconstruction") { property("extract bind") = forAll { (bind: Bind) => diff --git a/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala b/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala index 6a531071bf..b2bce124ee 100644 --- a/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala +++ b/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala @@ -1,15 +1,7 @@ -import scala.reflect.runtime.universe._ -import scala.reflect.runtime.universe.definitions._ -import scala.reflect.runtime.universe.Flag._ -import scala.reflect.runtime.currentMirror -import scala.reflect.api.{Liftable, Universe} -import scala.reflect.macros.TypecheckException +import org.scalacheck._, Prop._, Gen._, Arbitrary._ import scala.tools.reflect.{ToolBox, ToolBoxError} - -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ +import scala.reflect.runtime.currentMirror +import scala.reflect.runtime.universe._, Flag._ class QuasiquoteProperties(name: String) extends Properties(name) with ArbitraryTreesAndNames with Helpers @@ -18,14 +10,36 @@ trait Helpers { * if no exception has been thrown while executing code * block. This is useful for simple one-off tests. */ - def test[T](block: => T)= - Prop { (params) => + def test[T](block: => T) = + Prop { params => block Result(Prop.Proof) } + object simplify extends Transformer { + object SimplifiedName { + def unapply[T <: Name](name: T): Option[T] = + name.toString.split("\\$").toSeq match { + case first :+ last if scala.util.Try(last.toInt).isSuccess && first.nonEmpty => + val value = first.mkString("", "$", "$") + Some((if (name.isTermName) TermName(value) else TypeName(value)).asInstanceOf[T]) + case _ => None + } + } + + override def transform(tree: Tree): Tree = tree match { + case Ident(SimplifiedName(name)) => Ident(name) + case ValDef(mods, SimplifiedName(name), tpt, rhs) => ValDef(mods, name, tpt, rhs) + case Bind(SimplifiedName(name), rhs) => Bind(name, rhs) + case _ => + super.transform(tree) + } + + def apply(tree: Tree): Tree = transform(tree) + } + implicit class TestSimilarTree(tree1: Tree) { - def ≈(tree2: Tree) = tree1.equalsStructure(tree2) + def ≈(tree2: Tree) = simplify(tree1).equalsStructure(simplify(tree2)) } implicit class TestSimilarListTree(lst: List[Tree]) { @@ -68,6 +82,18 @@ trait Helpers { val compile = toolbox.compile(_) val eval = toolbox.eval(_) + def typecheck(tree: Tree) = toolbox.typeCheck(tree) + + def typecheckTyp(tree: Tree) = { + val q"type $_ = $res" = typecheck(q"type T = $tree") + res + } + + def typecheckPat(tree: Tree) = { + val q"$_ match { case $res => }" = typecheck(q"((): Any) match { case $tree => }") + res + } + def fails(msg: String, block: String) = { def result(ok: Boolean, description: String = "") = { val status = if (ok) Prop.Proof else Prop.False diff --git a/test/files/scalacheck/quasiquotes/TermConstructionProps.scala b/test/files/scalacheck/quasiquotes/TermConstructionProps.scala index f68656d0f7..cdd96205de 100644 --- a/test/files/scalacheck/quasiquotes/TermConstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/TermConstructionProps.scala @@ -1,10 +1,5 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.runtime.universe._ -import Flag._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._ object TermConstructionProps extends QuasiquoteProperties("term construction") { property("splice single tree return tree itself") = forAll { (t: Tree) => @@ -148,8 +143,8 @@ object TermConstructionProps extends QuasiquoteProperties("term construction") { val a1 = q"a1" val a2 = q"a2" val as = List(a1, a2) - assert(q"(..$as)" ≈ q"Tuple2($a1, $a2)") - assert(q"(a0, ..$as)" ≈ q"Tuple3(a0, $a1, $a2)") + assert(q"(..$as)" ≈ q"scala.Tuple2($a1, $a2)") + assert(q"(a0, ..$as)" ≈ q"scala.Tuple3(a0, $a1, $a2)") } property("splice empty list into tuple") = test { @@ -193,11 +188,11 @@ object TermConstructionProps extends QuasiquoteProperties("term construction") { property("fresh names are regenerated at each evaluation") = test { def plusOne = q"{ _ + 1 }" - assert(!(plusOne ≈ plusOne)) + assert(!plusOne.equalsStructure(plusOne)) def whileTrue = q"while(true) false" - assert(!(whileTrue ≈ whileTrue)) + assert(!whileTrue.equalsStructure(whileTrue)) def withEvidence = q"def foo[T: X]" - assert(!(withEvidence ≈ withEvidence)) + assert(!withEvidence.equalsStructure(withEvidence)) } property("make sure inference doesn't infer any") = test { diff --git a/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala b/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala index f37e4d9975..bd81afa125 100644 --- a/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala @@ -1,10 +1,5 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.runtime.universe._ -import Flag._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._ object TermDeconstructionProps extends QuasiquoteProperties("term deconstruction") { property("f(..x) = f") = test { diff --git a/test/files/scalacheck/quasiquotes/Test.scala b/test/files/scalacheck/quasiquotes/Test.scala index f41d961888..8b1e779ab2 100644 --- a/test/files/scalacheck/quasiquotes/Test.scala +++ b/test/files/scalacheck/quasiquotes/Test.scala @@ -12,4 +12,6 @@ object Test extends Properties("quasiquotes") { include(DefinitionConstructionProps) include(DefinitionDeconstructionProps) include(DeprecationProps) + include(ForProps) + include(TypecheckedProps) } diff --git a/test/files/scalacheck/quasiquotes/TypeConstructionProps.scala b/test/files/scalacheck/quasiquotes/TypeConstructionProps.scala index cac83ff8ac..be7a96d91e 100644 --- a/test/files/scalacheck/quasiquotes/TypeConstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/TypeConstructionProps.scala @@ -1,10 +1,5 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.runtime.universe._ -import Flag._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._ object TypeConstructionProps extends QuasiquoteProperties("type construction") { property("bare idents contain type names") = test { @@ -18,9 +13,9 @@ object TypeConstructionProps extends QuasiquoteProperties("type construction") property("tuple type") = test { val empty = List[Tree]() val ts = List(tq"t1", tq"t2") - assert(tq"(..$empty)" ≈ tq"scala.Unit") - assert(tq"(..$ts)" ≈ tq"Tuple2[t1, t2]") - assert(tq"(t0, ..$ts)" ≈ tq"Tuple3[t0, t1, t2]") + assert(tq"(..$empty)" ≈ build.ScalaDot(TypeName("Unit"))) + assert(tq"(..$ts)" ≈ tq"scala.Tuple2[t1, t2]") + assert(tq"(t0, ..$ts)" ≈ tq"scala.Tuple3[t0, t1, t2]") } property("refined type") = test { diff --git a/test/files/scalacheck/quasiquotes/TypeDeconstructionProps.scala b/test/files/scalacheck/quasiquotes/TypeDeconstructionProps.scala index e1d5f4df96..499f5d6d8e 100644 --- a/test/files/scalacheck/quasiquotes/TypeDeconstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/TypeDeconstructionProps.scala @@ -1,10 +1,5 @@ -import org.scalacheck._ -import Prop._ -import Gen._ -import Arbitrary._ - -import scala.reflect.runtime.universe._ -import Flag._ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._ object TypeDeconstructionProps extends QuasiquoteProperties("type deconstruction") { property("ident(type name)") = forAll { (name: TypeName) => diff --git a/test/files/scalacheck/quasiquotes/TypecheckedProps.scala b/test/files/scalacheck/quasiquotes/TypecheckedProps.scala new file mode 100644 index 0000000000..f443330e0b --- /dev/null +++ b/test/files/scalacheck/quasiquotes/TypecheckedProps.scala @@ -0,0 +1,53 @@ +import org.scalacheck._, Prop._, Gen._, Arbitrary._ +import scala.reflect.runtime.universe._, Flag._, build.{Ident => _, _} + +object TypecheckedProps extends QuasiquoteProperties("typechecked") { + def original(tree: Tree) = tree match { + case tt: TypeTree => Some(tt.original) + case _ => None + } + def originals(trees: List[Tree]) = trees.flatMap(original) + val int = ScalaDot(TypeName("Int")) + val intint = List(int, int) + + property("tuple term") = test { + val q"(..$elements)" = typecheck(q"(1, 2)") + assert(elements ≈ List(q"1", q"2")) + } + + property("tuple type") = test { + val tq"(..$els0)" = typecheckTyp(tq"Unit") + assert(els0.isEmpty) + val tq"(..$els1)" = typecheckTyp(tq"(Int, Int)") + assert(originals(els1) ≈ intint) + } + + property("function type") = test { + val tq"(..$argtpes) => $restpe" = typecheckTyp(tq"(Int, Int) => Int") + assert(originals(argtpes) ≈ intint) + assert(original(restpe).get ≈ int) + } + + property("for/for-yield") = test { + val enums = fq"x <- xs" :: fq"x1 = x + 1" :: fq"if x1 % 2 == 0" :: Nil + val body = q"x1" + val xs = q"val xs = List(1, 2, 3)" + val q"$_; for(..$enums0) yield $body0" = typecheck(q"$xs; for(..$enums) yield $body") + assert(enums0 ≈ enums) + assert(body0 ≈ body) + val q"$_; for(..$enums1) $body1" = typecheck(q"$xs; for(..$enums) $body") + assert(enums1 ≈ enums) + assert(body1 ≈ body) + } + + property("for .filter instead of .withFilter") = test { + val enums = fq"foo <- new Foo" :: fq"if foo != null" :: Nil + val body = q"foo" + val q"$_; for(..$enums1) yield $body1" = typecheck(q""" + class Foo { def map(f: Any => Any) = this; def filter(cond: Any => Boolean) = this } + for(..$enums) yield $body + """) + assert(enums1 ≈ enums) + assert(body1 ≈ body) + } +}
\ No newline at end of file |