Skip to content

Commit

Permalink
Ensure all derived typeclasses are Serializable (#166)
Browse files Browse the repository at this point in the history
We use our own `OrElse` which we anyway do now for Scala 2.13.
This unblocks us from waiting on a shapeless release for Serializable.
  • Loading branch information
joroKr21 authored Jun 27, 2019
1 parent 03b1289 commit 62decf7
Show file tree
Hide file tree
Showing 26 changed files with 138 additions and 94 deletions.
26 changes: 23 additions & 3 deletions core/src/main/scala-2.11/cats/derived/util/versionspecific.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,28 @@ package cats.derived.util

import scala.util.hashing.MurmurHash3

private[derived] object VersionSpecific {
object VersionSpecific {
type Lazy[+A] = shapeless.Lazy[A]
type OrElse[+A, +B] = shapeless.OrElse[A, B]
def productSeed(x: Product): Int = MurmurHash3.productSeed
private[derived] def productSeed(x: Product): Int = MurmurHash3.productSeed

sealed trait OrElse[+A, +B] extends Serializable {
def fold[C](prim: A => C, sec: B => C): C
def unify[C >: A](implicit ev: B <:< C): C = fold(identity, ev)
}

final class Primary[+A](value: A) extends OrElse[A, Nothing] {
def fold[C](prim: A => C, sec: Nothing => C): C = prim(value)
}

final class Secondary[+B](value: => B) extends OrElse[Nothing, B] {
def fold[C](prim: Nothing => C, sec: B => C): C = sec(value)
}

object OrElse extends OrElse0 {
implicit def primary[A, B](implicit a: A): A OrElse B = new Primary(a)
}

private[util] abstract class OrElse0 {
implicit def secondary[A, B](implicit b: Lazy[B]): A OrElse B = new Secondary(b.value)
}
}
26 changes: 23 additions & 3 deletions core/src/main/scala-2.12/cats/derived/util/versionspecific.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,28 @@ package cats.derived.util

import scala.util.hashing.MurmurHash3

private[derived] object VersionSpecific {
object VersionSpecific {
type Lazy[+A] = shapeless.Lazy[A]
type OrElse[+A, +B] = shapeless.OrElse[A, B]
def productSeed(x: Product): Int = MurmurHash3.productSeed
private[derived] def productSeed(x: Product): Int = MurmurHash3.productSeed

sealed trait OrElse[+A, +B] extends Serializable {
def fold[C](prim: A => C, sec: B => C): C
def unify[C >: A](implicit ev: B <:< C): C = fold(identity, ev)
}

final class Primary[+A](value: A) extends OrElse[A, Nothing] {
def fold[C](prim: A => C, sec: Nothing => C): C = prim(value)
}

final class Secondary[+B](value: => B) extends OrElse[Nothing, B] {
def fold[C](prim: Nothing => C, sec: B => C): C = sec(value)
}

object OrElse extends OrElse0 {
implicit def primary[A, B](implicit a: A): A OrElse B = new Primary(a)
}

private[util] abstract class OrElse0 {
implicit def secondary[A, B](implicit b: Lazy[B]): A OrElse B = new Secondary(b.value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ package cats.derived.util

import scala.util.hashing.MurmurHash3

private[derived] object VersionSpecific {
object VersionSpecific {

def productSeed(x: Product): Int =
private[derived] def productSeed(x: Product): Int =
MurmurHash3.mix(MurmurHash3.productSeed, x.productPrefix.hashCode)

abstract class Lazy[+A] {
abstract class Lazy[+A] extends Serializable {
def value(): A
}

object Lazy {
implicit def instance[A](implicit ev: => A): Lazy[A] = () => ev
}

sealed trait OrElse[+A, +B] {
sealed trait OrElse[+A, +B] extends Serializable {
def fold[C](prim: A => C, sec: B => C): C
def unify[C >: A](implicit ev: B <:< C): C = fold(identity, ev)
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/derived/hash.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object MkHash extends MkHashDerivation {
def apply[A](implicit ev: MkHash[A]): MkHash[A] = ev
}

private[derived] trait HashBuilder[A] {
private[derived] trait HashBuilder[A] extends Serializable {
def hashes(x: A): List[Int]
def eqv(x: A, y: A): Boolean

Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/derived/util/liftSome.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import shapeless._
/** Summons all available instances of the typeclass `F` for members of the coproduct `C`.
* Unlike `LiftAll` members of the coproduct without an instance will be skipped in the result.
*/
sealed trait LiftSome[F[_], C <: Coproduct] {
sealed trait LiftSome[F[_], C <: Coproduct] extends Serializable {
type Out <: HList
def instances: Out
}
Expand Down
28 changes: 1 addition & 27 deletions core/src/test/scala/cats/derived/KittensSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,10 @@ import cats.syntax.AllSyntax
import org.scalatest.funsuite.AnyFunSuite
import org.typelevel.discipline.scalatest.Discipline

import scala.util.control.NonFatal

/**
* An opinionated stack of traits to improve consistency and reduce
* boilerplate in Kittens tests. Note that unlike the corresponding
* CatsSuite in the Cat project, this trait does not mix in any
* instances.
*/
trait KittensSuite extends AnyFunSuite with Discipline with AllSyntax with SerializableTest


trait SerializableTest {
def isSerializable[T](a: T): Boolean = {
import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream }
val baos = new ByteArrayOutputStream()
val oos = new ObjectOutputStream(baos)
var ois: ObjectInputStream = null
try {
oos.writeObject(a)
oos.close()
val bais = new ByteArrayInputStream(baos.toByteArray())
ois = new ObjectInputStream(bais)
val a2 = ois.readObject()
ois.close()
true
} catch { case NonFatal(t) =>
throw new Exception(t)
} finally {
oos.close()
if (ois != null) ois.close()
}
}
}
trait KittensSuite extends AnyFunSuite with Discipline with AllSyntax
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/applicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats
package derived

import cats.instances.all._
import cats.laws.discipline.ApplicativeTests
import cats.laws.discipline.{ApplicativeTests, SerializableTests}
import cats.laws.discipline.SemigroupalTests.Isomorphisms

class ApplicativeSuite extends KittensSuite {
Expand All @@ -44,6 +44,7 @@ class ApplicativeSuite extends KittensSuite {
checkAll(s"$context.Applicative[AndInt]", ApplicativeTests[AndInt].applicative[Int, String, Long])
checkAll(s"$context.Applicative[Interleaved]", ApplicativeTests[Interleaved].applicative[Int, String, Long])
checkAll(s"$context.Applicative[ListBox]", ApplicativeTests[ListBox].applicative[Int, String, Long])
checkAll(s"$context.Applicative is Serializable", SerializableTests.serializable(Applicative[Interleaved]))
}

{
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/apply.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats
package derived

import cats.instances.all._
import cats.laws.discipline.ApplyTests
import cats.laws.discipline.{ApplyTests, SerializableTests}
import cats.laws.discipline.SemigroupalTests.Isomorphisms

class ApplySuite extends KittensSuite {
Expand All @@ -44,6 +44,7 @@ class ApplySuite extends KittensSuite {
checkAll(s"$context.Apply[AndInt]", ApplyTests[AndInt].apply[Int, String, Long])
checkAll(s"$context.Apply[Interleaved]", ApplyTests[Interleaved].apply[Int, String, Long])
checkAll(s"$context.Apply[ListBox]", ApplyTests[ListBox].apply[Int, String, Long])
checkAll(s"$context.Apply is Serializable", SerializableTests.serializable(Apply[Interleaved]))
}

{
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/consk.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package cats.derived

import alleycats.ConsK
import cats.laws.discipline.SerializableTests
import org.scalatest.prop.{Generator, GeneratorDrivenPropertyChecks}

class ConsKSuite extends KittensSuite with GeneratorDrivenPropertyChecks {
Expand All @@ -28,7 +29,7 @@ class ConsKSuite extends KittensSuite with GeneratorDrivenPropertyChecks {
def testConsK(context: String)(implicit iList: ConsK[IList], snoc: ConsK[Snoc]): Unit = {
test(s"$context.ConsK[IList]")(checkConsK[IList, Int](INil())(IList.fromSeq))
test(s"$context.ConsK[Snoc]")(checkConsK[Snoc, Int](SNil())(xs => Snoc.fromSeq(xs.reverse)))
test(s"$context.ConsK is Serializable")(assert(isSerializable(iList)))
checkAll(s"$context.ConsK is Serializable", SerializableTests.serializable(ConsK[IList]))
}

{
Expand Down
2 changes: 2 additions & 0 deletions core/src/test/scala/cats/derived/empty.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package derived

import alleycats.Empty
import cats.instances.all._
import cats.laws.discipline.SerializableTests
import shapeless.test.illTyped

class EmptySuite extends KittensSuite {
Expand Down Expand Up @@ -47,6 +48,7 @@ class EmptySuite extends KittensSuite {
test(s"$context.Empty respects existing instances")(assert(box.empty == Box(Mask(0xffffffff))))
// Known limitation of recursive typeclass derivation.
test(s"$context.Empty[Chain] throws a StackOverflowError")(assertThrows[StackOverflowError](chain.empty))
checkAll(s"$context.Empty is Serializable", SerializableTests.serializable(Empty[Interleaved[String]]))
}

{
Expand Down
2 changes: 2 additions & 0 deletions core/src/test/scala/cats/derived/emptyk.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package cats.derived
import alleycats.{EmptyK, Pure}
import alleycats.std.all._
import cats.data.NonEmptyList
import cats.laws.discipline.SerializableTests
import cats.instances.all._
import shapeless.test.illTyped

Expand All @@ -43,6 +44,7 @@ class EmptyKSuite extends KittensSuite {
test(s"$context.EmptyK[CaseClassWOption]")(assert(caseClassWOption.empty == CaseClassWOption(None)))
test(s"$context.EmptyK[NelOption]")(assert(nelOption.empty == NonEmptyList.of(None)))
test(s"$context.EmptyK respects existing instances")(assert(boxColor.empty == Box(Color(255, 255, 255))))
checkAll(s"$context.EmptyK is Serializable", SerializableTests.serializable(EmptyK[PList]))
}

implicit val pureBox: Pure[Box] = new Pure[Box] {
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/eq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package cats
package derived

import cats.kernel.laws.discipline._
import cats.kernel.laws.discipline.{EqTests, SerializableTests}
import cats.instances.all._

class EqSuite extends KittensSuite {
Expand All @@ -39,6 +39,7 @@ class EqSuite extends KittensSuite {
checkAll(s"$context.Eq[Interleaved[Int]]", EqTests[Interleaved[Int]].eqv)
checkAll(s"$context.Eq[Tree[Int]]", EqTests[Tree[Int]].eqv)
checkAll(s"$context.Eq[Recursive]", EqTests[Recursive].eqv)
checkAll(s"$context.Eq is Serializable", SerializableTests.serializable(Eq[Tree[Int]]))
}

{
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats
package derived

import cats.instances.all._
import cats.laws.discipline.FoldableTests
import cats.laws.discipline.{FoldableTests, SerializableTests}
import org.scalacheck.Arbitrary

class FoldableSuite extends KittensSuite {
Expand Down Expand Up @@ -49,6 +49,7 @@ class FoldableSuite extends KittensSuite {
checkAll(s"$context.Foldable[AndChar]", FoldableTests[AndChar].foldable[Int, Long])
checkAll(s"$context.Foldable[Interleaved]", FoldableTests[Interleaved].foldable[Int, Long])
checkAll(s"$context.Foldable[BoxNel]]", FoldableTests[BoxNel].foldable[Int, Long])
checkAll(s"$context.Foldable is Serializable", SerializableTests.serializable(Foldable[Tree]))

val n = 10000
val largeIList = IList.fromSeq(1 until n)
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/functor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats
package derived

import cats.instances.all._
import cats.laws.discipline.FunctorTests
import cats.laws.discipline.{FunctorTests, SerializableTests}

class FunctorSuite extends KittensSuite {
import TestDefns._
Expand All @@ -44,6 +44,7 @@ class FunctorSuite extends KittensSuite {
checkAll(s"$context.Functor[ListSnoc]", FunctorTests[ListSnoc].functor[Int, String, Long])
checkAll(s"$context.Functor[AndChar]", FunctorTests[AndChar].functor[Int, String, Long])
checkAll(s"$context.Functor[Interleaved]", FunctorTests[Interleaved].functor[Int, String, Long])
checkAll(s"$context.Functor is Serializable", SerializableTests.serializable(Functor[Tree]))

test(s"$context.Functor.map is stack safe") {
val n = 10000
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/hash.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package derived

import cats.kernel.laws.discipline.HashTests
import cats.kernel.laws.discipline.{HashTests, SerializableTests}
import cats.instances.all._

class HashSuite extends KittensSuite {
Expand All @@ -22,6 +22,7 @@ class HashSuite extends KittensSuite {
// checkAll(s"$context.Hash[Interleaved[Int]]", HashTests[Interleaved[Int]].hash)
checkAll(s"$context.Hash[Tree[Int]]", HashTests[Tree[Int]].hash)
checkAll(s"$context.Hash[Recursive]", HashTests[Recursive].hash)
checkAll(s"$context.Hash is Serializable", SerializableTests.serializable(Hash[Tree[Int]]))
}

{
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/monoid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats.derived

import cats.{Eq, Monoid}
import cats.instances.all._
import cats.kernel.laws.discipline._
import cats.kernel.laws.discipline.{MonoidTests, SerializableTests}
import org.scalacheck.Arbitrary

class MonoidSuite extends KittensSuite {
Expand All @@ -36,6 +36,7 @@ class MonoidSuite extends KittensSuite {
checkAll(s"$context.Monoid[Recursive]", MonoidTests[Recursive].monoid)
checkAll(s"$context.Monoid[Interleaved[Int]]", MonoidTests[Interleaved[Int]].monoid)
checkAll(s"$context.Monoid[Box[Mul]]", MonoidTests[Box[Mul]].monoid)
checkAll(s"$context.Monoid is Serializable", SerializableTests.serializable(Monoid[Interleaved[Int]]))

test(s"$context.Monoid respects existing instances") {
assert(box.empty == Box(Mul(1)))
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/monoidk.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats
package derived

import cats.instances.all._
import cats.laws.discipline.MonoidKTests
import cats.laws.discipline.{MonoidKTests, SerializableTests}
import org.scalacheck.Arbitrary

class MonoidKSuite extends KittensSuite {
Expand All @@ -36,6 +36,7 @@ class MonoidKSuite extends KittensSuite {
checkAll(s"$context.MonoidK[ComplexProduct]", MonoidKTests[ComplexProduct].monoidK[Char])
checkAll(s"$context.MonoidK[CaseClassWOption]", MonoidKTests[CaseClassWOption].monoidK[Char])
checkAll(s"$context.MonoidK[BoxMul]", MonoidKTests[BoxMul].monoidK[Char])
checkAll(s"$context.MonoidK is Serializable", SerializableTests.serializable(MonoidK[ComplexProduct]))

test(s"$context.MonoidK respects existing instances") {
assert(boxMul.empty[Char] == Box(Mul[Char](1)))
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/order.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package cats
package derived

import cats.kernel.laws.discipline._
import cats.kernel.laws.discipline.{OrderTests, SerializableTests}
import cats.instances.all._

class OrderSuite extends KittensSuite {
Expand All @@ -35,6 +35,7 @@ class OrderSuite extends KittensSuite {
checkAll(s"$context.Order[Interleaved[Int]]", OrderTests[Interleaved[Int]].order)
checkAll(s"$context.Order[Recursive]", OrderTests[Recursive].order)
checkAll(s"$context.Order[GenericAdt[Int]]", OrderTests[GenericAdt[Int]].order)
checkAll(s"$context.Order is Serializable", SerializableTests.serializable(Order[Interleaved[Int]]))
}

{
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/partialOrder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats
package derived

import cats.instances.all._
import cats.kernel.laws.discipline._
import cats.kernel.laws.discipline.{PartialOrderTests, SerializableTests}
import org.scalacheck.{Arbitrary, Cogen}

class PartialOrderSuite extends KittensSuite {
Expand All @@ -41,6 +41,7 @@ class PartialOrderSuite extends KittensSuite {
checkAll(s"$context.PartialOrder[Tree[Int]]", PartialOrderTests[Tree[Int]].partialOrder)
checkAll(s"$context.PartialOrder[Recursive]", PartialOrderTests[Recursive].partialOrder)
checkAll(s"$context.PartialOrder[Box[KeyValue]]", PartialOrderTests[Box[KeyValue]].partialOrder)
checkAll(s"$context.PartialOrder is Serialiable", SerializableTests.serializable(PartialOrder[Tree[Int]]))

test(s"$context.PartialOrder respects existing instances") {
val x = Box(KeyValue("red", 1))
Expand Down
4 changes: 3 additions & 1 deletion core/src/test/scala/cats/derived/pure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ package cats
package derived

import alleycats.Pure
import cats.instances.all._
import cats.data.NonEmptyList
import cats.instances.all._
import cats.laws.discipline.SerializableTests
import shapeless.test.illTyped

class PureSuite extends KittensSuite {
Expand All @@ -45,6 +46,7 @@ class PureSuite extends KittensSuite {
test(s"$context.Pure[NelOption]")(assert(nelOption.pure(42) == NonEmptyList.of(Some(42))))
test(s"$context.Pure[Interleaved]")(assert(interleaved.pure('x') == Interleaved(0, 'x', 0, 'x' :: Nil, "")))
test(s"$context.Pure respects existing instances")(assert(boxColor.pure(()) == Box(Color(255, 255, 255))))
checkAll(s"$context.Pure is Serializable", SerializableTests.serializable(Pure[Interleaved]))
}

{
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/scala/cats/derived/semigroup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats
package derived

import cats.instances.all._
import cats.kernel.laws.discipline._
import cats.kernel.laws.discipline.{SemigroupTests, SerializableTests}
import org.scalacheck.Arbitrary

class SemigroupSuite extends KittensSuite {
Expand All @@ -36,6 +36,7 @@ class SemigroupSuite extends KittensSuite {
checkAll(s"$context.Semigroup[Recursive]", SemigroupTests[Recursive].semigroup)
checkAll(s"$context.Semigroup[Interleaved[Int]]", SemigroupTests[Interleaved[Int]].semigroup)
checkAll(s"$context.Semigroup[Box[Mul]]", SemigroupTests[Box[Mul]].semigroup)
checkAll(s"$context.Semigroup is Serializable", SerializableTests.serializable(Semigroup[Interleaved[Int]]))

test(s"$context.Semigroup respects existing instances") {
assert(box.combine(Box(Mul(5)), Box(Mul(5))).content.value == 25)
Expand Down
Loading

0 comments on commit 62decf7

Please sign in to comment.