Repository: 7mind/izumi
Pull request #836 (2020-01-17 17:26:25)

Add an `@impl` annotation to document that a trait is used as an Auto-Trait or an Auto-Factory


Patch:
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala
index de61b61e6..7d4229ab6 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala
@@ -134,10 +134,16 @@ object DIResource {
     }
   }
 
-  def make[F[_], A](acquire: => F[A])(release: A => F[Unit]): DIResource[F, A] = {
-    @inline def a = acquire
-    @inline def r = release
+  implicit final class DIResourceUseEffect[F[_], A](
+    private val resource: DIResourceBase[F, F[A]],
+  ) extends AnyVal {
+    def useEffect(implicit F: DIEffect[F]): F[A] = {
+      resource.use(identity)
+    }
+  }
 
+  def make[F[_], A](acquire: => F[A])(release: A => F[Unit]): DIResource[F, A] = {
+    @inline def a = acquire; @inline def r = release
     new DIResource[F, A] {
       override def acquire: F[A] = a
       override def release(resource: A): F[Unit] = r(resource)
@@ -152,16 +158,12 @@ object DIResource {
     make[Identity, A](acquire)(release)
   }
 
-  def makePair[F[_], A](allocate: F[(A, F[Unit])]): DIResource.Cats[F, A] = {
-    new DIResource.Cats[F, A] {
+  def makePair[F[_], A](allocate: F[(A, F[Unit])]): DIResource.FromCats[F, A] = {
+    new DIResource.FromCats[F, A] {
       override def acquire: F[(A, F[Unit])] = allocate
     }
   }
 
-  def pure[A](a: A): DIResource[Identity, A] = {
-    DIResource.makeSimple(a)(_ => ())
-  }
-
   def liftF[F[_], A](acquire: => F[A])(implicit F: DIEffect[F]): DIResource[F, A] = {
     make(acquire)(_ => F.unit)
   }
@@ -185,16 +187,20 @@ object DIResource {
     }
   }
 
+  def pure[A](a: A): DIResource[Identity, A] = {
+    DIResource.makeSimple(a)(_ => ())
+  }
+
   /** Convert [[cats.effect.Resource]] to [[DIResource]] */
-  def fromCats[F[_]: Bracket[?[_], Throwable], A](resource: Resource[F, A]): DIResource.Cats[F, A] = {
-    new Cats[F, A] {
+  def fromCats[F[_]: Bracket[?[_], Throwable], A](resource: Resource[F, A]): DIResource.FromCats[F, A] = {
+    new FromCats[F, A] {
       override val acquire: F[(A, F[Unit])] = resource.allocated
     }
   }
 
   /** Convert [[zio.ZManaged]] to [[DIResource]] */
-  def fromZIO[R, E, A](managed: ZManaged[R, E, A]): DIResource.Zio[R, E, A] = {
-    new Zio[R, E, A] {
+  def fromZIO[R, E, A](managed: ZManaged[R, E, A]): DIResource.FromZIO[R, E, A] = {
+    new FromZIO[R, E, A] {
       override def acquire: ZIO[R, E, (A, ZIO[R, Nothing, Unit])] = {
         managed.reserve.uninterruptible.flatMap {
           reservation => reservation.acquire.map(a => a -> reservation.release(Exit.succeed(a)).unit)
@@ -222,10 +228,13 @@ object DIResource {
   implicit final class DIResourceZIOSyntax[-R, +E, +A](private val resource: DIResourceBase[ZIO[R, E, ?], A]) extends AnyVal {
     /** Convert [[DIResource]] to [[zio.ZManaged]] */
     def toZIO: ZManaged[R, E, A] = {
-      ZManaged(resource.acquire.map(r => Reservation(zio.ZIO.effectTotal(resource.extract(r)), _ => resource.release(r).orDieWith {
-        case e: Throwable => e
-        case any => new RuntimeException(s"DIResource finalizer: $any")
-      })))
+      ZManaged(resource.acquire.map(r => Reservation(
+        zio.ZIO.effectTotal(resource.extract(r)),
+        _ => resource.release(r).orDieWith {
+          case e: Throwable => e
+          case any: Any => new RuntimeException(s"DIResource finalizer: $any")
+        },
+      )))
     }
   }
 
@@ -280,10 +289,10 @@ object DIResource {
     *   class IntRes extends DIResource.MakePair(IO(1000 -> IO.unit))
     * }}}
     */
-  class MakePair[F[_], A] private[this](acquire0: () => F[(A, F[Unit])], @unused dummy: Boolean = false) extends Cats[F, A] {
+  class MakePair[F[_], A] private[this](acquire0: () => F[(A, F[Unit])], @unused dummy: Boolean = false) extends FromCats[F, A] {
     def this(acquire: => F[(A, F[Unit])]) = this(() => acquire)
 
-    override def acquire: F[(A, F[Unit])] = acquire0()
+    override final def acquire: F[(A, F[Unit])] = acquire0()
   }
 
   /**
@@ -340,27 +349,27 @@ object DIResource {
     *   class IntRes extends DIResource.OfZIO(Managed.succeed(1000))
     * }}}
     */
-  class OfZIO[R, E, A](inner: ZManaged[R, E, A]) extends DIResource.Of[ZIO[R, E, ?], A](fromZIO(inner))
+  class OfZIO[-R, +E, +A](inner: ZManaged[R, E, A]) extends DIResource.Of[ZIO[R, E, ?], A](fromZIO(inner))
 
-  trait Self[+F[_], +A] extends DIResourceBase[F, A] { this: A =>
-    override final type InnerResource = Unit
-    override final def release(resource: Unit): F[Unit] = release
+  abstract class SelfNoClose[+F[_]: DIEffect, +A] extends DIResourceBase.NoClose[F, A] { this: A =>
+    override type InnerResource = Unit
     override final def extract(resource: Unit): A = this
-    def release: F[Unit]
   }
 
-  abstract class SelfNoClose[+F[_]: DIEffect, +A] extends DIResourceBase.NoClose[F, A] { this: A =>
-    override type InnerResource = Unit
+  trait Self[+F[_], +A] extends DIResourceBase[F, A] { this: A =>
+    def release: F[Unit]
+    override final type InnerResource = Unit
+    override final def release(resource: Unit): F[Unit] = release
     override final def extract(resource: Unit): A = this
   }
 
-  trait Cats[F[_], A] extends DIResourceBase[F, A] {
+  trait FromCats[F[_], A] extends DIResourceBase[F, A] {
     override final type InnerResource = (A, F[Unit])
     override final def release(resource: (A, F[Unit])): F[Unit] = resource._2
     override final def extract(resource: (A, F[Unit])): A = resource._1
   }
 
-  trait Zio[R, E, A] extends DIResourceBase[ZIO[R, E, ?], A] {
+  trait FromZIO[R, E, A] extends DIResourceBase[ZIO[R, E, ?], A] {
     override final type InnerResource = (A, ZIO[R, Nothing, Unit])
     override final def release(resource: (A, ZIO[R, Nothing, Unit])): ZIO[R, Nothing, Unit] = resource._2
     override final def extract(resource: (A, ZIO[R, Nothing, Unit])): A = resource._1
@@ -413,7 +422,7 @@ object DIResource {
     *       dependency on `Bracket[F, Throwable]` for
     *       your corresponding `F` type
     */
-  implicit final def providerFromCats[F[_]: TagK, A](resource: => Resource[F, A])(implicit tag: Tag[DIResource.Cats[F, A]]): ProviderMagnet[DIResource.Cats[F, A]] = {
+  implicit final def providerFromCats[F[_]: TagK, A](resource: => Resource[F, A])(implicit tag: Tag[DIResource.FromCats[F, A]]): ProviderMagnet[DIResource.FromCats[F, A]] = {
     ProviderMagnet.identity[Bracket[F, Throwable]].map {
       implicit bracket: Bracket[F, Throwable] =>
         fromCats(resource)
@@ -422,7 +431,7 @@ object DIResource {
   /**
     * Allows you to bind [[zio.ZManaged]]-based constructors in `ModuleDef`:
     */
-  implicit final def providerFromZIO[R, E, A](managed: => ZManaged[R, E, A])(implicit tag: Tag[DIResource.Zio[R, E, A]]): ProviderMagnet[DIResource.Zio[R, E, A]] = {
+  implicit final def providerFromZIO[R, E, A](managed: => ZManaged[R, E, A])(implicit tag: Tag[DIResource.FromZIO[R, E, A]]): ProviderMagnet[DIResource.FromZIO[R, E, A]] = {
     ProviderMagnet.lift(fromZIO(managed))
   }
 
@@ -467,11 +476,11 @@ object DIResource {
       * dependency on `Bracket[F, Throwable]` for
       * your corresponding `F` type
       */
-    implicit final def providerFromCatsProvider[F[_], A]: AdaptProvider.Aux[Resource[F, A], DIResource.Cats[F, A]] = {
+    implicit final def providerFromCatsProvider[F[_], A]: AdaptProvider.Aux[Resource[F, A], DIResource.FromCats[F, A]] = {
       new AdaptProvider[Resource[F, A]] {
-        type Out = DIResource.Cats[F, A]
+        type Out = DIResource.FromCats[F, A]
 
-        override def apply(a: ProviderMagnet[Resource[F, A]])(implicit tag: ResourceTag[DIResource.Cats[F, A]]): ProviderMagnet[DIResource.Cats[F, A]] = {
+        override def apply(a: ProviderMagnet[Resource[F, A]])(implicit tag: ResourceTag[DIResource.FromCats[F, A]]): ProviderMagnet[DIResource.FromCats[F, A]] = {
           import tag.tagFull
           implicit val tagF: TagK[F] = tag.tagK.asInstanceOf[TagK[F]]; val _ = tagF
 
@@ -484,11 +493,11 @@ object DIResource {
     /**
       * Allows you to bind [[zio.ZManaged]]-based constructor functions in `ModuleDef`:
       */
-    implicit final def providerFromZIOProvider[R, E, A]: AdaptProvider.Aux[ZManaged[R, E, A], Zio[R, E, A]] = {
+    implicit final def providerFromZIOProvider[R, E, A]: AdaptProvider.Aux[ZManaged[R, E, A], FromZIO[R, E, A]] = {
       new AdaptProvider[ZManaged[R, E, A]] {
-        type Out = DIResource.Zio[R, E, A]
+        type Out = DIResource.FromZIO[R, E, A]
 
-        override def apply(a: ProviderMagnet[ZManaged[R, E, A]])(implicit tag: ResourceTag[Out]): ProviderMagnet[Zio[R, E, A]] = {
+        override def apply(a: ProviderMagnet[ZManaged[R, E, A]])(implicit tag: ResourceTag[Out]): ProviderMagnet[FromZIO[R, E, A]] = {
           import tag.tagFull
           a.map(fromZIO)
         }
@@ -608,7 +617,7 @@ object DIResource {
       *
       * (it's also used to display error trace from TagK's @implicitNotFound)
       *
-      * TODO: include link to IJ bug tracker
+      * TODO: report to IJ bug tracker
       */
     implicit final def fakeResourceTagMacroIntellijWorkaround[R <: DIResourceBase[Any, Any]]: ResourceTag[R] = macro ResourceTag.fakeResourceTagMacroIntellijWorkaroundImpl[R]
   }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/With.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/With.scala
index cd7011e70..79ce4c9b8 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/With.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/With.scala
@@ -34,6 +34,6 @@ package izumi.distage.model.definition
   *   }
   * }}}
   *
-  * @see Documentation for [[https://izumi.7mind.io/latest/release/doc/distage/basics.html#auto-factories Auto-Factories]]
+  * @see [[https://izumi.7mind.io/latest/release/doc/distage/basics.html#auto-factories Auto-Factories]]
   */
 final class With[T] extends DIStageAnnotation
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/impl.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/impl.scala
new file mode 100644
index 000000000..93f10edf0
--- /dev/null
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/impl.scala
@@ -0,0 +1,24 @@
+package izumi.distage.model.definition
+
+/**
+  * An optional, documentation-only annotation conveying that
+  * an abstract class or a trait is the 'actual' implementation
+  * of its supertypes and will be bound later in DI with
+  * [[izumi.distage.constructors.TraitConstructor]] or
+  * [[izumi.distage.constructors.FactoryConstructor]]
+  *
+  * Abstract classes or traits without obvious concrete subclasses
+  * may hinder the readability of a codebase, if you still want to use them
+  * to avoid writing the full constructor, you may use this annotation
+  * to aid the reader in understanding your intentions.
+  *
+  * {{{
+  *   @impl abstract class Impl(
+  *     pluser: Pluser
+  *   ) extends PlusedInt
+  * }}}
+  *
+  * @see [[https://izumi.7mind.io/latest/release/doc/distage/basics.html#auto-traits Auto-Traits]]
+  * @see [[https://izumi.7mind.io/latest/release/doc/distage/basics.html#auto-factories Auto-Factories]]
+  */
+final class impl extends DIStageAnnotation
diff --git a/distage/distage-core/src/main/scala/distage/Distage.scala b/distage/distage-core/src/main/scala/distage/Distage.scala
index 6e57454a8..cf01f9970 100644
--- a/distage/distage-core/src/main/scala/distage/Distage.scala
+++ b/distage/distage-core/src/main/scala/distage/Distage.scala
@@ -3,7 +3,7 @@ package distage
 import izumi.distage.model.plan.ExecutableOp
 import izumi.distage.model.reflection.universe.RuntimeDIUniverse
 import izumi.distage.planning.extensions
-import izumi.distage.{model, planning}
+import izumi.distage.{constructors, model, planning}
 import izumi.fundamentals.reflection.Tags
 
 trait Distage {
@@ -31,16 +31,25 @@ trait Distage {
 
   type DIResourceBase[+F[_], +Resource] = model.definition.DIResource.DIResourceBase[F, Resource]
 
+  type Axis = model.definition.Axis
+  val Axis: model.definition.Axis.type = model.definition.Axis
+
+  val StandardAxis: model.definition.StandardAxis.type = model.definition.StandardAxis
+
   type ProviderMagnet[+A] = model.providers.ProviderMagnet[A]
   val ProviderMagnet: model.providers.ProviderMagnet.type = model.providers.ProviderMagnet
 
-  type GCMode = model.plan.GCMode
-  val GCMode: model.plan.GCMode.type = model.plan.GCMode
+  type ClassConstructor[T] = constructors.ClassConstructor[T]
+  val ClassConstructor: constructors.ClassConstructor.type = constructors.ClassConstructor
 
-  val StandardAxis: model.definition.StandardAxis.type = model.definition.StandardAxis
+  type TraitConstructor[T] = constructors.TraitConstructor[T]
+  val TraitConstructor: constructors.TraitConstructor.type = constructors.TraitConstructor
 
-  type Axis = model.definition.Axis
-  val Axis: model.definition.Axis.type = model.definition.Axis
+  type FactoryConstructor[T] = constructors.FactoryConstructor[T]
+  val FactoryConstructor: constructors.FactoryConstructor.type = constructors.FactoryConstructor
+
+  type GCMode = model.plan.GCMode
+  val GCMode: model.plan.GCMode.type = model.plan.GCMode
 
   type BindingTag = model.definition.BindingTag
   val BindingTag: model.definition.BindingTag.type = model.definition.BindingTag
diff --git a/distage/distage-core/src/main/scala/distage/package.scala b/distage/distage-core/src/main/scala/distage/package.scala
index 613594a9c..502ce35cb 100644
--- a/distage/distage-core/src/main/scala/distage/package.scala
+++ b/distage/distage-core/src/main/scala/distage/package.scala
@@ -1,7 +1,7 @@
 import izumi.distage.model.plan.ExecutableOp
 import izumi.distage.model.reflection.universe.RuntimeDIUniverse
 import izumi.distage.planning.extensions
-import izumi.distage.{model, planning}
+import izumi.distage.{constructors, model, planning}
 import izumi.fundamentals.reflection.Tags
 
 package object distage extends Distage {
@@ -29,17 +29,25 @@ package object distage extends Distage {
 
   override type DIResourceBase[+F[_], +Resource] = model.definition.DIResource.DIResourceBase[F, Resource]
 
+  override type Axis = model.definition.Axis
+  override val Axis: model.definition.Axis.type = model.definition.Axis
+
+  override val StandardAxis: model.definition.StandardAxis.type = model.definition.StandardAxis
+
   override type ProviderMagnet[+A] = model.providers.ProviderMagnet[A]
   override val ProviderMagnet: model.providers.ProviderMagnet.type = model.providers.ProviderMagnet
 
+  override type ClassConstructor[T] = constructors.ClassConstructor[T]
+  override val ClassConstructor: constructors.ClassConstructor.type = constructors.ClassConstructor
 
-  override type GCMode = model.plan.GCMode
-  override val GCMode: model.plan.GCMode.type = model.plan.GCMode
+  override type TraitConstructor[T] = constructors.TraitConstructor[T]
+  override val TraitConstructor: constructors.TraitConstructor.type = constructors.TraitConstructor
 
-  override val StandardAxis: model.definition.StandardAxis.type = model.definition.StandardAxis
+  override type FactoryConstructor[T] = constructors.FactoryConstructor[T]
+  override val FactoryConstructor: constructors.FactoryConstructor.type = constructors.FactoryConstructor
 
-  override type Axis = model.definition.Axis
-  override val Axis: model.definition.Axis.type = model.definition.Axis
+  override type GCMode = model.plan.GCMode
+  override val GCMode: model.plan.GCMode.type = model.plan.GCMode
 
   override type BindingTag = model.definition.BindingTag
   override val BindingTag: model.definition.BindingTag.type = model.definition.BindingTag
@@ -99,4 +107,5 @@ package object distage extends Distage {
 
   override type TagTK3[T[_[_], _, _, _]] = Tags.TagTK3[T]
   override val TagTK3: Tags.TagTK3.type = Tags.TagTK3
+
 }
diff --git a/doc/microsite/src/main/tut/distage/basics.md b/doc/microsite/src/main/tut/distage/basics.md
index 6c99c916b..e9afd7468 100644
--- a/doc/microsite/src/main/tut/distage/basics.md
+++ b/doc/microsite/src/main/tut/distage/basics.md
@@ -164,7 +164,7 @@ Injector(Activation(Style -> Style.Normal))
   .use(_.get[HelloByeApp].run())
 ```
 
-In @scaladoc[distage.StandardAxis](izumi.distage.model.definition.StandardAxis) there are three example Axes for back-end development: `Repo.Prod/Dummy`, `Env.Prod/Test` & `ExternalApi.Prod/Mock`  
+@scaladoc[distage.StandardAxis](izumi.distage.model.definition.StandardAxis) contains some bundled Axis for back-end development: `Repo.Prod/Dummy`, `Env.Prod/Test` & `ExternalApi.Prod/Mock`  
 
 In `distage-framework`'s @scaladoc[RoleAppLauncher](izumi.distage.roles.RoleAppLauncher), you can choose axes using the `-u` command-line parameter:
 
@@ -291,7 +291,6 @@ To define a Set binding use `.many` and `.add` methods of the @scaladoc[ModuleDe
 For example, we may declare many [http4s](https://http4s.org) routes and serve them all from a central router:
 
 ```scala mdoc:silent:reset:to-string
-// import boilerplate
 import cats.implicits._
 import cats.effect.{Bracket, IO, Resource}
 import distage.{GCMode, ModuleDef, Injector}
@@ -458,10 +457,12 @@ distage can instantiate traits and structural types. All unimplemented fields in
 This can be used to create ZIO Environment cakes with required dependencies - https://gitter.im/ZIO/Core?at=5dbb06a86570b076740f6db2
 
 Trait implementations are derived at compile-time by @scaladoc[TraitConstructor](izumi.distage.constructors.TraitConstructor) macro
-and can be summoned at need. Example:
+and can be summoned at need. 
+
+Example:
 
 ```scala mdoc:reset:to-string
-import distage.{DIKey, GCMode, ModuleDef, Injector, ProviderMagnet, Tag}
+import distage.{DIKey, ModuleDef, Injector, ProviderMagnet, Tag}
 import izumi.distage.constructors.TraitConstructor
 import zio.console.{Console, putStrLn}
 import zio.{UIO, URIO, ZIO, Ref, Task}
@@ -523,12 +524,94 @@ val definition = new ModuleDef {
 }
 
 val main = Injector()
-  .produceF[Task](definition, GCMode(DIKey.get[UIO[Unit]]))
-  .use(_.get[UIO[Unit]])
+  .produceGetF[UIO[Unit]](definition)
+  .useEffect
 
 new zio.DefaultRuntime{}.unsafeRun(main)
 ```
 
+If a suitable trait is specified as an implementation class for a binding, `TraitConstructor` will be used automatically:
+
+Example:
+
+```scala mdoc:reset:to-string
+import distage.{Id, Injector}
+
+trait Trait1 {
+  def a: Int @Id("a")
+}
+trait Trait2 {
+  def b: Int @Id("b")
+}
+
+/** All methods in this trait are implemented,
+  * so a constructor for it will be generated
+  * even though it's not a class */
+trait Pluser {
+  def plus(a: Int, b: Int) = a + b
+}
+
+trait PlusedInt {
+  def result(): Int
+}
+object PlusedInt {
+
+  /** Besides the dependency on `Pluser`,
+    * this class defines 2 more dependencies
+    * to be injected from the object graph:
+    *
+    * `def a: Int @Id("a")` and
+    * `def b: Int @Id("b")`
+    * 
+    * When an abstract type is declared as an implementation,
+    * its no-argument abstract defs & vals are considered as
+    * dependency parameters by TraitConstructor. (empty-parens and
+    * parameterized methods are not considered parameters)
+    *
+    * Here, using an abstract class directly as an implementation
+    * lets us avoid writing a lengthier constructor, like this one:
+    * 
+    * {{{
+    *   final class Impl(
+    *     pluser: Pluser,
+    *     override val a: Int @Id("a"),
+    *     override val b: Int @Id("b"),
+    *   ) extends PlusedInt with Trait1 with Trait2
+    * }}}
+    */
+  abstract class Impl(
+    pluser: Pluser
+  ) extends PlusedInt
+    with Trait1
+    with Trait2 {
+    override def result(): Int = {
+      pluser.plus(a, b)
+    }
+  }
+
+}
+
+Injector()
+  .produceGet[PlusedInt](new ModuleDef {
+    make[Int].named("a").from(1)
+    make[Int].named("b").from(2)
+    make[Pluser]
+    make[PlusedInt].from[PlusedInt.Impl]
+  })
+  .use(_.result)
+```
+
+Abstract classes or traits without obvious concrete subclasses
+may hinder the readability of a codebase, if you still want to use them
+to avoid writing the full constructor, you may use an optional @scaladoc[@impl](izumi.distage.model.definition.impl) 
+documenting annotation to aid the reader in understanding your intention.
+
+```scala mdoc:to-string
+@impl abstract class Impl(
+  pluser: Pluser
+) extends PlusedInt
+```
+
 ### Auto-Factories
 
 `distage` can instantiate 'factory' classes from suitable traits. This feature is especially useful with `Akka`.


Test patch:
diff --git a/distage/distage-testkit-core/src/test/scala/izumi/distage/testkit/catstest/CatsResourcesTest.scala b/distage/distage-testkit-core/src/test/scala/izumi/distage/testkit/catstest/CatsResourcesTest.scala
index e0705cb43..5b4d90275 100644
--- a/distage/distage-testkit-core/src/test/scala/izumi/distage/testkit/catstest/CatsResourcesTest.scala
+++ b/distage/distage-testkit-core/src/test/scala/izumi/distage/testkit/catstest/CatsResourcesTest.scala
@@ -68,7 +68,7 @@ class CatsResourcesTest extends AnyWordSpec with GivenWhenThen {
     definition.bindings.foreach {
       case SingletonBinding(_, implDef@ImplDef.ResourceImpl(_, _, ImplDef.ProviderImpl(providerImplType, fn)), _, _) =>
         assert(implDef.implType == SafeType.get[Res1])
-        assert(providerImplType == SafeType.get[DIResource.Cats[IO, Res1]])
+        assert(providerImplType == SafeType.get[DIResource.FromCats[IO, Res1]])
         assert(fn.diKeys contains DIKey.get[Bracket[IO, Throwable]])
       case _ =>
         fail()