Skip to content

Commit 1597c26

Browse files
committed
Merge branch 'release/0.7.1'
2 parents 8894ba7 + d98ed4d commit 1597c26

File tree

18 files changed

+790
-4
lines changed

18 files changed

+790
-4
lines changed

CHANGES.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Bijection #
22

3+
### 0.7.1
4+
* Remove some package privacy so these things can be used in scalding and ...: https://github.com/twitter/bijection/pull/190
5+
* Add macros to create Trys: https://github.com/twitter/bijection/pull/187
6+
* Refactor macros: https://github.com/twitter/bijection/pull/186
7+
* Add TypeclassBijection: https://github.com/twitter/bijection/pull/183
8+
* Generate some useful case class conversions with macros: https://github.com/twitter/bijection/pull/179
9+
310
### 0.7.0
411
* Added thrift json injections: https://github.com/twitter/bijection/pull/172
512
* Make almost all the case classes extend AnyVal: https://github.com/twitter/bijection/pull/178

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ Discussion occurs primarily on the [Bijection mailing list](https://groups.googl
132132

133133
## Maven
134134

135-
Bijection modules are available on maven central. The current groupid and version for all modules is, respectively, `"com.twitter"` and `0.7.0`.
135+
Bijection modules are available on maven central. The current groupid and version for all modules is, respectively, `"com.twitter"` and `0.7.1`.
136136

137137
Current published artifacts are
138138

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.twitter.bijection
2+
3+
object TypeclassBijection {
4+
implicit class RichTypeclass[T[_], A](t: T[A]) {
5+
def bijectTo[B](implicit tcBij: TypeclassBijection[T], bij: ImplicitBijection[A, B]): T[B] = tcBij(t, bij.bijection)
6+
}
7+
8+
object BijectionAndTypeclass {
9+
implicit def get[T[_], From, To](implicit bij: ImplicitBijection[To, From], typeclass: T[From]) = BijectionAndTypeclass(bij, typeclass)
10+
}
11+
case class BijectionAndTypeclass[T[_], From, To](bij: ImplicitBijection[To, From], typeclass: T[From]) {
12+
def apply(tc: TypeclassBijection[T]): T[To] = TypeclassBijection.typeclassBijection[T, From, To](tc, typeclass, ImplicitBijection.reverse(bij.bijection))
13+
}
14+
15+
def typeclassBijection[T[_], A, B](implicit tcBij: TypeclassBijection[T], typeclass: T[A], bij: ImplicitBijection[A, B]): T[B] = tcBij(typeclass, bij.bijection)
16+
def deriveFor[T[_], To](implicit tcBij: TypeclassBijection[T], batc: BijectionAndTypeclass[T, From, To] forSome { type From }): T[To] = batc(tcBij)
17+
}
18+
trait TypeclassBijection[T[_]] {
19+
def apply[A, B](tc: T[A], bij: Bijection[A, B]): T[B]
20+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.twitter.bijection
2+
3+
import org.scalatest.{ PropSpec, MustMatchers }
4+
import org.scalatest.prop.PropertyChecks
5+
6+
class TypeclassBijectionLaws extends PropSpec with PropertyChecks with MustMatchers with BaseProperties {
7+
import FakeAlgebird._
8+
import TypeclassBijection._
9+
10+
case class A(x: Int, y: String)
11+
implicit val abij: Bijection[A, (Int, String)] = Bijection.build[A, (Int, String)] { A.unapply(_).get } { x => A(x._1, x._2) }
12+
13+
implicit val orderingTypeclassBijection: TypeclassBijection[Ordering] = new TypeclassBijection[Ordering] {
14+
def apply[A, B](tc: Ordering[A], bij: Bijection[A, B]) = tc.on { bij.invert(_) }
15+
}
16+
17+
implicit val numericTypeclassBijection: TypeclassBijection[Numeric] = new TypeclassBijection[Numeric] {
18+
def apply[A, B](tc: Numeric[A], bij: Bijection[A, B]) =
19+
new Numeric[B] {
20+
def plus(x: B, y: B) = bij(tc.plus(bij.invert(x), bij.invert(y)))
21+
def minus(x: B, y: B) = bij(tc.minus(bij.invert(x), bij.invert(y)))
22+
def times(x: B, y: B) = bij(tc.times(bij.invert(x), bij.invert(y)))
23+
def negate(x: B) = bij(tc.negate(bij.invert(x)))
24+
def fromInt(x: Int) = bij(tc.fromInt(x))
25+
def toInt(x: B) = tc.toInt(bij.invert(x))
26+
def toLong(x: B) = tc.toLong(bij.invert(x))
27+
def toFloat(x: B) = tc.toFloat(bij.invert(x))
28+
def toDouble(x: B) = tc.toDouble(bij.invert(x))
29+
def compare(x: B, y: B) = tc.compare(bij.invert(x), bij.invert(y))
30+
}
31+
}
32+
33+
case class Wrapper(get: Int)
34+
implicit val wrapperbij: Bijection[Wrapper, Int] = Bijection.build[Wrapper, Int] { _.get } { Wrapper(_) }
35+
36+
property("basic") {
37+
implicitly[Semigroup[Int]]
38+
implicitly[Semigroup[String]]
39+
implicitly[Semigroup[(Int, String)]]
40+
typeclassBijection[Semigroup, (Int, String), A]
41+
implicitly[Semigroup[(Int, String)]].bijectTo[A]
42+
deriveFor[Semigroup, A]
43+
deriveFor[Ordering, A]
44+
deriveFor[Ordering, Wrapper]
45+
deriveFor[Numeric, Wrapper]
46+
}
47+
}
48+
49+
object FakeAlgebird {
50+
object Semigroup {
51+
implicit val intSemigroup: Semigroup[Int] =
52+
new Semigroup[Int] {
53+
override def plus(left: Int, right: Int) = left + right
54+
}
55+
56+
implicit val strSemigroup: Semigroup[String] =
57+
new Semigroup[String] {
58+
override def plus(left: String, right: String) = left + right
59+
}
60+
61+
implicit def tup2Semigroup[A, B](implicit s1: Semigroup[A], s2: Semigroup[B]): Semigroup[(A, B)] =
62+
new Semigroup[(A, B)] {
63+
override def plus(l: (A, B), r: (A, B)) = (s1.plus(l._1, r._1), s2.plus(l._2, r._2))
64+
}
65+
66+
implicit val semigroupTypeclassBijection: TypeclassBijection[Semigroup] = new TypeclassBijection[Semigroup] {
67+
def apply[A, B](tc: Semigroup[A], bij: Bijection[A, B]) =
68+
new Semigroup[B] {
69+
override def plus(left: B, right: B) = bij(tc.plus(bij.invert(left), bij.invert(right)))
70+
}
71+
}
72+
}
73+
trait Semigroup[T] {
74+
def plus(left: T, right: T): T
75+
}
76+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.twitter.bijection.macros
2+
3+
import scala.language.experimental.macros
4+
import com.twitter.bijection.macros.impl._
5+
6+
/**
7+
* 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.
8+
*/
9+
object IsCaseClass {
10+
implicit def isCaseClass[T]: IsCaseClass[T] = macro IsCaseClassImpl.isCaseClassImpl[T]
11+
}
12+
13+
trait IsCaseClass[T]
14+
15+
/**
16+
* This is a tag trait to allow macros to signal, in a uniform way, that a piece of code was generated.
17+
*/
18+
trait MacroGenerated
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.twitter.bijection.macros
2+
3+
import scala.language.experimental.macros
4+
5+
import com.twitter.bijection._
6+
import com.twitter.bijection.macros.impl.{ CaseClassToTuple, CaseClassToMap, IsCaseClassImpl }
7+
8+
trait LowerPriorityMacroImplicits {
9+
// Since implicit macro's aborting makes them just appear to never having existed
10+
// its valid for this to be lower priority for the one in MacroImplicits.
11+
// We will attempt this one if we don't see the other
12+
implicit def materializeCaseClassToTupleNonRecursive[T: IsCaseClass, Tup]: Bijection[T, Tup] = macro CaseClassToTuple.caseClassToTupleImplNonRecursive[T, Tup]
13+
}
14+
15+
object MacroImplicits extends LowerPriorityMacroImplicits {
16+
17+
implicit def materializeCaseClassToTuple[T: IsCaseClass, Tup]: Bijection[T, Tup] = macro CaseClassToTuple.caseClassToTupleImpl[T, Tup]
18+
implicit def materializeCaseClassToMap[T: IsCaseClass]: Injection[T, Map[String, Any]] = macro CaseClassToMap.caseClassToMapImpl[T]
19+
20+
implicit def isCaseClass[T]: IsCaseClass[T] = macro IsCaseClassImpl.isCaseClassImpl[T]
21+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.twitter.bijection.macros
2+
3+
import scala.language.experimental.macros
4+
5+
import com.twitter.bijection._
6+
import com.twitter.bijection.macros.impl.{ CaseClassToTuple, CaseClassToMap, TryMacros }
7+
8+
import scala.util.Try
9+
10+
object Macros {
11+
def caseClassToTuple[T: IsCaseClass, Tup](recursivelyApply: Boolean): Bijection[T, Tup] = macro CaseClassToTuple.caseClassToTupleImplWithOption[T, Tup]
12+
def caseClassToMap[T: IsCaseClass](recursivelyApply: Boolean): Injection[T, Map[String, Any]] = macro CaseClassToMap.caseClassToMapImplWithOption[T]
13+
14+
/**
15+
* This can be used like Inversion.attempt. For example:
16+
* fastAttempt(intString)(intString.toInt)
17+
* The reason we don't take a B => A like attempt does is
18+
* that would still require calling apply on that function.
19+
* In contrast, here, we can inline it directly.
20+
*/
21+
def fastAttempt[A, B](b: B)(inv: A): Try[A] = macro TryMacros.fastAttempt[A, B]
22+
23+
/**
24+
* This macro expands out to a try block so it is only slower
25+
* than a try block in that it allocates Success/Failure wrappers
26+
*/
27+
def fastTry[T](toEval: T): Try[T] = macro TryMacros.fastTry[T]
28+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.twitter.bijection.macros.impl
2+
3+
import scala.collection.mutable.{ Map => MMap }
4+
import scala.language.experimental.macros
5+
import scala.reflect.macros.Context
6+
import scala.reflect.runtime.universe._
7+
import scala.util.Try
8+
9+
import com.twitter.bijection._
10+
import com.twitter.bijection.macros.{ IsCaseClass, MacroGenerated }
11+
12+
private[bijection] object CaseClassToMap {
13+
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]]] = {
14+
import c.universe._
15+
recursivelyApply.tree match {
16+
case q"""true""" => caseClassToMapNoProofImpl(c)(T)
17+
case q"""false""" => caseClassToMapNoProofImplNonRecursive(c)(T)
18+
}
19+
}
20+
21+
def caseClassToMapImpl[T](c: Context)(proof: c.Expr[IsCaseClass[T]])(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] =
22+
caseClassToMapNoProofImpl(c)(T)
23+
24+
def caseClassToMapImplNonRecursive[T](c: Context)(proof: c.Expr[IsCaseClass[T]])(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] =
25+
caseClassToMapNoProofImplNonRecursive(c)(T)
26+
27+
def caseClassToMapNoProofImplNonRecursive[T](c: Context)(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] = caseClassToMapNoProofImplCommon(c, false)(T)
28+
29+
// 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
30+
def caseClassToMapNoProofImpl[T](c: Context)(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] = caseClassToMapNoProofImplCommon(c, true)(T)
31+
32+
def caseClassToMapNoProofImplCommon[T](c: Context, recursivelyApply: Boolean)(implicit T: c.WeakTypeTag[T]): c.Expr[Injection[T, Map[String, Any]]] = {
33+
import c.universe._
34+
//TODO can make error handling better?
35+
val companion = T.tpe.typeSymbol.companionSymbol
36+
37+
val getPutConv = T.tpe.declarations.collect { case m: MethodSymbol if m.isCaseAccessor => m }.zipWithIndex.map {
38+
case (m, idx) =>
39+
val returnType = m.returnType
40+
val accStr = m.name.toTermName.toString
41+
returnType match {
42+
case tpe if recursivelyApply && IsCaseClassImpl.isCaseClassType(c)(tpe) =>
43+
val conv = newTermName("c2m_" + idx)
44+
(q"""$conv.invert(m($accStr).asInstanceOf[_root_.scala.collection.immutable.Map[String, Any]]).get""",
45+
q"""($accStr, $conv(t.$m))""",
46+
Some(q"""val $conv = implicitly[_root_.com.twitter.bijection.Injection[$tpe, _root_.scala.collection.immutable.Map[String, Any]]]""")) //TODO cache these
47+
case tpe =>
48+
(q"""m($accStr).asInstanceOf[$returnType]""",
49+
q"""($accStr, t.$m)""",
50+
None)
51+
}
52+
}
53+
54+
val getters = getPutConv.map(_._1)
55+
val putters = getPutConv.map(_._2)
56+
val converters = getPutConv.flatMap(_._3)
57+
58+
c.Expr[Injection[T, Map[String, Any]]](q"""
59+
new Injection[$T, _root_.scala.collection.immutable.Map[String, Any]] with MacroGenerated {
60+
override def apply(t: $T): _root_.scala.collection.immutable.Map[String, Any] = {
61+
..$converters
62+
_root_.scala.collection.immutable.Map[String, Any](..$putters)
63+
}
64+
override def invert(m: _root_.scala.collection.immutable.Map[String, Any]): _root_.scala.util.Try[ $T ] = {
65+
..$converters
66+
try { _root_.scala.util.Success($companion(..$getters)) }
67+
catch { case _root_.scala.util.control.NonFatal(e) =>
68+
_root_.scala.util.Failure(new _root_.com.twitter.bijection.InversionFailure(m, e)) }
69+
}
70+
}
71+
""")
72+
73+
}
74+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.twitter.bijection.macros.impl
2+
3+
import scala.collection.mutable.{ Map => MMap }
4+
import scala.language.experimental.macros
5+
import scala.reflect.macros.Context
6+
import scala.reflect.runtime.universe._
7+
import scala.util.Try
8+
9+
import com.twitter.bijection._
10+
import com.twitter.bijection.macros.{ IsCaseClass, MacroGenerated }
11+
12+
private[bijection] object CaseClassToTuple {
13+
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]] = {
14+
import c.universe._
15+
recursivelyApply match {
16+
case q"""true""" => caseClassToTupleNoProofImpl(c)(T, Tup)
17+
case q"""false""" => caseClassToTupleNoProofImplNonRecursive(c)(T, Tup)
18+
case _ => caseClassToTupleNoProofImpl(c)(T, Tup)
19+
}
20+
}
21+
22+
// Entry point
23+
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]] =
24+
caseClassToTupleNoProofImpl(c)(T, Tup)
25+
26+
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]] =
27+
caseClassToTupleNoProofImplNonRecursive(c)(T, Tup)
28+
29+
def caseClassToTupleNoProofImplNonRecursive[T, Tup](c: Context)(implicit T: c.WeakTypeTag[T],
30+
Tup: c.WeakTypeTag[Tup]): c.Expr[Bijection[T, Tup]] =
31+
caseClassToTupleNoProofImplCommon(c, false)(T, Tup)
32+
33+
def caseClassToTupleNoProofImpl[T, Tup](c: Context)(implicit T: c.WeakTypeTag[T],
34+
Tup: c.WeakTypeTag[Tup]): c.Expr[Bijection[T, Tup]] =
35+
caseClassToTupleNoProofImplCommon(c, true)(T, Tup)
36+
37+
def caseClassToTupleNoProofImplCommon[T, Tup](c: Context,
38+
recursivelyApply: Boolean)(implicit T: c.WeakTypeTag[T],
39+
Tup: c.WeakTypeTag[Tup]): c.Expr[Bijection[T, Tup]] = {
40+
import c.universe._
41+
val tupUtils = new TupleUtils[c.type](c)
42+
val convCache = MMap.empty[Type, TermName]
43+
44+
//TODO can make error handling better
45+
val companion = T.tpe.typeSymbol.companionSymbol
46+
val getPutConv = T.tpe
47+
.declarations
48+
.collect { case m: MethodSymbol if m.isCaseAccessor => m }
49+
.zip(tupUtils.tupleCaseClassEquivalent(T.tpe))
50+
.zip(Tup.tpe.declarations.collect { case m: MethodSymbol if m.isCaseAccessor => m })
51+
.zipWithIndex
52+
.map {
53+
case (((tM, treeEquiv), tupM), idx) =>
54+
tM.returnType match {
55+
case tpe if recursivelyApply && IsCaseClassImpl.isCaseClassType(c)(tpe) =>
56+
val needDeclaration = !convCache.contains(tpe)
57+
val conv = convCache.getOrElseUpdate(tpe, newTermName("c2t_" + idx))
58+
(q"""$conv.invert(tup.$tupM)""",
59+
q"""$conv(t.$tM)""",
60+
if (needDeclaration) Some(q"""val $conv = implicitly[_root_.com.twitter.bijection.Bijection[${tM.returnType}, $treeEquiv]]""") else None) // cache these
61+
case tpe =>
62+
(q"""tup.$tupM""",
63+
q"""t.$tM""",
64+
None)
65+
}
66+
}
67+
68+
val getters = getPutConv.map(_._1)
69+
val putters = getPutConv.map(_._2)
70+
val converters = getPutConv.flatMap(_._3)
71+
72+
c.Expr[Bijection[T, Tup]](q"""
73+
new Bijection[$T,$Tup] with MacroGenerated {
74+
override def apply(t: $T): $Tup = {
75+
..$converters
76+
(..$putters)
77+
}
78+
override def invert(tup: $Tup): $T = {
79+
..$converters
80+
$companion(..$getters)
81+
}
82+
}
83+
""")
84+
}
85+
}
86+
//TODO test serialization of them
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.twitter.bijection.macros.impl
2+
3+
import scala.language.experimental.macros
4+
import scala.reflect.macros.Context
5+
import scala.reflect.runtime.universe._
6+
import scala.util.{ Try => BasicTry }
7+
8+
import com.twitter.bijection.macros.{ IsCaseClass, MacroGenerated }
9+
10+
object IsCaseClassImpl {
11+
def isCaseClassImpl[T](c: Context)(implicit T: c.WeakTypeTag[T]): c.Expr[IsCaseClass[T]] = {
12+
import c.universe._
13+
if (isCaseClassType(c)(T.tpe)) {
14+
//TOOD we should support this, just need to make sure it is concrete
15+
if (T.tpe.typeConstructor.takesTypeArgs) {
16+
c.abort(c.enclosingPosition, "Case class with type parameters currently not supported")
17+
} else {
18+
c.Expr[IsCaseClass[T]](q"""_root_.com.twitter.bijection.macros.impl.MacroGeneratedIsCaseClass[$T]()""")
19+
}
20+
} else {
21+
c.abort(c.enclosingPosition, "Type parameter is not a case class")
22+
}
23+
}
24+
25+
def isCaseClassType(c: Context)(tpe: c.universe.Type): Boolean =
26+
BasicTry { tpe.typeSymbol.asClass.isCaseClass }.toOption.getOrElse(false)
27+
}
28+
29+
case class MacroGeneratedIsCaseClass[T]() extends IsCaseClass[T] with MacroGenerated

0 commit comments

Comments
 (0)