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

Do not generate constructor for `make[X]` if `make[X]` is followed by `.from[Y]`


Patch:
diff --git a/distage/distage-config/src/main/scala/izumi/distage/config/codec/CirceConfigInstances.scala b/distage/distage-config/src/main/scala/izumi/distage/config/codec/CirceConfigInstances.scala
index 615c51185..568951439 100644
--- a/distage/distage-config/src/main/scala/izumi/distage/config/codec/CirceConfigInstances.scala
+++ b/distage/distage-config/src/main/scala/izumi/distage/config/codec/CirceConfigInstances.scala
@@ -20,7 +20,7 @@ object CirceConfigInstances extends CirceConfigInstances
 trait CirceConfigInstances extends LowPriorityCirceConfigInstances {
 
   // re-export instances from io.circe.config.syntax
-  // use `Exported` so that if user imports their own instances, user's instances will have higher priority
+  // use `Exported` so that if user imports their own instances, user instances will have higher priority
 
   implicit final val finiteDurationDecoder: Exported[Decoder[FiniteDuration]] = Exported(syntax.durationDecoder)
   implicit final val memorySizeDecoder: Exported[Decoder[ConfigMemorySize]] = Exported(syntax.memorySizeDecoder)
diff --git a/distage/distage-core/src/main/scala/distage/Injector.scala b/distage/distage-core/src/main/scala/distage/Injector.scala
index af39a4f1b..788784b99 100644
--- a/distage/distage-core/src/main/scala/distage/Injector.scala
+++ b/distage/distage-core/src/main/scala/distage/Injector.scala
@@ -75,7 +75,7 @@ object Injector {
 
   private[Injector] sealed trait InjectorBootstrap {
     def apply(overrides: BootstrapModule*): Injector
-    def apply(): Injector = apply(Nil: _*)
+    def apply(): Injector = apply(overrides = Nil: _*)
 
     final def apply(activation: Activation, overrides: BootstrapModule*): Injector = {
       apply(overrides :+ activationModule(activation): _*)
diff --git a/distage/distage-model/src/main/scala/izumi/distage/constructors/constructors.scala b/distage/distage-model/src/main/scala/izumi/distage/constructors/constructors.scala
index c5256fd3c..f6694603b 100644
--- a/distage/distage-model/src/main/scala/izumi/distage/constructors/constructors.scala
+++ b/distage/distage-model/src/main/scala/izumi/distage/constructors/constructors.scala
@@ -1,16 +1,17 @@
 package izumi.distage.constructors
 
 import izumi.distage.constructors.macros.{AnyConstructorMacro, ConcreteConstructorMacro, FactoryConstructorMacro, TraitConstructorMacro}
-import izumi.distage.model.exceptions.TraitInitializationFailedException
+import izumi.distage.model.definition.dsl.ModuleDefDSL
+import izumi.distage.model.exceptions.{TraitInitializationFailedException, UnsupportedDefinitionException}
 import izumi.distage.model.providers.ProviderMagnet
 import izumi.distage.model.reflection.universe.RuntimeDIUniverse.SafeType
 
 import scala.language.experimental.{macros => enableMacros}
 
-sealed trait AnyConstructor[T] {
+sealed trait AnyConstructor[T] extends AnyConstructorOptionalMakeDSL[T] {
   def provider: ProviderMagnet[T]
 
-  // FIXME: better provider equality scheme ???
+  // FIXME: better immutable provider equality scheme ???
   provider.get.asGenerated
   // FIXME: better immutable provider equality scheme ???
 }
@@ -39,8 +40,6 @@ object TraitConstructor {
 
   def wrapInitialization[A](tpe: SafeType)(init: => A): A = {
     try init catch {
-      case e: AbstractMethodError =>
-        throw new TraitInitializationFailedException(s"TODO: Failed to initialize trait $tpe. Probably it contains fields (val or var) though fields are not supported yet, see https://github.com/7mind/izumi/issues/26", tpe, e)
       case e: Throwable =>
         throw new TraitInitializationFailedException(s"Failed to initialize trait $tpe. It may be an issue with the trait or a framework bug", tpe, e)
     }
@@ -52,3 +51,30 @@ object FactoryConstructor {
 
   implicit def materialize[T]: FactoryConstructor[T] = macro FactoryConstructorMacro.mkFactoryConstructor[T]
 }
+
+sealed trait AnyConstructorOptionalMakeDSL[T] {
+  def provider: ProviderMagnet[T]
+}
+object AnyConstructorOptionalMakeDSL {
+  def error[T](tpe: String, nonWhitelistedMethods: Set[String]): AnyConstructorOptionalMakeDSL[T] = {
+    import izumi.fundamentals.platform.strings.IzString._
+    AnyConstructorOptionalMakeDSL[T](ConcreteConstructor[T](ProviderMagnet.lift[Nothing] {
+      throw new UnsupportedDefinitionException(
+        s"`make[$tpe]` DSL failure: Called an empty error constructor, constructor for $tpe WAS NOT generated because after" +
+        s""" `make` call there were following method calls in the same expression:${nonWhitelistedMethods.niceList()}
+             |
+             |The assumption is that all method calls that aren't in ${ModuleDefDSL.MakeDSLNoOpMethodsWhitelist}
+             |Will eventually call `.from`/`.using`/`.todo` and fill in the constructor.
+             |""".stripMargin
+      )
+    }))
+  }
+
+  def apply[T](anyConstructor: AnyConstructor[T]): AnyConstructorOptionalMakeDSL[T] = {
+    new AnyConstructorOptionalMakeDSL[T] {
+      override val provider: ProviderMagnet[T] = anyConstructor.provider
+    }
+  }
+
+  implicit def materialize[T]: AnyConstructorOptionalMakeDSL[T] = macro AnyConstructorMacro.optional[T]
+}
diff --git a/distage/distage-model/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala b/distage/distage-model/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala
index 4c4e1d574..70b3f10ba 100644
--- a/distage/distage-model/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala
+++ b/distage/distage-model/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala
@@ -1,6 +1,7 @@
 package izumi.distage.constructors.macros
 
-import izumi.distage.constructors.AnyConstructor
+import izumi.distage.constructors.{AnyConstructor, AnyConstructorOptionalMakeDSL}
+import izumi.distage.model.definition.dsl.ModuleDefDSL
 import izumi.distage.model.reflection.universe.StaticDIUniverse
 import izumi.distage.reflection.ReflectionProviderDefaultImpl
 import izumi.fundamentals.reflection.ReflectionUtil
@@ -8,10 +9,37 @@ import izumi.fundamentals.reflection.ReflectionUtil
 import scala.reflect.macros.blackbox
 
 object AnyConstructorMacro {
-  def mkAnyConstructor[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AnyConstructor[T]] =
-    mkAnyConstructorImpl[T](c, generateUnsafeWeakSafeTypes = false)
-  def mkAnyConstructorUnsafeWeakSafeTypes[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AnyConstructor[T]] =
-    mkAnyConstructorImpl[T](c, generateUnsafeWeakSafeTypes = true)
+  def mkAnyConstructor[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AnyConstructor[T]] = mkAnyConstructorImpl[T](c, false)
+  def mkAnyConstructorUnsafeWeakSafeTypes[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AnyConstructor[T]] = mkAnyConstructorImpl[T](c, true)
+
+  def optional[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AnyConstructorOptionalMakeDSL[T]] = {
+    import c.universe._
+
+    def findBlockWithMakeExpr(t: Tree): c.universe.Tree = {
+      val maybeTrees = t.collect {
+        case Template(_, _, l) => l.find(_.exists(_.pos == c.macroApplication.pos))
+        case Block(init, last) =>
+          (init.iterator ++ Iterator.single(last))
+            .find(_.exists(_.pos == c.macroApplication.pos))
+      }
+      maybeTrees.flatMap(_.toList).lastOption.getOrElse(EmptyTree)
+    }
+
+    val nonwhiteListedMethods = findBlockWithMakeExpr(c.enclosingClass).collect {
+      case Select(lhs, TermName(method))
+        if !ModuleDefDSL.MakeDSLNoOpMethodsWhitelist(method) && lhs.exists(_.pos == c.macroApplication.pos) =>
+        method
+    }
+
+    val tpe = weakTypeOf[T]
+    c.Expr[AnyConstructorOptionalMakeDSL[T]] {
+      if (nonwhiteListedMethods.isEmpty) {
+        q"""_root_.izumi.distage.constructors.AnyConstructorOptionalMakeDSL.apply[$tpe](${mkAnyConstructorImpl[T](c, false)})"""
+      } else {
+        q"""_root_.izumi.distage.constructors.AnyConstructorOptionalMakeDSL.error[$tpe](${tpe.toString},${ModuleDefDSL.MakeDSLNoOpMethodsWhitelist})"""
+      }
+    }
+  }
 
   def mkAnyConstructorImpl[T: c.WeakTypeTag](c: blackbox.Context, generateUnsafeWeakSafeTypes: Boolean): c.Expr[AnyConstructor[T]] = {
     import c.universe._
@@ -21,28 +49,18 @@ object AnyConstructorMacro {
 
     val tpe = ReflectionUtil.norm(c.universe: c.universe.type)(weakTypeOf[T])
 
-    try {
-      if (reflectionProvider.isConcrete(tpe)) {
-        ConcreteConstructorMacro.mkConcreteConstructorImpl[T](c, generateUnsafeWeakSafeTypes)
-      } else if (reflectionProvider.isFactory(tpe)) {
-        FactoryConstructorMacro.mkFactoryConstructor[T](c)
-      } else if (reflectionProvider.isWireableAbstract(tpe)) {
-        TraitConstructorMacro.mkTraitConstructorImpl[T](c, generateUnsafeWeakSafeTypes)
-      } else {
-        c.abort(
-          c.enclosingPosition,
-          s"""AnyConstructor failure: couldn't generate a constructor for $tpe!
-             |It's neither a concrete class, nor a factory, nor a trait!""".stripMargin
-        )
-      }
-    } catch {
-      case _: Throwable =>
-        c.Expr[AnyConstructor[T]](q"""{
-          new _root_.izumi.distage.constructors.ConcreteConstructor[$tpe](
-            _root_.izumi.distage.model.providers.ProviderMagnet[$tpe](() =>
-              throw new _root_.izumi.distage.model.exceptions.UnsupportedDefinitionException("AnyConstructor failure: No constructor could be generated for " + ${tpe.toString}))
-          )
-        }""")
+    if (reflectionProvider.isConcrete(tpe)) {
+      ConcreteConstructorMacro.mkConcreteConstructorImpl[T](c, generateUnsafeWeakSafeTypes)
+    } else if (reflectionProvider.isFactory(tpe)) {
+      FactoryConstructorMacro.mkFactoryConstructor[T](c)
+    } else if (reflectionProvider.isWireableAbstract(tpe)) {
+      TraitConstructorMacro.mkTraitConstructorImpl[T](c, generateUnsafeWeakSafeTypes)
+    } else {
+      c.abort(
+        c.enclosingPosition,
+        s"""AnyConstructor failure: couldn't generate a constructor for $tpe!
+           |It's neither a concrete class, nor a factory, nor a trait!""".stripMargin
+      )
     }
   }
 }
diff --git a/distage/distage-model/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala b/distage/distage-model/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala
index 48dab1824..1a487343d 100644
--- a/distage/distage-model/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala
+++ b/distage/distage-model/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala
@@ -1,6 +1,6 @@
 package izumi.distage.model.definition.dsl
 
-import izumi.distage.constructors.AnyConstructor
+import izumi.distage.constructors.{AnyConstructor, AnyConstructorOptionalMakeDSL}
 import izumi.distage.model.definition.Binding.{EmptySetBinding, ImplBinding, SetElementBinding, SingletonBinding}
 import izumi.distage.model.definition.DIResource.{DIResourceBase, ResourceTag}
 import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.MultipleInstruction.ImplWithReference
@@ -34,8 +34,8 @@ trait AbstractBindingDefDSL[BindDSL[_], MultipleDSL[_], SetDSL[_]] {
     bindingRef
   }
 
-  final protected def make[T: Tag: AnyConstructor](implicit pos: CodePositionMaterializer): BindDSL[T] = {
-    val ref = _registered(new SingletonRef(Bindings.binding[T]))
+  final protected def make[T: Tag](implicit optionalConstructor: AnyConstructorOptionalMakeDSL[T], pos: CodePositionMaterializer): BindDSL[T] = {
+    val ref = _registered(new SingletonRef(Bindings.provider[T](optionalConstructor.provider)))
     _bindDSL[T](ref)
   }
 
@@ -174,7 +174,8 @@ object AbstractBindingDefDSL {
               val key = DIKey.IdKey(b.key.tpe, s.id)(s.idContract)
               b.withTarget(key)
             case _: SetIdFromImplName =>
-              b.withTarget(DIKey.IdKey(b.key.tpe, b.implementation.implType.tag.longName.toString.toLowerCase))
+              // b.key.tpe is the same b.implementation.tpe because `SetIdFromImplName` comes before `SetImpl`...
+              b.withTarget(DIKey.IdKey(b.key.tpe, b.key.tpe.tag.longName.toString.toLowerCase))
           }
       }
     )
diff --git a/distage/distage-model/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala b/distage/distage-model/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala
index 882be65c4..8efa51c20 100644
--- a/distage/distage-model/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala
+++ b/distage/distage-model/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala
@@ -433,6 +433,19 @@ object ModuleDefDSL {
 
   // DSL state machine
 
+  /** These are the _only_ methods that can chained after `make`
+    * such that make[T] will still generate the constructor for `T`
+    *
+    * See [[izumi.distage.constructors.macros.AnyConstructorMacro.optional]]
+    *
+    * If ANY other method is chained in the same expression
+    * it's assumed that it will replace make[T]'s constructor,
+    * so the constructor for `T` will NOT be generated.
+    *
+    * Please update this when adding new methods to [[MakeDSL]]!
+    */
+  private[distage] final lazy val MakeDSLNoOpMethodsWhitelist = Set("named", "namedByImpl", "tagged", "alias")
+
   final class MakeDSL[T]
   (
     override protected val mutableState: SingletonRef,
diff --git a/distage/distage-model/src/main/scala/izumi/distage/reflection/ReflectionProviderDefaultImpl.scala b/distage/distage-model/src/main/scala/izumi/distage/reflection/ReflectionProviderDefaultImpl.scala
index 71e55747a..5e7cb1577 100644
--- a/distage/distage-model/src/main/scala/izumi/distage/reflection/ReflectionProviderDefaultImpl.scala
+++ b/distage/distage-model/src/main/scala/izumi/distage/reflection/ReflectionProviderDefaultImpl.scala
@@ -71,9 +71,9 @@ trait ReflectionProviderDefaultImpl extends ReflectionProvider {
             if (excessiveSymbols.nonEmpty) {
               throw new UnsupportedDefinitionException(
                 s"""Augmentation failure.
-                   |  * Type $tpe has been considered a factory because of abstract method `$factoryMethodSymb` with result type `$resultType`
-                   |  * But method signature contains unrequired symbols: $excessiveSymbols
-                   |  * Only the following symbols are requird: ${methodTypeWireable.requiredKeys}
+                   |  * Type $tpe has been considered a factory because of abstract method `${factoryMethodSymb.underlying}` with result type `$resultType`
+                   |  * But method signature types not required by result type constructor: ${excessiveSymbols.map(_.tpe.use(identity))}
+                   |  * Only the following types are required: ${methodTypeWireable.requiredKeys.map(_.tpe.use(identity))}
                    |  * This may happen in case you unintentionally bind an abstract type (trait, etc) as implementation type.""".stripMargin)
             }
 
@@ -89,11 +89,11 @@ trait ReflectionProviderDefaultImpl extends ReflectionProvider {
     }
   }
 
-  override def constructorParameterLists(symbl: TypeNative): List[List[Association.Parameter]] = ReflectionLock.synchronized {
-    selectConstructorArguments(symbl).toList.flatten.map(_.map(associationFromParameter))
+  override def constructorParameterLists(tpe: TypeNative): List[List[Association.Parameter]] = ReflectionLock.synchronized {
+    selectConstructorArguments(tpe).toList.flatten.map(_.map(associationFromParameter))
   }
 
-  private[this] def mkConstructorWiring(symbl: TypeNative): SingletonWiring.ReflectiveInstantiationWiring = symbl match {
+  private[this] def mkConstructorWiring(tpe: TypeNative): SingletonWiring.ReflectiveInstantiationWiring = tpe match {
     case ConcreteSymbol(symb) =>
       SingletonWiring.Constructor(SafeType(symb), constructorParameters(symb), getPrefix(symb))
 
@@ -101,10 +101,16 @@ trait ReflectionProviderDefaultImpl extends ReflectionProvider {
       SingletonWiring.AbstractSymbol(SafeType(symb), traitMethods(symb), getPrefix(symb))
 
     case FactorySymbol(_, _) =>
-      throw new UnsupportedWiringException(s"Factory cannot produce factories, it's pointless: $symbl", SafeType(symbl))
+      throw new UnsupportedWiringException(
+        s"""Augmentation failure. Factory cannot produce factories, it's pointless: $tpe
+           |  * Type $tpe has been considered a factory because of an unimplemented abstract method with parameters
+           |  * This may happen in case you unintentionally bind an abstract type (trait, etc) as implementation type.
+           |""".stripMargin,
+        SafeType(tpe)
+      )
 
     case _ =>
-      throw new UnsupportedWiringException(s"Wiring unsupported: $symbl", SafeType(symbl))
+      throw new UnsupportedWiringException(s"Wiring unsupported: $tpe", SafeType(tpe))
   }
 
   private[this] def constructorParameters(symbl: TypeNative): List[Association.Parameter] = {


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 56551afda..bfb0b4f40 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
@@ -1,11 +1,14 @@
 package izumi.distage.dsl
 
 import distage._
+import izumi.distage.constructors.{AnyConstructor, ConcreteConstructor, FactoryConstructor}
 import izumi.distage.fixtures.BasicCases._
 import izumi.distage.fixtures.SetCases._
 import izumi.distage.injector.MkInjector
-import izumi.distage.model.definition.Binding.SetElementBinding
+import izumi.distage.model.definition.Binding.{SetElementBinding, SingletonBinding}
 import izumi.distage.model.definition.{BindingTag, Bindings, ImplDef, Module}
+import izumi.fundamentals.platform.functional.Identity
+import izumi.fundamentals.platform.language.SourceFilePosition
 import org.scalatest.WordSpec
 
 class DSLTest extends WordSpec with MkInjector {
@@ -322,74 +325,84 @@ class DSLTest extends WordSpec with MkInjector {
       assert(definition2.bindings.map(_.tags.strings) == Set(Set("tag1", "tag2")))
     }
 
-    // ???
-//    "support binding to multiple interfaces" in {
-//      import BasicCase6._
-//
-//      val implXYZ: Identity[ImplXYZ] = new ImplXYZ
-//      val implXYZResource = DIResource.make(implXYZ)(_ => ())
-//
-//      val definition = new ModuleDef {
-//        bind[ImplXYZ]
-//          .to[TraitX]
-//          .to[TraitY]
-//          .to[TraitZ]
-//      }
-//
-//      assert(definition === Module.make(
-//        Set(
-//          Bindings.binding[ImplXYZ]
-//          , Bindings.reference[TraitX, ImplXYZ]
-//          , Bindings.reference[TraitY, ImplXYZ]
-//          , Bindings.reference[TraitZ, ImplXYZ]
-//        )
-//      ))
-//
-//      val definitionEffect = new ModuleDef {
-//        bindEffect(implXYZ).to[TraitX].to[TraitY].to[TraitZ]
-//      }
-//
-//      assert(definitionEffect === Module.make(
-//        Set(
-//          SingletonBinding(DIKey.get[ImplXYZ], ImplDef.EffectImpl(SafeType.get[ImplXYZ], SafeType.getK[Identity],
-//            ImplDef.InstanceImpl(SafeType.get[ImplXYZ], implXYZ)), Set.empty, SourceFilePosition.unknown)
-//          , Bindings.reference[TraitX, ImplXYZ]
-//          , Bindings.reference[TraitY, ImplXYZ]
-//          , Bindings.reference[TraitZ, ImplXYZ]
-//        )
-//      ))
-//
-//      val definitionResource = new ModuleDef {
-//        bindResource[DIResource.Simple[ImplXYZ]].to[TraitX].to[TraitY].to[TraitZ]
-//      }
-//
-//      assert(definitionResource === Module.make(
-//        Set(
-//          SingletonBinding(DIKey.get[ImplXYZ]
-//            , ImplDef.ResourceImpl(SafeType.get[ImplXYZ], SafeType.getK[Identity], ImplDef.TypeImpl(SafeType.get[DIResource.Simple[ImplXYZ]]))
-//            , Set.empty, SourceFilePosition.unknown)
-//          , Bindings.reference[TraitX, ImplXYZ]
-//          , Bindings.reference[TraitY, ImplXYZ]
-//          , Bindings.reference[TraitZ, ImplXYZ]
-//        )
-//      ))
-//
-//      val definitionResourceFn = new ModuleDef {
-//        bindResource(implXYZResource).to[TraitX].to[TraitY].to[TraitZ]
-//      }
-//
-//      assert(definitionResourceFn === Module.make(
-//        Set(
-//          SingletonBinding(DIKey.get[ImplXYZ]
-//            , ImplDef.ResourceImpl(SafeType.get[ImplXYZ], SafeType.getK[Identity], ImplDef.InstanceImpl(SafeType.get[DIResource[Identity, ImplXYZ]], implXYZResource))
-//            , Set.empty, SourceFilePosition.unknown)
-//          , Bindings.reference[TraitX, ImplXYZ]
-//          , Bindings.reference[TraitY, ImplXYZ]
-//          , Bindings.reference[TraitZ, ImplXYZ]
-//        )
-//      ))
-//
-//    }
+    "support binding to multiple interfaces" in {
+      import BasicCase6._
+
+      val implXYZ: Identity[ImplXYZ] = new ImplXYZ
+      val implXYZResource = DIResource.make(implXYZ)(_ => ())
+
+      val definition = new ModuleDef {
+        bind[ImplXYZ]
+          .to[TraitX]
+          .to[TraitY]
+          .to[TraitZ]
+      }
+
+      assert(definition == Module.make(
+        Set(
+          Bindings.binding[ImplXYZ]
+          , Bindings.reference[TraitX, ImplXYZ]
+          , Bindings.reference[TraitY, ImplXYZ]
+          , Bindings.reference[TraitZ, ImplXYZ]
+        )
+      ))
+
+      val definitionEffect = new ModuleDef {
+        bindEffect(implXYZ).to[TraitX].to[TraitY].to[TraitZ]
+      }
+
+      assert(definitionEffect == Module.make(
+        Set(
+          SingletonBinding(DIKey.get[ImplXYZ], ImplDef.EffectImpl(SafeType.get[ImplXYZ], SafeType.getK[Identity],
+            ImplDef.InstanceImpl(SafeType.get[ImplXYZ], implXYZ)), Set.empty, SourceFilePosition.unknown)
+          , Bindings.reference[TraitX, ImplXYZ]
+          , Bindings.reference[TraitY, ImplXYZ]
+          , Bindings.reference[TraitZ, ImplXYZ]
+        )
+      ))
+
+      class X extends DIResource.Simple[ImplXYZ] {
+        override def acquire: ImplXYZ = new ImplXYZ
+        override def release(resource: ImplXYZ): Unit = ()
+      }
+
+      val definitionResource = new ModuleDef {
+        bindResource[X].to[TraitX].to[TraitY].to[TraitZ]
+      }
+      val expectedResource = Module.make(
+        Set(
+          SingletonBinding(DIKey.get[ImplXYZ],
+            ImplDef.ResourceImpl(
+              SafeType.get[ImplXYZ],
+              SafeType.getK[Identity],
+              ImplDef.ProviderImpl(SafeType.get[X], ConcreteConstructor[X].provider.get),
+            ),
+            Set.empty,
+            SourceFilePosition.unknown
+          )
+          , Bindings.reference[TraitX, ImplXYZ]
+          , Bindings.reference[TraitY, ImplXYZ]
+          , Bindings.reference[TraitZ, ImplXYZ]
+        )
+      )
+      assert(definitionResource == expectedResource)
+
+      val definitionResourceFn = new ModuleDef {
+        bindResource(implXYZResource).to[TraitX].to[TraitY].to[TraitZ]
+      }
+
+      assert(definitionResourceFn == Module.make(
+        Set(
+          SingletonBinding(DIKey.get[ImplXYZ]
+            , ImplDef.ResourceImpl(SafeType.get[ImplXYZ], SafeType.getK[Identity], ImplDef.InstanceImpl(SafeType.get[DIResource[Identity, ImplXYZ]], implXYZResource))
+            , Set.empty, SourceFilePosition.unknown)
+          , Bindings.reference[TraitX, ImplXYZ]
+          , Bindings.reference[TraitY, ImplXYZ]
+          , Bindings.reference[TraitZ, ImplXYZ]
+        )
+      ))
+
+    }
 
     "support bindings to multiple interfaces (injector test)" in {
       import BasicCase6._
diff --git a/distage/distage-core/src/test/scala/izumi/distage/fixtures/HigherKindCases.scala b/distage/distage-core/src/test/scala/izumi/distage/fixtures/HigherKindCases.scala
index fdc7a8c98..c15e9162a 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/fixtures/HigherKindCases.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/fixtures/HigherKindCases.scala
@@ -65,25 +65,10 @@ object HigherKindCases {
       override def get: F[_] = Pointed[F].point(getResult * 2)
     }
 
-    abstract class TestProvider[A, F[_]: Pointed] {
-      def f(a: A): F[A]
-    }
-
-    abstract class TestProvider0[A, B, F[_]: Pointed] {
-      def f(a: A): F[A]
-    }
-
-    abstract class TestProvider1[A, G[_]: Pointed, F[_]: Pointed] {
-      def f(a: A): F[A]
-    }
-
-    abstract class TestProvider2[G[_]: Pointed, F[_]: Pointed, A] {
-      def f(a: A): F[A]
-    }
-
-    abstract class TestProvider3[A, B, C, F[_]: Pointed] {
-      def f(a: A): F[A]
-    }
+    class TestProvider[A, F[_]: Pointed]
+    class TestProvider0[A, B, F[_]: Pointed]
+    class TestProvider1[A, G[_]: Pointed, F[_]: Pointed]
+    class TestProvider2[G[_]: Pointed, F[_]: Pointed, A]
   }
 
   object HigherKindsCase2 {
diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedTypesTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedTypesTest.scala
index da4e7931f..cd2f494f6 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedTypesTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedTypesTest.scala
@@ -168,7 +168,6 @@ class AdvancedTypesTest extends WordSpec with MkInjector {
     import TypesCase3._
 
     class Definition[T: Tag, G <: T with Trait1: Tag: AnyConstructor, C <: T with Trait4: Tag: AnyConstructor] extends ModuleDef {
-      // FIXME: `make` support ???
       make[Dep]
       make[T with Trait4].from[C]
       make[T with Trait1].from[G]
@@ -195,9 +194,7 @@ class AdvancedTypesTest extends WordSpec with MkInjector {
   "handle generic parameters in abstract `with` types" in {
     import TypesCase3._
 
-    // FIXME should work ???
     class Definition[T <: Dep: Tag: AnyConstructor, K >: Trait5[T]: Tag] extends ModuleDef {
-      // FIXME: `make` support ???
       make[T]
       make[Trait3[T] with K].from[Trait5[T]]
     }
diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/BasicTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/BasicTest.scala
index cec7d826b..e0fce29a8 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/injector/BasicTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/injector/BasicTest.scala
@@ -6,9 +6,10 @@ import izumi.distage.fixtures.SetCases._
 import izumi.distage.model.PlannerInput
 import izumi.distage.model.definition.Binding.SetElementBinding
 import izumi.distage.model.definition.{BindingTag, Id}
-import izumi.distage.model.exceptions.{ConflictingDIKeyBindingsException, ProvisioningException, UnsupportedDefinitionException}
+import izumi.distage.model.exceptions.{ConflictingDIKeyBindingsException, ProvisioningException}
 import izumi.distage.model.plan.ExecutableOp.ImportDependency
 import org.scalatest.WordSpec
+import org.scalatest.exceptions.TestFailedException
 
 class BasicTest extends WordSpec with MkInjector {
 
@@ -59,28 +60,21 @@ class BasicTest extends WordSpec with MkInjector {
     assert(ss.isEmpty)
   }
 
-  "fails on wrong @Id annotation" in {
-    import BadAnnotationsCase._
-    val definition = PlannerInput.noGc(new ModuleDef {
-      // FIXME: `make` support ??? should be compile time error
-      make[TestDependency0]
-      make[TestClass]
-    })
+  "fails on wrong @Id annotation at compile-time" in {
+    val exc = intercept[TestFailedException] {
+      assertCompiles("""
+        import BadAnnotationsCase._
 
-    val injector = mkInjector()
+        val definition = PlannerInput.noGc(new ModuleDef {
+          make[TestDependency0]
+          make[TestClass]
+        })
 
-//    val exc = intercept[BadIdAnnotationException] {
-//      injector.produce(injector.plan(definition))
-//        .use(_.get[TestClass])
-//    }
-//
-//    assert(exc.getMessage == "Wrong annotation value, only constants are supported. Got: @izumi.distage.model.definition.Id(izumi.distage.model.definition.Id(BadAnnotationsCase.this.value))")
-
-    val exc = intercept[ProvisioningException] {
-      injector.produceUnsafe(injector.plan(definition)).get[TestClass]
+        val injector = mkInjector()
+        injector.produceUnsafe(injector.plan(definition)).get[TestClass]
+        """)
     }
-
-    assert(exc.getSuppressed.head.isInstanceOf[UnsupportedDefinitionException])
+    assert(exc.getMessage.contains("BadIdAnnotationException"))
   }
 
   "regression test: issue #762 example (Predef.String vs. java.lang.String)" in {
@@ -165,22 +159,6 @@ class BasicTest extends WordSpec with MkInjector {
     assert(context.get[TestClass]("named.test.class").correctWired())
   }
 
-  // FIXME ???
-//  "fail on unbindable" in {
-//    import BasicCase3._
-//
-//    val definition = PlannerInput.noGc(new ModuleBase {
-//      override def bindings: Set[Binding] = Set(
-//        SingletonBinding(DIKey.get[Dependency], ImplDef.TypeImpl(SafeType.get[Long]), Set.empty, CodePositionMaterializer().get.position)
-//      )
-//    })
-//
-//    val injector = mkInjector()
-//    intercept[UnsupportedWiringException] {
-//      injector.plan(definition)
-//    }
-//  }
-
   "fail on unsolvable conflicts" in {
     import BasicCase3._
 
diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/FactoriesTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/FactoriesTest.scala
index 143d6b6d9..07d13a9f4 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/injector/FactoriesTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/injector/FactoriesTest.scala
@@ -4,8 +4,8 @@ import distage.{ModuleDef, With}
 import izumi.distage.constructors.FactoryConstructor
 import izumi.distage.fixtures.FactoryCases._
 import izumi.distage.model.PlannerInput
-import izumi.distage.model.exceptions.{ProvisioningException, UnsupportedDefinitionException}
 import org.scalatest.WordSpec
+import org.scalatest.exceptions.TestFailedException
 
 import scala.language.reflectiveCalls
 
@@ -117,26 +117,27 @@ class FactoriesTest extends WordSpec with MkInjector {
     assert(instance.isInstanceOf[ConcreteDep])
   }
 
-  "cglib factory cannot produce factories" in {
-    val exc = intercept[ProvisioningException] {
-      import FactoryCase1._
+  "Factory cannot produce factories" in {
+    val exc = intercept[TestFailedException] {
+      assertCompiles("""
+        import FactoryCase1._
 
-      // FIXME: `make` support? should be compile-time error
-      val definition = PlannerInput.noGc(new ModuleDef {
-        make[FactoryProducingFactory]
-        make[Dependency]
-      })
+        // FIXME: `make` support? should be compile-time error
+        val definition = PlannerInput.noGc(new ModuleDef {
+          make[FactoryProducingFactory]
+          make[Dependency]
+        })
 
-      val injector = mkInjector()
-      val plan = injector.plan(definition)
-      val context = injector.produceUnsafe(plan)
+        val injector = mkInjector()
+        val plan = injector.plan(definition)
+        val context = injector.produceUnsafe(plan)
 
-      val instantiated = context.get[FactoryProducingFactory]
+        val instantiated = context.get[FactoryProducingFactory]
 
-      assert(instantiated.x().x().b == context.get[Dependency])
+        assert(instantiated.x().x().b == context.get[Dependency])
+      """)
     }
-//    assert(exc.getSuppressed.head.isInstanceOf[UnsupportedWiringException])
-    assert(exc.getSuppressed.head.isInstanceOf[UnsupportedDefinitionException])
+    assert(exc.getMessage.contains("Factory cannot produce factories"))
   }
 
   "cglib factory always produces new instances" in {
diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/InnerClassesTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/InnerClassesTest.scala
index 234b364a4..05f965527 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/injector/InnerClassesTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/injector/InnerClassesTest.scala
@@ -4,10 +4,11 @@ import izumi.distage.constructors.FactoryConstructor
 import izumi.distage.fixtures.InnerClassCases._
 import izumi.distage.model.PlannerInput
 import izumi.distage.model.definition.ModuleDef
-import izumi.distage.model.exceptions.{ProvisioningException, UnsupportedDefinitionException}
+import izumi.distage.model.exceptions.ProvisioningException
 import izumi.distage.provisioning.strategies.cglib.exceptions.CgLibInstantiationOpException
 import net.sf.cglib.core.CodeGenerationException
 import org.scalatest.WordSpec
+import org.scalatest.exceptions.TestFailedException
 
 class InnerClassesTest extends WordSpec with MkInjector {
   "can instantiate inner classes from stable objects where the classes are inherited from a trait" in {
@@ -138,19 +139,20 @@ class InnerClassesTest extends WordSpec with MkInjector {
   }
 
   "progression test: can't handle factories inside stable objects that contain inner classes from inherited traits that depend on types defined inside trait (macros can't)" in {
-    val exc = intercept[ProvisioningException] {
-      import InnerClassStablePathsCase.StableObjectInheritingTrait.{TestClass, TestDependency, TestFactory}
+    val exc = intercept[TestFailedException] {
+      assertCompiles("""
+        import InnerClassStablePathsCase.StableObjectInheritingTrait.{TestClass, TestDependency, TestFactory}
 
-      val definition = PlannerInput.noGc(new ModuleDef {
-        // FIXME: `make` support ??? should be a compile error
-        make[TestFactory]
-      })
+        val definition = PlannerInput.noGc(new ModuleDef {
+          make[TestFactory]
+        })
 
-      val context = mkInjector().produceUnsafe(definition)
+        val context = mkInjector().produceUnsafe(definition)
 
-      assert(context.get[TestFactory].mk(TestDependency()) == TestClass(TestDependency()))
+        assert(context.get[TestFactory].mk(TestDependency()) == TestClass(TestDependency()))
+        """)
     }
-    assert(exc.getSuppressed.head.isInstanceOf[UnsupportedDefinitionException])
+    assert(exc.getMessage.contains("UnsupportedDefinitionException"))
   }
 
   "progression test: can't find proper constructor for circular dependencies inside stable objects that contain inner classes from inherited traits that depend on types defined inside trait" in {
@@ -172,7 +174,7 @@ class InnerClassesTest extends WordSpec with MkInjector {
 
   "can now find proper constructor for by-name circular dependencies inside stable objects that contain inner classes from inherited traits that depend on types defined inside trait" in {
     import InnerClassStablePathsCase._
-import StableObjectInheritingTrait._
+    import StableObjectInheritingTrait._
 
     val definition = PlannerInput.noGc(new ModuleDef {
       make[ByNameCircular1]
diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/ResourceEffectBindingsTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/ResourceEffectBindingsTest.scala
index ca80c5673..9e28d2b3e 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/injector/ResourceEffectBindingsTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/injector/ResourceEffectBindingsTest.scala
@@ -114,7 +114,6 @@ class ResourceEffectBindingsTest extends WordSpec with MkInjector {
           .addEffect(Suspend2('a'))
           .addEffect(Suspend2('b'))
 
-        // FIXME: `make` support ???
         make[Unit].fromEffect {
           (ref: Ref[Fn, Set[Char]], set: Set[Char]) =>
             ref.update(_ ++ set).void
@@ -386,7 +385,6 @@ class ResourceEffectBindingsTest extends WordSpec with MkInjector {
         assertCompiles {
           """
           def x[F[_]]: ModuleDef = new ModuleDef {
-            // FIXME: `make` support ???
             make[Any].fromResource[DIResource[F, Any]](() => ???)
           }; ""
           """
diff --git a/distage/distage-testkit/src/test/scala/izumi/distage/testkit/scalatest/TestkitTest.scala b/distage/distage-testkit/src/test/scala/izumi/distage/testkit/scalatest/TestkitTest.scala
index 92ca87bf4..383caaeeb 100644
--- a/distage/distage-testkit/src/test/scala/izumi/distage/testkit/scalatest/TestkitTest.scala
+++ b/distage/distage-testkit/src/test/scala/izumi/distage/testkit/scalatest/TestkitTest.scala
@@ -3,7 +3,6 @@ package izumi.distage.testkit.scalatest
 import cats.effect.IO
 import distage.{DIKey, Id, ModuleBase, TagK}
 import izumi.distage.config.ConfigModuleDef
-import izumi.distage.config.model.ConfPathId
 import izumi.distage.model.Locator.LocatorRef
 import izumi.distage.model.definition.ModuleDef
 import izumi.distage.model.effect.DIEffect
@@ -72,7 +71,7 @@ abstract class TestkitTest[F[_]: TagK] extends TestkitSelftest[F] {
 
   override protected def refineBindings(roots: Set[DIKey], primaryModule: ModuleBase): ModuleBase = {
     super.refineBindings(roots, primaryModule) overridenBy new ModuleDef {
-      make[TestConf].named(ConfPathId("missing-test-section")).from(TestConf(111, 222))
+      make[TestConf].named("missing-test-section").from(TestConf(111, 222))
     }
   }
 }