Repository: 7mind/izumi
Pull request #1059 (2020-05-17 16:38:55)

Rename GCMode to Roots
* Make NonEmptySet an immutable Set
* add `DIKey.apply` short-hand (write `DIKey[T]`/`DIKey[T]("x")` instead of `DIKey.get[T]`/`DIKey.get[T].named("x")`

codecov-commenter: # [Codecov](https://codecov.io/gh/7mind/izumi/pull/1059?src=pr&el=h1) Report
> Merging [#1059](https://codecov.io/gh/7mind/izumi/pull/1059?src=pr&el=desc) into [develop](https://codecov.io/gh/7mind/izumi/commit/f8bfc125816eaecd91c91c0d8d7e89a80e2ed1aa&el=desc) will **increase** coverage by `0.01%`.
> The diff coverage is `79.03%`.


```diff
@@             Coverage Diff             @@
##           develop    #1059      +/-   ##
===========================================
+ Coverage    55.68%   55.70%   +0.01%     
===========================================
  Files          380      380              
  Lines         6909     6912       +3     
  Branches       357      363       +6     
===========================================
+ Hits          3847     3850       +3     
  Misses        3062     3062              
```


Patch:
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/Injector.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/Injector.scala
index 7d3f597c8..0009c7966 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/Injector.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/Injector.scala
@@ -3,7 +3,7 @@ package izumi.distage.model
 import izumi.distage.model.definition.DIResource.DIResourceBase
 import izumi.distage.model.definition.ModuleBase
 import izumi.distage.model.effect.DIEffect
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import izumi.distage.model.providers.ProviderMagnet
 import izumi.distage.model.reflection.DIKey
 import izumi.reflect.{Tag, TagK}
@@ -149,8 +149,8 @@ trait Injector extends Planner with Producer {
   final def produceF[F[_]: TagK: DIEffect](input: PlannerInput): DIResourceBase[F, Locator] = {
     produceF[F](plan(input))
   }
-  final def produceF[F[_]: TagK: DIEffect](bindings: ModuleBase, mode: GCMode): DIResourceBase[F, Locator] = {
-    produceF[F](plan(PlannerInput(bindings, mode)))
+  final def produceF[F[_]: TagK: DIEffect](bindings: ModuleBase, roots: Roots): DIResourceBase[F, Locator] = {
+    produceF[F](plan(PlannerInput(bindings, roots)))
   }
 
   final def produceRun[A: Tag](bindings: ModuleBase)(function: ProviderMagnet[A]): A = produceRunF[Identity, A](bindings)(function)
@@ -160,5 +160,5 @@ trait Injector extends Planner with Producer {
   final def produceGet[A: Tag](name: String)(bindings: ModuleBase): DIResourceBase[Identity, A] = produceGetF[Identity, A](name)(bindings)
 
   final def produce(input: PlannerInput): DIResourceBase[Identity, Locator] = produceF[Identity](input)
-  final def produce(bindings: ModuleBase, mode: GCMode): DIResourceBase[Identity, Locator] = produceF[Identity](bindings, mode)
+  final def produce(bindings: ModuleBase, roots: Roots): DIResourceBase[Identity, Locator] = produceF[Identity](bindings, roots)
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/Planner.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/Planner.scala
index 1ac70b5e0..ffbda1cab 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/Planner.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/Planner.scala
@@ -10,8 +10,8 @@ import izumi.distage.model.reflection._
 trait Planner extends PlanSplittingOps {
   def plan(input: PlannerInput): OrderedPlan
 
-  final def plan(bindings: ModuleBase, gcMode: GCMode): OrderedPlan = {
-    plan(PlannerInput(bindings, gcMode))
+  final def plan(bindings: ModuleBase, roots: Roots): OrderedPlan = {
+    plan(PlannerInput(bindings, roots))
   }
 
   // plan lifecycle
@@ -27,6 +27,3 @@ trait Planner extends PlanSplittingOps {
 
   def truncate(plan: OrderedPlan, roots: Set[DIKey]): OrderedPlan
 }
-
-
-
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/PlannerInput.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/PlannerInput.scala
index 28b83e293..61b9f9f96 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/PlannerInput.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/PlannerInput.scala
@@ -1,30 +1,29 @@
 package izumi.distage.model
 
 import izumi.distage.model.definition.ModuleBase
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import izumi.distage.model.reflection.DIKey
 import izumi.reflect.Tag
 
 /**
   * Input for [[Planner]]
   *
-  * @param bindings Bindings created by [[izumi.distage.model.definition.ModuleDef]] DSL
-  * @param mode     Garbage collection roots.
+  * @param bindings Bindings. Can be created using [[izumi.distage.model.definition.ModuleDef]] DSL
   *
-  *                 Garbage collector will remove all bindings that aren't direct or indirect dependencies
-  *                 of the chosen root DIKeys from the plan - they will never be instantiated.
+  * @param roots    Garbage collection [[Roots roots]]. distage will ignore all bindings that aren't transitive dependencies
+  *                 of the chosen Root [[DIKey keys]] from the plan - they will never be instantiated.
   *
-  *                 Effectively, garbage collector selects and creates a *sub-graph* of the largest possible object graph
-  *                 that can be described by `bindings`, the sub-graph that can includes components designated as `roots`
-  *                 and their required dependencies.
+  *                 Effectively, the choice of roots selects a *sub-graph* of the largest possible object graph
+  *                 that can be described by `bindings` - the sub-graph that only includes components designated as `roots`
+  *                 and their transitive dependencies.
   *
-  *                 On [[GCMode.NoGC]] garbage collection will not be performed – that would be equivalent to
-  *                 designating _all_ DIKeys as roots. _Everything_ described in `bindings` will be instantiated eagerly.
+  *                 On [[Roots.Everything]] garbage collection will not be performed – that would be equivalent to
+  *                 designating _all_ DIKeys as roots.
   */
 final case class PlannerInput(
-                               bindings: ModuleBase,
-                               mode: GCMode,
-                             )
+  bindings: ModuleBase,
+  roots: Roots,
+)
 
 object PlannerInput {
   /**
@@ -33,10 +32,10 @@ object PlannerInput {
     * Effectively, this selects and creates a *sub-graph* of the largest possible object graph
     * that can be described by `bindings`
     */
-  def apply(bindings: ModuleBase, roots: Set[_ <: DIKey]): PlannerInput = PlannerInput(bindings, GCMode(roots))
+  def apply(bindings: ModuleBase, roots: Set[_ <: DIKey]): PlannerInput = PlannerInput(bindings, Roots(roots))
 
   /** Instantiate `root`, `roots` and their dependencies, discarding bindings that are unrelated. */
-  def apply(bindings: ModuleBase, root: DIKey, roots: DIKey*): PlannerInput = PlannerInput(bindings, GCMode(root, roots: _*))
+  def apply(bindings: ModuleBase, root: DIKey, roots: DIKey*): PlannerInput = PlannerInput(bindings, Roots(root, roots: _*))
 
   /** Instantiate `T` and the dependencies of `T`, discarding bindings that are unrelated. */
   def target[T: Tag](bindings: ModuleBase): PlannerInput = PlannerInput(bindings, DIKey.get[T])
@@ -48,5 +47,5 @@ object PlannerInput {
 
   /** Disable all pruning.
     * Every binding in `bindings` will be instantiated eagerly, without selection */
-  def noGc(bindings: ModuleBase): PlannerInput = PlannerInput(bindings, GCMode.NoGC)
+  def noGc(bindings: ModuleBase): PlannerInput = PlannerInput(bindings, Roots.Everything)
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/AbstractPlan.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/AbstractPlan.scala
index 3fbadbaff..5c7f500ed 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/AbstractPlan.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/AbstractPlan.scala
@@ -23,7 +23,7 @@ sealed trait AbstractPlan[OpType <: ExecutableOp] extends AbstractPlanExtendedAP
   */
 final case class SemiPlan(
                            steps: Vector[SemiplanOp],
-                           gcMode: GCMode,
+                           roots: Roots,
                          ) extends AbstractPlan[SemiplanOp] with SemiPlanOps
 
 object SemiPlan extends SemiPlanExtensions
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/GCMode.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/GCMode.scala
deleted file mode 100644
index e1c5ce7c3..000000000
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/GCMode.scala
+++ /dev/null
@@ -1,30 +0,0 @@
-package izumi.distage.model.plan
-
-import izumi.distage.model.reflection._
-import izumi.reflect.Tag
-
-sealed trait GCMode {
-  def toSet: Set[DIKey]
-}
-
-object GCMode {
-  def apply(root: DIKey, roots: DIKey*): GCMode = {
-    GCRoots(roots.toSet + root)
-  }
-  def apply(roots: Set[_ <: DIKey]): GCMode = {
-    GCMode.GCRoots(roots.toSet)
-  }
-  def target[T: Tag]: GCMode = GCMode(Set(DIKey.get[T]))
-  def target[T: Tag](name: String): GCMode = GCMode(Set(DIKey.get[T].named(name)))
-
-  final case class GCRoots(roots: Set[DIKey]) extends GCMode {
-    assert(roots.nonEmpty, "GC roots set cannot be empty")
-
-    override def toSet: Set[DIKey] = roots
-  }
-
-  case object NoGC extends GCMode {
-    override def toSet: Set[DIKey] = Set.empty
-  }
-
-}
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Roots.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Roots.scala
new file mode 100644
index 000000000..7db4b49a6
--- /dev/null
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Roots.scala
@@ -0,0 +1,45 @@
+package izumi.distage.model.plan
+
+import izumi.distage.model.reflection._
+import izumi.fundamentals.collections.nonempty.NonEmptySet
+import izumi.reflect.Tag
+
+/**
+  * `Roots` designate the components to choose as "garbage collection roots" for the object graph,
+  * designated components and their dependencies will be created and
+  *
+  * The purpose of the `Roots`/`garbage collection` mechanism is to allow you is to allow
+  * oversupplying the injector with bindings that may not be required to create a specific object graph,
+  * instead of carefully curating bindings to shape the graph, choosing root nodes allows you to "pluck"
+  * the sub-graph that you care about and instantiate just that sub-graph, ignoring all the other bindings.
+  *
+  * `distage-testkit` and `distage-framework`'s Roles make heavy use of the mechanism.
+  * Roles are effectively just a root components that come with command-line arguments.
+  * distage-testkit's test cases designate their parameters as roots and instantiate only the sub-graph
+  * required for the test case.
+  *
+  * @see Garbage Collection https://izumi.7mind.io/distage/advanced-features.html#garbage-collection
+  * @see distage Roles      https://izumi.7mind.io/distage/distage-framework.html#roles
+  * @see distage-testkit    https://izumi.7mind.io/distage/distage-testkit.html
+  */
+sealed trait Roots
+
+object Roots {
+  def apply(root: DIKey, roots: DIKey*): Roots = {
+    Roots.Of(NonEmptySet(root, roots: _*))
+  }
+  def apply(roots: Set[_ <: DIKey]): Roots = {
+    require(roots.nonEmpty, "GC roots set cannot be empty")
+    Roots.Of(NonEmptySet.from(roots.toSet[DIKey]).get)
+  }
+  def target[T: Tag]: Roots = Roots(Set(DIKey.get[T]))
+  def target[T: Tag](name: String): Roots = Roots(Set(DIKey.get[T].named(name)))
+
+  final case class Of(roots: NonEmptySet[DIKey]) extends Roots
+
+  /** Disable garbage collection and try to instantiate every single binding. */
+  case object Everything extends Roots
+
+  @deprecated("GCMode.NoGC has been renamed to `Roots.Everything`", "old name will be deleted in 0.11.1")
+  lazy val NoGC: Everything.type = Everything
+}
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanExtensions.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanExtensions.scala
index 5f6b095da..412173463 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanExtensions.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanExtensions.scala
@@ -6,7 +6,7 @@ import izumi.distage.model.plan.impl.PlanCatsSyntaxImpl.{ResolveImportFOrderedPl
 import izumi.distage.model.plan.repr.{CompactOrderedPlanFormatter, DepTreeRenderer}
 import izumi.distage.model.plan.topology.DepTreeNode.DepNode
 import izumi.distage.model.plan.topology.PlanTopology
-import izumi.distage.model.plan.{GCMode, OrderedPlan, SemiPlan}
+import izumi.distage.model.plan.{Roots, OrderedPlan, SemiPlan}
 import izumi.functional.Renderable
 import izumi.reflect.Tag
 
@@ -41,10 +41,10 @@ private[plan] object OrderedPlanExtensions {
     import cats.syntax.traverse._
 
     def traverse[F[_]: Applicative](f: SemiplanOp => F[SemiplanOp]): F[SemiPlan] =
-      plan.toSemi.steps.traverse(f).map(SemiPlan(_, GCMode(plan.declaredRoots)))
+      plan.toSemi.steps.traverse(f).map(SemiPlan(_, Roots(plan.declaredRoots)))
 
     def flatMapF[F[_]: Applicative](f: SemiplanOp => F[Seq[SemiplanOp]]): F[SemiPlan] =
-      plan.toSemi.steps.traverse(f).map(s => SemiPlan(s.flatten, GCMode(plan.declaredRoots)))
+      plan.toSemi.steps.traverse(f).map(s => SemiPlan(s.flatten, Roots(plan.declaredRoots)))
 
     def resolveImportF[T]: ResolveImportFOrderedPlanPartiallyApplied[T] = new ResolveImportFOrderedPlanPartiallyApplied(plan)
 
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanOps.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanOps.scala
index 295729b22..b3f31eb65 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanOps.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanOps.scala
@@ -4,7 +4,7 @@ import izumi.distage.model.Locator
 import izumi.distage.model.exceptions.{InvalidPlanException, MissingInstanceException}
 import izumi.distage.model.plan.ExecutableOp.ProxyOp.{InitProxy, MakeProxy}
 import izumi.distage.model.plan.ExecutableOp.{ImportDependency, ProxyOp, SemiplanOp}
-import izumi.distage.model.plan.{GCMode, OrderedPlan, SemiPlan}
+import izumi.distage.model.plan.{Roots, OrderedPlan, SemiPlan}
 import izumi.distage.model.reflection._
 import izumi.distage.model.recursive.LocatorRef
 import izumi.reflect.Tag
@@ -115,6 +115,6 @@ private[plan] trait OrderedPlanOps extends Any {
             Seq.empty
         }
     }
-    SemiPlan(safeSteps.toVector, GCMode(declaredRoots))
+    SemiPlan(safeSteps.toVector, Roots(declaredRoots))
   }
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/SemiPlanExtensions.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/SemiPlanExtensions.scala
index 782bf4210..0ac374db9 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/SemiPlanExtensions.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/SemiPlanExtensions.scala
@@ -5,7 +5,7 @@ import cats.kernel.Monoid
 import izumi.distage.model.plan.ExecutableOp.{ImportDependency, SemiplanOp}
 import izumi.distage.model.plan.impl.PlanCatsSyntaxImpl.{CatsMonoid, ResolveImportFSemiPlanPartiallyApplied, resolveImportsImpl1}
 import izumi.distage.model.plan.impl.SemiPlanExtensions.SemiPlanExts
-import izumi.distage.model.plan.{GCMode, SemiPlan}
+import izumi.distage.model.plan.{Roots, SemiPlan}
 import izumi.reflect.Tag
 
 import scala.language.implicitConversions
@@ -19,7 +19,7 @@ private[plan] trait SemiPlanExtensions extends Any { this: SemiPlan.type =>
     */
   implicit final def optionalCatsMonoidForSemiplan[K[_]: CatsMonoid]: K[SemiPlan] =
     new Monoid[SemiPlan] {
-      override def empty: SemiPlan = SemiPlan(Vector.empty, GCMode.NoGC)
+      override def empty: SemiPlan = SemiPlan(Vector.empty, Roots.Everything)
 
       override def combine(x: SemiPlan, y: SemiPlan): SemiPlan = x ++ y
     }.asInstanceOf[K[SemiPlan]]
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/SemiPlanOps.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/SemiPlanOps.scala
index a3516ac92..77c138858 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/SemiPlanOps.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/SemiPlanOps.scala
@@ -36,7 +36,7 @@ private[plan] trait SemiPlanOps extends Any {
   }
 
   final def resolveImportsOp(f: PartialFunction[ImportDependency, Seq[SemiplanOp]]): SemiPlan = {
-    SemiPlan(steps = AbstractPlanOps.resolveImports1(f, steps.toVector), gcMode)
+    SemiPlan(steps = AbstractPlanOps.resolveImports1(f, steps.toVector), roots)
   }
 
   final def providerImport[T](function: ProviderMagnet[T]): SemiPlan = {
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/initial/PrePlan.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/initial/PrePlan.scala
index 8037db416..61f2fb92c 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/initial/PrePlan.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/initial/PrePlan.scala
@@ -2,7 +2,7 @@ package izumi.distage.model.plan.initial
 
 import izumi.distage.model.definition.{Binding, ModuleBase}
 import izumi.distage.model.plan.ExecutableOp.{CreateSet, InstantiationOp}
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import izumi.distage.model.plan.initial.PrePlan.{JustOp, SetOp, TraceableOp}
 import izumi.distage.model.reflection.DIKey
 import izumi.fundamentals.collections.ImmutableMultiMap
@@ -13,9 +13,9 @@ import izumi.fundamentals.collections.IzCollections._
 import scala.collection.mutable
 
 final class PrePlan(
-                       val definition: ModuleBase,
-                       val gcMode: GCMode,
-                     ) {
+  val definition: ModuleBase,
+  val roots: Roots,
+) {
 
   private val ops = new mutable.ArrayBuffer[TraceableOp]()
 
@@ -51,9 +51,7 @@ object PrePlan {
   final case class JustOp(key: DIKey, op: InstantiationOp, binding: Binding) extends TraceableOp
   final case class SetOp(key: DIKey, op: CreateSet, binding: Binding) extends TraceableOp
 
-  def empty(definition: ModuleBase, gcMode: GCMode): PrePlan = {
-    new PrePlan(definition, gcMode)
+  def empty(definition: ModuleBase, roots: Roots): PrePlan = {
+    new PrePlan(definition, roots)
   }
 }
-
-
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/package.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/package.scala
new file mode 100644
index 000000000..a8309f080
--- /dev/null
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/package.scala
@@ -0,0 +1,8 @@
+package izumi.distage.model
+
+package object plan {
+  @deprecated("GCMode has been renamed to `Roots`", "old name will be deleted in 0.11.1")
+  type GCMode = Roots
+  @deprecated("GCMode has been renamed to `Roots`", "old name will be deleted in 0.11.1")
+  lazy val GCMode = Roots
+}
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/recursive/Bootloader.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/recursive/Bootloader.scala
index 2765544df..b387c65d0 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/recursive/Bootloader.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/recursive/Bootloader.scala
@@ -2,21 +2,21 @@ package izumi.distage.model.recursive
 
 import izumi.distage.InjectorFactory
 import izumi.distage.model.definition.{Activation, BootstrapModule, ModuleBase}
-import izumi.distage.model.plan.{GCMode, OrderedPlan}
+import izumi.distage.model.plan.{OrderedPlan, Roots}
 import izumi.distage.model.{Injector, PlannerInput}
 
 final case class BootstrappedApp(
-                                  injector: Injector,
-                                  module: ModuleBase,
-                                  plan: OrderedPlan,
-                                )
+  injector: Injector,
+  module: ModuleBase,
+  plan: OrderedPlan,
+)
 
 final case class BootConfig(
-                             bootstrap: BootstrapModule => BootstrapModule = identity,
-                             appModule: ModuleBase => ModuleBase = identity,
-                             activation: Activation => Activation = identity,
-                             gcMode: GCMode => GCMode = identity,
-                           )
+  bootstrap: BootstrapModule => BootstrapModule = identity,
+  appModule: ModuleBase => ModuleBase = identity,
+  activation: Activation => Activation = identity,
+  roots: Roots => Roots = identity,
+)
 
 class Bootloader(
   val bootstrapModule: BootstrapModule,
@@ -27,7 +27,7 @@ class Bootloader(
   def boot(config: BootConfig): BootstrappedApp = {
     val injector = injectorFactory(config.activation(activation), config.bootstrap(bootstrapModule))
     val module = config.appModule(input.bindings)
-    val roots = config.gcMode(input.mode)
+    val roots = config.roots(input.roots)
     val plan = injector.plan(PlannerInput(module, roots))
     BootstrappedApp(injector, module, plan)
   }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/DIKey.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/DIKey.scala
index f7058a080..d5a651331 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/DIKey.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/DIKey.scala
@@ -9,6 +9,9 @@ sealed trait DIKey {
 }
 
 object DIKey {
+  def apply[T: Tag]: DIKey.TypeKey = TypeKey(SafeType.get[T])
+  def apply[T: Tag](id: String): DIKey.IdKey[String] = DIKey[T].named(id)
+
   def get[T: Tag]: DIKey.TypeKey = TypeKey(SafeType.get[T])
 
   sealed trait BasicKey extends DIKey {
diff --git a/distage/distage-core/src/main/scala/distage/Distage.scala b/distage/distage-core/src/main/scala/distage/Distage.scala
index 4340cb477..03d7c1694 100644
--- a/distage/distage-core/src/main/scala/distage/Distage.scala
+++ b/distage/distage-core/src/main/scala/distage/Distage.scala
@@ -8,13 +8,16 @@ trait Distage {
 
   type ModuleDef = model.definition.ModuleDef
 
-  type Activation = model.definition.Activation
-  val Activation: model.definition.Activation.type = model.definition.Activation
-
   type Injector = model.Injector
   type Planner = model.Planner
   type Producer = model.Producer
 
+  type Activation = model.definition.Activation
+  val Activation: model.definition.Activation.type = model.definition.Activation
+
+  type Roots = model.plan.Roots
+  val Roots: model.plan.Roots.type = model.plan.Roots
+
   type Locator = model.Locator
   type LocatorRef = model.recursive.LocatorRef
 
@@ -38,6 +41,9 @@ trait Distage {
 
   val StandardAxis: model.definition.StandardAxis.type = model.definition.StandardAxis
 
+  type DIKey = model.reflection.DIKey
+  val DIKey: model.reflection.DIKey.type = model.reflection.DIKey
+
   type ProviderMagnet[+A] = model.providers.ProviderMagnet[A]
   val ProviderMagnet: model.providers.ProviderMagnet.type = model.providers.ProviderMagnet
 
@@ -53,8 +59,10 @@ trait Distage {
   type HasConstructor[T] = constructors.HasConstructor[T]
   val HasConstructor: constructors.HasConstructor.type = constructors.HasConstructor
 
-  type GCMode = model.plan.GCMode
-  val GCMode: model.plan.GCMode.type = model.plan.GCMode
+  @deprecated("GCMode has been renamed to `Roots`", "old name will be deleted in 0.11.1")
+  type GCMode = model.plan.Roots
+  @deprecated("GCMode has been renamed to `Roots`", "old name will be deleted in 0.11.1")
+  val GCMode: model.plan.Roots.type = model.plan.Roots
 
   type BindingTag = model.definition.BindingTag
   val BindingTag: model.definition.BindingTag.type = model.definition.BindingTag
@@ -74,9 +82,6 @@ trait Distage {
   val SemiPlan: model.plan.SemiPlan.type = model.plan.SemiPlan
   type AbstractPlan[OpType <: ExecutableOp] = model.plan.AbstractPlan[OpType]
 
-  type DIKey = model.reflection.DIKey
-  val DIKey: model.reflection.DIKey.type = model.reflection.DIKey
-
   type SafeType = model.reflection.SafeType
   val SafeType: model.reflection.SafeType.type = model.reflection.SafeType
 
diff --git a/distage/distage-core/src/main/scala/distage/package.scala b/distage/distage-core/src/main/scala/distage/package.scala
index 14d88728d..a624cb53d 100644
--- a/distage/distage-core/src/main/scala/distage/package.scala
+++ b/distage/distage-core/src/main/scala/distage/package.scala
@@ -6,13 +6,16 @@ package object distage extends Distage {
 
   override type ModuleDef = model.definition.ModuleDef
 
-  override type Activation = model.definition.Activation
-  override val Activation: model.definition.Activation.type = model.definition.Activation
-
   override type Injector = model.Injector
   override type Planner = model.Planner
   override type Producer = model.Producer
 
+  override type Activation = model.definition.Activation
+  override val Activation: model.definition.Activation.type = model.definition.Activation
+
+  override type Roots = model.plan.Roots
+  override val Roots: model.plan.Roots.type = model.plan.Roots
+
   override type Locator = model.Locator
   override type LocatorRef = model.recursive.LocatorRef
 
@@ -36,6 +39,9 @@ package object distage extends Distage {
 
   override val StandardAxis: model.definition.StandardAxis.type = model.definition.StandardAxis
 
+  override type DIKey = model.reflection.DIKey
+  override val DIKey: model.reflection.DIKey.type = model.reflection.DIKey
+
   override type ProviderMagnet[+A] = model.providers.ProviderMagnet[A]
   override val ProviderMagnet: model.providers.ProviderMagnet.type = model.providers.ProviderMagnet
 
@@ -51,8 +57,10 @@ package object distage extends Distage {
   override type HasConstructor[T] = constructors.HasConstructor[T]
   override val HasConstructor: constructors.HasConstructor.type = constructors.HasConstructor
 
-  override type GCMode = model.plan.GCMode
-  override val GCMode: model.plan.GCMode.type = model.plan.GCMode
+  @deprecated("GCMode has been renamed to `Roots`", "old name will be deleted in 0.11.1")
+  override type GCMode = model.plan.Roots
+  @deprecated("GCMode has been renamed to `Roots`", "old name will be deleted in 0.11.1")
+  override val GCMode: model.plan.Roots.type = model.plan.Roots
 
   override type BindingTag = model.definition.BindingTag
   override val BindingTag: model.definition.BindingTag.type = model.definition.BindingTag
@@ -72,9 +80,6 @@ package object distage extends Distage {
   override val SemiPlan: model.plan.SemiPlan.type = model.plan.SemiPlan
   override type AbstractPlan[OpType <: ExecutableOp] = model.plan.AbstractPlan[OpType]
 
-  override type DIKey = model.reflection.DIKey
-  override val DIKey: model.reflection.DIKey.type = model.reflection.DIKey
-
   override type SafeType = model.reflection.SafeType
   override val SafeType: model.reflection.SafeType.type = model.reflection.SafeType
 
diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/PlannerDefaultImpl.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/PlannerDefaultImpl.scala
index 0363db61a..d2429b418 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/planning/PlannerDefaultImpl.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/planning/PlannerDefaultImpl.scala
@@ -17,8 +17,7 @@ import izumi.distage.planning.gc.TracingDIGC
 import izumi.functional.Value
 import izumi.fundamentals.graphs.Toposort
 
-final class PlannerDefaultImpl
-(
+final class PlannerDefaultImpl(
   forwardingRefResolver: ForwardingRefResolver,
   sanityChecker: SanityChecker,
   gc: DIGarbageCollector,
@@ -68,7 +67,7 @@ final class PlannerDefaultImpl
   }
 
   override def prepare(input: PlannerInput): PrePlan = {
-    input.bindings.bindings.foldLeft(PrePlan.empty(input.bindings, input.mode)) {
+    input.bindings.bindings.foldLeft(PrePlan.empty(input.bindings, input.roots)) {
       case (currentPlan, binding) =>
         Value(bindingTranslator.computeProvisioning(currentPlan, binding))
           .eff(sanityChecker.assertProvisionsSane)
@@ -122,13 +121,16 @@ final class PlannerDefaultImpl
       .toMap
 
     val allOps = (imports.values ++ plan.steps).toVector
-    val roots = plan.gcMode.toSet
-    val missingRoots = roots.diff(allOps.map(_.target).toSet).map {
-      root =>
-        ImportDependency(root, Set.empty, OperationOrigin.Unknown)
-    }.toVector
-
-    SemiPlan(missingRoots ++ allOps, plan.gcMode)
+    val missingRoots = plan.roots match {
+      case Roots.Of(roots) =>
+        roots
+          .toSet.diff(allOps.map(_.target).toSet).map {
+            root =>
+              ImportDependency(root, Set.empty, OperationOrigin.Unknown)
+          }.toVector
+      case Roots.Everything => Vector.empty
+    }
+    SemiPlan(missingRoots ++ allOps, plan.roots)
   }
 
   private[this] def reorderOperations(completedPlan: SemiPlan): OrderedPlan = {
@@ -190,9 +192,9 @@ final class PlannerDefaultImpl
     }
 
     val sortedKeys = new Toposort().cycleBreaking(
-      topology.dependencies.graph
-      , Seq.empty
-      , break
+      topology.dependencies.graph,
+      Seq.empty,
+      break,
     ) match {
       case Left(value) =>
         throw new SanityCheckFailedException(s"Integrity check failed: cyclic reference not detected while it should be, ${value.issues}")
@@ -203,10 +205,10 @@ final class PlannerDefaultImpl
 
     val sortedOps = sortedKeys.flatMap(k => index.get(k).toSeq)
 
-    val roots = completedPlan.gcMode match {
-      case GCMode.GCRoots(roots) =>
-        roots
-      case GCMode.NoGC =>
+    val roots = completedPlan.roots match {
+      case Roots.Of(roots) =>
+        roots.toSet
+      case Roots.Everything =>
         topology.effectiveRoots
     }
     OrderedPlan(sortedOps.toVector, roots, topology)
@@ -232,12 +234,12 @@ final class PlannerDefaultImpl
 
   private[this] def effectKey(key: DIKey): Boolean = key match {
     case _: DIKey.ResourceKey | _: DIKey.EffectKey => true
-    case _ => false
+    case _                                         => false
   }
 
   private[this] def referenceOp(s: SemiplanOp): Boolean = s match {
-    case _: ReferenceKey /*| _: MonadicOp */=> true
-    case _ => false
+    case _: ReferenceKey /*| _: MonadicOp */ => true
+    case _                                   => false
   }
 
 }
diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/PruningPlanMergingPolicyDefaultImpl.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/PruningPlanMergingPolicyDefaultImpl.scala
index c31c2040d..b9c6e349c 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/planning/PruningPlanMergingPolicyDefaultImpl.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/planning/PruningPlanMergingPolicyDefaultImpl.scala
@@ -58,41 +58,39 @@ class PruningPlanMergingPolicyDefaultImpl
 
     val ops = resolved.values.flatten.toVector
     val index = ops.map(op => op.target -> op).toMap
-    val roots = plan.gcMode.toSet
+    plan.roots match {
+      case Roots.Of(roots) if roots.toSet.intersect(issues.keySet).isEmpty =>
+        val collected = new TracingDIGC(roots.toSet, index, ignoreMissingDeps = true).gc(ops)
+
+        val lastTry = issues.map {
+          case (k, v) =>
+            val reachableCandidates = v.candidates.filter(op => collected.reachable.contains(op.target))
+
+            if (reachableCandidates.size == 1) {
+              k -> DIKeyConflictResolution.Successful(reachableCandidates)
+            } else if (reachableCandidates.isEmpty) {
+              k -> DIKeyConflictResolution.Successful(Set.empty)
+            } else {
+              k -> v
+            }
+        }
 
-    if (roots.nonEmpty && roots.intersect(issues.keySet).isEmpty) {
-      val collected = new TracingDIGC(roots, index, ignoreMissingDeps = true).gc(ops)
+        val failed = lastTry.collect {
+          case (k, f: DIKeyConflictResolution.Failed) => k -> f
+        }
 
-      val lastTry = issues.map {
-        case (k, v) =>
-          val reachableCandidates = v.candidates.filter(op => collected.reachable.contains(op.target))
-
-          if (reachableCandidates.size == 1) {
-            k -> DIKeyConflictResolution.Successful(reachableCandidates)
-          } else if (reachableCandidates.isEmpty) {
-            k -> DIKeyConflictResolution.Successful(Set.empty)
-          } else {
-            k -> v
+        if (failed.nonEmpty) {
+          throwOnIssues(failed)
+        } else {
+          val good = lastTry.collect {
+            case (k, DIKeyConflictResolution.Successful(s)) => k -> s
           }
-      }
-
-      val failed = lastTry.collect {
-        case (k, f: DIKeyConflictResolution.Failed) => k -> f
-      }
-
-      if (failed.nonEmpty) {
-        throwOnIssues(failed)
-      } else {
-        val good = lastTry.collect {
-          case (k, DIKeyConflictResolution.Successful(s)) => k -> s
+          val erased = good.filter(_._2.isEmpty)
+          logPruningSuccesfulResolve(issues, erased)
+          val allResolved = (resolved.values.flatten ++ good.values.flatten).toVector
+          SemiPlan(allResolved, plan.roots)
         }
-        val erased = good.filter(_._2.isEmpty)
-        logPruningSuccesfulResolve(issues, erased)
-        val allResolved = (resolved.values.flatten ++ good.values.flatten).toVector
-        SemiPlan(allResolved, plan.gcMode)
-      }
-    } else {
-      throwOnIssues(issues)
+      case _ => throwOnIssues(issues)
     }
   }
 
@@ -132,7 +130,7 @@ object PruningPlanMergingPolicyDefaultImpl {
       if (issues.nonEmpty) {
         handleIssues(plan, resolved.toMap, issues.toMap)
       } else {
-        SemiPlan(resolved.values.flatten.toVector, plan.gcMode)
+        SemiPlan(resolved.values.flatten.toVector, plan.roots)
       }
     }
 
diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/gc/TracingDIGC.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/gc/TracingDIGC.scala
index 5e531c632..e3795dd09 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/planning/gc/TracingDIGC.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/planning/gc/TracingDIGC.scala
@@ -1,7 +1,7 @@
 package izumi.distage.planning.gc
 
 import izumi.distage.model.plan.ExecutableOp._
-import izumi.distage.model.plan.{ExecutableOp, GCMode, SemiPlan, Wiring}
+import izumi.distage.model.plan.{ExecutableOp, Roots, SemiPlan, Wiring}
 import izumi.distage.model.planning.DIGarbageCollector
 import izumi.distage.model.reflection._
 import izumi.fundamentals.graphs.AbstractGCTracer
@@ -90,14 +90,12 @@ class TracingDIGC[OpType <: ExecutableOp]
 
 object TracingDIGC extends DIGarbageCollector {
   override def gc(plan: SemiPlan): SemiPlan = {
-    plan.gcMode match {
-      case GCMode.GCRoots(roots) =>
-        assert(roots.nonEmpty)
+    plan.roots match {
+      case Roots.Of(roots) =>
+        val collected = new TracingDIGC(roots.toSet, plan.index, ignoreMissingDeps = false).gc(plan.steps)
+        SemiPlan(collected.nodes, plan.roots)
 
-        val collected = new TracingDIGC(roots, plan.index, ignoreMissingDeps = false).gc(plan.steps)
-        SemiPlan(collected.nodes, plan.gcMode)
-
-      case GCMode.NoGC =>
+      case Roots.Everything =>
         plan
     }
   }
diff --git a/distage/distage-extension-config/src/main/scala/izumi/distage/config/extractor/ConfigPathExtractor.scala b/distage/distage-extension-config/src/main/scala/izumi/distage/config/extractor/ConfigPathExtractor.scala
index 3746f2823..17a77e653 100644
--- a/distage/distage-extension-config/src/main/scala/izumi/distage/config/extractor/ConfigPathExtractor.scala
+++ b/distage/distage-extension-config/src/main/scala/izumi/distage/config/extractor/ConfigPathExtractor.scala
@@ -21,7 +21,7 @@ class ConfigPathExtractor extends PlanningHook {
 
     val addResolvedConfigOp = resolvedConfigOp(paths)
 
-    SemiPlan(plan.steps :+ addResolvedConfigOp, plan.gcMode)
+    SemiPlan(plan.steps :+ addResolvedConfigOp, plan.roots)
   }
 
   private def resolvedConfigOp(paths: Set[ConfigPath]): ExecutableOp.WiringOp.UseInstance = {
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/RoleAppPlanner.scala b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/RoleAppPlanner.scala
index a4ed7b618..64eb7a721 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/RoleAppPlanner.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/RoleAppPlanner.scala
@@ -12,23 +12,24 @@ import izumi.logstage.api.IzLogger
 
 trait RoleAppPlanner[F[_]] {
   def reboot(bsModule: BootstrapModule): RoleAppPlanner[F]
-  def makePlan(appMainRoots: Set[DIKey] /*, appModule: ModuleBase*/): AppStartupPlans
+  def makePlan(appMainRoots: Set[DIKey] /*, appModule: ModuleBase*/ ): AppStartupPlans
 }
 
 object RoleAppPlanner {
 
   final case class AppStartupPlans(
-                                    runtime: OrderedPlan,
-                                    app: TriSplittedPlan,
-                                    injector: Injector,
-                                  )
+    runtime: OrderedPlan,
+    app: TriSplittedPlan,
+    injector: Injector,
+  )
 
   class Impl[F[_]: TagK](
-                          options: PlanningOptions,
-                          bsModule: BootstrapModule,
-                          logger: IzLogger,
-                          bootloader: Bootloader,
-                        ) extends RoleAppPlanner[F] { self =>
+    options: PlanningOptions,
+    bsModule: BootstrapModule,
+    logger: IzLogger,
+    bootloader: Bootloader,
+  ) extends RoleAppPlanner[F] {
+    self =>
 
     private val runtimeGcRoots: Set[DIKey] = Set(
       DIKey.get[DIEffectRunner[F]],
@@ -42,11 +43,13 @@ object RoleAppPlanner {
 
     override def makePlan(appMainRoots: Set[DIKey]): AppStartupPlans = {
       val additionalModule = selfReflectionModule()
-      val app = bootloader.boot(BootConfig(
-        bootstrap = _ => bsModule,
-        appModule = _.overridenBy(additionalModule),
-        gcMode = _ => GCMode(runtimeGcRoots),
-      ))
+      val app = bootloader.boot(
+        BootConfig(
+          bootstrap = _ => bsModule,
+          appModule = _.overridenBy(additionalModule),
+          roots = _ => GCMode(runtimeGcRoots),
+        )
+      )
 
       val appPlan = app.injector.trisectByKeys(app.module.drop(runtimeGcRoots), appMainRoots) {
         _.collectChildren[IntegrationCheck].map(_.target).toSet
diff --git a/doc/microsite/src/main/tut/distage/basics.md b/doc/microsite/src/main/tut/distage/basics.md
index a1f91639a..5e003e46c 100644
--- a/doc/microsite/src/main/tut/distage/basics.md
+++ b/doc/microsite/src/main/tut/distage/basics.md
@@ -19,7 +19,7 @@ def readLine() = {
 ```
 
 ```scala mdoc:to-string
-import distage.{ModuleDef, Injector, GCMode}
+import distage.{ModuleDef, Injector, Roots}
 
 trait Greeter {
   def hello(name: String): Unit
@@ -64,7 +64,7 @@ actionable series of steps - an @scaladoc[OrderedPlan](izumi.distage.model.plan.
 @ref[inspect](debugging.md#pretty-printing-plans), @ref[test](debugging.md#testing-plans) or @ref[verify at compile-time](distage-framework.md#compile-time-checks) – without actually creating any objects or executing any effects.
 
 ```scala mdoc:to-string
-val plan = Injector().plan(HelloByeModule, GCMode.NoGC)
+val plan = Injector().plan(HelloByeModule, Roots.NoGC)
 ```
 
 The series of steps must be executed to produce the object graph. `Injector.produce` will interpret the steps into a @ref[Resource](basics.md#resource-bindings-lifecycle) value, that holds the lifecycle of the object graph:
@@ -102,7 +102,7 @@ new ModuleDef {
 }
 ```
 
-You can abstract over annotations with type aliases or with string constants:
+You can abstract over annotations using type aliases or string constants:
 
 ```scala mdoc:to-string
 object Ids {
@@ -111,14 +111,18 @@ object Ids {
 }
 ```
 
-For true non-singleton semantics, you must create explicit factory classes, or generate them (see @ref[Auto-Factories](#auto-factories))
+To create non-singleton components you must use explicit factory classes. You can use @ref[Auto-Factories](#auto-factories) implementations for these factories.
+
+### Root Components
+
+
 
 ### Activation Axis
 
 You can choose between different implementations of a component using `Axis` tags:
 
 ```scala mdoc:to-string
-import distage.{Axis, Activation, ModuleDef, Injector, GCMode}
+import distage.{Axis, Activation, ModuleDef, Injector, Roots}
 
 class AllCapsGreeter extends Greeter {
   def hello(name: String) = println(s"HELLO ${name.toUpperCase}")
@@ -240,7 +244,7 @@ receive an allocated resource and when the function exits the resource will be d
 Example with `cats.effect.Resource`:
 
 ```scala mdoc:reset:to-string
-import distage.{GCMode, ModuleDef, Injector}
+import distage.{Roots, ModuleDef, Injector}
 import cats.effect.{Bracket, Resource, IO}
 
 class DBConnection
@@ -275,7 +279,7 @@ Will produce the following output:
 ```scala mdoc:to-string
 import distage.DIKey
 
-val objectGraphResource = Injector().produceF[IO](module, GCMode(root = DIKey.get[MyApp]))
+val objectGraphResource = Injector().produceF[IO](module, Roots(root = DIKey.get[MyApp]))
 
 objectGraphResource
   .use(_.get[MyApp].run)
@@ -285,7 +289,7 @@ objectGraphResource
 Lifecycle management with `DIResource` is also available without an effect type, via `DIResource.Simple` and `DIResource.Mutable`:
 
 ```scala mdoc:reset:to-string
-import distage.{DIResource, GCMode, ModuleDef, Injector}
+import distage.{DIResource, Roots, ModuleDef, Injector}
 
 class Init {
   var initialized = false
@@ -334,7 +338,7 @@ For example, we may declare many [http4s](https://http4s.org) routes and serve t
 ```scala mdoc:silent:reset:to-string
 import cats.implicits._
 import cats.effect.{Bracket, IO, Resource}
-import distage.{GCMode, ModuleDef, Injector}
+import distage.{Roots, ModuleDef, Injector}
 import org.http4s._
 import org.http4s.server.Server
 import org.http4s.client.Client
@@ -405,7 +409,7 @@ val finalModule = Seq(
 ).merge
 
 // wire the graph
-val objects = Injector().produceF[IO](finalModule, GCMode.NoGC).unsafeGet().unsafeRunSync()
+val objects = Injector().produceF[IO](finalModule, Roots.NoGC).unsafeGet().unsafeRunSync()
 
 val server = objects.get[Server[IO]]
 val client = objects.get[Client[IO]]
@@ -439,7 +443,7 @@ In these cases we can use `.fromEffect` to create a value using an effectful con
 Example with a `Ref`-based Tagless Final `KVStore`:
 
 ```scala mdoc:reset:to-string
-import distage.{GCMode, ModuleDef, Injector}
+import distage.{Roots, ModuleDef, Injector}
 import izumi.functional.bio.{BIOMonadError, BIOPrimitives, F}
 import zio.{Task, IO}
 
@@ -569,7 +573,7 @@ Example:
 
 ```scala mdoc:reset:to-string
 import cats.Contravariant
-import distage.{GCMode, Injector, ModuleDef, ProviderMagnet, Tag, TagK, HasConstructor}
+import distage.{Roots, Injector, ModuleDef, ProviderMagnet, Tag, TagK, HasConstructor}
 import zio.{Task, UIO, URIO, ZIO, Has}
 
 trait Dependee[-R] {
@@ -823,7 +827,7 @@ First, the program we want to write:
 import cats.Monad
 import cats.effect.{Sync, IO}
 import cats.syntax.all._
-import distage.{GCMode, Module, ModuleDef, Injector, Tag, TagK, TagKK}
+import distage.{Roots, Module, ModuleDef, Injector, Tag, TagK, TagKK}
 
 trait Validation[F[_]] {
   def minSize(s: String, n: Int): F[Boolean]
@@ -884,7 +888,7 @@ def SyncProgram[F[_]: TagK: Sync] = ProgramModule[F] ++ SyncInterpreters[F]
 
 // create object graph Resource
 
-val objectsResource = Injector().produceF[IO](SyncProgram[IO], GCMode.NoGC)
+val objectsResource = Injector().produceF[IO](SyncProgram[IO], Roots.NoGC)
 
 // run
 
@@ -974,7 +978,7 @@ def SyncInterpreters[F[_]]: Module = Module.empty
 ```scala mdoc:to-string
 import cats.syntax.semigroup._
 import cats.effect.{ExitCode, IO, IOApp}
-import distage.{DIKey, GCMode, Injector}
+import distage.{DIKey, Roots, Injector}
 
 trait AppEntrypoint {
   def run: IO[Unit]
@@ -987,7 +991,7 @@ object Main extends App {
 
     val myModules = ProgramModule[IO] |+| SyncInterpreters[IO]
 
-    val plan = Injector().plan(myModules, GCMode(DIKey.get[AppEntrypoint]))
+    val plan = Injector().plan(myModules, Roots(DIKey.get[AppEntrypoint]))
 
     for {
       // resolveImportsF can effectfully add missing instances to an existing plan
diff --git a/fundamentals/fundamentals-collections/src/main/scala/izumi/fundamentals/collections/nonempty/NonEmptySet.scala b/fundamentals/fundamentals-collections/src/main/scala/izumi/fundamentals/collections/nonempty/NonEmptySet.scala
index b7bc076e7..364a3da25 100644
--- a/fundamentals/fundamentals-collections/src/main/scala/izumi/fundamentals/collections/nonempty/NonEmptySet.scala
+++ b/fundamentals/fundamentals-collections/src/main/scala/izumi/fundamentals/collections/nonempty/NonEmptySet.scala
@@ -4,10 +4,8 @@ package izumi.fundamentals.collections.nonempty
 
 import scala.collection.compat._
 import scala.collection.mutable.{ArrayBuffer, Buffer}
-import scala.collection.{Iterable, Set}
 import scala.reflect.ClassTag
 
-
 // Can't be a LinearSeq[T] because Builder would be able to create an empty one.
 /**
   * A non-empty Set: an ordered, immutable, non-empty collection of elements with <code>LinearSeq</code> performance characteristics.
@@ -815,7 +813,7 @@ final class NonEmptySet[T] private (val toSet: Set[T]) extends AnyVal {
     *
     * @return an immutable <code>IndexedSeq</code> containing all elements of this <code>NonEmptySet</code>.
     */
-  def toIndexedSeq: collection.immutable.IndexedSeq[T] = toSet.toVector
+  def toIndexedSeq: scala.collection.immutable.IndexedSeq[T] = toSet.toVector
 
   /**
     * Converts this <code>NonEmptySet</code> to an iterable collection.
@@ -862,7 +860,7 @@ final class NonEmptySet[T] private (val toSet: Set[T]) extends AnyVal {
     *
     * @return a set containing all elements of this <code>NonEmptySet</code>.
     */
-  def toList: collection.immutable.List[T] = toSet.toList
+  def toList: scala.collection.immutable.List[T] = toSet.toList
 
   /**
     * Returns a string representation of this <code>NonEmptySet</code>.
@@ -1022,7 +1020,7 @@ object NonEmptySet {
   def from[T](set: Set[T]): Option[NonEmptySet[T]] =
     set.headOption match {
       case None => None
-      case Some(_) => Some(new NonEmptySet(scala.collection.immutable.Set.empty[T] ++ set))
+      case Some(_) => Some(new NonEmptySet(collection.immutable.Set.empty[T] ++ set))
     }
 
 }


Test patch:
diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/compat/ZIOResourcesTest.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/compat/ZIOResourcesTest.scala
index 124acedad..49f490be0 100644
--- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/compat/ZIOResourcesTest.scala
+++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/compat/ZIOResourcesTest.scala
@@ -5,7 +5,7 @@ import distage.{TagKK, _}
 import izumi.distage.compat.ZIOResourcesTest._
 import izumi.distage.model.definition.Binding.SingletonBinding
 import izumi.distage.model.definition.{DIResource, ImplDef, ModuleDef}
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import izumi.functional.bio.BIO
 import izumi.fundamentals.platform.language.unused
 import org.scalatest.GivenWhenThen
@@ -42,7 +42,7 @@ final class ZIOResourcesTest extends AnyWordSpec with GivenWhenThen {
         make[MyApp]
       }
 
-      unsafeRun(Injector().produceF[Task](module, GCMode.NoGC).use {
+      unsafeRun(Injector().produceF[Task](module, Roots.Everything).use {
         objects =>
           objects.get[MyApp].run
       })
@@ -145,7 +145,7 @@ final class ZIOResourcesTest extends AnyWordSpec with GivenWhenThen {
         make[MyApp]
       }
 
-      unsafeRun(Injector().produceF[Task](module, GCMode.NoGC).use {
+      unsafeRun(Injector().produceF[Task](module, Roots.Everything).use {
         objects =>
           objects.get[MyApp].run
       })
diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala
index a8636b521..89f00fa3a 100644
--- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala
+++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala
@@ -3,7 +3,7 @@ package izumi.distage.gc
 import distage.DIKey
 import izumi.distage.model.PlannerInput
 import izumi.distage.model.definition.ModuleDef
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import org.scalatest.wordspec.AnyWordSpec
 
 import scala.collection.immutable
@@ -20,7 +20,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
         make[Circular3]
         make[Circular4]
         make[Trash]
-      }, GCMode(DIKey.get[Circular2])))
+      }, Roots(DIKey.get[Circular2])))
 
       val result = injector.produce(plan).unsafeGet()
       assert(result.find[Trash].isEmpty)
@@ -38,7 +38,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
         make[MkS3Client].from[Impl]
         make[S3Component]
         make[App]
-      }, GCMode(DIKey.get[App])))
+      }, Roots(DIKey.get[App])))
 
       val result = injector.produce(plan).unsafeGet()
       assert(result.get[App] != null)
@@ -55,7 +55,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
         make[S3Upload]
         make[Ctx]
         make[S3Component]
-      }, GCMode(DIKey.get[Ctx])))
+      }, Roots(DIKey.get[Ctx])))
 
       val result = injector.produce(plan).unsafeGet()
       assert(result.get[Ctx].upload.client != null)
@@ -75,7 +75,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
         make[S3Component]
         many[IntegrationComponent].add[S3Component]
         make[Initiator]
-      }, GCMode(DIKey.get[Ctx], DIKey.get[Initiator])))
+      }, Roots(DIKey.get[Ctx], DIKey.get[Initiator])))
 
       val result = injector.produce(plan).unsafeGet()
       assert(result.get[Ctx] != null)
@@ -91,7 +91,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
         make[Circular2]
         make[T1].using[Circular1]
         make[T2].using[Circular2]
-      }, GCMode(DIKey.get[Circular2])))
+      }, Roots(DIKey.get[Circular2])))
 
       val result = injector.produce(plan).unsafeGet()
       assert(result.get[Circular1].c2 != null)
@@ -107,7 +107,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
       val plan = injector.plan(PlannerInput(new ModuleDef {
         make[T1].from[Circular1]
         make[T2].from[Circular2]
-      }, GCMode(DIKey.get[T1])))
+      }, Roots(DIKey.get[T1])))
 
       val result = injector.produce(plan).unsafeGet()
       assert(result.get[T1] != null)
@@ -140,7 +140,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
               override def nothing: Int = 2
             }
         }
-      }, GCMode(DIKey.get[Circular2])))
+      }, Roots(DIKey.get[Circular2])))
       val result = injector.produce(plan).unsafeGet()
       assert(result.get[Circular1].nothing == 1)
       assert(result.get[Circular2].nothing == 2)
@@ -155,7 +155,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
       val plan = injector.plan(PlannerInput(new ModuleDef {
         make[Circular1]
         make[Circular2]
-      }, GCMode(DIKey.get[Circular2])))
+      }, Roots(DIKey.get[Circular2])))
 
       val result = injector.produce(plan).unsafeGet()
 
@@ -171,7 +171,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
       val plan = injector.plan(PlannerInput(new ModuleDef {
         make[Circular1]
         make[Circular2]
-      }, GCMode(DIKey.get[Circular2])))
+      }, Roots(DIKey.get[Circular2])))
 
       val result = injector.produce(plan).unsafeGet()
 
@@ -186,7 +186,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
       val plan = injector.plan(PlannerInput(new ModuleDef {
         make[Circular1]
         make[Circular2]
-      }, GCMode(DIKey.get[Circular2], DIKey.get[Set[AutoCloseable]])))
+      }, Roots(DIKey.get[Circular2], DIKey.get[Set[AutoCloseable]])))
 
       val result = injector.produce(plan).unsafeGet()
 
@@ -206,7 +206,7 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
         many[T1]
           .ref[Circular1]
           .ref[Circular2]
-      }, GCMode(DIKey.get[Circular4], DIKey.get[immutable.Set[T1]], DIKey.get[Circular3])))
+      }, Roots(DIKey.get[Circular4], DIKey.get[immutable.Set[T1]], DIKey.get[Circular3])))
 
       val result = injector.produce(plan).unsafeGet()
 
diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcIdempotenceTestsJvm.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcIdempotenceTestsJvm.scala
index c59f00115..0a0d9be70 100644
--- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcIdempotenceTestsJvm.scala
+++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcIdempotenceTestsJvm.scala
@@ -3,7 +3,7 @@ package izumi.distage.gc
 import distage.DIKey
 import izumi.distage.model.PlannerInput
 import izumi.distage.model.definition.ModuleDef
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import org.scalatest.wordspec.AnyWordSpec
 
 class GcIdempotenceTestsJvm extends AnyWordSpec with MkGcInjector {
@@ -17,7 +17,7 @@ class GcIdempotenceTestsJvm extends AnyWordSpec with MkGcInjector {
           make[Circular2]
           make[Circular3]
           make[Circular4]
-        }, GCMode(DIKey.get[Circular2])))
+        }, Roots(DIKey.get[Circular2])))
 
         val result = injector.finishProduce(plan)
         assert(result.get[Circular1].c2 != null)
@@ -33,7 +33,7 @@ class GcIdempotenceTestsJvm extends AnyWordSpec with MkGcInjector {
           make[MkS3Client].from[Impl]
           make[S3Component]
           make[App]
-        }, GCMode(DIKey.get[App])))
+        }, Roots(DIKey.get[App])))
 
         val result = injector.finishProduce(plan)
         assert(result.get[App] != null)
@@ -49,7 +49,7 @@ class GcIdempotenceTestsJvm extends AnyWordSpec with MkGcInjector {
           make[S3Upload]
           make[Ctx]
           make[S3Component]
-        }, GCMode(DIKey.get[Ctx])))
+        }, Roots(DIKey.get[Ctx])))
 
         val result = injector.finishProduce(plan)
         assert(result.get[Ctx].upload.client != null)
@@ -68,7 +68,7 @@ class GcIdempotenceTestsJvm extends AnyWordSpec with MkGcInjector {
           make[S3Component]
           many[IntegrationComponent].add[S3Component]
           make[Initiator]
-        }, GCMode(DIKey.get[Ctx], DIKey.get[Initiator])))
+        }, Roots(DIKey.get[Ctx], DIKey.get[Initiator])))
 
         val result = injector.finishProduce(plan)
         assert(result.get[Ctx] != null)
@@ -83,7 +83,7 @@ class GcIdempotenceTestsJvm extends AnyWordSpec with MkGcInjector {
           make[Circular2]
           make[T1].using[Circular1]
           make[T2].using[Circular2]
-        }, GCMode(DIKey.get[Circular2])))
+        }, Roots(DIKey.get[Circular2])))
 
         val result = injector.finishProduce(plan)
         assert(result.get[Circular1].c2 != null)
@@ -117,7 +117,7 @@ class GcIdempotenceTestsJvm extends AnyWordSpec with MkGcInjector {
                 override def nothing: Int = 2
               }
           }
-        }, GCMode(DIKey.get[Circular2])))
+        }, Roots(DIKey.get[Circular2])))
         val result = injector.finishProduce(plan)
         assert(result.get[Circular1].nothing == 1)
         assert(result.get[Circular2].nothing == 2)
diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/AnimalModel.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/AnimalModel.scala
index 2f8cb2a28..c1882f35f 100644
--- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/AnimalModel.scala
+++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/AnimalModel.scala
@@ -19,7 +19,7 @@ class AnimalModel extends AnyWordSpec with MkInjector {
         make[AccountsApiImpl]
         make[UnrequiredDep]
         make[App]
-      }, GCMode(DIKey.get[App]))
+      }, Roots(DIKey.get[App]))
 
       val debug = false
 
diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTest.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTest.scala
index c8b6e272f..2bb5b0b31 100644
--- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTest.scala
+++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTest.scala
@@ -6,7 +6,7 @@ import izumi.distage.fixtures.InnerClassCases.{InnerClassStablePathsCase, InnerC
 import izumi.distage.fixtures.ResourceCases.{CircularResourceCase, Ref, Suspend2}
 import izumi.distage.injector.ResourceEffectBindingsTest.Fn
 import izumi.distage.model.exceptions.ProvisioningException
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import izumi.distage.provisioning.strategies.cglib.exceptions.CgLibInstantiationOpException
 import net.sf.cglib.core.CodeGenerationException
 import org.scalatest.wordspec.AnyWordSpec
@@ -223,7 +223,7 @@ class CglibProxiesTest extends AnyWordSpec with MkInjector {
         make[ComponentWithByNameFwdRef]
         make[ComponentHolder]
         make[Root]
-      }, GCMode(DIKey.get[Root]))
+      }, Roots(DIKey.get[Root]))
 
       val injector = mkInjector()
       val context = injector.produce(definition).unsafeGet()
@@ -354,7 +354,7 @@ class CglibProxiesTest extends AnyWordSpec with MkInjector {
           .ref[S3Component]
         make[S3Component].fromResource(s3ComponentResource[Fn] _)
         make[S3Client].fromResource(s3clientResource[Fn] _)
-      }, GCMode(DIKey.get[S3Client]))
+      }, Roots(DIKey.get[S3Client]))
 
       val injector = mkInjector()
       val plan = injector.plan(definition)
diff --git a/distage/distage-core/src/test/scala/izumi/distage/compat/CatsResourcesTest.scala b/distage/distage-core/src/test/scala/izumi/distage/compat/CatsResourcesTest.scala
index 48f2a14a7..317a4ed70 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/compat/CatsResourcesTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/compat/CatsResourcesTest.scala
@@ -5,7 +5,7 @@ import distage._
 import izumi.distage.model.definition.Binding.SingletonBinding
 import izumi.distage.model.definition.{DIResource, ImplDef, ModuleDef}
 import izumi.distage.model.effect.LowPriorityDIEffectInstances
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import izumi.distage.compat.CatsResourcesTest._
 import izumi.fundamentals.platform.language.unused
 import org.scalatest.GivenWhenThen
@@ -42,7 +42,7 @@ final class CatsResourcesTest extends AnyWordSpec with GivenWhenThen {
       make[MyApp]
     }
 
-    Injector().produceF[IO](module, GCMode.NoGC).use {
+    Injector().produceF[IO](module, Roots.Everything).use {
       objects =>
         objects.get[MyApp].run
     }.unsafeRunSync()
diff --git a/distage/distage-core/src/test/scala/izumi/distage/gc/GcBasicTests.scala b/distage/distage-core/src/test/scala/izumi/distage/gc/GcBasicTests.scala
index c72fa64de..065b05de2 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/gc/GcBasicTests.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/gc/GcBasicTests.scala
@@ -4,7 +4,7 @@ import distage.DIKey
 import izumi.distage.model.PlannerInput
 import izumi.distage.model.definition.ModuleDef
 import izumi.distage.model.exceptions.UnsupportedOpException
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import org.scalatest.wordspec.AnyWordSpec
 
 class GcBasicTests extends AnyWordSpec with MkGcInjector {
@@ -18,7 +18,7 @@ class GcBasicTests extends AnyWordSpec with MkGcInjector {
         injector.plan(PlannerInput(new ModuleDef {
           make[Circular1]
           make[Circular2]
-        }, GCMode(DIKey.get[Circular2])))
+        }, Roots(DIKey.get[Circular2])))
       }
     }
 
@@ -31,7 +31,7 @@ class GcBasicTests extends AnyWordSpec with MkGcInjector {
         make[Circular2]
         make[T1]
         make[Box[T1]].from(new Box(new T1))
-      }, GCMode(DIKey.get[Circular1], DIKey.get[Circular2])))
+      }, Roots(DIKey.get[Circular1], DIKey.get[Circular2])))
 
       val result = injector.produce(plan).unsafeGet()
 
diff --git a/distage/distage-core/src/test/scala/izumi/distage/gc/GcIdempotenceTests.scala b/distage/distage-core/src/test/scala/izumi/distage/gc/GcIdempotenceTests.scala
index e5285c539..28158492e 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/gc/GcIdempotenceTests.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/gc/GcIdempotenceTests.scala
@@ -3,7 +3,7 @@ package izumi.distage.gc
 import izumi.distage.model.PlannerInput
 import izumi.distage.model.definition.ModuleDef
 import distage.DIKey
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import org.scalatest.wordspec.AnyWordSpec
 
 
@@ -18,7 +18,7 @@ class GcIdempotenceTests extends AnyWordSpec with MkGcInjector {
             .add[TestComponent]
 
           make[App]
-        }, GCMode(DIKey.get[App])))
+        }, Roots(DIKey.get[App])))
 
         val updated = injector.finish(plan.toSemi)
         val result = injector.produce(updated).unsafeGet()
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 1dff5dad2..1374a3017 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
@@ -4,7 +4,7 @@ import distage.{DIKey, Injector, Module}
 import izumi.distage.model.PlannerInput
 import izumi.distage.model.definition.StandardAxis.Repo
 import izumi.distage.model.definition.{Activation, BootstrapModuleDef, ModuleDef}
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import org.scalatest.wordspec.AnyWordSpec
 
 class AxisTest extends AnyWordSpec with MkInjector {
@@ -17,13 +17,13 @@ class AxisTest extends AnyWordSpec with MkInjector {
     }
 
     val injector1 = Injector(Activation(Repo -> Repo.Prod))
-    val context1 = injector1.produce(PlannerInput(definition, GCMode(DIKey.get[JustTrait]))).unsafeGet()
+    val context1 = injector1.produce(PlannerInput(definition, Roots(DIKey.get[JustTrait]))).unsafeGet()
 
     assert(context1.get[JustTrait].isInstanceOf[Impl1])
     assert(!context1.get[JustTrait].isInstanceOf[Impl0])
 
     val injector2 = Injector(Activation(Repo -> Repo.Dummy))
-    val context2 = injector2.produce(PlannerInput(definition, GCMode(DIKey.get[JustTrait]))).unsafeGet()
+    val context2 = injector2.produce(PlannerInput(definition, Roots(DIKey.get[JustTrait]))).unsafeGet()
 
     assert(context2.get[JustTrait].isInstanceOf[Impl0])
     assert(!context2.get[JustTrait].isInstanceOf[Impl1])
@@ -39,13 +39,13 @@ class AxisTest extends AnyWordSpec with MkInjector {
     val appDefinition = Module.empty
 
     val injector1 = Injector(Activation(Repo -> Repo.Prod), bsDefinition)
-    val context1 = injector1.produce(PlannerInput(appDefinition, GCMode.NoGC)).unsafeGet()
+    val context1 = injector1.produce(PlannerInput(appDefinition, Roots.Everything)).unsafeGet()
 
     assert(context1.get[JustTrait].isInstanceOf[Impl1])
     assert(!context1.get[JustTrait].isInstanceOf[Impl0])
 
     val injector2 = Injector(Activation(Repo -> Repo.Dummy), bsDefinition)
-    val context2 = injector2.produce(PlannerInput(appDefinition, GCMode.NoGC)).unsafeGet()
+    val context2 = injector2.produce(PlannerInput(appDefinition, Roots.Everything)).unsafeGet()
 
     assert(context2.get[JustTrait].isInstanceOf[Impl0])
     assert(!context2.get[JustTrait].isInstanceOf[Impl1])
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 be5cc2c7d..14ef9e000 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
@@ -6,7 +6,7 @@ import izumi.distage.fixtures.ResourceCases._
 import izumi.distage.injector.ResourceEffectBindingsTest.Fn
 import izumi.distage.model.definition.DIResource
 import izumi.distage.model.exceptions.ProvisioningException
-import izumi.distage.model.plan.GCMode
+import izumi.distage.model.plan.Roots
 import izumi.fundamentals.platform.functional.Identity
 import izumi.fundamentals.platform.language.Quirks._
 import org.scalatest.wordspec.AnyWordSpec
@@ -28,7 +28,7 @@ class ResourceEffectBindingsTest extends AnyWordSpec with MkInjector {
       val definition = PlannerInput(new ModuleDef {
         make[Int].named("2").from(2)
         make[Int].fromEffect[Identity, Int] { i: Int @Id("2") => 10 + i }
-      }, GCMode(DIKey.get[Int]))
+      }, Roots(DIKey.get[Int]))
 
       val injector = mkInjector()
       val plan = injector.plan(definition)
@@ -41,7 +41,7 @@ class ResourceEffectBindingsTest extends AnyWordSpec with MkInjector {
       val definition = PlannerInput(new ModuleDef {
         make[Int].named("2").from(2)
         make[Int].fromEffect { i: Int @Id("2") => Suspend2(10 + i) }
-      }, GCMode(DIKey.get[Int]))
+      }, Roots(DIKey.get[Int]))
 
       val injector = mkInjector()
       val plan = injector.plan(definition)
@@ -77,7 +77,7 @@ class ResourceEffectBindingsTest extends AnyWordSpec with MkInjector {
       val definition = PlannerInput(new ModuleDef {
         make[Int].named("2").from(2)
         make[Int].fromEffect[Identity, Int] { i: Int @Id("2") => 10 + i }
-      }, GCMode(DIKey.get[Int]))
+      }, Roots(DIKey.get[Int]))
 
       val injector = mkInjector()
       val plan = injector.plan(definition)