Skip to content

Commit

Permalink
Merge pull request #186 from twitter/ianoc/refactorMacros
Browse files Browse the repository at this point in the history
Ianoc/refactor macros
  • Loading branch information
johnynek committed Dec 12, 2014
2 parents d794c7e + 7106f33 commit 60c61f1
Show file tree
Hide file tree
Showing 15 changed files with 330 additions and 332 deletions.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package com.twitter.bijection.macros.common
package com.twitter.bijection.macros

import scala.language.experimental.macros
import com.twitter.bijection.macros.impl._

/**
* This trait is meant to be used exclusively to allow the type system to prove that a class is or is not a case class.
*/
object IsCaseClass {
implicit def isCaseClass[T]: IsCaseClass[T] = macro IsCaseClassImpl.isCaseClassImpl[T]
}

trait IsCaseClass[T]

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ package com.twitter.bijection.macros
import scala.language.experimental.macros

import com.twitter.bijection._
import com.twitter.bijection.macros.common.IsCaseClass
import com.twitter.bijection.macros.impl.MacroImpl
import com.twitter.bijection.macros.impl.{ CaseClassToTuple, CaseClassToMap, IsCaseClassImpl }

trait LowerPriorityMacroImplicits {
implicit def materializeCaseClassToTupleNonRecursive[T: IsCaseClass, Tup]: Bijection[T, Tup] = macro MacroImpl.caseClassToTupleImplNonRecursive[T, Tup]
implicit def materializeCaseClassToMapNonRecursive[T: IsCaseClass]: Injection[T, Map[String, Any]] = macro MacroImpl.caseClassToMapImplNonRecursive[T]
// Since implicit macro's aborting makes them just appear to never having existed
// its valid for this to be lower priority for the one in MacroImplicits.
// We will attempt this one if we don't see the other
implicit def materializeCaseClassToTupleNonRecursive[T: IsCaseClass, Tup]: Bijection[T, Tup] = macro CaseClassToTuple.caseClassToTupleImplNonRecursive[T, Tup]
}
object LowerPriorityMacroImplicits extends LowerPriorityMacroImplicits

object MacroImplicits extends LowerPriorityMacroImplicits {
implicit def materializeCaseClassToTuple[T: IsCaseClass, Tup]: Bijection[T, Tup] = macro MacroImpl.caseClassToTupleImpl[T, Tup]
implicit def materializeCaseClassToMap[T: IsCaseClass]: Injection[T, Map[String, Any]] = macro MacroImpl.caseClassToMapImpl[T]

implicit def materializeCaseClassToTuple[T: IsCaseClass, Tup]: Bijection[T, Tup] = macro CaseClassToTuple.caseClassToTupleImpl[T, Tup]
implicit def materializeCaseClassToMap[T: IsCaseClass]: Injection[T, Map[String, Any]] = macro CaseClassToMap.caseClassToMapImpl[T]

implicit def isCaseClass[T]: IsCaseClass[T] = macro IsCaseClassImpl.isCaseClassImpl[T]
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package com.twitter.bijection.macros
import scala.language.experimental.macros

import com.twitter.bijection._
import com.twitter.bijection.macros.common.IsCaseClass
import com.twitter.bijection.macros.impl.MacroImpl
import com.twitter.bijection.macros.impl.{ CaseClassToTuple, CaseClassToMap }

object Macros {
def caseClassToTuple[T: IsCaseClass, Tup](recursivelyApply: Boolean = true): Bijection[T, Tup] = macro MacroImpl.caseClassToTupleImplWithOption[T, Tup]
def caseClassToMap[T: IsCaseClass](recursivelyApply: Boolean = true): Injection[T, Map[String, Any]] = macro MacroImpl.caseClassToMapImplWithOption[T]
def caseClassToTuple[T: IsCaseClass, Tup](recursivelyApply: Boolean): Bijection[T, Tup] = macro CaseClassToTuple.caseClassToTupleImplWithOption[T, Tup]
def caseClassToMap[T: IsCaseClass](recursivelyApply: Boolean): Injection[T, Map[String, Any]] = macro CaseClassToMap.caseClassToMapImplWithOption[T]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.twitter.bijection.macros.impl

import scala.collection.mutable.{ Map => MMap }
import scala.language.experimental.macros
import scala.reflect.macros.Context
import scala.reflect.runtime.universe._
import scala.util.Try

import com.twitter.bijection._
import com.twitter.bijection.macros.{ IsCaseClass, MacroGenerated }

private[bijection] object CaseClassToMap {
def caseClassToMapImplWithOption[T](c: Context)(recursivelyApply: c.Expr[Boolean])(proof: c.Expr[IsCaseClass[T]])(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] = {
import c.universe._
recursivelyApply.tree match {
case q"""true""" => caseClassToMapNoProofImpl(c)(T)
case q"""false""" => caseClassToMapNoProofImplNonRecursive(c)(T)
}
}

def caseClassToMapImpl[T](c: Context)(proof: c.Expr[IsCaseClass[T]])(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] =
caseClassToMapNoProofImpl(c)(T)

def caseClassToMapImplNonRecursive[T](c: Context)(proof: c.Expr[IsCaseClass[T]])(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] =
caseClassToMapNoProofImplNonRecursive(c)(T)

def caseClassToMapNoProofImplNonRecursive[T](c: Context)(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] = caseClassToMapNoProofImplCommon(c, false)(T)

// TODO the only diff between this and the above is the case match and the converters. it's easy to gate this on the boolean
def caseClassToMapNoProofImpl[T](c: Context)(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] = caseClassToMapNoProofImplCommon(c, true)(T)

def caseClassToMapNoProofImplCommon[T](c: Context, recursivelyApply: Boolean)(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] = {
import c.universe._
//TODO can make error handling better?
val companion = T.tpe.typeSymbol.companionSymbol

val getPutConv = T.tpe.declarations.collect { case m: MethodSymbol if m.isCaseAccessor => m }.zipWithIndex.map {
case (m, idx) =>
val returnType = m.returnType
val accStr = m.name.toTermName.toString
returnType match {
case tpe if recursivelyApply && IsCaseClassImpl.isCaseClassType(c)(tpe) =>
val conv = newTermName("c2m_" + idx)
(q"""$conv.invert(m($accStr).asInstanceOf[_root_.scala.collection.immutable.Map[String, Any]]).get""",
q"""($accStr, $conv(t.$m))""",
Some(q"""val $conv = implicitly[_root_.com.twitter.bijection.Injection[$tpe, _root_.scala.collection.immutable.Map[String, Any]]]""")) //TODO cache these
case tpe =>
(q"""m($accStr).asInstanceOf[$returnType]""",
q"""($accStr, t.$m)""",
None)
}
}

val getters = getPutConv.map(_._1)
val putters = getPutConv.map(_._2)
val converters = getPutConv.flatMap(_._3)

c.Expr[Injection[T, Map[String, Any]]](q"""
new Injection[$T, _root_.scala.collection.immutable.Map[String, Any]] with MacroGenerated {
override def apply(t: $T): _root_.scala.collection.immutable.Map[String, Any] = {
..$converters
_root_.scala.collection.immutable.Map[String, Any](..$putters)
}
override def invert(m: _root_.scala.collection.immutable.Map[String, Any]): _root_.scala.util.Try[ $T ] = {
..$converters
try { _root_.scala.util.Success($companion(..$getters)) } catch { case _root_.scala.util.control.NonFatal(e) => _root_.scala.util.Failure(e) }
}
}
""")

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.twitter.bijection.macros.impl

import scala.collection.mutable.{ Map => MMap }
import scala.language.experimental.macros
import scala.reflect.macros.Context
import scala.reflect.runtime.universe._
import scala.util.Try

import com.twitter.bijection._
import com.twitter.bijection.macros.{ IsCaseClass, MacroGenerated }

private[bijection] object CaseClassToTuple {
def caseClassToTupleImplWithOption[T, Tup](c: Context)(recursivelyApply: c.Expr[Boolean])(proof: c.Expr[IsCaseClass[T]])(implicit T: c.WeakTypeTag[T], Tup: c.WeakTypeTag[Tup]): c.Expr[Bijection[T, Tup]] = {
import c.universe._
recursivelyApply match {
case q"""true""" => caseClassToTupleNoProofImpl(c)(T, Tup)
case q"""false""" => caseClassToTupleNoProofImplNonRecursive(c)(T, Tup)
case _ => caseClassToTupleNoProofImpl(c)(T, Tup)
}
}

// Entry point
def caseClassToTupleImpl[T, Tup](c: Context)(proof: c.Expr[IsCaseClass[T]])(implicit T: c.WeakTypeTag[T], Tup: c.WeakTypeTag[Tup]): c.Expr[Bijection[T, Tup]] =
caseClassToTupleNoProofImpl(c)(T, Tup)

def caseClassToTupleImplNonRecursive[T, Tup](c: Context)(proof: c.Expr[IsCaseClass[T]])(implicit T: c.WeakTypeTag[T], Tup: c.WeakTypeTag[Tup]): c.Expr[Bijection[T, Tup]] =
caseClassToTupleNoProofImplNonRecursive(c)(T, Tup)

def caseClassToTupleNoProofImplNonRecursive[T, Tup](c: Context)(implicit T: c.WeakTypeTag[T],
Tup: c.WeakTypeTag[Tup]): c.Expr[Bijection[T, Tup]] =
caseClassToTupleNoProofImplCommon(c, false)(T, Tup)

def caseClassToTupleNoProofImpl[T, Tup](c: Context)(implicit T: c.WeakTypeTag[T],
Tup: c.WeakTypeTag[Tup]): c.Expr[Bijection[T, Tup]] =
caseClassToTupleNoProofImplCommon(c, true)(T, Tup)

def caseClassToTupleNoProofImplCommon[T, Tup](c: Context,
recursivelyApply: Boolean)(implicit T: c.WeakTypeTag[T],
Tup: c.WeakTypeTag[Tup]): c.Expr[Bijection[T, Tup]] = {
import c.universe._
val tupUtils = new TupleUtils[c.type](c)
val convCache = MMap.empty[Type, TermName]

//TODO can make error handling better
val companion = T.tpe.typeSymbol.companionSymbol
val getPutConv = T.tpe
.declarations
.collect { case m: MethodSymbol if m.isCaseAccessor => m }
.zip(tupUtils.tupleCaseClassEquivalent(T.tpe))
.zip(Tup.tpe.declarations.collect { case m: MethodSymbol if m.isCaseAccessor => m })
.zipWithIndex
.map {
case (((tM, treeEquiv), tupM), idx) =>
tM.returnType match {
case tpe if recursivelyApply && IsCaseClassImpl.isCaseClassType(c)(tpe) =>
val needDeclaration = !convCache.contains(tpe)
val conv = convCache.getOrElseUpdate(tpe, newTermName("c2t_" + idx))
(q"""$conv.invert(tup.$tupM)""",
q"""$conv(t.$tM)""",
if (needDeclaration) Some(q"""val $conv = implicitly[_root_.com.twitter.bijection.Bijection[${tM.returnType}, $treeEquiv]]""") else None) // cache these
case tpe =>
(q"""tup.$tupM""",
q"""t.$tM""",
None)
}
}

val getters = getPutConv.map(_._1)
val putters = getPutConv.map(_._2)
val converters = getPutConv.flatMap(_._3)

c.Expr[Bijection[T, Tup]](q"""
new Bijection[$T,$Tup] with MacroGenerated {
override def apply(t: $T): $Tup = {
..$converters
(..$putters)
}
override def invert(tup: $Tup): $T = {
..$converters
$companion(..$getters)
}
}
""")
}
}
//TODO test serialization of them
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package com.twitter.bijection.macros.common.impl
package com.twitter.bijection.macros.impl

import scala.language.experimental.macros
import scala.reflect.macros.Context
import scala.reflect.runtime.universe._
import scala.util.{ Try => BasicTry }

import com.twitter.bijection.macros.common.{ IsCaseClass, MacroGenerated }
import com.twitter.bijection.macros.{ IsCaseClass, MacroGenerated }

object MacroImpl {
private[bijection] object IsCaseClassImpl {
def isCaseClassImpl[T](c: Context)(implicit T: c.WeakTypeTag[T]): c.Expr[IsCaseClass[T]] = {
import c.universe._
if (isCaseClassType(c)(T.tpe)) {
//TOOD we should support this, just need to make sure it is concrete
if (T.tpe.typeConstructor.takesTypeArgs) {
c.abort(c.enclosingPosition, "Case class with type parameters currently not supported")
} else {
c.Expr[IsCaseClass[T]](q"""_root_.com.twitter.bijection.macros.common.impl.MacroGeneratedIsCaseClass[$T]()""")
c.Expr[IsCaseClass[T]](q"""_root_.com.twitter.bijection.macros.impl.MacroGeneratedIsCaseClass[$T]()""")
}
} else {
c.abort(c.enclosingPosition, "Type parameter is not a case class")
Expand Down
Loading

0 comments on commit 60c61f1

Please sign in to comment.