Repository: 7mind/izumi (scala)

Issue:
BadSetAxis exception on the same element being added with and without an activation (InconsistentSet error in compile-time check)
From [Telegram [RU]](https://t.me/izumi_ru/4682), reproduction:

```scala
import distage._
import cats.effect.IO
import izumi.distage.framework.CoreCheckableAppSimple

trait PonvScenario[F[_]]
trait LogMessages[F[_]] extends PonvScenario[F]

object App extends CoreCheckableAppSimple[IO] with App {
  def a[F[_]: TagK] = new ModuleDef {
    make[LogMessages[F]]
    many[PonvScenario[F]].ref[LogMessages[F]]
  }
  
  def b[F[_]: TagK] = new ModuleDef {
   make[LogMessages[F]].tagged(Mode.Test)
   many[PonvScenario[F]].ref[LogMessages[F]].tagged(Mode.Test)
  }
  
  def program[F[_]: TagK] = a[F] ++ b[F]
  def module: ModuleBase = program[IO]

  def roots: Roots = Roots.Everything
  // BadSetAxis
  Injector[IO]().produce(program[IO], Roots.Everything, Activation(Mode -> Mode.Test)).use {
     _ => IO.unit
  }.unsafeRunSync()
}

object Test {
  import izumi.distage.framework.PlanCheck

  // InconsistentSetMembers
  object WiringCheck  extends PlanCheck.Main(App)
}
```

Scastie: https://scastie.scala-lang.org/GA5bSD3xRmORHidCmeTyNQ

Pull request:
closes #1439

Patch:
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/errors/DIError.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/errors/DIError.scala
index aa90e0480d..4395755518 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/errors/DIError.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/errors/DIError.scala
@@ -13,7 +13,6 @@ import izumi.distage.model.plan.repr.KeyMinimizer
 import izumi.distage.model.planning.ActivationChoices
 import izumi.distage.model.reflection.DIKey
 import izumi.fundamentals.graphs.DG
-import izumi.fundamentals.platform.IzumiProject
 
 sealed trait DIError
 
@@ -124,7 +123,7 @@ object DIError {
             case u: SetAxisIssue.UnconfiguredSetElementAxis =>
               s"Set element references axis ${u.unconfigured.mkString(",")} with undefined values: set ${u.set}, element ${u.element}"
             case i: SetAxisIssue.InconsistentSetElementAxis =>
-              IzumiProject.bugReportPrompt(s"Set ${i.set} has element with multiple axis sets: ${i.element}, unexpected axis sets: ${i.problems}")
+              s"Set ${i.set} has an element which is defined multiple times with valid activations: ${i.element}, unexpected axis sets: ${i.problems}"
           }.niceList()
     }
   }
diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanSolver.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanSolver.scala
index 475c911ddc..1137491c6c 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanSolver.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanSolver.scala
@@ -137,6 +137,13 @@ object PlanSolver {
         .mapValues(_.map(_._2).toList)
         .toMap
 
+      def handleSetWithSingleActivationSet(firstOp: CreateSet, memberKey: DIKey, ac: ActivationChoices, activations: Set[AxisPoint]) = {
+        isProperlyActivatedSetElement(ac, activations) {
+          unconfigured =>
+            Left(List(SetAxisIssue.UnconfiguredSetElementAxis(firstOp.target, memberKey, firstOp.origin.value, unconfigured)))
+        }.map(out => (memberKey, out))
+      }
+
       for {
         sets <- setMembersUnsafe.map {
           case (setKey, (firstOp, membersUnsafe)) =>
@@ -144,13 +151,25 @@ object PlanSolver {
               .map {
                 memberKey =>
                   reverseOpIndex.get(memberKey) match {
-                    case Some(value :: Nil) =>
-                      isProperlyActivatedSetElement(ac, value) {
-                        unconfigured =>
-                          Left(List(SetAxisIssue.UnconfiguredSetElementAxis(firstOp.target, memberKey, firstOp.origin.value, unconfigured)))
-                      }.map(out => (memberKey, out))
                     case Some(other) =>
-                      Left(List(SetAxisIssue.InconsistentSetElementAxis(firstOp.target, memberKey, other)))
+                      for {
+                        configuredElements <- other.map {
+                          v =>
+                            handleSetWithSingleActivationSet(firstOp, memberKey, ac, v)
+                              .map(isActivated => (v, isActivated))
+                        }.biAggregate
+                        out <- configuredElements.filter(_._2._2) match {
+                          case only :: Nil =>
+                            Right(only._2)
+                          case Nil =>
+                            Right((memberKey, false))
+                          case valid =>
+                            Left(List(SetAxisIssue.InconsistentSetElementAxis(firstOp.target, memberKey, valid.map(_._1))))
+                        }
+                      } yield {
+                        out
+                      }
+
                     case None =>
                       Right((memberKey, true))
                   }


Test patch:
diff --git a/distage/distage-core/src/test/scala/izumi/distage/fixtures/SetCases.scala b/distage/distage-core/src/test/scala/izumi/distage/fixtures/SetCases.scala
index 6ee98740ca..6011dfed8f 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/fixtures/SetCases.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/fixtures/SetCases.scala
@@ -68,5 +68,4 @@ object SetCases {
 
     class Service2 extends Service
   }
-
 }
diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedBindingsTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedBindingsTest.scala
index 74d77da5f2..fbc4d1d26b 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedBindingsTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedBindingsTest.scala
@@ -1,9 +1,9 @@
 package izumi.distage.injector
 
+import distage.{Injector, ModuleDef}
 import izumi.distage.fixtures.BasicCases.BasicCase1
 import izumi.distage.fixtures.SetCases.{SetCase2, SetCase4}
 import izumi.distage.model.PlannerInput
-import distage.{Injector, ModuleDef}
 import izumi.distage.model.exceptions.runtime.TODOBindingException
 import org.scalatest.wordspec.AnyWordSpec
 
@@ -12,7 +12,7 @@ import scala.util.Try
 class AdvancedBindingsTest extends AnyWordSpec with MkInjector {
 
   "Support TODO bindings" in {
-    import BasicCase1._
+    import BasicCase1.*
 
     val injector = mkInjector()
 
@@ -36,7 +36,7 @@ class AdvancedBindingsTest extends AnyWordSpec with MkInjector {
   }
 
   "Sets are being extended when injector inheritance happens (https://github.com/7mind/izumi/issues/330)" in {
-    import SetCase4._
+    import SetCase4.*
 
     val definitionParent = PlannerInput.everything(new ModuleDef {
       many[Service]
@@ -60,7 +60,7 @@ class AdvancedBindingsTest extends AnyWordSpec with MkInjector {
   }
 
   "Set element references are the same as their referees" in {
-    import SetCase2._
+    import SetCase2.*
 
     val definition = PlannerInput.everything(new ModuleDef {
       make[Service1]
@@ -77,5 +77,4 @@ class AdvancedBindingsTest extends AnyWordSpec with MkInjector {
     val set = context.get[Set[Service]]
     assert(set.head eq svc)
   }
-
 }
diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/AxisTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/AxisTest.scala
index 846907261b..7c93d30392 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/injector/AxisTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/injector/AxisTest.scala
@@ -2,7 +2,7 @@ package izumi.distage.injector
 
 import distage.{DIKey, Id, Injector, Module}
 import izumi.distage.fixtures.BasicCases.*
-import izumi.distage.fixtures.SetCases.SetCase1
+import izumi.distage.fixtures.SetCases.{SetCase1, SetCase2}
 import izumi.distage.model.PlannerInput
 import izumi.distage.model.definition.StandardAxis.{Mode, Repo}
 import izumi.distage.model.definition.{Activation, Axis, BootstrapModuleDef, ModuleDef}
@@ -213,6 +213,55 @@ class AxisTest extends AnyWordSpec with MkInjector {
     }
   }
 
+  "#1439: (case 2) properly handle indentical set element bindings tagged with contradictive axis" in {
+    import SetCase2._
+
+    val definition = new ModuleDef {
+      many[Service]
+        .add[Service1].tagged(Repo.Prod)
+        .add[Service1].tagged(Repo.Dummy)
+    }
+
+    val set = mkInjector()
+      .produce(PlannerInput(definition, Activation(Repo.Dummy), Roots(DIKey[Set[Service]])))
+      .unsafeGet()
+      .get[Set[Service]]
+    assert(set.size == 1)
+  }
+
+  "#1439: (case 1) fail on indentical set element bindings one of which tagged with active axis" in {
+    import SetCase2._
+
+    val definition = new ModuleDef {
+      many[Service]
+        .add[Service1]
+        .add[Service1].tagged(Repo.Dummy)
+    }
+
+    intercept[InjectorFailed] {
+      mkInjector()
+        .produce(PlannerInput(definition, Activation(Repo.Dummy), Roots(DIKey[Set[Service]])))
+        .unsafeGet()
+        .get[Set[Service]]
+    }
+  }
+
+  "#1439: (case 1) do not fail on indentical set element bindings one of which tagged with inactive axis" in {
+    import SetCase2._
+
+    val definition = new ModuleDef {
+      many[Service]
+        .add[Service1]
+        .add[Service1].tagged(Repo.Prod)
+    }
+
+    val set = mkInjector()
+      .produce(PlannerInput(definition, Activation(Repo.Dummy), Roots(DIKey[Set[Service]])))
+      .unsafeGet()
+      .get[Set[Service]]
+    assert(set.size == 1)
+  }
+
   "exclude set elements with unselected unsaturated axis" in {
     import SetCase1._