Skip to content

Commit

Permalink
Merge pull request scalamacros#79 from xeno-by/master
Browse files Browse the repository at this point in the history
more converter tests
  • Loading branch information
xeno-by authored Oct 14, 2016
2 parents ce6aa6d + b3bc7cf commit 37c710b
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ trait ToMtree { self: Converter =>
val mname = lname.toMtree[m.Name.Qualifier]
m.Term.This(mname)

case l.TermSuper(lthis, lsuper) =>
val mthis = lthis.toMtree[m.Name.Qualifier]
val msuper = lsuper.toMtree[m.Name.Qualifier]
m.Term.Super(mthis, msuper)

case l.TermName(lvalue) =>
m.Term.Name(lvalue)

Expand All @@ -74,6 +79,14 @@ trait ToMtree { self: Converter =>
val mrhs = lrhs.toMtree[m.Term]
m.Term.Assign(mlhs, mrhs)

case l.TermReturn(lexpr) =>
val mexpr = lexpr.toMtree[m.Term]
m.Term.Return(mexpr)

case l.TermThrow(lexpr) =>
val mexpr = lexpr.toMtree[m.Term]
m.Term.Throw(mexpr)

case l.TermBlock(lstats) =>
val mstats = lstats.toMtrees[m.Stat]
m.Term.Block(mstats)
Expand All @@ -89,15 +102,26 @@ trait ToMtree { self: Converter =>
val mcases = lcases.toMtrees[m.Case]
m.Term.Match(mscrut, mcases)

case l.TermTryWithCases(lexpr, lcatches, lfinally) =>
val mexpr = lexpr.toMtree[m.Term]
val mcatches = lcatches.toMtrees[m.Case]
val mfinally = lfinally.toMtreeopt[m.Term]
m.Term.TryWithCases(mexpr, mcatches, mfinally)

case l.TermFunction(lparams, lbody) =>
val mparams = lparams.toMtrees[m.Term.Param]
val mbody = lbody.toMtree[m.Term]
m.Term.Function(mparams, mbody)

case l.TermWhile(lcond, lbody) =>
val mcond = lcond.toMtree[m.Term]
case l.TermWhile(lexpr, lbody) =>
val mexpr = lexpr.toMtree[m.Term]
val mbody = lbody.toMtree[m.Term]
m.Term.While(mcond, mbody)
m.Term.While(mexpr, mbody)

case l.TermDo(lbody, lexpr) =>
val mbody = lbody.toMtree[m.Term]
val mexpr = lexpr.toMtree[m.Term]
m.Term.Do(mbody, mexpr)

case l.TermNew(ltempl) =>
val mtempl = ltempl.toMtree[m.Template]
Expand Down Expand Up @@ -127,10 +151,15 @@ trait ToMtree { self: Converter =>
case l.TypeIdent(lname) =>
lname.toMtree[m.Type.Name]

case l.TypeSelect(lpre, lname) =>
val mpre = lpre.toMtree[m.Term.Ref]
case l.TypeSelect(lqual, lname) =>
val mqual = lqual.toMtree[m.Term.Ref]
val mname = lname.toMtree[m.Type.Name]
m.Type.Select(mqual, mname)

case l.TypeProject(lqual, lname) =>
val mqual = lqual.toMtree[m.Type]
val mname = lname.toMtree[m.Type.Name]
m.Type.Select(mpre, mname)
m.Type.Project(mqual, mname)

case l.TypeApply(ltpt, largs) =>
val mtpt = ltpt.toMtree[m.Type]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,23 @@ trait LogicalTrees { self: ReflectToolkit =>
// ============ TERMS ============

object TermThis {
// qual
def unapply(tree: g.This): Option[l.QualifierName] = {
if (tree.qual == tpnme.EMPTY) Some(l.AnonymousName())
else Some(l.IndeterminateName(tree.displayName))
}
}

object TermSuper {
def unapply(tree: g.Super): Option[(l.QualifierName, l.QualifierName)] = {
val g.Super(l.TermThis(lthis), qual) = tree
val lsuper = {
if (qual == tpnme.EMPTY) l.AnonymousName()
else l.IndeterminateName(qual.displayName)
}
Some((lthis, lsuper))
}
}

case class TermName(value: String) extends Name with TermParamName
object TermName {
def apply(tree: g.NameTree): l.TermName = {
Expand Down Expand Up @@ -186,6 +196,7 @@ trait LogicalTrees { self: ReflectToolkit =>
object TermApply {
def unapply(tree: g.Apply): Option[(g.Tree, List[g.Tree])] = {
if (!tree.is(TermLoc)) return None
if (TermNew.unapply(tree).isDefined) return None
Some((tree.fun, tree.args))
}
}
Expand All @@ -202,8 +213,21 @@ trait LogicalTrees { self: ReflectToolkit =>
}
}

object TermReturn {
def unapply(tree: g.Return): Option[g.Tree] = {
Some(tree.expr)
}
}

object TermThrow {
def unapply(tree: g.Throw): Option[g.Tree] = {
Some(tree.expr)
}
}

object TermBlock {
def unapply(tree: g.Block): Option[List[g.Tree]] = {
if (TermNew.unapply(tree).isDefined) return None
val lstats = blockStats(tree.stats :+ tree.expr)
Some(lstats)
}
Expand All @@ -221,6 +245,13 @@ trait LogicalTrees { self: ReflectToolkit =>
}
}

object TermTryWithCases {
def unapply(tree: g.Try): Option[(g.Tree, List[g.Tree], Option[g.Tree])] = {
val lfinallyp = if (tree.finalizer != g.EmptyTree) Some(tree.finalizer) else None
Some((tree.block, tree.catches, lfinallyp))
}
}

object TermFunction {
def unapply(tree: g.Function): Option[(List[g.Tree], g.Tree)] = {
val g.Function(params, body) = tree
Expand All @@ -245,6 +276,22 @@ trait LogicalTrees { self: ReflectToolkit =>
}
}

object TermDo {
def unapply(tree: g.LabelDef): Option[(g.Tree, g.Tree)] = {
tree match {
case g.LabelDef(name1,
Nil,
g.Block(
List(body),
g.If(cond, g.Apply(Ident(name2), Nil), g.Literal(g.Constant(())))))
if name1 == name2 && name1.startsWith(nme.DO_WHILE_PREFIX) =>
Some((body, cond))
case _ =>
None
}
}
}

object TermNew {
def unapply(tree: g.Tree): Option[l.Template] = tree match {
case g.Apply(g.Select(g.New(tpt), nme.CONSTRUCTOR), args) =>
Expand All @@ -253,6 +300,13 @@ trait LogicalTrees { self: ReflectToolkit =>
g.ValDef(g.Modifiers(), g.nme.WILDCARD, g.TypeTree(), g.EmptyTree)
.set(SelfRole(g.EmptyTree))
Some(l.Template(Nil, List(lparent), lself, None))
case g.Block(
List(
tree @ g
.ClassDef(g.Modifiers(FINAL, g.tpnme.EMPTY, Nil), g.TypeName(anon1), Nil, templ)),
g.Apply(g.Select(g.New(g.Ident(g.TypeName(anon2))), nme.CONSTRUCTOR), args))
if anon1 == tpnme.ANON_CLASS_NAME.toString && anon2 == tpnme.ANON_CLASS_NAME.toString =>
Some(l.Template(tree))
case _ =>
None
}
Expand Down Expand Up @@ -319,6 +373,13 @@ trait LogicalTrees { self: ReflectToolkit =>
}
}

object TypeProject {
def unapply(tree: g.SelectFromTypeTree): Option[(g.Tree, l.TypeName)] = {
val g.SelectFromTypeTree(qual, name) = tree
Some((qual, l.TypeName(tree)))
}
}

object TypeApply {
def unapply(tree: g.AppliedTypeTree): Option[(g.Tree, List[g.Tree])] = {
Some((tree.tpt, tree.args))
Expand Down Expand Up @@ -398,7 +459,7 @@ trait LogicalTrees { self: ReflectToolkit =>
case head :: Nil => head
case trees @ (head :: tail) => g.Alternative(trees)
}
Some((llhs, lrhs))
Some((llhs.set(PatLoc), lrhs.set(PatLoc)))
}
}

Expand Down Expand Up @@ -1008,14 +1069,15 @@ trait LogicalTrees { self: ReflectToolkit =>
seenPrimaryCtor = true // skip this
case g.DefDef(_, nme.MIXIN_CONSTRUCTOR, _, _, _, _) => // and this
case g.ValDef(mods, _, _, _) if mods.hasFlag(PARAMACCESSOR) => // and this
case g.EmptyTree => // and this
case _ => lresult += stat
}
}
lresult.toList
}

private def blockStats(stats: List[g.Tree]): List[g.Tree] = {
stats
stats.filter(_ != g.EmptyTree)
}
}

Expand Down
96 changes: 90 additions & 6 deletions tests/converter/src/main/scala/ConverterSuite.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import scala.collection.immutable.Seq
import scala.{meta => m}
import scala.tools.cmd.CommandLineParser
import scala.tools.nsc.{Global, CompilerCommand, Settings}
Expand All @@ -23,7 +24,87 @@ trait ConverterSuite extends FunSuite {
g
}

def syntactic(code: String) {
case class MismatchException(details: String) extends Exception
private def checkMismatchesModuloDesugarings(parsed: m.Tree, converted: m.Tree): Unit = {
import scala.meta._
def loop(x: Any, y: Any): Boolean = {
val ok = (x, y) match {
case (x, y) if x == null || y == null =>
x == null && y == null
case (x: Some[_], y: Some[_]) =>
loop(x.get, y.get)
case (x: None.type, y: None.type) =>
true
case (xs: Seq[_], ys: Seq[_]) =>
xs.length == ys.length && xs.zip(ys).forall { case (x, y) => loop(x, y) }
case (x: Tree, y: Tree) =>
def sameDesugaring = {
// NOTE: Workaround for https://github.com/scalameta/scalameta/issues/519.
object TermApply519 {
def unapply(tree: Tree): Option[(Term, Seq[Seq[Type.Arg]], Seq[Seq[Term.Arg]])] =
tree match {
case q"$fun[..$targs](...$argss)" => Some((fun, Seq(targs), argss))
case q"$fun(...$argss)" => Some((fun, Nil, argss))
case _ => None
}
}

// NOTE: This is a desugaring performed by the scala.reflect parser.
// We may want to undo it in the converter.
object TermApplyInfixRightAssoc {
def unapply(tree: Tree): Option[(Term, Term.Name, Seq[Type.Arg], Seq[Term.Arg])] =
tree match {
case q"{ val $tmp1 = $lhs; ${ TermApply519(q"$rhs.$op", targss, Seq(Seq(tmp2))) } }"
if tmp1.syntax == tmp2.syntax && tmp1.syntax.contains("$") =>
val args = rhs match {
case q"$tuple(..$args)" if tuple.syntax.startsWith("scala.Tuple") => args
case arg => Seq(arg)
}
Some((lhs, op, targss.flatten, args))
case _ =>
None
}
}

(x, y) match {
case (q"$xlhs $xop [..$xtargs] (..$xargs)",
TermApply519(q"$ylhs.$yop", ytargss, Seq(yargs))) =>
loop(xlhs, ylhs) && loop(xop, yop) && loop(xtargs, ytargss.flatten) && loop(xargs,
yargs)
case (q"$xlhs $xop [..$xtargs] (..$xargs)",
TermApplyInfixRightAssoc(ylhs, yop, ytargs, yargs)) =>
loop(xlhs, ylhs) && loop(xop, yop) && loop(xtargs, ytargs) && loop(xargs, yargs)
case (q"{}", q"()") =>
true
case (q"{ $xstat }", q"$ystat") =>
loop(xstat, ystat)
case (q"(..$xargs)", q"$tuple(..$yargs)")
if tuple.syntax.startsWith("scala.Tuple") =>
loop(xargs, yargs)
case (ctor"$xctor(...${ Seq() })", ctor"$yctor(...${ Seq(Seq()) })") =>
loop(xctor, yctor)
case (xpat, p"$ypat @ _") =>
loop(xpat, ypat)
case (p"$xlhs: $xtpe", p"$ylhs @ (_: $ytpe)") =>
loop(xlhs, ylhs)
case _ =>
false
}
}
def sameStructure =
x.productPrefix == y.productPrefix && loop(x.productIterator.toList,
y.productIterator.toList)
sameDesugaring || sameStructure
case _ =>
x == y
}
if (!ok) throw MismatchException(s"$x != $y")
else true
}
loop(parsed, converted)
}

def syntactic(code: String): Unit = {
test(code.trim) {
val parsedScalacTree: g.Tree = {
import g._
Expand Down Expand Up @@ -51,11 +132,14 @@ trait ConverterSuite extends FunSuite {
converter(parsedScalacTree)
}

// TODO: account for the fact that scala.reflect desugars stuff (e.g. for loops) even during parsing
// TODO: alternatively, we can just go ahead and undesugar for loops, because for syntactic APIs that's actually easy
if (parsedMetaTree.structure != convertedMetaTree.structure) {
fail(
s"scala -> meta converter error\nparsed tree:\n${parsedMetaTree.structure}\nconverted tree\n${convertedMetaTree.structure}")
try {
checkMismatchesModuloDesugarings(parsedMetaTree, convertedMetaTree)
} catch {
case MismatchException(details) =>
val header = s"scala -> meta converter error\n$details"
val fullDetails =
s"parsed tree:\n${parsedMetaTree.structure}\nconverted tree:\n${convertedMetaTree.structure}"
fail(s"$header\n$fullDetails")
}
}
}
Expand Down
Loading

0 comments on commit 37c710b

Please sign in to comment.