Repository: 7mind/izumi
Pull request #828 (2020-01-15 21:08:38)

Improve error message when user tries to create a constructor for a type parameter without `AnyConstructor` context bound


Patch:
diff --git a/README.md b/README.md
index f37cba9ae..b7e31fd0f 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ Example projects:
 * [DIStage Example Project](https://github.com/7mind/distage-example)
 * [Idealingua Example Project with TypeScript and Scala](https://github.com/7mind/idealingua-example)
 
-Chats:
+Support Chats:
 
 * [Izumi on Gitter](https://gitter.im/7mind/izumi)
 * [distage Users on Telegram](https://t.me/distagesers)
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala
index 6ae1e0466..3e0dd5fd3 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala
@@ -1,13 +1,13 @@
 package izumi.distage.constructors
 
-import izumi.fundamentals.platform.properties.DebugProperties
+import izumi.fundamentals.platform.properties
 
 /**
   * Java properties that control debug output of [[AnyConstructor]] & [[izumi.distage.model.providers.ProviderMagnet]] macros
   *
   * @see [[DebugProperties]]
   */
-object DebugProperties extends DebugProperties {
+object DebugProperties extends properties.DebugProperties {
   final val `izumi.debug.macro.distage.constructors` = "izumi.debug.macro.distage.constructors"
   final val `izumi.debug.macro.distage.providermagnet` = "izumi.debug.macro.distage.providermagnet"
 }
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 bd856db30..8135cc76d 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
@@ -91,18 +91,19 @@ object AnyConstructorMacro {
     val macroUniverse = StaticDIUniverse(c)
     val reflectionProvider = ReflectionProviderDefaultImpl(macroUniverse)
 
-    val tpe = ReflectionUtil.norm(c.universe: c.universe.type)(weakTypeOf[T])
+    val targetType = ReflectionUtil.norm(c.universe: c.universe.type)(weakTypeOf[T])
+    requireConcreteTypeConstructor(c)("AnyConstructor", targetType)
 
-    if (reflectionProvider.isConcrete(tpe)) {
+    if (reflectionProvider.isConcrete(targetType)) {
       ClassConstructorMacro.mkClassConstructor[T](c)
-    } else if (reflectionProvider.isFactory(tpe)) {
+    } else if (reflectionProvider.isFactory(targetType)) {
       FactoryConstructorMacro.mkFactoryConstructor[T](c)
-    } else if (reflectionProvider.isWireableAbstract(tpe)) {
+    } else if (reflectionProvider.isWireableAbstract(targetType)) {
       TraitConstructorMacro.mkTraitConstructor[T](c)
     } else {
       c.abort(
         c.enclosingPosition,
-        s"""AnyConstructor failure: couldn't generate a constructor for $tpe!
+        s"""AnyConstructor failure: couldn't generate a constructor for $targetType!
            |It's neither a concrete class, nor a factory, nor a trait!""".stripMargin
       )
     }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ClassConstructorMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ClassConstructorMacro.scala
index 0dcfc0e1e..a5ed08aea 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ClassConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ClassConstructorMacro.scala
@@ -17,6 +17,7 @@ object ClassConstructorMacro {
     import c.universe._
 
     val targetType = ReflectionUtil.norm(c.universe: c.universe.type)(weakTypeOf[T])
+    requireConcreteTypeConstructor(c)("ClassConstructor", targetType)
 
     targetType match {
       case t: SingletonTypeApi =>
@@ -49,16 +50,16 @@ object ClassConstructorMacro {
         val res = c.Expr[ClassConstructor[T]] {
           q"{ new ${weakTypeOf[ClassConstructor[T]]}($provided) }"
         }
-        logger.log(s"Final syntax tree of concrete constructor for $targetType:\n$res")
+        logger.log(s"Final syntax tree of class for $targetType:\n$res")
         res
     }
   }
 
-  def mkClassConstructorUnwrapped(c: blackbox.Context)
-                                 (macroUniverse: StaticDIUniverse.Aux[c.universe.type])
-                                 (reflectionProvider: ReflectionProvider.Aux[macroUniverse.type],
-                                      logger: TrivialLogger)
-                                 (targetType: c.Type): (List[macroUniverse.Association.Parameter], c.Tree) = {
+  private[macros] def mkClassConstructorUnwrapped(c: blackbox.Context)
+                                                 (macroUniverse: StaticDIUniverse.Aux[c.universe.type])
+                                                 (reflectionProvider: ReflectionProvider.Aux[macroUniverse.type],
+                                                      logger: TrivialLogger)
+                                                 (targetType: c.Type): (List[macroUniverse.Association.Parameter], c.Tree) = {
     import c.universe._
 
     if (!reflectionProvider.isConcrete(targetType)) {
@@ -71,13 +72,13 @@ object ClassConstructorMacro {
     }
   }
 
-  def mkClassConstructorUnwrappedImpl(c: blackbox.Context)
-                                     (macroUniverse: StaticDIUniverse.Aux[c.universe.type])
-                                     (reflectionProvider: ReflectionProvider.Aux[macroUniverse.type],
-                                      logger: TrivialLogger)
-                                     (targetType: c.Type): (List[macroUniverse.Association.Parameter], List[c.Tree], List[List[c.universe.TermName]]) = {
+  private[macros] def mkClassConstructorUnwrappedImpl(c: blackbox.Context)
+                                                     (macroUniverse: StaticDIUniverse.Aux[c.universe.type])
+                                                     (reflectionProvider: ReflectionProvider.Aux[macroUniverse.type],
+                                                      logger: TrivialLogger)
+                                                     (targetType: c.Type): (List[macroUniverse.Association.Parameter], List[c.Tree], List[List[c.universe.TermName]]) = {
     val paramLists = reflectionProvider.constructorParameterLists(targetType)
-    val fnArgsNamesLists = paramLists.map(_.map(TraitConstructorMacro.mkArgFromAssociation(c)(macroUniverse)(logger)(_)))
+    val fnArgsNamesLists = paramLists.map(_.map(mkArgFromAssociation(c)(macroUniverse)(logger)(_)))
 
     val (associations, ctorArgs) = fnArgsNamesLists.flatten.map { case (p, a, _) => (p, a) }.unzip
     val ctorArgNamesLists = fnArgsNamesLists.map(_.map(_._3._2))
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 37c70d4aa..e4373bf0a 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
@@ -26,10 +26,11 @@ object FactoryConstructorMacro {
     import macroUniverse._
 
     val targetType = ReflectionUtil.norm(c.universe: c.universe.type)(weakTypeOf[T])
+    requireConcreteTypeConstructor(c)("FactoryConstructor", targetType)
 
     val factory@Factory(factoryMethods, factoryTraitDependencies) = reflectionProvider.symbolToWiring(targetType)
-    val traitMeta = factoryTraitDependencies.map(TraitConstructorMacro.mkArgFromAssociation(c)(macroUniverse)(logger)(_))
-    val paramMeta = factory.factoryProductDepsFromObjectGraph.map(TraitConstructorMacro.mkArgFromAssociation(c)(macroUniverse)(logger)(_))
+    val traitMeta = factoryTraitDependencies.map(mkArgFromAssociation(c)(macroUniverse)(logger)(_))
+    val paramMeta = factory.factoryProductDepsFromObjectGraph.map(mkArgFromAssociation(c)(macroUniverse)(logger)(_))
     val allMeta = traitMeta ++ paramMeta
     val dependencyMethods = traitMeta.map(_._3._1)
     val (dependencyAssociations, dependencyArgDecls, _) = allMeta.unzip3
@@ -94,7 +95,7 @@ object FactoryConstructorMacro {
 
     val allMethods = producerMethods ++ dependencyMethods
 
-    val instantiate = TraitConstructorMacro.newWithMethods(c)(targetType, Nil, allMethods)
+    val instantiate = TraitConstructorMacro.newTraitWithMethods(c)(targetType, Nil, allMethods)
 
     val constructor = q"(..$dependencyArgDecls) => _root_.izumi.distage.constructors.TraitConstructor.wrapInitialization[$targetType]($instantiate)"
 
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/TraitConstructorMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/TraitConstructorMacro.scala
index da9790475..a25ceb812 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/TraitConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/TraitConstructorMacro.scala
@@ -17,6 +17,7 @@ object TraitConstructorMacro {
     import c.universe._
 
     val targetType = ReflectionUtil.norm(c.universe: c.universe.type)(weakTypeOf[T])
+    requireConcreteTypeConstructor(c)("FactoryConstructor", targetType)
 
     val macroUniverse = StaticDIUniverse(c)
 
@@ -42,11 +43,11 @@ object TraitConstructorMacro {
     res
   }
 
-  def mkTraitConstructorUnwrapped(c: blackbox.Context)
-                                 (macroUniverse: StaticDIUniverse.Aux[c.universe.type])
-                                 (reflectionProvider: ReflectionProvider.Aux[macroUniverse.type],
-                                  logger: TrivialLogger)
-                                 (targetType: c.Type): (List[macroUniverse.Association.Parameter], c.Tree) = {
+  private[macros] def mkTraitConstructorUnwrapped(c: blackbox.Context)
+                                                 (macroUniverse: StaticDIUniverse.Aux[c.universe.type])
+                                                 (reflectionProvider: ReflectionProvider.Aux[macroUniverse.type],
+                                                  logger: TrivialLogger)
+                                                 (targetType: c.Type): (List[macroUniverse.Association.Parameter], c.Tree) = {
     import c.universe._
     import macroUniverse.Wiring.SingletonWiring.AbstractSymbol
 
@@ -68,13 +69,13 @@ object TraitConstructorMacro {
     val (wireMethods, _) = wireMethodsCtorArgNames.unzip
     val (ctorAssociations, classCtorArgs, ctorParams) = ClassConstructorMacro.mkClassConstructorUnwrappedImpl(c)(macroUniverse)(reflectionProvider, logger)(targetType)
 
-    val ctorLambda = newWithMethods(c)(targetType, ctorParams, wireMethods)
+    val ctorLambda = newTraitWithMethods(c)(targetType, ctorParams, wireMethods)
     val constructor = q"(..${classCtorArgs ++ traitCtorArgs}) => _root_.izumi.distage.constructors.TraitConstructor.wrapInitialization[$targetType]($ctorLambda)"
 
     (ctorAssociations ++ traitAssociations, constructor)
   }
 
-  def newWithMethods(c: blackbox.Context)(targetType: c.universe.Type, arguments: List[List[c.universe.TermName]], methods: List[c.universe.Tree]): c.universe.Tree = {
+  private[macros] def newTraitWithMethods(c: blackbox.Context)(targetType: c.universe.Type, arguments: List[List[c.universe.TermName]], methods: List[c.universe.Tree]): c.universe.Tree = {
     import c.universe._
 
     val parents = ReflectionUtil.intersectionTypeMembers[c.universe.type](targetType)
@@ -101,34 +102,4 @@ object TraitConstructorMacro {
     }
   }
 
-  def mkArgFromAssociation(c: blackbox.Context)
-                          (u: StaticDIUniverse.Aux[c.universe.type])
-                          (logger: TrivialLogger)
-                          (association: u.Association
-                          // parameter: u.Association.Parameter, ctorArgument: u.u.Tree, traitMethodImpl: u.u.Tree, ctorArgumentName: u.u.TermName
-                          ): (u.Association.Parameter, u.u.Tree, (u.u.Tree, u.u.TermName)) = {
-    import c.universe._
-    import u.Association._
-
-    association match {
-      case method: AbstractMethod =>
-        val paramTpe = method.symbol.finalResultType
-        val methodName = TermName(method.name)
-        val freshArgName = c.freshName(methodName)
-        // force by-name
-        val byNameParamTpe = appliedType(definitions.ByNameParamClass, paramTpe)
-
-        val parameter = method.asParameter
-        logger.log(s"original method return: $paramTpe, after by-name: $byNameParamTpe, $parameter")
-
-        (parameter, q"val $freshArgName: $byNameParamTpe", q"final lazy val $methodName: $paramTpe = $freshArgName" -> freshArgName)
-      case parameter: Parameter =>
-        val paramTpe = parameter.symbol.finalResultType
-        val methodName = TermName(parameter.name)
-        val freshArgName = c.freshName(TermName(parameter.name))
-
-        (parameter, q"val $freshArgName: $paramTpe", q"final lazy val $methodName: $paramTpe = $freshArgName" -> freshArgName)
-    }
-  }
-
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/package.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/package.scala
new file mode 100644
index 000000000..4b5ea9b20
--- /dev/null
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/package.scala
@@ -0,0 +1,51 @@
+package izumi.distage.constructors
+
+import izumi.distage.model.reflection.universe.StaticDIUniverse
+import izumi.fundamentals.platform.console.TrivialLogger
+import izumi.fundamentals.reflection.ReflectionUtil
+
+import scala.reflect.macros.blackbox
+
+package object macros {
+
+  private[macros] def requireConcreteTypeConstructor(c: blackbox.Context)(macroName: String, tpe: c.Type): Unit = {
+    if (!ReflectionUtil.allPartsStrong(tpe.dealias.typeConstructor)) {
+      c.abort(c.enclosingPosition,
+        s"""$macroName: Can't generate constructor for $tpe:
+           |Type constructor is an unresolved type parameter `${tpe.dealias.typeConstructor}`.
+           |Did you forget to put a $macroName context bound on the ${tpe.dealias.typeConstructor}, such as [${tpe.dealias.typeConstructor}: $macroName]?
+           |""".stripMargin)
+    }
+  }
+
+  private[macros] def mkArgFromAssociation(c: blackbox.Context)
+                                          (u: StaticDIUniverse.Aux[c.universe.type])
+                                          (logger: TrivialLogger)
+                                          (association: u.Association
+                                          // parameter: u.Association.Parameter, ctorArgument: u.u.Tree, traitMethodImpl: u.u.Tree, ctorArgumentName: u.u.TermName
+                                          ): (u.Association.Parameter, u.u.Tree, (u.u.Tree, u.u.TermName)) = {
+    import c.universe._
+    import u.Association._
+
+    association match {
+      case method: AbstractMethod =>
+        val paramTpe = method.symbol.finalResultType
+        val methodName = TermName(method.name)
+        val freshArgName = c.freshName(methodName)
+        // force by-name
+        val byNameParamTpe = appliedType(definitions.ByNameParamClass, paramTpe)
+
+        val parameter = method.asParameter
+        logger.log(s"original method return: $paramTpe, after by-name: $byNameParamTpe, $parameter")
+
+        (parameter, q"val $freshArgName: $byNameParamTpe", q"final lazy val $methodName: $paramTpe = $freshArgName" -> freshArgName)
+      case parameter: Parameter =>
+        val paramTpe = parameter.symbol.finalResultType
+        val methodName = TermName(parameter.name)
+        val freshArgName = c.freshName(TermName(parameter.name))
+
+        (parameter, q"val $freshArgName: $paramTpe", q"final lazy val $methodName: $paramTpe = $freshArgName" -> freshArgName)
+    }
+  }
+
+}
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 390e4d023..cd7011e70 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
@@ -1,3 +1,39 @@
 package izumi.distage.model.definition
 
+/**
+  * This annotation lets you choose a more specific implementation
+  * for a result of factory method other than its return type.
+  *
+  * Example:
+  *
+  * {{{
+  *   trait Actor {
+  *     def id: UUID
+  *   }
+  *
+  *   trait ActorFactory {
+  *     @With[ActorImpl]
+  *     def newActor(id: UUID): Actor
+  *   }
+  *
+  *   class ActorImpl(val id: UUID, someDependency: SomeDependency) extends Actor
+  *   class SomeDependency
+  *
+  *   val module = new ModuleDef {
+  *     make[ActorFactory]
+  *     // generated factory implementation:
+  *     //
+  *     // make[ActorFactory].from {
+  *     //  (someDependency: SomeDependency) =>
+  *     //    new ActorFactory {
+  *     //      override def newActor(id: UUID): Actor = {
+  *     //        new ActorImpl(id, someDependency)
+  *     //      }
+  *     //    }
+  *     // }
+  *   }
+  * }}}
+  *
+  * @see Documentation for [[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/reflection/ReflectionProviderDefaultImpl.scala b/distage/distage-core-api/src/main/scala/izumi/distage/reflection/ReflectionProviderDefaultImpl.scala
index da4701e14..785ffb45b 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/reflection/ReflectionProviderDefaultImpl.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/reflection/ReflectionProviderDefaultImpl.scala
@@ -76,7 +76,7 @@ trait ReflectionProviderDefaultImpl extends ReflectionProvider {
                 .map(p => keyFromParameter(SymbolInfo.Runtime(p, tpe, wasGeneric = false)))
             }
 
-            val methodTypeWireable = mkConstructorWiring(resultType)
+            val methodTypeWireable = mkConstructorWiring(factoryMethod, resultType)
 
             val excessiveSymbols = alreadyInSignature.toSet -- methodTypeWireable.requiredKeys
 
@@ -97,7 +97,7 @@ trait ReflectionProviderDefaultImpl extends ReflectionProvider {
         Wiring.Factory(mw, materials)
 
       case _ =>
-        mkConstructorWiring(tpe)
+        mkConstructorWiring(u.u.NoSymbol, tpe)
     }
   }
 
@@ -105,7 +105,7 @@ trait ReflectionProviderDefaultImpl extends ReflectionProvider {
     selectConstructorArguments(tpe).toList.flatten.map(_.map(associationFromParameter))
   }
 
-  private[this] def mkConstructorWiring(tpe: TypeNative): Wiring.SingletonWiring.ReflectiveInstantiationWiring = {
+  private[this] def mkConstructorWiring(factoryMethod: SymbNative, tpe: TypeNative): Wiring.SingletonWiring.ReflectiveInstantiationWiring = {
     tpe match {
       case ConcreteSymbol(t) =>
         Wiring.SingletonWiring.Constructor(t, constructorParameters(t), getPrefix(t))
@@ -115,15 +115,25 @@ trait ReflectionProviderDefaultImpl extends ReflectionProvider {
 
       case FactorySymbol(_, _) =>
         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
+          s"""Augmentation failure. Factory cannot produce factories, it's pointless.
+             |  * When trying to create an implementation for a factory `${factoryMethod.owner}`
+             |  * When trying to create a constructor for the result of `$factoryMethod` - `$tpe`
+             |  * Type `${factoryMethod.owner}` has been considered a factory because it's an abstract type and contains unimplemented abstract methods with parameters
+             |  * Type `$tpe` has been considered a factory because it's an abstract type and contains unimplemented abstract methods with parameters
+             |  * Did you forget a `distage.With` annotation on the factory method to specify a non-abstract implementation type?
              |  * This may happen in case you unintentionally bind an abstract type (trait, etc) as implementation type.
              |""".stripMargin,
-          SafeType.create(tpe)
+          SafeType.create(tpe),
         )
 
       case _ =>
-        throw new UnsupportedWiringException(s"Wiring unsupported: $tpe", SafeType.create(tpe))
+        val safeType = SafeType.create(tpe)
+        val factoryMsg = if (factoryMethod != u.u.NoSymbol) {
+          s"""
+             |  * When trying to create an implementation for result of `$factoryMethod` of factory `${factoryMethod.owner}`
+             |  * Type `${factoryMethod.owner}` has been considered a factory because it's an abstract type and contains unimplemented abstract methods with parameters""".stripMargin
+        } else ""
+        throw new UnsupportedWiringException(s"Wiring unsupported: `$tpe` / $safeType$factoryMsg", safeType)
     }
   }
 
@@ -259,11 +269,11 @@ trait ReflectionProviderDefaultImpl extends ReflectionProvider {
   }
 
   private[this] def isWireableMethod(decl: SymbNative): Boolean = {
-    decl.isMethod && decl.isAbstract && !decl.isSynthetic && decl.asMethod.paramLists.isEmpty
+    decl.isMethod && decl.isAbstract && !decl.isSynthetic && decl.owner != u.u.definitions.AnyClass && decl.asMethod.paramLists.isEmpty
   }
 
   private[this] def isFactoryMethod(decl: SymbNative): Boolean = {
-    decl.isMethod && decl.isAbstract && !decl.isSynthetic && decl.asMethod.paramLists.nonEmpty
+    decl.isMethod && decl.isAbstract && !decl.isSynthetic && decl.owner != u.u.definitions.AnyClass && decl.asMethod.paramLists.nonEmpty
   }
 
   private[this] def findConstructor(tpe: TypeNative): SymbNative = {
diff --git a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala
index f9b6b9989..8a697fa38 100644
--- a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala
+++ b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala
@@ -1,13 +1,13 @@
 package izumi.distage.plugins
 
-import izumi.fundamentals.platform.properties.DebugProperties
+import izumi.fundamentals.platform.properties
 
 /**
   * Java properties that control behavior of [[load.PluginLoader]]
   *
   * @see [[DebugProperties]]
   */
-object DebugProperties extends DebugProperties {
+object DebugProperties extends properties.DebugProperties {
   /** Scan classpath only once per test run */
   final val `izumi.distage.plugins.cache` = "izumi.distage.plugins.cache"
 }
diff --git a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/DebugProperties.scala b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/DebugProperties.scala
index 43a5ede95..28d23503f 100644
--- a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/DebugProperties.scala
+++ b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/DebugProperties.scala
@@ -1,13 +1,13 @@
 package izumi.fundamentals.reflection
 
-import izumi.fundamentals.platform.properties.DebugProperties
+import izumi.fundamentals.platform.properties
 
 /**
   * Java properties and macro settings that control behavior and debug output of Lightweight Reflection macros
   *
   * @see [[DebugProperties]]
   */
-object DebugProperties extends DebugProperties {
+object DebugProperties extends properties.DebugProperties {
   /**
     * To see macro debug output during compilation, set `-Dizumi.debug.macro.rtti=true` system property!
     *


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 5d21a736e..0f58b8fc0 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
@@ -9,6 +9,7 @@ import izumi.distage.model.definition.Binding.{SetElementBinding, SingletonBindi
 import izumi.distage.model.definition.{BindingTag, Bindings, ImplDef, Module}
 import izumi.fundamentals.platform.functional.Identity
 import izumi.fundamentals.platform.language.SourceFilePosition
+import org.scalatest.exceptions.TestFailedException
 import org.scalatest.wordspec.AnyWordSpec
 
 class DSLTest extends AnyWordSpec with MkInjector {
@@ -448,6 +449,29 @@ class DSLTest extends AnyWordSpec with MkInjector {
       assert(xInstance eq implInstance)
       assert(yInstance eq implInstance)
     }
+
+    "print a sensible error message at compile-time when user tries to derive a constructor for a type parameter" in {
+      val res1 = intercept[TestFailedException](assertCompiles(
+        """
+          def definition[T <: Int: Tag] = new ModuleDef {
+            make[Int].from[T]
+          }
+        """
+      ))
+      assert(res1.getMessage contains "[T: AnyConstructor]")
+      val res2 = intercept[TestFailedException](assertCompiles(
+        """
+          def definition[F[_]] = new ModuleDef {
+            make[Int].fromResource[DIResource[F, Int]]
+          }
+        """
+      ))
+      assert(res2.getMessage contains "Wiring unsupported: `F[Unit]`")
+      assert(res2.getMessage contains "trying to create an implementation")
+      assert(res2.getMessage contains "`method release`")
+      assert(res2.getMessage contains "`trait DIResource`")
+    }
+
   }
 
 }
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 d4f78e4e8..086fe78fc 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
@@ -63,7 +63,7 @@ class BasicTest extends AnyWordSpec with MkInjector {
   }
 
   "fails on wrong @Id annotation at compile-time" in {
-    val exc = intercept[TestFailedException] {
+    val res = intercept[TestFailedException] {
       assertCompiles("""
         import BadAnnotationsCase._
 
@@ -76,7 +76,7 @@ class BasicTest extends AnyWordSpec with MkInjector {
         injector.produceUnsafe(injector.plan(definition)).get[TestClass]
         """)
     }
-    assert(exc.getMessage.contains("BadIdAnnotationException"))
+    assert(res.getMessage.contains("BadIdAnnotationException"))
   }
 
   "regression test: issue #762 example (Predef.String vs. java.lang.String)" in {
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala
index 9eec6abc8..394a0aacd 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala
@@ -1,13 +1,13 @@
 package izumi.distage.testkit
 
-import izumi.fundamentals.platform.properties.DebugProperties
+import izumi.fundamentals.platform.properties
 
 /**
   * Java properties that debug logging for distage-testkit and caching of distage-testkit classpath scans (when used)
   *
   * @see [[DebugProperties]]
   */
-object DebugProperties extends DebugProperties {
+object DebugProperties extends properties.DebugProperties {
   /** Print debug messages */
   final val `izumi.distage.testkit.debug` = "izumi.distage.testkit.debug"