aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/scala/generic.scala
blob: c045cbc6d90b6cf820bde76f85d38656a547c11b (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
package magnolia

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

import scala.collection.immutable.ListMap

object GlobalMutableState {
  private[magnolia] var state: ListMap[AnyRef, AnyRef] = ListMap()
  private[magnolia] def push(key: AnyRef, value: AnyRef): Unit = state += ((key, value))
  private[magnolia] def pop(): Unit = state = state.init
  
  private[magnolia] def has(c: whitebox.Context)(key: AnyRef): Option[c.universe.TermName] =
    state.get(key).asInstanceOf[Option[c.universe.TermName]]
}

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

  def dereference(path: c.Tree, elem: String): c.Tree = path

  def getImplicit(genericType: c.universe.Type,
                  typeConstructor: c.universe.Type,
                  myName: c.universe.TermName): c.Tree = {
    
    import c.universe._
   
    GlobalMutableState.push(genericType, myName)
    
    val result = GlobalMutableState.has(c)(genericType).map { nm => q"$nm" }.orElse {
      val searchType = appliedType(typeConstructor, genericType)
      if(GlobalMutableState.has(c)(genericType).isEmpty) {
        val inferredImplicit =
          try Some(c.inferImplicitValue(searchType, false, false)) catch {
            case e: Exception => None
          }
        
        inferredImplicit.orElse {
          directInferImplicit(genericType, typeConstructor)
        }
      } else {
        directInferImplicit(genericType, typeConstructor)
      }
    }.getOrElse {
      c.abort(c.enclosingPosition, "Could not find extractor for type "+genericType)
    }

    GlobalMutableState.pop()

    result
  }
  
  def directInferImplicit(genericType: c.universe.Type,
         typeConstructor: c.universe.Type): Option[c.Tree] = {
    import c.universe._
   
    val myName = TermName(c.freshName("extractor$"))
    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, myName)
        q"$imp.extract(src)"
      }

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

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

    //GlobalMutableState.pop()

    println(result)

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

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

    val result = directInferImplicit(genericType, typeConstructor)

    result.getOrElse {
      c.abort(c.enclosingPosition, "Could not infer extractor. Sorry.")
    }
  }

}