Repository: 7mind/izumi
Pull request #830 (2020-01-16 00:17:45)

Add ability to abstract over `@Id` and `@With` annotations using type aliases


Patch:
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 31d4fd503..7cc6cc273 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
@@ -145,7 +145,7 @@ class ProviderMagnetMacro0[C <: blackbox.Context](val c: C) {
   }
 
   protected[this] def analyzeValRef(sig: Type): List[Association.Parameter] = {
-    sig.typeArgs.init.map {
+    widenFunctionObject(sig).typeArgs.init.map {
       tpe =>
         val symbol = SymbolInfo.Static(
           name = c.freshName(tpe.typeSymbol.name.toString),
@@ -159,4 +159,13 @@ class ProviderMagnetMacro0[C <: blackbox.Context](val c: C) {
     }
   }
 
+  protected[this] def widenFunctionObject(sig: Type): Type = {
+    sig match {
+      case s: SingleTypeApi =>
+        sig.baseType(s.sym.typeSignature.baseClasses.find(definitions.FunctionClass.seq.contains(_)).get)
+      case _ =>
+        sig
+    }
+  }
+
 }
diff --git a/doc/microsite/src/main/tut/distage/basics.md b/doc/microsite/src/main/tut/distage/basics.md
index 0fe88c123..6c99c916b 100644
--- a/doc/microsite/src/main/tut/distage/basics.md
+++ b/doc/microsite/src/main/tut/distage/basics.md
@@ -102,7 +102,16 @@ new ModuleDef {
 }
 ```
 
-For true non-singleton semantics, you must create explicit factory classes or generate them (see @ref[Auto-Factories](#auto-factories))
+You can abstract over annotations with type aliases or with string constants:
+
+```scala mdoc:to-string
+object Ids {
+  final val byer1Id = "byer-1"
+  type Byer1 = Byer @Id(byer1Id)
+}
+```
+
+For true non-singleton semantics, you must create explicit factory classes, or generate them (see @ref[Auto-Factories](#auto-factories))
 
 ### Activation Axis
 
@@ -528,7 +537,7 @@ All unimplemented methods _with parameters_ in a trait will be filled by factory
 Given a class `ActorFactory`:
 
 ```scala mdoc:to-string
-import distage._
+import distage.ModuleDef
 import java.util.UUID
 
 class SessionStorage
@@ -561,30 +570,38 @@ class ActorFactoryImpl(sessionStorage: SessionStorage) extends ActorFactory {
 }
 ```
 
-`@With` annotation can be used to specify the implementation class, when the factory result is abstract:
+`@With` annotation can be used to specify the implementation class, to avoid leaking the implementation type in factory method result:
+
+```scala mdoc:to-string:reset
+import distage.{ModuleDef, Injector, With}
 
-```scala mdoc:to-string
 trait Actor { 
- def anyToUnit: Any => Unit = _ => ()
+  def receive(msg: Any): Unit
 }
 
 object Actor {
   trait Factory {
-    def mkActor(name: String): Actor @With[Actor.Impl]
+    def newActor(id: String): Actor @With[Actor.Impl]
   }
 
-  final class Impl(name: String) extends Actor{
-    override def anyToUnit: Any => Unit = msg => println(s"Actor `$name` received a message: $msg")
+  final class Impl(id: String, config: Actor.Configuration) extends Actor {
+    def receive(msg: Any) = {
+      val response = s"Actor `$id` received a message: $msg"
+      println(if (config.allCaps) response.toUpperCase else response)
+    }
   }
+
+  final case class Configuration(allCaps: Boolean)
 }
 
 val factoryModule = new ModuleDef {
   make[Actor.Factory]
+  make[Actor.Configuration].from(Actor.Configuration(allCaps = false))
 }
 
 Injector()
-  .produce(factoryModule, GCMode.NoGC)
-  .use(_.get[Actor.Factory].mkActor("Martin").anyToUnit("ping"))
+  .produceGet[Actor.Factory](factoryModule)
+  .use(_.newActor("Martin Odersky").receive("ping"))
 ```
 
 You can use this feature to concisely provide non-Singleton semantics for some of your components.
diff --git a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/AnnotationTools.scala b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/AnnotationTools.scala
index c5ef28a15..d4261396b 100644
--- a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/AnnotationTools.scala
+++ b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/AnnotationTools.scala
@@ -20,11 +20,11 @@ object AnnotationTools {
     getAllTypeAnnotations(u)(typ).find(annotationTypeEq(u)(annType, _))
 
   def getAllTypeAnnotations(u: Universe)(typ: u.Type): List[u.Annotation] =
-    stripByName(u)(typ.finalResultType) match {
+    stripByName(u)(typ.finalResultType.dealias) match {
       case t: u.AnnotatedTypeApi =>
         t.annotations
       case _ =>
-        List()
+        Nil
     }
 
   def annotationTypeEq(u: Universe)(tpe: u.Type, ann: u.Annotation): Boolean =
diff --git a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTagImpl.scala b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTagImpl.scala
index d62756c9a..8617b2f78 100644
--- a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTagImpl.scala
+++ b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTagImpl.scala
@@ -57,7 +57,7 @@ final class LightTypeTagImpl[U <: SingletonUniverse](val u: U, withCache: Boolea
   @inline private[this] final val it = u.asInstanceOf[scala.reflect.internal.Types]
 
   def makeFullTagImpl(tpe0: Type): LightTypeTag = {
-    val tpe = norm(tpe0).dealias
+    val tpe = norm(tpe0.dealias).dealias
     val lttRef = makeRef(tpe)
 
     val allReferenceComponents: Set[Type] = Set(tpe) ++ {


Test patch:
diff --git a/distage/distage-core/src/test/scala/izumi/distage/fixtures/BasicCases.scala b/distage/distage-core/src/test/scala/izumi/distage/fixtures/BasicCases.scala
index a499af3c3..ae49bf7c9 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/fixtures/BasicCases.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/fixtures/BasicCases.scala
@@ -162,4 +162,11 @@ Forest fire, climbin' higher, real life, it can wait""")
 
   }
 
+  object BasicCase7 {
+    type Port = Int @Id("port")
+    type Address = String @Id("address")
+
+    final case class ServerConfig(port: Port, address: Address)
+  }
+
 }
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 086fe78fc..54883439e 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
@@ -8,10 +8,8 @@ import izumi.distage.model.definition.Binding.SetElementBinding
 import izumi.distage.model.definition.{BindingTag, Id}
 import izumi.distage.model.exceptions.{ConflictingDIKeyBindingsException, ProvisioningException}
 import izumi.distage.model.plan.ExecutableOp.ImportDependency
-import org.scalatest.wordspec.AnyWordSpec
 import org.scalatest.exceptions.TestFailedException
-
-
+import org.scalatest.wordspec.AnyWordSpec
 
 class BasicTest extends AnyWordSpec with MkInjector {
 
@@ -336,4 +334,19 @@ class BasicTest extends AnyWordSpec with MkInjector {
       case SetElementBinding(_, _, s, _) if Set.apply[BindingTag]("t1", "t2").diff(s).isEmpty => true
     }.nonEmpty)
   }
+
+  "Can abstract over Id annotations with type aliases" in {
+    import BasicCase7._
+
+    val definition = PlannerInput.noGc(new ModuleDef {
+      make[Int].named("port").from(80)
+      make[String].named("address").from("localhost")
+      make[ServerConfig].from(ServerConfig)
+    })
+
+    val context = Injector.Standard().produceUnsafe(definition)
+
+    assert(context.get[ServerConfig].port == context.get[Int]("port"))
+    assert(context.get[ServerConfig].address == context.get[String]("address"))
+  }
 }