summaryrefslogtreecommitdiff
path: root/src/main/scala/forge/DefCtx.scala
blob: 97f9bf3faeb9b4885908f9c3ea46b7d4a81cbfcb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package forge


import scala.annotation.compileTimeOnly
import scala.language.experimental.macros
import scala.reflect.macros.blackbox._


final case class DefCtx(label: String)
object DefCtx{
  @compileTimeOnly("A DefCtx can only be provided directly within a T{} macro")
  implicit def dummy: DefCtx with Int = ???
}

object T{
  def apply[T](expr: T): T = macro applyImpl[T]

  def applyImpl[T: c.WeakTypeTag](c: Context)(expr: c.Expr[T]): c.Expr[T] = {
    import c.universe._
    var count = 0
    object transformer extends c.universe.Transformer {
      override def transform(tree: c.Tree): c.Tree = {
        if (tree.toString.startsWith("forge.") && tree.toString.endsWith(".DefCtx.dummy")) {
          count += 1
          c.typecheck(q"forge.DefCtx(sourcecode.Enclosing() + $count)")
        }else tree match{
          case Apply(fun, args) =>
            val extendedParams = fun.tpe.paramLists.head.padTo(
              args.length,
              fun.tpe.paramLists.head.lastOption.getOrElse(null)
            )
            val newArgs =
              for((sym, tree) <- extendedParams.zip(args))
              yield {
                if (sym.asTerm.isByNameParam) tree
                else transform(tree)
              }
            treeCopy.Apply(tree, transform(fun), newArgs)

          case t: DefDef => t
          case t: ClassDef => t
          case t: Function => t
          case t: LabelDef => t
          case t => super.transform(t)
        }

      }
    }


    def transformTerminal(tree: c.Tree): c.Tree = tree match{
      case Block(stats, returnExpr) =>
        treeCopy.Block(
          tree,
          stats.map(transformer.transform(_)),
          transformTerminal(returnExpr)
        )

      case Apply(fun, args) =>
        var isTransformed = false
        val newArgs = for(x <- args) yield {
          if (x.toString.startsWith("forge.") && x.toString.endsWith(".DefCtx.dummy")) {
            isTransformed = true
            c.typecheck(q"forge.DefCtx(sourcecode.Enclosing())")
          }else transformer.transform(x)
        }

        assert(isTransformed)
        treeCopy.Apply(tree, transformer.transform(fun), newArgs)

      case _ => ???
    }

    val transformed = transformTerminal(expr.tree)
    c.Expr[T](transformed)
  }
}