Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add serialization support #2360

Merged
merged 7 commits into from
Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fileOverride {
project.excludeFilters = [
"scalafix/*"
]

lineEndings = preserve
maxColumn = 96

includeCurlyBraceInSelectChains = true
Expand Down
2 changes: 1 addition & 1 deletion core/jvm/src/main/scala/cats/effect/IOPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import scala.concurrent.duration._

import java.util.concurrent.{ArrayBlockingQueue, CompletableFuture, TimeUnit}

abstract private[effect] class IOPlatform[+A] { self: IO[A] =>
abstract private[effect] class IOPlatform[+A] extends Serializable { self: IO[A] =>

/**
* Produces the result by running the encapsulated effects as impure side effects.
Expand Down
2 changes: 1 addition & 1 deletion core/shared/src/main/scala/cats/effect/SyncIO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import scala.util.control.NonFatal
* the JVM blocks the calling thread while the async part of the computation is run and doing so
* on Scala.js is not supported.
*/
sealed abstract class SyncIO[+A] private () {
sealed abstract class SyncIO[+A] private () extends Serializable {

private[effect] def tag: Byte

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package cats.effect.tracing

private[effect] sealed trait TracingEvent
private[effect] sealed trait TracingEvent extends Serializable

private[effect] object TracingEvent {
final class StackTrace extends Throwable with TracingEvent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import scala.collection.immutable.SortedMap
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.FiniteDuration

trait GenK[F[_]] {
trait GenK[F[_]] extends Serializable {
vasilmkd marked this conversation as resolved.
Show resolved Hide resolved
def apply[A: Arbitrary: Cogen]: Gen[F[A]]
}

// Generators for * -> * kinded types
trait Generators1[F[_]] {
trait Generators1[F[_]] extends Serializable {
protected val maxDepth: Int = 10

// todo: uniqueness based on... names, I guess. Have to solve the diamond problem somehow
Expand Down Expand Up @@ -309,6 +309,28 @@ trait AsyncGenerators[F[_]] extends GenTemporalGenerators[F, Throwable] with Syn
} yield F.evalOn(fa, ec)
}

trait AsyncGeneratorsWithoutEvalShift[F[_]]
extends GenTemporalGenerators[F, Throwable]
with SyncGenerators[F] {
implicit val F: Async[F]
implicit protected val cogenFU: Cogen[F[Unit]] = Cogen[Unit].contramap(_ => ())

override protected def recursiveGen[A: Arbitrary: Cogen](deeper: GenK[F]) =
("async" -> genAsync[A](deeper)) :: super.recursiveGen[A](deeper)

private def genAsync[A: Arbitrary](deeper: GenK[F]) =
for {
result <- arbitrary[Either[Throwable, A]]

fo <- deeper[Option[F[Unit]]](
Arbitrary(Gen.option[F[Unit]](deeper[Unit])),
Cogen.cogenOption(cogenFU))
} yield F
.async[A](k => F.delay(k(result)) >> fo)
.flatMap(F.pure(_))
.handleErrorWith(F.raiseError(_))
}

trait ParallelFGenerators {
implicit def arbitraryParallelF[F[_], A](
implicit ArbF: Arbitrary[F[A]]): Arbitrary[ParallelF[F, A]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,10 @@ object pure {
ft.mapK(fk)
}

// todo: MVar is not Serializable, release then update here
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inclusion of racePairtest leads us here, we'd need to update Mvar with serialization

final class PureFiber[E, A](val state0: MVar[Outcome[PureConc[E, *], E, A]])
extends Fiber[PureConc[E, *], E, A] {
extends Fiber[PureConc[E, *], E, A]
with Serializable {

private[this] val state = state0[PureConc[E, *]]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats.effect.kernel

import java.util.concurrent.{CompletableFuture, CompletionException, CompletionStage}

private[kernel] trait AsyncPlatform[F[_]] { this: Async[F] =>
private[kernel] trait AsyncPlatform[F[_]] extends Serializable { this: Async[F] =>

def fromCompletionStage[A](completionStage: F[CompletionStage[A]]): F[A] =
fromCompletableFuture(flatMap(completionStage) { cs => delay(cs.toCompletableFuture()) })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats.effect.kernel

import java.time.Instant

private[effect] trait ClockPlatform[F[_]] { self: Clock[F] =>
private[effect] trait ClockPlatform[F[_]] extends Serializable { self: Clock[F] =>
def realTimeInstant: F[Instant] = {
self.applicative.map(self.realTime)(d => Instant.ofEpochMilli(d.toMillis))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import javax.security.auth.Destroyable
/**
* JVM-specific Resource methods
*/
private[effect] trait ResourcePlatform {
private[effect] trait ResourcePlatform extends Serializable {

/**
* Creates a [[Resource]] by wrapping a Java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import scala.concurrent.duration.FiniteDuration
* A typeclass which encodes various notions of time. Analogous to some of the time functions
* exposed by `java.lang.System`.
*/
trait Clock[F[_]] extends ClockPlatform[F] {
trait Clock[F[_]] extends ClockPlatform[F] with Serializable {

def applicative: Applicative[F]

Expand Down
2 changes: 1 addition & 1 deletion kernel/shared/src/main/scala/cats/effect/kernel/Cont.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import cats.~>
* override `Async[F].async` with your implementation, and use `Async.defaultCont` to implement
* `Async[F].cont`.
*/
trait Cont[F[_], K, R] {
trait Cont[F[_], K, R] extends Serializable {
def apply[G[_]](
implicit
G: MonadCancel[G, Throwable]): (Either[Throwable, K] => Unit, G[K], F ~> G) => G[R]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ object Deferred {
}
}

trait DeferredSource[F[_], A] {
trait DeferredSource[F[_], A] extends Serializable {

/**
* Obtains the value of the `Deferred`, or waits until it has been completed. The returned
Expand All @@ -240,7 +240,7 @@ object DeferredSource {
}
}

trait DeferredSink[F[_], A] {
trait DeferredSink[F[_], A] extends Serializable {

/**
* If this `Deferred` is empty, sets the current value to `a`, and notifies any and all
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import cats.syntax.all._
* @see
* [[GenSpawn]] documentation for more detailed information on the concurrency of fibers.
*/
trait Fiber[F[_], E, A] {
trait Fiber[F[_], E, A] extends Serializable {

/**
* Requests the cancelation of the fiber bound to this `Fiber` handle and awaits its
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import scala.collection.immutable.{Queue => ScalaQueue}
/**
* A cut-down version of semaphore used to implement parTraverseN
*/
private[kernel] abstract class MiniSemaphore[F[_]] {
private[kernel] abstract class MiniSemaphore[F[_]] extends Serializable {

/**
* Sequence an action while holding a permit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats.effect.kernel

//See https://failex.blogspot.com/2017/04/the-high-cost-of-anyval-subclasses.html
object Par {
sealed abstract class ParallelFImpl {
sealed abstract class ParallelFImpl extends Serializable {
type T[F[_], A]
def apply[F[_], A](fa: F[A]): T[F, A]
def value[F[_], A](t: T[F, A]): F[A]
Expand Down
4 changes: 2 additions & 2 deletions kernel/shared/src/main/scala/cats/effect/kernel/Ref.scala
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ object Ref {
}
}

trait RefSource[F[_], A] {
trait RefSource[F[_], A] extends Serializable {

/**
* Obtains the current value.
Expand All @@ -486,7 +486,7 @@ object RefSource {
}
}

trait RefSink[F[_], A] {
trait RefSink[F[_], A] extends Serializable {

/**
* Sets the current value to `a`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ import scala.concurrent.duration.FiniteDuration
* @tparam A
* the type of resource
*/
sealed abstract class Resource[F[_], +A] {
sealed abstract class Resource[F[_], +A] extends Serializable {
import Resource._

private[effect] def fold[B](
Expand Down
4 changes: 2 additions & 2 deletions kernel/shared/src/main/scala/cats/effect/kernel/Unique.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package cats.effect.kernel

import cats.{Applicative, Hash}

trait Unique[F[_]] {
trait Unique[F[_]] extends Serializable {
def applicative: Applicative[F]
def unique: F[Unique.Token]
}
Expand All @@ -27,7 +27,7 @@ object Unique {

def apply[F[_]](implicit F: Unique[F]): F.type = F

final class Token
final class Token extends Serializable

object Token {
implicit val tokenHash: Hash[Token] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package testkit
import cats.{~>, Applicative, Eq, Id, Order, Show}
import cats.effect.kernel.testkit.{
AsyncGenerators,
AsyncGeneratorsWithoutEvalShift,
GenK,
OutcomeGenerators,
ParallelFGenerators,
Expand Down Expand Up @@ -76,6 +77,24 @@ trait TestInstances extends ParallelFGenerators with OutcomeGenerators with Sync
Arbitrary(generators.generators[A])
}

def arbitraryIOWithoutContextShift[A: Arbitrary: Cogen]: Arbitrary[IO[A]] = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def arbitraryIOWithoutContextShift[A: Arbitrary: Cogen]: Arbitrary[IO[A]] = {
def arbitraryIOWithoutEvalOn[A: Arbitrary: Cogen]: Arbitrary[IO[A]] = {

val generators = new AsyncGeneratorsWithoutEvalShift[IO] {
override implicit val F: Async[IO] = IO.asyncForIO
override implicit protected val arbitraryFD: Arbitrary[FiniteDuration] =
outer.arbitraryFiniteDuration
override implicit val arbitraryE: Arbitrary[Throwable] = outer.arbitraryThrowable
override val cogenE: Cogen[Throwable] = Cogen[Throwable]

override def recursiveGen[B: Arbitrary: Cogen](deeper: GenK[IO]) =
super
.recursiveGen[B](deeper)
.filterNot(x =>
x._1 == "evalOn" || x._1 == "racePair") // todo: enable racePair after MVar been made serialization compatible
}

Arbitrary(generators.generators[A])
}

implicit def arbitrarySyncIO[A: Arbitrary: Cogen]: Arbitrary[SyncIO[A]] = {
val generators = new SyncGenerators[SyncIO] {
val arbitraryE: Arbitrary[Throwable] =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2020-2022 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cats.effect

import cats.kernel.laws.SerializableLaws.serializable

import org.scalacheck.Prop.forAll
import org.typelevel.discipline.specs2.mutable.Discipline

// collapse this back into SyncIOSpec once we're on a release with lampepfl/dotty#14686
trait SyncIOScalaVersionSpecification extends BaseSpec with Discipline {
def scalaVersionSpecs =
"serialize" in {
forAll { (io: SyncIO[Int]) => serializable(io) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2020-2022 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cats.effect

import org.specs2.mutable.SpecificationLike
import org.specs2.specification.core.Fragments

// collapse this back into SyncIOSpec once we're on a release with lampepfl/dotty#14686
trait SyncIOScalaVersionSpecification extends SpecificationLike {
def scalaVersionSpecs = Fragments.empty
}
13 changes: 12 additions & 1 deletion tests/shared/src/test/scala/cats/effect/IOSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ package cats.effect
import cats.effect.implicits._
import cats.effect.laws.AsyncTests
import cats.effect.testkit.TestContext
import cats.kernel.laws.SerializableLaws.serializable
import cats.kernel.laws.discipline.MonoidTests
import cats.laws.discipline.{AlignTests, SemigroupKTests}
import cats.laws.discipline.arbitrary._
import cats.syntax.all._

import org.scalacheck.Prop.forAll
import org.scalacheck.Prop
import org.typelevel.discipline.specs2.mutable.Discipline

import scala.concurrent.{ExecutionContext, TimeoutException}
import scala.concurrent.duration._

import Prop.forAll

class IOSpec extends BaseSpec with Discipline with IOPlatformSpecification {

// we just need this because of the laws testing, since the prop runs can interfere with each other
Expand Down Expand Up @@ -1454,6 +1457,14 @@ class IOSpec extends BaseSpec with Discipline with IOPlatformSpecification {
} yield res
}

"serialize" in {
forAll { (io: IO[Int]) => serializable(io) }(
implicitly,
arbitraryIOWithoutContextShift,
implicitly,
implicitly)
}

platformSpecs
}

Expand Down
8 changes: 7 additions & 1 deletion tests/shared/src/test/scala/cats/effect/SyncIOSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import cats.syntax.all._
import org.scalacheck.Prop.forAll
import org.typelevel.discipline.specs2.mutable.Discipline

class SyncIOSpec extends BaseSpec with Discipline with SyncIOPlatformSpecification {
class SyncIOSpec
extends BaseSpec
with Discipline
with SyncIOPlatformSpecification
with SyncIOScalaVersionSpecification {

"sync io monad" should {
"produce a pure value when run" in {
Expand Down Expand Up @@ -220,6 +224,8 @@ class SyncIOSpec extends BaseSpec with Discipline with SyncIOPlatformSpecificati
res <- IO.delay(res1 mustEqual res2)
} yield res
}

scalaVersionSpecs
}

{
Expand Down