scalameta/scalahost — pull request #5 by xeno-by (2014-08-24 11:20:02+00:00)
Title: a bunch of low-tech tree prettification techniques

What I've been doing lately is prettifying the trees that come out of the converter, so that they are in closer correspondence with what the programmer actually writes as opposed to what the typechecker produces based on the programmer's input.

```
13:07 ~/Projects/scalahost/sandbox (master)$ git diff
diff --git a/plugin/persistence/PersistencePhase.scala b/plugin/persistence/PersistencePhase.scala
index 53fd04e124..de7f7e9111 100644
--- a/plugin/persistence/PersistencePhase.scala
+++ b/plugin/persistence/PersistencePhase.scala
@@ -39,6 +39,11 @@ trait PersistencePhase {

override def apply(unit: CompilationUnit) {
val punit = h.toPalladium(unit.body, classOf[Aux.CompUnit])
+ println("\n\n\n=======\n\n\n")
+ import scala.meta.syntactic.show._
+ println(unit.body)
+ println(punit.show[Code])
+ println(punit.show[Raw])
// ???
}
}
```

With the aforementioned diff in place, it was enough to just run `sbt test` and get the printout of all ASTs of the files in the `test` subproject. I think the current result is pretty good (see the printouts below; actual code goes first, converted code goes second).

The plan for the nearest future
===
1. Remember originals of `RefTree`s, so that we don't pollute the trees (and consequently the printouts with meaningless qualifiers). This is something relatively high-tech, because it will require changes to the typechecker.
1. Run the converter through a couple of big files in the Scala compiler (e.g. `Typers.scala` or `Symbols.scala`) to tune it even more.
1. Codify the results of converting and prettyprinting of the manual tests performed so far into an automatic suite.
1. Proceed with implementing the rest of `scala.meta`.

Plugin.scala
===
```
import scala.tools.nsc.{Global, Phase, SubComponent}
import scala.tools.nsc.plugins.{Plugin => NscPlugin, PluginComponent => NscPluginComponent}

class Plugin(val global: Global) extends NscPlugin { self =>
val name = "attatest"
val description = "Tests attachments produced by scalahost"
val components = List[NscPluginComponent](PluginComponent)
object PluginComponent extends NscPluginComponent {
val global: self.global.type = self.global
import global._

override val runsAfter = List("typer")
override val runsRightAfter = None
val phaseName = "attatest"
override def description = "tests attachments produced by scalahost"

override def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
import analyzer._
override def apply(unit: CompilationUnit) {
unit.body.foreach(tree => {
if (hasMacroExpansionAttachment(tree)) {
tree.attachments.get[java.util.HashMap[String, Any]] match {
case None =>
reporter.error(tree.pos, "macro expansion without a property bag")
case Some(bag) =>
if (!bag.containsKey("expandeeTree")) reporter.error(tree.pos, "macro expansion without expandeeTree in the property bag")
if (!bag.containsKey("expandedTree")) reporter.error(tree.pos, "macro expansion without expandedTree in the property bag")
if (!bag.containsKey("expansionString")) reporter.error(tree.pos, "macro expansion without expansionString in the property bag")
}
}
})
}
}
}
}
```
```
import scala.tools.nsc.{ Global, Phase, SubComponent }
import scala.tools.nsc.plugins.{ Plugin => NscPlugin, PluginComponent => NscPluginComponent }
class Plugin(val global: scala.tools.nsc.Global) extends scala.tools.nsc.plugins.Plugin { self =>
val name = "attatest"
val description = "Tests attachments produced by scalahost"
val components = scala.collection.immutable.List[scala.tools.nsc.plugins.PluginComponent](Plugin.this.PluginComponent)
object PluginComponent extends scala.tools.nsc.plugins.PluginComponent {
val global: Plugin.this.global.type = Plugin.this.global
import PluginComponent.this.global._
override val runsAfter = scala.collection.immutable.List("typer")
override val runsRightAfter = scala.None
val phaseName = "attatest"
override def description = "tests attachments produced by scalahost"
override def newPhase(prev: _root_.scala.tools.nsc.`package`.Phase): PluginComponent.this.StdPhase = new PluginComponent.this.StdPhase(prev) {
import PluginComponent.this.global.analyzer._
override def apply(unit: PluginComponent.this.global.CompilationUnit): scala.Unit = unit.body.foreach(tree => if (PluginComponent.this.global.analyzer.hasMacroExpansionAttachment(tree)) tree.attachments.get[java.util.HashMap[scala.Predef.String, scala.Any]] match {
case scala.None =>
PluginComponent.this.global.reporter.error(tree.pos, "macro expansion without a property bag")
case scala.Some(bag) =>
if (!bag.containsKey("expandeeTree")) PluginComponent.this.global.reporter.error(tree.pos, "macro expansion without expandeeTree in the property bag");
if (!bag.containsKey("expandedTree")) PluginComponent.this.global.reporter.error(tree.pos, "macro expansion without expandedTree in the property bag");
if (!bag.containsKey("expansionString")) PluginComponent.this.global.reporter.error(tree.pos, "macro expansion without expansionString in the property bag")
})
}
}
}
```

Macros.scala
===
```
import scala.reflect.macros.whitebox._
import scala.language.experimental.macros

object Join {
def impl(c: Context)(x: c.Tree, y: c.Tree) = {
import c.universe._
def fields(tree: Tree) = tree.tpe.members.collect{ case m: TermSymbol if m.isGetter => m }
val xfields = fields(x).map(f => f -> q"xtemp")
val yfields = fields(y).map(f => f -> q"ytemp")
val getters = (xfields ++ yfields).map{ case (f, ref) => q"val ${f.name} = $ref.${f.name}" }
q"""
val xtemp = $x
val ytemp = $y
new { ..$getters }
"""
}

def apply[T, U](x: T, y: U): Any = macro impl
}
```
```
import scala.reflect.macros.whitebox._
import scala.language.experimental.macros
object Join {
def impl(c: scala.reflect.macros.whitebox.Context)(x: c.Tree, y: c.Tree) = {
import c.universe._
def fields(tree: c.universe.Tree) = tree.tpe.members.collect({
case m: c.universe.TermSymbol if m.isGetter =>
m
})
val xfields = fields(x).map(f => f -> q"xtemp")
val yfields = fields(y).map(f => f -> q"ytemp")
val getters = (xfields ++ yfields).map({
case (f, ref) =>
q"val ${f.name} = $ref.${f.name}"
})
q"""
val xtemp = $x
val ytemp = $y
new { ..$getters }
"""
}
def apply[T, U](x: T, y: U): scala.Any = macro Join.impl
}
```

OldSuite.scala
===
```
import org.scalatest._

class OldSuite extends FunSuite {
test("old join") {
import scala.language.reflectiveCalls
val x = new { val x = 2 }
val y = new { val y = 3 }
val result = Join(x, y)
assert(result.x === 2)
assert(result.y === 3)
}
}
```
```
import org.scalatest._
class OldSuite extends org.scalatest.FunSuite {
OldSuite.this.test("old join") {
import scala.language.reflectiveCalls
val x = new { val x = 2 }
val y = new { val y = 3 }
val result = Join(x, y)
OldSuite.this.assert(result.x === 2)
OldSuite.this.assert(result.y === 3)
}
}
```

NewSuite.scala
===
```
import org.scalatest._

import scala.language.reflectiveCalls
import scala.language.experimental.macros
import scala.meta._
import scala.meta.semantic._
import errors.throwExceptions

class NewSuite extends FunSuite {
test("new join") {
def join[T, U](x: T, y: U): Any = macro {
val xfields = x.tpe.vals.map(f => f -> q"xtemp")
val yfields = y.tpe.vals.map(f => f -> q"ytemp")
val getters = (xfields ++ yfields).map{ case (f, ref) => q"val ${f.name} = $ref.${f.name}" }
c.whitebox(q"""
val xtemp = $x
val ytemp = $y
new { ..$getters }
""")
}
val result = join(new { val x = 2 }, new { val y = 3 })
assert(result.x === 2)
assert(result.y === 3)
}
}
```
```
import org.scalatest._
import scala.language.reflectiveCalls
import scala.language.experimental.macros
import scala.meta._
import scala.meta.semantic._
import scala.meta.semantic.errors.throwExceptions
class NewSuite extends org.scalatest.FunSuite {
NewSuite.this.test("new join") {
def join[T, U](x: T, y: U): scala.Any = macro {
val xfields = x.tpe.vals.map(f => f -> q"xtemp")
val yfields = y.tpe.vals.map(f => f -> q"ytemp")
val getters = (xfields ++ yfields).map({
case (f, ref) =>
q"val ${f.name} = $ref.${f.name}"
})
q"""
val xtemp = $x
val ytemp = $y
new { ..$getters }
"""
}
val result = join(new { val x = 2 }, new { val y = 3 })
NewSuite.this.assert(result.x === 2)
NewSuite.this.assert(result.y === 3)
}
}
```