Repository: 7mind/izumi
Pull request #794 (2019-12-20 00:42:07)

Fix cats conversion priorities on 2.13; fix BlockingIO.apply


Patch:
diff --git a/fundamentals/fundamentals-bio/.jvm/src/main/scala/izumi/functional/bio/BlockingIO3.scala b/fundamentals/fundamentals-bio/.jvm/src/main/scala/izumi/functional/bio/BlockingIO3.scala
index badfb85fe..35debf8f8 100644
--- a/fundamentals/fundamentals-bio/.jvm/src/main/scala/izumi/functional/bio/BlockingIO3.scala
+++ b/fundamentals/fundamentals-bio/.jvm/src/main/scala/izumi/functional/bio/BlockingIO3.scala
@@ -2,11 +2,12 @@ package izumi.functional.bio
 
 import java.util.concurrent.ThreadPoolExecutor
 
+import izumi.functional.bio.BlockingIOInstances.blockingIOZIO3Blocking
 import zio.blocking.Blocking
 import zio.internal.Executor
 import zio.{IO, UIO, ZIO}
 
-trait BlockingIO3[F[_, _, _]] {
+trait BlockingIO3[F[_, _, _]] extends BlockingIOInstances {
 
   /** Execute a blocking action in `Blocking` thread pool, current task will be safely parked until the blocking task finishes **/
   def shiftBlocking[R, E, A](f: F[R, E ,A]): F[R, E, A]
@@ -21,13 +22,14 @@ trait BlockingIO3[F[_, _, _]] {
     * THIS IS USUALLY UNSAFE unless calling well-written libraries that specifically handle [[InterruptedException]]
     * **/
   def syncInterruptibleBlocking[A](f: => A): F[Any, Throwable, A]
-
 }
 
-object BlockingIO3 extends LowPriorityBlockingIO3Instances {
-  def apply[F[_, _, _]: BlockingIO3](implicit dummy: DummyImplicit): BlockingIO3[F] = implicitly
-  def apply[F[_, _]: BlockingIO]: BlockingIO[F] = implicitly
+object BlockingIO3 {
+  def apply[F[_, _, _]: BlockingIO3]: BlockingIO3[F] = implicitly
+}
 
+private[bio] sealed trait BlockingIOInstances
+object BlockingIOInstances extends LowPriorityBlockingIOInstances {
   // FIXME: bad encoding for lifting to 2-parameters...
   def BlockingZIOFromThreadPool[R](blockingPool: ThreadPoolExecutor): BlockingIO[ZIO[R, ?, ?]] = {
     BlockingZIO3FromThreadPool(blockingPool).asInstanceOf[BlockingIO[ZIO[R, ?, ?]]]
@@ -40,25 +42,29 @@ object BlockingIO3 extends LowPriorityBlockingIO3Instances {
         override val blockingExecutor: ZIO[Any, Nothing, Executor] = UIO.succeed(executor)
       }
     }
-    blockingIOZIO3(blocking)
+    blockingIOZIO3Blocking(blocking)
   }
 
   // FIXME: bad encoding for lifting to 2-parameters...
-  implicit def blockingIOIO[R](implicit serviceBlocking: Blocking): BlockingIO[ZIO[R, +?, +?]] = {
-    blockingIOZIO3(serviceBlocking).asInstanceOf[BlockingIO[ZIO[R, +?, +?]]]
+  implicit def blockingIOZIOBlocking[R](implicit serviceBlocking: Blocking): BlockingIO[ZIO[R, +?, +?]] = {
+    blockingIOZIO3Blocking(serviceBlocking).asInstanceOf[BlockingIO[ZIO[R, +?, +?]]]
   }
 
-  implicit final def blockingIOZIO3(implicit serviceBlocking: Blocking): BlockingIO3[ZIO] = new BlockingIO3[ZIO] {
+  implicit final def blockingIOZIO3Blocking(implicit serviceBlocking: Blocking): BlockingIO3[ZIO] = new BlockingIO3[ZIO] {
     override def shiftBlocking[R, E, A](f: ZIO[R, E ,A]): ZIO[R, E, A] = serviceBlocking.blocking.blocking(f)
     override def syncBlocking[A](f: => A): ZIO[Any, Throwable, A] = serviceBlocking.blocking.blocking(IO(f))
     override def syncInterruptibleBlocking[A](f: => A): ZIO[Any, Throwable, A] = serviceBlocking.blocking.effectBlocking(f)
   }
 }
 
-trait LowPriorityBlockingIO3Instances {
+trait LowPriorityBlockingIOInstances {
   type ZIOBlocking = { type l[-R, +E, +A] = ZIO[R with Blocking, E, A] }
 
-  implicit final def blockingIOZIO3: BlockingIO3[ZIOBlocking#l] = new BlockingIO3[ZIOBlocking#l] {
+  implicit def blockingIOZIOR[R]: BlockingIO[ZIO[R with Blocking, +?, +?]] = {
+    blockingIOZIO3R.asInstanceOf[BlockingIO[ZIO[R with Blocking, +?, +?]]]
+  }
+
+  implicit final def blockingIOZIO3R: BlockingIO3[ZIOBlocking#l] = new BlockingIO3[ZIOBlocking#l] {
     override def shiftBlocking[R, E, A](f: ZIO[R with Blocking, E, A]): ZIO[R with Blocking, E, A] = ZIO.accessM(_.blocking.blocking(f))
     override def syncBlocking[A](f: => A): ZIO[Blocking, Throwable, A] = ZIO.accessM(_.blocking.blocking(IO(f)))
     override def syncInterruptibleBlocking[A](f: => A): ZIO[Blocking, Throwable, A] = ZIO.accessM(_.blocking.effectBlocking(f))
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOCatsConversions.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOCatsConversions.scala
index 6dd844ffc..0428ec3e9 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOCatsConversions.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOCatsConversions.scala
@@ -49,16 +49,23 @@ trait BIOCatsConversions6 extends BIOCatsConversions7 {
   @inline implicit final def BIOToBracket[F[+_, +_]](implicit @deprecated("unused","unused") ev: BIOFunctor[F], F: BIOPanic[F]): BIOCatsBracket[F] = new BIOCatsBracket[F](F)
 }
 trait BIOCatsConversions7 extends BIOCatsConversions8 {
-  @inline implicit final def BIOToSync[F[+_, +_]](implicit @deprecated("unused","unused") ev: BIOFunctor[F], F: BIO[F]): BIOCatsSync[F] = new BIOCatsSync[F](F)
+  @inline implicit final def BIOToSync[F[+_, +_]](implicit @deprecated("unused","unused") ev: BIOFunctor[F], F: BIO[F]): BIOCatsSync[F] with Specifier with Specificier = new BIOCatsSync[F](F) with Specifier with Specificier
 }
 trait BIOCatsConversions8 extends BIOCatsConversions9 {
-  @inline implicit final def BIOAsyncToAsync[F[+_, +_]](implicit @deprecated("unused","unused") ev: BIOFunctor[F], F: BIOAsync[F]): BIOCatsAsync[F] = new BIOCatsAsync[F](F)
+  @inline implicit final def BIOAsyncToAsync[F[+_, +_]](implicit @deprecated("unused","unused") ev: BIOFunctor[F], F: BIOAsync[F]): BIOCatsAsync[F] with Specifier = new BIOCatsAsync[F](F) with Specifier
 }
-
 trait BIOCatsConversions9 {
-  @inline implicit final def BIOAsyncForkToConcurrent[F[+_, +_]](implicit @deprecated("unused","unused") ev: BIOFunctor[F], F: BIOAsync[F], Fork: BIOFork[F]): BIOCatsConcurrent[F] = new BIOCatsConcurrent[F](F, Fork)
+  @inline implicit final def BIOAsyncForkToConcurrent[F[+_, +_]](implicit @deprecated("unused","unused") ev: BIOFunctor[F], F: BIOAsync[F], Fork: BIOFork[F]): cats.effect.Concurrent[F[Throwable, ?]] = new BIOCatsConcurrent[F](F, Fork)
 }
 
+/**
+  * Just packaging conversions into implicit priority traits is not enough,
+  * scalac also has a rule that the most specific return type wins, for some reason. /_\
+  * So we make the higher priority types more specific by... *checks notes*... mixing more empty traits in
+  */
+private[bio] sealed trait Specifier
+private[bio] sealed trait Specificier
+
 object BIOCatsConversions {
 
   trait BIOCatsFunctor[F[_, +_], E] extends cats.Functor[F[E, ?]] {
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala
index 6ade55a97..623578cab 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala
@@ -313,9 +313,10 @@ package object bio extends BIOSyntax {
   type BIOLatch[F[+_, +_]] = BIOPromise[F, Nothing, Unit]
 
   type BIOFork[F[+_, +_]] = BIOFork3[Lambda[(`-R`, `+E`, `+A`) => F[E, A]]]
-
   type BlockingIO[F[_, _]] = BlockingIO3[Lambda[(R, E, A) => F[E, A]]]
-  @inline final lazy val BlockingIO: BlockingIO3.type = BlockingIO3
+  object BlockingIO {
+    def apply[F[_, _]: BlockingIO]: BlockingIO[F] = implicitly
+  }
 
   type BIOPrimitives3[F[-_, +_, +_]] = BIOPrimitives[F[Any, +?, +?]]
 


Test patch:
diff --git a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/BlockingIOSyntaxTest.scala b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/BlockingIOSyntaxTest.scala
index b25a86de3..b0f67ac59 100644
--- a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/BlockingIOSyntaxTest.scala
+++ b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/BlockingIOSyntaxTest.scala
@@ -1,11 +1,12 @@
 package izumi.functional.bio.test
 
-import izumi.functional.bio.{BIOFunctor, BlockingIO, BlockingIO3, F}
+import izumi.functional.bio.{BIOFunctor, BlockingIO, BlockingIO3, BlockingIOInstances, F}
 import izumi.fundamentals.bio.test.masking.BIOMonad3
+import org.scalatest.WordSpec
 import zio.ZIO
 import zio.blocking.Blocking
 
-object BlockingIOSyntaxTest {
+class BlockingIOSyntaxTest extends WordSpec {
 
   def `attach BlockingIO methods to a trifunctor BIO`[F[-_, +_, +_]: BIOMonad3: BlockingIO3]: F[Any, Throwable, Int] = {
     F.syncBlocking(2)
@@ -15,7 +16,24 @@ object BlockingIOSyntaxTest {
   }
   val _: ZIO[Blocking, Throwable, Int] = {
     implicit val b: zio.blocking.Blocking = zio.blocking.Blocking.Live
-    `attach BlockingIO methods to a trifunctor BIO`[BlockingIO3.ZIOBlocking#l]
+    `attach BlockingIO methods to a trifunctor BIO`[BlockingIOInstances.ZIOBlocking#l]
     `attach BlockingIO methods to a bifunctor BIO`[zio.IO]
   }
+
+  "BlockingIO.apply is callable" in {
+    class X[F[+_, +_]: BlockingIO] {
+      def hello = BlockingIO[F].syncBlocking(println("hello world!"))
+    }
+    class X3[F[-_, +_, +_]: BlockingIO3] {
+      def hello = BlockingIO3[F].syncBlocking(println("hello world!"))
+    }
+
+    assert(new X[zio.ZIO[Blocking, +?, +?]].hello != null)
+    locally {
+      implicit val blocking: Blocking.Live.type = zio.blocking.Blocking.Live
+      assert(new X[zio.IO].hello != null)
+    }
+    assert(new X3[BlockingIOInstances.ZIOBlocking#l].hello != null)
+  }
+
 }
diff --git a/fundamentals/fundamentals-bio/src/test/scala/izumi/fundamentals/bio/test/CatsConversionTest.scala b/fundamentals/fundamentals-bio/src/test/scala/izumi/fundamentals/bio/test/CatsConversionTest.scala
index d8b7553d9..a2f4a97be 100644
--- a/fundamentals/fundamentals-bio/src/test/scala/izumi/fundamentals/bio/test/CatsConversionTest.scala
+++ b/fundamentals/fundamentals-bio/src/test/scala/izumi/fundamentals/bio/test/CatsConversionTest.scala
@@ -1,7 +1,7 @@
 package izumi.fundamentals.bio.test
 
 import cats.effect.concurrent.Ref
-import cats.effect.{Async, Sync}
+import cats.effect.{Async, Concurrent, Sync}
 import izumi.functional.bio.BIO
 import izumi.functional.bio.catz._
 import org.scalatest.WordSpec
@@ -18,6 +18,7 @@ class CatsConversionTest extends WordSpec {
     conv[zio.IO]
     implicitly[Sync[zio.Task]]
     implicitly[Async[zio.Task]]
+    implicitly[Concurrent[zio.Task]]
   }
 
 }