Repository: 7mind/izumi
Pull request #956 (2020-03-16 01:13:21)

make *Constructor summoners return ProviderMagnet, fix provider equality after .map & .addUnused
* make *Constructor classes AnyVal and make sure they erase
* in proxies, resolve constructor parameters only for proxies over a class constructor



codecov-io: # [Codecov](https://codecov.io/gh/7mind/izumi/pull/956?src=pr&el=h1) Report
> Merging [#956](https://codecov.io/gh/7mind/izumi/pull/956?src=pr&el=desc) into [develop](https://codecov.io/gh/7mind/izumi/commit/dc924f3f3e03ed5a581f0f28ca52e0ebe74b6fd4&el=desc) will **decrease** coverage by `0.02%`.
> The diff coverage is `89.79%`.


```diff
@@             Coverage Diff             @@
##           develop     #956      +/-   ##
===========================================
- Coverage    58.27%   58.24%   -0.03%     
===========================================
  Files          357      357              
  Lines         7650     7655       +5     
  Branches       434      440       +6     
===========================================
+ Hits          4458     4459       +1     
- Misses        3192     3196       +4     
```


Patch:
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/constructors.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/constructors.scala
index cfbc65b81..d7ac58a1d 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/constructors.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/constructors.scala
@@ -9,27 +9,25 @@ import izumi.fundamentals.reflection.Tags.WeakTag
 
 import scala.language.experimental.{macros => enableMacros}
 
-sealed trait AnyConstructor[T] extends AnyConstructorOptionalMakeDSL[T] {
-  def provider: ProviderMagnet[T]
-}
-final case class ClassConstructor[T](provider: ProviderMagnet[T]) extends AnyConstructor[T]
-final case class TraitConstructor[T](provider: ProviderMagnet[T]) extends AnyConstructor[T]
-final case class FactoryConstructor[T](provider: ProviderMagnet[T]) extends AnyConstructor[T]
+sealed trait AnyConstructor[T] extends Any with AnyConstructorOptionalMakeDSL[T]
+final class ClassConstructor[T](val provider: ProviderMagnet[T]) extends AnyVal with AnyConstructor[T]
+final class TraitConstructor[T](val provider: ProviderMagnet[T]) extends AnyVal with AnyConstructor[T]
+final class FactoryConstructor[T](val provider: ProviderMagnet[T]) extends AnyVal with AnyConstructor[T]
 
 object AnyConstructor {
-  def apply[T: AnyConstructor]: AnyConstructor[T] = implicitly
+  def apply[T](implicit ctor: AnyConstructor[T]): ProviderMagnet[T] = ctor.provider
 
   implicit def materialize[T]: AnyConstructor[T] = macro AnyConstructorMacro.mkAnyConstructor[T]
 }
 
 object ClassConstructor {
-  def apply[T: ClassConstructor]: ClassConstructor[T] = implicitly
+  def apply[T](implicit ctor: ClassConstructor[T]): ProviderMagnet[T] = ctor.provider
 
   implicit def materialize[T]: ClassConstructor[T] = macro ClassConstructorMacro.mkClassConstructor[T]
 }
 
 object TraitConstructor {
-  def apply[T: TraitConstructor]: TraitConstructor[T] = implicitly
+  def apply[T](implicit ctor: TraitConstructor[T]): ProviderMagnet[T] = ctor.provider
 
   implicit def materialize[T]: TraitConstructor[T] = macro TraitConstructorMacro.mkTraitConstructor[T]
 
@@ -43,28 +41,29 @@ object TraitConstructor {
 }
 
 object FactoryConstructor {
-  def apply[T: FactoryConstructor]: FactoryConstructor[T] = implicitly
+  def apply[T](implicit ctor: FactoryConstructor[T]): ProviderMagnet[T] = ctor.provider
 
   implicit def materialize[T]: FactoryConstructor[T] = macro FactoryConstructorMacro.mkFactoryConstructor[T]
 }
 
-private[constructors] sealed trait AnyConstructorOptionalMakeDSL[T] {
+private[constructors] sealed trait AnyConstructorOptionalMakeDSL[T] extends Any {
   def provider: ProviderMagnet[T]
 }
 object AnyConstructorOptionalMakeDSL {
-  def errorConstructor[T](tpe: String, nonWhitelistedMethods: List[String]): AnyConstructorOptionalMakeDSL[T] = {
-    new AnyConstructorOptionalMakeDSL[T] {
-      val provider: ProviderMagnet[T] = ProviderMagnet.lift(throwError(tpe, nonWhitelistedMethods, scaladoc = false))
-    }
+  private[constructors] final class Impl[T](val provider: ProviderMagnet[T]) extends AnyVal with AnyConstructorOptionalMakeDSL[T]
+
+  @inline def apply[T](providerMagnet: ProviderMagnet[T]): AnyConstructorOptionalMakeDSL.Impl[T] = {
+    new AnyConstructorOptionalMakeDSL.Impl[T](providerMagnet)
   }
 
-  def throwError(tpe: String, nonWhitelistedMethods: List[String], scaladoc: Boolean): Nothing = {
+  def errorConstructor[T](tpe: String, nonWhitelistedMethods: List[String]): AnyConstructorOptionalMakeDSL.Impl[T] = {
+    AnyConstructorOptionalMakeDSL[T](ProviderMagnet.lift(throwError(tpe, nonWhitelistedMethods)))
+  }
+
+  def throwError(tpe: String, nonWhitelistedMethods: List[String]): Nothing = {
     import izumi.fundamentals.platform.strings.IzString._
 
     throw new UnsupportedDefinitionException(
-      (if (scaladoc)
-        """This method was generated for ScalaDoc: if you're seeing this error,
-          |then AnyConstructorMacro mistook your compiler's behavior for running under Scaladoc, please report this as a bug!""".stripMargin else "") +
       s"""`make[$tpe]` DSL failure: Called an empty error constructor, because constructor for $tpe WAS NOT generated.
          |Because after `make` there were following method calls in the same expression:${nonWhitelistedMethods.niceList()}
          |
@@ -74,11 +73,5 @@ object AnyConstructorOptionalMakeDSL {
     )
   }
 
-  def apply[T](anyConstructor: AnyConstructor[T]): AnyConstructorOptionalMakeDSL[T] = {
-    new AnyConstructorOptionalMakeDSL[T] {
-      val provider: ProviderMagnet[T] = anyConstructor.provider
-    }
-  }
-
-  implicit def materialize[T]: AnyConstructorOptionalMakeDSL[T] = macro AnyConstructorMacro.anyConstructorOptionalMakeDSL[T]
+  implicit def materialize[T]: AnyConstructorOptionalMakeDSL.Impl[T] = macro AnyConstructorMacro.anyConstructorOptionalMakeDSL[T]
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala
index bfec830d8..3dd8d1630 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala
@@ -15,10 +15,10 @@ object AnyConstructorMacro {
 
   def make[B[_], T: c.WeakTypeTag](c: blackbox.Context): c.Expr[B[T]] = {
     import c.universe._
-    c.Expr[B[T]](q"""${c.prefix}._make[${weakTypeOf[T]}]((${c.inferImplicitValue(weakTypeOf[AnyConstructorOptionalMakeDSL[T]], silent = false)}).provider)""")
+    c.Expr[B[T]](q"""${c.prefix}._make[${weakTypeOf[T]}](${c.inferImplicitValue(weakTypeOf[AnyConstructorOptionalMakeDSL[T]], silent = false)}.provider)""")
   }
 
-  def anyConstructorOptionalMakeDSL[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AnyConstructorOptionalMakeDSL[T]] = {
+  def anyConstructorOptionalMakeDSL[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AnyConstructorOptionalMakeDSL.Impl[T]] = {
     import c.universe._
 
     val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`)
@@ -62,7 +62,7 @@ object AnyConstructorMacro {
 
     val tpe = weakTypeOf[T]
 
-    c.Expr[AnyConstructorOptionalMakeDSL[T]] {
+    c.Expr[AnyConstructorOptionalMakeDSL.Impl[T]] {
       maybeNonwhiteListedMethods match {
         case None =>
           c.abort(c.enclosingPosition,
@@ -77,7 +77,7 @@ object AnyConstructorMacro {
           if (nonwhiteListedMethods.isEmpty) {
             logger.log(s"""For $tpe found no `.from`-like calls in $maybeTree""".stripMargin)
 
-            q"""_root_.izumi.distage.constructors.AnyConstructorOptionalMakeDSL.apply[$tpe](${mkAnyConstructor[T](c)})"""
+            q"""_root_.izumi.distage.constructors.AnyConstructorOptionalMakeDSL.apply[$tpe](${mkAnyConstructor[T](c)}.provider)"""
           } else {
             logger.log(s"For $tpe found `.from`-like calls, generating ERROR constructor: $nonwhiteListedMethods")
 
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ConstructorMacros.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ConstructorMacros.scala
index 03dc5bb3f..f5c9d9bfa 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ConstructorMacros.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ConstructorMacros.scala
@@ -1,9 +1,10 @@
 package izumi.distage.constructors.macros
 
 import izumi.distage.model.providers.ProviderMagnet
-import izumi.distage.model.reflection.{Provider, ReflectionProvider}
+import izumi.distage.model.reflection.Provider.ProviderType
 import izumi.distage.model.reflection.macros.DIUniverseLiftables
 import izumi.distage.model.reflection.universe.StaticDIUniverse
+import izumi.distage.model.reflection.{Provider, ReflectionProvider}
 import izumi.fundamentals.reflection.ReflectionUtil
 
 import scala.annotation.tailrec
@@ -14,7 +15,7 @@ abstract class ClassConstructorMacros extends ConstructorMacrosBase {
 
   def mkClassConstructorProvider[T: c.WeakTypeTag](reflectionProvider: ReflectionProvider.Aux[u.type])(targetType: Type): c.Expr[ProviderMagnet[T]] = {
     val associations = reflectionProvider.constructorParameterLists(targetType)
-    generateProvider[T](associations)(args => q"new $targetType(...$args)")
+    generateProvider[T, ProviderType.Class.type](associations)(args => q"new $targetType(...$args)")
   }
 }
 object ClassConstructorMacros {
@@ -34,7 +35,7 @@ abstract class TraitConstructorMacros extends ConstructorMacrosBase {
     val u.Wiring.SingletonWiring.Trait(targetType, classParameters, methods, _) = wiring
     val traitParameters = methods.map(_.asParameter)
 
-    generateProvider[T](classParameters :+ traitParameters) {
+    generateProvider[T, ProviderType.Trait.type](classParameters :+ traitParameters) {
       argss => q"_root_.izumi.distage.constructors.TraitConstructor.wrapInitialization[$targetType](${
         val methodDefs = methods.zip(argss.last).map {
           case (method, paramSeqIndexTree) => method.traitMethodExpr(paramSeqIndexTree)
@@ -243,8 +244,8 @@ abstract class ConstructorMacrosBase {
     }
   }
 
-  def generateProvider[T: c.WeakTypeTag](parameters: List[List[u.Association.Parameter]])
-                                        (fun: List[List[Tree]] => Tree): c.Expr[ProviderMagnet[T]] = {
+  def generateProvider[T: c.WeakTypeTag, P <: ProviderType with Singleton: c.WeakTypeTag](parameters: List[List[u.Association.Parameter]])
+                                                                                         (fun: List[List[Tree]] => Tree): c.Expr[ProviderMagnet[T]] = {
     val tools = DIUniverseLiftables(u)
     import tools.{liftTypeToSafeType, liftableParameter}
 
@@ -272,7 +273,7 @@ abstract class ConstructorMacrosBase {
               ${Liftable.liftList.apply(parameters.flatten)},
               ${liftTypeToSafeType(weakTypeOf[T])},
               { ($seqName: _root_.scala.Seq[_root_.scala.Any]) => ${fun(casts)}: ${weakTypeOf[T]} },
-              true,
+             ${symbolOf[P].asClass.module},
             )
           )
         }"""
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/FactoryConstructorMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/FactoryConstructorMacro.scala
index 0161be296..dd33b663c 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/FactoryConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/FactoryConstructorMacro.scala
@@ -2,6 +2,7 @@ package izumi.distage.constructors.macros
 
 import izumi.distage.constructors.{DebugProperties, FactoryConstructor}
 import izumi.distage.model.providers.ProviderMagnet
+import izumi.distage.model.reflection.Provider.ProviderType
 import izumi.distage.model.reflection.universe.StaticDIUniverse
 import izumi.distage.reflection.ReflectionProviderDefaultImpl
 import izumi.fundamentals.reflection.{ReflectionUtil, TrivialMacroLogger}
@@ -26,7 +27,7 @@ object FactoryConstructorMacro {
     val macroUniverse.Wiring.Factory.WithProductDeps(factoryMethods, classParameters, methods, factoryProductsDeps) = symbolToFactory(reflectionProvider)(targetType)
     val allParameters = classParameters :+ (methods ++ factoryProductsDeps).map(_.asParameter)
 
-    val provider: c.Expr[ProviderMagnet[T]] = generateProvider[T](allParameters) {
+    val provider: c.Expr[ProviderMagnet[T]] = generateProvider[T, ProviderType.Factory.type](allParameters) {
       argss =>
       val dependencyArgMap = allParameters.iterator.flatten.map(_.key).zip(argss.iterator.flatten).toMap
       logger.log(
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Binding.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Binding.scala
index 099cb96c5..259b92470 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Binding.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Binding.scala
@@ -78,7 +78,7 @@ object Binding {
 
   implicit final class WithImplementation[R](private val binding: ImplBinding {def withImplDef(implDef: ImplDef): R}) extends AnyVal {
     def withImpl[T: Tag: AnyConstructor]: R =
-      withImpl[T](AnyConstructor[T].provider)
+      withImpl[T](AnyConstructor[T])
 
     def withImpl[T: Tag](instance: T): R =
       binding.withImplDef(ImplDef.InstanceImpl(SafeType.get[T], instance))
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Bindings.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Bindings.scala
index ec4cf9aaf..70afaf255 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Bindings.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Bindings.scala
@@ -9,10 +9,10 @@ import izumi.fundamentals.reflection.Tags.Tag
 
 object Bindings {
   def binding[T: Tag: AnyConstructor](implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] =
-    provider[T](AnyConstructor[T].provider)
+    provider[T](AnyConstructor[T])
 
   def binding[T: Tag, I <: T: Tag: AnyConstructor](implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] =
-    provider[T](AnyConstructor[I].provider)
+    provider[T](AnyConstructor[I])
 
   def binding[T: Tag, I <: T: Tag](instance: I)(implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] =
     SingletonBinding(DIKey.get[T], ImplDef.InstanceImpl(SafeType.get[I], instance), Set.empty, pos.get.position)
@@ -30,7 +30,7 @@ object Bindings {
     EmptySetBinding(DIKey.get[Set[T]], Set.empty, pos.get.position)
 
   def setElement[T: Tag, I <: T: Tag: AnyConstructor](implicit pos: CodePositionMaterializer): SetElementBinding = {
-    setElementProvider[T](AnyConstructor[I].provider)
+    setElementProvider[T](AnyConstructor[I])
   }
 
   def setElement[T: Tag, I <: T: Tag](instance: I)(implicit pos: CodePositionMaterializer): SetElementBinding = {
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala
index e52a4debd..24a94dc01 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala
@@ -97,7 +97,7 @@ object ModuleDefDSL {
 
   trait MakeDSLBase[T, AfterBind] {
     final def from[I <: T: AnyConstructor]: AfterBind =
-      from(AnyConstructor[I].provider)
+      from(AnyConstructor[I])
 
     final def from[I <: T: Tag](function: => I): AfterBind =
       from(ProviderMagnet.lift(function))
@@ -119,6 +119,7 @@ object ModuleDefDSL {
       * }}}
       *
       * Method reference:
+      *
       * {{{
       *   def constructor(@Id("special") i: Int): Unit = ()
       *
@@ -127,14 +128,14 @@ object ModuleDefDSL {
       *   make[Unit].from(constructor(_))
       * }}}
       *
-      * Function value (possibly with annotated signature):
+      * Function value with an annotated signature:
+      *
       * {{{
       *   val constructor: (Int @Id("special"), String @Id("special")) => Unit = (_, _) => ()
       *
       *   make[Unit].from(constructor)
       * }}}
       *
-      *
       * Annotation processing is done by a macro and macros are rarely perfect,
       * Prefer passing an inline lambda such as { x => y } or a method reference such as (method _) or (method(_))
       * Annotation info may be lost ONLY in a few cases detailed below, though:
@@ -173,6 +174,7 @@ object ModuleDefDSL {
       * }}}
       *
       * @see [[izumi.distage.model.reflection.macros.ProviderMagnetMacro]]
+      * @see 'Magnet' in the name refers to the Magnet Pattern: http://spray.io/blog/2012-12-13-the-magnet-pattern/
       */
     final def from[I <: T](function: ProviderMagnet[I]): AfterBind =
       bind(ImplDef.ProviderImpl(function.get.ret, function.get))
@@ -273,7 +275,7 @@ object ModuleDefDSL {
       *      - [[DIResource]]
       */
     final def fromResource[R <: DIResourceBase[Any, T]: AnyConstructor](implicit tag: ResourceTag[R]): AfterBind = {
-      fromResource[R](AnyConstructor[R].provider)
+      fromResource[R](AnyConstructor[R])
     }
 
     final def fromResource[R](instance: R with DIResourceBase[Any, T])(implicit tag: ResourceTag[R]): AfterBind = {
@@ -318,7 +320,7 @@ object ModuleDefDSL {
   trait SetDSLBase[T, AfterAdd, AfterMultiAdd] {
 
     final def add[I <: T: Tag: AnyConstructor](implicit pos: CodePositionMaterializer): AfterAdd =
-      add[I](AnyConstructor[I].provider)
+      add[I](AnyConstructor[I])
 
     final def add[I <: T: Tag](function: => I)(implicit pos: CodePositionMaterializer): AfterAdd =
       add(ProviderMagnet.lift(function))
@@ -401,7 +403,7 @@ object ModuleDefDSL {
       appendElement(ImplDef.EffectImpl(SafeType.get[I], SafeType.getK[F], ImplDef.ReferenceImpl(SafeType.get[F[I]], DIKey.get[F[I]].named(name), weak = false)), pos)
 
     final def addResource[R <: DIResourceBase[Any, T]: AnyConstructor](implicit tag: ResourceTag[R], pos: CodePositionMaterializer): AfterAdd =
-      addResource[R](AnyConstructor[R].provider)
+      addResource[R](AnyConstructor[R])
 
     final def addResource[R](instance: R with DIResourceBase[Any, T])(implicit tag: ResourceTag[R], pos: CodePositionMaterializer): AfterAdd = {
       import tag._
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/OpFormatter.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/OpFormatter.scala
index 4c9977d9a..ad53f0d68 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/OpFormatter.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/OpFormatter.scala
@@ -134,7 +134,7 @@ object OpFormatter {
     }
 
     private def formatFunction(provider: Provider): String = {
-      s"${provider.fun}(${provider.argTypes.map(formatType).mkString(", ")}): ${formatType(provider.ret)}"
+      s"${provider.funString}(${provider.argTypes.map(formatType).mkString(", ")}): ${formatType(provider.ret)}"
     }
 
     //    private def formatPrefix(prefix: Option[DIKey]): Seq[String] = {
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/providers/ProviderMagnet.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/providers/ProviderMagnet.scala
index b502ad43a..915f38a95 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/providers/ProviderMagnet.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/providers/ProviderMagnet.scala
@@ -3,6 +3,7 @@ package izumi.distage.model.providers
 import izumi.distage.model.exceptions.TODOBindingException
 import izumi.distage.model.reflection.macros.ProviderMagnetMacro
 import izumi.distage.model.reflection.Association.Parameter
+import izumi.distage.model.reflection.Provider.ProviderType
 import izumi.distage.model.reflection._
 import izumi.fundamentals.platform.language.{CodePositionMaterializer, unused}
 import izumi.fundamentals.platform.language.Quirks._
@@ -34,7 +35,7 @@ import scala.language.implicitConversions
   *   make[Unit].from(constructor(_))
   * }}}
   *
-  * Function value with annotated signature:
+  * Function value with an annotated signature:
   *
   * {{{
   *   val constructor: (Int @Id("special"), String @Id("special")) => Unit = (_, _) => ()
@@ -42,7 +43,6 @@ import scala.language.implicitConversions
   *   make[Unit].from(constructor)
   * }}}
   *
-  *
   * Annotation processing is done by a macro and macros are rarely perfect,
   * Prefer passing an inline lambda such as { x => y } or a method reference such as (method _) or (method(_))
   * Annotation info may be lost ONLY in a few cases detailed below, though:
@@ -83,6 +83,7 @@ import scala.language.implicitConversions
   * ProviderMagnet forms an applicative functor via its [[ProviderMagnet.pure]] & [[map2]] methods
   *
   * @see [[izumi.distage.model.reflection.macros.ProviderMagnetMacro]]]
+  * @see 'Magnet' in the name refers to the Magnet Pattern: http://spray.io/blog/2012-12-13-the-magnet-pattern/
   */
 final case class ProviderMagnet[+A](get: Provider) {
   def map[B: Tag](f: A => B): ProviderMagnet[B] = {
@@ -152,7 +153,7 @@ object ProviderMagnet {
         parameters = Seq.empty,
         ret = key.tpe,
         fun = _ => throw new TODOBindingException(s"Tried to instantiate a 'TODO' binding for $key defined at ${pos.get}!", key, pos),
-        isGenerated = false,
+        providerType = ProviderType.Function,
       )
     )
 
@@ -167,9 +168,18 @@ object ProviderMagnet {
         ret = SafeType.get[A],
         originalFun = () => a,
         fun = (_: Seq[Any]) => a,
-        isGenerated = false,
-      )
-    )
+        providerType = ProviderType.Function,
+      ))
+  }
+
+  def singleton[A <: Singleton: Tag](a: A): ProviderMagnet[A] = {
+    new ProviderMagnet[A](
+      Provider.ProviderImpl[A](
+        parameters = Seq.empty,
+        ret = SafeType.get[A],
+        fun = (_: Seq[Any]) => a,
+        providerType = ProviderType.Singleton,
+      ))
   }
 
   def single[A: Tag, B: Tag](f: A => B): ProviderMagnet[B] = {
@@ -184,18 +194,7 @@ object ProviderMagnet {
         ret = retTpe,
         originalFun = f,
         fun = (s: Seq[Any]) => f(s.head.asInstanceOf[A]),
-        isGenerated = false,
-      )
-    )
-  }
-
-  def singleton[A <: Singleton: Tag](a: A): ProviderMagnet[A] = {
-    new ProviderMagnet[A](
-      Provider.ProviderImpl[A](
-        parameters = Seq.empty,
-        ret = SafeType.get[A],
-        fun = (_: Seq[Any]) => a,
-        isGenerated = true,
+        providerType = ProviderType.Function,
       )
     )
   }
@@ -209,7 +208,7 @@ object ProviderMagnet {
         parameters = Seq(Parameter(symbolInfo, key)),
         ret = tpe,
         fun = (_: Seq[Any]).head,
-        isGenerated = false,
+        providerType = ProviderType.Function,
       )
     )
   }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/Provider.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/Provider.scala
index e88c6d87a..a9f0033b7 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/Provider.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/Provider.scala
@@ -1,26 +1,41 @@
 package izumi.distage.model.reflection
 
 import izumi.distage.model.exceptions.UnsafeCallArgsMismatched
+import izumi.distage.model.reflection.Provider.ProviderType
 
-trait DIFunction {
+trait Provider {
   def parameters: Seq[Association.Parameter]
-  def argTypes: Seq[SafeType]
-  def diKeys: Seq[DIKey]
-
+  final def diKeys: Seq[DIKey] = parameters.map(_.key)
+  final def argTypes: Seq[SafeType] = parameters.map(_.key.tpe)
+  final def arity: Int = parameters.size
   def ret: SafeType
-
   def originalFun: AnyRef
   def fun: Seq[Any] => Any
-  def arity: Int
-
-  def isGenerated: Boolean
+  def providerType: ProviderType
 
   def unsafeApply(refs: Seq[TypedRef[_]]): Any = {
     val args = verifyArgs(refs)
     fun(args)
   }
 
-  private[this] def verifyArgs(refs: Seq[TypedRef[_]]): Seq[Any] = {
+  def unsafeMap(newRet: SafeType, f: Any => _): Provider
+  def unsafeZip(newRet: SafeType, that: Provider): Provider
+  def addUnused(keys: Seq[DIKey]): Provider
+
+  private val eqField: AnyRef = {
+    if (providerType eq ProviderType.Function) originalFun
+    else if (providerType eq ProviderType.FunctionWithUnusedKeys) (originalFun, diKeys)
+    else ret
+  }
+  override final def equals(obj: Any): Boolean = obj match {
+    case that: Provider => eqField == that.eqField
+    case _              => false
+  }
+  override final def hashCode(): Int = eqField.hashCode()
+  override final def toString: String = s"$funString(${argTypes.mkString(", ")}): $ret"
+  final def funString = if (providerType eq ProviderType.Function) fun.toString else providerType.toString
+
+  protected[this] def verifyArgs(refs: Seq[TypedRef[_]]): Seq[Any] = {
     val (newArgs, types, typesCmp) = parameters.zip(refs).map {
       case (param, TypedRef(v, tpe, isByName)) =>
 
@@ -53,64 +68,60 @@ trait DIFunction {
   }
 }
 
-trait Provider extends DIFunction {
-  def unsafeMap(newRet: SafeType, f: Any => _): Provider
-  def unsafeZip(newRet: SafeType, that: Provider): Provider
-  def addUnused(keys: Seq[DIKey]): Provider
+object Provider {
 
-  override final def diKeys: Seq[DIKey] = parameters.map(_.key)
-  override final def argTypes: Seq[SafeType] = parameters.map(_.key.tpe)
-  override final val arity: Int = parameters.size
-
-  private def eqField: AnyRef = if (isGenerated) ret else originalFun
-  override final def equals(obj: Any): Boolean = {
-    obj match {
-      case that: Provider =>
-        eqField == that.eqField
-      case _ =>
-        false
-    }
+  sealed trait ProviderType
+  object ProviderType {
+    case object Class extends ProviderType
+    case object Trait extends ProviderType
+    case object Factory extends ProviderType
+    case object Singleton extends ProviderType
+    case object Function extends ProviderType
+    case object FunctionWithUnusedKeys extends ProviderType
   }
-  override final def hashCode(): Int = eqField.hashCode()
-  override final def toString: String = s"$fun(${argTypes.mkString(", ")}): $ret"
-}
-
-object Provider {
 
   final case class ProviderImpl[+A](
                                      parameters: Seq[Association.Parameter],
                                      ret: SafeType,
                                      originalFun: AnyRef,
                                      fun: Seq[Any] => Any,
-                                     isGenerated: Boolean,
+                                     providerType: ProviderType,
                                    ) extends Provider {
-    def this(parameters: Seq[Association.Parameter], ret: SafeType, fun: Seq[Any] => Any, isGenerated: Boolean) =
-      this(parameters, ret, fun, fun, isGenerated)
+    def this(parameters: Seq[Association.Parameter], ret: SafeType, fun: Seq[Any] => Any, providerType: ProviderType) =
+      this(parameters, ret, fun, fun, providerType)
 
     override def unsafeApply(refs: Seq[TypedRef[_]]): A =
       super.unsafeApply(refs).asInstanceOf[A]
 
     override def unsafeMap(newRet: SafeType, f: Any => _): ProviderImpl[_] =
-      copy(ret = newRet, fun = xs => f.apply(fun(xs)))
+      copy(
+        ret = newRet,
+        originalFun = f,
+        fun = xs => f.apply(fun(xs)),
+        providerType = ProviderType.Function,
+      )
 
     override def addUnused(keys: Seq[DIKey]): Provider =
-      copy(parameters = parameters ++ keys.map(key => Association.Parameter(SymbolInfo("<unused>", key.tpe, isByName = false, wasGeneric = false), key)))
+      copy(
+        parameters = parameters ++ keys.map(key => Association.Parameter(SymbolInfo("<unused>", key.tpe, isByName = false, wasGeneric = false), key)),
+        providerType = ProviderType.FunctionWithUnusedKeys,
+      )
 
     override def unsafeZip(newRet: SafeType, that: Provider): Provider = {
-      ProviderImpl(
-        parameters ++ that.parameters,
-        newRet,
-        { args0 =>
+      new ProviderImpl(
+        parameters = parameters ++ that.parameters,
+        ret = newRet,
+        fun = { args0 =>
           val (args1, args2) = args0.splitAt(arity)
           fun(args1) -> that.fun(args2)
         },
-        isGenerated,
+        providerType = ProviderType.Function,
       )
     }
   }
   object ProviderImpl {
-    @inline def apply[A](parameters: Seq[Association.Parameter], ret: SafeType, fun: Seq[Any] => Any, isGenerated: Boolean): ProviderImpl[A] =
-      new ProviderImpl(parameters, ret, fun, isGenerated)
+    @inline def apply[A](parameters: Seq[Association.Parameter], ret: SafeType, fun: Seq[Any] => Any, providerType: ProviderType): ProviderImpl[A] =
+      new ProviderImpl(parameters, ret, fun, providerType)
   }
 
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/macros/ProviderMagnetMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/macros/ProviderMagnetMacro.scala
index 2dc91445c..1a7aac5a9 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/macros/ProviderMagnetMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/macros/ProviderMagnetMacro.scala
@@ -3,6 +3,7 @@ package izumi.distage.model.reflection.macros
 import izumi.distage.constructors.DebugProperties
 import izumi.distage.model.providers.ProviderMagnet
 import izumi.distage.model.reflection.Provider
+import izumi.distage.model.reflection.Provider.ProviderType
 import izumi.distage.model.reflection.universe.StaticDIUniverse
 import izumi.distage.model.reflection.universe.StaticDIUniverse.Aux
 import izumi.distage.reflection.ReflectionProviderDefaultImpl
@@ -35,7 +36,7 @@ class ProviderMagnetMacro0[C <: blackbox.Context](val c: C) {
 
   def impl[R: c.WeakTypeTag](fun: Tree): c.Expr[ProviderMagnet[R]] = {
     val associations = analyze(fun, weakTypeOf[R])
-    val result = generateProvider[R](associations, fun, false)
+    val result = generateProvider[R](associations, fun)
 
     logger.log(
       s"""DIKeyWrappedFunction info:
@@ -70,10 +71,7 @@ class ProviderMagnetMacro0[C <: blackbox.Context](val c: C) {
       )
   }
 
-  def generateProvider[R: c.WeakTypeTag](parameters: List[Association.Parameter],
-                                         fun: Tree,
-                                         isGenerated: Boolean,
-                                        ): c.Expr[ProviderMagnet[R]] = {
+  def generateProvider[R: c.WeakTypeTag](parameters: List[Association.Parameter], fun: Tree): c.Expr[ProviderMagnet[R]] = {
     val tools = DIUniverseLiftables(macroUniverse)
     import tools.{liftTypeToSafeType, liftableParameter}
 
@@ -90,7 +88,7 @@ class ProviderMagnetMacro0[C <: blackbox.Context](val c: C) {
             ${liftTypeToSafeType(weakTypeOf[R])},
             fun,
             { seqAny => fun.asInstanceOf[(..${casts.map(_ => definitions.AnyTpe)}) => ${definitions.AnyTpe}](..$casts) },
-            $isGenerated,
+            ${symbolOf[ProviderType.Function.type].asClass.module},
           )
         )
       }"""
diff --git a/distage/distage-core/.jvm/src/main/scala/izumi/distage/provisioning/strategies/ProxyStrategyDefaultImplPlatformSpecific.scala b/distage/distage-core/.jvm/src/main/scala/izumi/distage/provisioning/strategies/ProxyStrategyDefaultImplPlatformSpecific.scala
index 3c27710af..0d56d625f 100644
--- a/distage/distage-core/.jvm/src/main/scala/izumi/distage/provisioning/strategies/ProxyStrategyDefaultImplPlatformSpecific.scala
+++ b/distage/distage-core/.jvm/src/main/scala/izumi/distage/provisioning/strategies/ProxyStrategyDefaultImplPlatformSpecific.scala
@@ -6,6 +6,7 @@ import izumi.distage.model.plan.Wiring
 import izumi.distage.model.provisioning.ProvisioningKeyProvider
 import izumi.distage.model.provisioning.proxies.ProxyProvider
 import izumi.distage.model.provisioning.proxies.ProxyProvider.{DeferredInit, ProxyContext, ProxyParams}
+import izumi.distage.model.reflection.Provider.ProviderType
 import izumi.distage.model.reflection.{Association, DIKey, MirrorProvider, SafeType}
 import izumi.fundamentals.reflection.TypeUtil
 
@@ -23,8 +24,8 @@ abstract class ProxyStrategyDefaultImplPlatformSpecific
     } else {
       val allArgsAsNull: Array[(Class[_], Any)] = {
         op.op match {
-          case WiringOp.CallProvider(_, Wiring.SingletonWiring.Function(provider, params), _) if provider.isGenerated =>
-            // for generated constructors, try to fetch known dependencies from the object graph
+          case WiringOp.CallProvider(_, Wiring.SingletonWiring.Function(provider, params), _) if provider.providerType eq ProviderType.Class =>
+            // for class constructors, try to fetch known dependencies from the object graph
             params.map(fetchNonforwardRefParamWithClass(context, op.forwardRefs, _)).toArray
           case _ =>
             // otherwise fill everything with nulls
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/roles/logger/SimpleLoggerConfigurator.scala b/distage/distage-framework/src/main/scala/izumi/distage/roles/logger/SimpleLoggerConfigurator.scala
index 09f9fb137..8d7f903e4 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/roles/logger/SimpleLoggerConfigurator.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/roles/logger/SimpleLoggerConfigurator.scala
@@ -77,6 +77,6 @@ object SimpleLoggerConfigurator {
                                 layout: Option[String],
                               )
   object SinksConfig {
-    implicit val configReader: DIConfigReader[SinksConfig] = DIConfigReader.derive
+    implicit val configReader: DIConfigReader[SinksConfig] = DIConfigReader.derived
   }
 }
diff --git a/project/Versions.scala b/project/Versions.scala
index c0f79e10e..23a5f1a92 100644
--- a/project/Versions.scala
+++ b/project/Versions.scala
@@ -29,5 +29,5 @@ object V {
   // good to drop - java
   val cglib_nodep = "3.3.0"
   val scala_java_time = "2.0.0-RC3"
-  val docker_java = "3.2.0"
+  val docker_java = "3.2.1"
 }
diff --git a/project/project/PluginVersions.scala b/project/project/PluginVersions.scala
index eec044830..607045462 100644
--- a/project/project/PluginVersions.scala
+++ b/project/project/PluginVersions.scala
@@ -1,5 +1,5 @@
 object PV {
-  val sbt_mdoc = "2.1.3"
+  val sbt_mdoc = "2.1.4"
   val sbt_paradox_material_theme = "0.6.0"
   val sbt_ghpages = "0.6.3"
   val sbt_site = "1.3.3"


Test patch:
diff --git a/distage/distage-core/src/test/scala/izumi/distage/dsl/DSLTest.scala b/distage/distage-core/src/test/scala/izumi/distage/dsl/DSLTest.scala
index 4fd41eeb3..9b1659707 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/dsl/DSLTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/dsl/DSLTest.scala
@@ -382,7 +382,7 @@ class DSLTest extends AnyWordSpec with MkInjector {
             ImplDef.ResourceImpl(
               SafeType.get[ImplXYZ],
               SafeType.getK[Identity],
-              ImplDef.ProviderImpl(SafeType.get[X], ClassConstructor[X].provider.get),
+              ImplDef.ProviderImpl(SafeType.get[X], ClassConstructor[X].get),
             ),
             Set.empty,
             SourceFilePosition.unknown
diff --git a/distage/distage-core/src/test/scala/izumi/distage/impl/ProviderMagnetTest.scala b/distage/distage-core/src/test/scala/izumi/distage/impl/ProviderMagnetTest.scala
index 0780b6a70..1d10232d7 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/impl/ProviderMagnetTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/impl/ProviderMagnetTest.scala
@@ -8,6 +8,7 @@ import izumi.distage.model.reflection.TypedRef
 import izumi.fundamentals.platform.build.ProjectAttributeMacro
 import izumi.fundamentals.platform.language.IzScala.ScalaRelease
 import izumi.fundamentals.platform.language.Quirks._
+import org.scalatest.exceptions.TestFailedException
 import org.scalatest.wordspec.AnyWordSpec
 
 class ProviderMagnetTest extends AnyWordSpec {
@@ -345,11 +346,24 @@ class ProviderMagnetTest extends AnyWordSpec {
       assert(p1 == p2)
     }
 
-    "progression test: FAILS to handle case class .apply references with argument annotations" in {
-      val fn = ProviderMagnet.apply(ClassArgAnn.apply _).get
+    "should be unequal after .map" in {
+      val p1 = ClassConstructor[Some[Int]]
+      val p2 = p1.map(identity)
+      val p3 = p2.map(identity)
+
+      assert(p1 != p2)
+      assert(p2 != p3)
+    }
+
+    "should be unequal after .addUnused" in {
+      val p1 = ClassConstructor[Some[Int]]
+      val p2 = p1.addDependency[Int]
+      val p3 = p2.addDependency[String]
+      val p4 = p3.addDependency[Unit]
 
-      assert(!fn.diKeys.contains(DIKey.get[String].named("classargann1")))
-      assert(!fn.diKeys.contains(DIKey.get[Int].named("classargann2")))
+      assert(p1 != p2)
+      assert(p2 != p3)
+      assert(p3 != p4)
     }
 
     "fail on multiple conflicting annotations on the same parameter" in {
@@ -357,8 +371,21 @@ class ProviderMagnetTest extends AnyWordSpec {
       assertTypeError("ProviderMagnet.apply(defconfannfn2 _)")
     }
 
+    "progression test: Can't handle case class .apply references with argument annotations" in {
+      val fn = ProviderMagnet.apply(ClassArgAnn.apply _).get
+
+      intercept[TestFailedException] {
+        assert(fn.diKeys.contains(DIKey.get[String].named("classargann1")))
+      }
+      intercept[TestFailedException] {
+        assert(fn.diKeys.contains(DIKey.get[Int].named("classargann2")))
+      }
+    }
+
     "progression test: Can't expand functions with implicit arguments" in {
-      assertTypeError("ProviderMagnet.apply(defimplicitfn _)")
+      intercept[TestFailedException] {
+        assertCompiles("ProviderMagnet.apply(defimplicitfn _)")
+      }
     }
   }
 
diff --git a/distage/distage-core/src/test/scala/izumi/distage/staticinjector/MacroAutoTraitsTest.scala b/distage/distage-core/src/test/scala/izumi/distage/staticinjector/MacroAutoTraitsTest.scala
index 597d34a0b..b37b194a4 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/staticinjector/MacroAutoTraitsTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/staticinjector/MacroAutoTraitsTest.scala
@@ -12,7 +12,7 @@ import org.scalatest.wordspec.AnyWordSpec
 class MacroAutoTraitsTest extends AnyWordSpec with MkInjector {
 
   "construct a basic trait" in {
-    val traitCtor = AnyConstructor[Aaa].provider.get
+    val traitCtor = AnyConstructor[Aaa].get
 
     val value = traitCtor.unsafeApply(Seq(TypedRef.byName(5), TypedRef.byName(false))).asInstanceOf[Aaa]
 
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxZIOEnv.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxZIOEnv.scala
index 477d3992f..4406a664c 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxZIOEnv.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxZIOEnv.scala
@@ -45,11 +45,11 @@ import scala.language.implicitConversions
   */
 trait DISyntaxZIOEnv {
   implicit def zioToFn[R: TraitConstructor, E: Tag, A: Tag](zio: ZIO[R, E, A]): ProviderMagnet[IO[E, A]] = {
-    TraitConstructor[R].provider.map(zio.provide)
+    TraitConstructor[R].map(zio.provide)
   }
 
   def args[R: Tag: TraitConstructor, E: Tag, A: Tag](zio: ProviderMagnet[ZIO[R, E, A]]): ProviderMagnet[IO[E, A]] = {
-    zio.map2(TraitConstructor[R].provider)(_.provide(_))
+    zio.map2(TraitConstructor[R])(_.provide(_))
   }
 }