diff --git a/.scalafmt.conf b/.scalafmt.conf index 73af3bdd84..d48dee709f 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -10,7 +10,7 @@ fileOverride { project.excludeFilters = [ "scalafix/*" ] - +lineEndings = preserve maxColumn = 96 includeCurlyBraceInSelectChains = true diff --git a/core/jvm/src/main/scala/cats/effect/IOPlatform.scala b/core/jvm/src/main/scala/cats/effect/IOPlatform.scala index 9053c3837c..0ccc5f0724 100644 --- a/core/jvm/src/main/scala/cats/effect/IOPlatform.scala +++ b/core/jvm/src/main/scala/cats/effect/IOPlatform.scala @@ -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. diff --git a/core/shared/src/main/scala/cats/effect/SyncIO.scala b/core/shared/src/main/scala/cats/effect/SyncIO.scala index 6a970a3eae..2d252930eb 100644 --- a/core/shared/src/main/scala/cats/effect/SyncIO.scala +++ b/core/shared/src/main/scala/cats/effect/SyncIO.scala @@ -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 diff --git a/core/shared/src/main/scala/cats/effect/tracing/TracingEvent.scala b/core/shared/src/main/scala/cats/effect/tracing/TracingEvent.scala index b3ef4cc9e4..fd906d231b 100644 --- a/core/shared/src/main/scala/cats/effect/tracing/TracingEvent.scala +++ b/core/shared/src/main/scala/cats/effect/tracing/TracingEvent.scala @@ -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 diff --git a/kernel-testkit/shared/src/main/scala/cats/effect/kernel/testkit/Generators.scala b/kernel-testkit/shared/src/main/scala/cats/effect/kernel/testkit/Generators.scala index 871b901dbd..ecc3961b8f 100644 --- a/kernel-testkit/shared/src/main/scala/cats/effect/kernel/testkit/Generators.scala +++ b/kernel-testkit/shared/src/main/scala/cats/effect/kernel/testkit/Generators.scala @@ -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 { 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 @@ -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]] = diff --git a/kernel-testkit/shared/src/main/scala/cats/effect/kernel/testkit/pure.scala b/kernel-testkit/shared/src/main/scala/cats/effect/kernel/testkit/pure.scala index c844a41e0b..d036f192b0 100644 --- a/kernel-testkit/shared/src/main/scala/cats/effect/kernel/testkit/pure.scala +++ b/kernel-testkit/shared/src/main/scala/cats/effect/kernel/testkit/pure.scala @@ -362,8 +362,10 @@ object pure { ft.mapK(fk) } + // todo: MVar is not Serializable, release then update here 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, *]] diff --git a/kernel/jvm/src/main/scala/cats/effect/kernel/AsyncPlatform.scala b/kernel/jvm/src/main/scala/cats/effect/kernel/AsyncPlatform.scala index ab511ee640..72bf762426 100644 --- a/kernel/jvm/src/main/scala/cats/effect/kernel/AsyncPlatform.scala +++ b/kernel/jvm/src/main/scala/cats/effect/kernel/AsyncPlatform.scala @@ -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()) }) diff --git a/kernel/jvm/src/main/scala/cats/effect/kernel/ClockPlatform.scala b/kernel/jvm/src/main/scala/cats/effect/kernel/ClockPlatform.scala index 7ff31b1966..91726ad44e 100644 --- a/kernel/jvm/src/main/scala/cats/effect/kernel/ClockPlatform.scala +++ b/kernel/jvm/src/main/scala/cats/effect/kernel/ClockPlatform.scala @@ -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)) } diff --git a/kernel/jvm/src/main/scala/cats/effect/kernel/ResourcePlatform.scala b/kernel/jvm/src/main/scala/cats/effect/kernel/ResourcePlatform.scala index ab68b3e7f3..cfd6e31c63 100644 --- a/kernel/jvm/src/main/scala/cats/effect/kernel/ResourcePlatform.scala +++ b/kernel/jvm/src/main/scala/cats/effect/kernel/ResourcePlatform.scala @@ -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 diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Clock.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Clock.scala index 7eb412affe..e1333aa18c 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Clock.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Clock.scala @@ -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] diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Cont.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Cont.scala index d58de8f361..2a230c3bd8 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Cont.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Cont.scala @@ -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] diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Deferred.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Deferred.scala index 786737f7dc..bf015fd676 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Deferred.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Deferred.scala @@ -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 @@ -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 diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Fiber.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Fiber.scala index 758c04e5c5..2220d3c8c6 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Fiber.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Fiber.scala @@ -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 diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/MiniSemaphore.scala b/kernel/shared/src/main/scala/cats/effect/kernel/MiniSemaphore.scala index 95f68db34f..412329b1b6 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/MiniSemaphore.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/MiniSemaphore.scala @@ -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 diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/ParallelF.scala b/kernel/shared/src/main/scala/cats/effect/kernel/ParallelF.scala index a509718f98..ab75319eed 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/ParallelF.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/ParallelF.scala @@ -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] diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Ref.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Ref.scala index 81790ac8bf..b7747212db 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Ref.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Ref.scala @@ -464,7 +464,7 @@ object Ref { } } -trait RefSource[F[_], A] { +trait RefSource[F[_], A] extends Serializable { /** * Obtains the current value. @@ -486,7 +486,7 @@ object RefSource { } } -trait RefSink[F[_], A] { +trait RefSink[F[_], A] extends Serializable { /** * Sets the current value to `a`. diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Resource.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Resource.scala index 5af0010060..88202320a4 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Resource.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Resource.scala @@ -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]( diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Unique.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Unique.scala index 553981b2bc..ca4587d3e8 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Unique.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Unique.scala @@ -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] } @@ -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] = diff --git a/testkit/shared/src/main/scala/cats/effect/testkit/TestInstances.scala b/testkit/shared/src/main/scala/cats/effect/testkit/TestInstances.scala index 422ecffeb3..6875165654 100644 --- a/testkit/shared/src/main/scala/cats/effect/testkit/TestInstances.scala +++ b/testkit/shared/src/main/scala/cats/effect/testkit/TestInstances.scala @@ -20,6 +20,7 @@ package testkit import cats.{~>, Applicative, Eq, Id, Order, Show} import cats.effect.kernel.testkit.{ AsyncGenerators, + AsyncGeneratorsWithoutEvalShift, GenK, OutcomeGenerators, ParallelFGenerators, @@ -76,6 +77,24 @@ trait TestInstances extends ParallelFGenerators with OutcomeGenerators with Sync Arbitrary(generators.generators[A]) } + def arbitraryIOWithoutContextShift[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] = diff --git a/tests/shared/src/test/scala-2/cats/effect/SyncIOScalaVersionSpecification.scala b/tests/shared/src/test/scala-2/cats/effect/SyncIOScalaVersionSpecification.scala new file mode 100644 index 0000000000..87ef34a0e6 --- /dev/null +++ b/tests/shared/src/test/scala-2/cats/effect/SyncIOScalaVersionSpecification.scala @@ -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) } + } +} diff --git a/tests/shared/src/test/scala-3/cats/effect/SyncIOScalaVersionSpecification.scala b/tests/shared/src/test/scala-3/cats/effect/SyncIOScalaVersionSpecification.scala new file mode 100644 index 0000000000..ef2861067f --- /dev/null +++ b/tests/shared/src/test/scala-3/cats/effect/SyncIOScalaVersionSpecification.scala @@ -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 +} diff --git a/tests/shared/src/test/scala/cats/effect/IOSpec.scala b/tests/shared/src/test/scala/cats/effect/IOSpec.scala index 9dc800d711..5e390d1365 100644 --- a/tests/shared/src/test/scala/cats/effect/IOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/IOSpec.scala @@ -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 @@ -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 } diff --git a/tests/shared/src/test/scala/cats/effect/SyncIOSpec.scala b/tests/shared/src/test/scala/cats/effect/SyncIOSpec.scala index 3a725803bc..d09f671732 100644 --- a/tests/shared/src/test/scala/cats/effect/SyncIOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/SyncIOSpec.scala @@ -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 { @@ -220,6 +224,8 @@ class SyncIOSpec extends BaseSpec with Discipline with SyncIOPlatformSpecificati res <- IO.delay(res1 mustEqual res2) } yield res } + + scalaVersionSpecs } {