aboutsummaryrefslogblamecommitdiff
path: root/core/src/main/scala/generic.scala
blob: c045cbc6d90b6cf820bde76f85d38656a547c11b (plain) (tree)
1
2
3
4
5




                                








                                                                                            




                                       

                                                            
                                               

                                                          


                       


                                                                                     
                                                                



                                                                          
           


                                                           

              
                                                         



                                                                                    



                            

   

                                                              

                       
                                                    












                                                                             
                                                           




                                             

                                                                 
                                                                                                                                
                                                                                       
        











                                                                      


                              


          

                                                              




                                                              
                                                                  



                                                                       



   
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.")
    }
  }

}