Repository: 7mind/izumi
Pull request #834 (2020-01-17 02:26:16)

In `make` DSL, allow `.named` & `.tagged` after `.from`


Patch:
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala
index f94e338a3..8c356dddb 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala
@@ -129,6 +129,9 @@ object AbstractBindingDefDSL {
           // b.key.tpe is the same b.implementation.tpe because `SetIdFromImplName` comes before `SetImpl`...
           b = b.withTarget(DIKey.IdKey(b.key.tpe, b.key.tpe.tag.longName.toString.toLowerCase))
         case AliasTo(key, pos) =>
+          // it's ok to retrieve `tags`, `implType` & `key` from `b` because all changes to
+          // `b` properties must come before first `aliased` call
+          // after first `aliased` no more changes are possible
           val newRef = SingletonBinding(key, ImplDef.ReferenceImpl(b.implementation.implType, b.key, weak = false), b.tags, pos)
           refs = newRef :: refs
       }
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 056da94e3..03b62d80d 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
@@ -306,7 +306,13 @@ object ModuleDefDSL {
       bind(ImplDef.ResourceImpl(SafeType.get[A], SafeType.getK[F], ImplDef.ReferenceImpl(SafeType.get[R], DIKey.get[R].named(name), weak = false)))
     }
 
+    def todo(implicit pos: CodePositionMaterializer): AfterBind = {
+      val provider = ProviderMagnet.todoProvider(key)(pos).get
+      bind(ImplDef.ProviderImpl(provider.ret, provider))
+    }
+
     protected[this] def bind(impl: ImplDef): AfterBind
+    protected[this] def key: DIKey
   }
 
   trait SetDSLBase[T, AfterAdd, AfterMultiAdd] {
@@ -428,7 +434,7 @@ object ModuleDefDSL {
 
   // DSL state machine
 
-  /** These are the _only_ methods that can chained after `make`
+  /** These are the _only_ (not `from`-like) methods that can chained after `make`
     * such that make[T] will still generate the constructor for `T`
     *
     * See [[izumi.distage.constructors.macros.AnyConstructorMacro.anyConstructorOptionalMakeDSL]]
@@ -441,11 +447,10 @@ object ModuleDefDSL {
     */
   private[distage] final lazy val MakeDSLNoOpMethodsWhitelist = Set("named", "namedByImpl", "tagged", "aliased")
 
-  final class MakeDSL[T]
-  (
+  final class MakeDSL[T](
     override protected val mutableState: SingletonRef,
     override protected val key: DIKey.TypeKey,
-  ) extends MakeDSLMutBindBase[T] {
+  ) extends MakeDSLBase[T, MakeDSLUnnamedAfterFrom[T]] with MakeDSLMutBase[T] {
 
     def named[I](name: I)(implicit idContract: IdContract[I]): MakeNamedDSL[T] = {
       addOp(SetId(name, idContract))(new MakeNamedDSL[T](_, key.named(name)))
@@ -461,13 +466,16 @@ object ModuleDefDSL {
       }
     }
 
+    protected[this] override def bind(impl: ImplDef): MakeDSLUnnamedAfterFrom[T] = {
+      addOp(SetImpl(impl))(new MakeDSLUnnamedAfterFrom[T](_, key))
+    }
+
   }
 
-  final class MakeNamedDSL[T]
-  (
+  final class MakeNamedDSL[T](
     override protected val mutableState: SingletonRef,
     override protected val key: DIKey.IdKey[_],
-  ) extends MakeDSLMutBindBase[T] {
+  ) extends MakeDSLBase[T, MakeDSLNamedAfterFrom[T]] with MakeDSLMutBase[T] {
 
     def tagged(tags: BindingTag*): MakeNamedDSL[T] = {
       addOp(AddTags(tags.toSet)) {
@@ -475,34 +483,61 @@ object ModuleDefDSL {
       }
     }
 
+    protected[this] override def bind(impl: ImplDef): MakeDSLNamedAfterFrom[T] = {
+      addOp(SetImpl(impl))(new MakeDSLNamedAfterFrom[T](_, key))
+    }
+
   }
 
-  final class MakeDSLAfterFrom[I]
-  (
+  final class MakeDSLNamedAfterFrom[T](
     override protected val mutableState: SingletonRef,
-    override protected val key: DIKey,
-  ) extends MakeDSLMutBase[I]
+    override protected val key: DIKey.IdKey[_],
+  ) extends MakeDSLMutBase[T] {
 
-  sealed trait MakeDSLMutBindBase[T] extends MakeDSLBase[T, MakeDSLAfterFrom[T]] with MakeDSLMutBase[T] {
-    final def todo(implicit pos: CodePositionMaterializer): MakeDSLAfterFrom[T] = {
-      val provider = ProviderMagnet.todoProvider(key)(pos).get
-      addOp(SetImpl(ImplDef.ProviderImpl(provider.ret, provider)))(new MakeDSLAfterFrom[T](_, key))
+    def tagged(tags: BindingTag*): MakeDSLNamedAfterFrom[T] = {
+      addOp(AddTags(tags.toSet)) {
+        new MakeDSLNamedAfterFrom[T](_, key)
+      }
+    }
+
+  }
+
+  final class MakeDSLUnnamedAfterFrom[T](
+    override protected val mutableState: SingletonRef,
+    override protected val key: DIKey.TypeKey,
+  ) extends MakeDSLMutBase[T] {
+
+    def named[I](name: I)(implicit idContract: IdContract[I]): MakeDSLNamedAfterFrom[T] = {
+      addOp(SetId(name, idContract))(new MakeDSLNamedAfterFrom[T](_, key.named(name)))
+    }
+
+    def namedByImpl: MakeDSLNamedAfterFrom[T] = {
+      addOp(SetIdFromImplName())(new MakeDSLNamedAfterFrom[T](_, key.named(key.toString)))
+    }
+
+    def tagged(tags: BindingTag*): MakeDSLUnnamedAfterFrom[T] = {
+      addOp(AddTags(tags.toSet)) {
+        new MakeDSLUnnamedAfterFrom[T](_, key)
+      }
     }
 
-    protected[this] final def bind(impl: ImplDef): MakeDSLAfterFrom[T] =
-      addOp(SetImpl(impl))(new MakeDSLAfterFrom[T](_, key))
   }
 
+  final class MakeDSLAfterAlias[T](
+    override protected val mutableState: SingletonRef,
+    override protected val key: DIKey.BasicKey,
+  ) extends MakeDSLMutBase[T]
+
   sealed trait MakeDSLMutBase[T] {
     protected[this] def mutableState: SingletonRef
-    protected[this] def key: DIKey
+    protected[this] def key: DIKey.BasicKey
 
-    def aliased[T1 >: T: Tag](implicit pos: CodePositionMaterializer): MakeDSLAfterFrom[T] = {
-      addOp(AliasTo(DIKey.get[T1], pos.get.position))(new MakeDSLAfterFrom[T](_, key))
+    def aliased[T1 >: T: Tag](implicit pos: CodePositionMaterializer): MakeDSLAfterAlias[T] = {
+      addOp(AliasTo(DIKey.get[T1], pos.get.position))(new MakeDSLAfterAlias[T](_, key))
     }
 
-    def aliased[T1 >: T: Tag](name: String)(implicit pos: CodePositionMaterializer): MakeDSLAfterFrom[T] = {
-      addOp(AliasTo(DIKey.get[T1].named(name), pos.get.position))(new MakeDSLAfterFrom[T](_, key))
+    def aliased[T1 >: T: Tag](name: String)(implicit pos: CodePositionMaterializer): MakeDSLAfterAlias[T] = {
+      addOp(AliasTo(DIKey.get[T1].named(name), pos.get.position))(new MakeDSLAfterAlias[T](_, key))
     }
 
     protected[this] final def addOp[R](op: SingletonInstruction)(newState: SingletonRef => R): R = {
@@ -510,8 +545,7 @@ object ModuleDefDSL {
     }
   }
 
-  final class SetDSL[T]
-  (
+  final class SetDSL[T](
     protected val mutableState: SetRef,
   ) extends SetDSLMutBase[T] {
 
@@ -525,8 +559,7 @@ object ModuleDefDSL {
     }
   }
 
-  final class SetNamedDSL[T]
-  (
+  final class SetNamedDSL[T](
     override protected val mutableState: SetRef,
   ) extends SetDSLMutBase[T] {
 
@@ -535,8 +568,7 @@ object ModuleDefDSL {
     }
   }
 
-  final class SetElementDSL[T]
-  (
+  final class SetElementDSL[T](
     override protected val mutableState: SetRef,
     mutableCursor: SetElementRef,
   ) extends SetDSLMutBase[T] {
@@ -551,8 +583,7 @@ object ModuleDefDSL {
     }
   }
 
-  final class MultiSetElementDSL[T]
-  (
+  final class MultiSetElementDSL[T](
     override protected val mutableState: SetRef,
     mutableCursor: MultiSetElementRef,
   ) extends SetDSLMutBase[T] {
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/BindingFormatter.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/BindingFormatter.scala
index ffad4c94c..927078746 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/BindingFormatter.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/BindingFormatter.scala
@@ -27,10 +27,10 @@ object BindingFormatter {
 
     override def formatImplDef(implDef: ImplDef): String = {
       implDef match {
-        case ImplDef.ReferenceImpl(_, key, weak) =>
-          if (weak) s"weak[${formatKey(key)}]" else s"using[${formatKey(key)}]"
-        case ImplDef.InstanceImpl(_, instance) =>
-          s"value($instance)"
+        case ImplDef.ReferenceImpl(implType, key, weak) =>
+          if (weak) s"weak[${formatKey(key)}]" else s"using[${formatKey(key)}: $implType]"
+        case ImplDef.InstanceImpl(implType, instance) =>
+          s"value($instance: $implType)"
         case ImplDef.ProviderImpl(_, function) =>
           s"call($function)"
         case ImplDef.EffectImpl(_, effectHKTypeCtor, effectImpl) =>
diff --git a/distage/distage-extension-config/src/main/scala/izumi/distage/config/ConfigModuleDef.scala b/distage/distage-extension-config/src/main/scala/izumi/distage/config/ConfigModuleDef.scala
index 89ba3cf3c..e918906c6 100644
--- a/distage/distage-extension-config/src/main/scala/izumi/distage/config/ConfigModuleDef.scala
+++ b/distage/distage-extension-config/src/main/scala/izumi/distage/config/ConfigModuleDef.scala
@@ -5,7 +5,7 @@ import izumi.distage.config.codec.ConfigReader
 import izumi.distage.config.model.AppConfig
 import izumi.distage.config.model.exceptions.DIConfigReadException
 import izumi.distage.model.definition.BindingTag.ConfTag
-import izumi.distage.model.definition.dsl.ModuleDefDSL.{MakeDSL, MakeDSLAfterFrom}
+import izumi.distage.model.definition.dsl.ModuleDefDSL.{MakeDSL, MakeDSLNamedAfterFrom, MakeDSLUnnamedAfterFrom}
 import izumi.distage.model.definition.{BootstrapModuleDef, ModuleDef}
 import izumi.distage.model.planning.PlanningHook
 import izumi.fundamentals.platform.language.CodePositionMaterializer
@@ -30,13 +30,13 @@ class ConfigPathExtractorModule extends BootstrapModuleDef {
 }
 
 trait ConfigModuleDef extends ModuleDef {
-  final def makeConfig[T: Tag: ConfigReader](path: String)(implicit pos: CodePositionMaterializer): MakeDSLAfterFrom[T] = {
+  final def makeConfig[T: Tag: ConfigReader](path: String)(implicit pos: CodePositionMaterializer): MakeDSLUnnamedAfterFrom[T] = {
     pos.discard()
     make[T].tagged(ConfTag(path)).from(decodeConfig[T](path))
   }
-  final def makeConfigNamed[T: Tag: ConfigReader](path: String)(implicit pos: CodePositionMaterializer): MakeDSLAfterFrom[T] = {
+  final def makeConfigNamed[T: Tag: ConfigReader](path: String)(implicit pos: CodePositionMaterializer): MakeDSLNamedAfterFrom[T] = {
     pos.discard()
-    make[T].tagged(ConfTag(path)).named(path).from(decodeConfig[T](path))
+    make[T].named(path).tagged(ConfTag(path)).from(decodeConfig[T](path))
   }
 
   final def decodeConfig[T: Tag: ConfigReader](path: String): AppConfig => T = {
@@ -45,10 +45,10 @@ trait ConfigModuleDef extends ModuleDef {
   }
 
   implicit final class FromConfig[T](private val dsl: MakeDSL[T]) {
-    def fromConfig(path: String)(implicit tag: Tag[T], dec: ConfigReader[T]): MakeDSLAfterFrom[T] = {
+    def fromConfig(path: String)(implicit tag: Tag[T], dec: ConfigReader[T]): MakeDSLUnnamedAfterFrom[T] = {
       dsl.tagged(ConfTag(path)).from(decodeConfig[T](path))
     }
-    def fromConfigNamed(path: String)(implicit tag: Tag[T], dec: ConfigReader[T]): MakeDSLAfterFrom[T] = {
+    def fromConfigNamed(path: String)(implicit tag: Tag[T], dec: ConfigReader[T]): MakeDSLNamedAfterFrom[T] = {
       dsl.named(path).tagged(ConfTag(path)).from(decodeConfig[T](path))
     }
   }


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 ccfb36447..6469d998a 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
@@ -459,6 +459,63 @@ class DSLTest extends AnyWordSpec with MkInjector {
       assert(yInstance eq implInstance)
     }
 
+    "support .named & .tagged calls after .from" in {
+      import BasicCase6._
+
+      val definition = new ModuleDef {
+        make[TraitX].from[ImplXYZ]
+          .named("other")
+          .tagged("x")
+          .aliased[TraitX]("other2")
+
+        make[ImplXYZ]
+          .from[ImplXYZ]
+          .tagged("xa")
+          .aliased[TraitY]
+
+        // can't call named after .named.from
+        assertTypeError("""
+        make[TraitX]
+          .named("x1")
+          .from[ImplXYZ]
+          .named("xy")""")
+
+        // can't call named twice
+        assertTypeError("""
+        make[TraitX]
+          .named("x1")
+          .named("x2")""")
+
+        // can't call from after aliased
+        assertTypeError("""
+        make[TraitY]
+          .aliased[TraitY]
+          .from[ImplXYZ]""")
+
+        // can't call tagged after aliased
+        assertTypeError("""
+        make[ImplXYZ]
+          .aliased[TraitY]
+          .tagged("xa")""")
+      }
+
+      assert(definition.bindings == Set(
+        Bindings.binding[TraitX, ImplXYZ]
+          .copy(
+            key = DIKey.get[TraitX].named("other"),
+            tags = Set("x"),
+          ),
+        Bindings.binding[ImplXYZ, ImplXYZ].addTags(Set("xa")),
+        Bindings.reference[TraitX, TraitX]
+          .copy(
+            key = DIKey.get[TraitX].named("other2"),
+            implementation = ImplDef.ReferenceImpl(SafeType.get[ImplXYZ], DIKey.get[TraitX].named("other"), weak = false),
+            tags = Set("x")
+          ),
+        Bindings.reference[TraitY, ImplXYZ].addTags(Set("xa")),
+      ))
+    }
+
     "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(
         """