Repository: 7mind/izumi
Pull request #1806 (2022-10-17 19:05:46)

Allow using `Lifecycle#flatMap` and related methods with a non-Throwable error
* Extract a subset of `QuasiIO` not dependent on Throwable into `QuasiPrimitives`

codecov-commenter: # [Codecov](https://codecov.io/gh/7mind/izumi/pull/1806?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=7mind) Report
> Merging [#1806](https://codecov.io/gh/7mind/izumi/pull/1806?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=7mind) (cb30f36) into [develop](https://codecov.io/gh/7mind/izumi/commit/da55b7fe55cbe9429c1a5bf15b6af032839e7f0c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=7mind) (da55b7f) will **decrease** coverage by `0.00%`.
> The diff coverage is `77.33%`.

```diff
@@             Coverage Diff             @@
##           develop    #1806      +/-   ##
===========================================
- Coverage    63.51%   63.51%   -0.01%     
===========================================
  Files          453      453              
  Lines         9721     9748      +27     
  Branches      1017     1020       +3     
===========================================
+ Hits          6174     6191      +17     
- Misses        3547     3557      +10     
```


Patch:
diff --git a/.scalafmt.conf b/.scalafmt.conf
index e5cd2f877..efcd18433 100644
--- a/.scalafmt.conf
+++ b/.scalafmt.conf
@@ -1,5 +1,5 @@
-version = "3.5.9"
-runner.dialect = scala3
+version = "3.6.0"
+runner.dialect = scala3Future
 project.git = true
 project.excludePaths = ["glob:**.sbt", "glob:**sbtgen.sc"]
 
diff --git a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/AnyConstructorMacro.scala b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/AnyConstructorMacro.scala
new file mode 100644
index 000000000..138fceda0
--- /dev/null
+++ b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/AnyConstructorMacro.scala
@@ -0,0 +1,184 @@
+package izumi.distage.constructors
+
+import izumi.distage.constructors.{AnyConstructor, AnyConstructorOptionalMakeDSL, ClassConstructor, ClassConstructorMacro, FactoryConstructor, TraitConstructor, TraitConstructorMacro}
+import izumi.distage.model.definition.{Bindings, ModuleBase}
+import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.SingletonRef
+import izumi.distage.model.definition.dsl.ModuleDefDSL
+import izumi.distage.model.providers.Functoid
+import izumi.fundamentals.platform.language.CodePositionMaterializer
+import izumi.reflect.Tag
+import izumi.fundamentals.platform.exceptions.IzThrowable.toRichThrowable
+
+import scala.annotation.experimental
+import scala.quoted.{Expr, Quotes, Type}
+
+object AnyConstructorMacro {
+
+  @experimental
+  def make[T: Type](using qctx: Quotes): Expr[AnyConstructor[T]] = try {
+    import qctx.reflect.{*, given}
+
+    val tpe0 = TypeRepr.of[T].dealias.simplified
+    val typeSymbol = tpe0.typeSymbol
+
+    if (AndTypeTypeTest.unapply(tpe0).isDefined || RefinementTypeTest.unapply(tpe0).isDefined) {
+      // ignore intersections for now
+      '{ ??? : AnyConstructor[T] }
+    } else if (tpe0.classSymbol.isDefined && !typeSymbol.flags.is(Flags.Trait) && !typeSymbol.flags.is(Flags.Abstract)) {
+      ClassConstructorMacro.make[T]
+    } else if ({
+      // FIXME remove redundant check across macros
+      val util = new ConstructorUtil[qctx.type]()
+
+      val context = new ConstructorContext[T, qctx.type](util)
+      // TODO: check for sealed
+      context.isWireableTrait
+    }) {
+      TraitConstructorMacro.make[T]
+    } else {
+      report.errorAndAbort(
+        s"""AnyConstructor failure: couldn't generate a constructor for ${Type.show[T]}!
+           |It's neither a concrete class, nor a wireable trait or abstract class!""".stripMargin
+      )
+    }
+  } catch { case t: Throwable => qctx.reflect.report.errorAndAbort(t.stackTrace) }
+
+  @experimental
+  def makeMethod[T: Type, BT: Type](using qctx: Quotes): Expr[BT] = try {
+    import qctx.reflect.*
+
+    def goGetOuterClass(s: Symbol): Symbol = {
+      if (s.isClassDef) {
+        s
+      } else {
+        goGetOuterClass(s.owner)
+      }
+    }
+    val outerClass = goGetOuterClass(Symbol.spliceOwner)
+
+    Expr.summon[AnyConstructorOptionalMakeDSL[T]] match {
+      case Some(ctor) =>
+        applyMake[T, BT](outerClass)('{ $ctor.provider })
+      case None =>
+        makeMethodImpl[T, BT](outerClass)
+    }
+  } catch { case t: Throwable => qctx.reflect.report.errorAndAbort(t.stackTrace) }
+
+  @experimental
+  private def applyMake[T: Type, BT: Type](using qctx: Quotes)(outerClass: qctx.reflect.Symbol)(functoid: Expr[Functoid[T]]): Expr[BT] = {
+    import qctx.reflect.*
+
+    val tagT = '{ compiletime.summonInline[Tag[T]] }
+    val codep = '{ compiletime.summonInline[CodePositionMaterializer] }
+
+    Apply(Apply(TypeApply(Select.unique(This(outerClass), "_make"), List(TypeTree.of[T])), List(functoid.asTerm)), List(tagT.asTerm, codep.asTerm)).asExprOf[BT]
+  }
+
+  @experimental
+  private def makeMethodImpl[T: Type, BT: Type](using qctx: Quotes)(outerClass: qctx.reflect.Symbol): Expr[BT] = {
+    import qctx.reflect.*
+
+    def findPos(p: Position, t: Tree): Tree = {
+      new TreeAccumulator[Option[Tree]] {
+        override def foldTree(x: Option[Tree], tree: Tree)(owner: Symbol): Option[Tree] = {
+          if (x.isDefined) {
+            x
+          } else {
+            val treeStart =
+              try {
+                tree.pos.start
+              } catch { case _: Throwable => 0 }
+
+            if (treeStart != 0 && treeStart == p.start) {
+              Some(tree)
+            } else {
+              foldOverTree(x, tree)(owner)
+            }
+          }
+        }
+      }.foldOverTree(None, t)(Symbol.noSymbol)
+        .getOrElse {
+          report.errorAndAbort(s"Couldn't find position=$p in tree=${t.show}, all positions=${allPos(t)}")
+        }
+    }
+
+    def allPos(t: Tree): List[Position] = {
+      new TreeAccumulator[List[Position]] {
+        override def foldTree(x: List[Position], tree: Tree)(owner: Symbol): List[Position] = {
+          foldOverTree(x, tree)(owner) :+ tree.pos
+        }
+      }.foldOverTree(Nil, t)(Symbol.noSymbol)
+    }
+
+    var stopPos = List.empty[(Position, String)]
+
+    extension (biggerPos: Position) {
+      def contains(t: Tree, smallerPos: Position): Boolean = {
+        val res = biggerPos.start <= smallerPos.start
+          && biggerPos.end >= smallerPos.end
+        if !res then stopPos = stopPos :+ (biggerPos, t.show)
+        res
+      }
+    }
+
+    def allMethodsCalledOnPosition(macroPos: Position, t0: Tree): List[String] = {
+      new TreeAccumulator[List[String]] {
+        override def foldTree(x: List[String], tree: Tree)(owner: Symbol): List[String] = {
+          def continueIfContainsPos(t: Tree): List[String] = {
+            if (t.pos.contains(t, macroPos)) {
+              foldOverTree(x, tree)(owner)
+            } else {
+              x
+            }
+          }
+          tree match {
+            case Select(t, name) =>
+              name :: continueIfContainsPos(t)
+            case t =>
+              continueIfContainsPos(t)
+          }
+        }
+      }.foldOverTree(Nil, t0)(Symbol.noSymbol)
+    }
+
+    val outerowner = {
+      val outerExpr = Symbol.spliceOwner.owner
+      if (outerExpr.isLocalDummy) outerExpr.owner else outerExpr
+    }
+
+    val foundPos = findPos(Position.ofMacroExpansion, outerowner.tree)
+
+    val foundMethods = allMethodsCalledOnPosition(Position.ofMacroExpansion, foundPos)
+
+    val fromLikeMethods = foundMethods.filter(!ModuleDefDSL.MakeDSLNoOpMethodsWhitelist.contains(_))
+
+    // FIXME remove redundant wrapping and .provider call
+    val functoid: Expr[Functoid[T]] =
+      if (fromLikeMethods.isEmpty) {
+        '{ ${ AnyConstructorMacro.make[T] }.provider }
+      } else {
+        '{ AnyConstructorOptionalMakeDSL.errorConstructor[T](${ Expr(Type.show[T]) }, ${ Expr(fromLikeMethods) }).provider }
+      }
+
+    val res = applyMake[T, BT](outerClass)(functoid)
+
+    import Printer.TreeStructure
+    report.warning(
+      s"""Splice owner tree: ${Symbol.spliceOwner.tree.show}:${Symbol.spliceOwner.pos} (macro:${Position.ofMacroExpansion})
+         |Splice owner-owner tree: ${Symbol.spliceOwner.owner.tree}:${Symbol.spliceOwner.owner.pos}
+         |Splice outer tree: ${outerowner.tree.show}:${outerowner.pos}
+         |allPos: ${allPos(outerowner.tree)}
+         |stopPos: $stopPos
+         |findPos: ${foundPos.show}
+         |findPosTree: $foundPos
+         |allCalledMethods: $foundMethods
+         |fromLikeMethods: $fromLikeMethods
+         |res: ${res.show}
+         |resTree: ${res.asTerm}
+         |""".stripMargin
+    )
+
+    res
+  }
+
+}
diff --git a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/ClassConstructorMacro.scala b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/ClassConstructorMacro.scala
index da2219978..8a0f18ec4 100644
--- a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/ClassConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/ClassConstructorMacro.scala
@@ -18,11 +18,18 @@ object ClassConstructorMacro {
     Expr.summon[ValueOf[R]] match {
       case Some(valexpr) =>
         '{ new ClassConstructor[R](Functoid.singleton(${ valexpr.asExprOf[scala.Singleton & R] })) }
+
       case _ =>
         util.requireConcreteTypeConstructor[R]("ClassConstructor")
 
         val typeRepr = TypeRepr.of[R].dealias.simplified
 
+        if (typeRepr.typeSymbol.flags.is(Flags.Trait) || typeRepr.typeSymbol.flags.is(Flags.Abstract)) {
+          report.errorAndAbort(
+            s"Cannot create ClassConstructor for type ${Type.show[R]} - it's a trait or an abstract class, not a concrete class. It cannot be constructed with `new`"
+          )
+        }
+
         typeRepr.classSymbol match {
           case Some(cs) =>
             val ctorTreeParameterized = util.buildConstructorApplication(cs, typeRepr)
@@ -31,6 +38,7 @@ object ClassConstructorMacro {
 
             val f = functoidMacro.make[R](lamExpr)
             '{ new ClassConstructor[R](${ f }) }
+
           case None =>
             report.errorAndAbort(s"No class symbol defined for $typeRepr")
         }
diff --git a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/ConstructorUtil.scala b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/ConstructorUtil.scala
index 752dc7ae0..612b29470 100644
--- a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/ConstructorUtil.scala
+++ b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/ConstructorUtil.scala
@@ -1,5 +1,6 @@
 package izumi.distage.constructors
 
+import izumi.distage.model.definition.With
 import izumi.fundamentals.platform.reflection.ReflectionUtil
 
 import scala.annotation.tailrec
@@ -11,7 +12,7 @@ class ConstructorContext[R: Type, Q <: Quotes](using val qctx: Q)(val util: Cons
   import qctx.reflect.*
 
   val resultTpe = TypeRepr.of[R].dealias.simplified
-  val resultTypeTree = TypeTree.of[R]
+  val resultTpeTree = TypeTree.of[R]
   val resultTpeSym = resultTpe.typeSymbol
 
   val refinementMethods = util.unpackRefinement(resultTpe)
@@ -26,7 +27,7 @@ class ConstructorContext[R: Type, Q <: Quotes](using val qctx: Q)(val util: Cons
   val constructorParamLists = parentsSymbols.map(util.buildConstructorParameters(resultTpe))
   val flatCtorParams = constructorParamLists.flatMap(_._2.iterator.flatten)
 
-  val methodDecls = abstractMethods.map(m => (m.name, m.owner.typeRef.memberType(m))) ++ refinementMethods
+  val methodDecls = abstractMethods.map(m => (m.name, resultTpe.memberType(m))) ++ refinementMethods
 
   def isFactory: Boolean = abstractMethods.nonEmpty || refinementMethods.nonEmpty
 
@@ -36,13 +37,17 @@ class ConstructorContext[R: Type, Q <: Quotes](using val qctx: Q)(val util: Cons
 class ConstructorUtil[Q <: Quotes](using val qctx: Q) {
   import qctx.reflect.*
 
+  private val withAnnotationSym = TypeRepr.of[With].typeSymbol
+
   type ParamListRepr = List[(String, qctx.reflect.TypeRepr)]
   type ParamListsRepr = List[ParamListRepr]
   type ParamListTree = List[(String, qctx.reflect.TypeTree)]
   type ParamListsTree = List[ParamListTree]
 
-  implicit class ParamListExt(params: ParamListRepr) {
-    def toTrees: ParamListTree = params.map((n, t) => (n, TypeTree.of(using t.asType))).toList
+  implicit object ParamListExt {
+    extension (params: ParamListRepr) {
+      def toTrees: ParamListTree = params.map((n, t) => (n, TypeTree.of(using t.asType))).toList
+    }
   }
 
   def assertSignatureIsAcceptableForFactory(signatureParams: ParamListRepr, resultTpe: TypeRepr, clue: String): Unit = {
@@ -174,14 +179,13 @@ class ConstructorUtil[Q <: Quotes](using val qctx: Q) {
   }
 
   def readWithAnno(tpe: TypeRepr): Option[TypeRepr] = {
-    import izumi.distage.model.definition.With
     tpe match {
       case AnnotatedType(_, aterm) =>
-        aterm.asExprOf[Any] match {
-          case '{ new With[a] } =>
-            Some(TypeRepr.of[a].dealias.simplified)
+        aterm match {
+          case Apply(TypeApply(Select(New(_), _), c :: _), _) if aterm.tpe.baseClasses.contains(withAnnotationSym) =>
+            Some(c.tpe)
           case _ =>
-            None
+            report.errorAndAbort(s"distage.With annotation expects one type argument but got malformed tree ${aterm.show} ($aterm) : ${aterm.tpe}")
         }
       case _ =>
         None
diff --git a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/FactoryConstructorMacro.scala b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/FactoryConstructorMacro.scala
index 1ae1992e9..4cadd7022 100644
--- a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/FactoryConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/FactoryConstructorMacro.scala
@@ -10,8 +10,10 @@ import scala.quoted.{Expr, Quotes, Type}
 
 object FactoryConstructorMacro {
 
+  def make[R: Type](using qctx: Quotes): Expr[FactoryConstructor[R]] = '{ ??? }
+
   @experimental
-  def make[R: Type](using qctx: Quotes): Expr[FactoryConstructor[R]] = try {
+  def make0[R: Type](using qctx: Quotes): Expr[FactoryConstructor[R]] = try {
     import qctx.reflect.*
 
     val functoidMacro = new FunctoidMacro.FunctoidMacroImpl[qctx.type]()
@@ -25,11 +27,10 @@ object FactoryConstructorMacro {
 
     if (!isFactory) {
       report.errorAndAbort(
-        s"""$resultTpeSym has no abstract methods so it's not a factory;; ${resultTpeSym.methodMembers};; $resultTypeTree;; ${resultTypeTree.getClass}""".stripMargin
+        s"""$resultTpeSym has no abstract methods so it's not a factory;; ${resultTpeSym.methodMembers};; $resultTpeTree;; ${resultTpeTree.getClass}""".stripMargin
       )
     }
 
-
     val refinementNames = refinementMethods.map(_._1).toSet
 
     def decls(cls: Symbol): List[Symbol] = methodDecls.map {
@@ -63,7 +64,7 @@ object FactoryConstructorMacro {
 
         if (impltype.typeSymbol.flags.is(Flags.Trait) || impltype.typeSymbol.flags.is(Flags.Abstract)) {
           report.errorAndAbort(
-            s"Cannot build factory for ${resultTpe.show}, factory method $n returns type ${impltype.show} which cannot be constructed with new"
+            s"Cannot build factory for ${resultTpe.show}, factory method $n returns type ${impltype.show} which cannot be constructed with `new`"
           )
         }
 
@@ -112,7 +113,7 @@ object FactoryConstructorMacro {
 
     val ctorArgs = flatCtorParams.map((n, t) => (n, util.dropMethodType(t)))
     val byNameMethodArgs = factoryMethodData.flatMap(_.params).flatten.collect { case p: ProvidedParameter => (p.argName, p.tpe) }
-    val lamParams = (ctorArgs ++ byNameMethodArgs).toTrees
+    val lamParams: util.ParamListTree = (ctorArgs ++ byNameMethodArgs).toTrees
     val indexShift = ctorArgs.length
 
     val lamExpr = util.wrapIntoLambda[R](List(lamParams)) {
@@ -158,9 +159,9 @@ object FactoryConstructorMacro {
         }
 
         val clsDef = ClassDef(clsSym, parents.toList, body = defs)
-        val newCls = Typed(Apply(Select(New(TypeIdent(clsSym)), clsSym.primaryConstructor), Nil), resultTypeTree)
+        val newCls = Typed(Apply(Select(New(TypeIdent(clsSym)), clsSym.primaryConstructor), Nil), resultTpeTree)
         val block = Block(List(clsDef), newCls)
-        Typed(block, resultTypeTree)
+        Typed(block, resultTpeTree)
     }
 
 //    report.warning(
diff --git a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/TraitConstructorMacro.scala b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/TraitConstructorMacro.scala
index f78273e03..0b22cb4a2 100644
--- a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/TraitConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/TraitConstructorMacro.scala
@@ -40,8 +40,8 @@ object TraitConstructorMacro {
 
     // TODO: decopypaste
     val lamParams = {
-      val byNameMethodArgs = methodDecls.map((n, t) => (s"_$n", util.ensureByName(util.dropWrappers(t))))
       val ctorArgs = flatCtorParams.map((n, t) => (n, util.dropMethodType(t)))
+      val byNameMethodArgs = methodDecls.map((n, t) => (s"_$n", util.ensureByName(util.dropWrappers(t))))
       (ctorArgs ++ byNameMethodArgs).toTrees
     }
 
@@ -64,21 +64,22 @@ object TraitConstructorMacro {
         }
 
         val clsDef = ClassDef(clsSym, parents.toList, body = defs)
-        val newCls = Typed(Apply(Select(New(TypeIdent(clsSym)), clsSym.primaryConstructor), Nil), resultTypeTree)
+        val newCls = Typed(Apply(Select(New(TypeIdent(clsSym)), clsSym.primaryConstructor), Nil), resultTpeTree)
         val block = Block(List(clsDef), newCls)
-        Typed(block, resultTypeTree)
+        Typed(block, resultTpeTree)
     }
 
-    /*
+    import Printer.TreeStructure
     report.warning(
-      s"""|symbol = $resultTpeSym, flags=${resultTpeSym.flags.show}
+      s"""|tpe = $resultTpe
+          |symbol = $resultTpeSym, flags=${resultTpeSym.flags.show}
           |methods = ${resultTpeSym.methodMembers.map(s => s"name: ${s.name} flags ${s.flags.show}")}
           |tree = ${resultTpeSym.tree}
           |pcs  = ${resultTpeSym.primaryConstructor.tree.show}
           |pct  = ${resultTpeSym.primaryConstructor.tree}
           |pct-flags = ${resultTpeSym.primaryConstructor.flags.show}
-          |pctt = ${resultTpeSym.typeRef.memberType(resultTpeSym.primaryConstructor)}
-          |pcts = ${resultTpeSym.typeRef.baseClasses
+          |pctt = ${resultTpe.memberType(resultTpeSym.primaryConstructor)}
+          |pcts = ${resultTpe.baseClasses
            .map(s => (s, s.primaryConstructor)).map((cs, s) => if (s != Symbol.noSymbol) (cs, cs.flags.show, s.tree) else (cs, cs.flags.show, None))
            .mkString("\n")}
           |defn = ${resultTpeSym.tree.show}
@@ -86,7 +87,6 @@ object TraitConstructorMacro {
           |lam  = ${lamExpr.show}
           |""".stripMargin
     )
-     */
 
     val f = functoidMacro.make[R](lamExpr)
     '{ new TraitConstructor[R](${ f }) }
diff --git a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/constructors.scala b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/constructors.scala
index 5addee2b7..901cde4d7 100644
--- a/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/constructors.scala
+++ b/distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/constructors.scala
@@ -44,12 +44,19 @@ sealed trait AnyConstructor[T] extends Any with AnyConstructorOptionalMakeDSL[T]
   def provider: Functoid[T]
 }
 
+object AnyConstructor {
+  def apply[T](implicit ctor: AnyConstructor[T]): Functoid[T] = ctor.provider
+
+  inline implicit def materialize[T]: AnyConstructor[T] = ${ AnyConstructorMacro.make[T] }
+}
+
 /**
   * An implicitly summonable constructor for a concrete class `T`
   *
   * @see [[AnyConstructor]]
   */
 final class ClassConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructor[T]
+
 object ClassConstructor {
   def apply[T](implicit ctor: ClassConstructor[T]): Functoid[T] = ctor.provider
 
@@ -65,37 +72,10 @@ object ClassConstructor {
   */
 final class TraitConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructor[T]
 
-/**
-  * An implicitly summonable constructor for a "factory-like" trait or abstract class `T`
-  *
-  * @see [[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]]
-  * @see [[izumi.distage.model.definition.impl]] recommended documenting annotation for use with [[FactoryConstructor]]
-  * @see [[AnyConstructor]]
-  */
-final class FactoryConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructor[T]
-
-/**
-  * An implicitly summonable constructor for a `T <: zio.Has[A] with zio.Has[B] with zio.Has[C]`
-  *
-  * `zio.Has` heterogeneous map values may be used by ZIO or other Reader-like effects
-  *
-  * @see [[https://izumi.7mind.io/distage/basics.html#zio-has-bindings ZIO Has bindings]]
-  * @see [[AnyConstructor]]
-  */
-final class HasConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructor[T]
-
-object AnyConstructor {
-  def apply[T](implicit ctor: AnyConstructor[T]): Functoid[T] = ctor.provider
-
-  inline implicit def materialize[T]: AnyConstructor[T] = ???
-}
-
-
-
 object TraitConstructor {
   def apply[T](implicit ctor: TraitConstructor[T]): Functoid[T] = ctor.provider
 
-  inline implicit def materialize[T]: TraitConstructor[T] = ${TraitConstructorMacro.make[T]}
+  inline implicit def materialize[T]: TraitConstructor[T] = ${ TraitConstructorMacro.make[T] }
 
   def wrapInitialization[A](init: => A)(implicit weakTag: WeakTag[A]): A = {
     try init
@@ -107,12 +87,31 @@ object TraitConstructor {
   }
 }
 
+/**
+  * An implicitly summonable constructor for a "factory-like" trait or abstract class `T`
+  *
+  * @see [[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]]
+  * @see [[izumi.distage.model.definition.impl]] recommended documenting annotation for use with [[FactoryConstructor]]
+  * @see [[AnyConstructor]]
+  */
+final class FactoryConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructor[T]
+
 object FactoryConstructor {
   def apply[T](implicit ctor: FactoryConstructor[T]): Functoid[T] = ctor.provider
 
   inline implicit def materialize[T]: FactoryConstructor[T] = ${ FactoryConstructorMacro.make[T] }
 }
 
+/**
+  * An implicitly summonable constructor for a `T <: zio.Has[A] with zio.Has[B] with zio.Has[C]`
+  *
+  * `zio.Has` heterogeneous map values may be used by ZIO or other Reader-like effects
+  *
+  * @see [[https://izumi.7mind.io/distage/basics.html#zio-has-bindings ZIO Has bindings]]
+  * @see [[AnyConstructor]]
+  */
+final class HasConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructor[T]
+
 object HasConstructor {
   def apply[T](implicit ctor: HasConstructor[T]): Functoid[T] = ctor.provider
 
@@ -147,6 +146,4 @@ object AnyConstructorOptionalMakeDSL {
          |""".stripMargin
     )
   }
-
-  inline implicit def materialize[T]: AnyConstructorOptionalMakeDSL.Impl[T] = ???
 }
diff --git a/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/AbstractBindingDefDSLMacro.scala b/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/AbstractBindingDefDSLMacro.scala
index 207155e14..d9499ed26 100644
--- a/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/AbstractBindingDefDSLMacro.scala
+++ b/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/AbstractBindingDefDSLMacro.scala
@@ -1,16 +1,7 @@
 package izumi.distage.model.definition.dsl
 
-import izumi.distage.constructors.FactoryConstructor
-import izumi.distage.model.definition.Bindings
-import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.SingletonRef
-import izumi.fundamentals.platform.language.CodePositionMaterializer
-import izumi.reflect.Tag
+import izumi.distage.constructors.AnyConstructorMacro
 
 trait AbstractBindingDefDSLMacro[BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]] { this: AbstractBindingDefDSL[BindDSL, BindDSLAfterFrom, SetDSL] =>
-
-  final protected[this] def make[T]: BindDSL[T] = ???
-
-  inline final protected[this] def makeFactory[T](implicit tag: Tag[T], pos: CodePositionMaterializer): BindDSLAfterFrom[T] = {
-    _makeSimple(FactoryConstructor[T])(tag, pos)
-  }
+  inline final protected[this] def make[T]: BindDSL[T] = ${ AnyConstructorMacro.makeMethod[T, BindDSL[T]] }
 }
diff --git a/distage/distage-core-api/src/main/scala-3/izumi/distage/model/providers/FunctoidMacroMethods.scala b/distage/distage-core-api/src/main/scala-3/izumi/distage/model/providers/FunctoidMacroMethods.scala
index 9075db52f..530ca9aea 100644
--- a/distage/distage-core-api/src/main/scala-3/izumi/distage/model/providers/FunctoidMacroMethods.scala
+++ b/distage/distage-core-api/src/main/scala-3/izumi/distage/model/providers/FunctoidMacroMethods.scala
@@ -79,7 +79,7 @@ object FunctoidMacro {
       val paramDefs = params.map {
         case (name, tpe) =>
           val identifier = tpe match {
-            case Annotated(_, aterm) if aterm.tpe.dealias.simplified.baseClasses.contains(idAnnotationSym) =>
+            case Annotated(_, aterm) if aterm.tpe.baseClasses.contains(idAnnotationSym) =>
               aterm match {
                 case Apply(Select(New(_), _), c :: _) =>
                   c.asExprOf[String].value.orElse {
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Lifecycle.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Lifecycle.scala
index 9338d88cb..5ad1d45a1 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Lifecycle.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Lifecycle.scala
@@ -6,7 +6,7 @@ import cats.effect.kernel.{GenConcurrent, Resource, Sync}
 import izumi.distage.constructors.HasConstructor
 import izumi.distage.model.Locator
 import izumi.distage.model.definition.Lifecycle.{evalMapImpl, flatMapImpl, fromCats, fromZIO, mapImpl, redeemImpl, wrapAcquireImpl, wrapReleaseImpl}
-import izumi.distage.model.effect.{QuasiApplicative, QuasiFunctor, QuasiIO}
+import izumi.distage.model.effect.{QuasiApplicative, QuasiFunctor, QuasiIO, QuasiPrimitives, QuasiRef}
 import izumi.distage.model.providers.Functoid
 import izumi.functional.bio.data.Morphism1
 import izumi.functional.bio.{Fiber2, Fork2, Functor2, Functor3, Local3}
@@ -234,7 +234,8 @@ trait Lifecycle[+F[_], +OuterResource] {
   def extract[B >: OuterResource](resource: InnerResource): Either[F[B], B]
 
   final def map[G[x] >: F[x]: QuasiFunctor, B](f: OuterResource => B): Lifecycle[G, B] = mapImpl[G, OuterResource, B](this)(f)
-  final def flatMap[G[x] >: F[x]: QuasiIO, B](f: OuterResource => Lifecycle[G, B]): Lifecycle[G, B] = flatMapImpl[G, OuterResource, B](this)(f)
+  final def flatMap[G[x] >: F[x]: QuasiPrimitives, B](f: OuterResource => Lifecycle[G, B]): Lifecycle[G, B] = flatMapImpl[G, OuterResource, B](this)(f)
+  final def flatten[G[x] >: F[x]: QuasiPrimitives, B](implicit ev: OuterResource <:< Lifecycle[G, B]): Lifecycle[G, B] = this.flatMap(ev)
 
   final def catchAll[G[x] >: F[x]: QuasiIO, B >: OuterResource](recover: Throwable => Lifecycle[G, B]): Lifecycle[G, B] =
     redeemImpl[G, OuterResource, B](this)(recover, Lifecycle.pure[G](_))
@@ -244,9 +245,9 @@ trait Lifecycle[+F[_], +OuterResource] {
   final def redeem[G[x] >: F[x]: QuasiIO, B](onFailure: Throwable => Lifecycle[G, B], onSuccess: OuterResource => Lifecycle[G, B]): Lifecycle[G, B] =
     redeemImpl[G, OuterResource, B](this)(onFailure, onSuccess)
 
-  final def evalMap[G[x] >: F[x]: QuasiIO, B](f: OuterResource => G[B]): Lifecycle[G, B] = evalMapImpl[G, OuterResource, B](this)(f)
-  final def evalTap[G[x] >: F[x]: QuasiIO](f: OuterResource => G[Unit]): Lifecycle[G, OuterResource] =
-    evalMap[G, OuterResource](a => QuasiIO[G].map(f(a))(_ => a))
+  final def evalMap[G[x] >: F[x]: QuasiPrimitives, B](f: OuterResource => G[B]): Lifecycle[G, B] = evalMapImpl[G, OuterResource, B](this)(f)
+  final def evalTap[G[x] >: F[x]: QuasiPrimitives](f: OuterResource => G[Unit]): Lifecycle[G, OuterResource] =
+    evalMap[G, OuterResource](a => QuasiFunctor[G].map(f(a))(_ => a))
 
   /** Wrap acquire action of this resource in another effect, e.g. for logging purposes */
   final def wrapAcquire[G[x] >: F[x]](f: (=> G[InnerResource]) => G[InnerResource]): Lifecycle[G, OuterResource] =
@@ -320,7 +321,7 @@ object Lifecycle extends LifecycleInstances {
   }
 
   /** @param effect is performed interruptibly, unlike in [[make]] */
-  def suspend[F[_]: QuasiIO, A](effect: => F[Lifecycle[F, A]]): Lifecycle[F, A] = {
+  def suspend[F[_]: QuasiPrimitives, A](effect: => F[Lifecycle[F, A]]): Lifecycle[F, A] = {
     liftF(effect).flatten
   }
 
@@ -349,13 +350,13 @@ object Lifecycle extends LifecycleInstances {
     Lifecycle.make(F.start(f))(_.cancel)
   }
 
-  def traverse[F[_]: QuasiIO, A, B](l: Iterable[A])(f: A => Lifecycle[F, B]): Lifecycle[F, List[B]] = {
+  def traverse[F[_]: QuasiPrimitives, A, B](l: Iterable[A])(f: A => Lifecycle[F, B]): Lifecycle[F, List[B]] = {
     l.foldLeft(pure[F](List.empty[B])) {
       (acc, a) => acc.flatMap(list => f(a).map(r => list ++ List(r)))
     }
   }
 
-  def traverse_[F[_]: QuasiIO, A](l: Iterable[A])(f: A => Lifecycle[F, Unit]): Lifecycle[F, Unit] = {
+  def traverse_[F[_]: QuasiPrimitives, A](l: Iterable[A])(f: A => Lifecycle[F, Unit]): Lifecycle[F, Unit] = {
     l.foldLeft(unit) {
       (acc, a) => acc.flatMap(_ => f(a))
     }
@@ -402,7 +403,7 @@ object Lifecycle extends LifecycleInstances {
   }
 
   implicit final class SyntaxUse[F[_], +A](private val resource: Lifecycle[F, A]) extends AnyVal {
-    def use[B](use: A => F[B])(implicit F: QuasiIO[F]): F[B] = {
+    def use[B](use: A => F[B])(implicit F: QuasiPrimitives[F]): F[B] = {
       F.bracket(acquire = resource.acquire)(release = resource.release)(
         use = a =>
           F.suspendF(resource.extract(a) match {
@@ -414,12 +415,12 @@ object Lifecycle extends LifecycleInstances {
   }
 
   implicit final class SyntaxUseEffect[F[_], A](private val resource: Lifecycle[F, F[A]]) extends AnyVal {
-    def useEffect(implicit F: QuasiIO[F]): F[A] =
+    def useEffect(implicit F: QuasiPrimitives[F]): F[A] =
       resource.use(identity)
   }
 
   implicit final class SyntaxLocatorRun[F[_]](private val resource: Lifecycle[F, Locator]) extends AnyVal {
-    def run[B](function: Functoid[F[B]])(implicit F: QuasiIO[F]): F[B] =
+    def run[B](function: Functoid[F[B]])(implicit F: QuasiPrimitives[F]): F[B] =
       resource.use(_.run(function))
   }
 
@@ -434,10 +435,6 @@ object Lifecycle extends LifecycleInstances {
     }
   }
 
-  implicit final class SyntaxFlatten[F[_], +A](private val resource: Lifecycle[F, Lifecycle[F, A]]) extends AnyVal {
-    def flatten(implicit F: QuasiIO[F]): Lifecycle[F, A] = resource.flatMap(identity)
-  }
-
   implicit final class SyntaxUnsafeGet[F[_], A](private val resource: Lifecycle[F, A]) extends AnyVal {
     /** Unsafely acquire the resource and throw away the finalizer,
       * this will leak the resource and cause it to never be cleaned up.
@@ -447,7 +444,7 @@ object Lifecycle extends LifecycleInstances {
       *
       * @note will also acquire the resource without an uninterruptible section
       */
-    def unsafeGet()(implicit F: QuasiIO[F]): F[A] = {
+    def unsafeGet()(implicit F: QuasiPrimitives[F]): F[A] = {
       F.flatMap(resource.acquire)(resource.extract(_).fold(identity, F.pure))
     }
   }
@@ -530,10 +527,11 @@ object Lifecycle extends LifecycleInstances {
             Reservation(
               ZIO.effectSuspendTotal(resource.extract(r).fold(identity, ZIO.succeed(_))),
               _ =>
-                resource.release(r).orDieWith {
-                  case e: Throwable => e
-                  case any => new RuntimeException(s"Lifecycle finalizer: $any")
-                },
+                resource
+                  .release(r).orDieWith {
+                    case e: Throwable => e
+                    case any => new RuntimeException(s"Lifecycle finalizer: $any")
+                  },
             )
         )
       )
@@ -880,23 +878,16 @@ object Lifecycle extends LifecycleInstances {
     }
   }
 
-  @inline private final def flatMapImpl[F[_], A, B](self: Lifecycle[F, A])(f: A => Lifecycle[F, B])(implicit F: QuasiIO[F]): Lifecycle[F, B] = {
+  @inline private final def flatMapImpl[F[_], A, B](self: Lifecycle[F, A])(f: A => Lifecycle[F, B])(implicit F: QuasiPrimitives[F]): Lifecycle[F, B] = {
     import QuasiIO.syntax._
     new Lifecycle[F, B] {
-      override type InnerResource = AtomicReference[List[() => F[Unit]]]
+      override type InnerResource = QuasiRef[F, List[() => F[Unit]]]
 
       private[this] def bracketAppendFinalizer[a, b](finalizers: InnerResource)(lifecycle: Lifecycle[F, a])(use: lifecycle.InnerResource => F[b]): F[b] = {
         F.bracket(
           acquire = lifecycle.acquire.flatMap {
             a =>
-              F.maybeSuspend {
-                // can't use `.updateAndGet` because of Scala.js
-                var oldValue = finalizers.get()
-                while (!finalizers.compareAndSet(oldValue, (() => lifecycle.release(a)) :: oldValue)) {
-                  oldValue = finalizers.get()
-                }
-                a
-              }
+              finalizers.update((() => lifecycle.release(a)) :: _).map(_ => a)
           }
         )(release = _ => F.unit)(
           use = use
@@ -904,10 +895,10 @@ object Lifecycle extends LifecycleInstances {
       }
 
       override def acquire: F[InnerResource] = {
-        F.maybeSuspend(new AtomicReference(Nil))
+        F.mkRef(Nil)
       }
       override def release(finalizers: InnerResource): F[Unit] = {
-        F.suspendF(F.traverse_(finalizers.get())(_.apply()))
+        finalizers.get.flatMap(F.traverse_(_)(_.apply()))
       }
       override def extract[C >: B](finalizers: InnerResource): Either[F[C], C] = Left {
         bracketAppendFinalizer(finalizers)(self) {
@@ -926,7 +917,7 @@ object Lifecycle extends LifecycleInstances {
     }
   }
 
-  @inline private final def evalMapImpl[F[_], A, B](self: Lifecycle[F, A])(f: A => F[B])(implicit F: QuasiIO[F]): Lifecycle[F, B] = {
+  @inline private final def evalMapImpl[F[_], A, B](self: Lifecycle[F, A])(f: A => F[B])(implicit F: QuasiPrimitives[F]): Lifecycle[F, B] = {
     flatMapImpl(self)(a => Lifecycle.liftF(f(a)))
   }
 
@@ -1015,7 +1006,7 @@ private[definition] sealed trait LifecycleInstances extends LifecycleCatsInstanc
 
 private[definition] sealed trait LifecycleCatsInstances extends LifecycleCatsInstancesLowPriority {
   implicit final def catsMonadForLifecycle[Monad[_[_]]: `cats.Monad`, F[_]](
-    implicit F: QuasiIO[F]
+    implicit F: QuasiPrimitives[F]
   ): Monad[Lifecycle[F, _]] = {
     new cats.StackSafeMonad[Lifecycle[F, _]] {
       override def pure[A](x: A): Lifecycle[F, A] = Lifecycle.pure[F](x)
@@ -1025,7 +1016,7 @@ private[definition] sealed trait LifecycleCatsInstances extends LifecycleCatsIns
 
   implicit final def catsMonoidForLifecycle[Monoid[_]: `cats.kernel.Monoid`, F[_], A](
     implicit
-    F: QuasiIO[F],
+    F: QuasiPrimitives[F],
     A0: Monoid[A],
   ): Monoid[Lifecycle[F, A]] = {
     val A = A0.asInstanceOf[cats.Monoid[A]]
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 188ce24e1..7b05113d6 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
@@ -664,6 +664,7 @@ object ModuleDefDSL {
     * Please update this when adding new methods to [[MakeDSL]]!
     */
   private[distage] final lazy val MakeDSLNoOpMethodsWhitelist = Set(
+    "make",
     "named",
     "namedByImpl",
     "tagged",
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/QuasiIO.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/QuasiIO.scala
index c778003c4..7e8bf8437 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/QuasiIO.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/QuasiIO.scala
@@ -2,10 +2,12 @@ package izumi.distage.model.effect
 
 import cats.effect.kernel.Outcome
 import izumi.distage.model.effect.QuasiIO.QuasiIOIdentity
-import izumi.functional.bio.{Applicative2, Exit, Functor2, IO2}
+import izumi.functional.bio.data.Morphism1
+import izumi.functional.bio.{Applicative2, Exit, Functor2, IO2, TypedError}
 import izumi.fundamentals.orphans.{`cats.Applicative`, `cats.Functor`, `cats.effect.kernel.Sync`}
 import izumi.fundamentals.platform.functional.Identity
 
+import java.util.concurrent.atomic.AtomicReference
 import scala.annotation.tailrec
 import scala.language.implicitConversions
 import scala.util.{Failure, Success, Try}
@@ -28,21 +30,12 @@ import scala.util.{Failure, Success, Try}
   * @see [[izumi.distage.modules.DefaultModule]] - `DefaultModule` makes instances of `QuasiIO` for cats-effect, ZIO,
   *      monix, monix-bio, `Identity` and others available for summoning in your wiring automatically
   */
-trait QuasiIO[F[_]] extends QuasiApplicative[F] {
+trait QuasiIO[F[_]] extends QuasiPrimitives[F] {
   def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
 
-  def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = {
-    flatMap(f(a)) {
-      case Left(next) => tailRecM(next)(f)
-      case Right(res) => pure(res)
-    }
-  }
-
-  def guarantee[A](fa: => F[A])(`finally`: => F[Unit]): F[A] = bracket(acquire = unit)(release = _ => `finally`)(use = _ => fa)
   def guaranteeOnFailure[A](fa: => F[A])(cleanupOnFailure: Throwable => F[Unit]): F[A] = {
     bracketCase(acquire = unit)(release = (_, maybeExit) => maybeExit.fold(unit)(cleanupOnFailure))(use = _ => fa)
   }
-  def bracket[A, B](acquire: => F[A])(release: A => F[Unit])(use: A => F[B]): F[B]
   def bracketCase[A, B](acquire: => F[A])(release: (A, Option[Throwable]) => F[Unit])(use: A => F[B]): F[B]
   final def bracketAuto[A <: AutoCloseable, B](acquire: => F[A])(use: A => F[B]): F[B] = bracket(acquire)(a => maybeSuspend(a.close()))(use)
 
@@ -70,24 +63,12 @@ trait QuasiIO[F[_]] extends QuasiApplicative[F] {
 
   def fail[A](t: => Throwable): F[A]
 
-  def suspendF[A](effAction: => F[A]): F[A] = {
+  override def suspendF[A](effAction: => F[A]): F[A] = {
     flatMap(maybeSuspend(effAction))(identity)
   }
-  def traverse[A, B](l: Iterable[A])(f: A => F[B]): F[List[B]] = {
-    // All reasonable effect types will be stack-safe (not heap-safe!) on left-associative flatMaps so foldLeft is ok here.
-    // note: overriden in all default impls
-    l.foldLeft(pure(List.empty[B])) {
-      (acc, a) =>
-        flatMap(acc)(list => map(f(a))(r => list ++ List(r)))
-    }
-  }
-  def traverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit] = {
-    // All reasonable effect types will be stack-safe (not heap-safe!) on left-associative flatMaps so foldLeft is ok here.
-    // note: overriden in all default impls
-    l.foldLeft(unit) {
-      (acc, a) =>
-        flatMap(acc)(_ => f(a))
-    }
+
+  override def mkRef[A](a: A): F[QuasiRef[F, A]] = {
+    QuasiRef.fromMaybeSuspend(a)(Morphism1(f => maybeSuspend(f())))
   }
 }
 
@@ -98,12 +79,12 @@ object QuasiIO extends LowPriorityQuasiIOInstances {
     implicit def suspendedSyntax[F[_], A](fa: => F[A]): QuasiIOSuspendedSyntax[F, A] = new QuasiIOSuspendedSyntax(() => fa)
 
     implicit final class QuasiIOSyntax[F[_], A](private val fa: F[A]) extends AnyVal {
-      @inline def map[B](f: A => B)(implicit F: QuasiIO[F]): F[B] = F.map(fa)(f)
-      @inline def flatMap[B](f: A => F[B])(implicit F: QuasiIO[F]): F[B] = F.flatMap(fa)(f)
+      @inline def map[B](f: A => B)(implicit F: QuasiFunctor[F]): F[B] = F.map(fa)(f)
+      @inline def flatMap[B](f: A => F[B])(implicit F: QuasiPrimitives[F]): F[B] = F.flatMap(fa)(f)
     }
 
     final class QuasiIOSuspendedSyntax[F[_], A](private val fa: () => F[A]) extends AnyVal {
-      @inline def guarantee(`finally`: => F[Unit])(implicit F: QuasiIO[F]): F[A] = {
+      @inline def guarantee(`finally`: => F[Unit])(implicit F: QuasiPrimitives[F]): F[A] = {
         F.guarantee(fa())(`finally`)
       }
       @inline def guaranteeOnFailure(cleanupOnFailure: Throwable => F[Unit])(implicit F: QuasiIO[F]): F[A] = {
@@ -114,7 +95,7 @@ object QuasiIO extends LowPriorityQuasiIOInstances {
 
   @inline implicit def quasiIOIdentity: QuasiIO[Identity] = QuasiIOIdentity
 
-  object QuasiIOIdentity extends QuasiIO[Identity] {
+  private[effect] object QuasiIOIdentity extends QuasiIO[Identity] {
     override def pure[A](a: A): Identity[A] = a
     override def map[A, B](fa: Identity[A])(f: A => B): Identity[B] = f(fa)
     override def map2[A, B, C](fa: Identity[A], fb: => Identity[B])(f: (A, B) => C): Identity[C] = f(fa, fb)
@@ -181,33 +162,30 @@ object QuasiIO extends LowPriorityQuasiIOInstances {
     @inline private[this] def TryNonFatal[A](r: => A): Try[A] = Try(r)
   }
 
+}
+
+private[effect] sealed trait LowPriorityQuasiIOInstances extends LowPriorityQuasiIOInstances1 {
+
   implicit def fromBIO[F[+_, +_]](implicit F: IO2[F]): QuasiIO[F[Throwable, _]] = {
     type E = Throwable
-    new QuasiIO[F[Throwable, _]] {
-      override def pure[A](a: A): F[E, A] = F.pure(a)
-      override def map[A, B](fa: F[E, A])(f: A => B): F[E, B] = F.map(fa)(f)
-      override def map2[A, B, C](fa: F[E, A], fb: => F[E, B])(f: (A, B) => C): F[E, C] = F.map2(fa, fb)(f)
-      override def flatMap[A, B](fa: F[E, A])(f: A => F[E, B]): F[E, B] = F.flatMap(fa)(f)
-      override def tailRecM[A, B](a: A)(f: A => F[Throwable, Either[A, B]]): F[Throwable, B] = F.tailRecM(a)(f)
+    new QuasiPrimitivesFromBIO[F, Throwable] with QuasiIO[F[Throwable, _]] {
+      override final def suspendF[A](effAction: => F[E, A]): F[E, A] = super[QuasiPrimitivesFromBIO].suspendF(effAction)
+      override final def mkRef[A](a: A): F[E, QuasiRef[F[E, _], A]] = super[QuasiPrimitivesFromBIO].mkRef(a)
 
       override def maybeSuspend[A](eff: => A): F[E, A] = F.syncThrowable(eff)
-      override def maybeSuspendEither[A](eff: => Either[Throwable, A]): F[Throwable, A] = F.syncThrowable(eff).flatMap(F.fromEither(_))
-      override def suspendF[A](effAction: => F[Throwable, A]): F[Throwable, A] = F.suspend(effAction)
-      override def definitelyRecover[A](action: => F[E, A])(recover: Throwable => F[E, A]): F[E, A] = {
+      override def maybeSuspendEither[A](eff: => Either[E, A]): F[E, A] = F.syncThrowable(eff).flatMap(F.fromEither(_))
+      override def definitelyRecover[A](action: => F[E, A])(recover: E => F[E, A]): F[E, A] = {
         F.suspend(action).sandbox.catchAll(recover apply _.toThrowable)
       }
-      override def definitelyRecoverCause[A](action: => F[Throwable, A])(recover: (Throwable, () => Throwable) => F[Throwable, A]): F[Throwable, A] = {
+      override def definitelyRecoverCause[A](action: => F[E, A])(recover: (E, () => E) => F[E, A]): F[E, A] = {
         F.suspend(action).sandbox.catchAll(e => recover(e.toThrowable, () => e.trace.unsafeAttachTrace(identity)))
       }
-      override def redeem[A, B](action: => F[Throwable, A])(failure: Throwable => F[Throwable, B], success: A => F[Throwable, B]): F[Throwable, B] = {
+      override def redeem[A, B](action: => F[E, A])(failure: E => F[E, B], success: A => F[E, B]): F[E, B] = {
         action.redeem(failure, success)
       }
-      override def fail[A](t: => Throwable): F[Throwable, A] = F.fail(t)
-      override def bracket[A, B](acquire: => F[E, A])(release: A => F[E, Unit])(use: A => F[E, B]): F[E, B] = {
-        F.bracket(acquire = F.suspend(acquire))(release = release(_).orTerminate)(use = use)
-      }
+      override def fail[A](t: => E): F[E, A] = F.fail(t)
       override def bracketCase[A, B](acquire: => F[E, A])(release: (A, Option[E]) => F[E, Unit])(use: A => F[E, B]): F[E, B] = {
-        F.bracketCase[Any, Throwable, A, B](acquire = F.suspend(acquire))(release = {
+        F.bracketCase[Any, E, A, B](acquire = F.suspend(acquire))(release = {
           case (a, exit) =>
             exit match {
               case Exit.Success(_) => release(a, None).orTerminate
@@ -215,19 +193,15 @@ object QuasiIO extends LowPriorityQuasiIOInstances {
             }
         })(use = use)
       }
-      override def guarantee[A](fa: => F[Throwable, A])(`finally`: => F[Throwable, Unit]): F[Throwable, A] = {
-        F.guarantee(F.suspend(fa), F.suspend(`finally`).orTerminate)
-      }
-      override def guaranteeOnFailure[A](fa: => F[Throwable, A])(cleanupOnFailure: Throwable => F[Throwable, Unit]): F[Throwable, A] = {
+      override def guaranteeOnFailure[A](fa: => F[E, A])(cleanupOnFailure: E => F[E, Unit]): F[E, A] = {
         F.guaranteeOnFailure(F.suspend(fa), (e: Exit.Failure[E]) => cleanupOnFailure(e.toThrowable).orTerminate)
       }
-      override def traverse[A, B](l: Iterable[A])(f: A => F[E, B]): F[E, List[B]] = F.traverse(l)(f)
-      override def traverse_[A](l: Iterable[A])(f: A => F[E, Unit]): F[E, Unit] = F.traverse_(l)(f)
     }
   }
+
 }
 
-private[effect] sealed trait LowPriorityQuasiIOInstances {
+private[effect] sealed trait LowPriorityQuasiIOInstances1 {
 
   /**
     * This instance uses 'no more orphans' trick to provide an Optional instance
@@ -237,16 +211,12 @@ private[effect] sealed trait LowPriorityQuasiIOInstances {
     */
   implicit def fromCats[F[_], Sync[_[_]]: `cats.effect.kernel.Sync`](implicit F0: Sync[F]): QuasiIO[F] = {
     val F = F0.asInstanceOf[cats.effect.kernel.Sync[F]]
-    new QuasiIO[F] {
-      override def pure[A](a: A): F[A] = F.pure(a)
-      override def map[A, B](fa: F[A])(f: A => B): F[B] = F.map(fa)(f)
-      override def map2[A, B, C](fa: F[A], fb: => F[B])(f: (A, B) => C): F[C] = F.flatMap(fa)(a => F.map(fb)(f(a, _)))
-      override def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = F.flatMap(fa)(f)
-      override def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = F.tailRecM(a)(f)
+    new QuasiPrimitivesFromCats[F](F) with QuasiIO[F] {
+      override final def suspendF[A](effAction: => F[A]): F[A] = super[QuasiPrimitivesFromCats].suspendF(effAction)
+      override final def mkRef[A](a: A): F[QuasiRef[F, A]] = super[QuasiPrimitivesFromCats].mkRef(a)
 
       override def maybeSuspend[A](eff: => A): F[A] = F.delay(eff)
       override def maybeSuspendEither[A](eff: => Either[Throwable, A]): F[A] = F.defer(F.fromEither(eff))
-      override def suspendF[A](effAction: => F[A]): F[A] = F.defer(effAction)
       override def definitelyRecover[A](action: => F[A])(recover: Throwable => F[A]): F[A] = {
         F.handleErrorWith(F.defer(action))(recover)
       }
@@ -257,9 +227,6 @@ private[effect] sealed trait LowPriorityQuasiIOInstances {
         F.redeemWith(action)(failure, success)
       }
       override def fail[A](t: => Throwable): F[A] = F.defer(F.raiseError(t))
-      override def bracket[A, B](acquire: => F[A])(release: A => F[Unit])(use: A => F[B]): F[B] = {
-        F.bracket(acquire = F.defer(acquire))(use = use)(release = release)
-      }
       override def bracketCase[A, B](acquire: => F[A])(release: (A, Option[Throwable]) => F[Unit])(use: A => F[B]): F[B] = {
         F.bracketCase(acquire = F.defer(acquire))(use = use)(release = {
           case (a, exitCase) =>
@@ -270,9 +237,6 @@ private[effect] sealed trait LowPriorityQuasiIOInstances {
             }
         })
       }
-      override def guarantee[A](fa: => F[A])(`finally`: => F[Unit]): F[A] = {
-        F.guarantee(F.defer(fa), F.defer(`finally`))
-      }
       override def guaranteeOnFailure[A](fa: => F[A])(cleanupOnFailure: Throwable => F[Unit]): F[A] = {
         F.guaranteeCase(F.defer(fa)) {
           case Outcome.Succeeded(_) => F.unit
@@ -280,13 +244,134 @@ private[effect] sealed trait LowPriorityQuasiIOInstances {
           case Outcome.Canceled() => cleanupOnFailure(new InterruptedException("cats.effect.kernel.Outcome.Canceled()"))
         }
       }
-      override def traverse[A, B](l: Iterable[A])(f: A => F[B]): F[List[B]] = cats.instances.list.catsStdInstancesForList.traverse(l.toList)(f)(F)
-      override def traverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit] = cats.instances.list.catsStdInstancesForList.traverse_(l.toList)(f)(F)
     }
   }
 
 }
 
+/**
+  * Evidence that `F` supports a subset of [[QuasiIO]] capabilities - state, non-effectful not-guaranteed suspension,
+  * and setting finalizers that can't inspect the error.
+  *
+  * Internal use class, as with [[QuasiIO]], it's only public so that you can define your own instances,
+  * better use [[izumi.functional.bio]] or [[cats]] typeclasses for application logic.
+  */
+trait QuasiPrimitives[F[_]] extends QuasiApplicative[F] {
+  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
+
+  def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = {
+    flatMap(f(a)) {
+      case Left(next) => tailRecM(next)(f)
+      case Right(res) => pure(res)
+    }
+  }
+
+  def bracket[A, B](acquire: => F[A])(release: A => F[Unit])(use: A => F[B]): F[B]
+  def guarantee[A](fa: => F[A])(`finally`: => F[Unit]): F[A] = bracket(acquire = unit)(release = _ => `finally`)(use = _ => fa)
+
+  def mkRef[A](a: A): F[QuasiRef[F, A]]
+
+  def suspendF[A](effAction: => F[A]): F[A]
+
+  def traverse[A, B](l: Iterable[A])(f: A => F[B]): F[List[B]] = {
+    // All reasonable effect types will be stack-safe (not heap-safe!) on left-associative flatMaps so foldLeft is ok here.
+    // note: overriden in all default impls
+    l.foldLeft(pure(List.empty[B])) {
+      (acc, a) =>
+        flatMap(acc)(list => map(f(a))(r => list ++ List(r)))
+    }
+  }
+  def traverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit] = {
+    // All reasonable effect types will be stack-safe (not heap-safe!) on left-associative flatMaps so foldLeft is ok here.
+    // note: overriden in all default impls
+    l.foldLeft(unit) {
+      (acc, a) =>
+        flatMap(acc)(_ => f(a))
+    }
+  }
+}
+
+object QuasiPrimitives extends LowPriorityQuasiPrimitivesInstances {
+  @inline def apply[F[_]: QuasiPrimitives]: QuasiPrimitives[F] = implicitly
+
+  @inline implicit def quasiPrimitivesIdentity: QuasiPrimitives[Identity] = QuasiIOIdentity
+}
+
+private[effect] sealed trait LowPriorityQuasiPrimitivesInstances extends LowPriorityQuasiPrimitivesInstances1 {
+  implicit def fromBIO[F[+_, +_], E](implicit F: IO2[F]): QuasiPrimitives[F[E, _]] = new QuasiPrimitivesFromBIO[F, E]
+}
+
+private[effect] sealed trait LowPriorityQuasiPrimitivesInstances1 {
+
+  /**
+    * This instance uses 'no more orphans' trick to provide an Optional instance
+    * only IFF you have cats-effect as a dependency without REQUIRING a cats-effect dependency.
+    *
+    * Optional instance via https://blog.7mind.io/no-more-orphans.html
+    */
+  implicit def fromCats[F[_], Sync[_[_]]: `cats.effect.kernel.Sync`](implicit F0: Sync[F]): QuasiPrimitives[F] = {
+    new QuasiPrimitivesFromCats(F0.asInstanceOf[cats.effect.kernel.Sync[F]])
+  }
+
+}
+
+private[effect] sealed class QuasiPrimitivesFromBIO[F[+_, +_], E](implicit F: IO2[F]) extends QuasiPrimitives[F[E, _]] {
+  override def suspendF[A](f: => F[E, A]): F[E, A] = F.sync(f).flatten
+
+  override final def pure[A](a: A): F[E, A] = F.pure(a)
+  override final def map[A, B](fa: F[E, A])(f: A => B): F[E, B] = F.map(fa)(f)
+  override final def map2[A, B, C](fa: F[E, A], fb: => F[E, B])(f: (A, B) => C): F[E, C] = F.map2(fa, fb)(f)
+  override final def flatMap[A, B](fa: F[E, A])(f: A => F[E, B]): F[E, B] = F.flatMap(fa)(f)
+  override final def tailRecM[A, B](a: A)(f: A => F[E, Either[A, B]]): F[E, B] = F.tailRecM(a)(f)
+
+  override final def bracket[A, B](acquire: => F[E, A])(release: A => F[E, Unit])(use: A => F[E, B]): F[E, B] = {
+    F.bracket(acquire = suspendF(acquire))(release = release(_).catchAll {
+      case e: Throwable => F.terminate(e)
+      case e => F.terminate(TypedError(e))
+    })(use = use)
+  }
+  override final def guarantee[A](fa: => F[E, A])(`finally`: => F[E, Unit]): F[E, A] = {
+    F.guarantee(
+      suspendF(fa),
+      suspendF(`finally`).catchAll {
+        case e: Throwable => F.terminate(e)
+        case e => F.terminate(TypedError(e))
+      },
+    )
+  }
+
+  override def mkRef[A](a: A): F[E, QuasiRef[F[E, _], A]] = {
+    QuasiRef.fromMaybeSuspend(a)(Morphism1(f => F.sync(f())))
+  }
+
+  override final def traverse[A, B](l: Iterable[A])(f: A => F[E, B]): F[E, List[B]] = F.traverse(l)(f)
+  override final def traverse_[A](l: Iterable[A])(f: A => F[E, Unit]): F[E, Unit] = F.traverse_(l)(f)
+}
+
+private[effect] sealed class QuasiPrimitivesFromCats[F[_]](F: cats.effect.kernel.Sync[F]) extends QuasiPrimitives[F] {
+  override def suspendF[A](effAction: => F[A]): F[A] = F.defer(effAction)
+
+  override final def pure[A](a: A): F[A] = F.pure(a)
+  override final def map[A, B](fa: F[A])(f: A => B): F[B] = F.map(fa)(f)
+  override final def map2[A, B, C](fa: F[A], fb: => F[B])(f: (A, B) => C): F[C] = F.flatMap(fa)(a => F.map(fb)(f(a, _)))
+  override final def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = F.flatMap(fa)(f)
+  override final def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = F.tailRecM(a)(f)
+
+  override final def bracket[A, B](acquire: => F[A])(release: A => F[Unit])(use: A => F[B]): F[B] = {
+    F.bracket(acquire = F.defer(acquire))(use = use)(release = release)
+  }
+  override final def guarantee[A](fa: => F[A])(`finally`: => F[Unit]): F[A] = {
+    F.guarantee(F.defer(fa), F.defer(`finally`))
+  }
+
+  override def mkRef[A](a: A): F[QuasiRef[F, A]] = {
+    QuasiRef.fromMaybeSuspend(a)(Morphism1(f => F.delay(f())))
+  }
+
+  override final def traverse[A, B](l: Iterable[A])(f: A => F[B]): F[List[B]] = cats.instances.list.catsStdInstancesForList.traverse(l.toList)(f)(F)
+  override final def traverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit] = cats.instances.list.catsStdInstancesForList.traverse_(l.toList)(f)(F)
+}
+
 /**
   * An `Applicative` capability for `F`. Unlike `QuasiIO` there's nothing "quasi" about it – it makes sense. But named like that for consistency anyway.
   *
@@ -311,7 +396,9 @@ object QuasiApplicative extends LowPriorityQuasiApplicativeInstances {
   @inline def apply[F[_]: QuasiApplicative]: QuasiApplicative[F] = implicitly
 
   @inline implicit def quasiApplicativeIdentity: QuasiApplicative[Identity] = QuasiIOIdentity
+}
 
+private[effect] sealed trait LowPriorityQuasiApplicativeInstances extends LowPriorityQuasiApplicativeInstances1 {
   implicit def fromBIO[F[+_, +_], E](implicit F: Applicative2[F]): QuasiApplicative[F[E, _]] = {
     new QuasiApplicative[F[E, _]] {
       override def pure[A](a: A): F[E, A] = F.pure(a)
@@ -323,7 +410,7 @@ object QuasiApplicative extends LowPriorityQuasiApplicativeInstances {
   }
 }
 
-trait LowPriorityQuasiApplicativeInstances {
+private[effect] sealed trait LowPriorityQuasiApplicativeInstances1 {
   /**
     * This instance uses 'no more orphans' trick to provide an Optional instance
     * only IFF you have cats-core as a dependency without REQUIRING a cats-core dependency.
@@ -357,7 +444,9 @@ object QuasiFunctor extends LowPriorityQuasiFunctorInstances {
   @inline def apply[F[_]: QuasiFunctor]: QuasiFunctor[F] = implicitly
 
   @inline implicit def quasiFunctorIdentity: QuasiFunctor[Identity] = QuasiIOIdentity
+}
 
+private[effect] sealed trait LowPriorityQuasiFunctorInstances extends LowPriorityQuasiFunctorInstances1 {
   implicit def fromBIO[F[+_, +_], E](implicit F: Functor2[F]): QuasiFunctor[F[E, _]] = {
     new QuasiFunctor[F[E, _]] {
       override def map[A, B](fa: F[E, A])(f: A => B): F[E, B] = F.map(fa)(f)
@@ -365,7 +454,7 @@ object QuasiFunctor extends LowPriorityQuasiFunctorInstances {
   }
 }
 
-trait LowPriorityQuasiFunctorInstances {
+private[effect] sealed trait LowPriorityQuasiFunctorInstances1 {
   /**
     * This instance uses 'no more orphans' trick to provide an Optional instance
     * only IFF you have cats-core as a dependency without REQUIRING a cats-core dependency.
@@ -379,3 +468,33 @@ trait LowPriorityQuasiFunctorInstances {
     }
   }
 }
+
+trait QuasiRef[F[_], A] {
+  def get: F[A]
+  def set(a: A): F[Unit]
+  def update(f: A => A): F[Unit]
+}
+
+object QuasiRef {
+  def mk[F[_], A](a: A)(implicit F: QuasiPrimitives[F]): F[QuasiRef[F, A]] = F.mkRef(a)
+
+  def fromMaybeSuspend[F[_], A](a: A)(maybeSuspend: Morphism1[() => _, F]): F[QuasiRef[F, A]] = {
+    maybeSuspend {
+      () =>
+        val ref = new AtomicReference[A](a)
+        new QuasiRef[F, A] {
+          override def get: F[A] = maybeSuspend(() => ref.get())
+          override def set(a: A): F[Unit] = maybeSuspend(() => ref.set(a))
+          override def update(f: A => A): F[Unit] = {
+            maybeSuspend {
+              () =>
+                var oldValue = ref.get()
+                while (!ref.compareAndSet(oldValue, f(oldValue))) {
+                  oldValue = ref.get()
+                }
+            }
+          }
+        }
+    }
+  }
+}
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/package.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/package.scala
index 43581abdc..461ccf665 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/package.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/package.scala
@@ -1,12 +1,18 @@
 package izumi.distage.model
 
 package object effect {
-  type QuasiIO2[F[_, _]] = QuasiIO[F[Throwable, _]]
-  type QuasiIO3[F[_, _, _]] = QuasiIO[F[Any, Throwable, _]]
+  type QuasiFunctor2[F[_, _]] = QuasiFunctor[F[Throwable, _]]
+  type QuasiFunctor3[F[_, _, _]] = QuasiFunctor[F[Any, Throwable, _]]
 
   type QuasiApplicative2[F[_, _]] = QuasiApplicative[F[Throwable, _]]
   type QuasiApplicative3[F[_, _, _]] = QuasiApplicative[F[Any, Throwable, _]]
 
+  type QuasiPrimitives2[F[_, _]] = QuasiPrimitives[F[Throwable, _]]
+  type QuasiPrimitives3[F[_, _, _]] = QuasiPrimitives[F[Any, Throwable, _]]
+
+  type QuasiIO2[F[_, _]] = QuasiIO[F[Throwable, _]]
+  type QuasiIO3[F[_, _, _]] = QuasiIO[F[Any, Throwable, _]]
+
   type QuasiAsync2[F[_, _]] = QuasiAsync[F[Throwable, _]]
   type QuasiAsync3[F[_, _, _]] = QuasiAsync[F[Any, Throwable, _]]
 
diff --git a/distage/distage-core/.js/src/main/scala/izumi/distage/modules/support/IdentitySupportModule.scala b/distage/distage-core/.js/src/main/scala/izumi/distage/modules/support/IdentitySupportModule.scala
index ebe2bf859..d6073154a 100644
--- a/distage/distage-core/.js/src/main/scala/izumi/distage/modules/support/IdentitySupportModule.scala
+++ b/distage/distage-core/.js/src/main/scala/izumi/distage/modules/support/IdentitySupportModule.scala
@@ -1,7 +1,7 @@
 package izumi.distage.modules.support
 
 import izumi.distage.model.definition.ModuleDef
-import izumi.distage.model.effect.{QuasiApplicative, QuasiIO, QuasiIORunner}
+import izumi.distage.model.effect.*
 import izumi.functional.mono.{Clock, Entropy}
 import izumi.fundamentals.platform.functional.Identity
 
@@ -12,7 +12,9 @@ object IdentitySupportModule extends IdentitySupportModule
   * Adds [[izumi.distage.model.effect.QuasiIO]] instances to support running without an effect type in `Injector`, `distage-framework` & `distage-testkit-scalatest`
   */
 trait IdentitySupportModule extends ModuleDef {
+  addImplicit[QuasiFunctor[Identity]]
   addImplicit[QuasiApplicative[Identity]]
+  addImplicit[QuasiPrimitives[Identity]]
   addImplicit[QuasiIO[Identity]]
   addImplicit[QuasiIORunner[Identity]]
 //  addImplicit[QuasiAsync[Identity]] // No QuasiAsync for Identity on JS
diff --git a/distage/distage-core/.jvm/src/main/scala/izumi/distage/modules/support/IdentitySupportModule.scala b/distage/distage-core/.jvm/src/main/scala/izumi/distage/modules/support/IdentitySupportModule.scala
index 48f198cad..15336d065 100644
--- a/distage/distage-core/.jvm/src/main/scala/izumi/distage/modules/support/IdentitySupportModule.scala
+++ b/distage/distage-core/.jvm/src/main/scala/izumi/distage/modules/support/IdentitySupportModule.scala
@@ -1,7 +1,7 @@
 package izumi.distage.modules.support
 
 import izumi.distage.model.definition.ModuleDef
-import izumi.distage.model.effect.{QuasiApplicative, QuasiAsync, QuasiIO, QuasiIORunner}
+import izumi.distage.model.effect.*
 import izumi.functional.bio.{Clock1, Entropy1}
 import izumi.fundamentals.platform.functional.Identity
 
@@ -13,7 +13,9 @@ object IdentitySupportModule extends IdentitySupportModule
   * Adds [[izumi.distage.model.effect.QuasiIO]] instances to support running without an effect type in `Injector`, `distage-framework` & `distage-testkit-scalatest`
   */
 trait IdentitySupportModule extends ModuleDef {
+  addImplicit[QuasiFunctor[Identity]]
   addImplicit[QuasiApplicative[Identity]]
+  addImplicit[QuasiPrimitives[Identity]]
   addImplicit[QuasiIO[Identity]]
   addImplicit[QuasiIORunner[Identity]]
   addImplicit[QuasiAsync[Identity]]
diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/DefaultModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/DefaultModule.scala
index b10d6763d..b46182850 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/modules/DefaultModule.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/modules/DefaultModule.scala
@@ -1,13 +1,14 @@
 package izumi.distage.modules
 
 import izumi.distage.model.definition.{Module, ModuleDef}
-import izumi.distage.model.effect.{QuasiApplicative, QuasiAsync, QuasiIO, QuasiIORunner}
-import izumi.distage.modules.support._
+import izumi.distage.model.effect.{QuasiApplicative, QuasiAsync, QuasiFunctor, QuasiIO, QuasiIORunner, QuasiPrimitives}
+import izumi.distage.modules.support.*
 import izumi.distage.modules.typeclass.ZIOCatsEffectInstancesModule
 import izumi.functional.bio.retry.{Scheduler2, Scheduler3}
 import izumi.functional.bio.{Async2, Async3, Fork2, Fork3, Local3, Primitives2, Primitives3, Temporal2, Temporal3, UnsafeRun2, UnsafeRun3}
-import izumi.fundamentals.orphans._
+import izumi.fundamentals.orphans.*
 import izumi.fundamentals.platform.functional.Identity
+
 import scala.annotation.unused
 import izumi.reflect.{TagK, TagK3, TagKK}
 
@@ -156,9 +157,11 @@ sealed trait LowPriorityDefaultModulesInstances5 extends LowPriorityDefaultModul
 sealed trait LowPriorityDefaultModulesInstances6 {
   implicit final def fromQuasiIO[F[_]: TagK: QuasiIO: QuasiAsync: QuasiIORunner]: DefaultModule[F] = {
     DefaultModule(new ModuleDef {
+      addImplicit[QuasiFunctor[F]]
+      addImplicit[QuasiApplicative[F]]
+      addImplicit[QuasiPrimitives[F]]
       addImplicit[QuasiIO[F]]
       addImplicit[QuasiAsync[F]]
-      addImplicit[QuasiApplicative[F]]
       addImplicit[QuasiIORunner[F]]
     })
   }
diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIO2SupportModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIO2SupportModule.scala
index a0c092798..d65b85411 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIO2SupportModule.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIO2SupportModule.scala
@@ -4,7 +4,7 @@ import izumi.distage.model.definition.ModuleDef
 import izumi.distage.model.effect.*
 import izumi.distage.modules.typeclass.BIO2InstancesModule
 import izumi.functional.bio.retry.Scheduler2
-import izumi.functional.bio.{Applicative2, Async2, Clock1, Clock2, Entropy1, Entropy2, Fork2, IO2, Primitives2, PrimitivesM2, SyncSafe1, SyncSafe2, Temporal2, UnsafeRun2}
+import izumi.functional.bio.{Async2, Clock1, Clock2, Entropy1, Entropy2, Fork2, IO2, Primitives2, PrimitivesM2, SyncSafe1, SyncSafe2, Temporal2, UnsafeRun2}
 import izumi.fundamentals.platform.functional.Identity
 import izumi.reflect.TagKK
 
@@ -24,12 +24,13 @@ class AnyBIO2SupportModule[F[+_, +_]: TagKK] extends ModuleDef {
   make[QuasiIORunner2[F]]
     .from[QuasiIORunner.BIOImpl[F]]
 
-  make[QuasiIO2[F]].from {
-    QuasiIO.fromBIO(_: IO2[F])
-  }
-  make[QuasiApplicative2[F]].from {
-    QuasiApplicative.fromBIO[F, Throwable](_: Applicative2[F])
-  }
+  make[QuasiIO2[F]]
+    .aliased[QuasiPrimitives2[F]]
+    .aliased[QuasiApplicative2[F]]
+    .aliased[QuasiFunctor2[F]]
+    .from {
+      QuasiIO.fromBIO(_: IO2[F])
+    }
   make[QuasiAsync2[F]].from {
     QuasiAsync.fromBIO(_: Async2[F], _: Temporal2[F])
   }
diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIO3SupportModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIO3SupportModule.scala
index 1b2b22043..76e2bfd09 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIO3SupportModule.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIO3SupportModule.scala
@@ -76,8 +76,10 @@ object AnyBIO3SupportModule extends App with ModuleDef {
         implicitly[PrimitivesM3[F] =:= PrimitivesM2[F[Any, +_, +_]]]
         implicitly[SyncSafe3[F] =:= SyncSafe2[F[Any, +_, +_]]]
         implicitly[QuasiIORunner3[F] =:= QuasiIORunner2[F[Any, +_, +_]]]
-        implicitly[QuasiIO3[F] =:= QuasiIO2[F[Any, +_, +_]]]
+        implicitly[QuasiFunctor3[F] =:= QuasiFunctor2[F[Any, +_, +_]]]
         implicitly[QuasiApplicative3[F] =:= QuasiApplicative2[F[Any, +_, +_]]]
+        implicitly[QuasiPrimitives3[F] =:= QuasiPrimitives2[F[Any, +_, +_]]]
+        implicitly[QuasiIO3[F] =:= QuasiIO2[F[Any, +_, +_]]]
         implicitly[QuasiAsync3[F] =:= QuasiAsync2[F[Any, +_, +_]]]
         ()
       }
diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyCatsEffectSupportModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyCatsEffectSupportModule.scala
index 9e0fe0895..8f8ea57cf 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyCatsEffectSupportModule.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyCatsEffectSupportModule.scala
@@ -2,9 +2,9 @@ package izumi.distage.modules.support
 
 import cats.effect.kernel.{Async, Sync}
 import cats.effect.std.Dispatcher
-import cats.{Applicative, Parallel}
+import cats.Parallel
 import distage.{ModuleDef, TagK}
-import izumi.distage.model.effect.{QuasiApplicative, QuasiAsync, QuasiIO, QuasiIORunner}
+import izumi.distage.model.effect.{QuasiApplicative, QuasiAsync, QuasiFunctor, QuasiIO, QuasiIORunner, QuasiPrimitives}
 import izumi.distage.modules.typeclass.CatsEffectInstancesModule
 import izumi.functional.bio.{Clock1, Entropy1, SyncSafe1}
 import izumi.fundamentals.platform.functional.Identity
@@ -22,15 +22,16 @@ import izumi.fundamentals.platform.functional.Identity
 class AnyCatsEffectSupportModule[F[_]: TagK] extends ModuleDef {
   include(CatsEffectInstancesModule[F])
 
-  make[QuasiIO[F]].from {
-    implicit F: Sync[F] => QuasiIO.fromCats
-  }
+  make[QuasiIO[F]]
+    .aliased[QuasiPrimitives[F]]
+    .aliased[QuasiApplicative[F]]
+    .aliased[QuasiFunctor[F]]
+    .from {
+      implicit F: Sync[F] => QuasiIO.fromCats
+    }
   make[QuasiAsync[F]].from {
     implicit F: Async[F] => QuasiAsync.fromCats
   }
-  make[QuasiApplicative[F]].from {
-    implicit F: Applicative[F] => QuasiApplicative.fromCats
-  }
   make[SyncSafe1[F]].from {
     implicit F: Sync[F] => SyncSafe1.fromSync
   }
diff --git a/fundamentals/fundamentals-bio/src/main/scala-3/izumi/functional/bio/Convert3To2VersionSpecific.scala b/fundamentals/fundamentals-bio/src/main/scala-3/izumi/functional/bio/Convert3To2VersionSpecific.scala
index a777cad8b..104ff542c 100644
--- a/fundamentals/fundamentals-bio/src/main/scala-3/izumi/functional/bio/Convert3To2VersionSpecific.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala-3/izumi/functional/bio/Convert3To2VersionSpecific.scala
@@ -18,8 +18,8 @@ trait BlockingIOLowPriorityVersionSpecific {
     implicit BlockingIO3: C[FR] {
       type Divergence = Nondivergent; type IsPredefined = T // `IsPredefined = T` is required only on Scala 3
     }
-  ): Divergent.Of[BlockingIO2[FR[R, + _, + _]]] { type IsPredefined = T } = {
-    BlockingIO3.asInstanceOf[Divergent.Of[BlockingIO2[FR[R, + _, + _]]] { type IsPredefined = T }]
+  ): Divergent.Of[BlockingIO2[FR[R, +_, +_]]] { type IsPredefined = T } = {
+    BlockingIO3.asInstanceOf[Divergent.Of[BlockingIO2[FR[R, +_, +_]]] { type IsPredefined = T }]
   }
 
 }
diff --git a/fundamentals/fundamentals-language/src/main/scala-3/izumi/fundamentals/platform/language/CodePositionMaterializer.scala b/fundamentals/fundamentals-language/src/main/scala-3/izumi/fundamentals/platform/language/CodePositionMaterializer.scala
index 5f53844c9..2df004a2e 100644
--- a/fundamentals/fundamentals-language/src/main/scala-3/izumi/fundamentals/platform/language/CodePositionMaterializer.scala
+++ b/fundamentals/fundamentals-language/src/main/scala-3/izumi/fundamentals/platform/language/CodePositionMaterializer.scala
@@ -4,19 +4,17 @@ import scala.annotation.tailrec
 import scala.collection.mutable
 import scala.quoted.{Expr, Quotes, Type}
 
-
 final case class CodePositionMaterializer(get: CodePosition) extends AnyVal
 
 object CodePositionMaterializer {
   inline def apply()(implicit ev: CodePositionMaterializer, dummy: DummyImplicit): CodePositionMaterializer = ev
   inline def codePosition(implicit ev: CodePositionMaterializer): CodePosition = ev.get
-  inline def applicationPointId: String = ${ doapplicationPointId }
 
   inline implicit def materialize: CodePositionMaterializer = ${ doMaterialize }
+  inline def materializeApplicationPointId: String = ${ doApplicationPointId }
 
   private def doMaterialize(using Quotes): Expr[CodePositionMaterializer] = new CodePositionMaterializerMacro().getEnclosingPositionMat()
-  private def doapplicationPointId(using Quotes): Expr[String] = new CodePositionMaterializerMacro().getApplicationPointId()
-
+  private def doApplicationPointId(using Quotes): Expr[String] = new CodePositionMaterializerMacro().getApplicationPointId()
 
   private final class CodePositionMaterializerMacro(using val qctx: Quotes) {
     import qctx.reflect._
@@ -39,16 +37,17 @@ object CodePositionMaterializer {
 
     def getApplicationPointId(): Expr[String] = {
       val st = ownershipChain()
-      val applicationId = st.tail.flatMap {
-        case s if s.isPackageDef =>
-          Some(s.name)
-        case s if s.isValDef =>
-          None
-        case s if goodSymbol(s) =>
-          Some(s.name)
-        case s =>
-          None
-      }
+      val applicationId = st.tail
+        .flatMap {
+          case s if s.isPackageDef =>
+            Some(s.name)
+          case s if s.isValDef =>
+            None
+          case s if goodSymbol(s) =>
+            Some(s.name)
+          case s =>
+            None
+        }
         .map(_.toString.trim)
         .mkString(".")
 
@@ -59,12 +58,12 @@ object CodePositionMaterializer {
       val sourcePos = new SourceFilePositionMaterializer.SourceFilePositionMaterializerMacro().getSourceFilePosition()
       val applicationId = getApplicationPointId()
 
-      '{ CodePosition( ${sourcePos}, ${applicationId}) }
+      '{ CodePosition(${ sourcePos }, ${ applicationId }) }
     }
 
     def getEnclosingPositionMat(): Expr[CodePositionMaterializer] = {
       val pos = getEnclosingPosition()
-      '{ CodePositionMaterializer( ${ pos } )}
+      '{ CodePositionMaterializer(${ pos }) }
     }
 
     private def goodSymbol(s: Symbol): Boolean = {
diff --git a/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroLogger.scala b/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroLogger.scala
index 4aadc7766..06e76b452 100644
--- a/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroLogger.scala
+++ b/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroLogger.scala
@@ -23,7 +23,7 @@ trait AbstractMacroLogger { this: AbstractLogger =>
   transparent inline final def crit(inline message: String): Unit = log(Log.Level.Crit, message)
 
   transparent inline final def log(inline level: Log.Level, inline message: String): Unit = {
-    if (acceptable(Log.LoggerId(CodePositionMaterializer.applicationPointId), level)) {
+    if (acceptable(Log.LoggerId(CodePositionMaterializer.materializeApplicationPointId), level)) {
       
       unsafeLog(Log.Entry.create(level, Message(message))(CodePositionMaterializer.materialize))
     }
diff --git a/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroRawLogger.scala b/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroRawLogger.scala
index 87b4ab63e..d17367a48 100644
--- a/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroRawLogger.scala
+++ b/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroRawLogger.scala
@@ -28,7 +28,7 @@ trait AbstractMacroRawLogger { this: AbstractLogger =>
   inline final def crit(inline message: String): Unit = log(Log.Level.Crit, message)
 
   inline final def log(inline level: Log.Level, inline message: String): Unit = {
-    if (acceptable(Log.LoggerId(CodePositionMaterializer.applicationPointId), level)) {
+    if (acceptable(Log.LoggerId(CodePositionMaterializer.materializeApplicationPointId), level)) {
       unsafeLog(Log.Entry.create(level, Message.raw(message))(CodePositionMaterializer.materialize))
     }
   }
diff --git a/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroStrictLogger.scala b/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroStrictLogger.scala
index 9244f3c64..6cdcc7c9a 100644
--- a/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroStrictLogger.scala
+++ b/logstage/logstage-core/src/main/scala-3/izumi/logstage/api/logger/AbstractMacroStrictLogger.scala
@@ -27,7 +27,7 @@ trait AbstractMacroStrictLogger { this: AbstractLogger =>
   transparent inline final def crit(inline message: String): Unit = log(Log.Level.Crit, message)
 
   transparent inline final def log(inline level: Log.Level, inline message: String): Unit = {
-    if (acceptable(Log.LoggerId(CodePositionMaterializer.applicationPointId), level)) {
+    if (acceptable(Log.LoggerId(CodePositionMaterializer.materializeApplicationPointId), level)) {
       unsafeLog(Log.Entry.create(level, Message.strict(message))(CodePositionMaterializer.materialize))
     }
   }


Test patch:
diff --git a/distage/distage-core/src/test/scala-3/izumi/distage/fixtures/Scala3TraitCases.scala b/distage/distage-core/src/test/scala-3/izumi/distage/fixtures/Scala3TraitCases.scala
index 4c9a92034..9e7752cb7 100644
--- a/distage/distage-core/src/test/scala-3/izumi/distage/fixtures/Scala3TraitCases.scala
+++ b/distage/distage-core/src/test/scala-3/izumi/distage/fixtures/Scala3TraitCases.scala
@@ -50,7 +50,7 @@ object Scala3TraitCases {
   case class C3(a1: Int, a2: Long, a3: Number, a4: String)(a5: Double) extends T
 
   trait FactoryTrait1 {
-    def f1: T @With[C2]
+    def f1: T @distage.With[C2]
     def f2(): T @With[C2]
     def f3(c: C1): T @With[C2]
     def f4(a1: Int)(a2: Long, a5: Double): T @With[C3]
diff --git a/distage/distage-core/src/test/scala-3/izumi/distage/injector/Scala3AutoTraitsTest.scala b/distage/distage-core/src/test/scala-3/izumi/distage/injector/Scala3AutoTraitsTest.scala
index ba4e5353b..9dff3ff3d 100644
--- a/distage/distage-core/src/test/scala-3/izumi/distage/injector/Scala3AutoTraitsTest.scala
+++ b/distage/distage-core/src/test/scala-3/izumi/distage/injector/Scala3AutoTraitsTest.scala
@@ -78,7 +78,7 @@ class Scala3AutoTraitsTest extends AnyWordSpec with MkInjector {
 
       FactoryConstructor[{
           type U = Object
-          def makeConcreteDep(): T @With[C2]
+          def makeConcreteDep(): T @distage.With[C2]
           def makeConcreteDep1(d: Int): T @With[C2]
         }
       ]
diff --git a/distage/distage-extension-config/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala b/distage/distage-extension-config/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala
index 3386e4792..aa43035a4 100644
--- a/distage/distage-extension-config/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala
+++ b/distage/distage-extension-config/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala
@@ -3,7 +3,7 @@ package izumi.distage.impl
 import java.io.ByteArrayInputStream
 import distage.Lifecycle
 import izumi.distage.model.definition.ModuleDef
-import izumi.distage.model.effect.QuasiIO
+import izumi.distage.model.effect.{QuasiApplicative, QuasiFunctor, QuasiIO, QuasiPrimitives}
 import izumi.distage.modules.DefaultModule
 import izumi.functional.bio.{Applicative2, ApplicativeError2, Arrow3, ArrowChoice3, Ask3, Async2, Bifunctor2, Bracket2, Concurrent2, Error2, F, Fork2, Functor2, Guarantee2, IO2, IO3, Local3, Monad2, MonadAsk3, Panic2, Parallel2, Primitives2, PrimitivesM2, Profunctor3, Ref3, Temporal2}
 import izumi.fundamentals.platform.functional.{Identity, Identity2, Identity3}
@@ -86,6 +86,9 @@ class OptionalDependencyTest extends AnyWordSpec with GivenWhenThen {
       }
     }
 
+    assert(new optSearch1[QuasiFunctor].find == QuasiFunctor.quasiFunctorIdentity)
+    assert(new optSearch1[QuasiApplicative].find == QuasiApplicative.quasiApplicativeIdentity)
+    assert(new optSearch1[QuasiPrimitives].find == QuasiPrimitives.quasiPrimitivesIdentity)
     assert(new optSearch1[QuasiIO].find == QuasiIO.quasiIOIdentity)
 
     try QuasiIO.fromBIO(null)