aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/scala/generic.scala
blob: 63f3d8f6334dff35f77e5870d2e9a5d58775f117 (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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package magnolia

import scala.reflect._, macros._
import macrocompat.bundle

object GlobalState {
  var globalState: Map[AnyRef, AnyRef] = Map()
}

@bundle
class Macros(val c: whitebox.Context) {

  def getImplicit(genericType: c.universe.Type,
                  typeConstructor: c.universe.Type/*,
                  scope: Map[c.universe.Type, c.universe.TermName]*/): c.Tree = {
    
    import c.universe._
   
    val scope = GlobalState.globalState.asInstanceOf[Map[Type, TermName]]

    scope.get(genericType) match {
      case Some(ref) =>
        q"$ref"
      case None =>
        
      val searchType = appliedType(typeConstructor, genericType)
      println(s"${scope.keySet} vs $genericType")
      println(s"inferring on $genericType")
      try c.inferImplicitValue(searchType, false, false) catch {
        case e: Exception =>
          go(genericType, typeConstructor/*, scope*/)
      }
    }

    scope.get(genericType).map { nm =>
      println("substituting "+nm)
      q"$nm"
    }.orElse {
      val searchType = appliedType(typeConstructor, genericType)
      println(s"${scope.keySet} vs $genericType")
      if(!scope.keySet.contains(genericType)) {
        println(s"inferring on $genericType")
        Option({
          val x = try c.inferImplicitValue(searchType, false, false) catch {
            case e: Exception => null
          }
          println("Managed to infer "+x)
          x
        }).orElse {
          println("Failed, so recursing")
          go(genericType, typeConstructor/*, scope*/)
        }
      } else {
        println("recursing")
        go(genericType, typeConstructor/*, scope*/)
      }
    }.getOrElse {
      c.abort(c.enclosingPosition, "Could not find extractor for type "+genericType)
    }
  }
  
  def go(genericType: c.universe.Type,
         typeConstructor: c.universe.Type/*,
         scope: Map[c.universe.Type, c.universe.TermName]*/): Option[c.Tree] = {
    import c.universe._
   
    println(s"go($genericType, ${GlobalState.globalState})")

    
    val myName = TermName(c.freshName("extractor$"))
    println(s"before: ${GlobalState.globalState}")
    GlobalState.globalState = GlobalState.globalState + (genericType -> myName)
    println(s"after: ${GlobalState.globalState}")
    val typeSymbol = genericType.typeSymbol
    val classType = if(typeSymbol.isClass) Some(typeSymbol.asClass) else None
    val isCaseClass = classType.map(_.isCaseClass).getOrElse(false)
    val isSealedTrait = classType.map(_.isSealed).getOrElse(false)
    val isAnyVal = genericType <:< typeOf[AnyVal]
    
    val resultType = appliedType(typeConstructor, genericType)

    val construct = if(isCaseClass) {
      val implicits = genericType.decls.collect {
        case m: MethodSymbol if m.isCaseAccessor => m.asMethod
      }.map { p =>
        val ret = p.returnType
        val imp = getImplicit(ret, typeConstructor/*, newScope*/)
        q"$imp.extract(src)"
      }

      Some(q"new $genericType(..$implicits)")
    } else if(isSealedTrait) {
      //println(s"$resultType a sealed trait")
      val subtypes = classType.get.knownDirectSubclasses.to[List]
      
      val tries = subtypes.map(_.asType.toType).map(t => getImplicit(t, typeConstructor/*, newScope*/)).foldLeft(q"null": c.Tree) { (a, b) =>
        q"(try { $b.extract(src) } catch { case e: _root_.java.lang.Exception => $a })"
      }
      
      Some(q"$tries.asInstanceOf[$genericType]")
      
    } else None

    val result = construct.map { c =>
      q"""{
        def $myName: $resultType = new $resultType {
          def extract(src: _root_.java.lang.String): $genericType = $c
        }
        $myName
      }"""
    }

    //println(s"Generated result for $genericType: $result")

    result
  }
  def generic[T: c.WeakTypeTag, Tc: c.WeakTypeTag]: c.Tree = try {
    import c.universe._

    val genericType: Type = weakTypeOf[T]
    val typeConstructor: Type = weakTypeOf[Tc].typeConstructor

    val result = go(genericType, typeConstructor)

    println(result)

    result.getOrElse {
      c.abort(c.enclosingPosition, "Could not infer extractor. Sorry.")
    }
  } catch {
    case e: Exception =>
      println("Macro failed!!! "+e)
      //e.printStackTrace()
      ???
  }

}