diff --git a/.build.sh b/.build.sh index 2199491288..bf307c22bc 100644 --- a/.build.sh +++ b/.build.sh @@ -96,7 +96,7 @@ function init { export SCALA3=$(cat project/Deps.sc | grep 'val scala300 ' | sed -r 's/.*\"(.*)\".**/\1/') # details on github runners: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources - export _JAVA_OPTIONS="-Xmx3500M -XX:ReservedCodeCacheSize=256M -XX:MaxMetaspaceSize=1024M" + export _JAVA_OPTIONS="-Xmx4000M -XX:ReservedCodeCacheSize=256M -XX:MaxMetaspaceSize=1024M" printenv diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 26c74e2840..a2518fa626 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,6 +62,7 @@ jobs: # flags: unittests - name: Upload dependency graph uses: scalacenter/sbt-dependency-submission@ab086b50c947c9774b70f39fc7f6e20ca2706c91 + if: github.ref == 'develop' build-js: runs-on: ubuntu-latest needs: [ 'checksecret' ] diff --git a/README.md b/README.md index 5ae692335d..b487c7b597 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ including the following components: 2. [distage-testkit](https://izumi.7mind.io/distage/distage-testkit) – Hyper-pragmatic pure FP Test framework. Shares heavy resources globally across all test suites; lets you easily swap implementations of component; uses your effect type for parallelism. 3. [distage-framework-docker](https://izumi.7mind.io/distage/distage-framework-docker) – A distage extension for using docker containers in tests or for local application runs, comes with example Postgres, Cassandra, Kafka & DynamoDB containers. 4. [LogStage](https://izumi.7mind.io/logstage/) – Automatic structural logs from Scala string interpolations, -5. [BIO](https://izumi.7mind.io/bio/) - A typeclass hierarchy for tagless final style with Bifunctor and Trifunctor effect types. Focused on ergonomics and ease of use with zero boilerplate. -6. [izumi-reflect](https://github.com/zio/izumi-reflect) (moved to [zio/izumi-reflect](https://github.com/zio/izumi-reflect)) - Portable, lightweight and kind-polymorphic alternative to `scala-reflect`'s Typetag for Scala, Scala.js, Scala Native and Dotty +5. [BIO](https://izumi.7mind.io/bio/) - A typeclass hierarchy for tagless final style with Bifunctor effect types. Focused on ergonomics and ease of use with zero boilerplate. +6. [izumi-reflect](https://github.com/zio/izumi-reflect) (moved to [zio/izumi-reflect](https://github.com/zio/izumi-reflect)) - Portable, lightweight and kind-polymorphic alternative to `scala-reflect`'s Typetag for Scala, Scala.js, Scala Native and Scala 3 7. [IdeaLingua](https://izumi.7mind.io/idealingua/) (moved to [7mind/idealingua-v1](https://github.com/7mind/idealingua-v1)) – API Definition, Data Modeling and RPC language, optimized for fast prototyping – like gRPC or Swagger, but with a human face. Generates RPC servers and clients for Go, TypeScript, C# and Scala, 8. [Opinionated SBT plugins](https://izumi.7mind.io/sbt/) (moved to [7mind/sbtgen](https://github.com/7mind/sbtgen)) – Reduces verbosity of SBT builds and introduces new features – inter-project shared test scopes and BOM plugins (from Maven) 9. [Percept-Plan-Execute-Repeat (PPER)](https://izumi.7mind.io/pper/) – A pattern that enables modeling very complex domains and orchestrate deadly complex processes a lot easier than you're used to. diff --git a/build.sbt b/build.sbt index e864f329a1..ebde2c0bb3 100644 --- a/build.sbt +++ b/build.sbt @@ -22,7 +22,7 @@ lazy val `fundamentals-functional` = project.in(file("fundamentals/fundamentals- .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -108,7 +108,7 @@ lazy val `fundamentals-functional` = project.in(file("fundamentals/fundamentals- "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -147,6 +147,7 @@ lazy val `fundamentals-functional` = project.in(file("fundamentals/fundamentals- scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -159,7 +160,7 @@ lazy val `fundamentals-functional` = project.in(file("fundamentals/fundamentals- "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -185,7 +186,7 @@ lazy val `fundamentals-collections` = project.in(file("fundamentals/fundamentals .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -271,7 +272,7 @@ lazy val `fundamentals-collections` = project.in(file("fundamentals/fundamentals "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -310,6 +311,7 @@ lazy val `fundamentals-collections` = project.in(file("fundamentals/fundamentals scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -322,7 +324,7 @@ lazy val `fundamentals-collections` = project.in(file("fundamentals/fundamentals "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -346,7 +348,7 @@ lazy val `fundamentals-literals` = project.in(file("fundamentals/fundamentals-li .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -432,7 +434,7 @@ lazy val `fundamentals-literals` = project.in(file("fundamentals/fundamentals-li "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -471,6 +473,7 @@ lazy val `fundamentals-literals` = project.in(file("fundamentals/fundamentals-li scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -483,7 +486,7 @@ lazy val `fundamentals-literals` = project.in(file("fundamentals/fundamentals-li "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -511,7 +514,7 @@ lazy val `fundamentals-orphans` = project.in(file("fundamentals/fundamentals-orp .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -597,7 +600,7 @@ lazy val `fundamentals-orphans` = project.in(file("fundamentals/fundamentals-orp "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -636,6 +639,7 @@ lazy val `fundamentals-orphans` = project.in(file("fundamentals/fundamentals-orp scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -648,7 +652,7 @@ lazy val `fundamentals-orphans` = project.in(file("fundamentals/fundamentals-orp "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -687,7 +691,7 @@ lazy val `fundamentals-language` = project.in(file("fundamentals/fundamentals-la .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -773,7 +777,7 @@ lazy val `fundamentals-language` = project.in(file("fundamentals/fundamentals-la "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -812,6 +816,7 @@ lazy val `fundamentals-language` = project.in(file("fundamentals/fundamentals-la scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -824,7 +829,7 @@ lazy val `fundamentals-language` = project.in(file("fundamentals/fundamentals-la "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -853,7 +858,7 @@ lazy val `fundamentals-platform` = project.in(file("fundamentals/fundamentals-pl .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -939,7 +944,7 @@ lazy val `fundamentals-platform` = project.in(file("fundamentals/fundamentals-pl "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -978,6 +983,7 @@ lazy val `fundamentals-platform` = project.in(file("fundamentals/fundamentals-pl scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -990,7 +996,7 @@ lazy val `fundamentals-platform` = project.in(file("fundamentals/fundamentals-pl "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -1030,7 +1036,7 @@ lazy val `fundamentals-json-circe` = project.in(file("fundamentals/fundamentals- .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -1116,7 +1122,7 @@ lazy val `fundamentals-json-circe` = project.in(file("fundamentals/fundamentals- "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -1155,6 +1161,7 @@ lazy val `fundamentals-json-circe` = project.in(file("fundamentals/fundamentals- scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -1167,7 +1174,7 @@ lazy val `fundamentals-json-circe` = project.in(file("fundamentals/fundamentals- "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -1197,7 +1204,7 @@ lazy val `fundamentals-reflection` = project.in(file("fundamentals/fundamentals- .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -1283,7 +1290,7 @@ lazy val `fundamentals-reflection` = project.in(file("fundamentals/fundamentals- "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -1322,6 +1329,7 @@ lazy val `fundamentals-reflection` = project.in(file("fundamentals/fundamentals- scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -1334,7 +1342,7 @@ lazy val `fundamentals-reflection` = project.in(file("fundamentals/fundamentals- "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -1347,7 +1355,8 @@ lazy val `fundamentals-reflection` = project.in(file("fundamentals/fundamentals- lazy val `fundamentals-bio` = project.in(file("fundamentals/fundamentals-bio")) .dependsOn( `fundamentals-language` % "test->compile;compile->compile", - `fundamentals-orphans` % "test->compile;compile->compile" + `fundamentals-orphans` % "test->compile;compile->compile", + `fundamentals-collections` % "test->compile;compile->compile" ) .settings( libraryDependencies ++= Seq( @@ -1373,7 +1382,7 @@ lazy val `fundamentals-bio` = project.in(file("fundamentals/fundamentals-bio")) .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -1459,7 +1468,7 @@ lazy val `fundamentals-bio` = project.in(file("fundamentals/fundamentals-bio")) "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -1498,6 +1507,7 @@ lazy val `fundamentals-bio` = project.in(file("fundamentals/fundamentals-bio")) scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -1510,13 +1520,17 @@ lazy val `fundamentals-bio` = project.in(file("fundamentals/fundamentals-bio")) "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) case (_, _) => Seq.empty } }, - Test / packageDoc / publishArtifact := false + Test / packageDoc / publishArtifact := false, + Compile / doc / sources := { (isSnapshot.value, scalaVersion.value) match { + case (_, "3.2.2") => Seq.empty + case (_, _) => (Compile / doc / sources).value + } } ) .disablePlugins(AssemblyPlugin) @@ -1548,7 +1562,7 @@ lazy val `distage-core-api` = project.in(file("distage/distage-core-api")) .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -1634,7 +1648,7 @@ lazy val `distage-core-api` = project.in(file("distage/distage-core-api")) "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -1673,6 +1687,7 @@ lazy val `distage-core-api` = project.in(file("distage/distage-core-api")) scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -1685,7 +1700,7 @@ lazy val `distage-core-api` = project.in(file("distage/distage-core-api")) "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -1712,7 +1727,7 @@ lazy val `distage-core-proxy-bytebuddy` = project.in(file("distage/distage-core- .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -1798,7 +1813,7 @@ lazy val `distage-core-proxy-bytebuddy` = project.in(file("distage/distage-core- "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -1837,6 +1852,7 @@ lazy val `distage-core-proxy-bytebuddy` = project.in(file("distage/distage-core- scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -1849,7 +1865,7 @@ lazy val `distage-core-proxy-bytebuddy` = project.in(file("distage/distage-core- "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -1876,7 +1892,7 @@ lazy val `distage-framework-api` = project.in(file("distage/distage-framework-ap .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -1962,7 +1978,7 @@ lazy val `distage-framework-api` = project.in(file("distage/distage-framework-ap "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -2001,6 +2017,7 @@ lazy val `distage-framework-api` = project.in(file("distage/distage-framework-ap scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -2013,7 +2030,7 @@ lazy val `distage-framework-api` = project.in(file("distage/distage-framework-ap "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -2047,7 +2064,7 @@ lazy val `distage-core` = project.in(file("distage/distage-core")) .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -2133,7 +2150,7 @@ lazy val `distage-core` = project.in(file("distage/distage-core")) "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -2172,6 +2189,7 @@ lazy val `distage-core` = project.in(file("distage/distage-core")) scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -2184,7 +2202,7 @@ lazy val `distage-core` = project.in(file("distage/distage-core")) "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -2215,7 +2233,7 @@ lazy val `distage-extension-config` = project.in(file("distage/distage-extension .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -2301,7 +2319,7 @@ lazy val `distage-extension-config` = project.in(file("distage/distage-extension "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -2340,6 +2358,7 @@ lazy val `distage-extension-config` = project.in(file("distage/distage-extension scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -2352,7 +2371,7 @@ lazy val `distage-extension-config` = project.in(file("distage/distage-extension "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -2382,7 +2401,7 @@ lazy val `distage-extension-logstage` = project.in(file("distage/distage-extensi .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -2468,7 +2487,7 @@ lazy val `distage-extension-logstage` = project.in(file("distage/distage-extensi "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -2507,6 +2526,7 @@ lazy val `distage-extension-logstage` = project.in(file("distage/distage-extensi scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -2519,7 +2539,7 @@ lazy val `distage-extension-logstage` = project.in(file("distage/distage-extensi "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -2554,7 +2574,7 @@ lazy val `distage-extension-plugins` = project.in(file("distage/distage-extensio .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -2640,7 +2660,7 @@ lazy val `distage-extension-plugins` = project.in(file("distage/distage-extensio "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -2679,6 +2699,7 @@ lazy val `distage-extension-plugins` = project.in(file("distage/distage-extensio scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -2691,7 +2712,7 @@ lazy val `distage-extension-plugins` = project.in(file("distage/distage-extensio "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -2738,7 +2759,7 @@ lazy val `distage-framework` = project.in(file("distage/distage-framework")) .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -2824,7 +2845,7 @@ lazy val `distage-framework` = project.in(file("distage/distage-framework")) "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -2863,6 +2884,7 @@ lazy val `distage-framework` = project.in(file("distage/distage-framework")) scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -2875,7 +2897,7 @@ lazy val `distage-framework` = project.in(file("distage/distage-framework")) "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -2911,7 +2933,7 @@ lazy val `distage-framework-docker` = project.in(file("distage/distage-framework .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -2997,7 +3019,7 @@ lazy val `distage-framework-docker` = project.in(file("distage/distage-framework "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -3036,6 +3058,7 @@ lazy val `distage-framework-docker` = project.in(file("distage/distage-framework scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -3048,7 +3071,7 @@ lazy val `distage-framework-docker` = project.in(file("distage/distage-framework "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -3074,7 +3097,7 @@ lazy val `distage-testkit-core` = project.in(file("distage/distage-testkit-core" .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -3160,7 +3183,7 @@ lazy val `distage-testkit-core` = project.in(file("distage/distage-testkit-core" "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -3199,6 +3222,7 @@ lazy val `distage-testkit-core` = project.in(file("distage/distage-testkit-core" scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -3211,7 +3235,7 @@ lazy val `distage-testkit-core` = project.in(file("distage/distage-testkit-core" "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -3246,7 +3270,7 @@ lazy val `distage-testkit-scalatest` = project.in(file("distage/distage-testkit- .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -3332,7 +3356,7 @@ lazy val `distage-testkit-scalatest` = project.in(file("distage/distage-testkit- "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -3371,6 +3395,7 @@ lazy val `distage-testkit-scalatest` = project.in(file("distage/distage-testkit- scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -3383,7 +3408,7 @@ lazy val `distage-testkit-scalatest` = project.in(file("distage/distage-testkit- "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -3416,7 +3441,7 @@ lazy val `logstage-core` = project.in(file("logstage/logstage-core")) .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -3502,7 +3527,7 @@ lazy val `logstage-core` = project.in(file("logstage/logstage-core")) "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -3541,6 +3566,7 @@ lazy val `logstage-core` = project.in(file("logstage/logstage-core")) scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -3553,7 +3579,7 @@ lazy val `logstage-core` = project.in(file("logstage/logstage-core")) "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -3585,7 +3611,7 @@ lazy val `logstage-rendering-circe` = project.in(file("logstage/logstage-renderi .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -3671,7 +3697,7 @@ lazy val `logstage-rendering-circe` = project.in(file("logstage/logstage-renderi "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -3710,6 +3736,7 @@ lazy val `logstage-rendering-circe` = project.in(file("logstage/logstage-renderi scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -3722,7 +3749,7 @@ lazy val `logstage-rendering-circe` = project.in(file("logstage/logstage-renderi "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -3749,7 +3776,7 @@ lazy val `logstage-adapter-slf4j` = project.in(file("logstage/logstage-adapter-s .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -3835,7 +3862,7 @@ lazy val `logstage-adapter-slf4j` = project.in(file("logstage/logstage-adapter-s "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -3874,6 +3901,7 @@ lazy val `logstage-adapter-slf4j` = project.in(file("logstage/logstage-adapter-s scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -3886,7 +3914,7 @@ lazy val `logstage-adapter-slf4j` = project.in(file("logstage/logstage-adapter-s "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -3917,7 +3945,7 @@ lazy val `logstage-sink-slf4j` = project.in(file("logstage/logstage-sink-slf4j") .settings( crossScalaVersions := Seq( "3.3.1", - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -4003,7 +4031,7 @@ lazy val `logstage-sink-slf4j` = project.in(file("logstage/logstage-sink-slf4j") "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -4042,6 +4070,7 @@ lazy val `logstage-sink-slf4j` = project.in(file("logstage/logstage-sink-slf4j") scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -4054,7 +4083,7 @@ lazy val `logstage-sink-slf4j` = project.in(file("logstage/logstage-sink-slf4j") "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -4111,7 +4140,7 @@ lazy val `microsite` = project.in(file("doc/microsite")) ) .settings( crossScalaVersions := Seq( - "2.13.11", + "2.13.12", "2.12.18" ), scalaVersion := crossScalaVersions.value.head, @@ -4197,7 +4226,7 @@ lazy val `microsite` = project.in(file("doc/microsite")) "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -4236,6 +4265,7 @@ lazy val `microsite` = project.in(file("doc/microsite")) scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -4248,7 +4278,7 @@ lazy val `microsite` = project.in(file("doc/microsite")) "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) @@ -4313,8 +4343,10 @@ lazy val `microsite` = project.in(file("doc/microsite")) (ghpagesRepository.value / "paradox.json").getCanonicalPath == f.getCanonicalPath || (ghpagesRepository.value / "CNAME").getCanonicalPath == f.getCanonicalPath || (ghpagesRepository.value / ".nojekyll").getCanonicalPath == f.getCanonicalPath || - (ghpagesRepository.value / "index.html").getCanonicalPath == f.getCanonicalPath || - (ghpagesRepository.value / "README.md").getCanonicalPath == f.getCanonicalPath + (ghpagesRepository.value / "README.md").getCanonicalPath == f.getCanonicalPath || ( + f.toPath.getParent.toAbsolutePath == (ghpagesRepository.value / "index.html").toPath.getParent.toAbsolutePath && + f.getCanonicalPath.endsWith(".html") + ) } }, libraryDependencies += "io.7mind.izumi.sbt" % "sbtgen_2.13" % "0.0.99" @@ -4420,7 +4452,7 @@ lazy val `sbt-izumi-deps` = project.in(file("sbt-plugins/sbt-izumi-deps")) "-Ycache-plugin-class-loader:always", "-Ycache-macro-class-loader:last-modified" ) - case (_, "2.13.11") => Seq( + case (_, "2.13.12") => Seq( "-Wconf:any:error", "-release:8", "-explaintypes", @@ -4459,6 +4491,7 @@ lazy val `sbt-izumi-deps` = project.in(file("sbt-plugins/sbt-izumi-deps")) scalacOptions += "-Wconf:msg=legacy-binding:silent", scalacOptions += "-Wconf:msg=nowarn:silent", scalacOptions += "-Wconf:msg=parameter.*x\\$4.in.anonymous.function.is.never.used:silent", + scalacOptions += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", scalacOptions += "-Wconf:msg=package.object.inheritance:silent", scalacOptions += "-Wconf:cat=lint-eta-sam:silent", Compile / sbt.Keys.doc / scalacOptions -= "-Wconf:any:error", @@ -4471,7 +4504,7 @@ lazy val `sbt-izumi-deps` = project.in(file("sbt-plugins/sbt-izumi-deps")) "-opt:l:inline", "-opt-inline-from:izumi.**" ) - case (false, "2.13.11") => Seq( + case (false, "2.13.12") => Seq( "-opt:l:inline", "-opt-inline-from:izumi.**" ) diff --git a/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/constructors.scala b/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/constructors.scala index 28a87e61d6..ff35049b1b 100644 --- a/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/constructors.scala +++ b/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/constructors.scala @@ -11,23 +11,20 @@ import zio.ZEnvironment import scala.language.experimental.macros as enableMacros +sealed trait AnyConstructorBase[T] extends Any with ClassConstructorOptionalMakeDSL[T] { + def provider: Functoid[T] +} + /** - * An implicitly summonable constructor for a type `T`, can generate constructors for: - * - * - concrete classes (using [[ClassConstructor]]) - * - traits and abstract classes ([[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]], using [[TraitConstructor]]) - * - * Since version `1.1.0`, does not generate constructors "factory-like" traits and abstract classes, instead use [[FactoryConstructor]]. - * - * Use [[ZEnvConstructor]] to generate constructors for `zio.ZEnvironment` values. + * An implicitly summonable constructor for a concrete class `T` * * @example * {{{ - * import distage.{AnyConstructor, Functoid, Injector, ModuleDef} + * import distage.{ClassConstructor, Functoid, Injector, ModuleDef} * * class A(val i: Int) * - * val constructor: Functoid[A] = AnyConstructor[A] + * val constructor: Functoid[A] = ClassConstructor[A] * * val lifecycle = Injector().produceGet[A](new ModuleDef { * make[A].from(constructor) @@ -42,22 +39,7 @@ import scala.language.experimental.macros as enableMacros * * @return [[izumi.distage.model.providers.Functoid]][T] value */ -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 - - implicit def materialize[T]: AnyConstructor[T] = macro AnyConstructorMacro.mkAnyConstructor[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] +final class ClassConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructorBase[T] object ClassConstructor { def apply[T](implicit ctor: ClassConstructor[T]): Functoid[T] = ctor.provider @@ -70,9 +52,10 @@ object ClassConstructor { * * @see [[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]] * @see [[izumi.distage.model.definition.impl]] recommended documenting annotation for use with [[TraitConstructor]] - * @see [[AnyConstructor]] + * + * @return [[izumi.distage.model.providers.Functoid]][T] value */ -final class TraitConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructor[T] +final class TraitConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructorBase[T] object TraitConstructor { def apply[T](implicit ctor: TraitConstructor[T]): Functoid[T] = ctor.provider @@ -94,9 +77,10 @@ object TraitConstructor { * * @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]] + * + * @return [[izumi.distage.model.providers.Functoid]][T] value */ -final class FactoryConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructor[T] +final class FactoryConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructorBase[T] object FactoryConstructor { def apply[T](implicit ctor: FactoryConstructor[T]): Functoid[T] = ctor.provider @@ -110,9 +94,8 @@ object FactoryConstructor { * `zio.ZEnvironment` heterogeneous map values may be used by ZIO or other Reader-like effects * * @see [[https://izumi.7mind.io/distage/basics.html#zio-environment-bindings ZIO Environment bindings]] - * @see [[AnyConstructor]] */ -final class ZEnvConstructor[T](val provider: Functoid[ZEnvironment[T]]) extends AnyVal with AnyConstructor[ZEnvironment[T]] +final class ZEnvConstructor[T](val provider: Functoid[ZEnvironment[T]]) extends AnyVal with AnyConstructorBase[ZEnvironment[T]] object ZEnvConstructor { def apply[T](implicit ctor: ZEnvConstructor[T]): Functoid[ZEnvironment[T]] = ctor.provider @@ -122,19 +105,19 @@ object ZEnvConstructor { implicit def materialize[T]: ZEnvConstructor[T] = macro ZEnvConstructorMacro.mkZEnvConstructor[T] } -private[constructors] sealed trait AnyConstructorOptionalMakeDSL[T] extends Any { +private[constructors] sealed trait ClassConstructorOptionalMakeDSL[T] extends Any { def provider: Functoid[T] } -object AnyConstructorOptionalMakeDSL { - private[constructors] final class Impl[T](val provider: Functoid[T]) extends AnyVal with AnyConstructorOptionalMakeDSL[T] +object ClassConstructorOptionalMakeDSL { + private[constructors] final class Impl[T](val provider: Functoid[T]) extends AnyVal with ClassConstructorOptionalMakeDSL[T] - @inline def apply[T](functoid: Functoid[T]): AnyConstructorOptionalMakeDSL.Impl[T] = { - new AnyConstructorOptionalMakeDSL.Impl[T](functoid) + @inline def apply[T](functoid: Functoid[T]): ClassConstructorOptionalMakeDSL.Impl[T] = { + new ClassConstructorOptionalMakeDSL.Impl[T](functoid) } - def errorConstructor[T](tpe: String, nonWhitelistedMethods: List[String]): AnyConstructorOptionalMakeDSL.Impl[T] = { - AnyConstructorOptionalMakeDSL[T](Functoid.lift(throwError(tpe, nonWhitelistedMethods))) + def errorConstructor[T](tpe: String, nonWhitelistedMethods: List[String]): ClassConstructorOptionalMakeDSL.Impl[T] = { + ClassConstructorOptionalMakeDSL[T](Functoid.lift(throwError(tpe, nonWhitelistedMethods))) } def throwError(tpe: String, nonWhitelistedMethods: List[String]): Nothing = { @@ -149,5 +132,5 @@ object AnyConstructorOptionalMakeDSL { ) } - implicit def materialize[T]: AnyConstructorOptionalMakeDSL.Impl[T] = macro AnyConstructorMacro.anyConstructorOptionalMakeDSL[T] + implicit def materialize[T]: ClassConstructorOptionalMakeDSL.Impl[T] = macro MakeMacro.classConstructorOptionalMakeDSL[T] } diff --git a/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/macros/ClassConstructorMacro.scala b/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/macros/ClassConstructorMacro.scala index 42c772b3c9..cf3bf3519c 100644 --- a/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/macros/ClassConstructorMacro.scala +++ b/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/macros/ClassConstructorMacro.scala @@ -14,43 +14,55 @@ object ClassConstructorMacro { def mkClassConstructor[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[ClassConstructor[T]] = { import c.universe._ + val macroUniverse = StaticDIUniverse(c) + val reflectionProvider = ReflectionProviderDefaultImpl(macroUniverse) + val targetType = ReflectionUtil.norm(c.universe: c.universe.type)(weakTypeOf[T].dealias) requireConcreteTypeConstructor(c)("ClassConstructor", targetType) - (targetType match { - case t: SingletonTypeApi => - val functoid = symbolOf[Functoid.type].asClass.module - val term = t match { - case t: ThisTypeApi => This(t.sym) - case t: ConstantTypeApi => q"${t.value}" - case _ => q"${t.termSymbol}" - } - c.Expr[ClassConstructor[T]] { - q"{ new ${weakTypeOf[ClassConstructor[T]]}($functoid.singleton[$targetType]($term)) }" - } - - case _ => - val macroUniverse = StaticDIUniverse(c) - val impls = ClassConstructorMacros(c)(macroUniverse) - import impls.{c => _, u => _, _} - - val reflectionProvider = ReflectionProviderDefaultImpl(macroUniverse) - if (!reflectionProvider.isConcrete(targetType)) { - c.abort( - c.enclosingPosition, - s"""Tried to derive constructor function for class $targetType, but the class is an - |abstract class or a trait! Only concrete classes (`class` keyword) are supported""".stripMargin, - ) - } - - val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`.name) - - val provider: c.Expr[Functoid[T]] = mkClassConstructorProvider(reflectionProvider)(targetType) - - val res = c.Expr[ClassConstructor[T]](q"{ new ${weakTypeOf[ClassConstructor[T]]}($provider) }") - logger.log(s"Final syntax tree of class for $targetType:\n$res") - res - }): @nowarn("msg=outer reference") + if (reflectionProvider.isConcrete(targetType)) { + (targetType match { + case t: SingletonTypeApi => + val functoid = symbolOf[Functoid.type].asClass.module + val term = t match { + case t: ThisTypeApi => This(t.sym) + case t: ConstantTypeApi => q"${t.value}" + case _ => q"${t.termSymbol}" + } + c.Expr[ClassConstructor[T]] { + q"{ new ${weakTypeOf[ClassConstructor[T]]}($functoid.singleton[$targetType]($term)) }" + } + + case _ => + val impls = ClassConstructorMacros(c)(macroUniverse) + + val provider: c.Expr[Functoid[T]] = impls.mkClassConstructorProvider(reflectionProvider)(targetType) + + val res = c.Expr[ClassConstructor[T]](q"{ new ${weakTypeOf[ClassConstructor[T]]}($provider) }") + + val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`.name) + logger.log(s"Final syntax tree of class for $targetType:\n$res") + + res + }): @nowarn("msg=outer reference") + } else if (reflectionProvider.isWireableAbstract(targetType)) { + c.abort( + c.enclosingPosition, + s"ClassConstructor failure: $targetType is a trait or an abstract class, use `makeTrait` or `make[X].fromTrait` to wire traits.", + ) + } else if (reflectionProvider.isFactory(targetType)) { + c.abort( + c.enclosingPosition, + s"ClassConstructor failure: $targetType is a Factory, use `makeFactory` or `make[X].fromFactory` to wire factories.", + ) + } else { + c.abort( + c.enclosingPosition, + s"""ClassConstructor failure: couldn't derive a constructor for $targetType! + |It's neither a concrete class, nor a wireable trait or abstract class!""".stripMargin, + ) + } + } } diff --git a/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/macros/AnyConstructorMacro.scala b/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/macros/MakeMacro.scala similarity index 60% rename from distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/macros/AnyConstructorMacro.scala rename to distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/macros/MakeMacro.scala index 4d99a6776b..c12ddc8961 100644 --- a/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/macros/AnyConstructorMacro.scala +++ b/distage/distage-core-api/src/main/scala-2/izumi/distage/constructors/macros/MakeMacro.scala @@ -1,30 +1,28 @@ package izumi.distage.constructors.macros -import scala.annotation.nowarn -import izumi.distage.constructors.{AnyConstructor, AnyConstructorOptionalMakeDSL, DebugProperties} +import izumi.distage.constructors.{ClassConstructorOptionalMakeDSL, DebugProperties} import izumi.distage.model.definition.dsl.ModuleDefDSL -import izumi.distage.model.reflection.universe.StaticDIUniverse -import izumi.distage.reflection.ReflectionProviderDefaultImpl -import izumi.fundamentals.reflection.{ReflectionUtil, TrivialMacroLogger} +import izumi.fundamentals.reflection.TrivialMacroLogger +import scala.annotation.nowarn import scala.reflect.api.Universe import scala.reflect.macros.blackbox @nowarn("msg=deprecated.*since 2.11") -object AnyConstructorMacro { +object MakeMacro { def make[B[_], T: c.WeakTypeTag](c: blackbox.Context): c.Expr[B[T]] = { - import c.universe._ - c.Expr[B[T]](q"""${c.prefix}._make[${weakTypeOf[T]}](${c.inferImplicitValue(weakTypeOf[AnyConstructorOptionalMakeDSL[T]], silent = false)}.provider)""") + import c.universe.* + c.Expr[B[T]](q"""${c.prefix}._make[${weakTypeOf[T]}](${c.inferImplicitValue(weakTypeOf[ClassConstructorOptionalMakeDSL[T]], silent = false)}.provider)""") } - def anyConstructorOptionalMakeDSL[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AnyConstructorOptionalMakeDSL.Impl[T]] = { - import c.universe._ + def classConstructorOptionalMakeDSL[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[ClassConstructorOptionalMakeDSL.Impl[T]] = { + import c.universe.* val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`.name) val enclosingClass = c.enclosingClass - // We expect this macro to be called only and __ONLY__ from `AnyConstructorMacro.make` + // We expect this macro to be called only and __ONLY__ from `MakeMacro.make` // we're going to use the position of the `make` call to search for subsequent methods // instead of the position of the implicit search itself (which is unstable and // sometimes doesn't exist, for example during scaladoc compilation) @@ -62,12 +60,12 @@ object AnyConstructorMacro { val tpe = weakTypeOf[T] - c.Expr[AnyConstructorOptionalMakeDSL.Impl[T]] { + c.Expr[ClassConstructorOptionalMakeDSL.Impl[T]] { maybeNonwhiteListedMethods match { case None => c.abort( c.enclosingPosition, - s"""Couldn't find position of the `make` call when summoning AnyConstructorOptionalMakeDSL[$tpe] + s"""Couldn't find position of the `make` call when summoning ClassConstructorOptionalMakeDSL[$tpe] |Got tree: $maybeTree |Result of search: $maybeNonwhiteListedMethods |Searched for position: $positionOfMakeCall @@ -79,42 +77,14 @@ object AnyConstructorMacro { if (nonwhiteListedMethods.isEmpty) { logger.log(s"""For $tpe found no `.from`-like calls in $maybeTree""".stripMargin) - q"""_root_.izumi.distage.constructors.AnyConstructorOptionalMakeDSL.apply[$tpe](${mkAnyConstructor[T](c)}.provider)""" + q"""_root_.izumi.distage.constructors.ClassConstructorOptionalMakeDSL.apply[$tpe](${ClassConstructorMacro.mkClassConstructor[T](c)}.provider)""" } else { logger.log(s"For $tpe found `.from`-like calls, generating ERROR constructor: $nonwhiteListedMethods") - q"""_root_.izumi.distage.constructors.AnyConstructorOptionalMakeDSL.errorConstructor[$tpe](${tpe.toString}, $nonwhiteListedMethods)""" + q"""_root_.izumi.distage.constructors.ClassConstructorOptionalMakeDSL.errorConstructor[$tpe](${tpe.toString}, $nonwhiteListedMethods)""" } } } } - def mkAnyConstructor[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AnyConstructor[T]] = { - import c.universe._ - - val macroUniverse = StaticDIUniverse(c) - val reflectionProvider = ReflectionProviderDefaultImpl(macroUniverse) - - val targetType = ReflectionUtil.norm(c.universe: c.universe.type)(weakTypeOf[T].dealias) - requireConcreteTypeConstructor(c)("AnyConstructor", targetType) - - if (reflectionProvider.isConcrete(targetType)) { - ClassConstructorMacro.mkClassConstructor[T](c) - } else if (reflectionProvider.isWireableAbstract(targetType)) { - TraitConstructorMacro.mkTraitConstructor[T](c) - } else if (reflectionProvider.isFactory(targetType)) { - c.abort( - c.enclosingPosition, - s"""AnyConstructor failure: $targetType is a Factory, use makeFactory or fromFactory to wire factories.""".stripMargin, - ) - } else { - c.abort( - c.enclosingPosition, - s"""AnyConstructor failure: couldn't generate a constructor for $targetType! - |It's neither a concrete class, nor a wireable trait or abstract class!""".stripMargin, - ) - } - - } - } diff --git a/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/AbstractBindingDefDSLMacro.scala b/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/AbstractBindingDefDSLMacro.scala index 1761ac9654..58b042149d 100644 --- a/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/AbstractBindingDefDSLMacro.scala +++ b/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/AbstractBindingDefDSLMacro.scala @@ -1,9 +1,9 @@ package izumi.distage.model.definition.dsl -import izumi.distage.constructors.macros.AnyConstructorMacro +import izumi.distage.constructors.macros.MakeMacro import scala.language.experimental.macros trait AbstractBindingDefDSLMacro[BindDSL[_]] { - final protected[this] def make[T]: BindDSL[T] = macro AnyConstructorMacro.make[BindDSL, T] + final protected[this] def make[T]: BindDSL[T] = macro MakeMacro.make[BindDSL, T] } diff --git a/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/AnyKindShim.scala b/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/AnyKindShim.scala index bc413e4833..5993cf7172 100644 --- a/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/AnyKindShim.scala +++ b/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/AnyKindShim.scala @@ -1,7 +1,5 @@ package izumi.distage.model.definition.dsl -trait AnyKindShim { +object AnyKindShim { type LifecycleF[_] = Any } - -object AnyKindShim extends AnyKindShim diff --git a/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/LifecycleTagLowPriority.scala b/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/LifecycleTagLowPriority.scala index e61ab94b7f..84752e7b7c 100644 --- a/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/LifecycleTagLowPriority.scala +++ b/distage/distage-core-api/src/main/scala-2/izumi/distage/model/definition/dsl/LifecycleTagLowPriority.scala @@ -17,12 +17,6 @@ trait LifecycleTagLowPriority { macro LifecycleTagMacro.fakeResourceTagMacroIntellijWorkaroundImpl[R] } -// // fixme wtf -//trait TrifunctorHasLifecycleTagLowPriority1 { -// implicit final def fakeResourceTagMacroIntellijWorkaround[R <: Lifecycle[Any, Any], T]: LifecycleAdapters.TrifunctorHasLifecycleTag[R, T] = -// macro LifecycleTagMacro.fakeResourceTagMacroIntellijWorkaroundImpl[R] -//} - trait ZIOEnvLifecycleTagLowPriority1 { implicit final def fakeResourceTagMacroIntellijWorkaround[R <: Lifecycle[Any, Any], T]: LifecycleAdapters.ZIOEnvLifecycleTag[R, T] = /*scalafmt*/ macro LifecycleTagMacro.fakeResourceTagMacroIntellijWorkaroundImpl[R] 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 74c35a2440..e0ae506871 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 @@ -6,6 +6,7 @@ import izumi.distage.model.reflection.Provider.ProviderType import scala.quoted.{Expr, Quotes, Type} import izumi.fundamentals.platform.exceptions.IzThrowable.toRichThrowable +import scala.annotation.experimental import scala.collection.immutable.ArraySeq object ClassConstructorMacro { @@ -16,14 +17,44 @@ object ClassConstructorMacro { val util = new ConstructorUtil[qctx.type]() util.requireConcreteTypeConstructor(TypeRepr.of[R], "ClassConstructor") - makeImpl[R](util) + val typeRepr = TypeRepr.of[R].dealias.simplified + val typeSymbol = typeRepr.typeSymbol + val tpeDeref = util.dereferenceTypeRef(typeRepr) + + // FIXME remove redundant check across macros + lazy val context = new ConstructorContext[R, qctx.type, util.type](util) + + val isConcrete = + (typeRepr.classSymbol.isDefined && !util.symbolIsTraitOrAbstract(typeSymbol) && !isRefinement(tpeDeref)) + || isSingleton(tpeDeref) + + if (isConcrete) { + makeImpl[R](util, typeRepr, tpeDeref) + } else if (context.isWireableTrait) { + report.errorAndAbort( + s"ClassConstructor failure: ${Type.show[R]} is a trait or an abstract class, use `makeTrait` or `make[X].fromTrait` to wire traits." + ) + } else if (context.isFactoryOrTrait) { + report.errorAndAbort( + s"ClassConstructor failure: ${Type.show[R]} is a Factory, use `makeFactory` or `make[X].fromFactory` to wire factories." + ) + } else { + report.errorAndAbort( + s"""ClassConstructor failure: couldn't derive a constructor for ${Type.show[R]}! + |It's neither a concrete class, nor a wireable trait or abstract class!""".stripMargin + ) + } } catch { case t: scala.quoted.runtime.StopMacroExpansion => throw t; case t: Throwable => qctx.reflect.report.errorAndAbort(t.stacktraceString) } - def makeImpl[R: Type](using qctx: Quotes)(util: ConstructorUtil[qctx.type]): Expr[ClassConstructor[R]] = { + def makeImpl[R: Type]( + using qctx: Quotes + )(util: ConstructorUtil[qctx.type], + typeRepr: qctx.reflect.TypeRepr, + tpeDeref: qctx.reflect.TypeRepr, + ): Expr[ClassConstructor[R]] = { import qctx.reflect.* - val typeRepr = TypeRepr.of[R].dealias.simplified - util.dereferenceTypeRef(typeRepr) match { + tpeDeref match { case c: ConstantType => singletonClassConstructor[R](Literal(c.constant)) @@ -31,12 +62,6 @@ object ClassConstructorMacro { singletonClassConstructor[R](Ident(t)) case _ => - if (util.symbolIsTraitOrAbstract(typeRepr.typeSymbol)) { - 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(_) => val ctorTreeParameterized = util.buildConstructorTermAppliedToTypeParameters(typeRepr) @@ -52,6 +77,20 @@ object ClassConstructorMacro { } } + private def isSingleton(using qctx: Quotes)(tpeDeref: qctx.reflect.TypeRepr): Boolean = { + tpeDeref match { + case _: qctx.reflect.ConstantType | _: qctx.reflect.TermRef => true + case _ => false + } + } + + private def isRefinement(using qctx: Quotes)(tpeDeref: qctx.reflect.TypeRepr): Boolean = { + tpeDeref match { + case _: qctx.reflect.Refinement => true + case _ => false + } + } + private def singletonClassConstructor[R0](using qctx: Quotes, rtpe0: Type[R0])(tree: qctx.reflect.Tree): Expr[ClassConstructor[R0]] = { type R <: R0 & Singleton (rtpe0: @unchecked) match { 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 72ba1be60e..ac26308b1d 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 @@ -56,17 +56,17 @@ class ConstructorContext[R0, Q <: Quotes, U <: ConstructorUtil[Q]](using val rTy lazy val constructorParamLists = parentTypesParameterized.map(t => t -> util.extractConstructorParamLists(t)) lazy val flatCtorParams = constructorParamLists.flatMap(_._2.iterator.flatten) - val methodDecls = { + lazy val methodDecls = { val allMembers = abstractMembers.map(m => util.MemberRepr(m.name, m.flags.is(Flags.Method), Some(m), resultTpe.memberType(m), false)) ++ refinementMethods util .processOverrides(allMembers) .sortBy(_.name) // sort alphabetically because Dotty order is undefined (does not return in definition order) } - def isFactoryOrTrait: Boolean = abstractMembers.nonEmpty || refinementMethods.nonEmpty - def isWireableTrait: Boolean = abstractMethodsWithParams.isEmpty && !resultTpeSyms.exists(_.flags.is(Flags.Sealed)) + def isFactoryOrTrait: Boolean = abstractMembers.nonEmpty || refinementMethods.nonEmpty + def implementTraitAutoImplBody( lamSym: Symbol, lamOnlyCtorArguments: List[Term], 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/MakeMacro.scala similarity index 71% rename from distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/AnyConstructorMacro.scala rename to distage/distage-core-api/src/main/scala-3/izumi/distage/constructors/MakeMacro.scala index 8305fb9b1f..82e3e0d175 100644 --- 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/MakeMacro.scala @@ -1,6 +1,6 @@ package izumi.distage.constructors -import izumi.distage.constructors.{AnyConstructor, AnyConstructorOptionalMakeDSL, ClassConstructorMacro, TraitConstructor, TraitConstructorMacro} +import izumi.distage.constructors.{ClassConstructor, ClassConstructorMacro, ClassConstructorOptionalMakeDSL} import izumi.distage.model.definition.dsl.ModuleDefDSL import izumi.distage.model.providers.Functoid import izumi.fundamentals.platform.language.CodePositionMaterializer @@ -9,43 +9,7 @@ import izumi.fundamentals.platform.exceptions.IzThrowable.toRichThrowable import scala.quoted.{Expr, Quotes, Type} -object AnyConstructorMacro { - - def make[R: Type](using qctx: Quotes): Expr[AnyConstructor[R]] = try { - import qctx.reflect.{*, given} - - val tpe0 = TypeRepr.of[R].dealias.simplified - val typeSymbol = tpe0.typeSymbol - - // FIXME remove redundant check across macros - val util = new ConstructorUtil[qctx.type]() - util.requireConcreteTypeConstructor(TypeRepr.of[R], "AnyConstructor") - - // FIXME remove redundant check across macros - lazy val context = new ConstructorContext[R, qctx.type, util.type](util) - - val tpeDeref = util.dereferenceTypeRef(tpe0) - if ((tpe0.classSymbol.isDefined && !(util.symbolIsTraitOrAbstract(typeSymbol)) && (tpeDeref match { - case _: Refinement => false; case _ => true - })) || { - tpeDeref match { case _: ConstantType | _: TermRef => true; case _ => false } - }) { - ClassConstructorMacro.makeImpl[R](util) - } else if ({ - context.isWireableTrait - }) { - TraitConstructorMacro.makeImpl[R](util, context) - } else if (context.isFactoryOrTrait) { - report.errorAndAbort( - s"""AnyConstructor failure: ${Type.show[R]} is a Factory, use makeFactory or fromFactory to wire factories.""".stripMargin - ) - } else { - report.errorAndAbort( - s"""AnyConstructor failure: couldn't generate a constructor for ${Type.show[R]}! - |It's neither a concrete class, nor a wireable trait or abstract class!""".stripMargin - ) - } - } catch { case t: scala.quoted.runtime.StopMacroExpansion => throw t; case t: Throwable => qctx.reflect.report.errorAndAbort(t.stacktraceString) } +object MakeMacro { def makeMethod[T: Type, BT: Type](using qctx: Quotes): Expr[BT] = try { import qctx.reflect.* @@ -59,7 +23,7 @@ object AnyConstructorMacro { } val outerClass = goGetOuterClass(Symbol.spliceOwner) - Expr.summon[AnyConstructorOptionalMakeDSL[T]] match { + Expr.summon[ClassConstructorOptionalMakeDSL[T]] match { case Some(ctor) => applyMake[T, BT](outerClass)('{ $ctor.provider }) case None => @@ -153,9 +117,9 @@ object AnyConstructorMacro { // FIXME remove redundant wrapping and .provider call val functoid: Expr[Functoid[T]] = if (fromLikeMethods.isEmpty) { - '{ ${ AnyConstructorMacro.make[T] }.provider } + '{ ${ ClassConstructorMacro.make[T] }.provider } } else { - '{ AnyConstructorOptionalMakeDSL.errorConstructor[T](${ Expr(Type.show[T]) }, ${ Expr(fromLikeMethods) }).provider } + '{ ClassConstructorOptionalMakeDSL.errorConstructor[T](${ Expr(Type.show[T]) }, ${ Expr(fromLikeMethods) }).provider } } val res = applyMake[T, BT](outerClass)(functoid) 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 ed66b10dcb..68cbb90687 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 @@ -11,23 +11,20 @@ import scala.annotation.experimental import izumi.fundamentals.platform.reflection.ReflectionUtil import zio.ZEnvironment +sealed trait AnyConstructorBase[T] extends Any with ClassConstructorOptionalMakeDSL[T] { + def provider: Functoid[T] +} + /** - * An implicitly summonable constructor for a type `T`, can generate constructors for: - * - * - concrete classes (using [[ClassConstructor]]) - * - traits and abstract classes ([[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]], using [[TraitConstructor]]) - * - * Since version `1.1.0`, does not generate constructors "factory-like" traits and abstract classes, instead use [[FactoryConstructor]]. - * - * Use [[ZEnvConstructor]] to generate constructors for `zio.ZEnvironment` values. + * An implicitly summonable constructor for a concrete class `T` * * @example * {{{ - * import distage.{AnyConstructor, Functoid, Injector, ModuleDef} + * import distage.{ClassConstructor, Functoid, Injector, ModuleDef} * * class A(val i: Int) * - * val constructor: Functoid[A] = AnyConstructor[A] + * val constructor: Functoid[A] = ClassConstructor[A] * * val lifecycle = Injector().produceGet[A](new ModuleDef { * make[A].from(constructor) @@ -42,22 +39,7 @@ import zio.ZEnvironment * * @return [[izumi.distage.model.providers.Functoid]][T] value */ -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] +final class ClassConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructorBase[T] object ClassConstructor { def apply[T](implicit ctor: ClassConstructor[T]): Functoid[T] = ctor.provider @@ -70,9 +52,10 @@ object ClassConstructor { * * @see [[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]] * @see [[izumi.distage.model.definition.impl]] recommended documenting annotation for use with [[TraitConstructor]] - * @see [[AnyConstructor]] + * + * @return [[izumi.distage.model.providers.Functoid]][T] value */ -final class TraitConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructor[T] +final class TraitConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructorBase[T] object TraitConstructor { def apply[T](implicit ctor: TraitConstructor[T]): Functoid[T] = ctor.provider @@ -94,9 +77,10 @@ object TraitConstructor { * * @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]] + * + * @return [[izumi.distage.model.providers.Functoid]][T] value */ -final class FactoryConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructor[T] +final class FactoryConstructor[T](val provider: Functoid[T]) extends AnyVal with AnyConstructorBase[T] object FactoryConstructor { def apply[T](implicit ctor: FactoryConstructor[T]): Functoid[T] = ctor.provider @@ -105,14 +89,13 @@ object FactoryConstructor { } /** - * An implicitly summonable constructor for a `T <: zio.ZEnvironment[A] with zio.ZEnvironment[B] with zio.ZEnvironment[C]` + * An implicitly summonable constructor for a `ZEnvironment[A & B & C]` * * `zio.ZEnvironment` heterogeneous map values may be used by ZIO or other Reader-like effects * * @see [[https://izumi.7mind.io/distage/basics.html#zio-environment-bindings ZIO Environment bindings]] - * @see [[AnyConstructor]] */ -final class ZEnvConstructor[T](val provider: Functoid[ZEnvironment[T]]) extends AnyVal with AnyConstructor[ZEnvironment[T]] +final class ZEnvConstructor[T](val provider: Functoid[ZEnvironment[T]]) extends AnyVal with AnyConstructorBase[ZEnvironment[T]] object ZEnvConstructor { def apply[T](implicit ctor: ZEnvConstructor[T]): Functoid[ZEnvironment[T]] = ctor.provider @@ -122,19 +105,19 @@ object ZEnvConstructor { inline implicit def materialize[T]: ZEnvConstructor[T] = ${ ZEnvConstructorMacro.make[T] } } -private[constructors] sealed trait AnyConstructorOptionalMakeDSL[T] extends Any { +private[constructors] sealed trait ClassConstructorOptionalMakeDSL[T] extends Any { def provider: Functoid[T] } -object AnyConstructorOptionalMakeDSL { - private[constructors] final class Impl[T](val provider: Functoid[T]) extends AnyVal with AnyConstructorOptionalMakeDSL[T] +object ClassConstructorOptionalMakeDSL { + private[constructors] final class Impl[T](val provider: Functoid[T]) extends AnyVal with ClassConstructorOptionalMakeDSL[T] - @inline def apply[T](functoid: Functoid[T]): AnyConstructorOptionalMakeDSL.Impl[T] = { - new AnyConstructorOptionalMakeDSL.Impl[T](functoid) + @inline def apply[T](functoid: Functoid[T]): ClassConstructorOptionalMakeDSL.Impl[T] = { + new ClassConstructorOptionalMakeDSL.Impl[T](functoid) } - def errorConstructor[T](tpe: String, nonWhitelistedMethods: List[String]): AnyConstructorOptionalMakeDSL.Impl[T] = { - AnyConstructorOptionalMakeDSL[T](Functoid.lift[Nothing](throwError(tpe, nonWhitelistedMethods))) + def errorConstructor[T](tpe: String, nonWhitelistedMethods: List[String]): ClassConstructorOptionalMakeDSL.Impl[T] = { + ClassConstructorOptionalMakeDSL[T](Functoid.lift[Nothing](throwError(tpe, nonWhitelistedMethods))) } def throwError(tpe: String, nonWhitelistedMethods: List[String]): Nothing = { 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 d692eac75c..4d456aba69 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,7 +1,7 @@ package izumi.distage.model.definition.dsl -import izumi.distage.constructors.AnyConstructorMacro +import izumi.distage.constructors.MakeMacro trait AbstractBindingDefDSLMacro[BindDSL[_]] { - inline final protected[this] def make[T]: BindDSL[T] = ${ AnyConstructorMacro.makeMethod[T, BindDSL[T]] } + inline final protected[this] def make[T]: BindDSL[T] = ${ MakeMacro.makeMethod[T, BindDSL[T]] } } diff --git a/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/AnyKindShim.scala b/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/AnyKindShim.scala index 18067c67b5..5bfed3feef 100644 --- a/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/AnyKindShim.scala +++ b/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/AnyKindShim.scala @@ -1,7 +1,5 @@ package izumi.distage.model.definition.dsl -trait AnyKindShim { +object AnyKindShim { type LifecycleF = [_] =>> Any } - -object AnyKindShim extends AnyKindShim {} diff --git a/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/LifecycleTagLowPriority.scala b/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/LifecycleTagLowPriority.scala index 8260edfb55..ac0b4099d3 100644 --- a/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/LifecycleTagLowPriority.scala +++ b/distage/distage-core-api/src/main/scala-3/izumi/distage/model/definition/dsl/LifecycleTagLowPriority.scala @@ -1,10 +1,5 @@ package izumi.distage.model.definition.dsl -import izumi.distage.model.definition.dsl.AnyKindShim +trait LifecycleTagLowPriority -trait LifecycleTagLowPriority extends AnyKindShim {} - -// fixme wtf -//trait TrifunctorHasLifecycleTagLowPriority1 extends AnyKindShim {} - -trait ZIOEnvLifecycleTagLowPriority1 extends AnyKindShim {} +trait ZIOEnvLifecycleTagLowPriority1 diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/LocalContext.scala b/distage/distage-core-api/src/main/scala/izumi/distage/LocalContext.scala deleted file mode 100644 index 0b35909602..0000000000 --- a/distage/distage-core-api/src/main/scala/izumi/distage/LocalContext.scala +++ /dev/null @@ -1,81 +0,0 @@ -package izumi.distage - -import izumi.distage.model.definition.Identifier -import izumi.functional.lifecycle.Lifecycle -import izumi.distage.model.plan.Plan -import izumi.fundamentals.platform.language.CodePositionMaterializer -import izumi.reflect.Tag - -trait AnyLocalContext[F[_]] - -trait LocalContext[F[_], R] extends AnyLocalContext[F] { - def provide[T: Tag](value: T)(implicit pos: CodePositionMaterializer): LocalContext[F, R] - def provideNamed[T: Tag](id: Identifier, value: T)(implicit pos: CodePositionMaterializer): LocalContext[F, R] - def produceRun(): F[R] - - def produce(): Lifecycle[F, R] - def plan: Plan - - @inline final def provide[T1: Tag, T2: Tag](v1: T1, v2: T2)(implicit pos: CodePositionMaterializer): LocalContext[F, R] = { - provide(v1).provide(v2) - } - - @inline final def provide[T1: Tag, T2: Tag, T3: Tag](v1: T1, v2: T2, v3: T3)(implicit pos: CodePositionMaterializer): LocalContext[F, R] = { - provide(v1, v2).provide(v3) - } - - @inline final def provide[T1: Tag, T2: Tag, T3: Tag, T4: Tag](v1: T1, v2: T2, v3: T3, v4: T4)(implicit pos: CodePositionMaterializer): LocalContext[F, R] = { - provide(v1, v2, v3).provide(v4) - } - - @inline final def provide[T1: Tag, T2: Tag, T3: Tag, T4: Tag, T5: Tag]( - v1: T1, - v2: T2, - v3: T3, - v4: T4, - v5: T5, - )(implicit pos: CodePositionMaterializer - ): LocalContext[F, R] = { - provide(v1, v2, v3, v4).provide(v5) - } - - @inline final def provide[T1: Tag, T2: Tag, T3: Tag, T4: Tag, T5: Tag, T6: Tag]( - v1: T1, - v2: T2, - v3: T3, - v4: T4, - v5: T5, - v6: T6, - )(implicit pos: CodePositionMaterializer - ): LocalContext[F, R] = { - provide(v1, v2, v3, v4, v5).provide(v6) - } - - @inline final def provide[T1: Tag, T2: Tag, T3: Tag, T4: Tag, T5: Tag, T6: Tag, T7: Tag]( - v1: T1, - v2: T2, - v3: T3, - v4: T4, - v5: T5, - v6: T6, - v7: T7, - )(implicit pos: CodePositionMaterializer - ): LocalContext[F, R] = { - provide(v1, v2, v3, v4, v5, v6).provide(v7) - } - - @inline final def provide[T1: Tag, T2: Tag, T3: Tag, T4: Tag, T5: Tag, T6: Tag, T7: Tag, T8: Tag]( - v1: T1, - v2: T2, - v3: T3, - v4: T4, - v5: T5, - v6: T6, - v7: T7, - v8: T8, - )(implicit pos: CodePositionMaterializer - ): LocalContext[F, R] = { - provide(v1, v2, v3, v4, v5, v6, v7).provide(v8) - } - -} diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/Subcontext.scala b/distage/distage-core-api/src/main/scala/izumi/distage/Subcontext.scala new file mode 100644 index 0000000000..904857e85c --- /dev/null +++ b/distage/distage-core-api/src/main/scala/izumi/distage/Subcontext.scala @@ -0,0 +1,30 @@ +package izumi.distage + +import izumi.distage.model.definition.Identifier +import izumi.functional.lifecycle.Lifecycle +import izumi.distage.model.plan.Plan +import izumi.functional.quasi.QuasiIO +import izumi.fundamentals.platform.functional.Identity +import izumi.fundamentals.platform.language.CodePositionMaterializer +import izumi.reflect.{Tag, TagK} + +/** @see [[https://izumi.7mind.io/distage/basics.html#subcontexts Subcontexts feature]] */ +trait Subcontext[A] { + def produce[F[_]: QuasiIO: TagK](): Lifecycle[F, A] + + /** + * Same as `.produce[F]().use(f)` + * + * @note Resources allocated by the subcontext will be closed after `f` exits. + * Use `produce` if you need to extend the lifetime of the Subcontext's resources. + */ + def produceRun[F[_]: QuasiIO: TagK, B](f: A => F[B]): F[B] + final def produceRun[B](f: A => B): B = produceRun[Identity, B](f) + + def provide[T: Tag](value: T)(implicit pos: CodePositionMaterializer): Subcontext[A] + def provide[T: Tag](name: Identifier)(value: T)(implicit pos: CodePositionMaterializer): Subcontext[A] + + def plan: Plan + + def map[B: Tag](f: A => B): Subcontext[B] +} diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala index 88394bbc3c..8db1bd1bdf 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala @@ -3,7 +3,7 @@ package izumi.distage.constructors import izumi.fundamentals.platform.properties /** - * Java properties that control debug output of [[AnyConstructor]] & [[izumi.distage.model.providers.Functoid]] macros + * Java properties that control debug output of [[izumi.distage.constructors]] & [[izumi.distage.model.providers.Functoid]] macros * * @see [[DebugProperties]] */ diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/package.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/package.scala new file mode 100644 index 0000000000..bd5d34ff3e --- /dev/null +++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/package.scala @@ -0,0 +1,18 @@ +package izumi.distage + +package object constructors { + /** + * @deprecated Since version `1.2.0`, default behavior for `make[T]` changed to not implicitly generate constructors + * for traits and abstract classes, or for "factory-like" traits and abstract classes, but only for concrete classes. + * + * `AnyConstructor` as a proxy for default behavior of `make` has been removed in favor of [[ClassConstructor]], + * since it is the default behavior of `make` now. It is now recommended to use [[ClassConstructor]], + * [[TraitConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits]]) + * and [[FactoryConstructor]] ([[https://izumi.7mind.io/distage/basics#auto-factories Auto-Factories]]) explicitly + * instead of using [[AnyConstructor]]. + */ + @deprecated("Removed since 1.2.0. Use ClassConstructor instead.") + type AnyConstructor[T] = ClassConstructor[T] + @deprecated("Removed since 1.2.0. Use ClassConstructor instead.") + lazy val AnyConstructor: ClassConstructor.type = ClassConstructor +} diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Binding.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Binding.scala index bd160ffe0e..4f22c01818 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Binding.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Binding.scala @@ -1,6 +1,6 @@ package izumi.distage.model.definition -import izumi.distage.constructors.AnyConstructor +import izumi.distage.constructors.{ClassConstructor} import izumi.distage.model.definition.Binding.GroupingKey import izumi.distage.model.plan.repr.{BindingFormatter, KeyFormatter} import izumi.distage.model.providers.Functoid @@ -83,8 +83,8 @@ object Binding { } implicit final class WithImplementation[R](private val binding: ImplBinding { def withImplDef(implDef: ImplDef): R }) extends AnyVal { - def withImpl[T: Tag: AnyConstructor]: R = - withImpl[T](AnyConstructor[T]) + def withImpl[T: Tag: ClassConstructor]: R = + withImpl[T](ClassConstructor[T]) def withImpl[T: Tag](instance: T): R = binding.withImplDef(ImplDef.InstanceImpl(SafeType.get[T], instance)) diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Bindings.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Bindings.scala index 60df0987dc..04fb3c1faf 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Bindings.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/Bindings.scala @@ -1,6 +1,7 @@ package izumi.distage.model.definition -import izumi.distage.constructors.AnyConstructor +import izumi.distage.Subcontext +import izumi.distage.constructors.{ClassConstructor, FactoryConstructor, TraitConstructor} import izumi.distage.model.definition.Binding.{EmptySetBinding, SetElementBinding, SingletonBinding} import izumi.distage.model.providers.Functoid import izumi.distage.model.reflection.DIKey.SetKeyMeta @@ -9,11 +10,17 @@ import izumi.fundamentals.platform.language.CodePositionMaterializer import izumi.reflect.Tag object Bindings { - def binding[T: Tag: AnyConstructor](implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] = - provider[T](AnyConstructor[T]) + def binding[T: Tag: ClassConstructor](implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] = + provider[T](ClassConstructor[T]) - def binding[T: Tag, I <: T: Tag: AnyConstructor](implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] = - provider[T](AnyConstructor[I]) + def bindingTrait[T: Tag: TraitConstructor](implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] = + provider[T](TraitConstructor[T]) + + def bindingFactory[T: Tag: FactoryConstructor](implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] = + provider[T](FactoryConstructor[T]) + + def binding[T: Tag, I <: T: Tag: ClassConstructor](implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] = + provider[T](ClassConstructor[I]) def binding[T: Tag, I <: T: Tag](instance: I)(implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] = SingletonBinding(DIKey.get[T], ImplDef.InstanceImpl(SafeType.get[I], instance), Set.empty, pos.get.position) @@ -27,11 +34,28 @@ object Bindings { def provider[T: Tag](function: Functoid[T])(implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] = SingletonBinding(DIKey.get[T], ImplDef.ProviderImpl(function.get.ret, function.get), Set.empty, pos.get.position) + def subcontext[T: Tag]( + submodule: ModuleBase, + functoid: Functoid[T], + externalKeys: Set[DIKey], + )(implicit pos: CodePositionMaterializer + ): SingletonBinding[DIKey.TypeKey] = { + SingletonBinding(DIKey.get[Subcontext[T]], ImplDef.ContextImpl(functoid.get.ret, functoid.get, submodule, externalKeys), Set.empty, pos.get.position) + } + def emptySet[T](implicit tag: Tag[Set[T]], pos: CodePositionMaterializer): EmptySetBinding[DIKey.TypeKey] = EmptySetBinding(DIKey.get[Set[T]], Set.empty, pos.get.position) - def setElement[T: Tag, I <: T: Tag: AnyConstructor](implicit pos: CodePositionMaterializer): SetElementBinding = { - setElementProvider[T](AnyConstructor[I]) + def setElement[T: Tag, I <: T: Tag: ClassConstructor](implicit pos: CodePositionMaterializer): SetElementBinding = { + setElementProvider[T](ClassConstructor[I]) + } + + def setElementTrait[T: Tag, I <: T: Tag: TraitConstructor](implicit pos: CodePositionMaterializer): SetElementBinding = { + setElementProvider[T](TraitConstructor[I]) + } + + def setElementFactory[T: Tag, I <: T: Tag: FactoryConstructor](implicit pos: CodePositionMaterializer): SetElementBinding = { + setElementProvider[T](FactoryConstructor[I]) } def setElement[T: Tag, I <: T: Tag](instance: I)(implicit pos: CodePositionMaterializer): SetElementBinding = { diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ImplDef.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ImplDef.scala index e0e11bff54..691b88dea1 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ImplDef.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ImplDef.scala @@ -1,7 +1,6 @@ package izumi.distage.model.definition import izumi.distage.model.plan.repr.{BindingFormatter, KeyFormatter} -import izumi.distage.model.providers.Functoid import izumi.distage.model.reflection.{DIKey, Provider, SafeType} import izumi.fundamentals.platform.cache.CachedProductHashcode @@ -16,7 +15,7 @@ object ImplDef { final case class ReferenceImpl(implType: SafeType, key: DIKey, weak: Boolean) extends DirectImplDef final case class InstanceImpl(implType: SafeType, instance: Any) extends DirectImplDef final case class ProviderImpl(implType: SafeType, function: Provider) extends DirectImplDef - final case class ContextImpl(implType: SafeType, function: Functoid[Any], module: ModuleBase, externalKeys: Set[DIKey]) extends DirectImplDef + final case class ContextImpl(implType: SafeType, extractingFunction: Provider, module: ModuleBase, externalKeys: Set[DIKey]) extends DirectImplDef sealed trait RecursiveImplDef extends ImplDef final case class EffectImpl(implType: SafeType, effectHKTypeCtor: SafeType, effectImpl: DirectImplDef) extends RecursiveImplDef diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/LocalContextDef.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/LocalContextDef.scala deleted file mode 100644 index 868eb89c0e..0000000000 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/LocalContextDef.scala +++ /dev/null @@ -1,8 +0,0 @@ -package izumi.distage.model.definition - -import izumi.distage.model.providers.Functoid - -/** - * Can be obtained through [[ModuleBase.running]] implicit extension - */ -case class LocalContextDef[R](module: ModuleBase, function: Functoid[R]) diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/LocatorDef.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/LocatorDef.scala index d560ce2957..2e88905a6d 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/LocatorDef.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/LocatorDef.scala @@ -31,9 +31,8 @@ trait LocatorDef extends AbstractLocator with AbstractBindingDefDSL[LocatorDef.B override def finalizers[F[_]: TagK]: immutable.Seq[PlanInterpreter.Finalizer[F]] = Nil - override private[definition] final def _bindDSL[T](ref: SingletonRef): LocatorDef.BindDSL[T] = new LocatorDef.BindDSL[T](ref, ref.key) - override private[definition] final def _bindDSLAfterFrom[T](ref: SingletonRef): LocatorDef.BindDSLUnnamedAfterFrom[T] = - new LocatorDef.BindDSLUnnamedAfterFrom(ref, ref.key) + override private[definition] final def _bindDSL[T](ref: SingletonRef): LocatorDef.BindDSL[T] = new LocatorDef.BindDSL[T](ref) + override private[definition] final def _bindDSLAfterFrom[T](ref: SingletonRef): LocatorDef.BindDSLUnnamedAfterFrom[T] = new LocatorDef.BindDSLUnnamedAfterFrom(ref) override private[definition] final def _setDSL[T](ref: SetRef): LocatorDef.SetDSL[T] = new LocatorDef.SetDSL[T](ref) protected def initialState: mutable.ArrayBuffer[BindingRef] = mutable.ArrayBuffer.empty @@ -88,41 +87,36 @@ object LocatorDef { // DSL state machine - final class BindDSL[T](protected val mutableState: SingletonRef, protected val key: DIKey.TypeKey) - extends BindDSLBase[T, BindDSLUnnamedAfterFrom[T]] - with BindDSLMutBase[T] { + final class BindDSL[T](protected val mutableState: SingletonRef) extends BindDSLBase[T, BindDSLUnnamedAfterFrom[T]] with BindDSLMutBase[T] { def named(name: Identifier): BindNamedDSL[T] = - addOp(SetId(name))(new BindNamedDSL[T](_, key.named(name))) + addOp(SetId(name))(new BindNamedDSL[T](_)) override protected def bind(impl: ImplDef): BindDSLUnnamedAfterFrom[T] = - addOp(SetImpl(impl))(new BindDSLUnnamedAfterFrom[T](_, key)) + addOp(SetImpl(impl))(new BindDSLUnnamedAfterFrom[T](_)) } - final class BindNamedDSL[T](protected val mutableState: SingletonRef, protected val key: DIKey.IdKey[?]) - extends BindDSLBase[T, BindDSLNamedAfterFrom[T]] - with BindDSLMutBase[T] { + final class BindNamedDSL[T](protected val mutableState: SingletonRef) extends BindDSLBase[T, BindDSLNamedAfterFrom[T]] with BindDSLMutBase[T] { override protected def bind(impl: ImplDef): BindDSLNamedAfterFrom[T] = - addOp(SetImpl(impl))(new BindDSLNamedAfterFrom[T](_, key)) + addOp(SetImpl(impl))(new BindDSLNamedAfterFrom[T](_)) } - final class BindDSLUnnamedAfterFrom[T](override protected val mutableState: SingletonRef, override protected val key: DIKey.TypeKey) extends BindDSLMutBase[T] { + final class BindDSLUnnamedAfterFrom[T](override protected val mutableState: SingletonRef) extends BindDSLMutBase[T] { def named(name: Identifier): BindNamedDSL[T] = - addOp(SetId(name))(new BindNamedDSL[T](_, key.named(name))) + addOp(SetId(name))(new BindNamedDSL[T](_)) } - final class BindDSLNamedAfterFrom[T](override protected val mutableState: SingletonRef, override protected val key: DIKey.IdKey[?]) extends BindDSLMutBase[T] - final class BindDSLAfterAlias[T](override protected val mutableState: SingletonRef, override protected val key: DIKey) extends BindDSLMutBase[T] + final class BindDSLNamedAfterFrom[T](override protected val mutableState: SingletonRef) extends BindDSLMutBase[T] + final class BindDSLAfterAlias[T](override protected val mutableState: SingletonRef) extends BindDSLMutBase[T] sealed trait BindDSLMutBase[T] { protected[this] def mutableState: SingletonRef - protected[this] def key: DIKey def aliased[T1 >: T: Tag](implicit pos: CodePositionMaterializer): BindDSLAfterAlias[T] = { - addOp(AliasTo(DIKey.get[T1], pos.get.position))(new BindDSLAfterAlias[T](_, key)) + addOp(AliasTo(DIKey.get[T1], pos.get.position))(new BindDSLAfterAlias[T](_)) } def aliased[T1 >: T: Tag](name: Identifier)(implicit pos: CodePositionMaterializer): BindDSLAfterAlias[T] = { - addOp(AliasTo(DIKey.get[T1].named(name), pos.get.position))(new BindDSLAfterAlias[T](_, key)) + addOp(AliasTo(DIKey.get[T1].named(name), pos.get.position))(new BindDSLAfterAlias[T](_)) } protected[this] final def addOp[R](op: SingletonInstruction)(newState: SingletonRef => R): R = { diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleBase.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleBase.scala index ae33ad556c..04c45b0366 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleBase.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleBase.scala @@ -2,12 +2,10 @@ package izumi.distage.model.definition import cats.Hash import cats.kernel.{BoundedSemilattice, PartialOrder} -import izumi.distage.model.providers.Functoid import izumi.distage.model.reflection.DIKey import izumi.fundamentals.collections.IzCollections.* import izumi.fundamentals.orphans.{`cats.kernel.BoundedSemilattice`, `cats.kernel.PartialOrder with cats.kernel.Hash`} import izumi.fundamentals.platform.cache.CachedHashcode -import izumi.reflect.Tag import scala.annotation.unused @@ -76,10 +74,6 @@ object ModuleBase extends ModuleBaseLowPriorityInstances { def flatMap[T <: ModuleBase](f: Binding => Iterable[Binding])(implicit T: ModuleMake.Aux[S, T]): T = { T.make(module.bindings.flatMap(f)) } - - def running[R](function: Functoid[R]): LocalContextDef[R] = LocalContextDef(module, function) - - def exporting[R: Tag]: LocalContextDef[R] = LocalContextDef(module, Functoid.identity[R]) } implicit final class ModuleDefMorph(private val module: ModuleBase) extends AnyVal { diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleDef.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleDef.scala index 55e8092c85..bc7b668c72 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleDef.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleDef.scala @@ -22,7 +22,11 @@ import izumi.distage.model.definition.dsl.ModuleDefDSL * * Singleton bindings: * - `make[X]` = create X using its constructor + * - `makeTrait[X]` = create an abstract class or a trait `X` using [[izumi.distage.constructors.TraitConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]]) + * - `makeFactory[X]` = create a "factory-like" abstract class or a trait `X` using [[izumi.distage.constructors.FactoryConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]]) * - `make[X].from[XImpl]` = bind X to its subtype XImpl using XImpl's constructor + * - `make[X].fromTrait[XImpl]` = bind X to its abstract class or a trait subtype XImpl, deriving constructor using [[izumi.distage.constructors.TraitConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]]) + * - `make[X].fromFactory[XImpl]` = bind X to its "factory-like" abstract class or a trait subtype XImpl, deriving constructor using [[izumi.distage.constructors.FactoryConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]]) * - `make[X].from(myX)` = bind X to an already existing instance `myX` * - `make[X].from { y: Y => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]] requesting an Y parameter * - `make[X].from { y: Y @Id("special") => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]], requesting a named "special" Y parameter diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala index ddc7abdaf0..fc6d07fc1a 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/AbstractBindingDefDSL.scala @@ -1,6 +1,6 @@ package izumi.distage.model.definition.dsl -import izumi.distage.constructors.FactoryConstructor +import izumi.distage.constructors.{FactoryConstructor, TraitConstructor} import izumi.distage.model.definition.* import izumi.distage.model.definition.Binding.{EmptySetBinding, ImplBinding, SetElementBinding, SingletonBinding} import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.SetElementInstruction.ElementAddTags @@ -25,7 +25,7 @@ trait AbstractBindingDefDSL[BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]] extends private[definition] def _setDSL[T](ref: SetRef): SetDSL[T] private[definition] def frozenState: Iterator[Binding] = { - mutableState.iterator.flatMap(_.interpret) + mutableState.iterator.flatMap(_.interpret()) } protected[this] def _registered[T <: BindingRef](bindingRef: T): T = { @@ -33,6 +33,31 @@ trait AbstractBindingDefDSL[BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]] extends bindingRef } + final protected[this] def _make[T: Tag](provider: Functoid[T])(implicit pos: CodePositionMaterializer): BindDSL[T] = { + val ref = _registered(new SingletonRef(Bindings.provider[T](provider))) + _bindDSL[T](ref) + } + + /** @see [[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]] */ + final protected[this] def makeTrait[T: Tag: TraitConstructor]: BindDSLAfterFrom[T] = { + val ref = _registered(new SingletonRef(Bindings.provider[T](TraitConstructor[T]))) + _bindDSLAfterFrom[T](ref) + } + + /** @see [[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]] */ + final protected[this] def makeFactory[T: Tag: FactoryConstructor]: BindDSLAfterFrom[T] = { + val ref = _registered(new SingletonRef(Bindings.provider[T](FactoryConstructor[T]))) + _bindDSLAfterFrom[T](ref) + } + + /** @see [[https://izumi.7mind.io/distage/basics.html#subcontexts Subcontexts feature]] */ + final protected[this] def makeSubcontext[T: Tag](submodule: ModuleBase): SubcontextDSL[T] = { + val ref = _registered(new SubcontextRef(Bindings.subcontext[T](submodule, Functoid.identity[T], Set.empty))) + new SubcontextDSL[T](ref) + } + /** @see [[https://izumi.7mind.io/distage/basics.html#subcontexts Subcontexts feature]] */ + final protected[this] def makeSubcontext[T: Tag]: SubcontextDSL[T] = makeSubcontext[T](ModuleBase.empty) + /** * Set bindings are useful for implementing event listeners, plugins, hooks, http routes, etc. * @@ -144,17 +169,6 @@ trait AbstractBindingDefDSL[BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]] extends ref } - final protected[this] def _make[T: Tag](provider: Functoid[T])(implicit pos: CodePositionMaterializer): BindDSL[T] = { - val ref = _registered(new SingletonRef(Bindings.provider[T](provider))) - _bindDSL[T](ref) - } - - /** @see [[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]] */ - final protected[this] def makeFactory[T: Tag: FactoryConstructor]: BindDSLAfterFrom[T] = { - val ref = _registered(new SingletonRef(Bindings.provider[T](FactoryConstructor[T]))) - _bindDSLAfterFrom[T](ref) - } - /** * Use this to create utility functions that add bindings mutably to the current module, * as opposed to creating new modules and [[IncludesDSL.include including]] them. @@ -162,13 +176,13 @@ trait AbstractBindingDefDSL[BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]] extends * Example: * * {{{ - * import distage.{AnyConstructor, Tag, ModuleDef} + * import distage.{ClassConstructor, Tag, ModuleDef} * import izumi.distage.model.definition.dsl.ModuleDefDSL * * trait RegisteredComponent * class RegisteredComponentImpl extends RegisteredComponent * - * def addAndRegister[T <: RegisteredComponent: Tag: AnyConstructor](implicit mutateModule: ModuleDefDSL#MutationContext): Unit = { + * def addAndRegister[T <: RegisteredComponent: Tag: ClassConstructor](implicit mutateModule: ModuleDefDSL#MutationContext): Unit = { * new mutateModule.dsl { * make[T] * @@ -184,6 +198,12 @@ trait AbstractBindingDefDSL[BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]] extends */ final class MutationContext { abstract class dsl extends AbstractBindingDefDSLMacro[BindDSL] { + final protected[this] def _make[T: Tag](provider: Functoid[T])(implicit pos: CodePositionMaterializer): BindDSL[T] = self._make[T](provider) + final protected[this] def makeTrait[T: Tag: TraitConstructor]: BindDSLAfterFrom[T] = self.makeTrait[T] + final protected[this] def makeFactory[T: Tag: FactoryConstructor]: BindDSLAfterFrom[T] = self.makeFactory[T] + final protected[this] def makeSubcontext[T: Tag](submodule: ModuleBase): SubcontextDSL[T] = self.makeSubcontext[T](submodule) + final protected[this] def makeSubcontext[T: Tag]: SubcontextDSL[T] = self.makeSubcontext[T] + final protected[this] def many[T](implicit tag: Tag[Set[T]], pos: CodePositionMaterializer): SetDSL[T] = self.many[T] final protected[this] def addImplicit[T: Tag](implicit instance: T, pos: CodePositionMaterializer): BindDSLAfterFrom[T] = self.addImplicit[T] @@ -191,10 +211,6 @@ trait AbstractBindingDefDSL[BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]] extends final protected[this] def todo[T: Tag](implicit pos: CodePositionMaterializer): BindDSLAfterFrom[T] = self.todo[T] final protected[this] def modify[T]: ModifyDSL[T, BindDSL, BindDSLAfterFrom, SetDSL] = self.modify[T] - final protected[this] def _make[T: Tag](provider: Functoid[T])(implicit pos: CodePositionMaterializer): BindDSL[T] = self._make[T](provider) - - final protected[this] def makeFactory[T: Tag: FactoryConstructor]: BindDSLAfterFrom[T] = self.makeFactory[T] - /** * Avoid `discarded non-Unit value` warning. * @@ -209,19 +225,14 @@ trait AbstractBindingDefDSL[BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]] extends object AbstractBindingDefDSL { - final class ModifyDSL[T, BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]](private val dsl: AbstractBindingDefDSL[BindDSL, BindDSLAfterFrom, SetDSL]) extends AnyVal { - def named(name: Identifier): ModifyNamedDSL[T, BindDSL, BindDSLAfterFrom, SetDSL] = { - new ModifyNamedDSL(dsl, name) - } + sealed abstract class ModifyDSLBase[T] { + + def by(f: Functoid[T] => Functoid[T])(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] def apply(f: T => T)(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { by(_.map(f)) } - def by(f: Functoid[T] => Functoid[T])(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { - new ModifyTaggingDSL(dsl._modify(DIKey.get[T])(f)(pos)) - } - def addDependency[B: Tag](implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { by(_.addDependency(DIKey[B])) } @@ -237,74 +248,148 @@ object AbstractBindingDefDSL { def addDependencies(keys: Iterable[DIKey])(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { by(_.addDependencies(keys)) } - } - final class ModifyNamedDSL[T, BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]](dsl: AbstractBindingDefDSL[BindDSL, BindDSLAfterFrom, SetDSL], name: Identifier) { - def apply(f: T => T)(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { - by(_.map(f)) + def annotateParameter[P: Tag](name: Identifier)(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { + by(_.annotateParameter[P](name)) } - def by(f: Functoid[T] => Functoid[T])(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { - new ModifyTaggingDSL(dsl._modify(DIKey.get[T].named(name))(f)(pos)) - } + } - def addDependency[B: Tag](implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { - by(_.addDependency(DIKey[B])) - } + final class ModifyDSL[T, BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]](dsl: AbstractBindingDefDSL[BindDSL, BindDSLAfterFrom, SetDSL]) extends ModifyDSLBase[T] { - def addDependency[B: Tag](name: Identifier)(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { - by(_.addDependency(DIKey[B](name))) + def named(name: Identifier): ModifyNamedDSL[T, BindDSL, BindDSLAfterFrom, SetDSL] = { + new ModifyNamedDSL(dsl, name) } - def addDependency(key: DIKey)(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { - by(_.addDependency(key)) + override def by(f: Functoid[T] => Functoid[T])(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { + val key = DIKey.get[T] + new ModifyTaggingDSL(dsl._modify(key)(f)(pos)) } - def addDependencies(keys: Iterable[DIKey])(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { - by(_.addDependencies(keys)) + } + + final class ModifyNamedDSL[T, BindDSL[_], BindDSLAfterFrom[_], SetDSL[_]]( + dsl: AbstractBindingDefDSL[BindDSL, BindDSLAfterFrom, SetDSL], + name: Identifier, + ) extends ModifyDSLBase[T] { + + override def by(f: Functoid[T] => Functoid[T])(implicit tag: Tag[T], pos: CodePositionMaterializer): ModifyTaggingDSL[T] = { + val key = DIKey.get[T].named(name) + new ModifyTaggingDSL(dsl._modify(key)(f)(pos)) } + } - final class ModifyTaggingDSL[T](private val ref: SingletonRef) extends AnyVal { + final class ModifyTaggingDSL[T](private val mutableState: SingletonRef) extends AnyVal with AddDependencyDSL[T, ModifyTaggingDSL[T]] { + def tagged(tags: BindingTag*): ModifyTaggingDSL[T] = { - new ModifyTaggingDSL(ref.append(AddTags(tags.toSet))) + new ModifyTaggingDSL(mutableState.append(AddTags(tags.toSet))) } def by(f: Functoid[T] => Functoid[T]): ModifyTaggingDSL[T] = { - new ModifyTaggingDSL[T](ref.append(Modify(f))) + new ModifyTaggingDSL[T](mutableState.append(Modify(f))) } - def map(f: T => T): ModifyTaggingDSL[T] = { + def modify(f: T => T): ModifyTaggingDSL[T] = { by(_.mapSame(f)) } - def addDependency[B: Tag]: ModifyTaggingDSL[T] = { - by(_.addDependency(DIKey.get[B])) + @deprecated("Renamed to .modify", "1.2.0") + def map(f: T => T): ModifyTaggingDSL[T] = { + modify(f) } - def addDependency(key: DIKey): ModifyTaggingDSL[T] = { - by(_.addDependency(key)) + override protected[this] def _modifyBy(f: Functoid[T] => Functoid[T]): ModifyTaggingDSL[T] = by(f) + } + + trait AddDependencyDSL[T, Self] extends Any { + protected[this] def _modifyBy(f: Functoid[T] => Functoid[T]): Self + + def addDependency[B: Tag]: Self = { + _modifyBy(_.addDependency[B]) } - def addDependencies(keys: Iterable[DIKey]): ModifyTaggingDSL[T] = { - by(_.addDependencies(keys)) + def addDependency[B: Tag](name: Identifier): Self = { + _modifyBy(_.addDependency[B](name)) + } + + def addDependency(key: DIKey): Self = { + _modifyBy(_.addDependency(key)) + } + + def addDependencies(keys: Iterable[DIKey]): Self = { + _modifyBy(_.addDependencies(keys)) + } + + def annotateParameter[P: Tag](name: Identifier): Self = { + _modifyBy(_.annotateParameter[P](name)) + } + } + + final class SubcontextDSL[T](override protected val mutableState: SubcontextRef) extends SubcontextDSLBase[T, SubcontextDSL[T]] { + + def named(name: Identifier): SubcontextNamedDSL[T] = { + addOp(SubcontextInstruction.SetId(name))(new SubcontextNamedDSL[T](_)) + } + + override protected[this] def toSame: SubcontextRef => SubcontextDSL[T] = new SubcontextDSL[T](_) + } + + final class SubcontextNamedDSL[T](override protected val mutableState: SubcontextRef) extends SubcontextDSLBase[T, SubcontextNamedDSL[T]] { + override protected[this] def toSame: SubcontextRef => SubcontextNamedDSL[T] = new SubcontextNamedDSL[T](_) + } + + sealed abstract class SubcontextDSLBase[T, Self] { + protected[this] def mutableState: SubcontextRef + protected[this] def toSame: SubcontextRef => Self + + final def tagged(tags: BindingTag*): Self = { + addOp(SubcontextInstruction.AddTags(tags.toSet))(toSame) + } + + final def withSubmodule(submodule: ModuleBase): Self = { + addOp(SubcontextInstruction.AddSubmodule(submodule))(toSame) + } + + final def extractWith(f: Functoid[T]): Self = { + addOp(SubcontextInstruction.SetExtractor(f))(toSame) + } + + final def localDependency[B: Tag]: Self = { + localDependency(DIKey[B]) + } + + final def localDependency[B: Tag](name: Identifier): Self = { + localDependency(DIKey[B](name)) + } + + final def localDependency(key: DIKey): Self = { + localDependencies(key :: Nil) + } + + final def localDependencies(keys: Iterable[DIKey]): Self = { + addOp(SubcontextInstruction.AddLocalDependencies(keys))(toSame) + } + + protected[this] final def addOp[R](op: SubcontextInstruction)(newState: SubcontextRef => R): R = { + newState(mutableState.append(op)) } } trait BindingRef { - def interpret: collection.Seq[Binding] + def interpret(): collection.Seq[Binding] } final class SingletonRef(initial: SingletonBinding[DIKey.TypeKey], ops: mutable.Queue[SingletonInstruction] = mutable.Queue.empty) extends BindingRef { - override def interpret: collection.Seq[ImplBinding] = { + override def interpret(): collection.Seq[ImplBinding] = { var b: SingletonBinding[DIKey.BasicKey] = initial var refs: List[SingletonBinding[DIKey.BasicKey]] = Nil val sortedOps = ops.sortBy { case _: SetImpl => 0 case _: AddTags => 0 case _: SetId => 0 - case _: Modify[?] => 1 - case _: SetIdFromImplName => 2 + case _: SetIdFromImplName => 1 + case _: Modify[?] => 2 case _: AliasTo => 3 } sortedOps.foreach { @@ -317,7 +402,7 @@ object AbstractBindingDefDSL { b = b.withTarget(key) case SetIdFromImplName() => b = b.withTarget(DIKey.IdKey(b.key.tpe, b.implementation.implType.tag.longNameWithPrefix.toLowerCase)) - case Modify(functoidModifier) => + case Modify(functoidModifier: (Functoid[t] => Functoid[u])) => b.implementation match { case ImplDef.ProviderImpl(implType, function) => val newProvider = functoidModifier(Functoid(function)).get @@ -325,10 +410,14 @@ object AbstractBindingDefDSL { b = b.withImplDef(ImplDef.ProviderImpl(implType, newProvider)) } else { throw new InvalidFunctoidModifier( - s"Cannot apply invalid Functoid modifier $functoidModifier, new return type `${newProvider.ret}` is not a subtype of the old return type `${function.ret}`" + s"Cannot apply invalid Functoid modifier $functoidModifier, new return type `${newProvider.ret}` is not a subtype of the old return type `${function.ret}` (${initial.origin})" ) } - case _ => () + case _ => + // add an independent mutator instead of modifying the original functoid, if no original functoid is available + val newProvider = functoidModifier(Functoid.identityKey(b.key).asInstanceOf[Functoid[t]]).get + val newRef = SingletonBinding(b.key, ImplDef.ProviderImpl(newProvider.ret, newProvider), Set.empty, b.origin, isMutator = true) + refs = newRef :: refs } case AliasTo(key, pos) => // it's ok to retrieve `tags`, `implType` & `key` from `b` because all changes to @@ -354,7 +443,7 @@ object AbstractBindingDefDSL { private[this] val elems: mutable.Queue[SetElementRef] = mutable.Queue.empty private[this] val multiElems: mutable.Queue[MultiSetElementRef] = mutable.Queue.empty - override def interpret: collection.Seq[Binding] = { + override def interpret(): collection.Seq[Binding] = { val emptySetBinding = setOps.foldLeft(initial: EmptySetBinding[DIKey.BasicKey]) { (b, instr) => instr match { @@ -432,6 +521,36 @@ object AbstractBindingDefDSL { } } + final class SubcontextRef(initial: SingletonBinding[DIKey.TypeKey], ops: mutable.Queue[SubcontextInstruction] = mutable.Queue.empty) extends BindingRef { + override def interpret(): collection.Seq[ImplBinding] = { + require(initial.implementation.isInstanceOf[ImplDef.ContextImpl]) + var b: SingletonBinding[DIKey.BasicKey] = initial + def bImpl(): ImplDef.ContextImpl = (b.implementation: @unchecked) match { case implDef: ImplDef.ContextImpl => implDef } + ops.foreach { + case SubcontextInstruction.AddTags(tags) => + b = b.addTags(tags) + case SubcontextInstruction.SetId(contractedId) => + val key = DIKey.TypeKey(b.key.tpe).named(contractedId) + b = b.withTarget(key) + case SubcontextInstruction.SetExtractor(functoid) => + val i = bImpl() + b = b.withImplDef(i.copy(implType = functoid.get.ret, extractingFunction = functoid.get)) + case SubcontextInstruction.AddLocalDependencies(localDependencies) => + val i = bImpl() + b = b.withImplDef(i.copy(externalKeys = i.externalKeys ++ localDependencies)) + case SubcontextInstruction.AddSubmodule(submodule) => + val i = bImpl() + b = b.withImplDef(i.copy(module = i.module ++ submodule)) + } + Seq(b) + } + + def append(op: SubcontextInstruction): SubcontextRef = { + ops += op + this + } + } + sealed trait SingletonInstruction object SingletonInstruction { final case class SetImpl(implDef: ImplDef) extends SingletonInstruction @@ -458,4 +577,13 @@ object AbstractBindingDefDSL { final case class MultiAddTags(tags: Set[BindingTag]) extends MultiSetElementInstruction } + sealed trait SubcontextInstruction + object SubcontextInstruction { + final case class AddTags(tags: Set[BindingTag]) extends SubcontextInstruction + final case class SetId(id: Identifier) extends SubcontextInstruction + final case class SetExtractor[T](functoid: Functoid[T]) extends SubcontextInstruction + final case class AddLocalDependencies(localDependencies: Iterable[DIKey]) extends SubcontextInstruction + final case class AddSubmodule(submodule: ModuleBase) extends SubcontextInstruction + } + } diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/LifecycleAdapters.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/LifecycleAdapters.scala index 5136dd2f6a..a5d81febe2 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/LifecycleAdapters.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/LifecycleAdapters.scala @@ -142,71 +142,6 @@ object LifecycleAdapters { } } - // FIXME wtf there's no Local3 anymore -// trait TrifunctorHasLifecycleTag[R0, T] { -// type F[-RR, +EE, +AA] -// type R -// type E -// type A <: T -// -// implicit def tagFull: Tag[Lifecycle[F[Any, E, _], A]] -// implicit def ctorR: ZEnvConstructor[R] -// implicit def ev: R0 <:< Lifecycle[F[R, E, _], A] -// implicit def resourceTag: LifecycleTag[Lifecycle[F[Any, E, _], A]] -// } -// -// object TrifunctorHasLifecycleTag extends TrifunctorHasLifecycleTagLowPriority { -// -// import scala.annotation.unchecked.uncheckedVariance as v -// -// implicit def trifunctorResourceTag[ -// R1 <: Lifecycle[F0[R0, E0, _], A0], -// F0[_, _, _]: TagK3, -// R0: ZEnvConstructor, -// E0: Tag, -// A0 <: A1: Tag, -// A1, -// ]: TrifunctorHasLifecycleTag[R1 with Lifecycle[F0[R0, E0, _], A0], A1] { -// type R = R0 -// type E = E0 -// type A = A0 -// type F[-RR, +EE, +AA] = F0[RR @v, EE @v, AA @v] -// } = new TrifunctorHasLifecycleTag[R1, A1] { self => -// type F[-RR, +EE, +AA] = F0[RR @v, EE @v, AA @v] -// type R = R0 -// type E = E0 -// type A = A0 -// val ctorR: ZEnvConstructor[R0] = implicitly -// val tagFull: Tag[Lifecycle[F0[Any, E0, _], A0]] = implicitly -// val ev: R1 <:< Lifecycle[F0[R0, E0, _], A0] = implicitly -// val resourceTag: LifecycleTag[Lifecycle[F0[Any, E0, _], A0]] = new LifecycleTag[Lifecycle[F0[Any, E0, _], A0]] { -// type F[AA] = F0[Any, E0, AA] -// type A = A0 -// val tagFull: Tag[Lifecycle[F0[Any, E0, _], A0]] = self.tagFull -// val tagK: TagK[F0[Any, E0, _]] = TagK[F0[Any, E0, _]] -// val tagA: Tag[A0] = implicitly -// } -// } -// } -// -// private[definition] sealed trait TrifunctorHasLifecycleTagLowPriority extends TrifunctorHasLifecycleTagLowPriority1 { -// -// import scala.annotation.unchecked.uncheckedVariance as v -// -// implicit def trifunctorResourceTagNothing[ -// R1 <: Lifecycle[F0[R0, Nothing, _], A0], -// F0[_, _, _]: TagK3, -// R0: ZEnvConstructor, -// A0 <: A1: Tag, -// A1, -// ]: TrifunctorHasLifecycleTag[R1 with Lifecycle[F0[R0, Nothing, _], A0], A1] { -// type R = R0 -// type E = Nothing -// type A = A0 -// type F[-RR, +EE, +AA] = F0[RR @v, EE @v, AA @v] @v -// } = TrifunctorHasLifecycleTag.trifunctorResourceTag[R1, F0, R0, Nothing, A0, A1] -// } - trait ZIOEnvLifecycleTag[R0, T] { type R type E 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 124e52100a..dbfb4d568f 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 @@ -1,24 +1,22 @@ package izumi.distage.model.definition.dsl -import izumi.distage.LocalContext -import izumi.distage.constructors.{AnyConstructor, FactoryConstructor, ZEnvConstructor} +import izumi.distage.constructors.{ClassConstructor, FactoryConstructor, TraitConstructor, ZEnvConstructor} import izumi.distage.model.definition.* import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.* import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.MultiSetElementInstruction.MultiAddTags import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.SetElementInstruction.ElementAddTags import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.SingletonInstruction.* +import izumi.distage.model.definition.dsl.AnyKindShim.LifecycleF import izumi.distage.model.definition.dsl.LifecycleAdapters.{LifecycleTag, ZIOEnvLifecycleTag} import izumi.distage.model.definition.dsl.ModuleDefDSL.{MakeDSL, MakeDSLUnnamedAfterFrom, SetDSL} import izumi.distage.model.providers.Functoid import izumi.distage.model.reflection.{DIKey, SafeType} import izumi.functional.bio.data.Morphism1 -import izumi.fundamentals.platform.functional.Identity import izumi.fundamentals.platform.language.CodePositionMaterializer import izumi.reflect.{Tag, TagK} import zio.* import zio.managed.ZManaged -import scala.annotation.unused import scala.collection.immutable.HashSet /** @@ -41,7 +39,11 @@ import scala.collection.immutable.HashSet * * Singleton bindings: * - `make[X]` = create X using its constructor + * - `makeTrait[X]` = create an abstract class or a trait `X` using [[izumi.distage.constructors.TraitConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]]) + * - `makeFactory[X]` = create a "factory-like" abstract class or a trait `X` using [[izumi.distage.constructors.FactoryConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]]) * - `make[X].from[XImpl]` = bind X to its subtype XImpl using XImpl's constructor + * - `make[X].fromTrait[XImpl]` = bind X to its abstract class or a trait subtype XImpl, deriving constructor using [[izumi.distage.constructors.TraitConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]]) + * - `make[X].fromFactory[XImpl]` = bind X to its "factory-like" abstract class or a trait subtype XImpl, deriving constructor using [[izumi.distage.constructors.FactoryConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]]) * - `make[X].from(myX)` = bind X to an already existing instance `myX` * - `make[X].from { y: Y => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]] requesting an Y parameter * - `make[X].from { y: Y @Id("special") => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]], requesting a named "special" Y parameter @@ -98,15 +100,15 @@ trait ModuleDefDSL extends AbstractBindingDefDSL[MakeDSL, MakeDSLUnnamedAfterFro } override private[definition] final def _bindDSL[T](ref: SingletonRef): MakeDSL[T] = new MakeDSL[T](ref, ref.key) - override private[definition] final def _bindDSLAfterFrom[T](ref: SingletonRef): MakeDSLUnnamedAfterFrom[T] = new MakeDSLUnnamedAfterFrom[T](ref, ref.key) + override private[definition] final def _bindDSLAfterFrom[T](ref: SingletonRef): MakeDSLUnnamedAfterFrom[T] = new MakeDSLUnnamedAfterFrom[T](ref) override private[definition] final def _setDSL[T](ref: SetRef): SetDSL[T] = new SetDSL[T](ref) } object ModuleDefDSL { - trait MakeDSLBase[T, AfterBind] extends AnyKindShim { - final def from[I <: T: AnyConstructor]: AfterBind = - from(AnyConstructor[I]) + trait MakeDSLBase[T, AfterBind] { + final def from[I <: T: ClassConstructor]: AfterBind = + from(ClassConstructor[I]) final def from[I <: T: Tag](function: => I): AfterBind = from(Functoid.lift(function)) @@ -114,24 +116,6 @@ object ModuleDefDSL { final def fromValue[I <: T: Tag](instance: I): AfterBind = bind(ImplDef.InstanceImpl(SafeType.get[I], instance)) - /** - * Defines local context with empty local module and local keys - */ - def fromLocalContext[F[_], R](defn: LocalContextDef[F[R]])(implicit @unused ev: T =:= LocalContext[F, R]): LocalContextDSL[F, R, AfterBind] = { - new LocalContextDSL[F, R, AfterBind](defn.module, Set.empty, bind, defn.function).bound() - } - - /** - * Defines local context with empty local module and local keys, specialised for Identity - */ - def fromLocalContext[R]( - defn: LocalContextDef[R] - )(implicit ev: T =:= LocalContext[Identity, R], - dummyImplicit: DummyImplicit, - ): LocalContextDSL[Identity, R, AfterBind] = { - fromLocalContext[Identity, R](defn) - } - /** * A function that receives its arguments from DI object graph, including named instances via [[izumi.distage.model.definition.Id]] annotation. * @@ -189,9 +173,17 @@ object ModuleDefDSL { * @see Functoid is based on the Magnet Pattern: [[http://spray.io/blog/2012-12-13-the-magnet-pattern/]] * @see Essentially Functoid is a function-like entity with additional properties, so it's funny name is reasonable enough: [[https://en.wiktionary.org/wiki/-oid#English]] */ - final def from[I <: T](function: Functoid[I]): AfterBind = + final def from[I <: T](function: Functoid[I])(implicit d: DummyImplicit): AfterBind = bind(ImplDef.ProviderImpl(function.get.ret, function.get)) + /** @see [[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]] */ + final def fromTrait[I <: T: TraitConstructor]: AfterBind = + from[I](TraitConstructor[I]) + + /** @see [[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]] */ + final def fromFactory[I <: T: FactoryConstructor]: AfterBind = + from[I](FactoryConstructor[I]) + /** * Bind by reference to another bound key * @@ -287,8 +279,8 @@ object ModuleDefDSL { * @see - [[cats.effect.Resource]]: https://typelevel.org/cats-effect/datatypes/resource.html * - [[Lifecycle]] */ - final def fromResource[R <: Lifecycle[LifecycleF, T]: AnyConstructor](implicit tag: LifecycleTag[R]): AfterBind = { - fromResource(AnyConstructor[R]) + final def fromResource[R <: Lifecycle[LifecycleF, T]: ClassConstructor](implicit tag: LifecycleTag[R]): AfterBind = { + fromResource(ClassConstructor[R]) } final def fromResource[R](instance: R with Lifecycle[LifecycleF, T])(implicit tag: LifecycleTag[R]): AfterBind = { @@ -296,7 +288,7 @@ object ModuleDefDSL { bind(ImplDef.ResourceImpl(SafeType.get[A], SafeType.getK[F], ImplDef.InstanceImpl(SafeType.get[R], instance))) } - final def fromResource[R](function: Functoid[R with Lifecycle[LifecycleF, T]])(implicit tag: LifecycleTag[R]): AfterBind = { + final def fromResource[R](function: Functoid[R with Lifecycle[LifecycleF, T]])(implicit tag: LifecycleTag[R], d: DummyImplicit): AfterBind = { import tag.* bind(ImplDef.ResourceImpl(SafeType.get[A], SafeType.getK[F], ImplDef.ProviderImpl(SafeType.get[R], function.get))) } @@ -335,18 +327,14 @@ object ModuleDefDSL { bind(ImplDef.ProviderImpl(provider.ret, provider)) } - final def fromFactory[I <: T: FactoryConstructor]: AfterBind = { - from[I](FactoryConstructor[I]) - } - protected[this] def bind(impl: ImplDef): AfterBind protected[this] def key: DIKey } - trait SetDSLBase[T, AfterAdd, AfterMultiAdd] extends AnyKindShim { + trait SetDSLBase[T, AfterAdd, AfterMultiAdd] { - final def add[I <: T: Tag: AnyConstructor](implicit pos: CodePositionMaterializer): AfterAdd = - add[I](AnyConstructor[I]) + final def add[I <: T: Tag: ClassConstructor](implicit pos: CodePositionMaterializer): AfterAdd = + add[I](ClassConstructor[I]) final def add[I <: T: Tag](function: => I)(implicit pos: CodePositionMaterializer): AfterAdd = add(Functoid.lift(function)) @@ -357,6 +345,11 @@ object ModuleDefDSL { final def addValue[I <: T: Tag](instance: I)(implicit pos: CodePositionMaterializer): AfterAdd = appendElement(ImplDef.InstanceImpl(SafeType.get[I], instance), pos) + /** @see [[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]] */ + final def addTrait[I <: T: Tag: TraitConstructor](implicit pos: CodePositionMaterializer): AfterAdd = + add[I](TraitConstructor[I]) + + /** @see [[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]] */ final def addFactory[I <: T: Tag: FactoryConstructor](implicit pos: CodePositionMaterializer): AfterAdd = add[I](FactoryConstructor[I]) @@ -409,15 +402,20 @@ object ModuleDefDSL { final def refEffect[F[_]: TagK, I <: T: Tag](name: Identifier)(implicit pos: CodePositionMaterializer): AfterAdd = appendElement(ImplDef.EffectImpl(SafeType.get[I], SafeType.getK[F], ImplDef.ReferenceImpl(SafeType.get[F[I]], DIKey.get[F[I]].named(name), weak = false)), pos) - final def addResource[R <: Lifecycle[LifecycleF, T]: AnyConstructor](implicit tag: LifecycleTag[R], pos: CodePositionMaterializer): AfterAdd = - addResource[R](AnyConstructor[R]) + final def addResource[R <: Lifecycle[LifecycleF, T]: ClassConstructor](implicit tag: LifecycleTag[R], pos: CodePositionMaterializer): AfterAdd = + addResource[R](ClassConstructor[R]) final def addResource[R](instance: R with Lifecycle[LifecycleF, T])(implicit tag: LifecycleTag[R], pos: CodePositionMaterializer): AfterAdd = { import tag.* appendElement(ImplDef.ResourceImpl(SafeType.get[A], SafeType.getK[F], ImplDef.InstanceImpl(SafeType.get[R], instance)), pos) } - final def addResource[R](function: Functoid[R with Lifecycle[LifecycleF, T]])(implicit tag: LifecycleTag[R], pos: CodePositionMaterializer): AfterAdd = { + final def addResource[R]( + function: Functoid[R with Lifecycle[LifecycleF, T]] + )(implicit tag: LifecycleTag[R], + pos: CodePositionMaterializer, + d: DummyImplicit, + ): AfterAdd = { import tag.* appendElement(ImplDef.ResourceImpl(SafeType.get[A], SafeType.getK[F], ImplDef.ProviderImpl(SafeType.get[R], function.get)), pos) } @@ -489,7 +487,7 @@ object ModuleDefDSL { /** Workaround for https://github.com/lampepfl/dotty/issues/16406#issuecomment-1712058227 */ type DottyNothing = Nothing - object MakeDSLBase extends AnyKindShim { + object MakeDSLBase { implicit final class MakeFromZIOZEnv[T, AfterBind](protected val dsl: MakeDSLBase[T, AfterBind]) extends AnyVal { @@ -550,10 +548,10 @@ object ModuleDefDSL { * Warning: removes the precise subtype of Lifecycle because of `Lifecycle.map`: * Integration checks mixed-in as a trait onto a Lifecycle value result here will be lost */ - def fromZEnvResource[R1 <: Lifecycle[ZIO[Nothing, Any, +_], T]: AnyConstructor](implicit tag: ZIOEnvLifecycleTag[R1, T]): AfterBind = { + def fromZEnvResource[R1 <: Lifecycle[ZIO[Nothing, Any, +_], T]: ClassConstructor](implicit tag: ZIOEnvLifecycleTag[R1, T]): AfterBind = { import tag.{R, E, A, ctorR, tagFull, resourceTag, ev} - val provider = AnyConstructor[R1].map2(ctorR.provider)((r1, zenv) => provideZEnvLifecycle[R, E, A](ev(r1), zenv))(tagFull) - dsl.fromResource(provider)(resourceTag) + val provider = ClassConstructor[R1].map2(ctorR.provider)((r1, zenv) => provideZEnvLifecycle[R, E, A](ev(r1), zenv))(tagFull) + dsl.fromResource(provider)(resourceTag, DummyImplicit.dummyImplicit) } /** @@ -578,71 +576,11 @@ object ModuleDefDSL { dsl.fromResource[Lifecycle[ZIO[Any, E, _], I]](provider) } - // FIXME wtf -// /** -// * Adds a dependency on `Local3[F]` -// * -// * Warning: removes the precise subtype of Lifecycle because of `Lifecycle.map`: -// * Integration checks mixed-in as a trait onto a Lifecycle value result here will be lost -// */ -// def fromZEnv[R1 <: Lifecycle[LifecycleF, T]: AnyConstructor](implicit tag: TrifunctorHasLifecycleTag[R1, T]): AfterBind = { -// import tag._ -// val provider: Functoid[Lifecycle[F[Any, E, _], A]] = -// AnyConstructor[R1].zip(ZEnvConstructor[R]).map2(Functoid.identity[Local3[F]](tagLocal3)) { -// case ((resource, r), f) => provideLifecycle(f)(resource, r) -// } -// dsl.fromResource(provider) -// } } - // FIXME wtf -// sealed trait MakeFromHasLowPriorityOverloads[T, AfterBind] extends Any { -// protected[this] def dsl: MakeDSLBase[T, AfterBind] -// -// /** Adds a dependency on `Local3[F]` */ -// final def fromZEnv[F[-_, +_, +_]: TagK3, R: ZEnvConstructor, E: Tag, I <: T: Tag](effect: F[R, E, I]): AfterBind = { -// dsl.fromEffect[F[Any, E, _], I](ZEnvConstructor[R].map2(Functoid.identity[Local3[F]]) { -// (r, F: Local3[F]) => F.provide(effect)(r) -// }) -// } -// -// /** Adds a dependency on `Local3[F]` */ -// final def fromZEnv[F[-_, +_, +_]: TagK3, R: ZEnvConstructor, E: Tag, I <: T: Tag](function: Functoid[F[R, E, I]]): AfterBind = { -// dsl.fromEffect[F[Any, E, _], I](function.zip(ZEnvConstructor[R]).map2(Functoid.identity[Local3[F]]) { -// case ((effect, r), f) => f.provide(effect)(r) -// }) -// } -// -// /** -// * Adds a dependency on `Local3[F]` -// * -// * Warning: removes the precise subtype of Lifecycle because of `Lifecycle.map`: -// * Integration checks mixed-in as a trait onto a Lifecycle value result here will be lost -// */ -// final def fromZEnv[F[-_, +_, +_]: TagK3, R: ZEnvConstructor, E: Tag, I <: T: Tag](resource: Lifecycle[F[R, E, _], I]): AfterBind = { -// dsl.fromResource[Lifecycle[F[Any, E, _], I]](ZEnvConstructor[R].map2(Functoid.identity[Local3[F]]) { -// (r: R, F: Local3[F]) => provideLifecycle(F)(resource, r) -// }) -// } -// -// /** -// * Adds a dependency on `Local3[F]` -// * -// * Warning: removes the precise subtype of Lifecycle because of `Lifecycle.map`: -// * Integration checks mixed-in as a trait onto a Lifecycle value result here will be lost -// */ -// final def fromZEnv[F[-_, +_, +_]: TagK3, R: ZEnvConstructor, E: Tag, I <: T: Tag]( -// function: Functoid[Lifecycle[F[R, E, _], I]] -// )(implicit d1: DummyImplicit -// ): AfterBind = { -// dsl.fromResource[Lifecycle[F[Any, E, _], I]](function.zip(ZEnvConstructor[R]).map2(Functoid.identity[Local3[F]]) { -// case ((resource, r), f) => provideLifecycle(f)(resource, r) -// }) -// } -// } } - object SetDSLBase extends AnyKindShim { + object SetDSLBase { implicit final class AddFromZIOZEnv[T, AfterAdd](protected val dsl: SetDSLBase[T, AfterAdd, ?]) extends AnyVal { def addZIOEnv[R: ZEnvConstructor, E >: DottyNothing: Tag, I <: T: Tag](effect: ZIO[R, E, I])(implicit pos: CodePositionMaterializer): AfterAdd = { @@ -676,10 +614,13 @@ object ModuleDefDSL { * Warning: removes the precise subtype of Lifecycle because of `Lifecycle.map`: * Integration checks on mixed-in as a trait onto a Lifecycle value result here will be lost */ - def addZEnvResource[R1 <: Lifecycle[ZIO[Nothing, Any, +_], T]: AnyConstructor](implicit tag: ZIOEnvLifecycleTag[R1, T], pos: CodePositionMaterializer): AfterAdd = { + def addZEnvResource[R1 <: Lifecycle[ZIO[Nothing, Any, +_], T]: ClassConstructor]( + implicit tag: ZIOEnvLifecycleTag[R1, T], + pos: CodePositionMaterializer, + ): AfterAdd = { import tag.{R, E, A, ctorR, tagFull, resourceTag, ev} - val provider = AnyConstructor[R1].map2(ctorR.provider)((r1, zenv) => provideZEnvLifecycle[R, E, A](ev(r1), zenv))(tagFull) - dsl.addResource(provider)(resourceTag, pos) + val provider = ClassConstructor[R1].map2(ctorR.provider)((r1, zenv) => provideZEnvLifecycle[R, E, A](ev(r1), zenv))(tagFull) + dsl.addResource(provider)(resourceTag, pos, DummyImplicit.dummyImplicit) } /** @@ -710,83 +651,10 @@ object ModuleDefDSL { dsl.addResource[Lifecycle[ZIO[Any, E, _], I]](provider) } - // FIXME wtf -// /** -// * Adds a dependency on `Local3[F]` -// * -// * Warning: removes the precise subtype of Lifecycle because of `Lifecycle.map`: -// * Integration checks on mixed-in as a trait onto a Lifecycle value result here will be lost -// */ -// final def addZEnv[R1 <: Lifecycle[LifecycleF, T]: AnyConstructor](implicit tag: TrifunctorHasLifecycleTag[R1, T], pos: CodePositionMaterializer): AfterAdd = { -// import tag._ -// val provider: Functoid[Lifecycle[F[Any, E, _], A]] = -// AnyConstructor[R1].zip(ZEnvConstructor[R]).map2(Functoid.identity[Local3[F]](tagLocal3)) { -// case ((resource, r), f) => provideLifecycle(f)(resource, r) -// } -// dsl.addResource(provider) -// } } - // FIXME wtf -// sealed trait AddFromHasLowPriorityOverloads[T, AfterAdd] extends Any { -// protected[this] def dsl: SetDSLBase[T, AfterAdd, ?] -// -// /** Adds a dependency on `Local3[F]` */ -// final def addZEnv[F[-_, +_, +_]: TagK3, R: ZEnvConstructor, E: Tag, I <: T: Tag](effect: F[R, E, I])(implicit pos: CodePositionMaterializer): AfterAdd = { -// dsl.addEffect[F[Any, E, _], I](ZEnvConstructor[R].map2(Functoid.identity[Local3[F]]) { -// (r, F: Local3[F]) => F.provide(effect)(r) -// }) -// } -// -// /** Adds a dependency on `Local3[F]` */ -// final def addZEnv[F[-_, +_, +_]: TagK3, R: ZEnvConstructor, E: Tag, I <: T: Tag]( -// function: Functoid[F[R, E, I]] -// )(implicit pos: CodePositionMaterializer -// ): AfterAdd = { -// dsl.addEffect[F[Any, E, _], I](function.zip(ZEnvConstructor[R]).map2(Functoid.identity[Local3[F]]) { -// case ((effect, r), f) => f.provide(effect)(r) -// }) -// } -// -// /** -// * Adds a dependency on `Local3[F]` -// * -// * Warning: removes the precise subtype of Lifecycle because of `Lifecycle.map`: -// * Integration checks on mixed-in as a trait onto a Lifecycle value result here will be lost -// */ -// final def addZEnv[F[-_, +_, +_]: TagK3, R: ZEnvConstructor, E: Tag, I <: T: Tag]( -// resource: Lifecycle[F[R, E, _], I] -// )(implicit pos: CodePositionMaterializer -// ): AfterAdd = { -// dsl.addResource[Lifecycle[F[Any, E, _], I]](ZEnvConstructor[R].map2(Functoid.identity[Local3[F]]) { -// (r: R, F: Local3[F]) => provideLifecycle(F)(resource, r) -// }) -// } -// -// /** -// * Adds a dependency on `Local3[F]` -// * -// * Warning: removes the precise subtype of Lifecycle because of `Lifecycle.map`: -// * Integration checks on mixed-in as a trait onto a Lifecycle value result here will be lost -// */ -// final def addZEnv[F[-_, +_, +_]: TagK3, R: ZEnvConstructor, E: Tag, I <: T: Tag]( -// function: Functoid[Lifecycle[F[R, E, _], I]] -// )(implicit pos: CodePositionMaterializer, -// d1: DummyImplicit, -// ): AfterAdd = { -// dsl.addResource[Lifecycle[F[Any, E, _], I]](function.zip(ZEnvConstructor[R]).map2(Functoid.identity[Local3[F]]) { -// case ((resource, r), f) => provideLifecycle(f)(resource, r) -// }) -// } -// -// } } - // FIXME wtf -// @inline private[this] def provideLifecycle[F[-_, +_, +_], R, E, A](F: Local3[F])(resource: Lifecycle[F[R, E, _], A], r: R): Lifecycle[F[Any, E, _], A] = { -// resource.mapK[F[R, E, _], F[Any, E, _]](Morphism1(F.provide(_)(r))) -// } - @inline private[this] def provideZEnvLifecycle[R, E, A](lifecycle: Lifecycle[ZIO[R, E, _], A], zenv: ZEnvironment[R]): Lifecycle[ZIO[Any, E, _], A] = { lifecycle.mapK[ZIO[R, E, _], ZIO[Any, E, _]](Morphism1(_.provideEnvironment(zenv))) } @@ -796,7 +664,7 @@ object ModuleDefDSL { /** These are the _only_ (not `from`-like) methods that can chained after `make` * such that make[T] will still generate the constructor for `T` * - * See [[izumi.distage.constructors.macros.AnyConstructorMacro.anyConstructorOptionalMakeDSL]] + * See [[izumi.distage.constructors.macros.MakeMacro.classConstructorOptionalMakeDSL]] * * If ANY other method is chained in the same expression * it's assumed that it will replace make[T]'s constructor, @@ -831,7 +699,7 @@ object ModuleDefDSL { } override protected[this] def bind(impl: ImplDef): MakeDSLUnnamedAfterFrom[T] = { - addOp(SetImpl(impl))(new MakeDSLUnnamedAfterFrom[T](_, key)) + addOp(SetImpl(impl))(new MakeDSLUnnamedAfterFrom[T](_)) } override protected[this] def toSame: SingletonRef => MakeDSL[T] = { @@ -847,7 +715,7 @@ object ModuleDefDSL { with MakeDSLBase[T, MakeDSLNamedAfterFrom[T]] { override protected[this] def bind(impl: ImplDef): MakeDSLNamedAfterFrom[T] = { - addOp(SetImpl(impl))(new MakeDSLNamedAfterFrom[T](_, key)) + addOp(SetImpl(impl))(new MakeDSLNamedAfterFrom[T](_)) } override protected[this] def toSame: SingletonRef => MakeNamedDSL[T] = { @@ -857,37 +725,34 @@ object ModuleDefDSL { } final class MakeDSLUnnamedAfterFrom[T]( - override protected val mutableState: SingletonRef, - override protected val key: DIKey.TypeKey, - ) extends MakeDSLMutBase[T, MakeDSLUnnamedAfterFrom[T]] { + override protected val mutableState: SingletonRef + ) extends AnyVal + with MakeDSLMutBase[T, MakeDSLUnnamedAfterFrom[T]] { def named(name: Identifier): MakeDSLNamedAfterFrom[T] = { - addOp(SetId(name))(new MakeDSLNamedAfterFrom[T](_, key.named(name))) + addOp(SetId(name))(new MakeDSLNamedAfterFrom[T](_)) } def namedByImpl: MakeDSLNamedAfterFrom[T] = { - addOp(SetIdFromImplName())(new MakeDSLNamedAfterFrom[T](_, key)) + addOp(SetIdFromImplName())(new MakeDSLNamedAfterFrom[T](_)) } override protected[this] def toSame: SingletonRef => MakeDSLUnnamedAfterFrom[T] = { - new MakeDSLUnnamedAfterFrom[T](_, key) + new MakeDSLUnnamedAfterFrom[T](_) } } final class MakeDSLNamedAfterFrom[T]( - override protected val mutableState: SingletonRef, - override protected val key: DIKey.BasicKey, + override protected val mutableState: SingletonRef ) extends MakeDSLMutBase[T, MakeDSLNamedAfterFrom[T]] { override protected[this] def toSame: SingletonRef => MakeDSLNamedAfterFrom[T] = { - new MakeDSLNamedAfterFrom[T](_, key) + new MakeDSLNamedAfterFrom[T](_) } } - sealed trait MakeDSLMutBase[T, Self <: MakeDSLMutBase[T, Self]] { + sealed trait MakeDSLMutBase[T, Self <: MakeDSLMutBase[T, Self]] extends Any with AddDependencyDSL[T, Self] { protected[this] def mutableState: SingletonRef - protected[this] def key: DIKey.BasicKey - protected[this] def toSame: SingletonRef => Self final def tagged(tags: BindingTag*): Self = { @@ -902,22 +767,6 @@ object ModuleDefDSL { addOp(Modify(f))(toSame) } - final def addDependency[B: Tag]: Self = { - modifyBy(_.addDependency(DIKey.get[B])) - } - - final def addDependency(key: DIKey): Self = { - modifyBy(_.addDependency(key)) - } - - final def addDependencies(keys: Iterable[DIKey]): Self = { - modifyBy(_.addDependencies(keys)) - } - - final def annotateParameter[P: Tag](name: Identifier): Self = { - addOp(annotateParameterOp[P](name))(toSame) - } - final def aliased[T1 >: T: Tag](implicit pos: CodePositionMaterializer): Self = { addOp(AliasTo(DIKey.get[T1], pos.get.position))(toSame) } @@ -930,9 +779,8 @@ object ModuleDefDSL { newState(mutableState.append(op)) } - private[this] final def annotateParameterOp[P: Tag](name: Identifier): Modify[T] = { - Modify[T](_.annotateParameter[P](name)) - } + override protected[this] def _modifyBy(f: Functoid[T] => Functoid[T]): Self = modifyBy(f) + } final class SetDSL[T]( @@ -978,41 +826,6 @@ object ModuleDefDSL { } } - final class LocalContextDSL[F[_], R, AfterBind](module: ModuleBase, ext: Set[DIKey], bind: ImplDef => AfterBind, functoid: Functoid[F[R]]) { - protected[dsl] def doBind(): AfterBind = { - // it's okay to call this function multiple times, the underlying state is mutable and the last operation will be successful - bind(ImplDef.ContextImpl(functoid.get.ret, functoid, module, ext)) - } - - protected[dsl] def bound(): LocalContextDSL[F, R, AfterBind] = { - doBind() - this - } - - /** - * Defines local context with empty local module - */ - def external(keys: DIKey*): LocalContextDSL[F, R, AfterBind] = { - new LocalContextDSL[F, R, AfterBind](module, ext ++ keys.toSet, bind, functoid).bound() - } - - def external[K1: Tag]: LocalContextDSL[F, R, AfterBind] = { - external(DIKey.get[K1]) - } - - def external[K1: Tag, K2: Tag]: LocalContextDSL[F, R, AfterBind] = { - external(DIKey.get[K1], DIKey.get[K2]) - } - - def external[K1: Tag, K2: Tag, K3: Tag]: LocalContextDSL[F, R, AfterBind] = { - external(DIKey.get[K1], DIKey.get[K2], DIKey.get[K3]) - } - - def external[K1: Tag, K2: Tag, K3: Tag, K4: Tag]: LocalContextDSL[F, R, AfterBind] = { - external(DIKey.get[K1], DIKey.get[K2], DIKey.get[K3], DIKey.get[K4]) - } - } - sealed trait SetDSLMutBase[T] extends SetDSLBase[T, SetElementDSL[T], MultiSetElementDSL[T]] { protected[this] def mutableState: SetRef diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/errors/ProvisionerIssue.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/errors/ProvisionerIssue.scala index dc65103285..31c979ccf3 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/errors/ProvisionerIssue.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/errors/ProvisionerIssue.scala @@ -7,7 +7,6 @@ import izumi.distage.model.plan.ExecutableOp.{ImportDependency, MonadicOp, Proxy import izumi.distage.model.provisioning.NewObjectOp import izumi.distage.model.provisioning.proxies.ProxyProvider.ProxyContext import izumi.distage.model.reflection.{DIKey, LinkedParameter, SafeType} -import izumi.fundamentals.collections.nonempty.NEList sealed trait ProvisionerIssue { def key: DIKey @@ -62,8 +61,6 @@ object ProvisionerIssue { final case class MissingInstance(key: DIKey) extends ProvisionerIssue - final case class LocalContextPlanningFailed(key: DIKey, issues: NEList[DIError]) extends ProvisionerIssue - final case class UnsupportedOp(tpe: SafeType, op: ExecutableOp, context: String) extends ProvisionerIssue { override def key: DIKey = op.target } diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/ExecutableOp.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/ExecutableOp.scala index e9a576f2a1..8afb706b37 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/ExecutableOp.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/ExecutableOp.scala @@ -77,7 +77,7 @@ object ExecutableOp { } } - final case class LocalContext(target: DIKey, wiring: SingletonWiring.PrepareLocalContext, origin: EqualizedOperationOrigin) extends WiringOp { + final case class CreateSubcontext(target: DIKey, wiring: SingletonWiring.PrepareSubcontext, origin: EqualizedOperationOrigin) extends WiringOp { override def replaceKeys(targets: DIKey => DIKey, parameters: DIKey => DIKey): InstantiationOp = { this.copy(target = targets(this.target), wiring = this.wiring.replaceKeys(parameters)) } diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Wiring.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Wiring.scala index af996637b0..6ad799c676 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Wiring.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Wiring.scala @@ -1,8 +1,6 @@ package izumi.distage.model.plan -import izumi.distage.model.definition.ModuleBase import izumi.distage.model.plan.ExecutableOp.AddRecursiveLocatorRef -import izumi.distage.model.providers.Functoid import izumi.distage.model.reflection.{DIKey, LinkedParameter, Provider, SafeType} sealed trait Wiring { @@ -34,12 +32,12 @@ object Wiring { } } - final case class PrepareLocalContext(provider: Functoid[Any], module: ModuleBase, implType: SafeType, externalKeys: Set[DIKey], importedParentKeys: Set[DIKey]) + final case class PrepareSubcontext(provider: Provider, subplan: Plan, implType: SafeType, externalKeys: Set[DIKey], importedFromParentKeys: Set[DIKey]) extends SingletonWiring { override def instanceType: SafeType = implType - override def requiredKeys: Set[DIKey] = Set(AddRecursiveLocatorRef.magicLocatorKey) ++ importedParentKeys + override def requiredKeys: Set[DIKey] = Set(AddRecursiveLocatorRef.magicLocatorKey) ++ importedFromParentKeys override def associations: Seq[LinkedParameter] = Seq.empty - override def replaceKeys(f: DIKey => DIKey): PrepareLocalContext = this + override def replaceKeys(f: DIKey => DIKey): PrepareSubcontext = this } final case class Reference(instanceType: SafeType, key: DIKey, weak: Boolean) extends SingletonWiring { diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/OpFormatter.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/OpFormatter.scala index 9bfdc73cb2..97fad53a06 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/OpFormatter.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/repr/OpFormatter.scala @@ -64,7 +64,7 @@ object OpFormatter { case w: WiringOp => w match { - case LocalContext(target, wiring, origin) => + case CreateSubcontext(target, wiring, origin) => formatProviderOp(target, wiring, origin, deferred) case CallProvider(target, wiring, origin) => formatProviderOp(target, wiring, origin, deferred) @@ -150,8 +150,8 @@ object OpFormatter { case f: Function => doFormat(formatFunction(f.provider), f.associations.map(formatDependency(deferred)), "call", ('(', ')'), ('{', '}')) - case f: PrepareLocalContext => - doFormat(formatFunction(f.provider.get), f.associations.map(formatDependency(deferred)), "context", ('(', ')'), ('{', '}')) + case f: PrepareSubcontext => + doFormat(formatFunction(f.provider), f.associations.map(formatDependency(deferred)), "subcontext", ('(', ')'), ('{', '}')) case other @ (_: Effect | _: Resource | _: Instance | _: Reference) => s"UNEXPECTED WIREABLE: $other" diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/providers/Functoid.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/providers/Functoid.scala index e8769d3d9f..970fd017aa 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/providers/Functoid.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/providers/Functoid.scala @@ -1,6 +1,6 @@ package izumi.distage.model.providers -import izumi.distage.constructors.{AnyConstructor, ClassConstructor, FactoryConstructor, TraitConstructor, ZEnvConstructor} +import izumi.distage.constructors.{ClassConstructor, FactoryConstructor, TraitConstructor, ZEnvConstructor} import izumi.distage.model.definition.Identifier import izumi.distage.model.exceptions.runtime.TODOBindingException import izumi.distage.model.reflection.LinkedParameter @@ -232,8 +232,9 @@ object Functoid extends FunctoidMacroMethods with FunctoidLifecycleAdapters { */ def makeHas[A: ZEnvConstructor]: Functoid[ZEnvironment[A]] = ZEnvConstructor[A] - /** Derive constructor for a type `A` using [[AnyConstructor]] */ - def makeAny[A: AnyConstructor]: Functoid[A] = AnyConstructor[A] + @deprecated("Same as `makeClass`, use `makeClass`") + /** @deprecated Same as `makeClass`, use `makeClass` */ + def makeAny[A: ClassConstructor]: Functoid[A] = ClassConstructor[A] def todoProvider(key: DIKey)(implicit pos: CodePositionMaterializer): Functoid[Nothing] = { new Functoid[Nothing]( diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/PlanInterpreter.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/PlanInterpreter.scala index 1a6b9e89a1..1f38dea6b4 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/PlanInterpreter.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/PlanInterpreter.scala @@ -174,8 +174,6 @@ object PlanInterpreter { case ProxyInstantiationFailed(context, cause) => s"Failed to instantiate class with ByteBuddy, make sure you don't dereference proxied parameters in constructors: " + s"class=${context.runtimeClass}, params=${context.params}, exception=${cause.stacktraceString}" - case LocalContextPlanningFailed(key, issues) => - s"Failed to prepare plan for local context with key=$key: ${issues.map(DIError.format).toList.niceList()}" } .niceMultilineList("[!]") s"Plan interpreter failed:\n$messages" diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/strategies/ContextStrategy.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/strategies/SubcontextStrategy.scala similarity index 63% rename from distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/strategies/ContextStrategy.scala rename to distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/strategies/SubcontextStrategy.scala index d8b6e32d39..ea72ea1f11 100644 --- a/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/strategies/ContextStrategy.scala +++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/strategies/SubcontextStrategy.scala @@ -6,6 +6,6 @@ import izumi.distage.model.provisioning.{NewObjectOp, ProvisioningKeyProvider} import izumi.functional.quasi.QuasiIO import izumi.reflect.TagK -trait ContextStrategy { - def prepareContext[F[_]: TagK: QuasiIO](context: ProvisioningKeyProvider, op: WiringOp.LocalContext): F[Either[ProvisionerIssue, Seq[NewObjectOp]]] +trait SubcontextStrategy { + def prepareSubcontext[F[_]: TagK: QuasiIO](context: ProvisioningKeyProvider, op: WiringOp.CreateSubcontext): F[Either[ProvisionerIssue, Seq[NewObjectOp]]] } diff --git a/distage/distage-core-api/src/test/scala/izumi/LifecycleIzumiInstancesTest.scala b/distage/distage-core-api/src/test/scala/izumi/LifecycleIzumiInstancesTest.scala index e771b30896..9547730adb 100644 --- a/distage/distage-core-api/src/test/scala/izumi/LifecycleIzumiInstancesTest.scala +++ b/distage/distage-core-api/src/test/scala/izumi/LifecycleIzumiInstancesTest.scala @@ -1,23 +1,20 @@ package izumi -import izumi.distage.model.definition.{Lifecycle2, Lifecycle3} -import izumi.functional.bio.{Functor2, Functor3} +import izumi.distage.model.definition.Lifecycle2 +import izumi.functional.bio.{Applicative2, Functor2, Monad2} +import izumi.functional.quasi.QuasiPrimitives import org.scalatest.wordspec.AnyWordSpec class LifecycleIzumiInstancesTest extends AnyWordSpec { - "Summon Functor2/Functor3 instances for Lifecycle" in { - def t2[F[+_, +_]: Functor2]: Functor2[Lifecycle2[F, +_, +_]] = { + "Summon Monad2 instances for Lifecycle" in { + def t2[F[+_, +_]: Functor2](implicit P: QuasiPrimitives[F[Any, _]]): Functor2[Lifecycle2[F, +_, +_]] = { Functor2[Lifecycle2[F, +_, +_]] - } - - def t3[F[-_, +_, +_]: Functor3]: Functor3[Lifecycle3[F, -_, +_, +_]] = { - Functor3[Lifecycle3[F, -_, +_, +_]] + Applicative2[Lifecycle2[F, +_, +_]] + Monad2[Lifecycle2[F, +_, +_]] } t2[zio.IO] // t2[monix.bio.IO] - - t3[zio.ZIO] } } diff --git a/distage/distage-core/.jvm/src/main/scala/izumi/distage/planning/extensions/GraphDumpObserver.scala b/distage/distage-core/.jvm/src/main/scala/izumi/distage/planning/extensions/GraphDumpObserver.scala index adf0b840f9..0af1c1e1fe 100644 --- a/distage/distage-core/.jvm/src/main/scala/izumi/distage/planning/extensions/GraphDumpObserver.scala +++ b/distage/distage-core/.jvm/src/main/scala/izumi/distage/planning/extensions/GraphDumpObserver.scala @@ -129,8 +129,8 @@ final class GraphDumpObserver() extends PlanningObserver { "newset" case op: ExecutableOp.WiringOp => op.wiring match { - case _: Wiring.SingletonWiring.PrepareLocalContext => - "context" + case _: Wiring.SingletonWiring.PrepareSubcontext => + "subcontext" case Wiring.SingletonWiring.Function(_) => "lambda" case Wiring.SingletonWiring.Instance(_, _) => diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/compat/DefaultModuleTest.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/compat/DefaultModuleTest.scala index ad8eb939a6..4dbf9172f5 100644 --- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/compat/DefaultModuleTest.scala +++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/compat/DefaultModuleTest.scala @@ -29,19 +29,14 @@ final class DefaultModuleTest extends AnyWordSpec with MkInjector with CatsIOPla ) } - "build for fromBIO2" in { + "build for fromBIO" in { implicit val unsafeRun2: UnsafeRun2[zio.IO] = UnsafeRun2.createZIO() unsafeRun( - Injector[zio.Task]()(implicitly[QuasiIO[zio.Task]], implicitly[TagK[zio.Task]], DefaultModule.fromBIO2[zio.IO]) + Injector[zio.Task]()(implicitly[QuasiIO[zio.Task]], implicitly[TagK[zio.Task]], DefaultModule.fromBIO[zio.IO]) .produce(Module.empty, Roots.Everything).unsafeGet() ) } -// "build for fromBIO3" in { -// implicit val unsafeRun2: UnsafeRun3[zio.ZIO] = UnsafeRun2.createZIO() -// unsafeRun(Injector[zio.Task]()(implicitly[QuasiIO[zio.Task]], implicitly[TagK[zio.Task]], DefaultModule.fromBIO3[zio.ZIO]).produce(Module.empty, Roots.Everything).unsafeGet()) -// } - "build for fromCats" in { catsIOUnsafeRunSync { Dispatcher.sequential[cats.effect.IO].use { diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTestJvm.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTestJvm.scala index 349eb485b5..e30fd2d536 100644 --- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTestJvm.scala +++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTestJvm.scala @@ -22,7 +22,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector with ScalatestGuar val definition = PlannerInput.everything(new ModuleDef { make[Circular2] - make[Circular1] + makeTrait[Circular1] }) val injector = mkInjector() diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/ZIOHasInjectionTest.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/ZIOHasInjectionTest.scala index 6f73f104c8..07cdc50f0d 100644 --- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/ZIOHasInjectionTest.scala +++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/ZIOHasInjectionTest.scala @@ -25,24 +25,6 @@ class ZIOHasInjectionTest extends AnyWordSpec with MkInjector with ZIOTest with def trait1(d1: Dependency1): Trait1 = new Trait1 { override def dep1: Dependency1 = d1 } - // FIXME wtf -// def getDep1[F[-_, +_, +_]: Ask3]: F[Has[Dependency1], Nothing, Dependency1] = -// F.askWith((_: Has[Dependency1]).get) -// def getDep2[F[-_, +_, +_]: Ask3]: F[Has[Dependency2], Nothing, Dependency2] = -// F.askWith((_: Has[Dependency2]).get) -// -// final class ResourceHasImpl[F[-_, +_, +_]: Local3]( -// ) extends Lifecycle.LiftF(for { -// d1 <- getDep1 -// d2 <- getDep2 -// } yield new Trait2 { val dep1 = d1; val dep2 = d2 }) -// -// final class ResourceEmptyHasImpl[F[+_, +_]: Applicative2]( -// d1: Dependency1 -// ) extends Lifecycle.LiftF[F[Throwable, _], Trait1]( -// F.pure(trait1(d1)) -// ) - def getDep1: URIO[Dependency1, Dependency1] = ZIO.service[Dependency1] def getDep2: URIO[Dependency2, Dependency2] = ZIO.service[Dependency2] @@ -269,60 +251,6 @@ class ZIOHasInjectionTest extends AnyWordSpec with MkInjector with ZIOTest with assert(!instantiated.acquired) } - // FIXME wtf -// "polymorphic ZIOHas injection" in { -// import TraitCase2._ -// -// def definition[F[-_, +_, +_]: TagK3: Local3] = PlannerInput.everything(new ModuleDef { -// make[Dependency1] -// make[Dependency2] -// make[Dependency3] -// addImplicit[Local3[F]] -// addImplicit[Applicative2[F[Any, +_, +_]]] -// make[Trait3 { def dep1: Dependency1 }].fromZEnv( -// (d3: Dependency3) => -// (for { -// d1 <- getDep1 -// d2 <- getDep2 -// } yield new Trait3 { -// override val dep1 = d1 -// override val dep2 = d2 -// override val dep3 = d3 -// }): F[Has[Dependency1] with Has[Dependency2], Nothing, Trait3] -// ) -// make[Trait2].fromZEnv[ResourceHasImpl[F]] -// make[Trait1].fromZEnv[ResourceEmptyHasImpl[F[Any, +_, +_]]] -// -// many[Trait2].addZEnv[ResourceHasImpl[F]] -// many[Trait1].addZEnv[ResourceEmptyHasImpl[F[Any, +_, +_]]] -// }) -// -// val injector = mkNoCyclesInjector() -// val plan = injector.planUnsafe(definition[ZIO]) -// val context = unsafeRun(injector.produceCustomF[Task](plan).unsafeGet()) -// -// val instantiated = context.get[Trait3 { def dep1: Dependency1 }] -// -// assert(instantiated.dep1 eq context.get[Dependency1]) -// assert(instantiated.dep2 eq context.get[Dependency2]) -// assert(instantiated.dep3 eq context.get[Dependency3]) -// -// val instantiated1 = context.get[Trait3 { def dep1: Dependency1 }] -// assert(instantiated1.dep2 eq context.get[Dependency2]) -// -// val instantiated10 = context.get[Trait2] -// assert(instantiated10.dep2 eq context.get[Dependency2]) -// -// val instantiated2 = context.get[Trait1] -// assert(instantiated2 ne null) -// -// val instantiated3 = context.get[Set[Trait2]].head -// assert(instantiated3.dep2 eq context.get[Dependency2]) -// -// val instantiated4 = context.get[Set[Trait1]].head -// assert(instantiated4 ne null) -// } - "can handle AnyVals" in { import TraitCase6.* diff --git a/distage/distage-core/src/main/scala/distage/Distage.scala b/distage/distage-core/src/main/scala/distage/Distage.scala index d0206ae37f..4a3a0ec10f 100644 --- a/distage/distage-core/src/main/scala/distage/Distage.scala +++ b/distage/distage-core/src/main/scala/distage/Distage.scala @@ -22,6 +22,8 @@ trait Distage { type LocatorRef = model.recursive.LocatorRef + type Subcontext[A] = izumi.distage.Subcontext[A] + type PlanVerifier = solver.PlanVerifier val PlanVerifier: solver.PlanVerifier.type = solver.PlanVerifier @@ -68,7 +70,9 @@ trait Distage { type Functoid[+A] = model.providers.Functoid[A] val Functoid: model.providers.Functoid.type = model.providers.Functoid + @deprecated("Removed since 1.2.0. Use ClassConstructor instead.") type AnyConstructor[T] = constructors.AnyConstructor[T] + @deprecated("Removed since 1.2.0. Use ClassConstructor instead.") val AnyConstructor: constructors.AnyConstructor.type = constructors.AnyConstructor type ClassConstructor[T] = constructors.ClassConstructor[T] diff --git a/distage/distage-core/src/main/scala/distage/Injector.scala b/distage/distage-core/src/main/scala/distage/Injector.scala index 34736ddac3..b7142f7662 100644 --- a/distage/distage-core/src/main/scala/distage/Injector.scala +++ b/distage/distage-core/src/main/scala/distage/Injector.scala @@ -52,7 +52,7 @@ object Injector extends InjectorFactory { ): Injector[F] = { bootstrap(this, bootstrapBase, defaultBootstrapActivation ++ bootstrapActivation, parent, overrides) } - + /** * Create a default Injector with [[izumi.fundamentals.platform.functional.Identity]] effect type * @@ -107,10 +107,10 @@ object Injector extends InjectorFactory { super.bootloader(bootstrapModule, bootstrapActivation, defaultModule, input) } - /** Enable cglib proxies, but try to resolve cycles using by-name parameters if they can be used */ + /** Enable bytebuddy proxies, but try to resolve cycles using by-name parameters if they can be used */ def Standard: Injector.type = this - /** Disable cglib proxies, allow only by-name parameters to resolve cycles */ + /** Disable bytebuddy proxies, allow only by-name parameters to resolve cycles */ object NoProxies extends InjectorBootstrap(Cycles.Byname) /** Disable all cycle resolution, immediately throw when circular dependencies are found, whether by-name or not */ diff --git a/distage/distage-core/src/main/scala/distage/LocalContextImpl.scala b/distage/distage-core/src/main/scala/distage/LocalContextImpl.scala deleted file mode 100644 index 8ea2d4c954..0000000000 --- a/distage/distage-core/src/main/scala/distage/LocalContextImpl.scala +++ /dev/null @@ -1,68 +0,0 @@ -package distage - -import distage.LocalContextImpl.LocalInstance -import izumi.distage.LocalContext -import izumi.distage.model.definition.Identifier -import izumi.distage.model.exceptions.runtime.UndeclaredKeyException -import izumi.distage.model.plan.ExecutableOp.ImportDependency -import izumi.functional.quasi.QuasiIO -import izumi.fundamentals.platform.language.{CodePosition, CodePositionMaterializer} - -final class LocalContextImpl[F[_]: QuasiIO: TagK, A] private ( - externalKeys: Set[DIKey], - parent: LocatorRef, - val plan: Plan, - functoid: Functoid[F[A]], - values: Map[DIKey, LocalInstance[AnyRef]], - selfKey: DIKey, -) extends LocalContext[F, A] { - - def provide[T: Tag](value: T)(implicit pos: CodePositionMaterializer): LocalContext[F, A] = { - val key = DIKey.get[T] - doAdd(value.asInstanceOf[AnyRef], pos, key) - } - - def provideNamed[T: Tag](id: Identifier, value: T)(implicit pos: CodePositionMaterializer): LocalContext[F, A] = { - val key = DIKey[T](id) - doAdd(value.asInstanceOf[AnyRef], pos, key) - } - - private def doAdd(value: AnyRef, pos: CodePositionMaterializer, key: DIKey): LocalContextImpl[F, A] = { - if (!externalKeys.contains(key)) { - throw new UndeclaredKeyException(s"Key $key is not declared as an external key for this local context. The key is declared at ${pos.get.position.toString}", key) - } - - new LocalContextImpl( - externalKeys, - parent, - plan, - functoid, - values ++ Map(key -> LocalInstance(value, pos.get)), - selfKey, - ) - } - - override def produceRun(): F[A] = { - produce().use(QuasiIO[F].pure(_)) - } - - override def produce(): Lifecycle[F, A] = { - val lookup: PartialFunction[ImportDependency, AnyRef] = { - case i: ImportDependency if values.contains(i.target) => - values(i.target).value - case i: ImportDependency if i.target == selfKey => - this - - } - val imported = plan.resolveImports(lookup) - Injector.inherit(parent.get).produce(imported).evalMap(_.run(functoid)) - } - -} - -object LocalContextImpl { - def empty[F[_]: QuasiIO: TagK, R](externalKeys: Set[DIKey], locatorRef: LocatorRef, subplan: Plan, impl: Functoid[F[R]], selfKey: DIKey) = - new LocalContextImpl[F, R](externalKeys, locatorRef, subplan, impl, Map.empty, selfKey) - - private case class LocalInstance[+T](value: T, position: CodePosition) -} diff --git a/distage/distage-core/src/main/scala/distage/package.scala b/distage/distage-core/src/main/scala/distage/package.scala index 8023436525..aeb77829ea 100644 --- a/distage/distage-core/src/main/scala/distage/package.scala +++ b/distage/distage-core/src/main/scala/distage/package.scala @@ -66,7 +66,9 @@ package object distage extends Distage { override type Functoid[+A] = model.providers.Functoid[A] override val Functoid: model.providers.Functoid.type = model.providers.Functoid + @deprecated("Removed since 1.2.0. Use ClassConstructor instead.") override type AnyConstructor[T] = constructors.AnyConstructor[T] + @deprecated("Removed since 1.2.0. Use ClassConstructor instead.") override val AnyConstructor: constructors.AnyConstructor.type = constructors.AnyConstructor override type ClassConstructor[T] = constructors.ClassConstructor[T] diff --git a/distage/distage-core/src/main/scala/izumi/distage/SubcontextImpl.scala b/distage/distage-core/src/main/scala/izumi/distage/SubcontextImpl.scala new file mode 100644 index 0000000000..645eba65f4 --- /dev/null +++ b/distage/distage-core/src/main/scala/izumi/distage/SubcontextImpl.scala @@ -0,0 +1,77 @@ +package izumi.distage + +import distage.Injector +import izumi.distage.model.definition.Identifier +import izumi.distage.model.exceptions.runtime.UndeclaredKeyException +import izumi.distage.model.plan.ExecutableOp.ImportDependency +import izumi.distage.model.plan.Plan +import izumi.distage.model.providers.Functoid +import izumi.distage.model.recursive.LocatorRef +import izumi.distage.model.reflection.DIKey +import izumi.functional.lifecycle.Lifecycle +import izumi.functional.quasi.QuasiIO +import izumi.fundamentals.platform.language.CodePositionMaterializer +import izumi.reflect.{Tag, TagK} + +final class SubcontextImpl[A] private ( + externalKeys: Set[DIKey], + parent: LocatorRef, + val plan: Plan, + functoid: Functoid[A], + providedExternals: Map[DIKey, AnyRef], + selfKey: DIKey, +) extends Subcontext[A] { + + def provide[T: Tag](value: T)(implicit pos: CodePositionMaterializer): Subcontext[A] = { + val key = DIKey.get[T] + doAdd(value.asInstanceOf[AnyRef], pos, key) + } + + def provide[T: Tag](name: Identifier)(value: T)(implicit pos: CodePositionMaterializer): Subcontext[A] = { + val key = DIKey[T](name) + doAdd(value.asInstanceOf[AnyRef], pos, key) + } + + override def produce[F[_]: QuasiIO: TagK](): Lifecycle[F, A] = { + val lookup: PartialFunction[ImportDependency, AnyRef] = { + case i: ImportDependency if providedExternals.contains(i.target) => + providedExternals(i.target) + case i: ImportDependency if i.target == selfKey => + this + } + val imported = plan.resolveImports(lookup) + Injector + .inherit(parent.get) + .produce(imported) + .map(_.run(functoid)) + } + + override def produceRun[F[_]: QuasiIO: TagK, B](f: A => F[B]): F[B] = { + produce().use(f) + } + + override def map[B: Tag](f: A => B): Subcontext[B] = { + new SubcontextImpl(externalKeys, parent, plan, functoid.map[B](f), providedExternals, selfKey) + } + + private def doAdd(value: AnyRef, pos: CodePositionMaterializer, key: DIKey): SubcontextImpl[A] = { + if (!externalKeys.contains(key)) { + throw new UndeclaredKeyException(s"Key $key is not declared as an external key for this local context. The value is provided at ${pos.get.position.toString}", key) + } + + new SubcontextImpl( + externalKeys = externalKeys, + parent = parent, + plan = plan, + functoid = functoid, + providedExternals = providedExternals + (key -> value), + selfKey = selfKey, + ) + } + +} + +object SubcontextImpl { + def empty[A](externalKeys: Set[DIKey], locatorRef: LocatorRef, subplan: Plan, impl: Functoid[A], selfKey: DIKey) = + new SubcontextImpl[A](externalKeys, locatorRef, subplan, impl, Map.empty, selfKey) +} diff --git a/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala b/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala index 5710cf7dea..2d86411a0d 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala @@ -90,7 +90,7 @@ object BootstrapLocator { instanceStrategy = new InstanceStrategyDefaultImpl, effectStrategy = new EffectStrategyDefaultImpl, resourceStrategy = new ResourceStrategyDefaultImpl, - contextStrategy = new ContextStrategyDefaultImpl(bootstrapPlanner), + subcontextStrategy = new SubcontextStrategyDefaultImpl, ) private final val bootstrapProducer: PlanInterpreter = { @@ -126,7 +126,7 @@ object BootstrapLocator { make[ProviderStrategy].from[ProviderStrategyDefaultImpl] make[ImportStrategy].from[ImportStrategyDefaultImpl] make[InstanceStrategy].from[InstanceStrategyDefaultImpl] - make[ContextStrategy].from[ContextStrategyDefaultImpl] + make[SubcontextStrategy].from[SubcontextStrategyDefaultImpl] make[EffectStrategy].from[EffectStrategyDefaultImpl] make[ResourceStrategy].from[ResourceStrategyDefaultImpl] diff --git a/distage/distage-core/src/main/scala/izumi/distage/bootstrap/Cycles.scala b/distage/distage-core/src/main/scala/izumi/distage/bootstrap/Cycles.scala index 313ba1aa99..335f7c9fb1 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/bootstrap/Cycles.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/bootstrap/Cycles.scala @@ -5,10 +5,10 @@ import izumi.distage.model.definition.Axis object Cycles extends Axis { override def name: String = "cycles" - /** Enable cglib proxies, but try to resolve cycles using by-name parameters if they can be used */ + /** Enable bytebuddy proxies, but try to resolve cycles using by-name parameters if they can be used */ case object Proxy extends AxisChoiceDef - /** Disable cglib proxies, allow only by-name parameters to resolve cycles */ + /** Disable bytebuddy proxies, allow only by-name parameters to resolve cycles */ case object Byname extends AxisChoiceDef /** Disable all cycle resolution, immediately throw when circular dependencies are found, whether by-name or not */ 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 113d322b9b..cf0b161ac9 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 @@ -4,13 +4,13 @@ import izumi.distage.model.definition.{Module, ModuleDef} import izumi.functional.quasi.{QuasiApplicative, QuasiAsync, QuasiFunctor, QuasiIO, QuasiIORunner, QuasiPrimitives, QuasiTemporal} 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.functional.bio.retry.Scheduler2 +import izumi.functional.bio.{Async2, Fork2, Primitives2, Temporal2, UnsafeRun2} import izumi.fundamentals.orphans.* import izumi.fundamentals.platform.functional.Identity import scala.annotation.unused -import izumi.reflect.{Tag, TagK, TagK3, TagKK} +import izumi.reflect.{Tag, TagK, TagKK} /** * Implicitly available effect type support for `distage` resources, effects, roles & tests. @@ -120,20 +120,13 @@ sealed trait LowPriorityDefaultModulesInstances2 extends LowPriorityDefaultModul } sealed trait LowPriorityDefaultModulesInstances3 extends LowPriorityDefaultModulesInstances4 { - /** @see [[izumi.distage.modules.support.AnyBIO2SupportModule]] */ - implicit final def fromBIO2[F[+_, +_]: TagKK: Async2: Temporal2: UnsafeRun2: Fork2: Primitives2: Scheduler2]: DefaultModule2[F] = { - DefaultModule(AnyBIO2SupportModule.withImplicits[F]) + /** @see [[izumi.distage.modules.support.AnyBIOSupportModule]] */ + implicit final def fromBIO[F[+_, +_]: TagKK: Async2: Temporal2: UnsafeRun2: Fork2: Primitives2: Scheduler2]: DefaultModule2[F] = { + DefaultModule(AnyBIOSupportModule.withImplicits[F]) } } sealed trait LowPriorityDefaultModulesInstances4 extends LowPriorityDefaultModulesInstances5 { - /** @see [[izumi.distage.modules.support.AnyBIO3SupportModule]] */ - implicit final def fromBIO3[F[-_, +_, +_]: TagK3: Async3: Temporal3: Local3: UnsafeRun3: Fork3: Primitives3: Scheduler3]: DefaultModule3[F] = { - DefaultModule(AnyBIO3SupportModule.withImplicits[F, Any]) - } -} - -sealed trait LowPriorityDefaultModulesInstances5 extends LowPriorityDefaultModulesInstances6 { /** * 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. @@ -154,7 +147,7 @@ sealed trait LowPriorityDefaultModulesInstances5 extends LowPriorityDefaultModul } } -sealed trait LowPriorityDefaultModulesInstances6 { +sealed trait LowPriorityDefaultModulesInstances5 { implicit final def fromQuasiIO[F[_]: TagK: QuasiIO: QuasiAsync: QuasiTemporal: QuasiIORunner]: DefaultModule[F] = { DefaultModule(new ModuleDef { addImplicit[QuasiFunctor[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 deleted file mode 100644 index 7adb3c4f69..0000000000 --- a/distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIO3SupportModule.scala +++ /dev/null @@ -1,86 +0,0 @@ -package izumi.distage.modules.support - -import izumi.distage.model.definition.ModuleDef -import izumi.distage.modules.typeclass.BIO3InstancesModule -import izumi.functional.bio.retry.{Scheduler2, Scheduler3} -import izumi.functional.bio.{Async2, Async3, Fork2, Fork3, Local3, Primitives2, Primitives3, PrimitivesM2, PrimitivesM3, SyncSafe2, SyncSafe3, Temporal2, Temporal3, UnsafeRun2, UnsafeRun3} -import izumi.functional.quasi.* -import izumi.reflect.{Tag, TagK3} - -/** - * Any `BIO` effect type support for `distage` resources, effects, roles & tests. - * - * For all `F[-_, +_, +_]` with available `make[Async3[F]]`, `make[Temporal3[F]]` and `make[UnsafeRun3[F]]` bindings. - * - * - Adds [[izumi.functional.quasi.QuasiIO]] instances to support using `F[-_, +_, +_]` in `Injector`, `distage-framework` & `distage-testkit-scalatest` - * - Adds [[izumi.functional.bio]] typeclass instances for `F[-_, +_, +_]` - * - * Depends on `make[Async3[F]]`, `make[Temporal3[F]]`, `make[Local3[F]]`, `make[Fork3[F]]`, `make[UnsafeRun3[F]]` - * Optional additions: `make[Primitives3[F]]`, `make[PrimitivesM3[F]]`, `make[Scheduler3[F]]` - */ -class AnyBIO3SupportModule[F[-_, +_, +_]: TagK3, R0: Tag] extends ModuleDef { - // trifunctor bio instances - include(BIO3InstancesModule[F]) - - def bio2Module[R: Tag]: ModuleDef = new ModuleDef { - // QuasiIO & bifunctor bio instances - include(AnyBIO2SupportModule[F[R, +_, +_]]) - - make[Async2[F[R, +_, +_]]].from { - implicit F: Async3[F] => Async2[F[R, +_, +_]] - } - make[Temporal2[F[R, +_, +_]]].from { - implicit F: Temporal3[F] => Temporal2[F[R, +_, +_]] - } - make[Fork2[F[R, +_, +_]]].from { - implicit Fork: Fork3[F] => Fork2[F[R, +_, +_]] - } - } - - include(bio2Module[Any]) - if (!(Tag[R0] =:= Tag[Any])) { - include(bio2Module[R0]) - } -} - -object AnyBIO3SupportModule extends App with ModuleDef { - @inline def apply[F[-_, +_, +_]: TagK3, R: Tag]: AnyBIO3SupportModule[F, R] = new AnyBIO3SupportModule[F, R] - - /** - * Make [[AnyBIO3SupportModule]], binding the required dependencies in place to values from implicit scope - * - * `make[Fork3[F]]` and `make[Primitives3[F]]` are not required by [[AnyBIO3SupportModule]] - * but are added for completeness - */ - def withImplicits[F[-_, +_, +_]: TagK3: Async3: Temporal3: Local3: UnsafeRun3: Fork3: Primitives3: PrimitivesM3: Scheduler3, R: Tag]: ModuleDef = - new ModuleDef { - include(AnyBIO3SupportModule[F, R]) - - addImplicit[Async3[F]] - addImplicit[Temporal3[F]] - addImplicit[Local3[F]] - addImplicit[Fork3[F]] - addImplicit[Primitives3[F]] - addImplicit[PrimitivesM3[F]] - addImplicit[UnsafeRun3[F]] - addImplicit[Scheduler3[F]] - - // no corresponding bifunctor (`F[Any, +_, +_]`) instances need to be added for these types because they already match - private[this] def aliasingCheck(): Unit = { - lazy val _ = aliasingCheck() - implicitly[Scheduler3[F] =:= Scheduler2[F[Any, +_, +_]]] - implicitly[UnsafeRun3[F] =:= UnsafeRun2[F[Any, +_, +_]]] - implicitly[Primitives3[F] =:= Primitives2[F[Any, +_, +_]]] - implicitly[PrimitivesM3[F] =:= PrimitivesM2[F[Any, +_, +_]]] - implicitly[SyncSafe3[F] =:= SyncSafe2[F[Any, +_, +_]]] - implicitly[QuasiIORunner3[F] =:= QuasiIORunner2[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, +_, +_]]] - implicitly[QuasiTemporal3[F] =:= QuasiTemporal2[F[Any, +_, +_]]] - () - } - } -} 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/AnyBIOSupportModule.scala similarity index 84% rename from distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIO2SupportModule.scala rename to distage/distage-core/src/main/scala/izumi/distage/modules/support/AnyBIOSupportModule.scala index bffd958f16..3da1d9e44a 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/AnyBIOSupportModule.scala @@ -2,7 +2,7 @@ package izumi.distage.modules.support import izumi.distage.model.definition.ModuleDef import izumi.functional.quasi.* -import izumi.distage.modules.typeclass.BIO2InstancesModule +import izumi.distage.modules.typeclass.BIOInstancesModule import izumi.functional.bio.retry.Scheduler2 import izumi.functional.bio.{Async2, Clock1, Clock2, Entropy1, Entropy2, Fork2, IO2, Primitives2, PrimitivesM2, SyncSafe1, SyncSafe2, Temporal2, UnsafeRun2} import izumi.fundamentals.platform.functional.Identity @@ -21,8 +21,8 @@ import scala.concurrent.ExecutionContext * Depends on `make[Async2[F]]`, `make[Temporal2[F]]`, `make[UnsafeRun2[F]]`, `make[Fork2[F]]` * Optional additions: `make[Primitives2[F]]`, `make[PrimitivesM2[F]]`, `make[Scheduler2[F]]` */ -class AnyBIO2SupportModule[F[+_, +_]: TagKK] extends ModuleDef { - include(BIO2InstancesModule[F]) +class AnyBIOSupportModule[F[+_, +_]: TagKK] extends ModuleDef { + include(BIOInstancesModule[F]) make[QuasiIORunner2[F]] .from[QuasiIORunner.BIOImpl[F]] @@ -59,17 +59,17 @@ class AnyBIO2SupportModule[F[+_, +_]: TagKK] extends ModuleDef { } } -object AnyBIO2SupportModule extends ModuleDef { - @inline def apply[F[+_, +_]: TagKK]: AnyBIO2SupportModule[F] = new AnyBIO2SupportModule +object AnyBIOSupportModule extends ModuleDef { + @inline def apply[F[+_, +_]: TagKK]: AnyBIOSupportModule[F] = new AnyBIOSupportModule /** - * Make [[AnyBIO2SupportModule]], binding the required dependencies in place to values from implicit scope + * Make [[AnyBIOSupportModule]], binding the required dependencies in place to values from implicit scope * - * `make[Fork2[F]]` and `make[Primitives2[F]]` are not required by [[AnyBIO2SupportModule]] + * `make[Fork2[F]]` and `make[Primitives2[F]]` are not required by [[AnyBIOSupportModule]] * but are added for completeness */ def withImplicits[F[+_, +_]: TagKK: Async2: Temporal2: UnsafeRun2: Fork2: Primitives2: PrimitivesM2: Scheduler2]: ModuleDef = new ModuleDef { - include(AnyBIO2SupportModule[F]) + include(AnyBIOSupportModule[F]) addImplicit[Async2[F]] addImplicit[Fork2[F]] 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 0d17ceaf31..3ded7c42ee 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 @@ -3,11 +3,12 @@ package izumi.distage.modules.support import cats.Parallel import cats.effect.kernel.{Async, GenTemporal, Sync} import cats.effect.std.Dispatcher -import distage.{ModuleDef, TagK} +import izumi.distage.model.definition.ModuleDef import izumi.distage.modules.typeclass.CatsEffectInstancesModule import izumi.functional.bio.{Clock1, Entropy1, SyncSafe1} import izumi.functional.quasi.* import izumi.fundamentals.platform.functional.Identity +import izumi.reflect.TagK /** * Any `cats-effect` effect type support for `distage` resources, effects, roles & tests. diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixBIOSupportModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixBIOSupportModule.scala index b84c3239bf..407322d268 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixBIOSupportModule.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixBIOSupportModule.scala @@ -33,7 +33,7 @@ // */ //trait MonixBIOSupportModule extends ModuleDef with MonixBIOPlatformDependentSupportModule { // // QuasiIO & BIO instances -// include(AnyBIO2SupportModule[IO]) +// include(AnyBIOSupportModule[IO]) // // cats-effect instances // include(CatsEffectInstancesModule[Task]) // diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/support/ZIOSupportModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/support/ZIOSupportModule.scala index 89616560d7..16644b7f02 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/modules/support/ZIOSupportModule.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/modules/support/ZIOSupportModule.scala @@ -1,12 +1,10 @@ package izumi.distage.modules.support -import distage.DIKey import izumi.distage.model.definition.Id import izumi.distage.modules.platform.ZIOPlatformDependentSupportModule import izumi.functional.bio.* import izumi.functional.bio.UnsafeRun2.{FailureHandler, ZIORunner} -import izumi.functional.bio.retry.{Scheduler3, SchedulerInstances} -import izumi.fundamentals.platform.language.Quirks.Discarder +import izumi.functional.bio.retry.{Scheduler2, SchedulerInstances} import izumi.reflect.Tag import zio.{Executor, IO, Runtime, ZEnvironment, ZIO, ZLayer} @@ -34,31 +32,17 @@ object ZIOSupportModule { * Bindings to the same keys in your own [[izumi.distage.model.definition.ModuleDef]] or plugins will override these defaults. */ class ZIOSupportModule[R: Tag] extends ZIOPlatformDependentSupportModule[R] { - include( - AnyBIO3SupportModule[ZIO, R] - // FIXME wtf - // FIXME wtf trifunctor broken - .--( - Set( - DIKey[Ask3[ZIO]], - DIKey[MonadAsk3[ZIO]], - DIKey[Profunctor3[ZIO]], - DIKey[Arrow3[ZIO]], - ) - ) - ) + include(AnyBIOSupportModule[ZIO[Any, +_, +_]]) + if (!(Tag[R] =:= Tag[Any])) { + include(AnyBIOSupportModule[ZIO[R, +_, +_]]) + } // assume default environment is `Any`, otherwise let the error message guide the user here. make[ZEnvironment[Any]].named("zio-initial-env").fromValue(ZEnvironment.empty) make[UnsafeRun2[ZIO[R, _, _]]].using[ZIORunner[R]] - make[BlockingIO3[ZIO]].from(BlockingIOInstances.BlockingZIODefault) - make[BlockingIO2[ZIO[R, +_, +_]]].from { - implicit B: BlockingIO3[ZIO] => - B.discard() // Parameter not used on .js - BlockingIO2[ZIO[R, +_, +_]] - } + make[BlockingIO2[ZIO[R, +_, +_]]].from(BlockingIOInstances.BlockingZIODefaultR[ZIO, R]) make[ZIORunner[R]].from { ( @@ -87,17 +71,21 @@ class ZIOSupportModule[R: Tag] extends ZIOPlatformDependentSupportModule[R] { make[ExecutionContext].named("cpu").from((_: Executor @Id("cpu")).asExecutionContext) make[ExecutionContext].named("io").from((_: Executor @Id("io")).asExecutionContext) - // FIXME wtf - addImplicit[Async3[ZIO]] - addImplicit[Temporal3[ZIO]] - // FIXME wtf -// addImplicit[Local3[ZIO]] - addImplicit[Fork3[ZIO]] - addImplicit[Primitives3[ZIO]] - addImplicit[PrimitivesM3[ZIO]] + addImplicit[Async2[zio.IO]] + addImplicit[Temporal2[zio.IO]] + addImplicit[Fork2[zio.IO]] + addImplicit[Primitives2[zio.IO]] + addImplicit[PrimitivesM2[zio.IO]] + if (!(Tag[R] =:= Tag[Any])) { + addImplicit[Async2[ZIO[R, +_, +_]]] + addImplicit[Temporal2[ZIO[R, +_, +_]]] + addImplicit[Fork2[ZIO[R, +_, +_]]] + addImplicit[Primitives2[ZIO[R, +_, +_]]] + addImplicit[PrimitivesM2[ZIO[R, +_, +_]]] + } - make[Scheduler3[ZIO]].from { - SchedulerInstances.SchedulerFromTemporalAndClock(_: Temporal2[zio.IO], _: Clock3[ZIO]) + make[Scheduler2[ZIO[R, +_, +_]]].from { + SchedulerInstances.SchedulerFromTemporalAndClock(_: Temporal2[ZIO[R, +_, +_]], _: Clock2[ZIO[R, +_, +_]]) } addImplicit[TransZio[IO]] diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/BIO3InstancesModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/BIO3InstancesModule.scala deleted file mode 100644 index 37e26c1ab0..0000000000 --- a/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/BIO3InstancesModule.scala +++ /dev/null @@ -1,58 +0,0 @@ -package izumi.distage.modules.typeclass - -import izumi.distage.model.definition.ModuleDef -import izumi.functional.bio._ -import izumi.functional.bio.retry.Scheduler3 -import izumi.reflect.TagK3 - -/** - * Adds `bio` typeclass instances for any effect type `F[-_, +_, +_]` with an available `make[Async3[F]` binding - * - * Depends on `Async3[F]` and `Local3[F]` - * - * @note doesn't add bifunctor variants from [[BIO2InstancesModule]] - * - * @see [[izumi.functional.bio]] - */ -class BIO3InstancesModule[F[-_, +_, +_]: TagK3] extends ModuleDef { - make[Functor3[F]].using[Async3[F]] - make[Bifunctor3[F]].using[Async3[F]] - make[Applicative3[F]].using[Async3[F]] - make[Guarantee3[F]].using[Async3[F]] - make[ApplicativeError3[F]].using[Async3[F]] - make[Monad3[F]].using[Async3[F]] - make[Error3[F]].using[Async3[F]] - make[Bracket3[F]].using[Async3[F]] - make[Panic3[F]].using[Async3[F]] - make[IO3[F]].using[Async3[F]] - make[Parallel3[F]].using[Async3[F]] - make[Concurrent3[F]].using[Async3[F]] - - make[Ask3[F]].using[Local3[F]] - make[MonadAsk3[F]].using[Local3[F]] - make[Profunctor3[F]].using[Local3[F]] - make[Arrow3[F]].using[Local3[F]] -} - -object BIO3InstancesModule { - @inline def apply[F[-_, +_, +_]: TagK3]: BIO3InstancesModule[F] = new BIO3InstancesModule - - /** - * Make [[BIO3InstancesModule]], binding the required dependencies in place to values from implicit scope - * - * `make[Temporal3[F]]`, `make[UnsafeRun3[F]]` `make[Fork3[F]]` and `make[Primitives3[F]]` are not required by [[BIO3InstancesModule]] - * but are added for completeness - */ - def withImplicits[F[-_, +_, +_]: TagK3: Async3: Temporal3: Local3: UnsafeRun3: Fork3: Primitives3: PrimitivesM3: Scheduler3]: ModuleDef = new ModuleDef { - include(BIO3InstancesModule[F]) - - addImplicit[Async3[F]] - addImplicit[Temporal3[F]] - addImplicit[Local3[F]] - addImplicit[Fork3[F]] - addImplicit[Primitives3[F]] - addImplicit[PrimitivesM3[F]] - addImplicit[UnsafeRun3[F]] - addImplicit[Scheduler3[F]] - } -} diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/BIO2InstancesModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/BIOInstancesModule.scala similarity index 76% rename from distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/BIO2InstancesModule.scala rename to distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/BIOInstancesModule.scala index 289389c2d7..0cf893bad8 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/BIO2InstancesModule.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/BIOInstancesModule.scala @@ -12,7 +12,7 @@ import izumi.reflect.TagKK * * @see [[izumi.functional.bio]] */ -class BIO2InstancesModule[F[+_, +_]: TagKK] extends ModuleDef { +class BIOInstancesModule[F[+_, +_]: TagKK] extends ModuleDef { make[Functor2[F]].using[Async2[F]] make[Bifunctor2[F]].using[Async2[F]] make[Applicative2[F]].using[Async2[F]] @@ -27,17 +27,17 @@ class BIO2InstancesModule[F[+_, +_]: TagKK] extends ModuleDef { make[Concurrent2[F]].using[Async2[F]] } -object BIO2InstancesModule { - @inline def apply[F[+_, +_]: TagKK]: BIO2InstancesModule[F] = new BIO2InstancesModule +object BIOInstancesModule { + @inline def apply[F[+_, +_]: TagKK]: BIOInstancesModule[F] = new BIOInstancesModule /** - * Make [[BIO2InstancesModule]], binding the required dependencies in place to values from implicit scope + * Make [[BIOInstancesModule]], binding the required dependencies in place to values from implicit scope * - * `make[Temporal2[F]]`, `make[UnsafeRun2[F]]` `make[Fork2[F]]` and `make[Primitives2[F]]` are not required by [[BIO2InstancesModule]] + * `make[Temporal2[F]]`, `make[UnsafeRun2[F]]` `make[Fork2[F]]` and `make[Primitives2[F]]` are not required by [[BIOInstancesModule]] * but are added for completeness */ def withImplicits[F[+_, +_]: TagKK: Async2: Temporal2: UnsafeRun2: Fork2: Primitives2: PrimitivesM2: Scheduler2]: ModuleDef = new ModuleDef { - include(BIO2InstancesModule[F]) + include(BIOInstancesModule[F]) addImplicit[Async2[F]] addImplicit[Fork2[F]] diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/CatsEffectInstancesModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/CatsEffectInstancesModule.scala index 63a5ca60f6..62961c7669 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/CatsEffectInstancesModule.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/CatsEffectInstancesModule.scala @@ -2,8 +2,8 @@ package izumi.distage.modules.typeclass import cats.effect.kernel.* import cats.{Applicative, ApplicativeError, Apply, FlatMap, Functor, Invariant, InvariantSemigroupal, Monad, MonadError, Parallel, Semigroupal} -import distage.TagK import izumi.distage.model.definition.ModuleDef +import izumi.reflect.TagK /** * Adds `cats-effect` typeclass instances for any effect type `F[_]` with an available `make[ConcurrentEffect[F]` binding diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/ZIOCatsEffectInstancesModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/ZIOCatsEffectInstancesModule.scala index 9aeb8be742..49b5ca436f 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/ZIOCatsEffectInstancesModule.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/ZIOCatsEffectInstancesModule.scala @@ -2,7 +2,7 @@ package izumi.distage.modules.typeclass import cats.Parallel import cats.effect.kernel.Async -import distage.ModuleDef +import izumi.distage.model.definition.ModuleDef import izumi.reflect.Tag import zio.ZIO diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/BindingTranslator.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/BindingTranslator.scala index b78a1965a9..5dc75ec16a 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/planning/BindingTranslator.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/planning/BindingTranslator.scala @@ -11,7 +11,7 @@ import izumi.distage.model.reflection.DIKey import izumi.distage.planning.BindingTranslator.NextOps trait BindingTranslator { - def computeProvisioning[Err](handler: LocalContextHandler[Err], binding: Binding): Either[Err, NextOps] + def computeProvisioning[Err](handler: SubcontextHandler[Err], binding: Binding): Either[Err, NextOps] } object BindingTranslator { @@ -22,7 +22,7 @@ object BindingTranslator { ) class Impl extends BindingTranslator { - def computeProvisioning[Err](handler: LocalContextHandler[Err], binding: Binding): Either[Err, NextOps] = { + def computeProvisioning[Err](handler: SubcontextHandler[Err], binding: Binding): Either[Err, NextOps] = { binding match { case singleton: SingletonBinding[?] => for { @@ -63,7 +63,7 @@ object BindingTranslator { } } - private[this] def provisionSingleton[Err](handler: LocalContextHandler[Err], binding: Binding.ImplBinding): Either[Err, Seq[InstantiationOp]] = { + private[this] def provisionSingleton[Err](handler: SubcontextHandler[Err], binding: Binding.ImplBinding): Either[Err, Seq[InstantiationOp]] = { val target = binding.key for { wiring <- implToWiring(handler, binding) @@ -103,14 +103,14 @@ object BindingTranslator { case w: Reference => WiringOp.ReferenceKey(target, w, userBinding) - case w: PrepareLocalContext => - // it's safe to import self reference and it can always be synthetised within a context - val removedSelfImport = w.importedParentKeys.diff(Set(target)) - WiringOp.LocalContext(target, w.copy(importedParentKeys = removedSelfImport), userBinding) + case w: PrepareSubcontext => + // it's safe to import self reference and it can always be synthesised within a context + val removedSelfImport = w.importedFromParentKeys.diff(Set(target)) + WiringOp.CreateSubcontext(target, w.copy(importedFromParentKeys = removedSelfImport), userBinding) } } - private[this] def implToWiring[Err](handler: LocalContextHandler[Err], binding: Binding.ImplBinding): Either[Err, Wiring] = { + private[this] def implToWiring[Err](handler: SubcontextHandler[Err], binding: Binding.ImplBinding): Either[Err, Wiring] = { binding.implementation match { case d: ImplDef.DirectImplDef => directImplToPureWiring(handler, binding, d) @@ -132,7 +132,7 @@ object BindingTranslator { } private[this] def directImplToPureWiring[Err]( - handler: LocalContextHandler[Err], + handler: SubcontextHandler[Err], binding: Binding, implementation: ImplDef.DirectImplDef, ): Either[Err, SingletonWiring] = { diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/LocalContextHandler.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/SubcontextHandler.scala similarity index 59% rename from distage/distage-core/src/main/scala/izumi/distage/planning/LocalContextHandler.scala rename to distage/distage-core/src/main/scala/izumi/distage/planning/SubcontextHandler.scala index 39aeb0a1c9..83a1916dd3 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/planning/LocalContextHandler.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/planning/SubcontextHandler.scala @@ -1,8 +1,8 @@ package izumi.distage.planning -import distage.Roots import izumi.distage.model.definition.errors.{LocalContextPlanningFailure, LocalContextVerificationFailure} import izumi.distage.model.definition.{Binding, ImplDef} +import izumi.distage.model.plan.{Plan, Roots} import izumi.distage.model.plan.Wiring.SingletonWiring import izumi.distage.model.planning.{AxisPoint, PlanIssue} import izumi.distage.model.{Planner, PlannerInput} @@ -11,31 +11,38 @@ import izumi.distage.planning.solver.PlanVerifier.PlanVerifierResult import izumi.fundamentals.collections.nonempty.NESet import izumi.fundamentals.platform.functional.Identity -trait LocalContextHandler[+E] { +trait SubcontextHandler[+E] { def handle(binding: Binding, c: ImplDef.ContextImpl): Either[E, SingletonWiring] } -object LocalContextHandler { - class KnownActivationHandler(planner: Planner, input: PlannerInput) extends LocalContextHandler[LocalContextPlanningFailure] { +object SubcontextHandler { + class KnownActivationHandler( + planner: Planner, + input: PlannerInput, + ) extends SubcontextHandler[LocalContextPlanningFailure] { override def handle(binding: Binding, c: ImplDef.ContextImpl): Either[LocalContextPlanningFailure, SingletonWiring] = { - val roots = c.function.get.diKeys.toSet + val roots = c.extractingFunction.diKeys.toSet for { subplan <- planner - .plan(PlannerInput(c.module, input.activation, roots)).left.map(errors => LocalContextPlanningFailure(binding, c, errors)) + .plan(PlannerInput(c.module, input.activation, roots)) + .left.map(errors => LocalContextPlanningFailure(binding, c, errors)) } yield { val allImported = subplan.importedKeys val importedParents = allImported.diff(c.externalKeys) - SingletonWiring.PrepareLocalContext(c.function, c.module, c.implType, c.externalKeys, importedParents) + SingletonWiring.PrepareSubcontext(c.extractingFunction, subplan, c.implType, c.externalKeys, importedParents) } } } - class VerificationHandler(verifier: PlanVerifier, excludedActivations: Set[NESet[AxisPoint]]) extends LocalContextHandler[LocalContextVerificationFailure] { + class VerificationHandler( + verifier: PlanVerifier, + excludedActivations: Set[NESet[AxisPoint]], + ) extends SubcontextHandler[LocalContextVerificationFailure] { override def handle(binding: Binding, c: ImplDef.ContextImpl): Either[LocalContextVerificationFailure, SingletonWiring] = { - val roots = c.function.get.diKeys.toSet - val ver = verifier.verify[Identity](c.module, Roots(roots), k => c.externalKeys.contains(k), excludedActivations) + val roots = c.extractingFunction.diKeys.toSet + val verifierResult = verifier.verify[Identity](c.module, Roots(roots), c.externalKeys, excludedActivations) - ver match { + verifierResult match { case incorrect: PlanVerifierResult.Incorrect => val issues = incorrect.issues.value.toSet @@ -45,13 +52,13 @@ object LocalContextHandler { } if (issues.forall(_.isInstanceOf[PlanIssue.MissingImport])) { - Right(SingletonWiring.PrepareLocalContext(c.function, c.module, c.implType, c.externalKeys, missingImports)) + Right(SingletonWiring.PrepareSubcontext(c.extractingFunction, Plan.empty, c.implType, c.externalKeys, missingImports)) } else { Left(LocalContextVerificationFailure(binding, c, issues)) } case _: PlanVerifierResult.Correct => - Right(SingletonWiring.PrepareLocalContext(c.function, c.module, c.implType, c.externalKeys, Set.empty)) + Right(SingletonWiring.PrepareSubcontext(c.extractingFunction, Plan.empty, c.implType, c.externalKeys, Set.empty)) } } diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/sequential/CycleTools.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/sequential/CycleTools.scala index bcb359e257..9aafd50c68 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/planning/sequential/CycleTools.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/planning/sequential/CycleTools.scala @@ -41,7 +41,7 @@ object CycleTools { Seq(w.target -> false) case _: WiringOp.ReferenceKey => Seq(w.target -> false) - case _: WiringOp.LocalContext => + case _: WiringOp.CreateSubcontext => Seq(w.target -> false) } case o => diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/sequential/SanityCheckerDefaultImpl.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/sequential/SanityCheckerDefaultImpl.scala index c3e633a975..1cfefcc680 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/planning/sequential/SanityCheckerDefaultImpl.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/planning/sequential/SanityCheckerDefaultImpl.scala @@ -1,8 +1,7 @@ package izumi.distage.planning.sequential -import distage.Roots import izumi.distage.model.definition.errors.DIError -import izumi.distage.model.plan.ExecutableOp +import izumi.distage.model.plan.{ExecutableOp, Roots} import izumi.distage.model.plan.ExecutableOp.ProxyOp import izumi.distage.model.planning.SanityChecker import izumi.distage.model.reflection.DIKey diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/solver/GraphPreparations.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/solver/GraphPreparations.scala index 3edd94d0dd..1fd693ba60 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/planning/solver/GraphPreparations.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/planning/solver/GraphPreparations.scala @@ -8,7 +8,7 @@ import izumi.distage.model.plan.{ExecutableOp, Roots, Wiring} import izumi.distage.model.planning.AxisPoint import izumi.distage.model.reflection.DIKey import izumi.distage.planning.solver.SemigraphSolver.SemiEdgeSeq -import izumi.distage.planning.{BindingTranslator, LocalContextHandler} +import izumi.distage.planning.{BindingTranslator, SubcontextHandler} import izumi.functional.IzEither.* import izumi.fundamentals.collections.MutableMultiMap import izumi.fundamentals.collections.nonempty.NEList @@ -110,7 +110,7 @@ class GraphPreparations( } def computeOperationsUnsafe[Err]( - handler: LocalContextHandler[Err], + handler: SubcontextHandler[Err], bindings: ModuleBase, ): Either[NEList[Err], Iterator[(Annotated[DIKey], InstantiationOp, Binding)]] = { diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanSolver.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanSolver.scala index 5db85df185..e269024d71 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanSolver.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanSolver.scala @@ -10,7 +10,7 @@ import izumi.distage.model.plan.{ExecutableOp, Wiring} import izumi.distage.model.planning.{ActivationChoices, AxisPoint} import izumi.distage.model.reflection.DIKey import izumi.distage.model.{Planner, PlannerInput} -import izumi.distage.planning.LocalContextHandler +import izumi.distage.planning.SubcontextHandler import izumi.distage.planning.solver.SemigraphSolver.* import izumi.functional.IzEither.* import izumi.fundamentals.collections.nonempty.NEList @@ -107,8 +107,8 @@ object PlanSolver { planner: Planner, ac: ActivationChoices, input: PlannerInput, - ) = { - val handler = new LocalContextHandler.KnownActivationHandler(planner, input) + ): Either[NEList[ConflictResolutionError[DIKey, InstantiationOp]], Seq[(Annotated[DIKey], InstantiationOp)]] = { + val handler = new SubcontextHandler.KnownActivationHandler(planner, input) for { maybeOps <- preps.computeOperationsUnsafe(handler, input.bindings).left.map(issues => NEList(CannotProcessLocalContext[DIKey](issues))) @@ -133,8 +133,7 @@ object PlanSolver { } yield { out } - - } // Either[List[UnconfiguredMutatorAxis], Seq[(Annotated[DIKey], InstantiationOp)]] + } private def computeSets( ac: ActivationChoices, diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanVerifier.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanVerifier.scala index 2fde52557b..641fde46d1 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanVerifier.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/planning/solver/PlanVerifier.scala @@ -1,6 +1,5 @@ package izumi.distage.planning.solver -import distage.TagK import izumi.distage.model.definition.ModuleBase import izumi.distage.model.definition.conflicts.{Annotated, Node} import izumi.distage.model.exceptions.PlanVerificationException @@ -13,13 +12,14 @@ import izumi.distage.model.reflection.DIKey.SetElementKey import izumi.distage.model.reflection.{DIKey, SafeType} import izumi.distage.planning.solver.PlanVerifier.PlanVerifierResult import izumi.distage.planning.solver.SemigraphSolver.SemiEdgeSeq -import izumi.distage.planning.{BindingTranslator, LocalContextHandler} +import izumi.distage.planning.{BindingTranslator, SubcontextHandler} import izumi.distage.provisioning.strategies.ImportStrategyDefaultImpl import izumi.functional.IzEither.* import izumi.fundamentals.collections.IzCollections.* import izumi.fundamentals.collections.nonempty.{NEList, NEMap, NESet} import izumi.fundamentals.collections.{ImmutableMultiMap, MutableMultiMap} import izumi.fundamentals.platform.strings.IzString.toRichIterable +import izumi.reflect.TagK import java.util.concurrent.TimeUnit import scala.annotation.{nowarn, tailrec} @@ -42,12 +42,9 @@ class PlanVerifier( val before = System.currentTimeMillis() var after = before + val verificationHandler = new SubcontextHandler.VerificationHandler(this, excludedActivations) (for { - ops <- preps - .computeOperationsUnsafe( - new LocalContextHandler.VerificationHandler(this, excludedActivations), - bindings, - ).map(_.toSeq) + ops <- preps.computeOperationsUnsafe(verificationHandler, bindings).map(_.toSeq) } yield { val allAxis: Map[String, Set[String]] = ops.flatMap(_._1.axis).groupBy(_.axis).map { case (axis, points) => @@ -98,10 +95,10 @@ class PlanVerifier( case None => PlanVerifierResult.Correct(visitedKeys, time) } }) match { - case Left(value) => + case Left(errors) => after = System.currentTimeMillis() val time = FiniteDuration(after - before, TimeUnit.MILLISECONDS) - val issues = value.map(f => PlanIssue.CantVerifyLocalContext(f)).toSet[PlanIssue] + val issues = errors.map(f => PlanIssue.CantVerifyLocalContext(f)).toSet[PlanIssue] PlanVerifierResult.Incorrect(Some(NESet.unsafeFrom(issues)), Set.empty, time) case Right(value) => value } diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/solver/SemigraphSolver.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/solver/SemigraphSolver.scala index 89d8f463f8..cc976d5875 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/planning/solver/SemigraphSolver.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/planning/solver/SemigraphSolver.scala @@ -17,12 +17,12 @@ import scala.collection.mutable /** * Combined Garbage Collector, Conflict Resolver and Mutation Resolver * - * Traces the graph from the roots, solves conflict by applying axis rules and + * Traces the graph from the roots, solves the conflicts just in time by applying the axis rules, and * orders mutators in sane and predictable order. * - * "predecessor" stands for "a node which should be processed before it's successor". + * "predecessor" stands for "a node which should be processed before its successor". * - * Map of predecessors is a map where key is a dependant and value is a set of all its direct dependencies + * Map of predecessors is a map where the key is a dependent and the value is a set of all its direct dependencies */ trait SemigraphSolver[N, I, V] { import izumi.distage.planning.solver.SemigraphSolver.* diff --git a/distage/distage-core/src/main/scala/izumi/distage/provisioning/OperationExecutorImpl.scala b/distage/distage-core/src/main/scala/izumi/distage/provisioning/OperationExecutorImpl.scala index 760bc04a5d..9d64b231a4 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/provisioning/OperationExecutorImpl.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/provisioning/OperationExecutorImpl.scala @@ -15,7 +15,7 @@ class OperationExecutorImpl( instanceStrategy: InstanceStrategy, effectStrategy: EffectStrategy, resourceStrategy: ResourceStrategy, - contextStrategy: ContextStrategy, + subcontextStrategy: SubcontextStrategy, ) extends OperationExecutor { override def execute[F[_]: TagK]( @@ -45,8 +45,8 @@ class OperationExecutorImpl( case op: WiringOp.CallProvider => providerStrategy.callProvider(context, op) - case op: WiringOp.LocalContext => - contextStrategy.prepareContext(context, op) + case op: WiringOp.CreateSubcontext => + subcontextStrategy.prepareSubcontext(context, op) case op: ProxyOp.MakeProxy => proxyStrategy.makeProxy(context, op) diff --git a/distage/distage-core/src/main/scala/izumi/distage/provisioning/PlanInterpreterNonSequentialRuntimeImpl.scala b/distage/distage-core/src/main/scala/izumi/distage/provisioning/PlanInterpreterNonSequentialRuntimeImpl.scala index a31c8348d4..d56a52b024 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/provisioning/PlanInterpreterNonSequentialRuntimeImpl.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/provisioning/PlanInterpreterNonSequentialRuntimeImpl.scala @@ -1,17 +1,17 @@ package izumi.distage.provisioning -import distage.{DIKey, Id, Roots, SafeType} import izumi.distage.LocatorDefaultImpl -import izumi.distage.model.definition.Lifecycle +import izumi.distage.model.definition.{Id, Lifecycle} import izumi.distage.model.definition.errors.ProvisionerIssue import izumi.distage.model.definition.errors.ProvisionerIssue.IncompatibleEffectTypes import izumi.distage.model.definition.errors.ProvisionerIssue.ProvisionerExceptionIssue.{IntegrationCheckFailure, UnexpectedIntegrationCheck} import izumi.distage.model.exceptions.runtime.IntegrationCheckException import izumi.distage.model.plan.ExecutableOp.* -import izumi.distage.model.plan.{ExecutableOp, Plan} +import izumi.distage.model.plan.{ExecutableOp, Plan, Roots} import izumi.distage.model.provisioning.* import izumi.distage.model.provisioning.PlanInterpreter.{FailedProvision, FailedProvisionInternal, FinalizerFilter} import izumi.distage.model.provisioning.strategies.* +import izumi.distage.model.reflection.{DIKey, SafeType} import izumi.distage.model.{Locator, Planner} import izumi.distage.provisioning.PlanInterpreterNonSequentialRuntimeImpl.{abstractCheckType, integrationCheckIdentityType, nullType} import izumi.functional.quasi.QuasiIO diff --git a/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ImportStrategyDefaultImpl.scala b/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ImportStrategyDefaultImpl.scala index 81b597d22d..882a3ab259 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ImportStrategyDefaultImpl.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ImportStrategyDefaultImpl.scala @@ -1,12 +1,12 @@ package izumi.distage.provisioning.strategies -import distage.{DIKey, ModuleBase} -import izumi.distage.model.definition.Binding +import izumi.distage.model.definition.{Binding, ModuleBase} import izumi.distage.model.definition.errors.ProvisionerIssue.MissingImport import izumi.distage.model.plan.ExecutableOp.ImportDependency import izumi.distage.model.plan.Plan import izumi.distage.model.provisioning.strategies.ImportStrategy import izumi.distage.model.provisioning.{NewObjectOp, ProvisioningKeyProvider} +import izumi.distage.model.reflection.DIKey class ImportStrategyDefaultImpl extends ImportStrategy { override def importDependency(context: ProvisioningKeyProvider, plan: Plan, op: ImportDependency): Either[MissingImport, Seq[NewObjectOp]] = { diff --git a/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ProxyStrategyDefaultImpl.scala b/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ProxyStrategyDefaultImpl.scala index 0e1910117a..e24b7d6e55 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ProxyStrategyDefaultImpl.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ProxyStrategyDefaultImpl.scala @@ -136,7 +136,7 @@ class ProxyStrategyDefaultImpl( Right(op.target.tpe) case op: WiringOp.UseInstance => Left(UnsupportedProxyOp(op)) - case op: WiringOp.LocalContext => + case op: WiringOp.CreateSubcontext => Left(UnsupportedProxyOp(op)) case op: WiringOp.ReferenceKey => Left(UnsupportedProxyOp(op)) diff --git a/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ContextStrategyDefaultImpl.scala b/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/SubcontextStrategyDefaultImpl.scala similarity index 52% rename from distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ContextStrategyDefaultImpl.scala rename to distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/SubcontextStrategyDefaultImpl.scala index 32020a17d3..2e9000ea16 100644 --- a/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/ContextStrategyDefaultImpl.scala +++ b/distage/distage-core/src/main/scala/izumi/distage/provisioning/strategies/SubcontextStrategyDefaultImpl.scala @@ -1,34 +1,30 @@ package izumi.distage.provisioning.strategies -import distage.{LocalContextImpl, LocatorRef, Planner, PlannerInput} +import izumi.distage.SubcontextImpl import izumi.distage.model.definition.errors.ProvisionerIssue import izumi.distage.model.definition.errors.ProvisionerIssue.MissingInstance import izumi.distage.model.plan.ExecutableOp.{AddRecursiveLocatorRef, WiringOp} import izumi.distage.model.providers.Functoid -import izumi.distage.model.provisioning.strategies.ContextStrategy +import izumi.distage.model.provisioning.strategies.SubcontextStrategy import izumi.distage.model.provisioning.{NewObjectOp, ProvisioningKeyProvider} +import izumi.distage.model.recursive.LocatorRef import izumi.functional.quasi.QuasiIO import izumi.reflect.TagK -class ContextStrategyDefaultImpl( - planner: Planner -) extends ContextStrategy { - override def prepareContext[F[_]: TagK]( +class SubcontextStrategyDefaultImpl extends SubcontextStrategy { + override def prepareSubcontext[F[_]: TagK]( context: ProvisioningKeyProvider, - op: WiringOp.LocalContext, + op: WiringOp.CreateSubcontext, )(implicit F: QuasiIO[F] ): F[Either[ProvisionerIssue, Seq[NewObjectOp]]] = { val locatorKey = AddRecursiveLocatorRef.magicLocatorKey context.fetchKey(locatorKey, makeByName = false) match { case Some(value) => val locatorRef = value.asInstanceOf[LocatorRef] - val impl = op.wiring.provider.asInstanceOf[Functoid[F[Any]]] - F.pure((for { - subplan <- planner.plan(PlannerInput(op.wiring.module, context.plan.input.activation, impl.get.diKeys.toSet)) - } yield { - val ctx = LocalContextImpl.empty[F, Any](op.wiring.externalKeys, locatorRef, subplan, impl, op.target) - Seq(NewObjectOp.UseInstance(op.target, ctx)) - }).left.map(err => ProvisionerIssue.LocalContextPlanningFailed(op.target, err))) + val provider = op.wiring.provider + val subplan = op.wiring.subplan + val ctx = SubcontextImpl.empty[Any](op.wiring.externalKeys, locatorRef, subplan, Functoid(provider), op.target) + F.pure(Right(Seq(NewObjectOp.UseInstance(op.target, ctx)))) case None => F.pure(Left(MissingInstance(locatorKey))) diff --git a/distage/distage-core/src/test/scala-2/izumi/distage/injector/CompactPlanFormatterTest.scala b/distage/distage-core/src/test/scala-2/izumi/distage/injector/CompactPlanFormatterTest.scala index a5a8071303..8ed6c35182 100644 --- a/distage/distage-core/src/test/scala-2/izumi/distage/injector/CompactPlanFormatterTest.scala +++ b/distage/distage-core/src/test/scala-2/izumi/distage/injector/CompactPlanFormatterTest.scala @@ -29,8 +29,8 @@ class CompactPlanFormatterTest extends AnyWordSpec with MkInjector { make[JustTrait].from[Impl1] make[OptionT[scala.Either[Nothing, _], Unit]].from(OptionT[Either[Nothing, _], Unit](Right(None))) make[K1[T1]].from(new K1[T1] {}) - make[W1.T2] - make[W2.T2] + makeTrait[W1.T2] + makeTrait[W2.T2] })) val formatted = plan.render().replaceAll("\u001B\\[[;\\d]*m", "") 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 78080e2a77..7b827dfb44 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 @@ -1,7 +1,7 @@ package izumi.distage.injector import distage.With -import izumi.distage.constructors.{AnyConstructor, FactoryConstructor, TraitConstructor} +import izumi.distage.constructors.{FactoryConstructor, TraitConstructor} import izumi.distage.fixtures.Scala3TraitCases.* import izumi.distage.model.reflection.TypedRef import org.scalatest.wordspec.AnyWordSpec diff --git a/distage/distage-core/src/test/scala/izumi/distage/StaticDSLTest.scala b/distage/distage-core/src/test/scala/izumi/distage/StaticDSLTest.scala index deeb7a553c..2efbc38bcc 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/StaticDSLTest.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/StaticDSLTest.scala @@ -20,7 +20,7 @@ class StaticDSLTest extends AnyWordSpec { .from[TestClass] make[TestDependency0] .named("named.test.dependency.0") - .from[TestDependency0] + .fromTrait[TestDependency0] make[TestInstanceBinding] .named("named.test") .from(TestInstanceBinding()) @@ -29,7 +29,7 @@ class StaticDSLTest extends AnyWordSpec { many[JustTrait] .add[Impl0] .add(new Impl1) - .add[JustTrait] + .addTrait[JustTrait] many[JustTrait] .named("named.set") .add(new Impl2()) diff --git a/distage/distage-core/src/test/scala/izumi/distage/compat/ModuleBaseInstancesTest.scala b/distage/distage-core/src/test/scala/izumi/distage/compat/ModuleBaseInstancesTest.scala index 5576f8c158..9dd73dbed0 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/compat/ModuleBaseInstancesTest.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/compat/ModuleBaseInstancesTest.scala @@ -1,9 +1,9 @@ package izumi.distage.compat -import cats.syntax.all._ -import izumi.distage.fixtures.BasicCases._ -import izumi.distage.model.definition.Bindings.binding -import izumi.distage.model.definition._ +import cats.syntax.all.* +import izumi.distage.fixtures.BasicCases.* +import izumi.distage.model.definition.Bindings.{binding, bindingTrait} +import izumi.distage.model.definition.* import org.scalatest.wordspec.AnyWordSpec final class ModuleBaseInstancesTest extends AnyWordSpec { @@ -20,12 +20,12 @@ final class ModuleBaseInstancesTest extends AnyWordSpec { } val mod3_1: Module = new ModuleDef { - make[TestDependency1] + makeTrait[TestDependency1] } val mod3_2 = Module.empty - val mod3 = (mod3_1 |+| mod3_2) :+ binding[NotInContext] + val mod3 = (mod3_1 |+| mod3_2) :+ bindingTrait[NotInContext] val mod4 = Module.make( Set( diff --git a/distage/distage-core/src/test/scala/izumi/distage/dsl/DSLTest.scala b/distage/distage-core/src/test/scala/izumi/distage/dsl/DSLTest.scala index f6a6fcbd04..2db5f355a1 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/dsl/DSLTest.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/dsl/DSLTest.scala @@ -8,6 +8,7 @@ import izumi.distage.injector.MkInjector import izumi.distage.model.definition.Binding.{SetElementBinding, SingletonBinding} import izumi.distage.model.definition.StandardAxis.{Mode, Repo} import izumi.distage.model.definition.{Binding, BindingTag, Bindings, ImplDef, Lifecycle, Module, ModuleBase} +import izumi.distage.model.planning.PlanIssue import izumi.fundamentals.platform.functional.Identity import izumi.fundamentals.platform.language.SourceFilePosition import org.scalatest.exceptions.TestFailedException @@ -30,7 +31,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { make[TestClass] .named("named.test.class") - make[TestDependency0] + makeTrait[TestDependency0] .named("named.test.dependency.0") make[TestInstanceBinding] .named("named.test") @@ -47,7 +48,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { .add[Impl3] make[TestDependency0].namedByImpl.from[TestImpl0] - make[TestDependency0].namedByImpl + makeTrait[TestDependency0].namedByImpl } assert(definition != null) @@ -161,12 +162,12 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { } val mod3_1 = new ModuleDef { - make[TestDependency1] + makeTrait[TestDependency1] } val mod3_2 = Module.empty - val mod3 = (mod3_1 ++ mod3_2) :+ Bindings.binding[NotInContext] + val mod3 = (mod3_1 ++ mod3_2) :+ Bindings.bindingTrait[NotInContext] val mod4: ModuleBase = Module.make { Set( @@ -201,7 +202,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { make[TestClass] } object mod2 extends ModuleDef { - make[TestDependency0] + makeTrait[TestDependency0] } mod1 ++ mod2 @@ -214,7 +215,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { make[TestClass] } class mod2 extends ModuleDef { - make[TestDependency0] + makeTrait[TestDependency0] } val _: Module = new mod1 ++ new mod2 @@ -226,11 +227,13 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { val definition: ModuleBase = new ModuleDef { tag("tag1") make[TestClass] - make[TestDependency0].tagged("sniv") + makeTrait[TestDependency0].tagged("sniv") tag("tag2") } - assert(definition.bindings == Set(Bindings.binding[TestClass].addTags(Set("tag1", "tag2")), Bindings.binding[TestDependency0].addTags(Set("tag1", "tag2", "sniv")))) + assert( + definition.bindings == Set(Bindings.binding[TestClass].addTags(Set("tag1", "tag2")), Bindings.bindingTrait[TestDependency0].addTags(Set("tag1", "tag2", "sniv"))) + ) } "ModuleBuilder supports tags; same bindings with different tags are NOT merged (tag merging removed in 0.11.0)" in { @@ -299,8 +302,8 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { import BasicCase1._ val def1 = new ModuleDef { - make[TestDependency0].tagged("a") - make[TestDependency0].tagged("b") + makeTrait[TestDependency0].tagged("a") + makeTrait[TestDependency0].tagged("b") tag("1") } @@ -308,7 +311,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { val def2 = new ModuleDef { tag("2") - make[TestDependency0].tagged("x").tagged("y") + makeTrait[TestDependency0].tagged("x").tagged("y") } val definition = def1 ++ def2 @@ -324,7 +327,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { val tags12: Seq[BindingTag] = Seq("1", "2") val def1 = new ModuleDef { - make[TestDependency0].tagged("a").tagged("b") + makeTrait[TestDependency0].tagged("a").tagged("b") tag(tags12: _*) } @@ -332,7 +335,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { val def2 = new ModuleDef { tag("2", "3") - make[TestDependency0].tagged("x").tagged("y") + makeTrait[TestDependency0].tagged("x").tagged("y") } val definition = def1 overriddenBy def2 @@ -344,14 +347,14 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { "support zero element" in { import BasicCase1._ val def1 = new ModuleDef { - make[TestDependency0] + makeTrait[TestDependency0] } val def2 = new ModuleDef { - make[TestDependency0] + makeTrait[TestDependency0] } val def3 = new ModuleDef { - make[TestDependency1] + makeTrait[TestDependency1] } assert((def1 overriddenBy Module.empty) == def1) @@ -363,7 +366,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { import BasicCase1._ trait Def1 extends ModuleDef { - make[TestDependency0] + makeTrait[TestDependency0] tag("tag2") } @@ -626,7 +629,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { """ ) ) - assert(res1.getMessage contains "[T: AnyConstructor]") + assert(res1.getMessage contains "[T: ClassConstructor]") val res2 = intercept[TestFailedException]( assertCompiles( """ @@ -637,7 +640,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { ) ) - res2.getMessage should include regex "AnyConstructor failure: izumi\\.distage\\.model\\.definition\\.Lifecycle\\.Basic\\[F,.*(scala\\.)?Int\\] is a Factory, use makeFactory or fromFactory to wire factories" + res2.getMessage should include regex "ClassConstructor failure: izumi\\.distage\\.model\\.definition\\.Lifecycle\\.Basic\\[F,.*(scala\\.)?Int\\] is a Factory, use `makeFactory` or `make\\[X\\].fromFactory` to wire factories" } "define multiple bindings with different axis but the same implementation" in { @@ -665,7 +668,7 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { assert(bindings.size == 3) } - "Progression test: set bindings with the same source position and implementation shouldn't conflict" in { + "progression test: set bindings with the same source position and implementation shouldn't conflict" in { val definition: ModuleDef = new ModuleDef { val fn = { var i = 0 @@ -691,6 +694,22 @@ class DSLTest extends AnyWordSpec with MkInjector with should.Matchers { } } + "addDependency supports adding dependencies for .fromValue and .using bindings" in { + val definition = new ModuleDef { + make[Int] + .fromValue(5) + .addDependency[String] + make[Unit].fromValue(()) + make[Unit].named("x").using[Unit].addDependency[String] + } + + val verification = PlanVerifier().verify[Identity](definition, Roots.Everything, Set.empty, Set.empty) + assert(verification.verificationFailed) + assert(verification.issues.get.forall(_.isInstanceOf[PlanIssue.MissingImport])) + val imports = verification.issues.get.toSet.collect { case i: PlanIssue.MissingImport => (i.dependee, i.key) } + assert(imports == Set(DIKey[Int] -> DIKey[String], DIKey[Unit]("x") -> DIKey[String])) + } + } } diff --git a/distage/distage-core/src/test/scala/izumi/distage/fixtures/ResourceCases.scala b/distage/distage-core/src/test/scala/izumi/distage/fixtures/ResourceCases.scala index e2d4580a89..fabd65fe40 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/fixtures/ResourceCases.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/fixtures/ResourceCases.scala @@ -69,10 +69,10 @@ object ResourceCases { object CircularResourceCase { sealed trait Ops { def invert: Ops } - case object ComponentStart extends Ops { val invert = ComponentStop } - case object ClientStart extends Ops { val invert = ClientStop } - case object ComponentStop extends Ops { val invert = ComponentStart } - case object ClientStop extends Ops { val invert = ClientStart } + case object ComponentStart extends Ops { val invert: Ops = ComponentStop } + case object ClientStart extends Ops { val invert: Ops = ClientStop } + case object ComponentStop extends Ops { val invert: Ops = ComponentStart } + case object ClientStop extends Ops { val invert: Ops = ClientStart } trait S3Client { def c: S3Component diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedTypesTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedTypesTest.scala index 31f73f89aa..742de53769 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedTypesTest.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/injector/AdvancedTypesTest.scala @@ -1,7 +1,6 @@ package izumi.distage.injector import distage.* -import izumi.distage.constructors.AnyConstructor import izumi.distage.fixtures.TraitCases.* import izumi.distage.fixtures.TypesCases.* import izumi.distage.model.PlannerInput @@ -54,7 +53,7 @@ class AdvancedTypesTest extends AnyWordSpec with MkInjector with ScalatestGuards val definition = PlannerInput.everything(new ModuleDef { make[DepA] - make[TestTrait] + makeTrait[TestTrait] }) val injector = mkInjector() @@ -69,7 +68,7 @@ class AdvancedTypesTest extends AnyWordSpec with MkInjector with ScalatestGuards val definition = PlannerInput.everything(new ModuleDef { make[Dependency1 @Id("special")] - make[Trait1] + makeTrait[Trait1] }) val injector = mkInjector() @@ -88,7 +87,7 @@ class AdvancedTypesTest extends AnyWordSpec with MkInjector with ScalatestGuards val definition = PlannerInput.everything(new ModuleDef { make[Dep] make[Dep2] - make[Trait2 with Trait1].from[Trait6] + make[Trait2 with Trait1].fromTrait[Trait6] }) val injector = mkInjector() @@ -106,9 +105,9 @@ class AdvancedTypesTest extends AnyWordSpec with MkInjector with ScalatestGuards val definition = PlannerInput.everything(new ModuleDef { make[Dep] make[Dep2] - make[Trait1 { def dep: Dep2 }].from[Trait3[Dep2]] - make[Trait1 { def dep: Dep }].from[Trait3[Dep]] - make[{ def dep: Dep }].from[Trait6] + make[Trait1 { def dep: Dep2 }].fromTrait[Trait3[Dep2]] + make[Trait1 { def dep: Dep }].fromTrait[Trait3[Dep]] + make[{ def dep: Dep }].fromTrait[Trait6] }) val injector = mkInjector() @@ -132,7 +131,7 @@ class AdvancedTypesTest extends AnyWordSpec with MkInjector with ScalatestGuards make[Dep2] locally { type X[A] = Trait1[Dep, A] - make[X[T]] + makeTrait[X[T]] } } @@ -149,10 +148,10 @@ class AdvancedTypesTest extends AnyWordSpec with MkInjector with ScalatestGuards "light type tags can handle abstract structural refinement types" in { import TypesCase3._ - class Definition[T >: Null: Tag, G <: T { def dep: Dep }: Tag: AnyConstructor] extends ModuleDef { + class Definition[T >: Null: Tag, G <: T { def dep: Dep }: Tag: TraitConstructor] extends ModuleDef { make[Dep] make[T { def dep2: Dep }].from(() => null.asInstanceOf[T { def dep2: Dep }]) - make[T { def dep: Dep }].from[G] + make[T { def dep: Dep }].from(TraitConstructor[G]) } val definition = PlannerInput.everything(new Definition[Trait1, Trait1]) @@ -170,10 +169,10 @@ class AdvancedTypesTest extends AnyWordSpec with MkInjector with ScalatestGuards "handle abstract `with` types" in { import TypesCase3._ - class Definition[T: Tag, G <: T with Trait1: Tag: AnyConstructor, C <: T with Trait4: Tag: AnyConstructor] extends ModuleDef { + class Definition[T: Tag, G <: T with Trait1: Tag: TraitConstructor, C <: T with Trait4: Tag: TraitConstructor] extends ModuleDef { make[Dep] - make[T with Trait4].from[C] - make[T with Trait1].from[G] + make[T with Trait4].from(TraitConstructor[C]) + make[T with Trait1].from(TraitConstructor[G]) } val definition = PlannerInput.everything(new Definition[Trait3[Dep], Trait3[Dep], Trait5[Dep]]) @@ -199,9 +198,9 @@ class AdvancedTypesTest extends AnyWordSpec with MkInjector with ScalatestGuards "handle generic parameters in abstract `with` types" in { import TypesCase3._ - class Definition[T <: Dep: Tag: AnyConstructor, K >: Trait5[T]: Tag] extends ModuleDef { + class Definition[T <: Dep: Tag: ClassConstructor, K >: Trait5[T]: Tag] extends ModuleDef { make[T] - make[Trait3[T] with K].from[Trait5[T]] + make[Trait3[T] with K].fromTrait[Trait5[T]] } val definition = PlannerInput.everything(new Definition[Dep, Trait4]) diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/ArityTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/ArityTest.scala index f6daf11dda..d2052d51f2 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/injector/ArityTest.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/injector/ArityTest.scala @@ -25,7 +25,7 @@ class ArityTest extends AnyWordSpec with MkInjector { val definition = PlannerInput.everything(new ModuleDef { make[Beep[Int]] - make[BopTrait[Int]] + makeTrait[BopTrait[Int]] }) val context = Injector.Standard().produce(definition).unsafeGet() @@ -39,7 +39,7 @@ class ArityTest extends AnyWordSpec with MkInjector { val definition = PlannerInput.everything(new ModuleDef { make[Beep[Int]] - make[BopAbstractClass[Int]] + makeTrait[BopAbstractClass[Int]] }) val context = Injector.Standard().produce(definition).unsafeGet() @@ -72,8 +72,8 @@ class ArityTest extends AnyWordSpec with MkInjector { val definition = PlannerInput.everything(new ModuleDef { make[NoArgClass] - make[NoArgTrait] - make[NoArgAbstractClass] + makeTrait[NoArgTrait] + makeTrait[NoArgAbstractClass] }) val context = Injector.Standard().produce(definition).unsafeGet() diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/AutoTraitsTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/AutoTraitsTest.scala index 2011464251..9d4e1f9c49 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/injector/AutoTraitsTest.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/injector/AutoTraitsTest.scala @@ -1,7 +1,7 @@ package izumi.distage.injector -import izumi.distage.constructors.AnyConstructor -import izumi.distage.fixtures.TraitCases._ +import izumi.distage.constructors.TraitConstructor +import izumi.distage.fixtures.TraitCases.* import izumi.distage.fixtures.TypesCases.TypesCase3 import izumi.distage.fixtures.TypesCases.TypesCase6 import izumi.distage.model.PlannerInput @@ -19,7 +19,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { } "construct a basic trait" in { - val traitCtor = AnyConstructor[Aaa].get + val traitCtor = TraitConstructor[Aaa].get val value = traitCtor.unsafeApply(Seq(TypedRef.byName(5), TypedRef.byName(false))).asInstanceOf[Aaa] @@ -32,7 +32,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { val definition = new ModuleDef { make[Dependency1] - make[TestTrait] + makeTrait[TestTrait] } val injector = mkNoCyclesInjector() @@ -49,7 +49,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { val definition = new ModuleDef { make[Dependency1] - make[TestTrait].named("named-trait").from[TestTrait] + make[TestTrait].named("named-trait").fromTrait[TestTrait] } val injector = mkNoCyclesInjector() @@ -65,9 +65,9 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { import TraitCase2._ val definition = new ModuleDef { - make[Trait3] - make[Trait2] - make[Trait1] + makeTrait[Trait3] + makeTrait[Trait2] + makeTrait[Trait1] make[Dependency3] make[Dependency2] make[Dependency1] @@ -93,7 +93,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { import TraitCase2._ val definition = new ModuleDef { - make[Trait2].from[Trait3] + make[Trait2].fromTrait[Trait3] make[Dependency3] make[Dependency2] make[Dependency1] @@ -112,7 +112,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { import TraitCase3._ val definition = PlannerInput.everything(new ModuleDef { - make[ATraitWithAField] + makeTrait[ATraitWithAField] }) val injector = mkInjector() @@ -128,8 +128,8 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { val definition = PlannerInput.everything(new ModuleDef { make[Dep].named("A").from[DepA] make[Dep].named("B").from[DepB] - make[Trait] - make[Trait1] + makeTrait[Trait] + makeTrait[Trait1] }) val injector = mkInjector() @@ -150,7 +150,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { import TraitCase5._ val definition = PlannerInput.everything(new ModuleDef { - make[TestTrait] + makeTrait[TestTrait] make[Dep] }) @@ -167,7 +167,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { import TraitCase5._ val definition = PlannerInput.everything(new ModuleDef { - make[TestTraitAny { def dep: Dep }] + makeTrait[TestTraitAny { def dep: Dep }] make[Dep] }) @@ -181,7 +181,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { "can instantiate structural types" in { val definition = PlannerInput.everything(new ModuleDef { - make[{ def a: Int }] + makeTrait[{ def a: Int }] make[Int].from(5) }) @@ -198,7 +198,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { val definition = PlannerInput.everything(new ModuleDef { make[Dep] make[Dep2] - make[Trait2 with (Trait2 with (Trait2 with Trait1))] + makeTrait[Trait2 with (Trait2 with (Trait2 with Trait1))] }) val injector = mkNoCyclesInjector() @@ -217,7 +217,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { val definition = PlannerInput.everything(new ModuleDef { make[Dep] make[Dep2] - make[Trait1 with Trait2] + makeTrait[Trait1 with Trait2] }) val injector = mkNoCyclesInjector() @@ -236,7 +236,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { val definition = PlannerInput.everything(new ModuleDef { make[Dep] make[AnyValDep] - make[TestTrait] + makeTrait[TestTrait] }) val injector = mkInjector() @@ -255,7 +255,7 @@ class AutoTraitsTest extends AnyWordSpec with MkInjector { val definition = PlannerInput.everything(new ModuleDef { make[Dependency1] make[Dependency2] - make[X].from[XImpl] + make[X].fromTrait[XImpl] }) val injector = mkInjector() diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/BasicTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/BasicTest.scala index 6204c626e2..e3f1fe450a 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/injector/BasicTest.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/injector/BasicTest.scala @@ -12,6 +12,7 @@ import izumi.distage.model.definition.errors.{ConflictResolutionError, DIError} import izumi.distage.model.exceptions.planning.InjectorFailed import izumi.distage.model.exceptions.runtime.ProvisioningException import izumi.distage.model.plan.ExecutableOp.ImportDependency +import izumi.fundamentals.collections.nonempty.NEList import izumi.fundamentals.platform.assertions.ScalatestGuards import izumi.fundamentals.platform.functional.Identity import org.scalatest.exceptions.TestFailedException @@ -23,9 +24,9 @@ class BasicTest extends AnyWordSpec with MkInjector with ScalatestGuards { val definition = PlannerInput( new ModuleDef { make[TestClass] - make[TestDependency3] + makeTrait[TestDependency3] make[TestDependency0].from[TestImpl0] - make[TestDependency1] + makeTrait[TestDependency1] make[TestCaseClass] make[LocatorDependent] make[TestInstanceBinding].from(TestInstanceBinding()) @@ -99,7 +100,7 @@ class BasicTest extends AnyWordSpec with MkInjector with ScalatestGuards { import BadAnnotationsCase._ val definition = PlannerInput.everything(new ModuleDef { - make[TestDependency0] + makeTrait[TestDependency0] make[TestClass] }) @@ -132,7 +133,7 @@ class BasicTest extends AnyWordSpec with MkInjector with ScalatestGuards { many[JustTrait].named("named.empty.set") many[JustTrait] - .add[JustTrait] + .addTrait[JustTrait] .add(new Impl1) many[JustTrait] @@ -312,7 +313,7 @@ class BasicTest extends AnyWordSpec with MkInjector with ScalatestGuards { import BasicCase4.* val definition = PlannerInput.everything(new ModuleDef { - make[Dependency].named("special") + makeTrait[Dependency].named("special") make[TestClass] }) @@ -449,13 +450,16 @@ class BasicTest extends AnyWordSpec with MkInjector with ScalatestGuards { .named("x") { (m: Mutable) => m.copy(a = m.a + 10) - }.tagged(Repo.Prod) + } + .tagged(Repo.Prod) modify[Mutable] .named("x") { (m: Mutable) => - m.copy(a = m.a + 20) - }.tagged(Repo.Dummy) + m.copy(a = m.a + 10) + } + .tagged(Repo.Dummy) + .modify((m: Mutable) => m.copy(a = m.a + 10)) }, Activation(Repo -> Repo.Prod), ) @@ -465,7 +469,7 @@ class BasicTest extends AnyWordSpec with MkInjector with ScalatestGuards { assert(context.get[Mutable]("x") == Mutable(11, Some(SomethingUseful("x")))) } - "support mutations with axis tags when axis is unconfigured" in { + "fail mutations with axis tags when axis is unconfigured" in { import Mutations01.* val definition = PlannerInput.everything( @@ -500,9 +504,13 @@ class BasicTest extends AnyWordSpec with MkInjector with ScalatestGuards { Activation.empty, ) - intercept[InjectorFailed] { + val failure = intercept[InjectorFailed] { mkInjector().produce(definition).unsafeGet() } + assert(failure.errors.exists { + case DIError.ConflictResolutionFailed(ConflictResolutionError.UnconfiguredAxisInMutators(NEList(a @ _, b @ _))) => true + case _ => false + }) } "regression test: imports correctly specify which binding they are required by when missing" in { @@ -519,7 +527,7 @@ class BasicTest extends AnyWordSpec with MkInjector with ScalatestGuards { class RegisteredComponentImpl1 extends RegisteredComponent class RegisteredComponentImpl2 extends RegisteredComponent - def addAndRegister[T <: RegisteredComponent: Tag: AnyConstructor](implicit mutateModule: ModuleDefDSL#MutationContext): Unit = { + def addAndRegister[T <: RegisteredComponent: Tag: ClassConstructor](implicit mutateModule: ModuleDefDSL#MutationContext): Unit = { new mutateModule.dsl { make[T] .named("xyz") diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/CircularDependenciesTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/CircularDependenciesTest.scala index baf6115cfd..b28d3f4308 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/injector/CircularDependenciesTest.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/injector/CircularDependenciesTest.scala @@ -16,8 +16,8 @@ class CircularDependenciesTest extends AnyWordSpec with MkInjector with Scalates import CircularCase2._ val definition = PlannerInput.everything(new ModuleDef { - make[CircularBad1] - make[CircularBad2] + makeTrait[CircularBad1] + makeTrait[CircularBad2] }) val injector = mkInjector() @@ -35,10 +35,10 @@ class CircularDependenciesTest extends AnyWordSpec with MkInjector with Scalates import CircularCase2._ val definition = PlannerInput.everything(new ModuleDef { - make[Circular3] - make[Circular1] - make[Circular2] - make[Circular5] + makeTrait[Circular3] + makeTrait[Circular1] + makeTrait[Circular2] + makeTrait[Circular5] makeFactory[Circular4] }) @@ -82,7 +82,7 @@ class CircularDependenciesTest extends AnyWordSpec with MkInjector with Scalates import CircularCase3._ val definition = PlannerInput.everything(new ModuleDef { - make[TraitSelfReference] + makeTrait[TraitSelfReference] }) val injector = mkNoProxiesInjector() diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/FactoriesTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/FactoriesTest.scala index 0b6f7972df..e6874d152b 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/injector/FactoriesTest.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/injector/FactoriesTest.scala @@ -17,7 +17,7 @@ class FactoriesTest extends AnyWordSpec with MkInjector with ScalatestGuards { val definition = PlannerInput.everything(new ModuleDef { makeFactory[Factory] - make[Dependency] + makeTrait[Dependency] makeFactory[OverridingFactory] makeFactory[AssistedFactory] makeFactory[AbstractFactory] @@ -74,7 +74,7 @@ class FactoriesTest extends AnyWordSpec with MkInjector with ScalatestGuards { val definition = PlannerInput.everything(new ModuleDef { makeFactory[NamedAssistedFactory] - make[Dependency] + makeTrait[Dependency] make[Dependency].named("special").from(SpecialDep()) make[Dependency].named("veryspecial").from(VerySpecialDep()) }) @@ -100,7 +100,7 @@ class FactoriesTest extends AnyWordSpec with MkInjector with ScalatestGuards { val definition = PlannerInput.everything(new ModuleDef { makeFactory[MixedAssistendNonAssisted] - make[Dependency] + makeTrait[Dependency] }) val injector = mkInjector() @@ -239,7 +239,7 @@ class FactoriesTest extends AnyWordSpec with MkInjector with ScalatestGuards { import FactoryCase1.* val definition = PlannerInput.everything(new ModuleDef { - make[Dependency] + makeTrait[Dependency] make[TestClass] makeFactory[Factory] }) @@ -290,7 +290,7 @@ class FactoriesTest extends AnyWordSpec with MkInjector with ScalatestGuards { import FactoryCase1.* val definition = PlannerInput.everything(new ModuleDef { - make[Dependency] + makeTrait[Dependency] make[TestClass] makeFactory[AbstractClassFactory] }) diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/HigherKindsTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/HigherKindsTest.scala index 2e979fadea..a39a2eff6b 100644 --- a/distage/distage-core/src/test/scala/izumi/distage/injector/HigherKindsTest.scala +++ b/distage/distage-core/src/test/scala/izumi/distage/injector/HigherKindsTest.scala @@ -16,7 +16,7 @@ class HigherKindsTest extends AnyWordSpec with MkInjector { make[TestTrait].from[TestServiceClass[F]] make[TestServiceClass[F]] - make[TestServiceTrait[F]] + makeTrait[TestServiceTrait[F]] make[Int].named("TestService").from(getResult) make[F[String]].from { (res: Int @Id("TestService")) => Pointed[F].point(s"Hello $res!") diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/LocalContextTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/LocalContextTest.scala deleted file mode 100644 index d6c7c15d85..0000000000 --- a/distage/distage-core/src/test/scala/izumi/distage/injector/LocalContextTest.scala +++ /dev/null @@ -1,169 +0,0 @@ -package izumi.distage.injector - -import distage.{Activation, DIKey, Injector, Module, ModuleDef, PlanVerifier} -import izumi.distage.LocalContext -import izumi.distage.injector.LocalContextTest.* -import izumi.distage.model.PlannerInput -import izumi.distage.model.plan.Roots -import izumi.fundamentals.platform.functional.Identity -import izumi.fundamentals.platform.language.Quirks -import org.scalatest.wordspec.AnyWordSpec - -class LocalContextTest extends AnyWordSpec with MkInjector { - - "support local contexts" in { - val module = new ModuleDef { - make[GlobalServiceDependency] - make[GlobalService] - - // this will not be used/instantiated - make[LocalService].from[LocalServiceBadImpl] - - make[LocalContext[Identity, Int]] - .named("test") - .fromLocalContext(new ModuleDef { - make[LocalService].from[LocalServiceGoodImpl] - }.running { - (summator: LocalService) => - summator.localSum - }) - .external(DIKey.get[Arg]) - } - - val definition = PlannerInput(module, Activation.empty, DIKey.get[LocalContext[Identity, Int]].named("test")) - - val injector = mkNoCyclesInjector() - val plan = injector.planUnsafe(definition) - val context = injector.produce(plan).unsafeGet() - - val local = context.get[LocalContext[Identity, Int]]("test") - assert(context.find[GlobalServiceDependency].nonEmpty) - assert(context.find[GlobalService].nonEmpty) - assert(context.find[LocalService].isEmpty) - val out = local.provide[Arg](Arg(1)).produceRun() - assert(out == 230) - - val result = PlanVerifier().verify[Identity](module, Roots.Everything, Injector.providedKeys(), Set.empty) - assert(result.issues.isEmpty) - } - - "support incomplete dsl chains (good case, no externals)" in { - val module = new ModuleDef { - make[GlobalServiceDependency] - make[GlobalService] - - make[LocalContext[Identity, Int]] - .named("test") - .fromLocalContext( - new ModuleDef { - make[Arg].fromValue(Arg(2)) - make[LocalService].from[LocalServiceGoodImpl] - }.running { - (summator: LocalService) => - summator.localSum - } - ) - } - - val definition = PlannerInput(module, Activation.empty, DIKey.get[LocalContext[Identity, Int]].named("test")) - - val injector = mkNoCyclesInjector() - val plan = injector.planUnsafe(definition) - val context = injector.produce(plan).unsafeGet() - - val local = context.get[LocalContext[Identity, Int]]("test") - - assert(local.produceRun() == 231) - } - - "support self references" in { - val module = new ModuleDef { - make[LocalContext[Identity, Int]] - .fromLocalContext( - new ModuleDef { - make[LocalRecursiveService].from[LocalRecursiveServiceGoodImpl] - }.running { - (summator: LocalRecursiveService) => - summator.localSum - } - ).external[Arg] - } - - val definition = PlannerInput(module, Activation.empty, DIKey.get[LocalContext[Identity, Int]]) - - val injector = mkNoCyclesInjector() - val plan = injector.planUnsafe(definition) - val context = injector.produce(plan).unsafeGet() - - val local = context.get[LocalContext[Identity, Int]] - - assert(local.provide(Arg(10)).produceRun() == 20) - } - - "support various local context syntax modes" in { - val module1 = new ModuleDef { - make[LocalContext[Identity, Int]] - .named("test") - .fromLocalContext(Module.empty.running { - (summator: LocalService) => - summator.localSum - }) - .external(DIKey.get[Int]) - } - - val module2 = new ModuleDef { - make[LocalContext[Identity, Int]] - .named("test") - .fromLocalContext(Module.empty.running { - (summator: LocalService) => - summator.localSum - }) - } - - val module3 = new ModuleDef { - make[LocalContext[cats.effect.IO, Int]] - .named("test") - .fromLocalContext(Module.empty.running { - (summator: LocalService) => - cats.effect.IO(summator.localSum) - }) - } - - Quirks.discard((module1, module2, module3)) - } -} - -object LocalContextTest { - class GlobalServiceDependency { - def uselessConst: Int = 88 - } - class GlobalService(uselessDependency: GlobalServiceDependency) { - def sum(i: Int): Int = i + 42 + uselessDependency.uselessConst - } - - trait LocalService { - def localSum: Int - } - class LocalServiceGoodImpl(main: GlobalService, value: Arg) extends LocalService { - def localSum: Int = main.sum(value.value) + 99 - } - - class LocalServiceBadImpl() extends LocalService { - def localSum: Int = throw new RuntimeException("boom") - } - - case class Arg(value: Int) - - trait LocalRecursiveService { - def localSum: Int - } - - class LocalRecursiveServiceGoodImpl(value: Arg, self: LocalContext[Identity, Int]) extends LocalRecursiveService { - def localSum: Int = if (value.value > 0) { - 2 + self.provide(Arg(value.value - 1)).produceRun() - } else { - 0 - } - } - -} 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 f1f7aab481..ca3866ae60 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 @@ -363,11 +363,11 @@ class ResourceEffectBindingsTest extends AnyWordSpec with MkInjector with GivenW import BasicCase1._ val definition = PlannerInput.everything(new ModuleDef { - make[NotInContext] + makeTrait[NotInContext] make[TestClass] - make[TestDependency3] + makeTrait[TestDependency3] make[TestDependency0].from[TestImpl0] - make[TestDependency1] + makeTrait[TestDependency1] make[TestCaseClass] make[LocatorDependent] make[TestInstanceBinding].fromResource(new Lifecycle.Basic[Option, TestInstanceBinding] { diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/SubcontextTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/SubcontextTest.scala new file mode 100644 index 0000000000..9927718416 --- /dev/null +++ b/distage/distage-core/src/test/scala/izumi/distage/injector/SubcontextTest.scala @@ -0,0 +1,204 @@ +package izumi.distage.injector + +import distage.{Activation, DIKey, Injector, ModuleDef, PlanVerifier, Repo} +import izumi.distage.Subcontext +import izumi.distage.injector.SubcontextTest.* +import izumi.distage.model.PlannerInput +import izumi.distage.model.plan.Roots +import izumi.fundamentals.platform.functional.Identity +import org.scalatest.wordspec.AnyWordSpec + +class SubcontextTest extends AnyWordSpec with MkInjector { + + "support local contexts" in { + val module = new ModuleDef { + make[GlobalServiceDependency] + make[GlobalService] + + // this will not be used/instantiated + make[LocalService].from[LocalServiceBadImpl] + + makeSubcontext[Int] + .named("test") + .withSubmodule { + new ModuleDef { + make[LocalService] + .from[LocalServiceGoodImpl] + .annotateParameter[Arg]("x") + } + } + .extractWith { + (summator: LocalService) => + summator.localSum + } + .localDependency[Arg]("x") + } + + val definition = PlannerInput(module, Activation.empty, DIKey.get[Subcontext[Int]].named("test")) + + val injector = mkNoCyclesInjector() + val plan = injector.planUnsafe(definition) + val context = injector.produce(plan).unsafeGet() + + val local = context.get[Subcontext[Int]]("test") + assert(context.find[GlobalServiceDependency].nonEmpty) + assert(context.find[GlobalService].nonEmpty) + assert(context.find[LocalService].isEmpty) + val out = local.provide[Arg]("x")(Arg(1)).produceRun(x => x /* `identity` doesn't work on 2.12 */ ) + assert(out == 230) + + val result = PlanVerifier().verify[Identity](module, Roots.Everything, Injector.providedKeys(), Set.empty) + assert(result.issues.isEmpty) + } + + "support incomplete dsl chains (good case, no externals)" in { + val module = new ModuleDef { + make[GlobalServiceDependency] + make[GlobalService] + + makeSubcontext[Int] + .named("test") + .withSubmodule(new ModuleDef { + make[Arg].fromValue(Arg(2)) + make[LocalService].from[LocalServiceGoodImpl] + }) + .extractWith { + (summator: LocalService) => + summator.localSum + } + } + + val definition = PlannerInput(module, Activation.empty, DIKey.get[Subcontext[Int]].named("test")) + + val injector = mkNoCyclesInjector() + val plan = injector.planUnsafe(definition) + val context = injector.produce(plan).unsafeGet() + + val local = context.get[Subcontext[Int]]("test") + + assert(local.produceRun(x => x /* `identity` doesn't work on 2.12 */ ) == 231) + } + + "support self references" in { + val module = new ModuleDef { + makeSubcontext[Int](new ModuleDef { + make[LocalRecursiveService].from[LocalRecursiveServiceGoodImpl] + }) + .extractWith { + (summator: LocalRecursiveService) => + summator.localSum + } + .localDependency[Arg] + } + + val definition = PlannerInput(module, Activation.empty, DIKey.get[Subcontext[Int]]) + + val injector = mkNoCyclesInjector() + val plan = injector.planUnsafe(definition) + val context = injector.produce(plan).unsafeGet() + + val local = context.get[Subcontext[Int]] + + assert(local.provide(Arg(10)).produceRun(x => x /* `identity` doesn't work on 2.12 */ ) == 20) + } + + "support activations on subcontexts" in { + val module = new ModuleDef { + make[GlobalServiceDependency] + make[GlobalService] + make[LocalService].from[LocalServiceGoodImpl] + make[Arg].fromValue(Arg(1)) + + makeSubcontext[Int] + .named("test") + .tagged(Repo.Dummy) + .extractWith { + (summator: LocalService) => + summator.localSum + } + .localDependency[Int] + + makeSubcontext[Int] + .named("test") + .tagged(Repo.Prod) + .extractWith { + (summator: LocalService) => + summator.localSum - 2 + } + .localDependency[Int] + } + + val injector = mkNoCyclesInjector() + val dummySubcontext = injector.produceGet[Subcontext[Int]]("test")(module, Activation(Repo.Dummy)).unsafeGet() + val prodSubcontext = injector.produceGet[Subcontext[Int]]("test")(module, Activation(Repo.Prod)).unsafeGet() + + val dummyRes = dummySubcontext.produceRun(x => x /* `identity` doesn't work on 2.12 */ ) + val prodRes = prodSubcontext.produceRun(x => x: Identity[Int]) + + assert(dummyRes == 230) + assert(prodRes == 228) + } + + "support activations in subcontexts" in { + val module = new ModuleDef { + make[GlobalServiceDependency] + make[GlobalService] + + makeSubcontext[Int](new ModuleDef { + make[LocalService].from[LocalServiceGoodImpl] + + make[Arg].tagged(Repo.Dummy).fromValue(Arg(1)) + make[Arg].tagged(Repo.Prod).fromValue(Arg(-1)) + }) + .extractWith { + (summator: LocalService) => + summator.localSum + } + } + + val injector = mkNoCyclesInjector() + val subcontext = injector.produceGet[Subcontext[Int]](module, Activation(Repo.Dummy)).unsafeGet() + val prodSubcontext = injector.produceGet[Subcontext[Int]](module, Activation(Repo.Prod)).unsafeGet() + + val dummyRes = subcontext.produceRun(x => x /* `identity` doesn't work on 2.12 */ ) + val prodRes = prodSubcontext.produceRun(x => x /* `identity` doesn't work on 2.12 */ ) + + assert(dummyRes == 230) + assert(prodRes == 228) + } +} + +object SubcontextTest { + class GlobalServiceDependency { + def uselessConst: Int = 88 + } + class GlobalService(uselessDependency: GlobalServiceDependency) { + def sum(i: Int): Int = i + 42 + uselessDependency.uselessConst + } + + trait LocalService { + def localSum: Int + } + class LocalServiceGoodImpl(main: GlobalService, value: Arg) extends LocalService { + def localSum: Int = main.sum(value.value) + 99 + } + + class LocalServiceBadImpl() extends LocalService { + def localSum: Int = throw new RuntimeException("boom") + } + + case class Arg(value: Int) + + trait LocalRecursiveService { + def localSum: Int + } + + class LocalRecursiveServiceGoodImpl(value: Arg, self: Subcontext[Int]) extends LocalRecursiveService { + def localSum: Int = if (value.value > 0) { + 2 + self.provide(Arg(value.value - 1)).produceRun(x => x /* `identity` doesn't work on 2.12 */ ) + } else { + 0 + } + } + +} diff --git a/distage/distage-extension-config/.js/src/main/scala/izumi/distage/config/model/AppConfig.scala b/distage/distage-extension-config/.js/src/main/scala/izumi/distage/config/model/AppConfig.scala new file mode 100644 index 0000000000..c0a4cc28b2 --- /dev/null +++ b/distage/distage-extension-config/.js/src/main/scala/izumi/distage/config/model/AppConfig.scala @@ -0,0 +1,12 @@ +package izumi.distage.config.model + +import izumi.distage.config.DistageConfigImpl + +final case class AppConfig( + config: DistageConfigImpl +) + +object AppConfig { + val empty: AppConfig = AppConfig(Map.empty[String, String]) + def provided(config: DistageConfigImpl): AppConfig = AppConfig(config) +} diff --git a/distage/distage-extension-config/.js/src/main/scala/izumi/distage/config/model/AppConfigSyntax.scala b/distage/distage-extension-config/.js/src/main/scala/izumi/distage/config/model/AppConfigSyntax.scala deleted file mode 100644 index ab1d91b0a8..0000000000 --- a/distage/distage-extension-config/.js/src/main/scala/izumi/distage/config/model/AppConfigSyntax.scala +++ /dev/null @@ -1,5 +0,0 @@ -package izumi.distage.config.model - -trait AppConfigSyntax { - val empty: AppConfig = AppConfig(Map.empty[String, String]) -} diff --git a/distage/distage-extension-config/.jvm/src/main/scala/izumi/distage/config/model/AppConfig.scala b/distage/distage-extension-config/.jvm/src/main/scala/izumi/distage/config/model/AppConfig.scala new file mode 100644 index 0000000000..50d291efcd --- /dev/null +++ b/distage/distage-extension-config/.jvm/src/main/scala/izumi/distage/config/model/AppConfig.scala @@ -0,0 +1,68 @@ +package izumi.distage.config.model + +import com.typesafe.config.{Config, ConfigFactory} +import izumi.distage.config.DistageConfigImpl + +import java.io.File + +final case class AppConfig( + config: DistageConfigImpl, + shared: List[ConfigLoadResult.Success], + roles: List[LoadedRoleConfigs], +) { + // FIXME: exclude `shared` & `roles` fields from equals/hashCode for now, + // to prevent them breaking test environment merging for memoization + // (fields added in https://github.com/7mind/izumi/pull/2040) + override def equals(obj: Any): Boolean = obj match { + case that: AppConfig => this.config == that.config + case _ => false + } + override def hashCode(): Int = config.hashCode() +} + +object AppConfig { + val empty: AppConfig = AppConfig(ConfigFactory.empty(), List.empty, List.empty) + def provided(config: DistageConfigImpl): AppConfig = AppConfig(config, List.empty, List.empty) +} + +sealed trait GenericConfigSource + +object GenericConfigSource { + case class ConfigFile(file: File) extends GenericConfigSource + + case object ConfigDefault extends GenericConfigSource +} + +case class RoleConfig(role: String, active: Boolean, configSource: GenericConfigSource) + +case class LoadedRoleConfigs(roleConfig: RoleConfig, loaded: Seq[ConfigLoadResult.Success]) + +sealed trait ConfigLoadResult { + def clue: String + + def src: ConfigSource + + def toEither: Either[ConfigLoadResult.Failure, ConfigLoadResult.Success] +} + +object ConfigLoadResult { + case class Success(clue: String, src: ConfigSource, config: Config) extends ConfigLoadResult { + override def toEither: Either[ConfigLoadResult.Failure, ConfigLoadResult.Success] = Right(this) + } + + case class Failure(clue: String, src: ConfigSource, failure: Throwable) extends ConfigLoadResult { + override def toEither: Either[ConfigLoadResult.Failure, ConfigLoadResult.Success] = Left(this) + } +} + +sealed trait ConfigSource + +object ConfigSource { + final case class Resource(name: String) extends ConfigSource { + override def toString: String = s"resource:$name" + } + + final case class File(file: java.io.File) extends ConfigSource { + override def toString: String = s"file:$file" + } +} diff --git a/distage/distage-extension-config/.jvm/src/main/scala/izumi/distage/config/model/AppConfigSyntax.scala b/distage/distage-extension-config/.jvm/src/main/scala/izumi/distage/config/model/AppConfigSyntax.scala deleted file mode 100644 index 50111341e4..0000000000 --- a/distage/distage-extension-config/.jvm/src/main/scala/izumi/distage/config/model/AppConfigSyntax.scala +++ /dev/null @@ -1,7 +0,0 @@ -package izumi.distage.config.model - -import com.typesafe.config.ConfigFactory - -trait AppConfigSyntax { - val empty: AppConfig = AppConfig(ConfigFactory.empty()) -} diff --git a/distage/distage-extension-config/.jvm/src/test/scala-2.13/izumi/distage/impl/OptionalDependencyTest213.scala b/distage/distage-extension-config/.jvm/src/test/scala-2.13/izumi/distage/impl/OptionalDependencyTest213.scala index 1b4968047a..61f4d3f735 100644 --- a/distage/distage-extension-config/.jvm/src/test/scala-2.13/izumi/distage/impl/OptionalDependencyTest213.scala +++ b/distage/distage-extension-config/.jvm/src/test/scala-2.13/izumi/distage/impl/OptionalDependencyTest213.scala @@ -1,17 +1,15 @@ package izumi.distage.impl import izumi.functional.bio.impl.BioEither -import izumi.functional.bio.{Applicative2, ApplicativeError2, Arrow3, ArrowChoice3, Ask3, Async2, Bifunctor2, Bracket2, Concurrent2, Error2, Fork2, Functor2, Guarantee2, IO2, IO3, Local3, Monad2, MonadAsk3, Panic2, Parallel2, Primitives2, PrimitivesM2, Profunctor3, Temporal2} +import izumi.functional.bio.{Applicative2, ApplicativeError2, Async2, Bifunctor2, BlockingIO2, Bracket2, Concurrent2, Error2, Fork2, Functor2, Guarantee2, IO2, Monad2, Panic2, Parallel2, Primitives2, PrimitivesM2, Temporal2} import org.scalatest.wordspec.AnyWordSpec class OptionalDependencyTest213 extends AnyWordSpec { "Perform exhaustive search for BIO and not find instances for ZIO / monix-bio when they're not on classpath" in { final class optSearch2[C[_[+_, +_]]] { def find[F[+_, +_]](implicit a: C[F] = null.asInstanceOf[C[F]]): C[F] = a } - final class optSearch3[C[_[-_, +_, +_]]] { def find[F[-_, +_, +_]](implicit a: C[F] = null.asInstanceOf[C[F]]): C[F] = a } assert(new optSearch2[IO2].find == null) - assert(new optSearch3[IO3].find == null) assert(new optSearch2[Functor2].find == BioEither) assert(new optSearch2[Applicative2].find == BioEither) @@ -27,17 +25,11 @@ class OptionalDependencyTest213 extends AnyWordSpec { assert(new optSearch2[Async2].find == null) assert(new optSearch2[Temporal2].find == null) assert(new optSearch2[Concurrent2].find == null) - assert(new optSearch3[Ask3].find == null) - assert(new optSearch3[MonadAsk3].find == null) - assert(new optSearch3[Profunctor3].find == null) - assert(new optSearch3[Arrow3].find == null) - assert(new optSearch3[ArrowChoice3].find == null) - assert(new optSearch3[Local3].find == null) assert(new optSearch2[Fork2].find == null) assert(new optSearch2[Primitives2].find == null) assert(new optSearch2[PrimitivesM2].find == null) -// assert(new optSearch2[BlockingIO].find == null) // hard to make searching this not require zio currently (`type ZIOWithBlocking` creates issue) + assert(new optSearch2[BlockingIO2].find == null) } } diff --git a/distage/distage-extension-config/.jvm/src/test/scala/izumi/distage/config/ConfigTest.scala b/distage/distage-extension-config/.jvm/src/test/scala/izumi/distage/config/ConfigTest.scala index bcc38a4b75..1570a1c9c6 100644 --- a/distage/distage-extension-config/.jvm/src/test/scala/izumi/distage/config/ConfigTest.scala +++ b/distage/distage-extension-config/.jvm/src/test/scala/izumi/distage/config/ConfigTest.scala @@ -20,7 +20,7 @@ final class ConfigTest extends AnyWordSpec { } def mkModule(config: Config): AppConfigModule = { - val appConfig = AppConfig(config) + val appConfig = AppConfig(config, List.empty, List.empty) new AppConfigModule(appConfig) } diff --git a/distage/distage-extension-config/.jvm/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala b/distage/distage-extension-config/.jvm/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala index 67ae631f57..6625b51b92 100644 --- a/distage/distage-extension-config/.jvm/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala +++ b/distage/distage-extension-config/.jvm/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala @@ -5,31 +5,13 @@ import distage.Lifecycle import izumi.distage.model.definition.ModuleDef import izumi.functional.quasi.{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} +import izumi.functional.bio.{Applicative2, ApplicativeError2, Async2, Bifunctor2, BlockingIO2, Bracket2, Concurrent2, Error2, F, Fork2, Functor2, Guarantee2, IO2, Monad2, Panic2, Parallel2, Primitives2, PrimitivesM2, Temporal2} +import izumi.fundamentals.platform.functional.{Identity, Identity2} import org.scalatest.GivenWhenThen import org.scalatest.wordspec.AnyWordSpec class OptionalDependencyTest extends AnyWordSpec with GivenWhenThen { - "test 2" in { - // update ref from the environment and return result - def adderEnv[F[-_, +_, +_]: MonadAsk3](i: Int): F[Ref3[F, Int], Nothing, Int] = { - F.access { - ref => - for { - _ <- ref.update(_ + i) - res <- ref.get - } yield res - } - } - - locally { - implicit val ask: MonadAsk3[Identity3] = null - intercept[NullPointerException](adderEnv[Identity3](0)) - } - } - "test 1" in { def adder[F[+_, +_]: Monad2: Primitives2](i: Int): F[Nothing, Int] = { F.mkRef(0) @@ -68,24 +50,10 @@ class OptionalDependencyTest extends AnyWordSpec with GivenWhenThen { assert(x[Identity] == 1) trait SomeBIO[+E, +A] - type SomeBIO3[-R, +E, +A] = R => SomeBIO[E, A] - - def threeTo2[FR[-_, +_, +_]](implicit FR: IO3[FR]): FR[Any, Nothing, Unit] = { - val F: IO2[FR[Any, +_, +_]] = implicitly // must use `BIOConvert3To2` instance to convert FR -> F - F.unit - } def optSearch[A](implicit a: A = null.asInstanceOf[A]) = a final class optSearch1[C[_[_]]] { def find[F[_]](implicit a: C[F] = null.asInstanceOf[C[F]]): C[F] = a } - locally { - implicit val BIO3SomeBIO3: IO3[SomeBIO3] = null - try threeTo2[SomeBIO3] - catch { - case _: NullPointerException => - } - } - assert(new optSearch1[QuasiFunctor].find == QuasiFunctor.quasiFunctorIdentity) assert(new optSearch1[QuasiApplicative].find == QuasiApplicative.quasiApplicativeIdentity) assert(new optSearch1[QuasiPrimitives].find == QuasiPrimitives.quasiPrimitivesIdentity) @@ -119,17 +87,11 @@ class OptionalDependencyTest extends AnyWordSpec with GivenWhenThen { optSearch[Async2[SomeBIO]] optSearch[Temporal2[SomeBIO]] optSearch[Concurrent2[SomeBIO]] - optSearch[Ask3[SomeBIO3]] - optSearch[MonadAsk3[SomeBIO3]] - optSearch[Profunctor3[SomeBIO3]] - optSearch[Arrow3[SomeBIO3]] - optSearch[ArrowChoice3[SomeBIO3]] - optSearch[Local3[SomeBIO3]] optSearch[Fork2[SomeBIO]] optSearch[Primitives2[SomeBIO]] optSearch[PrimitivesM2[SomeBIO]] -// optSearch[BlockingIO2[SomeBIO]] // hard to make searching this not require zio currently (`type ZIOWithBlocking` creates issue) + optSearch[BlockingIO2[SomeBIO]] And("`No More Orphans` type provider object is accessible") izumi.fundamentals.orphans.`cats.effect.kernel.Sync`.hashCode() diff --git a/distage/distage-extension-config/src/main/scala/izumi/distage/config/AppConfigModule.scala b/distage/distage-extension-config/src/main/scala/izumi/distage/config/AppConfigModule.scala index c69db5fc05..8f6bd3e44a 100644 --- a/distage/distage-extension-config/src/main/scala/izumi/distage/config/AppConfigModule.scala +++ b/distage/distage-extension-config/src/main/scala/izumi/distage/config/AppConfigModule.scala @@ -5,11 +5,9 @@ import izumi.distage.model.definition.ModuleDef class AppConfigModule(appConfig: AppConfig) extends ModuleDef { make[AppConfig].fromValue(appConfig) - - def this(config: DistageConfigImpl) = this(AppConfig(config)) } object AppConfigModule { def apply(appConfig: AppConfig): AppConfigModule = new AppConfigModule(appConfig) - def apply(config: DistageConfigImpl): AppConfigModule = new AppConfigModule(config) + def apply(config: DistageConfigImpl): AppConfigModule = new AppConfigModule(AppConfig.provided(config)) } diff --git a/distage/distage-extension-config/src/main/scala/izumi/distage/config/model/AppConfig.scala b/distage/distage-extension-config/src/main/scala/izumi/distage/config/model/AppConfig.scala deleted file mode 100644 index 58382b4da9..0000000000 --- a/distage/distage-extension-config/src/main/scala/izumi/distage/config/model/AppConfig.scala +++ /dev/null @@ -1,7 +0,0 @@ -package izumi.distage.config.model - -import izumi.distage.config.DistageConfigImpl - -final case class AppConfig(config: DistageConfigImpl) - -object AppConfig extends AppConfigSyntax {} diff --git a/distage/distage-extension-logstage/src/main/scala/izumi/logstage/distage/LogIOModule.scala b/distage/distage-extension-logstage/src/main/scala/izumi/logstage/distage/LogIOModule.scala index 7c845cd742..aa83650a6b 100644 --- a/distage/distage-extension-logstage/src/main/scala/izumi/logstage/distage/LogIOModule.scala +++ b/distage/distage-extension-logstage/src/main/scala/izumi/logstage/distage/LogIOModule.scala @@ -10,6 +10,8 @@ import logstage.{LogCreateIO, LogIO, LogRouter, UnsafeLogIO} * Add a `LogIO[F]` component and others, depending on an existing `IzLogger` * * To setup `IzLogger` at the same time, use `apply` with parameters + * + * Depends on `IzLogger` */ class LogIOModule[F[_]: TagK] extends ModuleDef { make[LogIO[F]] @@ -31,7 +33,10 @@ object LogIOModule { } } -/** [[LogIOModule]] for bifunctors */ +/** [[LogIOModule]] for bifunctors + * + * Depends on `IzLogger` + */ class LogIO2Module[F[_, _]: TagKK] extends LogIOModule[F[Nothing, _]] object LogIO2Module { @@ -41,7 +46,10 @@ object LogIO2Module { } } -/** [[LogIOModule]] for trifunctors */ +/** [[LogIOModule]] for trifunctors + * + * Depends on `IzLogger` + */ class LogIO3Module[F[_, _, _]: TagK3] extends LogIOModule[F[Any, Nothing, _]] object LogIO3Module { diff --git a/distage/distage-extension-plugins/.jvm/src/test/resources/custom-role.conf b/distage/distage-extension-plugins/.jvm/src/test/resources/custom-role.conf new file mode 100644 index 0000000000..e2b482d55f --- /dev/null +++ b/distage/distage-extension-plugins/.jvm/src/test/resources/custom-role.conf @@ -0,0 +1,3 @@ +somesection { + somevalue = 1 +} diff --git a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala index 5ade906530..63be22fd3a 100644 --- a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala +++ b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala @@ -43,11 +43,23 @@ object PluginConfig extends PluginConfigStatic { def packages(packagesEnabled: Seq[String]): PluginConfig = PluginConfig(packagesEnabled, Nil, cachePackages = false, debug = false, Nil, Nil) def packagesThisPkg(implicit pkg: SourcePackageMaterializer): PluginConfig = packages(pkg.get.pkg) - /** Create a [[PluginConfig]] that simply contains the specified modules */ - def const(plugins: Seq[ModuleBase]): PluginConfig = PluginConfig(Nil, Nil, cachePackages = false, debug = false, plugins, Nil) + /** Create a [[PluginConfig]] that simply contains the specified plugins */ + def const(plugins: Seq[PluginBase]): PluginConfig = PluginConfig(Nil, Nil, cachePackages = false, debug = false, plugins, Nil) - /** Create a [[PluginConfig]] that simply contains the specified modules */ - def const(plugin: ModuleBase): PluginConfig = const(Seq(plugin)) + /** Create a [[PluginConfig]] that simply contains the specified plugin */ + def const(plugin: PluginBase): PluginConfig = const(Seq(plugin)) + + /** + * Like [[const]], but accepts simple [[ModuleBase]]. + * Unlike for inheritors of [[PluginDef]], changing a ModuleDef source code may not trigger recompilation of compile-time checks + */ + def constUnchecked(modules: Seq[ModuleBase]): PluginConfig = PluginConfig(Nil, Nil, cachePackages = false, debug = false, modules, Nil) + + /** + * Like [[const]], but accepts simple [[ModuleBase]]. + * Unlike for inheritors of [[PluginDef]], changing a ModuleDef source code may not trigger recompilation of compile-time checks + */ + def constUnchecked(module: ModuleBase): PluginConfig = constUnchecked(Seq(module)) /** A [[PluginConfig]] that returns no plugins */ lazy val empty: PluginConfig = const(Nil) diff --git a/distage/distage-framework-api/src/main/scala-2/izumi/distage/roles/model/definition/RoleModuleDef.scala b/distage/distage-framework-api/src/main/scala-2/izumi/distage/roles/model/definition/RoleModuleDef.scala index 02bcb4879d..4796167ff5 100644 --- a/distage/distage-framework-api/src/main/scala-2/izumi/distage/roles/model/definition/RoleModuleDef.scala +++ b/distage/distage-framework-api/src/main/scala-2/izumi/distage/roles/model/definition/RoleModuleDef.scala @@ -1,6 +1,6 @@ package izumi.distage.roles.model.definition -import izumi.distage.constructors.macros.AnyConstructorMacro +import izumi.distage.constructors.macros.MakeMacro import izumi.distage.model.definition.ModuleDef import izumi.distage.model.definition.dsl.ModuleDefDSL.MakeDSL import izumi.distage.roles.model.RoleDescriptor.GetRoleDescriptor @@ -18,7 +18,7 @@ object RoleModuleDef { object RoleModuleDefMacros { def makeRole[T: c.WeakTypeTag](c: blackbox.Context)(getRoleDescriptor: c.Expr[GetRoleDescriptor[T]]): c.Expr[MakeDSL[T]] = { c.universe.reify { - AnyConstructorMacro.make[MakeDSL, T](c).splice.tagged(RoleTag(getRoleDescriptor.splice.roleDescriptor)) + MakeMacro.make[MakeDSL, T](c).splice.tagged(RoleTag(getRoleDescriptor.splice.roleDescriptor)) } } } diff --git a/distage/distage-framework-api/src/main/scala-3/izumi/distage/roles/model/definition/RoleModuleDef.scala b/distage/distage-framework-api/src/main/scala-3/izumi/distage/roles/model/definition/RoleModuleDef.scala index 7ec8a6d34b..0ac2186a87 100644 --- a/distage/distage-framework-api/src/main/scala-3/izumi/distage/roles/model/definition/RoleModuleDef.scala +++ b/distage/distage-framework-api/src/main/scala-3/izumi/distage/roles/model/definition/RoleModuleDef.scala @@ -1,6 +1,6 @@ package izumi.distage.roles.model.definition -import izumi.distage.constructors.AnyConstructorMacro +import izumi.distage.constructors.MakeMacro import izumi.distage.model.definition.ModuleDef import izumi.distage.model.definition.dsl.ModuleDefDSL.MakeDSL import izumi.distage.roles.model.RoleDescriptor.GetRoleDescriptor @@ -18,7 +18,7 @@ object RoleModuleDef { object RoleModuleDefMacros { def makeRole[T: Type](getRoleDescriptor: Expr[GetRoleDescriptor[T]])(using qctx: Quotes): Expr[MakeDSL[T]] = { '{ - ${ AnyConstructorMacro.makeMethod[T, MakeDSL[T]] }.tagged(RoleTag(${ getRoleDescriptor }.roleDescriptor)) + ${ MakeMacro.makeMethod[T, MakeDSL[T]] }.tagged(RoleTag(${ getRoleDescriptor }.roleDescriptor)) } } } diff --git a/distage/distage-framework/.js/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala b/distage/distage-framework/.js/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala index dd86d98b0f..e095f0c481 100644 --- a/distage/distage-framework/.js/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala +++ b/distage/distage-framework/.js/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala @@ -1,7 +1,9 @@ package izumi.distage.framework.services -trait ConfigLoader extends AbstractConfigLoader {} +import distage.config.AppConfig + +trait ConfigLoader extends AbstractConfigLoader object ConfigLoader { - def empty: ConfigLoader = () => ??? + def empty: ConfigLoader = (_: String) => AppConfig.empty } diff --git a/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/RoleAppMain.scala b/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/RoleAppMain.scala index b5dc556bea..509ebdf49c 100644 --- a/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/RoleAppMain.scala +++ b/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/RoleAppMain.scala @@ -1,6 +1,7 @@ package izumi.distage.roles import distage.Injector +import distage.config.AppConfig import izumi.distage.framework.config.PlanningOptions import izumi.distage.model.Locator import izumi.distage.model.definition.{Activation, Axis, Module, ModuleDef} @@ -9,6 +10,7 @@ import izumi.distage.plugins.PluginConfig import izumi.distage.roles.RoleAppMain.ArgV import izumi.distage.roles.launcher.AppResourceProvider.AppResource import izumi.distage.roles.launcher.AppShutdownStrategy +import izumi.distage.roles.launcher.ActivationParser import izumi.functional.lifecycle.Lifecycle import izumi.functional.quasi.QuasiIO import izumi.fundamentals.platform.cli.model.raw.{RawAppArgs, RawEntrypointParams, RawRoleParams, RequiredRoles} @@ -107,7 +109,12 @@ abstract class RoleAppMain[F[_]]( ) ++ new ModuleDef { make[RawAppArgs].fromValue(RawAppArgs(RawEntrypointParams.empty, additionalRoles.requiredRoles)) make[PlanningOptions].fromValue(planningOptions()) - make[Activation].named("roleapp").fromValue(activation()) + make[ActivationParser].from[ActivationParser.Impl] + make[Activation].named("entrypoint").fromValue(activation()) + make[Activation].named("roleapp").from { + (parser: ActivationParser, config: AppConfig) => + parser.parseActivation(config) + } } } diff --git a/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/launcher/ActivationParser.scala b/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/launcher/ActivationParser.scala new file mode 100644 index 0000000000..a9334c98e9 --- /dev/null +++ b/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/launcher/ActivationParser.scala @@ -0,0 +1,23 @@ +package izumi.distage.roles.launcher + +import distage.config.AppConfig +import izumi.distage.model.definition.{Activation, Id} +import scala.annotation.unused + +trait ActivationParser extends AbstractActivationParser {} + +object ActivationParser { + + class Impl( + defaultActivations: Activation@Id("default"), + additionalActivations: Activation@Id("additional"), + activation: Activation@Id("entrypoint"), + ) extends ActivationParser { + def parseActivation(@unused config: AppConfig): Activation = { + defaultActivations ++ + additionalActivations ++ + activation + } + } + +} diff --git a/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/launcher/PreparedAppSyntax.scala b/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/launcher/PreparedAppSyntax.scala index bb8bd1b532..5c340f9fb1 100644 --- a/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/launcher/PreparedAppSyntax.scala +++ b/distage/distage-framework/.js/src/main/scala/izumi/distage/roles/launcher/PreparedAppSyntax.scala @@ -5,7 +5,7 @@ import scala.concurrent.Future trait PreparedAppSyntax { implicit class PreparedAppSyntaxImpl[F[_]](app: PreparedApp[F]) { def run(): Future[Unit] = { - app.runner.runFuture(app.appResource.use(_ => app.effect.unit)(app.effect)) + app.runner.runFuture(app.appResource.use(l => app.roleAppEntrypoint.runTasksAndRoles(l, app.effect))(app.effect)) } } } diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/CheckableApp.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/CheckableApp.scala index 2398c823f4..53bcfaac3d 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/CheckableApp.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/CheckableApp.scala @@ -168,12 +168,12 @@ abstract class RoleCheckableApp[F[_]](override implicit val tagK: TagK[F]) exten } private[this] def specificResourceConfigLoader(classLoader: ClassLoader, resourceName: String): ConfigLoader = { - () => + (_: String) => val cfg = ConfigFactory.parseResources(classLoader, resourceName).resolve() if (cfg.origin().resource() eq null) { throw new DIConfigReadException(s"Couldn't find a config resource with name `$resourceName` - file not found", null) } - AppConfig(cfg) + AppConfig(cfg, List.empty, List.empty) } } } diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/PlanCheck.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/PlanCheck.scala index 87896db730..36f422377c 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/PlanCheck.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/PlanCheck.scala @@ -51,10 +51,15 @@ object PlanCheck { discard(app, cfg) def main(@unused args: Array[String]): Unit = { - assertAtRuntime() + assertAgainAtRuntime() } + def assertAgainAtRuntime(): Unit = planCheck.assertAgainAtRuntime() + def checkAgainAtRuntime(): PlanCheckResult = planCheck.checkAgainAtRuntime() + + @deprecated("Renamed to `assertAgainAtRuntime`", "1.2.0") def assertAtRuntime(): Unit = planCheck.assertAgainAtRuntime() + @deprecated("Renamed to `checkAgainAtRuntime`", "1.2.0") def checkAtRuntime(): PlanCheckResult = planCheck.checkAgainAtRuntime() } @@ -257,7 +262,7 @@ object PlanCheck { val reachableKeys = providedKeys ++ planVerifierResult.visitedKeys val configIssues = if (checkConfig) { - val realAppConfig = configLoader.loadConfig() + val realAppConfig = configLoader.loadConfig("compile-time validation") reportEffectiveConfig(realAppConfig.config.origin().toString) module.iterator diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/model/PlanCheckInput.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/model/PlanCheckInput.scala index c35f6dedc4..6fa43a4d19 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/model/PlanCheckInput.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/model/PlanCheckInput.scala @@ -1,8 +1,8 @@ package izumi.distage.framework.model import distage.Injector -import izumi.distage.framework.services.ConfigLoader -import izumi.distage.framework.services.ConfigLoader.ConfigLocation +import izumi.distage.framework.services.ConfigMerger.ConfigMergerImpl +import izumi.distage.framework.services.{ConfigArgsProvider, ConfigLoader, ConfigLocationProvider} import izumi.distage.model.definition.ModuleBase import izumi.distage.model.plan.Roots import izumi.distage.model.reflection.DIKey @@ -22,11 +22,17 @@ final case class PlanCheckInput[F[_]]( bsPlugins: LoadedPlugins, ) object PlanCheckInput { + private val emptyConfigArgs = ConfigArgsProvider.const(ConfigLoader.Args(None, List.empty, alwaysIncludeReferenceRoleConfigs = true)) + def apply[F[_]]( module: ModuleBase, roots: Roots, roleNames: Set[String] = Set.empty, - configLoader: ConfigLoader = new ConfigLoader.LocalFSImpl(IzLogger(), ConfigLocation.Default, ConfigLoader.Args.empty), + configLoader: ConfigLoader = { + val logger = IzLogger() + val merger = new ConfigMergerImpl(logger) + new ConfigLoader.LocalFSImpl(logger, merger, ConfigLocationProvider.Default, emptyConfigArgs) + }, appPlugins: LoadedPlugins = LoadedPlugins.empty, bsPlugins: LoadedPlugins = LoadedPlugins.empty, )(implicit effectType: TagK[F], diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigArgsProvider.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigArgsProvider.scala new file mode 100644 index 0000000000..6854f2de65 --- /dev/null +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigArgsProvider.scala @@ -0,0 +1,51 @@ +package izumi.distage.framework.services + +import izumi.distage.config.model.{GenericConfigSource, RoleConfig} +import izumi.distage.model.definition.Id +import izumi.distage.roles.RoleAppMain +import izumi.distage.roles.model.meta.RolesInfo +import izumi.fundamentals.platform.cli.model.raw.RawAppArgs + +import java.io.File +import scala.annotation.nowarn + +trait ConfigArgsProvider { + def args(): ConfigLoader.Args +} + +object ConfigArgsProvider { + def const(args: ConfigLoader.Args): ConfigArgsProvider = new ConfigArgsProvider.Const(args) + + open class Const(args0: ConfigLoader.Args) extends ConfigArgsProvider { + override def args(): ConfigLoader.Args = args0 + } + + @nowarn("msg=Unused import") + class Default( + parameters: RawAppArgs, + rolesInfo: RolesInfo, + alwaysIncludeReferenceRoleConfigs: Boolean @Id("distage.roles.always-include-reference-role-configs"), + ) extends ConfigArgsProvider { + override def args(): ConfigLoader.Args = { + import scala.collection.compat.* + + val specifiedRoleConfigs: Map[String, Option[File]] = parameters.roles.iterator + .map(roleParams => roleParams.role -> roleParams.roleParameters.findValue(RoleAppMain.Options.configParam).asFile) + .toMap + + val roleConfigs = rolesInfo.availableRoleNames.toList.map { + roleName => + val source = specifiedRoleConfigs.get(roleName).flatten match { + case Some(value) => + GenericConfigSource.ConfigFile(value) + case _ => + GenericConfigSource.ConfigDefault + } + RoleConfig(roleName, rolesInfo.requiredRoleNames.contains(roleName), source) + } + val maybeGlobalConfig = parameters.globalParameters.findValue(RoleAppMain.Options.configParam).asFile + + ConfigLoader.Args(maybeGlobalConfig, roleConfigs, alwaysIncludeReferenceRoleConfigs) + } + } +} diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala index c1ddfdff48..5625a68b98 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala @@ -1,24 +1,20 @@ package izumi.distage.framework.services -import com.typesafe.config.{Config, ConfigFactory, ConfigResolveOptions} -import izumi.distage.config.model.AppConfig -import izumi.distage.framework.services.ConfigLoader.LocalFSImpl.{ConfigLoaderException, ConfigSource, ResourceConfigKind} +import com.typesafe.config.{Config, ConfigFactory} +import izumi.distage.config.model.* import izumi.distage.model.definition.Id import izumi.distage.model.exceptions.DIException -import izumi.distage.roles.RoleAppMain -import izumi.distage.roles.model.meta.RolesInfo -import izumi.fundamentals.platform.cli.model.raw.RawAppArgs +import izumi.functional.IzEither.* +import izumi.fundamentals.platform.exceptions.IzThrowable.* import izumi.fundamentals.platform.resources.IzResources import izumi.fundamentals.platform.resources.IzResources.{LoadablePathReference, UnloadablePathReference} import izumi.fundamentals.platform.strings.IzString.* import izumi.logstage.api.IzLogger import java.io.{File, FileNotFoundException} -import scala.jdk.CollectionConverters.* +import scala.annotation.nowarn import scala.util.{Failure, Success, Try} - - /** * Default config resources: * - `\${roleName}.conf` @@ -31,7 +27,7 @@ import scala.util.{Failure, Success, Try} * - `common-reference.conf` * - `common-reference-dev.conf` * - * NOTE: You can change default config locations by overriding `make[ConfigLocation]` + * NOTE: You can change default config locations by overriding `make[ConfigLocationProvider]` * binding in [[izumi.distage.roles.RoleAppMain#roleAppBootOverrides]] (defaults defined in [[izumi.distage.roles.RoleAppBootModule]]) * * When explicit configs are passed to the role launcher on the command-line using the `-c` option, they have higher priority than all the reference configs. @@ -48,191 +44,128 @@ import scala.util.{Failure, Success, Try} * - explicits: `role1.conf`, `role2.conf`, `global.conf`, * - resources: `role1[-reference,-dev].conf`, `role2[-reference,-dev].conf`, ,`application[-reference,-dev].conf`, `common[-reference,-dev].conf` * - * @see [[ConfigLoader.ConfigLocation]] + * @see [[ConfigLocationProvider]] * @see [[ConfigLoader.LocalFSImpl]] */ -trait ConfigLoader extends AbstractConfigLoader { - -} +trait ConfigLoader extends AbstractConfigLoader +@nowarn("msg=Unused import") object ConfigLoader { - def empty: ConfigLoader = () => AppConfig(ConfigFactory.empty()) + def empty: ConfigLoader = (_: String) => AppConfig(ConfigFactory.empty(), List.empty, List.empty) import scala.collection.compat.* - trait ConfigLocation { - def forRole(roleName: String): Seq[ConfigSource] = ConfigLocation.defaultConfigReferences(roleName) - def forBase(filename: String): Seq[ConfigSource] = ConfigLocation.defaultConfigReferences(filename) - def defaultBaseConfigs: Seq[String] = ConfigLocation.defaultBaseConfigs - } - object ConfigLocation { - object Default extends ConfigLocation - - def defaultBaseConfigs: Seq[String] = Seq("application", "common") - - def defaultConfigReferences(name: String): Seq[ConfigSource] = { - Seq( - ConfigSource.Resource(s"$name.conf", ResourceConfigKind.Primary), - ConfigSource.Resource(s"$name-reference.conf", ResourceConfigKind.Primary), - ConfigSource.Resource(s"$name-reference-dev.conf", ResourceConfigKind.Development), - ) - } - } - final case class Args( global: Option[File], - role: Map[String, Option[File]], + configs: List[RoleConfig], + alwaysIncludeReferenceRoleConfigs: Boolean, ) - object Args { - def makeConfigLoaderArgs( - parameters: RawAppArgs, - rolesInfo: RolesInfo, - ): ConfigLoader.Args = { - val maybeGlobalConfig = parameters.globalParameters.findValue(RoleAppMain.Options.configParam).asFile - val emptyRoleConfigs = rolesInfo.requiredRoleNames.map(_ -> None).toMap - val specifiedRoleConfigs = parameters.roles.iterator - .map(roleParams => roleParams.role -> roleParams.roleParameters.findValue(RoleAppMain.Options.configParam).asFile) - .toMap - ConfigLoader.Args(maybeGlobalConfig, (emptyRoleConfigs ++ specifiedRoleConfigs).view.filterKeys(rolesInfo.requiredRoleNames).toMap) - } - - def empty: ConfigLoader.Args = ConfigLoader.Args(None, Map.empty) - } + final class ConfigLoaderException(message: String, val failures: List[Throwable]) extends DIException(message) open class LocalFSImpl( logger: IzLogger @Id("early"), - configLocation: ConfigLocation, - configArgs: ConfigLoader.Args, + merger: ConfigMerger, + configLocation: ConfigLocationProvider, + args: ConfigArgsProvider, ) extends ConfigLoader { protected def resourceClassLoader: ClassLoader = getClass.getClassLoader - def loadConfig(): AppConfig = { - val commonReferenceConfigs = configLocation.defaultBaseConfigs.flatMap(configLocation.forBase) - val commonExplicitConfigs = configArgs.global.map(ConfigSource.File.apply).toList - - val (roleReferenceConfigs, roleExplicitConfigs) = (configArgs.role: Iterable[(String, Option[File])]).partitionMap { - case (role, None) => Left(configLocation.forRole(role)) - case (_, Some(file)) => Right(ConfigSource.File(file)) + def loadConfig(clue: String): AppConfig = { + val configArgs = args.args() + + val maybeLoadedRoleConfigs = configArgs.configs.map { + rc => + val defaults = configLocation.forRole(rc.role).map(loadConfigSource) + val loaded = rc.configSource match { + case GenericConfigSource.ConfigFile(file) => + val provided = Seq(loadConfigSource(ConfigSource.File(file))) + if (configArgs.alwaysIncludeReferenceRoleConfigs) { + provided ++ defaults + } else { + provided + } + case GenericConfigSource.ConfigDefault => + defaults + } + (rc, loaded) } - val allConfigs = (roleExplicitConfigs.iterator ++ commonExplicitConfigs ++ roleReferenceConfigs.iterator.flatten ++ commonReferenceConfigs).toList - - val (cfgInfo, loaded) = loadConfigSources(allConfigs) - - logger.info(s"Using system properties with fallback ${cfgInfo.niceList() -> "config files"}") - - val (good, bad) = loaded.partition(_._2.isSuccess) - - if (bad.nonEmpty) { - val failuresList = bad.collect { - case (s, Failure(f)) => - s"$s: $f" - } - val failures = failuresList.niceList() - - logger.error(s"Failed to load configs: $failures") - throw new ConfigLoaderException(s"Failed to load configs: failures=$failures", failuresList) - } else { - val folded = foldConfigs(good.collect { - case (src, Success(c)) => src -> c - }) - - val config = ConfigFactory - .systemProperties() - .withFallback(folded) - .resolve() + val commonExplicitConfigs = configArgs.global.map(ConfigSource.File.apply).toList + val commonReferenceConfigs = configLocation.commonReferenceConfigs.toList + val commonConfigs = commonReferenceConfigs ++ commonExplicitConfigs + val loaded = for { + loadedCommonConfigs <- commonConfigs.map(loadConfigSource).map(_.toEither).biSequenceScalar + loadedRoleConfigs <- maybeLoadedRoleConfigs.map { + case (rc, loaded) => + loaded.map { + lr => + lr.toEither + }.biSequenceScalar match { + case Left(value) => + Left(value) + case Right(value) => + Right(LoadedRoleConfigs(rc, value)) + } + }.biSequence + } yield { + (loadedCommonConfigs, loadedRoleConfigs) + } - AppConfig(config) + loaded match { + case Left(value) => + val failures = value.map(f => s"Failed to load ${f.src} ${f.clue}: ${f.failure.stacktraceString}") + logger.error(s"Cannot load configuration: ${failures.toList.niceList() -> "failures"}") + throw new ConfigLoaderException(s"Cannot load configuration: ${failures.toList.niceList()}", value.map(_.failure).toList) + case Right((shared, role)) => + val merged = merger.addSystemProps(merger.merge(shared, role, clue)) + AppConfig(merged, shared, role) } - } - protected def loadConfigSources(allConfigs: List[ConfigSource]): (List[String], List[(ConfigSource, Try[Config])]) = { - allConfigs.map(loadConfigSource).unzip } - protected def loadConfigSource(configSource: ConfigSource): (String, (ConfigSource, Try[Config])) = configSource match { - case r: ConfigSource.Resource => - def tryLoadResource(): Try[Config] = { - Try(ConfigFactory.parseResources(resourceClassLoader, r.name)).flatMap { - cfg => - if (cfg.origin().resource() eq null) { - Failure(new FileNotFoundException(s"Couldn't find config file $r")) - } else Success(cfg) + protected def loadConfigSource(configSource: ConfigSource): ConfigLoadResult = { + configSource match { + case r: ConfigSource.Resource => + def tryLoadResource(): Try[Config] = { + Try(ConfigFactory.parseResources(resourceClassLoader, r.name)).flatMap { + cfg => + if (cfg.origin().resource() eq null) { + Failure(new FileNotFoundException(s"Couldn't find config file $r")) + } else Success(cfg) + } } - } - - IzResources(resourceClassLoader).getPath(r.name) match { - case Some(LoadablePathReference(path, _)) => - s"$r (available: $path)" -> (r -> tryLoadResource()) - case Some(UnloadablePathReference(path)) => - s"$r (exists: $path)" -> (r -> tryLoadResource()) - case None => - s"$r (missing)" -> (r -> Success(ConfigFactory.empty())) - } - - case f: ConfigSource.File => - if (f.file.exists()) { - s"$f (exists: ${f.file.getCanonicalPath})" -> (f -> Try(ConfigFactory.parseFile(f.file)).flatMap { - cfg => if (cfg.origin().filename() ne null) Success(cfg) else Failure(new FileNotFoundException(s"Couldn't find config file $f")) - }) - } else { - s"$f (missing)" -> (f -> Success(ConfigFactory.empty())) - } - } - - protected def foldConfigs(roleConfigs: IterableOnce[(ConfigSource, Config)]): Config = { - roleConfigs.iterator.foldLeft(ConfigFactory.empty()) { - case (acc, (src, cfg)) => - verifyConfigs(src, cfg, acc) - acc.withFallback(cfg) - } - } - protected def verifyConfigs(src: ConfigSource, cfg: Config, acc: Config): Unit = { - val duplicateKeys = getKeys(acc) intersect getKeys(cfg) - if (duplicateKeys.nonEmpty) { - src match { - case ConfigSource.Resource(_, ResourceConfigKind.Development) => - logger.debug(s"Some keys in supplied ${src -> "development config"} duplicate already defined keys: ${duplicateKeys.niceList() -> "keys" -> null}") - case _ => - logger.warn(s"Some keys in supplied ${src -> "config"} duplicate already defined keys: ${duplicateKeys.niceList() -> "keys" -> null}") - } - } - } + IzResources(resourceClassLoader).getPath(r.name) match { + case Some(LoadablePathReference(path, _)) => + doLoad(s"$r (available: $path)", configSource, tryLoadResource()) + case Some(UnloadablePathReference(path)) => + doLoad(s"$r (exists: $path)", configSource, tryLoadResource()) + case None => + doLoad(s"$r (missing)", configSource, Success(ConfigFactory.empty())) + } - protected def getKeys(c: Config): collection.Set[String] = { - if (c.isResolved) { - c.entrySet().asScala.map(_.getKey) - } else { - Try { - c.resolve(ConfigResolveOptions.defaults().setAllowUnresolved(true)) - }.toOption.filter(_.isResolved) match { - case Some(value) => value.entrySet().asScala.map(_.getKey) - case None => Set.empty - } + case f: ConfigSource.File => + if (f.file.exists()) { + doLoad( + s"$f (exists: ${f.file.getCanonicalPath})", + configSource, + Try(ConfigFactory.parseFile(f.file)).flatMap { + cfg => if (cfg.origin().filename() ne null) Success(cfg) else Failure(new FileNotFoundException(s"Couldn't find config file $f")) + }, + ) + } else { + doLoad(s"$f (missing)", configSource, Failure(new FileNotFoundException(f.file.getCanonicalPath))) + } } } - } - - object LocalFSImpl { - sealed trait ResourceConfigKind - object ResourceConfigKind { - case object Primary extends ResourceConfigKind - case object Development extends ResourceConfigKind - } - - sealed trait ConfigSource - object ConfigSource { - final case class Resource(name: String, kind: ResourceConfigKind) extends ConfigSource { - override def toString: String = s"resource:$name" - } - final case class File(file: java.io.File) extends ConfigSource { - override def toString: String = s"file:$file" + private def doLoad(clue: String, source: ConfigSource, loader: => Try[Config]): ConfigLoadResult = { + loader match { + case Failure(exception) => ConfigLoadResult.Failure(clue, source, exception) + case Success(value) => ConfigLoadResult.Success(clue, source, value) } } - final class ConfigLoaderException(message: String, val failures: List[String]) extends DIException(message) } } diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigLocationProvider.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigLocationProvider.scala new file mode 100644 index 0000000000..78c3e96470 --- /dev/null +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigLocationProvider.scala @@ -0,0 +1,31 @@ +package izumi.distage.framework.services + +import izumi.distage.config.model.ConfigSource + +trait ConfigLocationProvider { + def forRole(roleName: String): Seq[ConfigSource] + + def commonReferenceConfigs: Seq[ConfigSource] +} + +object ConfigLocationProvider { + object Default extends ConfigLocationProvider { + def forRole(roleName: String): Seq[ConfigSource] = { + ConfigLocationProvider.defaultConfigReferences(roleName) + } + + def commonReferenceConfigs: Seq[ConfigSource] = { + ConfigLocationProvider.defaultBaseConfigs.flatMap(ConfigLocationProvider.defaultConfigReferences) + } + } + + private def defaultBaseConfigs: Seq[String] = Seq("application", "common") + + private def defaultConfigReferences(name: String): Seq[ConfigSource] = { + Seq( + ConfigSource.Resource(s"$name.conf"), + ConfigSource.Resource(s"$name-reference.conf"), + ConfigSource.Resource(s"$name-reference-dev.conf"), + ) + } +} diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigMerger.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigMerger.scala new file mode 100644 index 0000000000..57be7e5426 --- /dev/null +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/framework/services/ConfigMerger.scala @@ -0,0 +1,99 @@ +package izumi.distage.framework.services + +import com.typesafe.config.{Config, ConfigFactory, ConfigResolveOptions} +import izumi.distage.config.model.* +import izumi.distage.model.definition.Id +import izumi.fundamentals.platform.strings.IzString.* +import izumi.logstage.api.IzLogger + +import scala.jdk.CollectionConverters.* +import scala.util.Try + +trait ConfigMerger { + def merge(shared: List[ConfigLoadResult.Success], role: List[LoadedRoleConfigs], clue: String): Config + def mergeFilter(shared: List[ConfigLoadResult.Success], role: List[LoadedRoleConfigs], filter: LoadedRoleConfigs => Boolean, clue: String): Config + def foldConfigs(roleConfigs: List[ConfigLoadResult.Success]): Config + def addSystemProps(config: Config): Config +} + +object ConfigMerger { + class ConfigMergerImpl(logger: IzLogger @Id("early")) extends ConfigMerger { + override def merge(shared: List[ConfigLoadResult.Success], role: List[LoadedRoleConfigs], clue: String): Config = { + mergeFilter(shared, role, _.roleConfig.active, clue) + } + + override def mergeFilter(shared: List[ConfigLoadResult.Success], role: List[LoadedRoleConfigs], filter: LoadedRoleConfigs => Boolean, clue: String): Config = { + val nonEmptyShared = shared.filterNot(_.config.isEmpty) + val roleConfigs = role.flatMap(_.loaded) + val nonEmptyRole = roleConfigs.filterNot(_.config.isEmpty) + + val toMerge = (shared ++ role.filter(filter).flatMap(_.loaded)).filterNot(_.config.isEmpty) + + val folded = foldConfigs(toMerge) + + val repr = toMerge.map(c => c.clue) + + val sub = logger("config context" -> clue) + sub.info(s"Config input: ${shared.size -> "shared configs"} of which ${nonEmptyShared.size -> "non empty shared configs"}") + sub.info(s"Config input: ${roleConfigs.size -> "role configs"} of which ${nonEmptyRole.size -> "non empty role configs"}") + sub.info(s"Output config has ${folded.entrySet().size() -> "root nodes"}") + sub.info(s"The following configs were used (ascending priority): ${repr.niceList() -> "used configs"}") + + val configRepr = (shared.map(c => (c.clue, true)) ++ role.flatMap(r => r.loaded.map(c => (s"${c.clue}, role=${r.roleConfig.role}", filter(r))))) + .map(c => s"${c._1}, active = ${c._2}") + logger.debug(s"Full list of processed configs: ${configRepr.niceList() -> "locations"}") + + folded + } + + def foldConfigs(roleConfigs: List[ConfigLoadResult.Success]): Config = { + val fallbackOrdered = roleConfigs.reverse // rightmost config has the highest priority, so we need it to become leftmost + + verifyConfigs(fallbackOrdered) + + fallbackOrdered.foldLeft(ConfigFactory.empty()) { + case (acc, loaded) => + acc.withFallback(loaded.config) + } + } + + private def verifyConfigs(fallbackOrdered: List[ConfigLoadResult.Success]): Unit = { + import izumi.fundamentals.collections.IzCollections.* + val keyIndex = fallbackOrdered + .filter(_.src.isInstanceOf[ConfigSource.Resource]) + .flatMap(c => getKeys(c.config).map(key => (key, c))) + + keyIndex.toUniqueMap(identity) match { + case Left(value) => + val diag = value.map { case (key, configs) => s"$key is defined in ${configs.map(_.clue).niceList(prefix = "* ").shift(2)}" } + logger.warn(s"Reference config resources have ${diag.niceList() -> "conflicting keys"}") + case Right(_) => + } + } + + protected def getKeys(c: Config): collection.Set[String] = { + if (c.isResolved) { + c.entrySet().asScala.map(_.getKey) + } else { + Try { + c.resolve(ConfigResolveOptions.defaults().setAllowUnresolved(true)) + }.toOption.filter(_.isResolved) match { + case Some(value) => value.entrySet().asScala.map(_.getKey) + case None => Set.empty + } + } + } + + override def addSystemProps(config: Config): Config = { + val result = ConfigFactory + .systemProperties() + .withFallback(config) + .resolve() + logger.info( + s"Config with ${config.entrySet().size() -> "root nodes"} has been enhanced with system properties, new config has ${result.entrySet().size() -> "new root nodes"}" + ) + + result + } + } +} diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppBootArgsModule.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppBootArgsModule.scala index cb405eefbf..b47ab780c5 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppBootArgsModule.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppBootArgsModule.scala @@ -1,5 +1,6 @@ package izumi.distage.roles +import distage.config.AppConfig import izumi.distage.model.definition.ModuleDef import izumi.distage.modules.DefaultModule import izumi.distage.roles.RoleAppMain.ArgV @@ -38,8 +39,8 @@ class RoleAppBootArgsModule[F[_]: TagK: DefaultModule]( make[RoleAppActivationParser].from[RoleAppActivationParser.Impl] make[ActivationParser].from[ActivationParser.Impl] make[Activation].named("roleapp").from { - (parser: ActivationParser) => - parser.parseActivation() + (parser: ActivationParser, config: AppConfig) => + parser.parseActivation(config) } make[AppArgsInterceptor].from[AppArgsInterceptor.Impl] diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppBootConfigModule.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppBootConfigModule.scala index a7072a8127..3fee8688ca 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppBootConfigModule.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppBootConfigModule.scala @@ -1,17 +1,19 @@ package izumi.distage.roles import izumi.distage.config.model.AppConfig -import izumi.distage.framework.services.ConfigLoader +import izumi.distage.framework.services.{ConfigArgsProvider, ConfigLoader, ConfigLocationProvider, ConfigMerger} import izumi.distage.model.definition.ModuleDef import izumi.distage.modules.DefaultModule import izumi.reflect.TagK class RoleAppBootConfigModule[F[_]: TagK: DefaultModule]() extends ModuleDef { make[ConfigLoader].from[ConfigLoader.LocalFSImpl] - make[ConfigLoader.ConfigLocation].from(ConfigLoader.ConfigLocation.Default) - make[ConfigLoader.Args].from(ConfigLoader.Args.makeConfigLoaderArgs _) + make[ConfigMerger].from[ConfigMerger.ConfigMergerImpl] + make[ConfigLocationProvider].from(ConfigLocationProvider.Default) + make[ConfigArgsProvider].from[ConfigArgsProvider.Default] + make[Boolean].named("distage.roles.always-include-reference-role-configs").fromValue(false) make[AppConfig].from { (configLoader: ConfigLoader) => - configLoader.loadConfig() + configLoader.loadConfig("application startup") } } diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppMain.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppMain.scala index 8474053f3b..be62136191 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppMain.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/RoleAppMain.scala @@ -6,21 +6,21 @@ import izumi.distage.framework.services.ModuleProvider import izumi.distage.framework.{PlanCheckConfig, PlanCheckMaterializer, RoleCheckableApp} import izumi.distage.model.Locator import izumi.distage.model.definition.{Axis, Module, ModuleDef} -import izumi.distage.modules.{DefaultModule, DefaultModule2, DefaultModule3} +import izumi.distage.modules.{DefaultModule, DefaultModule2} import izumi.distage.plugins.PluginConfig import izumi.distage.roles.RoleAppMain.ArgV import izumi.distage.roles.launcher.AppResourceProvider.AppResource import izumi.distage.roles.launcher.AppShutdownStrategy.* import izumi.distage.roles.launcher.{AppFailureHandler, AppShutdownStrategy} -import izumi.functional.bio.{Async2, Async3} +import izumi.functional.bio.Async2 import izumi.functional.lifecycle.Lifecycle import izumi.functional.quasi.QuasiIO import izumi.fundamentals.platform.cli.model.raw.{RawRoleParams, RequiredRoles} import izumi.fundamentals.platform.cli.model.schema.ParserDef import izumi.fundamentals.platform.functional.Identity import izumi.fundamentals.platform.resources.IzArtifactMaterializer -import izumi.logstage.distage.{LogIO2Module, LogIO3Module} -import izumi.reflect.{TagK, TagK3, TagKK} +import izumi.logstage.distage.LogIO2Module +import izumi.reflect.{TagK, TagKK} import scala.annotation.unused @@ -183,7 +183,7 @@ abstract class RoleAppMain[F[_]]( object RoleAppMain { - abstract class LauncherBIO2[F[+_, +_]: TagKK: Async2: DefaultModule2](implicit artifact: IzArtifactMaterializer) extends RoleAppMain[F[Throwable, _]] { + abstract class LauncherBIO[F[+_, +_]: TagKK: Async2: DefaultModule2](implicit artifact: IzArtifactMaterializer) extends RoleAppMain[F[Throwable, _]] { override protected def shutdownStrategy: AppShutdownStrategy[F[Throwable, _]] = new BIOShutdownStrategy[F] // add LogIO2[F] for bifunctor convenience to match existing LogIO[F[Throwable, _]] @@ -192,15 +192,6 @@ object RoleAppMain { } } - abstract class LauncherBIO3[F[-_, +_, +_]: TagK3: Async3: DefaultModule3](implicit artifact: IzArtifactMaterializer) extends RoleAppMain[F[Any, Throwable, _]] { - override protected def shutdownStrategy: AppShutdownStrategy[F[Any, Throwable, _]] = new BIOShutdownStrategy[F[Any, +_, +_]] - - // add LogIO2[F] for trifunctor convenience to match existing LogIO[F[Throwable, _]] - override protected def roleAppBootOverrides(argv: ArgV): Module = super.roleAppBootOverrides(argv) ++ new ModuleDef { - modify[ModuleProvider](_.mapApp(LogIO3Module[F]() +: _)) - } - } - abstract class LauncherCats[F[_]: TagK: Async: DefaultModule](implicit artifact: IzArtifactMaterializer) extends RoleAppMain[F] { override protected def shutdownStrategy: AppShutdownStrategy[F] = new CatsEffectIOShutdownStrategy } diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/bundled/ConfigWriter.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/bundled/ConfigWriter.scala index 4500bca660..e184a95a94 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/bundled/ConfigWriter.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/bundled/ConfigWriter.scala @@ -1,19 +1,19 @@ package izumi.distage.roles.bundled import com.typesafe.config.{Config, ConfigFactory, ConfigRenderOptions} +import distage.config.AppConfig +import distage.{BootstrapModuleDef, Plan} import izumi.distage.config.model.ConfTag -import izumi.distage.framework.services.RoleAppPlanner +import izumi.distage.framework.services.{ConfigMerger, RoleAppPlanner} import izumi.distage.model.definition.Id -import izumi.functional.quasi.QuasiIO +import izumi.distage.model.plan.ExecutableOp import izumi.distage.model.plan.operations.OperationOrigin -import izumi.distage.model.plan.{ExecutableOp, Plan} -import izumi.distage.roles.bundled.ConfigWriter.{ConfigPath, ConfigurableComponent, ExtractConfigPath, WriteReference} +import izumi.distage.roles.bundled.ConfigWriter.{ConfigPath, ExtractConfigPath, WriteReference} import izumi.distage.roles.model.meta.{RoleBinding, RolesInfo} import izumi.distage.roles.model.{RoleDescriptor, RoleTask} -import izumi.functional.Value +import izumi.functional.quasi.QuasiIO import izumi.fundamentals.platform.cli.model.raw.RawEntrypointParams import izumi.fundamentals.platform.cli.model.schema.{ParserDef, RoleParserSchema} -import scala.annotation.unused import izumi.fundamentals.platform.resources.ArtifactVersion import izumi.logstage.api.IzLogger import izumi.logstage.api.logger.LogRouter @@ -21,7 +21,7 @@ import izumi.logstage.distage.LogstageModule import java.nio.charset.StandardCharsets import java.nio.file.{Files, Paths} -import scala.annotation.nowarn +import scala.annotation.{nowarn, unused} import scala.collection.compat.immutable.ArraySeq import scala.util.Try @@ -30,6 +30,7 @@ final class ConfigWriter[F[_]]( launcherVersion: ArtifactVersion @Id("launcher-version"), roleInfo: RolesInfo, roleAppPlanner: RoleAppPlanner, + appConfig: AppConfig, F: QuasiIO[F], ) extends RoleTask[F] with BundledTask { @@ -38,6 +39,7 @@ final class ConfigWriter[F[_]]( // should've been unnecessary after https://github.com/7mind/izumi/issues/779 // but, the contents of the MainAppModule (including `"activation"` config read) are not accessible here from `RoleAppPlanner` yet... private[this] val _HackyMandatorySection = ConfigPath("activation") + private val configMerger = new ConfigMerger.ConfigMergerImpl(logger) override def start(roleParameters: RawEntrypointParams, @unused freeArgs: Vector[String]): F[Unit] = { F.maybeSuspend { @@ -56,29 +58,32 @@ final class ConfigWriter[F[_]]( logger.info(s"Going to process ${roleInfo.availableRoleBindings.size -> "roles"}") - val commonComponent = ConfigurableComponent("common", Some(launcherVersion), None) - val commonConfig = buildConfig(options, commonComponent) - - if (!options.includeCommon) { - writeConfig(options, commonComponent, None, commonConfig) - } + val index = appConfig.roles.map(c => (c.roleConfig.role, c)).toMap + assert(roleInfo.availableRoleNames == index.keySet) roleInfo.availableRoleBindings.foreach { role => - val component = ConfigurableComponent(role.descriptor.id, role.descriptor.artifact.map(_.version), Some(commonConfig)) - val refConfig = buildConfig(options, component) - val versionedComponent = if (options.useLauncherVersion) { - component.copy(version = Some(ArtifactVersion(launcherVersion.version))) - } else { - component - } - try { - writeConfig(options, versionedComponent, None, refConfig) - minimizedConfig(refConfig, role) + val roleId = role.descriptor.id + val roleVersion = if (options.useLauncherVersion) { + Some(launcherVersion.version) + } else { + role.descriptor.artifact.map(_.version).map(_.version) + } + val subLogger = logger("role" -> roleId) + val fileNameFull = outputFileName(roleId, roleVersion, options.asJson, Some("full")) + + val loaded = index(roleId) + + // TODO: mergeFilter considers system properties, we might want to AVOID that in configwriter + val mergedRoleConfig = configMerger.mergeFilter(appConfig.shared, List(loaded), _ => true, "configwriter") + writeConfig(options, fileNameFull, mergedRoleConfig, subLogger) + + minimizedConfig(mergedRoleConfig, role) .foreach { cfg => - writeConfig(options, versionedComponent, Some("minimized"), cfg) + val fileNameMinimized = outputFileName(roleId, roleVersion, options.asJson, Some("minimized")) + writeConfig(options, fileNameMinimized, cfg, subLogger) } } catch { case exception: Throwable => @@ -88,36 +93,30 @@ final class ConfigWriter[F[_]]( } } - private[this] def buildConfig(config: WriteReference, cmp: ConfigurableComponent): Config = { - val referenceConfig = s"${cmp.roleId}-reference.conf" - logger.info(s"[${cmp.roleId}] Resolving $referenceConfig... with ${config.includeCommon -> "shared sections"}") - - val reference = Value(ConfigFactory.parseResourcesAnySyntax(referenceConfig)) - .mut(cmp.parent.filter(_ => config.includeCommon))(_.withFallback(_)) - .get - .resolve() - - if (reference.isEmpty) { - logger.warn(s"[${cmp.roleId}] Reference config is empty.") - } - - val resolved = ConfigFactory - .systemProperties() - .withFallback(reference) - .resolve() + private[this] def outputFileName(service: String, version: Option[String], asJson: Boolean, suffix: Option[String]): String = { + val extension = if (asJson) "json" else "conf" + val vstr = version.getOrElse("0.0.0-UNKNOWN") + val suffixStr = suffix.fold("")("-" + _) - val filtered = cleanupEffectiveAppConfig(resolved, reference) - filtered.checkValid(reference) - filtered + s"$service$suffixStr-$vstr.$extension" } - private[this] def minimizedConfig(config: Config, role: RoleBinding): Option[Config] = { + private[this] def minimizedConfig(roleConfig: Config, role: RoleBinding): Option[Config] = { val roleDIKey = role.binding.key - val bootstrapOverride = new LogstageModule(LogRouter.nullRouter, setupStaticLogRouter = false) + val roleConfigs = appConfig.roles.map(lrc => lrc.copy(roleConfig = lrc.roleConfig.copy(active = lrc.roleConfig.role == role.descriptor.id))) + + // TODO: mergeFilter considers system properties, we might want to AVOID that in configwriter + // TODO: here we accept all the role configs regardless of them being active or not, that might resolve cross-role conflicts in unpredictable manner + val fullConfig = configMerger.mergeFilter(appConfig.shared, roleConfigs, _ => true, "configwriter") + val correctedAppConfig = appConfig.copy(config = fullConfig, roles = roleConfigs) + + val bootstrapOverride = new BootstrapModuleDef { + include(new LogstageModule(LogRouter.nullRouter, setupStaticLogRouter = false)) + } val plans = roleAppPlanner - .reboot(bootstrapOverride) + .reboot(bootstrapOverride, Some(correctedAppConfig)) .makePlan(Set(roleDIKey)) def getConfig(plan: Plan): Iterator[ConfigPath] = { @@ -130,46 +129,70 @@ final class ConfigWriter[F[_]]( getConfig(plans.app).toSet + _HackyMandatorySection if (plans.app.stepsUnordered.exists(_.target == roleDIKey)) { - Some(ConfigWriter.minimized(resolvedConfig, config)) + Some(ConfigWriter.minimized(resolvedConfig, roleConfig)) } else { logger.warn(s"$roleDIKey is not in the refined plan") None } } - private[this] def writeConfig(config: WriteReference, cmp: ConfigurableComponent, suffix: Option[String], typesafeConfig: Config): Try[Unit] = { - val fileName = outputFileName(cmp.roleId, cmp.version, config.asJson, suffix) - val target = Paths.get(config.targetDir, fileName) - + // private[this] def buildConfig(config: WriteReference, cmp: ConfigurableComponent): Config = { + // val referenceConfig = s"${cmp.roleId}-reference.conf" + // logger.info(s"[${cmp.roleId}] Resolving $referenceConfig... with ${config.includeCommon -> "shared sections"}") + // + // val reference = Value(ConfigFactory.parseResourcesAnySyntax(referenceConfig)) + // .mut(cmp.parent.filter(_ => config.includeCommon))(_.withFallback(_)) + // .get + // .resolve() + // + // if (reference.isEmpty) { + // logger.warn(s"[${cmp.roleId}] Reference config is empty.") + // } + // + // val resolved = ConfigFactory + // .systemProperties() + // .withFallback(reference) + // .resolve() + // + // val filtered = cleanupEffectiveAppConfig(resolved, reference) + // filtered.checkValid(reference) + // filtered + // } + // + // + // + // // TODO: sdk? + // @nowarn("msg=Unused import") + // private[this] def cleanupEffectiveAppConfig(effectiveAppConfig: Config, reference: Config): Config = { + // import scala.collection.compat._ + // import scala.jdk.CollectionConverters._ + // + // ConfigFactory.parseMap(effectiveAppConfig.root().unwrapped().asScala.view.filterKeys(reference.hasPath).toMap.asJava) + // } + // + // private[this] def outputFileName(service: String, version: Option[ArtifactVersion], asJson: Boolean, suffix: Option[String]): String = { + // val extension = if (asJson) "json" else "conf" + // val vstr = version.map(_.version).getOrElse("0.0.0-UNKNOWN") + // val suffixStr = suffix.fold("")("-" + _) + // + // s"$service$suffixStr-$vstr.$extension" + // } + // + + private[this] def writeConfig(options: WriteReference, fileName: String, typesafeConfig: Config, subLogger: IzLogger): Try[Unit] = { + val configRenderOptions = ConfigRenderOptions.defaults.setOriginComments(false).setComments(false) + + val target = Paths.get(options.targetDir, fileName) Try { - val cfg = typesafeConfig.root().render(configRenderOptions.setJson(config.asJson)) + val cfg = typesafeConfig.root().render(configRenderOptions.setJson(options.asJson)) val bytes = cfg.getBytes(StandardCharsets.UTF_8) Files.write(target, bytes) - logger.info(s"[${cmp.roleId}] Reference config saved -> $target (${bytes.size} bytes)") + subLogger.info(s"Reference config saved -> $target (${bytes.size} bytes)") }.recover { case error: Throwable => - logger.error(s"[${cmp.roleId -> "component id" -> null}] Can't write reference config to $target, $error") + subLogger.error(s"Can't write reference config to $target, $error") } } - - // TODO: sdk? - @nowarn("msg=Unused import") - private[this] def cleanupEffectiveAppConfig(effectiveAppConfig: Config, reference: Config): Config = { - import scala.collection.compat._ - import scala.jdk.CollectionConverters._ - - ConfigFactory.parseMap(effectiveAppConfig.root().unwrapped().asScala.view.filterKeys(reference.hasPath).toMap.asJava) - } - - private[this] def outputFileName(service: String, version: Option[ArtifactVersion], asJson: Boolean, suffix: Option[String]): String = { - val extension = if (asJson) "json" else "conf" - val vstr = version.map(_.version).getOrElse("0.0.0-UNKNOWN") - val suffixStr = suffix.fold("")("-" + _) - - s"$service$suffixStr-$vstr.$extension" - } - - private[this] final val configRenderOptions = ConfigRenderOptions.defaults.setOriginComments(false).setComments(false) } object ConfigWriter extends RoleDescriptor { @@ -220,8 +243,8 @@ object ConfigWriter extends RoleDescriptor { @nowarn("msg=Unused import") def minimized(requiredPaths: Set[ConfigPath], source: Config): Config = { - import scala.collection.compat._ - import scala.jdk.CollectionConverters._ + import scala.collection.compat.* + import scala.jdk.CollectionConverters.* val paths = requiredPaths.map(_.toPath) diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/ActivationParser.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/ActivationParser.scala index d87a6b06e7..fe81b75cf9 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/ActivationParser.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/ActivationParser.scala @@ -20,7 +20,6 @@ object ActivationParser { class Impl( parser: RoleAppActivationParser, parameters: RawAppArgs, - config: AppConfig, activationInfo: ActivationInfo, defaultActivations: Activation @Id("default"), additionalActivations: Activation @Id("additional"), @@ -28,10 +27,11 @@ object ActivationParser { warnUnsetActivations: Boolean @Id("distage.roles.activation.warn-unset"), ) extends ActivationParser { - def parseActivation(): Activation = { + def parseActivation(config: AppConfig): Activation = { val cmdChoices = parameters.globalParameters.findValues(RoleAppMain.Options.use).map(AxisPoint parseAxisPoint _.value) val cmdActivations = parser.parseActivation(cmdChoices, activationInfo) + // println(s"PARSEACT: ${config.config}") val configChoices = if (config.config.hasPath(configActivationSection)) { ActivationConfig.diConfigReader.decodeConfig(configActivationSection)(config.config).activation.map(AxisPoint(_)) } else Iterable.empty diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/LateLoggerFactory.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/LateLoggerFactory.scala index f6787e8948..c0447cfb86 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/LateLoggerFactory.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/LateLoggerFactory.scala @@ -18,7 +18,7 @@ object LateLoggerFactory { ) extends LateLoggerFactory { def makeLateLogRouter(config: DeclarativeLoggerConfig): Lifecycle[Identity, LogRouter] = { for { - router <- Lifecycle.pure(routerFactory.createRouter(config, buffer)) + router <- Lifecycle.liftF[Identity, LogRouter](routerFactory.createRouter(config, buffer)) _ <- Lifecycle.make[Identity, Unit](StaticLogRouter.instance.setup(router))(_ => ()) _ <- Lifecycle .make[Identity, Option[AutoCloseable]] { diff --git a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/PreparedAppSyntax.scala b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/PreparedAppSyntax.scala index b95c9463cd..978018b661 100644 --- a/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/PreparedAppSyntax.scala +++ b/distage/distage-framework/.jvm/src/main/scala/izumi/distage/roles/launcher/PreparedAppSyntax.scala @@ -3,7 +3,7 @@ package izumi.distage.roles.launcher trait PreparedAppSyntax { implicit class PreparedAppSyntaxImpl[F[_]](app: PreparedApp[F]) { def run(): Unit = { - app.runner.run(app.appResource.use(_ => app.effect.unit)(app.effect)) + app.runner.run(app.appResource.use(l => app.roleAppEntrypoint.runTasksAndRoles(l, app.effect))(app.effect)) } } } diff --git a/distage/distage-framework/.jvm/src/test/resources/statictestrole-reference.conf b/distage/distage-framework/.jvm/src/test/resources/statictestrole-reference.conf new file mode 100644 index 0000000000..b8c73c6042 --- /dev/null +++ b/distage/distage-framework/.jvm/src/test/resources/statictestrole-reference.conf @@ -0,0 +1 @@ +somekey = 1 \ No newline at end of file diff --git a/distage/distage-framework/.jvm/src/test/resources/testrole00-reference.conf b/distage/distage-framework/.jvm/src/test/resources/testrole00-reference.conf index eee0f23864..ce0f29d394 100644 --- a/distage/distage-framework/.jvm/src/test/resources/testrole00-reference.conf +++ b/distage/distage-framework/.jvm/src/test/resources/testrole00-reference.conf @@ -5,7 +5,7 @@ testservice2 { testservice { intval: 123 strval: "xxx" - overridenInt: 111 + overridenInt: 555 systemPropInt: 222 systemPropList: [1, 2, 3] diff --git a/distage/distage-framework/.jvm/src/test/resources/testrole05-reference.conf b/distage/distage-framework/.jvm/src/test/resources/testrole05-reference.conf new file mode 100644 index 0000000000..b4aabb760b --- /dev/null +++ b/distage/distage-framework/.jvm/src/test/resources/testrole05-reference.conf @@ -0,0 +1,3 @@ +activation { + role05localaxis = rolelocal1 +} diff --git a/distage/distage-framework/.jvm/src/test/scala/com/github/pshirshov/test/plugins/StaticTestMain.scala b/distage/distage-framework/.jvm/src/test/scala/com/github/pshirshov/test/plugins/StaticTestMain.scala index 507b9fd443..fdf0f94465 100644 --- a/distage/distage-framework/.jvm/src/test/scala/com/github/pshirshov/test/plugins/StaticTestMain.scala +++ b/distage/distage-framework/.jvm/src/test/scala/com/github/pshirshov/test/plugins/StaticTestMain.scala @@ -1,13 +1,15 @@ package com.github.pshirshov.test.plugins import com.github.pshirshov.test.plugins.StaticTestMain.staticTestMainPlugin -import distage.{ClassConstructor, TagK} -import izumi.functional.quasi.QuasiApplicative +import distage.{ClassConstructor, ModuleDef, TagK} +import izumi.distage.model.definition.Module import izumi.distage.modules.DefaultModule2 import izumi.distage.plugins.{PluginConfig, PluginDef} import izumi.distage.roles.RoleAppMain +import izumi.distage.roles.RoleAppMain.ArgV import izumi.distage.roles.model.definition.RoleModuleDef import izumi.functional.bio.Async2 +import izumi.functional.quasi.QuasiApplicative import izumi.fundamentals.platform.functional.Identity import izumi.reflect.TagKK import logstage.LogIO2 @@ -28,7 +30,12 @@ object StaticTestMainBadEffect extends RoleAppMain.LauncherIdentity { override protected def pluginConfig: PluginConfig = PluginConfig.cached("com.github.pshirshov.test.plugins") ++ staticTestMainPlugin[Identity, cats.effect.IO] } -class StaticTestMainLogIO2[F[+_, +_]: TagKK: Async2: DefaultModule2] extends RoleAppMain.LauncherBIO2[F] { +class StaticTestMainLogIO2[F[+_, +_]: TagKK: Async2: DefaultModule2] extends RoleAppMain.LauncherBIO[F] { + + override protected def roleAppBootOverrides(argv: ArgV): Module = super.roleAppBootOverrides(argv) ++ new ModuleDef { + make[Boolean].named("distage.roles.always-include-reference-role-configs").fromValue(true) + } + override protected def pluginConfig: PluginConfig = PluginConfig .cached("com.github.pshirshov.test.plugins") diff --git a/distage/distage-framework/.jvm/src/test/scala/com/github/pshirshov/test4/Fixture4.scala b/distage/distage-framework/.jvm/src/test/scala/com/github/pshirshov/test4/Fixture4.scala new file mode 100644 index 0000000000..50ef82453c --- /dev/null +++ b/distage/distage-framework/.jvm/src/test/scala/com/github/pshirshov/test4/Fixture4.scala @@ -0,0 +1,73 @@ +package com.github.pshirshov.test4 + +import distage.Mode +import izumi.distage.Subcontext +import izumi.distage.model.definition.{Lifecycle, ModuleDef} +import izumi.distage.plugins.{PluginConfig, PluginDef} +import izumi.distage.roles.RoleAppMain +import izumi.distage.roles.model.definition.RoleModuleDef +import izumi.distage.roles.model.{RoleDescriptor, RoleService} +import izumi.fundamentals.platform.cli.model.raw.RawEntrypointParams +import izumi.fundamentals.platform.functional.Identity + +object Fixture4 { + + object TestMainBad extends RoleAppMain.LauncherIdentity { + override protected def pluginConfig: PluginConfig = PluginConfig.const(BadModule) + } + + object TestMainGood extends RoleAppMain.LauncherIdentity { + override protected def pluginConfig: PluginConfig = PluginConfig.const(GoodModule) + } + + object BadModule extends PluginDef with RoleModuleDef { + makeSubcontext[Dep]( + new ModuleDef { + make[Dep].tagged(Mode.Prod).from[DepGood] + make[Dep].tagged(Mode.Test).from[DepBad] + } + ) + + makeRole[TargetRole] + } + + object GoodModule extends PluginDef with RoleModuleDef { + makeSubcontext[Dep]( + new ModuleDef { + make[Dep].tagged(Mode.Prod).from[DepGood] + make[Dep].tagged(Mode.Test).from[DepBad] + } + ).localDependency[MissingDep] + + makeRole[TargetRole] + } + + class TargetRole( + val depCtx: Subcontext[Dep] + ) extends RoleService[Identity] { + def mkDep(): Dep = { + depCtx + .provide[MissingDep](new MissingDep {}) + .produceRun(identity) + } + + override def start(roleParameters: RawEntrypointParams, freeArgs: Vector[String]): Lifecycle[Identity, Unit] = { + Lifecycle.makeSimple(mkDep())(_ => ()).void + } + } + + object TargetRole extends RoleDescriptor { + final val id = "target" + } + + trait Dep + + class DepGood extends Dep + + class DepBad( + val missingDep: MissingDep + ) extends Dep + + trait MissingDep + +} diff --git a/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/ExitLatchTestEntrypoint.scala b/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/ExitLatchTestEntrypoint.scala index 6daed32c21..6a9d44d0ae 100644 --- a/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/ExitLatchTestEntrypoint.scala +++ b/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/ExitLatchTestEntrypoint.scala @@ -8,7 +8,7 @@ import izumi.fundamentals.platform.cli.model.raw.RawRoleParams class TestPluginZIO extends TestPluginBase[zio.IO[Throwable, _]] -class ExitLatchEntrypointBase extends RoleAppMain.LauncherBIO2[zio.IO] { +class ExitLatchEntrypointBase extends RoleAppMain.LauncherBIO[zio.IO] { /** Roles always enabled in this [[RoleAppMain]] */ override protected def requiredRoles(argv: RoleAppMain.ArgV): Vector[RawRoleParams] = Vector(RawRoleParams(ExitAfterSleepRole.id)) diff --git a/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/RoleAppTest.scala b/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/RoleAppTest.scala index 745c5bbec7..665ecf1e46 100644 --- a/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/RoleAppTest.scala +++ b/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/RoleAppTest.scala @@ -5,6 +5,7 @@ import cats.effect.unsafe.IORuntime import com.github.pshirshov.test.plugins.{StaticTestMainLogIO2, StaticTestRole} import com.github.pshirshov.test3.plugins.Fixture3 import com.typesafe.config.ConfigFactory +import distage.config.AppConfig import distage.plugins.{PluginBase, PluginDef} import distage.{DIKey, Injector, Locator, LocatorRef} import izumi.distage.framework.config.PlanningOptions @@ -15,6 +16,7 @@ import izumi.distage.model.definition.{Activation, BootstrapModule, Lifecycle} import izumi.distage.modules.DefaultModule import izumi.distage.plugins.PluginConfig import izumi.distage.roles.DebugProperties +import izumi.distage.roles.launcher.ActivationParser import izumi.distage.roles.test.fixtures.* import izumi.distage.roles.test.fixtures.Fixture.* import izumi.distage.roles.test.fixtures.roles.TestRole00 @@ -179,6 +181,9 @@ class RoleAppTest extends AnyWordSpec with WithProperties { bsModule = BootstrapModule.empty, bootloader = Injector.bootloader[Identity](BootstrapModule.empty, Activation.empty, DefaultModule.empty, PlannerInput(definition, Activation.empty, roots)), logger = logger, + parser = new ActivationParser { + override def parseActivation(config: AppConfig): Activation = ??? + }, ) val plans = roleAppPlanner.makePlan(roots) @@ -216,6 +221,9 @@ class RoleAppTest extends AnyWordSpec with WithProperties { bsModule = BootstrapModule.empty, bootloader = Injector.bootloader[Identity](BootstrapModule.empty, Activation.empty, DefaultModule.empty, PlannerInput(definition, Activation.empty, roots)), logger = logger, + parser = new ActivationParser { + override def parseActivation(config: AppConfig): Activation = ??? + }, ) val plans = roleAppPlanner.makePlan(roots) @@ -257,6 +265,9 @@ class RoleAppTest extends AnyWordSpec with WithProperties { bsModule = BootstrapModule.empty, bootloader = Injector.bootloader[Identity](BootstrapModule.empty, Activation.empty, DefaultModule.empty, PlannerInput(definition, Activation.empty, roots)), logger = logger, + parser = new ActivationParser { + override def parseActivation(config: AppConfig): Activation = ??? + }, ) val plans = roleAppPlanner.makePlan(roots) @@ -289,14 +300,14 @@ class RoleAppTest extends AnyWordSpec with WithProperties { TestEntrypoint.main(Array("-ll", logLevel, "-u", "axiscomponentaxis:incorrect", ":configwriter", "-t", targetPath)) } - val cwCfg = cfg("configwriter", version) + val cwCfg = cfg("configwriter-full", version) val cwCfgMin = cfg("configwriter-minimized", version) assert(cwCfg.exists(), s"$cwCfg exists") assert(cwCfgMin.exists(), s"$cwCfgMin exists") assert(cwCfg.length() > cwCfgMin.length()) - val role0Cfg = cfg("testrole00", version) + val role0Cfg = cfg("testrole00-full", version) val role0CfgMin = cfg("testrole00-minimized", version) assert(role0Cfg.exists(), s"$role0Cfg exists") @@ -316,11 +327,13 @@ class RoleAppTest extends AnyWordSpec with WithProperties { assert(role0CfgMinParsed.hasPath("testservice")) assert(role0CfgMinParsed.getString("testservice2.strval") == "xxx") - assert(role0CfgMinParsed.getString("testservice.overridenInt") == "111") - assert(role0CfgMinParsed.getInt("testservice.systemPropInt") == 265) - assert(role0CfgMinParsed.getList("testservice.systemPropList").unwrapped().asScala.toList == List("111", "222")) + assert(role0CfgMinParsed.getString("testservice.overridenInt") == "555") - val role4Cfg = cfg("testrole04", version) + // ConfigWriter DOES NOT consider system properties! + assert(role0CfgMinParsed.getInt("testservice.systemPropInt") == 222) + assert(role0CfgMinParsed.getList("testservice.systemPropList").unwrapped().asScala.toList == List(1, 2, 3)) + + val role4Cfg = cfg("testrole04-full", version) val role4CfgMin = cfg("testrole04-minimized", version) assert(role4Cfg.exists(), s"$role4Cfg exists") @@ -387,9 +400,10 @@ class RoleAppTest extends AnyWordSpec with WithProperties { DebugProperties.`izumi.distage.roles.activation.ignore-unknown`.name -> "true", DebugProperties.`izumi.distage.roles.activation.warn-unset`.name -> "false", ) { - val checkTestGoodResouce = getClass.getResource("/check-test-good.conf").getPath - new StaticTestMainLogIO2[zio.IO].main(Array("-ll", logLevel, "-c", checkTestGoodResouce, ":" + StaticTestRole.id)) -// new StaticTestMainLogIO2[monix.bio.IO].main(Array("-ll", logLevel, "-c", checkTestGoodResouce, ":" + StaticTestRole.id)) + val checkTestGoodRes = getClass.getResource("/check-test-good.conf").getPath + val customRoleConfigRes = getClass.getResource("/custom-role.conf").getPath + new StaticTestMainLogIO2[zio.IO].main(Array("-ll", logLevel, "-c", checkTestGoodRes, ":" + StaticTestRole.id, "-c", customRoleConfigRes)) +// new StaticTestMainLogIO2[monix.bio.IO].main(Array("-ll", logLevel, "-c", checkTestGoodRes, ":" + StaticTestRole.id)) } } } diff --git a/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestPlugin.scala b/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestPlugin.scala index 7958a3c28b..72a254728a 100644 --- a/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestPlugin.scala +++ b/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestPlugin.scala @@ -42,6 +42,7 @@ class TestPluginBase[F[_]: TagK] extends PluginDef with ConfigModuleDef with Rol makeRole[TestRole03[F]] makeRole[TestRole04[F]] + include(new TestRole05.Role05Module[F]) makeRole[FailingRole01[F]] makeRole[FailingRole02[F]] diff --git a/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestRole00.scala b/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestRole00.scala index f18aed3236..ea39967aef 100644 --- a/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestRole00.scala +++ b/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestRole00.scala @@ -51,7 +51,7 @@ object roles { override def start(roleParameters: RawEntrypointParams, freeArgs: Vector[String]): Lifecycle[F, Unit] = Lifecycle.make(QuasiIO[F].maybeSuspend { logger.info(s"[TestRole00] started: $roleParameters, $freeArgs, $dummies, $conflict") - assert(conf.overridenInt == 111) + assert(conf.overridenInt == 555, s"Common value is 111, role-specific value is 555, found ${conf.overridenInt}") }) { _ => QuasiIO[F].maybeSuspend { diff --git a/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestRole05.scala b/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestRole05.scala new file mode 100644 index 0000000000..1cee8662d5 --- /dev/null +++ b/distage/distage-framework/.jvm/src/test/scala/izumi/distage/roles/test/fixtures/TestRole05.scala @@ -0,0 +1,45 @@ +package izumi.distage.roles.test.fixtures + +import distage.{Axis, ModuleDef} +import izumi.distage.config.ConfigModuleDef +import izumi.distage.model.definition.Lifecycle +import izumi.distage.roles.model.{RoleDescriptor, RoleService} +import izumi.distage.roles.model.definition.RoleModuleDef +import izumi.distage.roles.test.fixtures.TestRole05.{TestRole05Dependency, TestRole05DependencyImpl1} +import izumi.functional.quasi.QuasiIO +import izumi.fundamentals.platform.cli.model.raw.RawEntrypointParams +import izumi.reflect.TagK + +class TestRole05[F[_] : QuasiIO]( + dependency: TestRole05Dependency + ) extends RoleService[F] { + override def start(roleParameters: RawEntrypointParams, freeArgs: Vector[String]): Lifecycle[F, Unit] = Lifecycle.make(QuasiIO[F].maybeSuspend { + assert(dependency.isInstanceOf[TestRole05DependencyImpl1]) + }) { + _ => + QuasiIO[F].unit + } +} + +object TestRole05 extends RoleDescriptor { + override final val id = "testrole05" + + trait TestRole05Dependency + + class TestRole05DependencyImpl1 extends TestRole05Dependency + + class TestRole05DependencyImpl2 extends TestRole05Dependency + + object Role05LocalAxis extends Axis { + case object Rolelocal2 extends AxisChoiceDef + + case object Rolelocal1 extends AxisChoiceDef + } + + class Role05Module[F[_] : TagK] extends ModuleDef with ConfigModuleDef with RoleModuleDef { + makeRole[TestRole05[F]] + make[TestRole05Dependency].from[TestRole05DependencyImpl1].tagged(Role05LocalAxis.Rolelocal1) + make[TestRole05Dependency].from[TestRole05DependencyImpl2].tagged(Role05LocalAxis.Rolelocal2) + } + +} diff --git a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/AbstractConfigLoader.scala b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/AbstractConfigLoader.scala index 7ed54d1426..5b83530765 100644 --- a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/AbstractConfigLoader.scala +++ b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/AbstractConfigLoader.scala @@ -3,7 +3,7 @@ package izumi.distage.framework.services import izumi.distage.config.model.AppConfig trait AbstractConfigLoader { - def loadConfig(): AppConfig + def loadConfig(clue: String): AppConfig - final def map(f: AppConfig => AppConfig): ConfigLoader = () => f(loadConfig()) + final def map(f: AppConfig => AppConfig): ConfigLoader = (clue: String) => f(loadConfig(clue)) } 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 d1cea0ca2e..120de4ca41 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 @@ -1,20 +1,22 @@ package izumi.distage.framework.services -import distage.{Injector, PlannerInput} +import distage.config.{AppConfig, AppConfigModule} +import distage.{BootstrapModuleDef, Injector, PlannerInput} import izumi.distage.framework.config.PlanningOptions import izumi.distage.framework.services.RoleAppPlanner.AppStartupPlans -import izumi.distage.model.definition.{Activation, BootstrapModule, Id} -import izumi.functional.quasi.{QuasiAsync, QuasiIO, QuasiIORunner} +import izumi.distage.model.definition.{Activation, BootstrapModule, Id, ModuleBase} import izumi.distage.model.plan.{Plan, Roots} -import izumi.distage.model.recursive.{BootConfig, Bootloader, BootstrappedApp} +import izumi.distage.model.recursive.{BootConfig, Bootloader} import izumi.distage.model.reflection.DIKey import izumi.distage.modules.DefaultModule +import izumi.distage.roles.launcher.ActivationParser +import izumi.functional.quasi.{QuasiAsync, QuasiIO, QuasiIORunner} import izumi.fundamentals.platform.functional.Identity import izumi.logstage.api.IzLogger import izumi.reflect.TagK trait RoleAppPlanner { - def reboot(bsModule: BootstrapModule): RoleAppPlanner + def reboot(bsModule: BootstrapModule, config: Option[AppConfig]): RoleAppPlanner def makePlan(appMainRoots: Set[DIKey]): AppStartupPlans } @@ -32,6 +34,7 @@ object RoleAppPlanner { bsModule: BootstrapModule @Id("roleapp"), bootloader: Bootloader @Id("roleapp"), logger: IzLogger, + parser: ActivationParser, )(implicit defaultModule: DefaultModule[F] ) extends RoleAppPlanner { self => @@ -42,30 +45,37 @@ object RoleAppPlanner { DIKey.get[QuasiAsync[F]], ) - override def reboot(bsOverride: BootstrapModule): RoleAppPlanner = { - new RoleAppPlanner.Impl[F](options, activation, bsModule overriddenBy bsOverride, bootloader, logger) + override def reboot(bsOverride: BootstrapModule, config: Option[AppConfig]): RoleAppPlanner = { + val configOverride = new BootstrapModuleDef { + config.foreach(cfg => include(AppConfigModule(cfg))) + } + val updatedBsModule = bsModule overriddenBy bsOverride overriddenBy configOverride + + val activation = config.map(parser.parseActivation).getOrElse(this.activation) + + new RoleAppPlanner.Impl[F](options, activation, updatedBsModule, bootloader, logger, parser) } override def makePlan(appMainRoots: Set[DIKey]): AppStartupPlans = { logger.trace(s"Application will use: ${appMainRoots -> "app roots"} and $activation") - // TODO: why .module doesn't work within for-comprehension?.. - def log(runtimeBsApp: BootstrappedApp): Either[Nothing, Unit] = Right { - logger.trace(s"Bootstrap plan:\n${runtimeBsApp.plan.render() -> "bootstrap dump" -> null}") - logger.trace(s"App module: ${runtimeBsApp.module -> "app module" -> null}") - } - val out = for { bootstrapped <- bootloader.boot( BootConfig( - bootstrap = _ => bsModule, + bootstrap = _ => + bsModule overriddenBy new BootstrapModuleDef { + make[RoleAppPlanner].fromValue(self) + }, activation = _ => activation, roots = _ => Roots(runtimeGcRoots), ) ) runtimeKeys = bootstrapped.plan.keys - _ <- log(bootstrapped) + _ <- Right { + logger.trace(s"Bootstrap plan:\n${bootstrapped.plan.render() -> "bootstrap dump" -> null}") + logger.trace(s"App module: ${(bootstrapped.module: ModuleBase) -> "app module" -> null}") + } appPlan <- bootstrapped.injector.plan(PlannerInput(bootstrapped.module.drop(runtimeKeys), activation, appMainRoots)) } yield { diff --git a/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/AbstractActivationParser.scala b/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/AbstractActivationParser.scala index 1e00fa9dd7..8d5dd1b96a 100644 --- a/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/AbstractActivationParser.scala +++ b/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/AbstractActivationParser.scala @@ -1,7 +1,8 @@ package izumi.distage.roles.launcher +import distage.config.AppConfig import izumi.distage.model.definition.Activation trait AbstractActivationParser { - def parseActivation(): Activation + def parseActivation(config: AppConfig): Activation } diff --git a/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/AppResourceProvider.scala b/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/AppResourceProvider.scala index 36f9808863..e7ffce9541 100644 --- a/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/AppResourceProvider.scala +++ b/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/AppResourceProvider.scala @@ -43,7 +43,7 @@ object AppResourceProvider { val runner = runtimeLocator.get[QuasiIORunner[F]] val F = runtimeLocator.get[QuasiIO[F]] - PreparedApp(prepareMainResource(runtimeLocator)(F), runner, F) + PreparedApp(prepareMainResource(runtimeLocator)(F), entrypoint, runner, F) } } @@ -51,7 +51,6 @@ object AppResourceProvider { injectorFactory .inherit(runtimeLocator) .produceFX[F](appPlan.app, filters.filterF) - .evalTap(entrypoint.runTasksAndRoles(_, F)) .wrapRelease((r, a) => r(a).guarantee(F.maybeSuspend(hook.finishShutdown()))) } } diff --git a/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/PreparedApp.scala b/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/PreparedApp.scala index e28d6037fc..5bdf4602ec 100644 --- a/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/PreparedApp.scala +++ b/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/PreparedApp.scala @@ -6,6 +6,7 @@ import izumi.functional.quasi.{QuasiIO, QuasiIORunner} final case class PreparedApp[F[_]]( appResource: Lifecycle[F, Locator], + roleAppEntrypoint: RoleAppEntrypoint[F], runner: QuasiIORunner[F], effect: QuasiIO[F], ) diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/model/TestConfig.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/model/TestConfig.scala index c5892b95c2..52c1e6a46e 100644 --- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/model/TestConfig.scala +++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/model/TestConfig.scala @@ -81,7 +81,7 @@ final case class TestConfig( parallelSuites: Parallelism = Parallelism.Unlimited, parallelTests: Parallelism = Parallelism.Unlimited, // other options - configBaseName: String, + configBaseName: String = "test", configOverrides: Option[AppConfig] = None, bootstrapFactory: BootstrapFactory = BootstrapFactory.Impl, planningOptions: PlanningOptions = PlanningOptions(), @@ -96,8 +96,10 @@ final case class TestConfig( } object TestConfig { + @deprecated("Use TestConfig() constructor instead, always provide pluginConfig explicitly", "1.2.3") def forSuite(clazz: Class[?]): TestConfig = { val packageName = clazz.getPackage.getName + TestConfig( pluginConfig = PluginConfig.cached(Seq(packageName)), configBaseName = s"${packageName.split('.').last}-test", diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/TestPlanner.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/TestPlanner.scala index cb3fc01a30..10c8ed8edb 100644 --- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/TestPlanner.scala +++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/TestPlanner.scala @@ -212,14 +212,13 @@ class TestPlanner[F[_]: TagK: DefaultModule]( val activationParser = new ActivationParser.Impl( roleAppActivationParser, RawAppArgs.empty, - config, env.activationInfo, env.activation, Activation.empty, lateLogger, warnUnset, ) - val configActivation = activationParser.parseActivation() + val configActivation = activationParser.parseActivation(config) configActivation ++ env.activation } @@ -278,17 +277,23 @@ class TestPlanner[F[_]: TagK: DefaultModule]( reducedAppModule = appModule.drop(runtimeKeys) // produce plan for each test - testPlans <- tests.map { - distageTest => - val forcedRoots = env.forcedRoots.getActiveKeys(fullActivation) - val testRoots = distageTest.test.get.diKeys.toSet ++ forcedRoots - for { - plan <- if (testRoots.nonEmpty) injector.plan(PlannerInput(reducedAppModule, fullActivation, testRoots)) else Right(Plan.empty) - _ <- Right(planChecker.showProxyWarnings(plan)) - } yield { - AlmostPreparedTest(distageTest, reducedAppModule, plan.keys, fullActivation) - } - }.biSequence + testPlans <- tests + .groupBy { + distageTest => + val forcedRoots = env.forcedRoots.getActiveKeys(fullActivation) + val testRoots = forcedRoots ++ distageTest.test.get.diKeys + testRoots + } + .toSeq + .map { + case (testRoots, distageTests) => + for { + plan <- if (testRoots.nonEmpty) injector.plan(PlannerInput(reducedAppModule, fullActivation, testRoots)) else Right(Plan.empty) + _ <- Right(planChecker.showProxyWarnings(plan)) + } yield { + distageTests.map(AlmostPreparedTest(_, reducedAppModule, plan.keys, fullActivation)) + } + }.biFlatten envKeys = testPlans.flatMap(_.targetKeys).toSet // we need to "strengthen" all _memoized_ weak set instances that occur in our tests to ensure that they diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/services/BootstrapFactory.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/services/BootstrapFactory.scala index f9591efd89..b1d81fccc2 100644 --- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/services/BootstrapFactory.scala +++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/services/BootstrapFactory.scala @@ -1,10 +1,11 @@ package izumi.distage.testkit.runner.impl.services import distage.config.AppConfig +import izumi.distage.config.model.{GenericConfigSource, RoleConfig} import izumi.distage.framework.config.PlanningOptions import izumi.distage.framework.model.ActivationInfo -import izumi.distage.framework.services.ConfigLoader.ConfigLocation -import izumi.distage.framework.services.{ConfigLoader, ModuleProvider} +import izumi.distage.framework.services.ConfigMerger.ConfigMergerImpl +import izumi.distage.framework.services.{ConfigArgsProvider, ConfigLoader, ConfigLocationProvider, ModuleProvider} import izumi.distage.model.definition.Activation import izumi.distage.roles.launcher.AppShutdownInitiator import izumi.distage.roles.model.meta.RolesInfo @@ -13,6 +14,10 @@ import izumi.logstage.api.IzLogger import izumi.logstage.api.logger.LogRouter import izumi.reflect.TagK +/** + * The purpose of this class is to allow testkit user to override + * module loading and config loading logic by overriding [[izumi.distage.testkit.model.TestConfig]] + */ trait BootstrapFactory { def makeModuleProvider[F[_]: TagK]( options: PlanningOptions, @@ -25,17 +30,26 @@ trait BootstrapFactory { def makeConfigLoader(configBaseName: String, logger: IzLogger): ConfigLoader - protected def makeConfigLocation(configBaseName: String): ConfigLocation + protected def makeConfigLocationProvider(configBaseName: String): ConfigLocationProvider } object BootstrapFactory { object Impl extends BootstrapFactory { - override protected def makeConfigLocation(configBaseName: String): ConfigLocation = { - ConfigLocation.Default + override protected def makeConfigLocationProvider(configBaseName: String): ConfigLocationProvider = { + ConfigLocationProvider.Default } override def makeConfigLoader(configBaseName: String, logger: IzLogger): ConfigLoader = { - new ConfigLoader.LocalFSImpl(logger, makeConfigLocation(configBaseName), ConfigLoader.Args(None, Map(configBaseName -> None))) + val argsProvider = ConfigArgsProvider.const( + ConfigLoader.Args( + None, + List(RoleConfig(configBaseName, active = true, GenericConfigSource.ConfigDefault)), + alwaysIncludeReferenceRoleConfigs = true, // we expect no user-provided role configs in tests + ) + ) + val merger = new ConfigMergerImpl(logger) + val locationProvider = makeConfigLocationProvider(configBaseName) + new ConfigLoader.LocalFSImpl(logger, merger, locationProvider, argsProvider) } override def makeModuleProvider[F[_]: TagK]( diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/services/TestConfigLoader.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/services/TestConfigLoader.scala index 71280516d4..e5b9158fc9 100644 --- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/services/TestConfigLoader.scala +++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/runner/impl/services/TestConfigLoader.scala @@ -26,12 +26,12 @@ object TestConfigLoader { appConfig => env.configOverrides match { case Some(overrides) => - AppConfig(overrides.config.withFallback(appConfig.config).resolve()) + AppConfig.provided(overrides.config.withFallback(appConfig.config).resolve()) case None => appConfig } } - configLoader.loadConfig() + configLoader.loadConfig("testkit startup") }, ) } diff --git a/distage/distage-testkit-scalatest/src/main/scala-2/izumi/distage/testkit/scalatest/AssertIO3.scala b/distage/distage-testkit-scalatest/src/main/scala-2/izumi/distage/testkit/scalatest/AssertIO3.scala deleted file mode 100644 index 2d3e2f2401..0000000000 --- a/distage/distage-testkit-scalatest/src/main/scala-2/izumi/distage/testkit/scalatest/AssertIO3.scala +++ /dev/null @@ -1,33 +0,0 @@ -package izumi.distage.testkit.scalatest - -import izumi.distage.testkit.scalatest.AssertIO3.AssertIO3Macro -import izumi.functional.bio.IO3 -import org.scalactic.{Prettifier, source} -import org.scalatest.Assertion -import org.scalatest.distage.DistageAssertionsMacro - -import scala.language.experimental.macros -import scala.reflect.macros.blackbox - -/** scalatest assertion macro for any [[izumi.functional.bio.IO3]] */ -trait AssertIO3[F[-_, +_, +_]] { - final def assertIO(arg: Boolean)(implicit IO3: IO3[F], prettifier: Prettifier, pos: source.Position): F[Any, Nothing, Assertion] = macro AssertIO3Macro.impl[F] -} - -object AssertIO3 { - final def assertIO[F[-_, +_, +_]](arg: Boolean)(implicit IO3: IO3[F], prettifier: Prettifier, pos: source.Position): F[Any, Nothing, Assertion] = - macro AssertIO3Macro.impl[F] - - object AssertIO3Macro { - def impl[F[-_, +_, +_]]( - c: blackbox.Context - )(arg: c.Expr[Boolean] - )(IO3: c.Expr[IO3[F]], - prettifier: c.Expr[Prettifier], - pos: c.Expr[org.scalactic.source.Position], - ): c.Expr[F[Any, Nothing, Assertion]] = { - import c.universe._ - c.Expr[F[Any, Nothing, Assertion]](q"$IO3.sync(${DistageAssertionsMacro.assert(c)(arg)(prettifier, pos)})") - } - } -} diff --git a/distage/distage-testkit-scalatest/src/main/scala-3/izumi/distage/testkit/scalatest/AssertIO3.scala b/distage/distage-testkit-scalatest/src/main/scala-3/izumi/distage/testkit/scalatest/AssertIO3.scala deleted file mode 100644 index 26b203deac..0000000000 --- a/distage/distage-testkit-scalatest/src/main/scala-3/izumi/distage/testkit/scalatest/AssertIO3.scala +++ /dev/null @@ -1,18 +0,0 @@ -package izumi.distage.testkit.scalatest - -import izumi.functional.bio.IO3 -import org.scalactic.{Prettifier, source} -import org.scalatest.{Assertion, Assertions} - -/** scalatest assertion macro for any [[izumi.functional.bio.IO3]] */ -trait AssertIO3[F[-_, +_, +_]] { - inline final def assertIO(inline arg: Boolean)(implicit IO3: IO3[F], prettifier: Prettifier, pos: source.Position): F[Any, Nothing, Assertion] = { - IO3.sync(Assertions.assert(arg)) - } -} - -object AssertIO3 { - inline final def assertIO[F[-_, +_, +_]](inline arg: Boolean)(implicit IO3: IO3[F], prettifier: Prettifier, pos: source.Position): F[Any, Nothing, Assertion] = { - IO3.sync(Assertions.assert(arg)) - } -} diff --git a/distage/distage-testkit-scalatest/src/main/scala-3/izumi/distage/testkit/scalatest/AssertZIO.scala b/distage/distage-testkit-scalatest/src/main/scala-3/izumi/distage/testkit/scalatest/AssertZIO.scala index 5bc7db05f8..cc078282ce 100644 --- a/distage/distage-testkit-scalatest/src/main/scala-3/izumi/distage/testkit/scalatest/AssertZIO.scala +++ b/distage/distage-testkit-scalatest/src/main/scala-3/izumi/distage/testkit/scalatest/AssertZIO.scala @@ -1,9 +1,8 @@ package izumi.distage.testkit.scalatest -import izumi.functional.bio.{IO2, IO3} import org.scalactic.source.Position -import org.scalactic.{Prettifier, source} -import org.scalatest.{Assertion, Assertions, AssertionsMacro} +import org.scalactic.Prettifier +import org.scalatest.{Assertion, Assertions} import zio.ZIO /** scalatest assertion macro for [[zio.ZIO]] */ diff --git a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/Spec3.scala b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/SpecZIO.scala similarity index 59% rename from distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/Spec3.scala rename to distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/SpecZIO.scala index a91887b816..b5f4cf4b68 100644 --- a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/Spec3.scala +++ b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/SpecZIO.scala @@ -3,9 +3,10 @@ package izumi.distage.testkit.scalatest import distage.{DefaultModule3, TagK3, TagKK} import izumi.distage.testkit.model.TestConfig import izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec -import izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec.DSWordSpecStringWrapper3 +import izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec.DSWordSpecStringWrapperZIO import izumi.logstage.distage.LogIO2Module import org.scalatest.distage.DistageScalatestTestSuiteRunner +import zio.ZIO import scala.language.implicitConversions @@ -21,14 +22,11 @@ import scala.language.implicitConversions * def myPets: F[Throwable, List[String]] * } * - * type PetStoreEnv = Has[PetStore[IO]] - * type PetsEnv = Has[Pets[IO]] - * - * val store = new PetStore[ZIO[PetStoreEnv, _, _]] { - * def purchasePet(name: String, cost: Int): RIO[PetStoreEnv, Boolean] = ZIO.accessM(_.get.purchasePet(name, cost)) + * val store = new PetStore[ZIO[PetStore[IO], _, _]] { + * def purchasePet(name: String, cost: Int): RIO[PetStore[IO], Boolean] = ZIO.accessM(_.get.purchasePet(name, cost)) * } - * val pets = new Pets[ZIO[PetsEnv, _, _]] { - * def myPets: RIO[PetsEnv, List[String]] = ZIO.accessM(_.get.myPets) + * val pets = new Pets[ZIO[Pets[IO], _, _]] { + * def myPets: RIO[Pets[IO], List[String]] = ZIO.accessM(_.get.myPets) * } * * "test purchase pets" in { @@ -37,7 +35,7 @@ import scala.language.implicitConversions * pets <- pets.myPets * _ <- assertIO(pets.contains("Zab")) * } yield () - * // : ZIO[PetStoreEnv with PetsEnv, Throwable, Unit] + * // : ZIO[PetStore[IO] with Pets[IO], Throwable, Unit] * } * }}} * @@ -47,7 +45,7 @@ import scala.language.implicitConversions * "test purchase pets" in { * (store: PetStore[IO]) => * for { - * _ <- store.purchasePet("Zab", 213) + * _ <- store.purchasePet("Zab", cost = 213) * pets <- pets.myPets * _ <- assertIO(pets.contains("Zab")) * } yield () @@ -55,15 +53,15 @@ import scala.language.implicitConversions * } * }}} */ -abstract class Spec3[FR[-_, +_, +_]: DefaultModule3](implicit val tagBIO3: TagK3[FR], val tagBIO: TagKK[FR[Any, _, _]]) - extends DistageScalatestTestSuiteRunner[FR[Any, Throwable, _]] - with DistageAbstractScalatestSpec[FR[Any, Throwable, _]] { +abstract class SpecZIO(implicit val defaultModule3: DefaultModule3[ZIO], val tagBIO3: TagK3[ZIO], val tagBIO: TagKK[ZIO[Any, _, _]]) + extends DistageScalatestTestSuiteRunner[ZIO[Any, Throwable, _]] + with DistageAbstractScalatestSpec[ZIO[Any, Throwable, _]] { - protected implicit def convertToWordSpecStringWrapperDS3(s: String): DSWordSpecStringWrapper3[FR] = { - new DSWordSpecStringWrapper3(context, distageSuiteName, distageSuiteId, Seq(s), this, testEnv) + protected implicit def convertToWordSpecStringWrapperDS3(s: String): DSWordSpecStringWrapperZIO = { + new DSWordSpecStringWrapperZIO(context, distageSuiteName, distageSuiteId, Seq(s), this, testEnv) } override protected def config: TestConfig = super.config.copy( - moduleOverrides = LogIO2Module[FR[Any, _, _]]()(tagBIO) + moduleOverrides = LogIO2Module[ZIO[Any, _, _]]()(tagBIO) ) } diff --git a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageAbstractScalatestSpec.scala b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageAbstractScalatestSpec.scala index d1c77d6e89..d7c83a28c0 100644 --- a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageAbstractScalatestSpec.scala +++ b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageAbstractScalatestSpec.scala @@ -8,7 +8,6 @@ import izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestS import izumi.distage.testkit.spec.{AbstractDistageSpec, DISyntaxBIOBase, DISyntaxBase, DistageTestEnv, TestRegistration} import izumi.functional.quasi.QuasiIO import izumi.fundamentals.platform.language.{SourceFilePosition, SourceFilePositionMaterializer} -import izumi.reflect.TagK3 import org.scalactic.source import org.scalatest.Assertion import org.scalatest.distage.TestCancellation @@ -93,7 +92,7 @@ object DistageAbstractScalatestSpec { } } - class DSWordSpecStringWrapper[F[_]]( + open class DSWordSpecStringWrapper[F[_]]( context: Option[SuiteContext], suiteName: String, suiteId: SuiteId, @@ -129,7 +128,7 @@ object DistageAbstractScalatestSpec { } } - class DSWordSpecStringWrapper2[F[+_, +_]]( + open class DSWordSpecStringWrapper2[F[+_, +_]]( context: Option[SuiteContext], suiteName: String, suiteId: SuiteId, @@ -166,74 +165,61 @@ object DistageAbstractScalatestSpec { } } - class DSWordSpecStringWrapper3[F[-_, +_, +_]: TagK3]( + open class DSWordSpecStringWrapperZIO( context: Option[SuiteContext], suiteName: String, suiteId: SuiteId, testname: Seq[String], - reg: TestRegistration[F[Any, Throwable, _]], + reg: TestRegistration[ZIO[Any, Throwable, _]], env: TestEnvironment, - )(implicit override val tagBIO: TagKK[F[Any, _, _]], - override val tagMonoIO: TagK[F[Any, Throwable, _]], - ) extends DISyntaxBIOBase[F[Any, +_, +_]] - with LowPriorityIdentityOverloads[F[Any, Throwable, _]] { + )(implicit override val tagBIO: TagKK[ZIO[Any, _, _]], + override val tagMonoIO: TagK[ZIO[Any, Throwable, _]], + ) extends DISyntaxBIOBase[ZIO[Any, +_, +_]] + with LowPriorityIdentityOverloads[ZIO[Any, Throwable, _]] { - // FIXME wtf specialize to ZIO? def in[R: ZEnvConstructor](function: Functoid[ZIO[R, Any, Unit]])(implicit pos: SourceFilePositionMaterializer): Unit = { takeBIO( function.map2(ZEnvConstructor[R]) { - case (eff, r) => eff.provideEnvironment(r).asInstanceOf[F[Any, Any, Unit]] + case (eff, r) => eff.provideEnvironment(r) }, pos.get, ) } + + def in[R: ZEnvConstructor](function: Functoid[ZIO[R, Any, Assertion]])(implicit pos: SourceFilePositionMaterializer, d1: DummyImplicit): Unit = { + takeBIO( + function.map2(ZEnvConstructor[R]) { + case (eff, r) => eff.provideEnvironment(r) + }, + pos.get, + ) + } + def in[R: ZEnvConstructor](value: => ZIO[R, Any, Unit])(implicit pos: SourceFilePositionMaterializer): Unit = { - takeBIO(ZEnvConstructor[R].map(value.provideEnvironment(_).asInstanceOf[F[Any, Any, Unit]]), pos.get) + takeBIO(ZEnvConstructor[R].map(value.provideEnvironment(_)), pos.get) } - // FIXME wtf specialize to ZIO? -// def in[R: ZEnvConstructor](function: Functoid[F[R, Any, Unit]])(implicit pos: SourceFilePositionMaterializer): Unit = { -// takeBIO( -// function.zip(ZEnvConstructor[R]).map2(Functoid.identity[Local3[F]]) { -// case ((eff, r), f) => f.provide(eff.asInstanceOf[F[R, Any, Unit]])(r) -// }, -// pos.get, -// ) -// } -// -// def in[R: ZEnvConstructor](function: Functoid[F[R, Any, Assertion]])(implicit pos: SourceFilePositionMaterializer, d1: DummyImplicit): Unit = { -// takeBIO( -// function.zip(ZEnvConstructor[R]).map2(Functoid.identity[Local3[F]]) { -// case ((eff, r), f) => f.provide(eff.asInstanceOf[F[R, Any, Assertion]])(r) -// }, -// pos.get, -// ) -// } -// -// def in[R: ZEnvConstructor](value: => F[R, Any, Unit])(implicit pos: SourceFilePositionMaterializer): Unit = { -// takeBIO(Functoid.identity[Local3[F]].map2(ZEnvConstructor[R])(_.provide(value.asInstanceOf[F[R, Any, Unit]])(_)), pos.get) -// } -// -// def in[R: ZEnvConstructor](value: => F[R, Any, Assertion])(implicit pos: SourceFilePositionMaterializer, d1: DummyImplicit): Unit = { -// takeBIO(Functoid.identity[Local3[F]].map2(ZEnvConstructor[R])(_.provide(value.asInstanceOf[F[R, Any, Unit]])(_)), pos.get) -// } - - def in(function: Functoid[F[Any, Any, Unit]])(implicit pos: SourceFilePositionMaterializer): Unit = { - takeBIO(function.asInstanceOf[Functoid[F[Any, Any, Any]]], pos.get) + + def in[R: ZEnvConstructor](value: => ZIO[R, Any, Assertion])(implicit pos: SourceFilePositionMaterializer, d1: DummyImplicit): Unit = { + takeBIO(ZEnvConstructor[R].map(value.provideEnvironment(_)), pos.get) + } + + def in(function: Functoid[ZIO[Any, Any, Unit]])(implicit pos: SourceFilePositionMaterializer): Unit = { + takeBIO(function, pos.get) } - def in(function: Functoid[F[Any, Any, Assertion]])(implicit pos: SourceFilePositionMaterializer, d1: DummyImplicit): Unit = { - takeBIO(function.asInstanceOf[Functoid[F[Any, Any, Assertion]]], pos.get) + def in(function: Functoid[ZIO[Any, Any, Assertion]])(implicit pos: SourceFilePositionMaterializer, d1: DummyImplicit): Unit = { + takeBIO(function, pos.get) } - def in(value: => F[Any, Any, Unit])(implicit pos: SourceFilePositionMaterializer): Unit = { - takeBIO(() => value.asInstanceOf[F[Any, Any, Any]], pos.get) + def in(value: => ZIO[Any, Any, Unit])(implicit pos: SourceFilePositionMaterializer): Unit = { + takeBIO(() => value, pos.get) } - def in(value: => F[Any, Any, Assertion])(implicit pos: SourceFilePositionMaterializer, d1: DummyImplicit): Unit = { - takeBIO(() => value.asInstanceOf[F[Any, Any, Any]], pos.get) + def in(value: => ZIO[Any, Any, Assertion])(implicit pos: SourceFilePositionMaterializer, d1: DummyImplicit): Unit = { + takeBIO(() => value, pos.get) } - override protected def takeIO[A](fAsThrowable: Functoid[F[Any, Throwable, A]], pos: SourceFilePosition): Unit = { + override protected def takeIO[A](fAsThrowable: Functoid[ZIO[Any, Throwable, A]], pos: SourceFilePosition): Unit = { val id = TestId( context.fold(testname)(_.toName(testname)), suiteId, diff --git a/distage/distage-testkit-scalatest/src/test/scala-2/izumi/distage/testkit/distagesuite/memoized/DistageMemoizationEnvsTest.scala b/distage/distage-testkit-scalatest/src/test/scala-2/izumi/distage/testkit/distagesuite/memoized/DistageMemoizationEnvsTest.scala index a39a05b207..3829062c74 100644 --- a/distage/distage-testkit-scalatest/src/test/scala-2/izumi/distage/testkit/distagesuite/memoized/DistageMemoizationEnvsTest.scala +++ b/distage/distage-testkit-scalatest/src/test/scala-2/izumi/distage/testkit/distagesuite/memoized/DistageMemoizationEnvsTest.scala @@ -1,20 +1,19 @@ package izumi.distage.testkit.distagesuite.memoized -import java.util.UUID - import distage.DIKey import distage.plugins.PluginDef import izumi.distage.model.definition.StandardAxis.Repo import izumi.distage.model.providers.Functoid -import izumi.distage.testkit.distagesuite.memoized.MemoizationEnv.{MemoizedInstance, MemoizedLevel1, MemoizedLevel2, MemoizedLevel3, TestInstance} +import izumi.distage.testkit.distagesuite.memoized.MemoizationEnv.* import izumi.distage.testkit.model.TestConfig -import izumi.distage.testkit.scalatest.{AssertZIO, Spec3} +import izumi.distage.testkit.scalatest.{AssertZIO, SpecZIO} import org.scalatest.Assertion -import zio.{IO, ZIO} +import zio.IO +import java.util.UUID // TODO: for some mysterious reason these tests fail on Scala 3. That must be fixed. -abstract class DistageMemoizationEnvsTest extends Spec3[ZIO] with AssertZIO { +abstract class DistageMemoizationEnvsTest extends SpecZIO with AssertZIO { override protected def config: TestConfig = { super.config .copy( diff --git a/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/compiletime/CompileTimePlanCheckerTest.scala b/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/compiletime/CompileTimePlanCheckerTest.scala index b1cc14dd68..d40d11ce82 100644 --- a/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/compiletime/CompileTimePlanCheckerTest.scala +++ b/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/compiletime/CompileTimePlanCheckerTest.scala @@ -5,6 +5,7 @@ import com.github.pshirshov.test2.plugins.Fixture2 import com.github.pshirshov.test2.plugins.Fixture2.{Dep, MissingDep} import com.github.pshirshov.test3.bootstrap.BootstrapFixture3.{BasicConfig, BootstrapComponent, UnsatisfiedDep} import com.github.pshirshov.test3.plugins.Fixture3 +import com.github.pshirshov.test4.Fixture4 import izumi.distage.framework.model.exceptions.PlanCheckException import izumi.distage.framework.{PlanCheck, PlanCheckConfig} import izumi.distage.model.planning.{AxisPoint, PlanIssue} @@ -291,6 +292,22 @@ final class CompileTimePlanCheckerTest extends AnyWordSpec with GivenWhenThen { assert(res.visitedKeys contains DIKey[LogIO2[zio.IO]]) } + "check subcontext submodule fails for missing bindings" in { + val Some(issues) = PlanCheck.runtime.checkApp(Fixture4.TestMainBad).issues + assert(issues.size == 1) + assert(issues.forall(_.isInstanceOf[PlanIssue.MissingImport])) + assert(issues.forall(_.asInstanceOf[PlanIssue.MissingImport].key == DIKey[Fixture4.MissingDep])) + } + + "check passes for subcontext submodule if missing binding is marked as a local dependency" in { + new PlanCheck.Main(Fixture4.TestMainGood) + .assertAgainAtRuntime() + val (loc, close) = Fixture4.TestMainGood.replLocatorWithClose(":target") + val dep = loc.get[Fixture4.TargetRole].mkDep() + close() + assert(dep != null) + } + "progression test: role app fails check for excluded compound activations that are equivalent to just excluding `mode:test`" in { val res = PlanCheck.runtime.checkApp( TestEntrypointPatchedLeak, diff --git a/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/generic/tests.scala b/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/generic/tests.scala index eff25737a5..eb55d58dcd 100644 --- a/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/generic/tests.scala +++ b/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/generic/tests.scala @@ -6,7 +6,7 @@ import izumi.distage.modules.DefaultModule import izumi.distage.testkit.distagesuite.fixtures.* import izumi.distage.testkit.distagesuite.generic.DistageTestExampleBase.* import izumi.distage.testkit.model.TestConfig -import izumi.distage.testkit.scalatest.{AssertZIO, Spec1, Spec2, Spec3} +import izumi.distage.testkit.scalatest.{AssertZIO, Spec1, Spec2, SpecZIO} import izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec import izumi.functional.quasi.QuasiIO import izumi.functional.quasi.QuasiIO.syntax.* @@ -42,7 +42,7 @@ class DistageTestExampleBIO extends Spec2[zio.IO] with DistageMemoizeExample[Tas } -class DistageTestExampleBIOEnv extends Spec3[ZIO] with DistageMemoizeExample[Task] with AssertZIO { +class DistageTestExampleBIOEnv extends SpecZIO with DistageMemoizeExample[Task] with AssertZIO { val service = ZIO.environmentWith[MockUserRepository[Task]](_.get) diff --git a/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/tagged/DistageTestTaggedAxesExampleBase.scala b/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/tagged/DistageTestTaggedAxesExampleBase.scala index b6553788fe..4ea88cc9c8 100644 --- a/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/tagged/DistageTestTaggedAxesExampleBase.scala +++ b/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/tagged/DistageTestTaggedAxesExampleBase.scala @@ -1,16 +1,15 @@ package izumi.distage.testkit.distagesuite.tagged -import java.util.concurrent.atomic.AtomicBoolean - import distage.DIKey import distage.plugins.PluginDef import izumi.distage.model.definition.StandardAxis.Repo import izumi.distage.testkit.distagesuite.tagged.DistageTestTaggedAxesExampleBase.{DepsCounters, DummyDep, PrdDep} import izumi.distage.testkit.model.TestConfig -import izumi.distage.testkit.scalatest.{AssertZIO, Spec3} -import zio.ZIO +import izumi.distage.testkit.scalatest.{AssertZIO, SpecZIO} + +import java.util.concurrent.atomic.AtomicBoolean -abstract class DistageTestTaggedAxesExampleBase extends Spec3[ZIO] with AssertZIO { +abstract class DistageTestTaggedAxesExampleBase extends SpecZIO with AssertZIO { override protected def config: TestConfig = super.config.copy( forcedRoots = Map( Set(Repo.Prod) -> Set(DIKey[PrdDep]), diff --git a/doc/microsite/src/main/tut/bio/00_bio.md b/doc/microsite/src/main/tut/bio/00_bio.md index 205496e663..bac9abe325 100644 --- a/doc/microsite/src/main/tut/bio/00_bio.md +++ b/doc/microsite/src/main/tut/bio/00_bio.md @@ -5,7 +5,7 @@ out: index.html BIO Hierarchy ============= -**BIO** is a set of typeclasses and algebras for programming in tagless final style using bifunctor or trifunctor effect types with variance. +**BIO** is a set of typeclasses and algebras for programming in tagless final style using bifunctor effect types with variance. Key syntactic features: @@ -15,35 +15,20 @@ Key syntactic features: These syntactic features allow you to write in a low ceremony, IDE-friendly and newcomer-friendly style: ```scala mdoc:to-string -import izumi.functional.bio.{F, Monad2, MonadAsk3, Primitives2, Ref3} +import izumi.functional.bio.{F, Monad2, Primitives2} def adder[F[+_, +_]: Monad2: Primitives2](i: Int): F[Nothing, Int] = F.mkRef(0) .flatMap(ref => ref.update(_ + i) *> ref.get) - -// update ref from the environment and return result -def adderEnv[F[-_, +_, +_]: MonadAsk3](i: Int): F[Ref3[F, Int], Nothing, Int] = - F.access { - ref => - for { - _ <- ref.update(_ + i) - res <- ref.get - } yield res - } ``` Key semantic features: 1. Typed error handling with bifunctor effect types 2. Automatic conversions to equivalent `cats.effect` instances using `import izumi.functional.bio.catz._` -3. Automatic adaptation of trifunctor typeclasses to bifunctor typeclasses when required -4. No ambiguous implicit errors. It's legal to have both `Monad3` and `MonadAsk3` as constraints, - despite the fact that `MonadAsk3` provides a `Monad3`: - ```scala -import izumi.functional.bio.{Monad3, MonadAsk3} - def adderEnv[F[-_, +_, +_]: Monad3: MonadAsk3] // would still work - ``` -5. Primitive concurrent data structures: `Ref`, `Promise`, `Semaphore` +3. No ambiguous implicit errors. It's legal to have both `Monad2` and `Applicative2` as constraints, + despite the fact that `Monad2` provides an `Applicative2`. +4. Primitive concurrent data structures: `Ref`, `Promise`, `Semaphore` To use it, add `fundamentals-bio` library: @@ -73,7 +58,7 @@ addCompilerPlugin("org.typelevel" % "kind-projector" % "0.13.2" cross CrossVersi ## Overview -The following graphic shows the current `BIO` hierarchy. Note that all the trifunctor typeclasses ending in `*3` typeclasses have bifunctor counterparts ending in `*2`. +The following graphic shows the current `BIO` hierarchy. ![BIO-relationship-hierarchy](media/bio-relationship-hierarchy.svg) @@ -124,8 +109,8 @@ Import `izumi.functional.bio.catz._` for shim compatibilty with cats-effect. You ## Data Types -`Ref2/3`, `Promise2/3` and `Semaphore2/3` provide basic concurrent mutable state primitives. They require a `Primitives2/3` capability to create. -With `Primitiives*[F]` in implicit scope, use `F.mkRef`/`F.mkPromise`/`F.mkSemaphore` respectively. (See also example at top of the page) +`Ref2`, `Promise2` and `Semaphore2` provide basic concurrent mutable state primitives. They require a `Primitives2` capability to create. +With `Primitiives2[F]` in implicit scope, use `F.mkRef`/`F.mkPromise`/`F.mkSemaphore` respectively. (See also example at top of the page) `Free` monad, as well as `FreeError` and `FreePanic` provide building blocks for DSLs when combined with a DSL describing functor. @@ -137,10 +122,16 @@ With `Primitiives*[F]` in implicit scope, use `F.mkRef`/`F.mkPromise`/`F.mkSemap `Entropy1/2/3` models random numbers. -`UnsafeRun2/3` allows executing effects (it is required for conversion to cats' `ConcurrentEffect` which also allows unsafe running) +`UnsafeRun2` allows executing effects (it is required for conversion to cats' `ConcurrentEffect` which also allows unsafe running) ## Examples [distage-example](https://github.com/7mind/distage-example) is a full example application written in Tagless Final Style using BIO typeclasses. You may also find a video walkthrough of using BIO on Youtube by [DevInsideYou — Tagless Final with BIO](https://www.youtube.com/watch?v=ZdGK1uedAE0&t=580s) + +## Removal of trifunctor hierarchy + +Since version 1.2.0 the trifunctor hierarchy has been removed, due to the fact that since ZIO version 2.0 they cannot be implemented for ZIO and also because of lack of use. + +See details in https://github.com/7mind/izumi/issues/2026 diff --git a/doc/microsite/src/main/tut/bio/media/algebras.svg b/doc/microsite/src/main/tut/bio/media/algebras.svg index b3dd35b63c..bcb948d28d 100644 --- a/doc/microsite/src/main/tut/bio/media/algebras.svg +++ b/doc/microsite/src/main/tut/bio/media/algebras.svg @@ -1,85 +1,257 @@ - - nomnoml - [cats.effect.*]<:--[CatsConversions] - -[Fork3]<:--[Fiber3] - -[BlockingIO3]<:--[BlockingIO2] - -[Primitives2]<:--[Primitives3] -[Primitives3]<:--[Ref3] -[Primitives3]<:--[Semaphore3] -[Primitives3]<:--[Promise3] - -[Entropy]<:--[Entropy2] -[Entropy2]<:--[Entropy3] - -[Clock]<:--[Clock2] -[Clock2]<:--[Clock3] - -[UnsafeRun2]<:--[UnsafeRun3] - - - - - - - - - - - - - - - - - - - - - - - - - - - - cats.effect.* - - CatsConversions - - Fork3 - - Fiber3 - - BlockingIO3 - - BlockingIO2 - - Primitives2 - - Primitives3 - - Ref3 - - Semaphore3 - - Promise3 - - Entropy - - Entropy2 - - Entropy3 - - Clock - - Clock2 - - Clock3 - - UnsafeRun2 - - UnsafeRun3 - \ No newline at end of file + + [cats.effect.*]<:--[CatsConversions] + + [Fork2]<:--[Fiber2] + + [BlockingIO2] + + [Primitives2] + + [Primitives2]<:--[Ref2] + [Primitives2]<:--[Semaphore2] + [Primitives2]<:--[Promise2] + + [PrimitivesM2] + [PrimitivesM2]<:--[RefM2] + [PrimitivesM2]<:--[Mutex2] + + [Entropy1]<:--[Entropy2] + [Clock1]<:--[Clock2] + + [UnsafeRun2] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cats.effect.* + + + + + + + + + + + CatsConversions + + + + + + + + + + + Fork2 + + + + + + + + + + + Fiber2 + + + + + + + + + + + BlockingIO2 + + + + + + + + + + + Primitives2 + + + + + + + + + + + Ref2 + + + + + + + + + + + Semaphore2 + + + + + + + + + + + Promise2 + + + + + + + + + + + PrimitivesM2 + + + + + + + + + + + RefM2 + + + + + + + + + + + Mutex2 + + + + + + + + + + + Entropy1 + + + + + + + + + + + Entropy2 + + + + + + + + + + + Clock1 + + + + + + + + + + + Clock2 + + + + + + + + + + + UnsafeRun2 + + + + + + + + + \ No newline at end of file diff --git a/doc/microsite/src/main/tut/bio/media/bio-hierarchy.svg b/doc/microsite/src/main/tut/bio/media/bio-hierarchy.svg index 9b5e944a45..5801284cef 100644 --- a/doc/microsite/src/main/tut/bio/media/bio-hierarchy.svg +++ b/doc/microsite/src/main/tut/bio/media/bio-hierarchy.svg @@ -1,99 +1,235 @@ - - nomnoml - [Functor3]<--[Applicative3] -[Applicative3]<--[Guarantee3] -[Applicative3]<--[Monad3] -[Guarantee3]<--[ApplicativeError3] -[Bifunctor3]<--[ApplicativeError3] -[ApplicativeError3]<--[Error3] -[Monad3]<--[Error3] -[Error3]<--[Bracket3] -[Bracket3]<--[Panic3] -[Panic3]<--[IO3] -[Parallel3]<--[Concurrent3] -[Concurrent3]<--[Async3] -[IO3]<--[Async3] -[Temporal3] -[Profunctor3]<--[Arrow3] -[Arrow3]<--[ArrowChoice3] -[ArrowChoice3]<--[Local3] -[Ask3]<--[MonadAsk3] -[MonadAsk3]<--[Local3] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Functor3 - - Applicative3 - - Guarantee3 - - Monad3 - - ApplicativeError3 - - Bifunctor3 - - Error3 - - Bracket3 - - Panic3 - - IO3 - - Parallel3 - - Concurrent3 - - Async3 - - Temporal3 - - Profunctor3 - - Arrow3 - - ArrowChoice3 - - Local3 - - Ask3 - - MonadAsk3 - \ No newline at end of file + + [Functor2]<--[Applicative2] + [Applicative2]<--[Guarantee2] + [Applicative2]<--[Monad2] + [Guarantee2]<--[ApplicativeError2] + [Bifunctor2]<--[ApplicativeError2] + [ApplicativeError2]<--[Error2] + [Monad2]<--[Error2] + [Error2]<--[Bracket2] + [Bracket2]<--[Panic2] + [Panic2]<--[IO2] + + [Parallel2]<--[Concurrent2] + [Concurrent2]<--[Async2] + [IO2]<--[Async2] + + [Temporal2] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Functor2 + + + + + + + + + + + Applicative2 + + + + + + + + + + + Guarantee2 + + + + + + + + + + + Monad2 + + + + + + + + + + + ApplicativeError2 + + + + + + + + + + + Bifunctor2 + + + + + + + + + + + Error2 + + + + + + + + + + + Bracket2 + + + + + + + + + + + Panic2 + + + + + + + + + + + IO2 + + + + + + + + + + + Parallel2 + + + + + + + + + + + Concurrent2 + + + + + + + + + + + Async2 + + + + + + + + + + + Temporal2 + + + + + + + + + \ No newline at end of file diff --git a/doc/microsite/src/main/tut/bio/media/bio-relationship-hierarchy.svg b/doc/microsite/src/main/tut/bio/media/bio-relationship-hierarchy.svg index be22bf96c4..e29a70d95a 100644 --- a/doc/microsite/src/main/tut/bio/media/bio-relationship-hierarchy.svg +++ b/doc/microsite/src/main/tut/bio/media/bio-relationship-hierarchy.svg @@ -1,116 +1,249 @@ - - nomnoml - [Functor3]<--[Bifunctor3] -[Bifunctor3]<--[ApplicativeError3] -[Functor3]<--[Applicative3] -[Applicative3]<--[Guarantee3] -[Applicative3]<--[Monad3] -[Guarantee3]<--[ApplicativeError3] -[ApplicativeError3]<--[Error3] -[Monad3]<--[Error3] -[Error3]<--[Bracket3] -[Bracket3]<--[Panic3] -[Panic3]<--[IO3] -[IO3]<--[Async3] -[Monad3]<--[Parallel3] -[Parallel3]<--[Concurrent3] -[Concurrent3]<--[Async3] -[Error3]<--[Temporal3] -[Functor3]<--[Profunctor3] -[Profunctor3]<--[Arrow3] -[Arrow3]<--[ArrowChoice3] -[ArrowChoice3]<--[Local3] -[Applicative3]<--[Ask3] -[Monad3]<--[MonadAsk3] -[Ask3]<--[MonadAsk3] -[MonadAsk3]<--[Local3] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Functor3 - - Bifunctor3 - - ApplicativeError3 - - Applicative3 - - Guarantee3 - - Monad3 - - Error3 - - Bracket3 - - Panic3 - - IO3 - - Async3 - - Parallel3 - - Concurrent3 - - Temporal3 - - Profunctor3 - - Arrow3 - - ArrowChoice3 - - Local3 - - Ask3 - - MonadAsk3 - \ No newline at end of file + + [Functor2]<--[Bifunctor2] + [Bifunctor2]<--[ApplicativeError2] + [Functor2]<--[Applicative2] + [Applicative2]<--[Guarantee2] + [Applicative2]<--[Monad2] + [Guarantee2]<--[ApplicativeError2] + [ApplicativeError2]<--[Error2] + [Monad2]<--[Error2] + [Error2]<--[Bracket2] + [Bracket2]<--[Panic2] + [Panic2]<--[IO2] + [IO2]<--[Async2] + + [Monad2]<--[Parallel2] + [Parallel2]<--[Concurrent2] + [Concurrent2]<--[Async2] + + [Error2]<--[Temporal2] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Functor2 + + + + + + + + + + + Bifunctor2 + + + + + + + + + + + ApplicativeError2 + + + + + + + + + + + Applicative2 + + + + + + + + + + + Guarantee2 + + + + + + + + + + + Monad2 + + + + + + + + + + + Error2 + + + + + + + + + + + Bracket2 + + + + + + + + + + + Panic2 + + + + + + + + + + + IO2 + + + + + + + + + + + Async2 + + + + + + + + + + + Parallel2 + + + + + + + + + + + Concurrent2 + + + + + + + + + + + Temporal2 + + + + + + + + + \ No newline at end of file diff --git a/doc/microsite/src/main/tut/distage/advanced-features.md b/doc/microsite/src/main/tut/distage/advanced-features.md index daa5e80484..c6fc9d215f 100644 --- a/doc/microsite/src/main/tut/distage/advanced-features.md +++ b/doc/microsite/src/main/tut/distage/advanced-features.md @@ -111,7 +111,7 @@ objects.get[C] eq objects.get[C].c #### Automatic Resolution with generated proxies -The above strategy depends on `distage-core-proxy-cglib` module and is enabled by default. +The above strategy depends on `distage-core-proxy-bytebuddy` module and is enabled by default. If you want to disable it, use `NoProxies` bootstrap configuration: @@ -152,14 +152,14 @@ val locator = Injector.NoProxies() .produce(module, Roots(DIKey[A], DIKey[C])) .unsafeGet() -locator.get[A].b eq locator.get[B] +assert(locator.get[A].b eq locator.get[B]) -locator.get[B].a eq locator.get[A] +assert(locator.get[B].a eq locator.get[A]) -locator.get[C].c eq locator.get[C] +assert(locator.get[C].c eq locator.get[C]) ``` -The proxy generation via `cglib` is enabled by default, because in scenarios with extreme late-binding cycles can emerge unexpectedly, +The proxy generation via `bytebuddy` is enabled by default, because in scenarios with extreme late-binding cycles can emerge unexpectedly, out of control of the origin module. Note: Currently a limitation applies to by-names - ALL dependencies of a class engaged in a by-name circular dependency must @@ -358,6 +358,9 @@ val plan: Plan = objects.plan val bindings: ModuleBase = plan.definition ``` +Directly depending on `Locator` is a low-level API. If you only need to wire a subgraph within a larger object graph, +you may be able to do this using a high-level @ref[Subcontexts](basics.md#subcontexts) API. + #### Injector inheritance You may run a new planning cycle, inheriting the instances from an existing `Locator` into your new object subgraph: @@ -366,7 +369,7 @@ You may run a new planning cycle, inheriting the instances from an existing `Loc val childInjector = Injector.inherit(objects) class Printer(a: A, b: B, c: C) { - def printEm() = + def printEm(): Unit = println(s"I've got A=$a, B=$b, C=$c, all here!") } @@ -375,6 +378,8 @@ childInjector.produceRun(new ModuleDef { make[Printer] }) { } ``` +It's safe, performance-wise, to run `Injector` to create nested graphs – `Injector` is extremely fast. + #### Bootloader The plan and bindings in Locator are saved in the state they were AFTER @ref[Garbage Collection](#dependency-pruning) has been performed. diff --git a/doc/microsite/src/main/tut/distage/basics.md b/doc/microsite/src/main/tut/distage/basics.md index 5142b4b14d..b39b33dedd 100644 --- a/doc/microsite/src/main/tut/distage/basics.md +++ b/doc/microsite/src/main/tut/distage/basics.md @@ -220,7 +220,7 @@ object Ids { You cannot embed non-singletons into the object graph, but you may create them as normal using factories. `distage`'s @ref[Auto-Factories](#auto-factories) can generate implementations for your factories, removing the associated boilerplate. -While Auto-Factories may remove the boilerplate of generating factories for singular components, if you need to create a new non-trivial subgraph dynamically, you'll need to run `Injector` again – you may use `Injector.inherit` to reuse components from the outer object graph in your new nested object graph, see @ref[Injector Inheritance](advanced-features.md#injector-inheritance). It's safe, performance-wise, to run `Injector` to create nested graphs, it's extremely fast. +While Auto-Factories may remove the boilerplate of generating factories for singular components, if you need to create a new non-trivial subgraph dynamically, you'll need to run `Injector` again. @ref[Subcontexts](#subcontexts) feature automates running nested Injectors and makes it easier to define nested object graphs. You may also manually use `Injector.inherit` to reuse components from the outer object graph in your new nested object graph, see @ref[Injector Inheritance](advanced-features.md#injector-inheritance). ## Real-world example @@ -426,7 +426,7 @@ Try { Injector().produceRun(SpecificityModule, Activation(Style -> Style.Normal) ## Resource Bindings, Lifecycle -You can specify object lifecycle by injecting @scaladoc[distage.Lifecycle](izumi.distage.model.definition.Lifecycle), [cats.effect.Resource](https://typelevel.org/cats-effect/datatypes/resource.html), [scoped zio.ZIO](https://zio.dev/guides/migrate/zio-2.x-migration-guide#scopes-1), [zio.ZLayer](https://zio.dev/reference/contextual/zlayer) or +You can specify object lifecycle by injecting @scaladoc[distage.Lifecycle](izumi.functional.lifecycle.Lifecycle), [cats.effect.Resource](https://typelevel.org/cats-effect/docs/std/resource), [scoped zio.ZIO](https://zio.dev/guides/migrate/zio-2.x-migration-guide#scopes-1), [zio.ZLayer](https://zio.dev/reference/contextual/zlayer) or [zio.managed.ZManaged](https://zio.dev/1.0.18/reference/resource/zmanaged/) values specifying the allocation and finalization actions of an object. @@ -528,33 +528,34 @@ and between a `Lifecycle` and scoped `zio.ZIO`/`zio.managed.ZManaged`/`zio.ZLaye ### Inheritance helpers -The following helpers allow defining `Lifecycle` sub-classes using expression-like syntax: - -- @scaladoc[Lifecycle.Of](izumi.distage.model.definition.Lifecycle$$Of) -- @scaladoc[Lifecycle.OfInner](izumi.distage.model.definition.Lifecycle$$OfInner) -- @scaladoc[Lifecycle.OfCats](izumi.distage.model.definition.Lifecycle$$OfCats) -- @scaladoc[Lifecycle.OfZIO](izumi.distage.model.definition.Lifecycle$$OfZIO) -- @scaladoc[Lifecycle.OfZManaged](izumi.distage.model.definition.Lifecycle$$OfZManaged) -- @scaladoc[Lifecycle.OfZLayer](izumi.distage.model.definition.Lifecycle$$OfZLayer) -- @scaladoc[Lifecycle.LiftF](izumi.distage.model.definition.Lifecycle$$LiftF) -- @scaladoc[Lifecycle.Make](izumi.distage.model.definition.Lifecycle$$Make) -- @scaladoc[Lifecycle.Make_](izumi.distage.model.definition.Lifecycle$$Make_) -- @scaladoc[Lifecycle.MakePair](izumi.distage.model.definition.Lifecycle$$MakePair) -- @scaladoc[Lifecycle.FromAutoCloseable](izumi.distage.model.definition.Lifecycle$$FromAutoCloseable) -- @scaladoc[Lifecycle.SelfOf](izumi.distage.model.definition.Lifecycle$$SelfOf) -- @scaladoc[Lifecycle.MutableOf](izumi.distage.model.definition.Lifecycle$$MutableOf) - -The main reason to employ them is to workaround a limitation in Scala 2's eta-expansion — when converting a method to a function value, +The following helpers allow defining `Lifecycle` subclasses using expression-like syntax: + +- @scaladoc[Lifecycle.Of](izumi.functional.lifecycle.Lifecycle$$Of) +- @scaladoc[Lifecycle.OfInner](izumi.functional.lifecycle.Lifecycle$$OfInner) +- @scaladoc[Lifecycle.OfCats](izumi.functional.lifecycle.Lifecycle$$OfCats) +- @scaladoc[Lifecycle.OfZIO](izumi.functional.lifecycle.Lifecycle$$OfZIO) +- @scaladoc[Lifecycle.OfZManaged](izumi.functional.lifecycle.Lifecycle$$OfZManaged) +- @scaladoc[Lifecycle.OfZLayer](izumi.functional.lifecycle.Lifecycle$$OfZLayer) +- @scaladoc[Lifecycle.LiftF](izumi.functional.lifecycle.Lifecycle$$LiftF) +- @scaladoc[Lifecycle.Make](izumi.functional.lifecycle.Lifecycle$$Make) +- @scaladoc[Lifecycle.Make_](izumi.functional.lifecycle.Lifecycle$$Make_) +- @scaladoc[Lifecycle.MakePair](izumi.functional.lifecycle.Lifecycle$$MakePair) +- @scaladoc[Lifecycle.FromAutoCloseable](izumi.functional.lifecycle.Lifecycle$$FromAutoCloseable) +- @scaladoc[Lifecycle.SelfOf](izumi.functional.lifecycle.Lifecycle$$SelfOf) +- @scaladoc[Lifecycle.MutableOf](izumi.functional.lifecycle.Lifecycle$$MutableOf) + +The main reason to employ them is to work around a limitation in Scala 2's eta-expansion — when converting a method to a function value, Scala always tries to fulfill implicit parameters eagerly instead of making them parameters of the function value, this limitation makes it harder to inject implicits using `distage`. However, when using `distage`'s type-based syntax: `make[A].fromResource[A.Resource[F]]` — this limitation does not apply and implicits inject successfully. -So to workaround this limitation you can convert an expression based resource-constructor: +So to work around this limitation you can convert an expression based resource constructor: ```scala mdoc:reset:to-string -import distage.Lifecycle, cats.Monad +import distage.{Lifecycle, ModuleDef} +import cats.Monad class A(val n: Int) @@ -564,12 +565,18 @@ object A { Lifecycle.pure[F](new A(1)) } + +def module = new ModuleDef { + // Bad: summons Monad[cats.effect.IO] immediately, instead of getting it from the object graph + make[A].fromResource(A.resource[cats.effect.IO]) +} ``` Into a class-based form: ```scala mdoc:reset:to-string -import distage.Lifecycle, cats.Monad +import distage.{Lifecycle, ModuleDef} +import cats.Monad class A(val n: Int) @@ -581,19 +588,25 @@ object A { ) } + +def module = new ModuleDef { + // Good: implicit Monad[cats.effect.IO] parameter is wired from the object graph, same as the non-implicit parameters + make[A].fromResource[A.Resource[cats.effect.IO]] + addImplicit[Monad[cats.effect.IO]] +} ``` And inject successfully using `make[A].fromResource[A.Resource[F]]` syntax of @scaladoc[ModuleDefDSL](izumi.distage.model.definition.dsl.ModuleDefDSL). -The following helpers ease defining `Lifecycle` sub-classes using traditional inheritance where `acquire`/`release` parts are defined as methods: +The following helpers ease defining `Lifecycle` subclasses using traditional inheritance where `acquire`/`release` parts are defined as methods: -- @scaladoc[Lifecycle.Basic](izumi.distage.model.definition.Lifecycle$$Basic) -- @scaladoc[Lifecycle.Simple](izumi.distage.model.definition.Lifecycle$$Simple) -- @scaladoc[Lifecycle.Mutable](izumi.distage.model.definition.Lifecycle$$Mutable) -- @scaladoc[Lifecycle.MutableNoClose](izumi.distage.model.definition.Lifecycle$$MutableNoClose) -- @scaladoc[Lifecycle.Self](izumi.distage.model.definition.Lifecycle$$Self) -- @scaladoc[Lifecycle.SelfNoClose](izumi.distage.model.definition.Lifecycle$$SelfNoClose) -- @scaladoc[Lifecycle.NoClose](izumi.distage.model.definition.Lifecycle$$NoClose) +- @scaladoc[Lifecycle.Basic](izumi.functional.lifecycle.Lifecycle$$Basic) +- @scaladoc[Lifecycle.Simple](izumi.functional.lifecycle.Lifecycle$$Simple) +- @scaladoc[Lifecycle.Mutable](izumi.functional.lifecycle.Lifecycle$$Mutable) +- @scaladoc[Lifecycle.MutableNoClose](izumi.functional.lifecycle.Lifecycle$$MutableNoClose) +- @scaladoc[Lifecycle.Self](izumi.functional.lifecycle.Lifecycle$$Self) +- @scaladoc[Lifecycle.SelfNoClose](izumi.functional.lifecycle.Lifecycle$$SelfNoClose) +- @scaladoc[Lifecycle.NoClose](izumi.functional.lifecycle.Lifecycle$$NoClose) ## Out-of-the-box typeclass instances @@ -916,7 +929,11 @@ val runtime = UnsafeRun2.createZIO() runtime.unsafeRun(io) ``` -You need to specify your effect type when constructing `Injector`, as in `Injector[F]()`, to use effect bindings in chosen `F[_]`. +You must specify your effect type when constructing an `Injector`, as in `Injector[F]()`, to use effect bindings in the chosen `F[_]` type. + +You may want to use @scaladoc[Lifecycle.LiftF](izumi.functional.lifecycle.Lifecycle$$LiftF) to convert effect methods +with implicit parameters into a class-based form to ensure that implicit parameters are wired from the object graph, not +from the surrounding implicit scope. (See @ref[Inheritance Helpers](#inheritance-helpers)) ## ZIO Environment bindings @@ -1125,12 +1142,11 @@ runtime.unsafeRun { ## Auto-Traits -distage can instantiate traits and structural types. All unimplemented fields in a trait, or a refinement are filled in from the object graph. +distage can instantiate traits and structural types. -Trait implementations are derived at compile-time by @scaladoc[TraitConstructor](izumi.distage.constructors.TraitConstructor) macro -and can be summoned at need. +Use `makeTrait[X]` or `make[X].fromTrait[Y]` to wire traits, abstract classes or a structural types. -If a suitable trait is specified as an implementation class for a binding, `TraitConstructor` will be used automatically: +All unimplemented fields in a trait, or a refinement are filled in from the object graph. Trait implementations are derived at compile-time by @scaladoc[TraitConstructor](izumi.distage.constructors.TraitConstructor) macro and can be summoned at need. Example: @@ -1195,8 +1211,8 @@ object PlusedInt { def module = new ModuleDef { make[Int].named("a").from(1) make[Int].named("b").from(2) - make[Pluser] - make[PlusedInt].from[PlusedInt.Impl] + makeTrait[Pluser] + make[PlusedInt].fromTrait[PlusedInt.Impl] } Injector().produceRun(module) { @@ -1225,8 +1241,8 @@ import distage.impl ### Avoiding constructors even further -When overriding behavior of a class, you may avoid writing a repeat of its constructor in your sub-class by inheriting -it with a trait instead. Example: +When overriding behavior of a class, you may avoid writing a repeat of its constructor in your subclass by inheriting +a trait from it instead. Example: ```scala mdoc:to-string /** @@ -1250,7 +1266,7 @@ it with a trait instead. Example: } Injector().produceRun(module overriddenBy new ModuleDef { - make[PlusedInt].from[OverridenPlusedIntImpl] + make[PlusedInt].fromTrait[OverridenPlusedIntImpl] }) { plusedInt: PlusedInt => plusedInt.result() @@ -1299,6 +1315,23 @@ class ActorFactoryImpl(sessionStorage: SessionStorage) extends ActorFactory { } ``` +Note that ordinary function types conform to distage's definition of a 'factory', since they are just traits with an unimplemented method. +Sometimes declaring a separate named factory trait isn't worth it, in these cases you can use `makeFactory` to generate ordinary function types: + +```scala mdoc:to-string +object UserActor { + type Factory = UUID => UserActor +} + +class ActorFunctionModule extends ModuleDef { + makeFactory[UserActor.Factory] +} +``` + +You can use this feature to concisely provide non-Singleton semantics for some of your components. + +Factory implementations are derived at compile-time by @scaladoc[FactoryConstructor](izumi.distage.constructors.FactoryConstructor) macro and can be summoned at need. + Since `distage` version `1.1.0` you have to bind factories explicitly using `makeFactory` and `fromFactory` methods, not implicitly via `make`; parameterless methods in factories now produce new instances instead of summoning a dependency. ### @With annotation @@ -1318,7 +1351,7 @@ object Actor { } final class Impl(id: String, config: Actor.Configuration) extends Actor { - def receive(msg: Any) = { + def receive(msg: Any): Unit = { val response = s"Actor `$id` received a message: $msg" println(if (config.allCaps) response.toUpperCase else response) } @@ -1337,11 +1370,260 @@ Injector() .use(_.newActor("Martin Odersky").receive("ping")) ``` -You can use this feature to concisely provide non-Singleton semantics for some of your components. +## Subcontexts + +Sometimes multiple components depend on the same piece of data that appears locally, after all the components were already wired. +This data may need to be passed around repeatedly, possibly across the entire application. To do this, we may have to add an argument +to most methods of an application, or have to use a Reader monad everywhere. + +For example, we could be adding distributed tracing to our application - after getting a RequestId from a request, we may +need to carry it everywhere to add it to logs and metrics. + +Ideally, instead of adding the same argument to our methods, we'd want to just move that argument data out to the class constructor - +passing the argument just once during the construction of a class. However, we'd lose the ability to automatically wire our objects, +since we can only get a RequestId from a request, it's not available when we initially wire our object graph. + +Since 1.2.0 this problem is addressed in distage using `Subcontext`s - using them we can define a wireable sub-graph of +our components that depend on local data unavailable during wiring, but that we can then finish wiring once we pass them the data. + +Starting with a graph that has no local dependencies: + +```scala mdoc:reset:invisible:to-string +class PetStoreRepository[F[+_, +_]] + +class Pet +class PetId +class RequestId +def RequestId(): RequestId = ??? +``` + +```scala mdoc:to-string +import izumi.functional.bio.IO2 +import distage.{ModuleDef, Subcontext, TagKK} + +class PetStoreBusinessLogic[F[+_, +_]] { + // requestId is a method parameter + def buyPetLogic(requestId: RequestId, petId: PetId, payment: Int): F[Throwable, Pet] = ??? +} + +def module1[F[+_, +_]: TagKK] = new ModuleDef { + make[PetStoreAPIHandler[F]] + + make[PetStoreRepository[F]] + make[PetStoreBusinessLogic[F]] +} + +class PetStoreAPIHandler[F[+_, +_]: IO2]( + petStoreBusinessLogic: PetStoreBusinessLogic[F] +) { + def buyPet(petId: PetId, payment: Int): F[Throwable, Pet] = { + petStoreBusinessLogic.buyPetLogic(RequestId(), petId, payment) + } +} +``` + +We use `makeSubcontext` to delineate a portion of the graph that requires a `RequestId` to be wired: + +```scala mdoc:override:to-string +class HACK_OVERRIDE_PetStoreBusinessLogic[F[+_, +_]]( + // requestId is a now a class parameter + requestId: RequestId +) { + def buyPetLogic(petId: PetId, payment: Int): F[Throwable, Pet] = ??? +} + +def module2[F[+_, +_] : TagKK] = new ModuleDef { + make[HACK_OVERRIDE_PetStoreAPIHandler[F]] + + makeSubcontext[PetStoreBusinessLogic[F]] + .withSubmodule(new ModuleDef { + make[PetStoreRepository[F]] + make[HACK_OVERRIDE_PetStoreBusinessLogic[F]] + }) + .localDependency[RequestId] +} + +class HACK_OVERRIDE_PetStoreAPIHandler[F[+_, +_]: IO2: TagKK]( + petStoreBusinessLogic: Subcontext[HACK_OVERRIDE_PetStoreBusinessLogic[F]] +) { + def buyPet(petId: PetId, payment: Int): F[Throwable, Pet] = { + // we have to pass the parameter and create the component now, since it's not already wired. + petStoreBusinessLogic + .provide[RequestId](RequestId()) + .produceRun { + _.buyPetLogic(petId, payment) + } + } +} +``` + +We managed to move RequestId from a method parameter that polluted every method signature, to a class parameter, that we pass to the subgraph just once - when the RequestId is generated. + +Full example: + +```scala mdoc:reset:invisible:to-string +def HACK_OVERRIDE_IzLogger(): logstage.IzLogger = { + logstage.IzLogger(sink = new izumi.logstage.api.logger.LogSink { + val policy = izumi.logstage.api.rendering.RenderingPolicy.simplePolicy() + + override def flush(e: logstage.Log.Entry): Unit = { + val rendered = policy.render(e) + println(rendered) + } + + override def sync(): Unit = { + print("") + } + }) +} +``` + +```scala mdoc:override:to-string +import distage.{Injector, Lifecycle, ModuleDef, Subcontext, TagKK} +import izumi.functional.bio.{Error2, F, IO2, Monad2, Primitives2} +import izumi.functional.bio.data.Morphism1 +import logstage.{IzLogger, LogIO2} +import izumi.logstage.distage.LogIO2Module + +import java.util.UUID + +final case class PetId(petId: UUID) +final case class RequestId(requestId: UUID) + +sealed trait TransactionFailure +object TransactionFailure { + case object NoSuchPet extends TransactionFailure + case object InsufficientFunds extends TransactionFailure +} + +final case class Pet(name: String, species: String, price: Int) + +final class PetStoreAPIHandler[F[+_, +_]: IO2: TagKK]( + petStoreBusinessLogic: Subcontext[PetStoreBusinessLogic[F]] +) { + def buyPet(petId: PetId, payment: Int): F[TransactionFailure, Pet] = { + for { + requestId <- F.sync(RequestId(UUID.randomUUID())) + pet <- petStoreBusinessLogic + .provide[RequestId](requestId) + .produce[F[Throwable, _]]() + .mapK[F[Throwable, _], F[TransactionFailure, _]](Morphism1(_.orTerminate)) + .use { + component => + component.buyPetLogic(petId, payment) + } + } yield pet + } +} + +final class PetStoreBusinessLogic[F[+_, +_]: Error2]( + requestId: RequestId, + petStoreReposistory: PetStoreReposistory[F], + log: LogIO2[F], +) { + private val contextLog = log.withCustomContext("requestId" -> requestId) + + def buyPetLogic(petId: PetId, payment: Int): F[TransactionFailure, Pet] = { + for { + pet <- petStoreReposistory.findPet(petId).fromOption(TransactionFailure.NoSuchPet) + _ <- if (payment < pet.price) { + contextLog.error(s"Insufficient $payment, couldn't afford ${pet.price}") *> + F.fail(TransactionFailure.InsufficientFunds) + } else { + for { + result <- petStoreReposistory.removePet(petId) + _ <- F.when(!result)(F.fail(TransactionFailure.NoSuchPet)) + _ <- contextLog.info(s"Successfully bought $pet with $petId for $payment! ${payment - pet.price -> "overpaid"}") + } yield () + } + } yield pet + } +} + +trait PetStoreReposistory[F[+_, +_]] { + def findPet(petId: PetId): F[Nothing, Option[Pet]] + def removePet(petId: PetId): F[Nothing, Boolean] +} +object PetStoreReposistory { + final class Impl[F[+_, +_]: Monad2: Primitives2]( + requestId: RequestId, + log: LogIO2[F], + ) extends Lifecycle.LiftF[F[Nothing, _], PetStoreReposistory[F]](for { + state <- F.mkRef(Pets.builtinPetMap) + } yield new PetStoreReposistory[F] { + private val contextLog = log("requestId" -> requestId) + + override def findPet(petId: PetId): F[Nothing, Option[Pet]] = { + for { + _ <- contextLog.info(s"Looking up $petId") + maybePet <- state.get.map(_.get(petId)) + _ <- contextLog.info(s"Got $maybePet") + } yield maybePet + } + + override def removePet(petId: PetId): F[Nothing, Boolean] = { + for { + success <- state.get.map(_.contains(petId)) + _ <- state.get.map(_ - petId) + _ <- contextLog.info(s"Tried to remove $petId, $success") + } yield success + + } + }) +} + +object Pets { + val arnoldId = PetId(UUID.randomUUID()) + val buckId = PetId(UUID.randomUUID()) + val chipId = PetId(UUID.randomUUID()) + val derryId = PetId(UUID.randomUUID()) + val emmyId = PetId(UUID.randomUUID()) + + val builtinPetMap = Map[PetId, Pet]( + arnoldId -> Pet("Arnold", "Dog", 99), + buckId -> Pet("Buck", "Rabbit", 60), + chipId -> Pet("Chip", "Cat", 75), + derryId -> Pet("Derry", "Dog", 250), + emmyId -> Pet("Emmy", "Guinea Pig", 20) + ) +} + +object Module extends ModuleDef { + include(module[zio.IO]) + + def module[F[+_, +_]: TagKK] = new ModuleDef { + make[PetStoreAPIHandler[F]] + + make[IzLogger].from(HACK_OVERRIDE_IzLogger()) + include(LogIO2Module[F]()) + + addImplicit[TagKK[F]] -Factory implementations are derived at compile-time by -@scaladoc[FactoryConstructor](izumi.distage.constructors.FactoryConstructor) macro -and can be summoned at need. + makeSubcontext[PetStoreBusinessLogic[F]] + .withSubmodule(new ModuleDef { + make[PetStoreReposistory[F]].fromResource[PetStoreReposistory.Impl[F]] + make[PetStoreBusinessLogic[F]] + }) + .localDependency[RequestId] + } +} + +import izumi.functional.bio.UnsafeRun2 + +val runner = UnsafeRun2.createZIO() + +val result = runner.unsafeRun { + Injector[zio.Task]() + .produceRun(Module) { + (p: PetStoreAPIHandler[zio.IO]) => + p.buyPet(Pets.arnoldId, 100).attempt + } +} +``` + +Using subcontexts is more efficient than @ref[nesting Injectors](advanced-features.md#depending-on-locator) manually, since subcontexts are planned ahead of time - there's no planning step for subcontexts, only execution step. + +Note: When your subcontext's submodule only contains one binding, you may be able to achieve the same result using an @ref[Auto-Factory](#auto-factories) instead. ## Tagless Final Style @@ -1493,11 +1775,7 @@ class BifunctorIOModule[F[_, _]: TagKK] extends ModuleDef Or use `Tag.auto.T` to abstract over any kind: ```scala mdoc:to-string -class MonadTransModule[F[_[_], _]: Tag.auto.T] extends ModuleDef -``` - -```scala mdoc:to-string -class TrifunctorModule[F[_, _, _]: Tag.auto.T] extends ModuleDef +class MonadTransformerModule[F[_[_], _]: Tag.auto.T] extends ModuleDef ``` ```scala mdoc:to-string diff --git a/doc/microsite/src/main/tut/distage/debugging.md b/doc/microsite/src/main/tut/distage/debugging.md index da611b5e94..fa259ac230 100644 --- a/doc/microsite/src/main/tut/distage/debugging.md +++ b/doc/microsite/src/main/tut/distage/debugging.md @@ -131,7 +131,7 @@ import izumi.distage.framework.config.PlanningOptions import izumi.distage.roles.RoleAppMain import zio.IO -abstract class MyRoleLauncher extends RoleAppMain.LauncherBIO2[IO] { +abstract class MyRoleLauncher extends RoleAppMain.LauncherBIO[IO] { override protected def roleAppBootOverrides(argv: RoleAppMain.ArgV): Module = new ModuleDef { make[PlanningOptions].from(PlanningOptions(addGraphVizDump = true)) } diff --git a/doc/microsite/src/main/tut/distage/distage-framework.md b/doc/microsite/src/main/tut/distage/distage-framework.md index 8d2d208630..70e8f2e44f 100644 --- a/doc/microsite/src/main/tut/distage/distage-framework.md +++ b/doc/microsite/src/main/tut/distage/distage-framework.md @@ -57,7 +57,7 @@ import distage.plugins.PluginConfig import izumi.distage.roles.RoleAppMain import zio.IO -object ExampleLauncher extends RoleAppMain.LauncherBIO2[IO] { +object ExampleLauncher extends RoleAppMain.LauncherBIO[IO] { override def pluginConfig = { PluginConfig.const( // add the plugin with ExampleRoleTask @@ -88,7 +88,29 @@ I 2020-12-14T10:00:16.267 (RoleAppEntrypoint.scala:106) ….R.I.r.1.logge Only the components required by the specified roles will be created, everything else will be pruned. (see: @ref[Dependency Pruning](advanced-features.md#dependency-pruning)) -@scaladoc[BundledRolesModule](izumi.distage.roles.bundled.BundledRolesModule) contains two example roles: +Further reading: + +- [distage-example](https://github.com/7mind/distage-example) - Example web service using roles +- [Roles: a viable alternative to Microservices and Monoliths](https://github.com/7mind/slides/blob/master/02-roles/roles.pdf) + +### Inspecting components in REPL + +Use `.replLocator` method of a `RoleAppMain` to obtain an object graph of the application inspectable in the REPL: + +```scala mdoc:to-string +import izumi.functional.bio.UnsafeRun2 + +val runner = UnsafeRun2.createZIO() + +val objects = runner.unsafeRun(ExampleLauncher.replLocator(":exampletask")) + +// inspecting a component inside the application +val logger = objects.get[LogIO[UIO]] +``` + +### Bundled roles + +@scaladoc[BundledRolesModule](izumi.distage.roles.bundled.BundledRolesModule) contains two bundled roles: - @scaladoc[Help](izumi.distage.roles.bundled.Help) - prints help message when launched `./launcher :help` - @scaladoc[ConfigWriter](izumi.distage.roles.bundled.ConfigWriter) - writes reference config into files, split by @@ -106,11 +128,6 @@ object RolesPlugin extends PluginDef { } ``` -Further reading: - -- [distage-example](https://github.com/7mind/distage-example) - Example web service using roles -- [Roles: a viable alternative to Microservices and Monoliths](https://github.com/7mind/slides/blob/master/02-roles/roles.pdf) - ## Compile-time checks To use compile-time checks, add `distage-framework` library: @@ -259,7 +276,7 @@ val module = new ConfigModuleDef { makeConfig[OtherConf]("conf").named("other") // add config instance - make[AppConfig].from(AppConfig( + make[AppConfig].from(AppConfig.provided( ConfigFactory.parseString( """conf { | name = "John" diff --git a/doc/microsite/src/main/tut/distage/distage-testkit.md b/doc/microsite/src/main/tut/distage/distage-testkit.md index b36be60dbe..b1b9fbaf2b 100644 --- a/doc/microsite/src/main/tut/distage/distage-testkit.md +++ b/doc/microsite/src/main/tut/distage/distage-testkit.md @@ -6,7 +6,7 @@ `distage-testkit` simplifies pragmatic purely-functional program testing providing `Spec*` [ScalaTest](https://www.scalatest.org/) base classes for any existing Scala effect type with kind `F[_]`, -`F[+_, +_]`, `F[-_, +_, +_]` or `Identity`. `Spec`s provide an interface similar to ScalaTest's +`F[+_, +_]`, `ZIO[-R, +E, +A]` or `Identity`. `Spec`s provide an interface similar to ScalaTest's [`WordSpec`](http://doc.scalatest.org/3.1.0/org/scalatest/wordspec/AnyWordSpec.html), however `distage-testkit` adds additional capabilities such as: first class support for effect types; dependency injection; and parallel execution. @@ -18,7 +18,7 @@ Usage of `distage-testkit` generally follows these steps: - `F[_]` - @scaladoc[`Spec1[F]`](izumi.distage.testkit.scalatest.Spec1), for monofunctors (`cats.effect.IO` , `monix`) - `F[+_, +_]` - @scaladoc[`Spec2[F]`](izumi.distage.testkit.scalatest.Spec2), for bifunctors (`ZIO`, `monix-bio`) - - `F[-_, +_, +_]` - @scaladoc[`Spec3[F]`](izumi.distage.testkit.scalatest.Spec3) for trifunctors (`ZIO`) + - `ZIO[-R, +E, +A]` - @scaladoc[`SpecZIO`](izumi.distage.testkit.scalatest.SpecZIO) for `ZIO` with environment support in tests 2. Override `def config: TestConfig` to customize the @scaladoc[`TestConfig`](izumi.distage.testkit.TestConfig) 3. Establish test case contexts using [`should`](https://www.scalatest.org/scaladoc/3.2.0/org/scalatest/verbs/ShouldVerb.html), @@ -32,7 +32,7 @@ Usage of `distage-testkit` generally follows these steps: @scaladoc[`in`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$LowPriorityIdentityOverloads) - @scaladoc[`in` for `F[_]`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapper) - @scaladoc[`in` for `F[+_, +_]`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapper2) - - @scaladoc[`in` for `F[-_, +_, +_]`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapper3) + - @scaladoc[`in` for `ZIO[-R, +E, +A]`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapperZIO) - Test cases dependent on injectables: @scaladoc[`Functoid`](izumi.distage.model.providers.Functoid) ### API Overview @@ -101,12 +101,12 @@ matches our application's effect type from the following: - No effect type, imperative usage - @scaladoc[`SpecIdentity`](izumi.distage.testkit.scalatest.SpecIdentity) - `F[_]` - @scaladoc[`Spec1[F]`](izumi.distage.testkit.scalatest.Spec1) - `F[+_, +_]` - @scaladoc[`Spec2[F]`](izumi.distage.testkit.scalatest.Spec2) -- `F[-_, +_, +_]` - @scaladoc[`Spec3[F]`](izumi.distage.testkit.scalatest.Spec3) +- `ZIO[-R, +E, +A]` - @scaladoc[`SpecZIO`](izumi.distage.testkit.scalatest.SpecZIO) The effect monad is expected to support sync and async effects. `distage-testkit` provides this support for `Identity` , `monix`, `monix-bio`, `ZIO`, and monads wth instances of `cats-effect` or @ref[BIO](../bio/00_bio.md) typeclasses. For our demonstration application, the tests will use the `ZIO[-R, +E, +A]` effect type. This means we'll be -using `Spec3[ZIO]` for the test suite base class. +using `SpecZIO` for the test suite base class. The default config (`super.config`) has `pluginConfig`, which by default will scan the package the test is defined in for defined Plugin modules. See the @ref:[`distage-extension-plugins`](./distage-framework.md#plugins) documentation for @@ -121,9 +121,9 @@ classpath scanning, like so: import com.typesafe.config.ConfigFactory import distage.ModuleDef -import izumi.distage.testkit.scalatest.{AssertZIO, Spec3} +import izumi.distage.testkit.scalatest.{AssertZIO, SpecZIO} -abstract class Test extends Spec3[ZIO] with AssertZIO { +abstract class Test extends SpecZIO with AssertZIO { val defaultConfig = Config( starValue = 10, mangoValue = 256, @@ -258,7 +258,7 @@ The different effect types fix the `F[_]` argument for this syntax: - `Spec1`: `F[_]` - `Spec2`: `F[Throwable, _]` -- `Spec3`: `F[Any, Throwable, _]` +- `SpecZIO`: `ZIO[R, Any, _]` With our demonstration application we'll use this to verify the `Score.echoConfig` method. The `Config` required is from the `distage` object graph defined in `moduleOverrides`. @@ -296,7 +296,7 @@ __runTest__(new ScoreEffectsTest with MdocTest { def name = "ScoreEffectsTest" } #### Assertions with Effects with Environments -@scaladoc[The `in` method for `F[_, _, _]` effect types](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapper3) +@scaladoc[The `in` method for `ZIO`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapperZIO) supports injection of environments from the object graph in addition to simple assertions and assertions with effects. A test that verifies the `BonusService` in our demonstration would be: @@ -527,7 +527,7 @@ For `F[_]`, including `Identity`: and `b` will be injected from the object graph. The test case will fail if the effect fails or produces a failure assertion. -For `F[-_, +_, +_]`, it's same with `F[Any, _, _]`: +For `ZIO[-R, +E, +A]` in `SpecZIO`: - `in { ???: ZIO[C with D, _, Unit] }`: The test case is an effect requiring an environment. The test case will fail if the effect fails. The environment will be injected from the object graph. @@ -535,7 +535,7 @@ For `F[-_, +_, +_]`, it's same with `F[Any, _, _]`: test case will fail if the effect fails or produces a failure assertion. The environment will be injected from the object graph. - `in { (a: A, b: B) => ZIO[C with D, _, Assertion] }`: The test case is a function producing an - effect requiring an environment. All of `a: A`, `b: B`, `zio.ZEnvironment[C]` and `zio.ZEnvironment[D]` + effect requiring an environment. All of `a: A`, `b: B`, and `zio.ZEnvironment[C with D]` will be injected from the object graph. Provided by trait @scaladoc[AssertZIO](izumi.distage.testkit.scalatest.AssertZIO): @@ -654,7 +654,7 @@ class MemoizedLevel3 ``` ```scala mdoc:to-string -class SameLevel_1_WithActivationsOverride extends Spec3[ZIO] { +class SameLevel_1_WithActivationsOverride extends SpecZIO { override protected def config: TestConfig = { super.config.copy( memoizationRoots = Map( @@ -957,13 +957,13 @@ import distage.{Activation, DIKey, ModuleDef} import distage.StandardAxis.{Scene, Repo} import distage.plugins.PluginConfig import izumi.distage.testkit.TestConfig -import izumi.distage.testkit.scalatest.{AssertZIO, Spec3} +import izumi.distage.testkit.scalatest.{AssertZIO, SpecZIO} import leaderboard.model.{Score, UserId} import leaderboard.repo.{Ladder, Profiles} import leaderboard.zioenv.{ladder, rnd} import zio.{ZIO, IO} -abstract class LeaderboardTest extends Spec3[ZIO] with AssertZIO { +abstract class LeaderboardTest extends SpecZIO with AssertZIO { override def config = TestConfig( pluginConfig = PluginConfig.cached(packagesEnabled = Seq("leaderboard.plugins")), moduleOverrides = new ModuleDef { diff --git a/doc/microsite/src/main/tut/distage/reference.md b/doc/microsite/src/main/tut/distage/reference.md index 8fcf8403d9..5961c867d4 100644 --- a/doc/microsite/src/main/tut/distage/reference.md +++ b/doc/microsite/src/main/tut/distage/reference.md @@ -5,7 +5,11 @@ ``` Singleton bindings: - `make[X]` = create X using its constructor + - `makeTrait[X]` = create an abstract class or a trait `X` using [[izumi.distage.constructors.TraitConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]]) + - `makeFactory[X]` = create a "factory-like" abstract class or a trait `X` using [[izumi.distage.constructors.FactoryConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]]) - `make[X].from[XImpl]` = bind X to its subtype XImpl using XImpl's constructor + - `make[X].fromTrait[XImpl]` = bind X to its abstract class or a trait subtype XImpl, deriving constructor using [[izumi.distage.constructors.TraitConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-traits Auto-Traits feature]]) + - `make[X].fromFactory[XImpl]` = bind X to its "factory-like" abstract class or a trait subtype XImpl, deriving constructor using [[izumi.distage.constructors.FactoryConstructor]] ([[https://izumi.7mind.io/distage/basics.html#auto-factories Auto-Factories feature]]) - `make[X].from(myX)` = bind X to an already existing instance `myX` - `make[X].from { y: Y => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]] requesting an Y parameter - `make[X].from { y: Y @Id("special") => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]], requesting a named "special" Y parameter diff --git a/doc/microsite/src/main/tut/izumi.md b/doc/microsite/src/main/tut/izumi.md index 6300a5b126..7d182f5967 100644 --- a/doc/microsite/src/main/tut/izumi.md +++ b/doc/microsite/src/main/tut/izumi.md @@ -14,8 +14,8 @@ including the following components: 2. @ref[**distage-testkit**](distage/distage-testkit.md) – Hyper-pragmatic pure FP Test framework. Shares heavy resources globally across all test suites; lets you easily swap implementations of component; uses your effect type for parallelism. 3. @ref[**distage-framework-docker**](distage/distage-framework-docker.md) – A distage extension for using docker containers in tests or for local application runs, comes with example Postgres, Cassandra, Kafka & DynamoDB containers. 4. @ref[**LogStage**](logstage/00_logstage.md) – Automatic structural logs from Scala string interpolations, -5. @ref[**BIO**](bio/00_bio.md) - A typeclass hierarchy for tagless final style with Bifunctor and Trifunctor effect types. Focused on ergonomics and ease of use with zero boilerplate. -6. [**izumi-reflect**](https://github.com/zio/izumi-reflect) (moved to [zio/izumi-reflect](https://github.com/zio/izumi-reflect)) - Portable, lightweight and kind-polymorphic alternative to `scala-reflect`'s Typetag for Scala, Scala.js, Scala Native and ([soon](https://github.com/7mind/dotty-typetag-research)) Dotty +5. @ref[**BIO**](bio/00_bio.md) - A typeclass hierarchy for tagless final style with Bifunctor effect types. Focused on ergonomics and ease of use with zero boilerplate. +6. [**izumi-reflect**](https://github.com/zio/izumi-reflect) (moved to [zio/izumi-reflect](https://github.com/zio/izumi-reflect)) - Portable, lightweight and kind-polymorphic alternative to `scala-reflect`'s Typetag for Scala, Scala.js, Scala Native and Scala 3 7. @ref[**IdeaLingua**](idealingua/00_idealingua.md) (moved to [7mind/idealingua-v1](https://github.com/7mind/idealingua-v1)) – API Definition, Data Modeling and RPC language, optimized for fast prototyping – like gRPC or Swagger, but with a human face. Generates RPC servers and clients for Go, TypeScript, C# and Scala, 8. @ref[**Opinionated SBT plugins**](sbt/00_sbt.md) (moved to [7mind/sbtgen](https://github.com/7mind/sbtgen)) – Reduces verbosity of SBT builds and introduces new features – inter-project shared test scopes and BOM plugins (from Maven) 9. @ref[**Percept-Plan-Execute-Repeat (PPER)**](pper/00_pper.md) – A pattern that enables modeling very complex domains and orchestrate deadly complex processes a lot easier than you're used to. diff --git a/doc/microsite/src/main/tut/logstage/00_logstage.md b/doc/microsite/src/main/tut/logstage/00_logstage.md index e1dc0b072c..b337a146be 100644 --- a/doc/microsite/src/main/tut/logstage/00_logstage.md +++ b/doc/microsite/src/main/tut/logstage/00_logstage.md @@ -291,35 +291,6 @@ I 2021-08-17T15:07:54.367 (00_logstage.md:235) ….controllerFunction.232.233 [ I 2021-08-17T15:07:54.371 (00_logstage.md:237) …on.App12.controllerFunction [2280:Thread-60 ] Some log after controller function (without correlation_id) ``` -```scala mdoc:invisible -// FIXME wtf - -//### Tagless trifunctor support -// -//`LogIO3Ask.log` adds environment support for all trifunctor effect types with an instance of `MonadAsk3[F]` typeclass from @ref[BIO](../bio/00_bio.md) hierarchy. -// -//Example: -// -//```scala mdoc:to-string:reset -//import logstage.{LogIO3, LogIO3Ask, IzLogger} -//import logstage.LogIO3Ask.log -//import zio.{Has, ZIO} -// -//def fn[F[-_, +_, +_]: LogIO3Ask]: F[Has[LogIO3[F]], Nothing, Unit] = { -// log.info(s"I'm logging with ${log}stage!") -//} -// -//val logger = LogIO3.fromLogger(IzLogger()) -// -//import izumi.functional.bio.UnsafeRun2 -// -//val runtime = UnsafeRun2.createZIO() -// -//runtime.unsafeRun { -// fn[ZIO].provideEnvironment(ZEnvironment(logger)) -//} -``` - Custom JSON rendering with LogstageCodec ---------------------------------------- diff --git a/fundamentals/fundamentals-bio/.js/src/main/scala/izumi/functional/bio/BlockingIO2.scala b/fundamentals/fundamentals-bio/.js/src/main/scala/izumi/functional/bio/BlockingIO2.scala new file mode 100644 index 0000000000..e9ea0d58da --- /dev/null +++ b/fundamentals/fundamentals-bio/.js/src/main/scala/izumi/functional/bio/BlockingIO2.scala @@ -0,0 +1,30 @@ +package izumi.functional.bio + +import izumi.functional.bio.PredefinedHelper.Predefined +import izumi.fundamentals.orphans.`zio.ZIO` + +/** Scala.js does not support blockingIO */ +trait BlockingIO2[F[_, _]] extends BlockingIOInstances with DivergenceHelper with PredefinedHelper { + def shiftBlocking[E, A](f: F[E, A]): F[E, A] + def syncBlocking[A](f: => A): F[Throwable, A] + def syncInterruptibleBlocking[A](f: => A): F[Throwable, A] +} +object BlockingIO2 { + def apply[F[_, _]: BlockingIO2]: BlockingIO2[F] = implicitly +} + +private[bio] sealed trait BlockingIOInstances +object BlockingIOInstances { + + implicit def fromSyncSafe2[F[+_, +_]: SyncSafe2]: Predefined.Of[BlockingIO2[F]] = Predefined(new BlockingIO2[F] { + override def shiftBlocking[E, A](f: F[E, A]): F[E, A] = f + override def syncBlocking[A](f: => A): F[Throwable, A] = SyncSafe2[F].syncSafe(f) + override def syncInterruptibleBlocking[A](f: => A): F[Throwable, A] = SyncSafe2[F].syncSafe(f) + }) + + def BlockingZIODefault: BlockingIO2[zio.IO] = fromSyncSafe2[zio.IO] + + def BlockingZIODefaultR[F[-_, +_, +_]: `zio.ZIO`, R]: BlockingIO2[F[R, +_, +_]] = + fromSyncSafe2[zio.ZIO[R, +_, +_]].asInstanceOf[BlockingIO2[F[R, +_, +_]]] + +} diff --git a/fundamentals/fundamentals-bio/.js/src/main/scala/izumi/functional/bio/BlockingIO3.scala b/fundamentals/fundamentals-bio/.js/src/main/scala/izumi/functional/bio/BlockingIO3.scala deleted file mode 100644 index 3f978e8901..0000000000 --- a/fundamentals/fundamentals-bio/.js/src/main/scala/izumi/functional/bio/BlockingIO3.scala +++ /dev/null @@ -1,37 +0,0 @@ -package izumi.functional.bio - -import izumi.functional.bio.DivergenceHelper.{Divergent, Nondivergent} -import izumi.functional.bio.PredefinedHelper.Predefined - -/** Scala.js does not support blockingIO */ -trait BlockingIO3[F[_, _, _]] extends BlockingIOInstances with DivergenceHelper with PredefinedHelper { - def shiftBlocking[R, E, A](f: F[R, E, A]): F[R, E, A] - def syncBlocking[A](f: => A): F[Any, Throwable, A] - def syncInterruptibleBlocking[A](f: => A): F[Any, Throwable, A] -} -object BlockingIO3 { - def apply[F[-_, +_, +_]: BlockingIO3]: BlockingIO3[F] = implicitly -} - -private[bio] sealed trait BlockingIOInstances -object BlockingIOInstances extends LowPriorityBlockingIOInstances { - - implicit def fromSyncSafe3[F[-_, +_, +_]: SyncSafe3]: Predefined.Of[BlockingIO3[F]] = Predefined(new BlockingIO3[F] { - override def shiftBlocking[R, E, A](f: F[R, E, A]): F[R, E, A] = f - override def syncBlocking[A](f: => A): F[Any, Throwable, A] = SyncSafe3[F].syncSafe(f) - override def syncInterruptibleBlocking[A](f: => A): F[Any, Throwable, A] = SyncSafe3[F].syncSafe(f) - }) - - def BlockingZIODefault: BlockingIO3[zio.ZIO] = fromSyncSafe3[zio.ZIO] - -} - -sealed trait LowPriorityBlockingIOInstances { - - @inline implicit final def blockingConvert3To2[C[f[-_, +_, +_]] <: BlockingIO3[f], FR[-_, +_, +_], R]( - implicit BlockingIO3: C[FR] { type Divergence = Nondivergent } - ): Divergent.Of[BlockingIO2[FR[R, +_, +_]]] = { - Divergent(BlockingIO3.asInstanceOf[BlockingIO2[FR[R, +_, +_]]]) - } - -} diff --git a/fundamentals/fundamentals-bio/.js/src/test/scala/izumi/functional/bio/test/PlatformDependentTestBase.scala b/fundamentals/fundamentals-bio/.js/src/test/scala/izumi/functional/bio/test/PlatformDependentTestBase.scala index 68f1c1e6a8..a79bc74832 100644 --- a/fundamentals/fundamentals-bio/.js/src/test/scala/izumi/functional/bio/test/PlatformDependentTestBase.scala +++ b/fundamentals/fundamentals-bio/.js/src/test/scala/izumi/functional/bio/test/PlatformDependentTestBase.scala @@ -1,8 +1,7 @@ package izumi.functional.bio.test -import izumi.functional.bio.{BlockingIO3, BlockingIOInstances} -import zio.ZIO +import izumi.functional.bio.{BlockingIO2, BlockingIOInstances} trait PlatformDependentTestBase { - implicit val blockingIO3: BlockingIO3[ZIO] = BlockingIOInstances.fromSyncSafe3 + implicit val blockingIO3: BlockingIO2[zio.IO] = BlockingIOInstances.fromSyncSafe2 } diff --git a/fundamentals/fundamentals-bio/.jvm/src/main/scala/izumi/functional/bio/BlockingIO3.scala b/fundamentals/fundamentals-bio/.jvm/src/main/scala/izumi/functional/bio/BlockingIO2.scala similarity index 64% rename from fundamentals/fundamentals-bio/.jvm/src/main/scala/izumi/functional/bio/BlockingIO3.scala rename to fundamentals/fundamentals-bio/.jvm/src/main/scala/izumi/functional/bio/BlockingIO2.scala index 4926d672a2..8273a351e8 100644 --- a/fundamentals/fundamentals-bio/.jvm/src/main/scala/izumi/functional/bio/BlockingIO3.scala +++ b/fundamentals/fundamentals-bio/.jvm/src/main/scala/izumi/functional/bio/BlockingIO2.scala @@ -1,18 +1,20 @@ package izumi.functional.bio +import izumi.functional.bio.BlockingIOInstances.BlockingZio import izumi.functional.bio.PredefinedHelper.Predefined +import izumi.fundamentals.orphans.`zio.ZIO` import izumi.fundamentals.platform.language.Quirks.Discarder import zio.internal.stacktracer.{InteropTracer, Tracer} import zio.stacktracer.TracingImplicits.disableAutoTrace import zio.{Executor, ZIO} -trait BlockingIO3[F[-_, +_, +_]] extends BlockingIOInstances with DivergenceHelper with PredefinedHelper { +trait BlockingIO2[F[+_, +_]] extends BlockingIOInstances with DivergenceHelper with PredefinedHelper { /** Execute a blocking action in `Blocking` thread pool, current task will be safely parked until the blocking task finishes * */ - def shiftBlocking[R, E, A](f: F[R, E, A]): F[R, E, A] + def shiftBlocking[E, A](f: F[E, A]): F[E, A] /** Execute a blocking impure task in `Blocking` thread pool, current task will be safely parked until the blocking task finishes * */ - def syncBlocking[A](f: => A): F[Any, Throwable, A] + def syncBlocking[A](f: => A): F[Throwable, A] /** Execute a blocking impure task in `Blocking` thread pool, current task will be safely parked until the blocking task finishes * @@ -21,18 +23,18 @@ trait BlockingIO3[F[-_, +_, +_]] extends BlockingIOInstances with DivergenceHelp * * THIS IS USUALLY UNSAFE unless calling well-written libraries that specifically handle [[java.lang.InterruptedException]] */ - def syncInterruptibleBlocking[A](f: => A): F[Any, Throwable, A] + def syncInterruptibleBlocking[A](f: => A): F[Throwable, A] } -object BlockingIO3 { - def apply[F[-_, +_, +_]: BlockingIO3]: BlockingIO3[F] = implicitly +object BlockingIO2 { + def apply[F[+_, +_]: BlockingIO2]: BlockingIO2[F] = implicitly } private[bio] sealed trait BlockingIOInstances -object BlockingIOInstances extends BlockingIOLowPriorityVersionSpecific { +object BlockingIOInstances extends BlockingIOInstancesLowPriority { - def BlockingZIOFromExecutor(blockingExecutor: Executor): BlockingIO3[ZIO] = new BlockingIO3[ZIO] { - override def shiftBlocking[R, E, A](f: ZIO[R, E, A]): ZIO[R, E, A] = { + def BlockingZIOFromExecutor[R](blockingExecutor: Executor): BlockingIO2[ZIO[R, +_, +_]] = new BlockingIO2[ZIO[R, +_, +_]] { + override def shiftBlocking[E, A](f: ZIO[R, E, A]): ZIO[R, E, A] = { implicit val trace: zio.Trace = Tracer.newTrace ZIO.environmentWithZIO[R] { r => @@ -57,19 +59,31 @@ object BlockingIOInstances extends BlockingIOLowPriorityVersionSpecific { } } - @inline implicit final def BlockingZIODefault: Predefined.Of[BlockingIO3[ZIO]] = Predefined(new BlockingIO3[ZIO] { - override def shiftBlocking[R, E, A](f: ZIO[R, E, A]): ZIO[R, E, A] = ZIO.blocking(f)(Tracer.newTrace) + /** + * This instance uses 'no more orphans' trick to provide an Optional instance + * only IFF you have zio-core as a dependency without REQUIRING a zio-core dependency. + * + * Optional instance via https://blog.7mind.io/no-more-orphans.html + */ + @inline implicit final def BlockingZIODefault[Zio[-_, +_, +_]: `zio.ZIO`]: Predefined.Of[BlockingIO2[Zio[Any, +_, +_]]] = + Predefined(BlockingZio.asInstanceOf[BlockingIO2[Zio[Any, +_, +_]]]) + + object BlockingZio extends BlockingZio[Any] + open class BlockingZio[R] extends BlockingIO2[ZIO[R, +_, +_]] { + override def shiftBlocking[E, A](f: ZIO[R, E, A]): ZIO[R, E, A] = ZIO.blocking(f)(Tracer.newTrace) + override def syncBlocking[A](f: => A): ZIO[Any, Throwable, A] = { val byName: () => A = () => f implicit val trace: zio.Trace = InteropTracer.newTrace(byName) ZIO.attemptBlocking(f) } + override def syncInterruptibleBlocking[A](f: => A): ZIO[Any, Throwable, A] = { val byName: () => A = () => f implicit val trace: zio.Trace = InteropTracer.newTrace(byName) ZIO.attemptBlockingInterrupt(f) } - }) + } // @inline final def BlockingMonixBIOFromScheduler(ioScheduler: Scheduler): BlockingIO2[monix.bio.IO] = new BlockingIO2[monix.bio.IO] { // override def shiftBlocking[R, E, A](f: monix.bio.IO[E, A]): monix.bio.IO[E, A] = f.executeOn(ioScheduler, forceAsync = true) @@ -79,3 +93,8 @@ object BlockingIOInstances extends BlockingIOLowPriorityVersionSpecific { disableAutoTrace.discard() } + +sealed trait BlockingIOInstancesLowPriority { + @inline implicit final def BlockingZIODefaultR[Zio[-_, +_, +_]: `zio.ZIO`, R]: Predefined.Of[BlockingIO2[Zio[R, +_, +_]]] = + Predefined(BlockingZio.asInstanceOf[BlockingIO2[Zio[R, +_, +_]]]) +} diff --git a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/ErrorAccumulatingOpsTestZIO.scala b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/ErrorAccumulatingOpsTestZIO.scala new file mode 100644 index 0000000000..a95d4e7a85 --- /dev/null +++ b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/ErrorAccumulatingOpsTestZIO.scala @@ -0,0 +1,8 @@ +package izumi.functional.bio + +final class ErrorAccumulatingOpsTestZIO extends ErrorAccumulatingOpsTest[zio.IO] { + private val runner: UnsafeRun2[zio.IO] = UnsafeRun2.createZIO() + + override implicit def F: Error2[zio.IO] = Root.BIOZIO + override def unsafeRun[E, A](f: zio.IO[E, A]): Either[E, A] = runner.unsafeRun(f.attempt) +} diff --git a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/laws/MiniBIOLawsTest.scala b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/laws/MiniBIOLawsTest.scala index a3c28fe342..def8aaa05b 100644 --- a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/laws/MiniBIOLawsTest.scala +++ b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/laws/MiniBIOLawsTest.scala @@ -8,8 +8,8 @@ import izumi.functional.bio.laws.env.MiniBIOEnv class MiniBIOLawsTest extends CatsLawsTestBase with MiniBIOEnv { val syncTests: SyncTests[MiniBIO[Throwable, _]] = new SyncTests[MiniBIO[Throwable, _]] { - override val laws = new SyncLaws[MiniBIO[Throwable, _]] { - override val F = Sync[MiniBIO[Throwable, _]] + override val laws: SyncLaws[MiniBIO[Throwable, _]] = new SyncLaws[MiniBIO[Throwable, _]] { + override val F: Sync[MiniBIO[Throwable, _]] = Sync[MiniBIO[Throwable, _]] } } diff --git a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/retry/SchedulerTest.scala b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/retry/SchedulerTest.scala index af78d71704..d9faa286fd 100644 --- a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/retry/SchedulerTest.scala +++ b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/retry/SchedulerTest.scala @@ -3,10 +3,10 @@ package izumi.functional.bio.retry import izumi.functional.bio.Clock1.ClockAccuracy import izumi.functional.bio.__VersionSpecificDurationConvertersCompat.toFiniteDuration import izumi.functional.bio.retry.RetryPolicy.{ControllerDecision, RetryFunction} -import izumi.functional.bio.{Clock2, Clock3, Error2, F, Functor2, IO2, Monad2, Primitives2, Ref2, Root, Temporal2, Temporal3, TemporalInstances, UnsafeRun2} +import izumi.functional.bio.{Clock2, Error2, F, Functor2, IO2, Monad2, Primitives2, Ref2, Temporal2, TemporalInstances, UnsafeRun2} import org.scalatest.Assertion import org.scalatest.wordspec.AnyWordSpec -import zio.ZIO +import zio.IO import java.time.{Instant, ZoneOffset, ZonedDateTime} import scala.annotation.tailrec @@ -18,15 +18,15 @@ class SchedulerTest extends AnyWordSpec { // private val monixRunner: UnsafeRun2[bio.IO] = UnsafeRun2.createMonixBIO(Scheduler.global, bio.IO.defaultOptions) // private val monixScheduler: Scheduler2[bio.IO] = new SchedulerMonix(implicitly[cats.effect.kernel.Clock[bio.UIO]]) - private val zioClock: Clock3[ZIO] = Clock3[ZIO] - private val zioTemporal: Temporal3[ZIO] = TemporalInstances.Temporal3Zio - private val zioScheduler: Scheduler2[zio.IO] = SchedulerInstances.SchedulerFromTemporalAndClock(Root.Convert3To2(zioTemporal), zioClock) - private val zioRunner: UnsafeRun2[zio.IO] = UnsafeRun2.createZIO[Any]() + private val zioClock: Clock2[IO] = Clock2[IO] + private val zioTemporal: Temporal2[IO] = TemporalInstances.Temporal2Zio + private val zioScheduler: Scheduler2[IO] = SchedulerInstances.SchedulerFromTemporalAndClock(zioTemporal, zioClock) + private val zioRunner: UnsafeRun2[IO] = UnsafeRun2.createZIO[Any]() private object implicits { - implicit val zioClockImplicit: Clock3[ZIO] = zioClock - implicit val zioTemporalImplicit: Temporal3[ZIO] = zioTemporal - implicit val zioSchedulerImplicit: Scheduler2[zio.IO] = zioScheduler + implicit val zioClockImplicit: Clock2[IO] = zioClock + implicit val zioTemporalImplicit: Temporal2[IO] = zioTemporal + implicit val zioSchedulerImplicit: Scheduler2[IO] = zioScheduler } def toZonedDateTime(epochMillis: Long): ZonedDateTime = { @@ -36,8 +36,8 @@ class SchedulerTest extends AnyWordSpec { "Scheduler" should { "recurs with zero or negative argument repeats effect 0 additional time" in { - val zio1 = zioRunner.unsafeRun(simpleCounter[zio.IO, Long](zioScheduler)(RetryPolicy.recurs[zio.IO](0))) - val zio2 = zioRunner.unsafeRun(simpleCounter[zio.IO, Long](zioScheduler)(RetryPolicy.recurs[zio.IO](-5))) + val zio1 = zioRunner.unsafeRun(simpleCounter[IO, Long](zioScheduler)(RetryPolicy.recurs[IO](0))) + val zio2 = zioRunner.unsafeRun(simpleCounter[IO, Long](zioScheduler)(RetryPolicy.recurs[IO](-5))) // val m1 = monixRunner.unsafeRun(simpleCounter[bio.IO, Long](monixScheduler)(RetryPolicy.recurs[bio.IO](0))) // val m2 = monixRunner.unsafeRun(simpleCounter[bio.IO, Long](monixScheduler)(RetryPolicy.recurs[bio.IO](-283))) @@ -49,14 +49,14 @@ class SchedulerTest extends AnyWordSpec { } "recur N times" in { - val res1 = zioRunner.unsafeRun(simpleCounter[zio.IO, Long](zioScheduler)(RetryPolicy.recurs[zio.IO](3))) + val res1 = zioRunner.unsafeRun(simpleCounter[IO, Long](zioScheduler)(RetryPolicy.recurs[IO](3))) // val res2 = monixRunner.unsafeRun(simpleCounter[bio.IO, Long](monixScheduler)(RetryPolicy.recurs[bio.IO](3))) assert(res1 == 4) // assert(res2 == 4) } "recur while predicate is true" in { - val res1 = zioRunner.unsafeRun(simpleCounter[zio.IO, Int](zioScheduler)(RetryPolicy.recursWhile[zio.IO, Int](_ < 3))) + val res1 = zioRunner.unsafeRun(simpleCounter[IO, Int](zioScheduler)(RetryPolicy.recursWhile[IO, Int](_ < 3))) // val res2 = monixRunner.unsafeRun(simpleCounter[bio.IO, Int](monixScheduler)(RetryPolicy.recursWhile[bio.IO, Int](_ < 3))) assert(res1 == 3) // assert(res2 == 3) @@ -106,7 +106,7 @@ class SchedulerTest extends AnyWordSpec { } val expected = List(0, 1, 2, 3).map(i => (i * 100).millis) - val zioTest = test(zioRunner)(policy[zio.IO].action, ZonedDateTime.now(), 4) + val zioTest = test(zioRunner)(policy[IO].action, ZonedDateTime.now(), 4) assert(zioTest == expected) // val monixTest = test(monixRunner)(policy[bio.IO].action, ZonedDateTime.now(), 4) @@ -127,7 +127,7 @@ class SchedulerTest extends AnyWordSpec { "compute exponential backoff intervals correctly" in { val baseDelay = 100 - val policy1 = RetryPolicy.exponential[zio.IO](baseDelay.millis) + val policy1 = RetryPolicy.exponential[IO](baseDelay.millis) // val policy2 = RetryPolicy.exponential[bio.IO](baseDelay.millis) def test[F[+_, +_]](runner: UnsafeRun2[F])(rf: RetryFunction[F, Any, FiniteDuration], numOfRetries: Int): Unit = { @@ -151,7 +151,7 @@ class SchedulerTest extends AnyWordSpec { } "compute fixed intervals correctly" in { - val policy1 = RetryPolicy.fixed[zio.IO](100.millis) + val policy1 = RetryPolicy.fixed[IO](100.millis) // val policy2 = RetryPolicy.fixed[bio.IO](100.millis) def test[F[+_, +_]](runner: UnsafeRun2[F])(rf: RetryFunction[F, Any, Long], numOfTries: Int): mutable.Seq[Long] = { @@ -177,8 +177,8 @@ class SchedulerTest extends AnyWordSpec { "combine different policies properly" in { import implicits.* - val intersectPZio = RetryPolicy.recursWhile[zio.IO, Boolean](identity) && RetryPolicy.recurs(4) - val unionPZio = RetryPolicy.recursWhile[zio.IO, Boolean](identity) || RetryPolicy.recurs(4) + val intersectPZio = RetryPolicy.recursWhile[IO, Boolean](identity) && RetryPolicy.recurs(4) + val unionPZio = RetryPolicy.recursWhile[IO, Boolean](identity) || RetryPolicy.recurs(4) val effZio = (counter: zio.Ref[Int]) => counter.updateAndGet(_ + 1).map(_ < 3) // val intersectPMonix = RetryPolicy.recursWhile[bio.IO, Boolean](identity) && RetryPolicy.recurs(4) @@ -197,11 +197,11 @@ class SchedulerTest extends AnyWordSpec { res2 <- counter2.get _ = assert(res2 == 5) - np1 = RetryPolicy.spaced[zio.IO](300.millis) && RetryPolicy.spaced[zio.IO](200.millis) + np1 = RetryPolicy.spaced[IO](300.millis) && RetryPolicy.spaced[IO](200.millis) delays1 <- testTimedScheduler(zio.ZIO.unit)(np1, 1) _ = assert(delays1 == Vector(300.millis)) - np2 = RetryPolicy.spaced[zio.IO](300.millis) || RetryPolicy.spaced[zio.IO](200.millis) + np2 = RetryPolicy.spaced[IO](300.millis) || RetryPolicy.spaced[IO](200.millis) delays2 <- testTimedScheduler(zio.ZIO.unit)(np2, 1) _ = assert(delays2 == Vector(200.millis)) } yield () @@ -279,7 +279,7 @@ class SchedulerTest extends AnyWordSpec { import implicits.* - val zioRetries = zioRunner.unsafeRun(test[zio.IO]()) + val zioRetries = zioRunner.unsafeRun(test[IO]()) assert(zioRetries == ((10, 10))) } @@ -309,8 +309,8 @@ class SchedulerTest extends AnyWordSpec { zioRunner.unsafeRun { for { - _ <- test[zio.IO](2, 3) - _ <- test[zio.IO](1, -1) + _ <- test[IO](2, 3) + _ <- test[IO](1, -1) } yield () } diff --git a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/BlockingIOSyntaxTest.scala b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/BlockingIOSyntaxTest.scala index f8394160bd..5bb7d35824 100644 --- a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/BlockingIOSyntaxTest.scala +++ b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/BlockingIOSyntaxTest.scala @@ -1,20 +1,16 @@ package izumi.functional.bio.test -import izumi.functional.bio.{BlockingIO2, BlockingIO3, F, Functor2, Monad3} +import izumi.functional.bio.{BlockingIO2, F, Functor2} import org.scalatest.wordspec.AnyWordSpec import zio.{IO, ZIO} class BlockingIOSyntaxTest extends AnyWordSpec { - def `attach BlockingIO methods to a trifunctor BIO`[F[-_, +_, +_]: Monad3: BlockingIO3]: F[Any, Throwable, Int] = { - F.syncBlocking(2) - } def `attach BlockingIO methods to a bifunctor BIO`[F[+_, +_]: Functor2: BlockingIO2]: F[Throwable, Int] = { F.syncBlocking(2) } locally { val _: ZIO[Any, Throwable, Int] = { - `attach BlockingIO methods to a trifunctor BIO`[ZIO] `attach BlockingIO methods to a bifunctor BIO`[IO] } } @@ -24,7 +20,7 @@ class BlockingIOSyntaxTest extends AnyWordSpec { def hello = BlockingIO2[F].syncBlocking(println("hello world!")) } def zioBlockingApply2(): ZIO[Any, Throwable, Unit] = BlockingIO2.apply[zio.IO].syncBlocking(()) - def zioBlockingApply3(): ZIO[Any, Throwable, Unit] = BlockingIO3.apply.syncBlocking(()) + def zioBlockingApply3(): ZIO[Any, Throwable, Unit] = BlockingIO2.apply.syncBlocking(()) assert(new X[zio.ZIO[Any, +_, +_]].hello != null) locally { diff --git a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/SoundnessTest.scala b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/SoundnessTest.scala deleted file mode 100644 index f7aba8b536..0000000000 --- a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/SoundnessTest.scala +++ /dev/null @@ -1,59 +0,0 @@ -package izumi.functional.bio.test - -import izumi.functional.bio._ -import org.scalatest.exceptions.TestFailedException -import org.scalatest.wordspec.AnyWordSpec - -class SoundnessTest extends AnyWordSpec { - /* - There's a `<: RootBifunctor[f]` constraint now in Convert3To2 - to prevent trifunctorial classes from being converted to bifunctorial, - but it's still circumventable by inheriting from Functor + Trifunctor - hierarchies in one implicit instance, like `Async3[F] with Local3[F]`. - - A solution may be to add a marker type member inside instances: - - trait FunctorialityHelper { - type Functoriality - } - object FunctorialityHelper { - type Monofunctorial - type Bifunctorial <: Monofunctorial - type Trifunctorial <: Bifunctorial - } - - And then add a LUB guard to check that type member is _no more specific_ - than `Bifunctorial`: `(implicit guard: Lub[F#Functoriality, Trifunctorial, Bifunctorial])` - */ - - type BIOArrow2[F[+_, +_]] = Arrow3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type FR[F[-_, +_, +_], R] = { type l[+E, +A] = F[R, E, A] } - - "Cannot convert polymorphic BIOArrow into a bifunctor typeclass (normally)" in { - val res = intercept[TestFailedException](assertCompiles(""" - def valueF[F[-_, +_, +_]: Arrow3: MonadAsk3: IO3] = { - val FA: BIOArrow2[FR[F, Int]#l] = implicitly[BIOArrow2[FR[F, Int]#l]] - FA.andThen(F.unit, F.access((i: Int) => F.sync(println(i)))) - } - implicit val u: zio.Unsafe = zio.Unsafe.unsafe(identity) - zio.Runtime.default.unsafe.run(valueF[zio.ZIO].provideEnvironment(zio.ZEnvironment(1))) - """)) - assert(!res.getMessage.contains("not found type")) - assert(res.getMessage.contains("implicit error") || res.getMessage.contains("No given instance") || res.getMessage.contains("implicit value")) - assert(res.getMessage contains "BIOArrow2") - } - - "Cannot convert ZIO BIOArrow instance into a bifunctor typeclass (normally)" in { - val res = intercept[TestFailedException](assertCompiles(""" - def valueZIO = { - val F: BIOArrow2[FR[zio.ZIO, Int]#l] = implicitly[BIOArrow2[FR[zio.ZIO, Int]#l]] - F.andThen(zio.ZIO.unit, zio.ZIO.environmentWithZIO[Int](i => zio.ZIO.attempt(println(i)))) - } - implicit val u: zio.Unsafe = zio.Unsafe.unsafe(identity) - zio.Runtime.default.unsafe.run(valueZIO.provideEnvironment(zio.ZEnvironment(1))) - """)) - assert(!res.getMessage.contains("not found type")) - assert(res.getMessage.contains("implicit error") || res.getMessage.contains("No given instance") || res.getMessage.contains("implicit value")) - assert(res.getMessage contains "BIOArrow2") - } -} diff --git a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/SyntaxTest.scala b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/SyntaxTest.scala index 9c7a007b1f..4a6e5e3928 100644 --- a/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/SyntaxTest.scala +++ b/fundamentals/fundamentals-bio/.jvm/src/test/scala/izumi/functional/bio/test/SyntaxTest.scala @@ -1,7 +1,7 @@ package izumi.functional.bio.test import izumi.functional.bio.data.{Morphism1, Morphism2, Morphism3} -import izumi.functional.bio.retry.{RetryPolicy, Scheduler2, Scheduler3} +import izumi.functional.bio.retry.{RetryPolicy, Scheduler2} import izumi.fundamentals.platform.language.{IzScala, ScalaRelease} import org.scalatest.wordspec.AnyWordSpec @@ -9,9 +9,6 @@ import scala.concurrent.duration.* class SyntaxTest extends AnyWordSpec { - // FIXME wtf - def fixme(): Unit = () - "BIOParallel.zipPar/zipParLeft/zipParRight/zipWithPar is callable" in { import izumi.functional.bio.Parallel2 @@ -26,20 +23,6 @@ class SyntaxTest extends AnyWordSpec { x[zio.IO](zio.ZIO.succeed(()), zio.ZIO.succeed(())) } - "BIOParallel3.zipPar/zipParLeft/zipParRight/zipWithPar is callable" in { - import izumi.functional.bio.Parallel3 - - def x[F[-_, +_, +_]: Parallel3](a: F[Any, Nothing, Unit], b: F[Any, Nothing, Unit]) = { - a.zipPar(b) - a.zipParLeft(b) - a.zipParRight(b) - a.zipWithPar(b)((a, b) => (a, b)) - a.flatMap(_ => b) - } - - x[zio.ZIO](zio.ZIO.succeed(()), zio.ZIO.succeed(())) - } - "BIOConcurrent syntax / attachment / conversion works" in { import izumi.functional.bio.{Concurrent2, F} @@ -56,22 +39,6 @@ class SyntaxTest extends AnyWordSpec { x[zio.IO](zio.ZIO.succeed(()), zio.ZIO.succeed(())) } - "BIOConcurrent3 syntax / attachment / conversion works" in { - import izumi.functional.bio.{Concurrent3, F} - - def x[F[-_, +_, +_]: Concurrent3](a: F[Any, Nothing, Unit], b: F[Any, Nothing, Unit]) = { - a.zipPar(b) - a.zipParLeft(b) - a.zipParRight(b) - a.zipWithPar(b)((a, b) => (a, b)) - a.flatMap(_ => b) - a.guaranteeCase(_ => a.race(b).widenError[Throwable].catchAll(_ => F.unit orElse F.uninterruptible(F.race(a, b))).void) - F.unit - } - - x[zio.ZIO](zio.ZIO.succeed(()), zio.ZIO.succeed(())) - } - "Async2.race is callable along with all BIOParallel syntax" in { import izumi.functional.bio.Async2 @@ -88,21 +55,6 @@ class SyntaxTest extends AnyWordSpec { // x[bio.IO](bio.UIO.evalTotal(()), bio.UIO.evalTotal(())) } - "Async3.race is callable along with all BIOParallel syntax" in { - import izumi.functional.bio.Async3 - - def x[F[-_, +_, +_]: Async3](a: F[Any, Nothing, Unit], b: F[Any, Nothing, Unit]) = { - a.zipPar(b) - a.zipParLeft(b) - a.zipParRight(b) - a.zipWithPar(b)((a, b) => (a, b)) - a.race(b) - a.flatMap(_ => b) - } - - x[zio.ZIO](zio.ZIO.succeed(()), zio.ZIO.succeed(())) - } - "IO2.apply is callable" in { import izumi.functional.bio.IO2 @@ -171,42 +123,6 @@ class SyntaxTest extends AnyWordSpec { // zz[bio.IO] } - "Bracket3.bracketCase & guaranteeCase are callable" in { - import izumi.functional.bio.{Bracket3, Exit, F} - - def x[F[-_, +_, +_]: Bracket3]: F[Any, Throwable, Int] = { - F.pure(None).bracketCase(release = { - (_, _: Exit[Throwable, Int]) => F.unit - })(_ => F.pure(1)) - } - def y[F[-_, +_, +_]: Bracket3]: F[Any, Throwable, Int] = { - F.pure(None).bracketCase { - (_, exit: Exit[Throwable, Int]) => - exit match { - case Exit.Success(x) => F.pure(x).as(()) - case _ => F.unit - } - }(_ => F.pure(1)) - } - def z[F[-_, +_, +_]: Bracket3]: F[Any, Throwable, Int] = { - F.pure(1).guaranteeCase { - case Exit.Success(x) => F.pure(x).as(()) - case _ => F.unit - } - } - def zz[F[-_, +_, +_]: Bracket3]: F[Any, Throwable, Int] = { - F.when(F.pure(false).widenError[Throwable])(F.unit).as(1).guaranteeCase { - case Exit.Success(x) => F.pure(x).as(()) - case _ => F.unit - }.widenError[Throwable] - } - - x[zio.ZIO] - y[zio.ZIO] - z[zio.ZIO] - zz[zio.ZIO] - } - "Bracket2.bracketOnFailure & guaranteeOnFailure are callable" in { import izumi.functional.bio.{Bracket2, Exit, F} @@ -238,32 +154,6 @@ class SyntaxTest extends AnyWordSpec { // zz[bio.IO] } - "Bracket3.bracketOnFailure & guaranteeOnFailure are callable" in { - import izumi.functional.bio.{Bracket3, Exit, F} - - def x[F[-_, +_, +_]: Bracket3]: F[Any, Throwable, Int] = { - F.pure(None).bracketOnFailure(cleanupOnFailure = { - (_, _: Exit.Failure[Throwable]) => F.unit - })(_ => F.pure(1)) - } - def y[F[-_, +_, +_]: Bracket3]: F[Any, Throwable, Int] = { - F.pure(None).bracketOnFailure { - (_, _: Exit.Failure[Throwable]) => F.unit - }(_ => F.pure(1)) - } - def z[F[-_, +_, +_]: Bracket3]: F[Any, Throwable, Int] = { - F.pure(1).guaranteeOnFailure(_ => F.unit) - } - def zz[F[-_, +_, +_]: Bracket3]: F[Any, Throwable, Int] = { - F.when(F.pure(false).widenError[Throwable])(F.unit).as(1).guaranteeOnFailure(_ => F.unit).widenError[Throwable] - } - - x[zio.ZIO] - y[zio.ZIO] - z[zio.ZIO] - zz[zio.ZIO] - } - "BIO.when/unless/ifThenElse have nice inference" in { import izumi.functional.bio.{F, Monad2} @@ -334,7 +224,7 @@ class SyntaxTest extends AnyWordSpec { } "F summoner examples" in { - import izumi.functional.bio.{F, Fork2, Fork3, Functor2, Monad2, Monad3, Primitives2, Primitives3, Temporal2, PrimitivesM3} + import izumi.functional.bio.{F, Fork2, Functor2, Monad2, Primitives2, Temporal2, PrimitivesM2} def x[F[+_, +_]: Monad2] = { F.when(false)(F.unit) @@ -347,35 +237,26 @@ class SyntaxTest extends AnyWordSpec { F.map(z[F])(_ => ()) } def `attach Primitives2 & Fork2 methods even when they aren't imported`[F[+_, +_]: Monad2: Primitives2: Fork2]: F[Nothing, Int] = { - F.fork[Any, Nothing, Int] { + F.fork[Nothing, Int] { F.mkRef(4).flatMap(r => r.update(_ + 5) *> r.get.map(_ - 1)) }.flatMap(_.join) *> F.mkRef(4).flatMap(r => r.update(_ + 5) *> r.get.map(_ - 1)).fork.flatMap(_.join) } - def `attach Primitives2 & Fork23 methods to a trifunctor BIO even when not imported`[FR[-_, +_, +_]: Monad3: Primitives3: Fork3]: FR[Nothing, Nothing, Int] = { - F.fork(F.mkRef(4).flatMap(r => r.update(_ + 5) *> r.get.map(_ - 1))).flatMap(_.join) *> - F.mkRef(4).flatMap(r => r.update(_ + 5) *> r.get.map(_ - 1)).fork.flatMap(_.join) - } - def `attach PrimitivesM2 methods to a trifunctor BIO even when not imported`[FR[-_, +_, +_]: Monad3: PrimitivesM3]: FR[Nothing, Nothing, Int] = { + def `attach PrimitivesM2 methods to BIO even when not imported`[F[+_, +_]: Monad2: PrimitivesM2]: F[Nothing, Int] = { F.mkRefM(4).flatMap(r => r.update(_ => F.pure(5)) *> r.get.map(_ - 1)) *> F.mkMutex.flatMap(m => m.bracket(F.pure(10))) } def attachScheduler2[F[+_, +_]: Monad2: Scheduler2]: F[Nothing, Int] = { F.repeat(F.pure(42))(RetryPolicy.recurs(2)) } - def attachScheduler3[FR[-_, +_, +_]: Monad3: Scheduler3]: FR[Nothing, Nothing, Int] = { - F.repeat(F.pure(42))(RetryPolicy.recurs(2)) - } lazy val zioTest = { ( x[zio.IO], y[zio.IO], z[zio.IO], `attach Primitives2 & Fork2 methods even when they aren't imported`[zio.IO], - `attach Primitives2 & Fork23 methods to a trifunctor BIO even when not imported`[zio.ZIO], - `attach PrimitivesM2 methods to a trifunctor BIO even when not imported`[zio.ZIO], + `attach PrimitivesM2 methods to BIO even when not imported`[zio.IO], attachScheduler2[zio.IO], - attachScheduler3[zio.ZIO], ) } @@ -399,115 +280,6 @@ class SyntaxTest extends AnyWordSpec { zio.ZIO.succeed(List(4)).flatMap { F.traverse(_)(_ => zio.ZIO.unit) } *> F.unit - implicitly[Bifunctor3[zio.ZIO]] - implicitly[Bifunctor2[zio.IO]] - } - - "FR: Local/Ask summoners examples" in { - import izumi.functional.bio.{Arrow3, Ask3, Bifunctor3, F, Local3, MonadAsk3, Temporal3} - // FIXME wtf -// import izumi.functional.bio.{Arrow3, Ask3, Bifunctor3, F, Local3, Monad3, MonadAsk3, Profunctor3, Temporal3} - - // FIXME wtf -// def x[FR[-_, +_, +_]: Monad3: Ask3]: FR[Int, Nothing, Boolean] = { -// F.unit *> F.ask[Int].map { -// (_: Int) => -// true -// } *> -// F.unit *> F.askWith { -// (_: Int) => -// true -// } -// } -// def onlyMonadAsk[FR[-_, +_, +_]: MonadAsk3]: FR[Int, Nothing, Unit] = { -// F.unit <* F.askWith { -// (_: Int) => -// true -// } -// } -// def onlyMonadAskAccess[FR[-_, +_, +_]: MonadAsk3]: FR[Int, Nothing, Unit] = { -// F.unit <* F.access { -// (_: Int) => -// F.unit -// } -// } -// def onlyAsk[FR[-_, +_, +_]: Ask3]: FR[Int, Nothing, Unit] = { -// F.askWith { -// (_: Int) => -// true -// } *> F.unit -// } -// def y[FR[-_, +_, +_]: Local3]: FR[Any, Throwable, Unit] = { -// F.fromKleisli { -// F.askWith { -// (_: Int) => -// () -// }.toKleisli -// }.provide(4) -// } -// def arrowAsk[FR[-_, +_, +_]: Arrow3: Ask3]: FR[String, Throwable, Int] = { -// F.askWith { -// (_: Int) => -// () -// }.dimap { -// (_: String) => -// 4 -// }(_ => 1) -// } -// def profunctorOnly[FR[-_, +_, +_]: Profunctor3](f: FR[Unit, Throwable, Int]): FR[String, Throwable, Int] = { -// F.contramap(f) { -// (_: Int) => -// () -// }.dimap { -// (_: String) => -// 4 -// }(_ => 1) -// .map(_ + 2) -// } - def bifunctorOnly[FR[-_, +_, +_]: Bifunctor3](f: FR[Unit, Int, Int]): FR[Unit, Int, Int] = { - F.leftMap(f) { - (_: Int) => - () - }.bimap( - { - (_: Unit) => - 4 - }, - _ => 1, - ) - .map(_ + 2) - } -// def Temporal2PlusLocal[FR[-_, +_, +_]: Temporal3: Local3]: FR[Any, Throwable, Unit] = { -// F.fromKleisli { -// F.askWith { -// (_: Int) => -// () -// }.toKleisli -// }.provide(4).flatMap(_ => F.unit).widenError[Throwable].leftMap(identity) -// } -// def biomonadPlusLocal[FR[-_, +_, +_]: Monad3: Bifunctor3: Local3]: FR[Any, Throwable, Unit] = { -// F.fromKleisli { -// F.askWith { -// (_: Int) => -// () -// }.toKleisli -// }.provide(4).flatMap(_ => F.unit).widenError[Throwable].leftMap(identity) -// } - - val _ = ( - // FIXME trifunctor broken - fixme(), -// x[zio.ZIO], -// onlyMonadAsk[zio.ZIO], -// onlyMonadAskAccess[zio.ZIO], -// onlyAsk[zio.ZIO], -// y[zio.ZIO], -// arrowAsk[zio.ZIO], -// profunctorOnly[zio.ZIO](zio.ZIO.succeed(1)), -// Temporal2PlusLocal[zio.ZIO], -// biomonadPlusLocal[zio.ZIO], - bifunctorOnly[zio.ZIO](zio.ZIO.succeed(1)), - ) } "doc examples" in { @@ -520,23 +292,6 @@ class SyntaxTest extends AnyWordSpec { lazy val _ = adder[zio.IO](1) } - locally { -// import izumi.functional.bio.{F, MonadAsk3, Ref3} - // update ref from the environment and return result -// def adderEnv[F[-_, +_, +_]: MonadAsk3](i: Int): F[Ref3[F, Int], Nothing, Int] = -// F.access { -// ref => -// for { -// _ <- ref.update(_ + i) -// res <- ref.get -// } yield res -// } - - // FIXME trifunctor broken - fixme() -// lazy val _ = adderEnv[zio.ZIO](1) - } - locally { import izumi.functional.bio.{F, Temporal2} @@ -567,23 +322,6 @@ class SyntaxTest extends AnyWordSpec { y[zio.IO](zio.ZIO.succeed(())) } - "BIO3.iterateUntil/iterateWhile are callable" in { - import izumi.functional.bio.{Error3, Monad3} - - def x[F[-_, +_, +_]: Monad3](a: F[Any, Nothing, Unit]) = { - a.iterateWhile(_ => true) - a.iterateUntil(_ => false) - } - - def y[F[-_, +_, +_]: Error3](a: F[Any, Nothing, Unit]) = { - a.iterateWhile(_ => true) - a.iterateUntil(_ => false) - } - - x[zio.ZIO](zio.ZIO.succeed(())) - y[zio.ZIO](zio.ZIO.succeed(())) - } - "BIO.retryUntil/retryUntilF/retryWhile/retryWhileF/fromOptionOr/fromOptionF/fromOption are callable" in { import izumi.functional.bio.{Error2, F, Functor2, Monad2} @@ -611,72 +349,13 @@ class SyntaxTest extends AnyWordSpec { z[zio.IO](zio.ZIO.succeed(()), zio.ZIO.succeed(Option(()))) } - "BIO3.retryUntil/retryUntilF/retryWhile/retryWhileF/fromOptionOr/fromOptionF/fromOption are callable" in { - import izumi.functional.bio.{Error3, F, Functor3, Monad3} - - def x[F[-_, +_, +_]: Functor3](aOpt: F[Any, String, Option[Unit]]) = { - aOpt.fromOptionOr(()) - } - - def y[F[-_, +_, +_]: Monad3](aOpt: F[Any, String, Option[Unit]]) = { - aOpt.fromOptionOr(()) - aOpt.fromOptionF(F.unit) - } - - def z[F[-_, +_, +_]: Error3](a: F[Any, String, Unit], aOpt: F[Any, String, Option[Unit]]) = { - a.retryUntil(_ => true) - a.retryUntilF(_ => F.pure(false)) - a.retryWhile(_ => false) - a.retryWhileF(_ => F.pure(true)) - aOpt.fromOptionOr(()) - aOpt.fromOptionF(F.unit) - aOpt.fromOption("ooops") - } - - x[zio.ZIO](zio.ZIO.succeed(Option(()))) - y[zio.ZIO](zio.ZIO.succeed(Option(()))) - z[zio.ZIO](zio.ZIO.succeed(()), zio.ZIO.succeed(Option(()))) - } - "Fiber#toCats syntax works" in { - import izumi.functional.bio.{Applicative2, Applicative3, F, Fork2, Fork3} + import izumi.functional.bio.{Applicative2, F, Fork2} def x2[F[+_, +_]: Applicative2: Fork2] = { F.unit.fork.map(_.toCats) } - def x3[F[-_, +_, +_]: Applicative3: Fork3] = { - F.unit.fork.map(_.toCats) - } x2[zio.IO] - x3[zio.ZIO] - } - - "Fiber3 and Fiber2 types are wholly compatible" in { - import izumi.functional.bio.{Applicative2, Applicative3, F, Fiber2, Fiber3, Fork2, Fork3} - - def x2[FR[-_, +_, +_]](implicit applicative: Applicative2[FR[Any, +_, +_]], fork: Fork2[FR[Any, +_, +_]]) = { - type F[+E, +A] = FR[Any, E, A] - for { - fiber <- F.unit.fork - } yield { - val fiber2: Fiber2[F, Nothing, Unit] = fiber - val fiber3: Fiber3[FR, Nothing, Unit] = fiber - (fiber2, fiber3) - } - } - def x3[FR[-_, +_, +_]: Applicative3: Fork3] = { - type F[+E, +A] = FR[Any, E, A] - for { - fiber <- F.unit.fork - } yield { - val fiber2: Fiber2[F, Nothing, Unit] = fiber - val fiber3: Fiber3[FR, Nothing, Unit] = fiber - (fiber2, fiber3) - } - } - - x2[zio.ZIO] - x3[zio.ZIO] } "Morphism1/2/3 identity is available" in { @@ -706,25 +385,4 @@ class SyntaxTest extends AnyWordSpec { F.clock.now() } - "BIO3.clock/entropy are callable" in { - import izumi.functional.bio.{F, Functor3, Temporal3, Clock3, Entropy3} - - def x[F[-_, +_, +_]: Temporal3: Clock3] = { - F.clock.now() - } - - def y[F[-_, +_, +_]: Functor3: Clock3] = { - F.clock.now() - } - - def z[F[-_, +_, +_]: Functor3: Entropy3] = { - F.entropy.nextInt() - } - - x[zio.ZIO] - y[zio.ZIO] - z[zio.ZIO] - F.entropy.nextInt() - } - } diff --git a/fundamentals/fundamentals-bio/src/main/scala-2/izumi/functional/bio/Convert3To2VersionSpecific.scala b/fundamentals/fundamentals-bio/src/main/scala-2/izumi/functional/bio/Convert3To2VersionSpecific.scala deleted file mode 100644 index eb414767b2..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala-2/izumi/functional/bio/Convert3To2VersionSpecific.scala +++ /dev/null @@ -1,21 +0,0 @@ -package izumi.functional.bio - -import izumi.functional.bio.DivergenceHelper.{Divergent, Nondivergent} - -trait RootInstancesLowPriorityVersionSpecific { - @inline implicit final def Convert3To2[C[f[-_, +_, +_]] <: RootBifunctor[f], FR[-_, +_, +_], R0]( - implicit BifunctorPlus: C[FR] { type Divergence = Nondivergent } - ): Divergent.Of[C[λ[(`-R`, `+E`, `+A`) => FR[R0, E, A]]]] = { - Divergent(cast3To2[C, FR, R0](BifunctorPlus)) - } -} - -trait BlockingIOLowPriorityVersionSpecific { - - @inline implicit final def blockingConvert3To2[C[f[-_, +_, +_]] <: BlockingIO3[f], FR[-_, +_, +_], R]( - implicit BlockingIO3: C[FR] { type Divergence = Nondivergent } - ): Divergent.Of[BlockingIO2[FR[R, +_, +_]]] = { - Divergent(cast3To2[C, FR, R](BlockingIO3)) - } - -} 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 deleted file mode 100644 index 104ff542cd..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala-3/izumi/functional/bio/Convert3To2VersionSpecific.scala +++ /dev/null @@ -1,25 +0,0 @@ -package izumi.functional.bio - -import izumi.functional.bio.DivergenceHelper.{Divergent, Nondivergent} - -trait RootInstancesLowPriorityVersionSpecific { - @inline implicit final def Convert3To2[C[f[-_, +_, +_]] <: RootBifunctor[f], FR[-_, +_, +_], R0, T]( - implicit BifunctorPlus: C[FR] { - type Divergence = Nondivergent; type IsPredefined = T // `IsPredefined = T` is required only on Scala 3 - } - ): Divergent.Of[C[λ[(`-R`, `+E`, `+A`) => FR[R0, E, A]]]] { type IsPredefined = T } = { - BifunctorPlus.asInstanceOf[Divergent.Of[C[λ[(`-R`, `+E`, `+A`) => FR[R0, E, A]]]] { type IsPredefined = T }] - } -} - -trait BlockingIOLowPriorityVersionSpecific { - - @inline implicit final def blockingConvert3To2[C[f[-_, +_, +_]] <: BlockingIO3[f], FR[-_, +_, +_], R, T]( - 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 }] - } - -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Applicative2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Applicative2.scala new file mode 100644 index 0000000000..88dad8d51f --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Applicative2.scala @@ -0,0 +1,40 @@ +package izumi.functional.bio + +import scala.collection.immutable.Queue + +trait Applicative2[F[+_, +_]] extends Functor2[F] { + def pure[A](a: A): F[Nothing, A] + + /** execute two operations in order, map their results */ + def map2[E, A, B, C](firstOp: F[E, A], secondOp: => F[E, B])(f: (A, B) => C): F[E, C] + + /** execute two operations in order, return result of second operation */ + def *>[E, A, B](firstOp: F[E, A], secondOp: => F[E, B]): F[E, B] = map2(firstOp, secondOp)((_, b) => b) + + /** execute two operations in order, same as `*>`, but return result of first operation */ + def <*[E, A, B](firstOp: F[E, A], secondOp: => F[E, B]): F[E, A] = map2(firstOp, secondOp)((a, _) => a) + + def traverse[E, A, B](l: Iterable[A])(f: A => F[E, B]): F[E, List[B]] = { + map( + l.foldLeft(pure(Queue.empty[B]): F[E, Queue[B]])((q, a) => map2(q, f(a))(_ :+ _)) + )(_.toList) + } + + @inline final def forever[E, A](r: F[E, A]): F[E, Nothing] = *>(r, forever(r)) + def traverse_[E, A](l: Iterable[A])(f: A => F[E, Unit]): F[E, Unit] = void(traverse(l)(f)) + def sequence[E, A](l: Iterable[F[E, A]]): F[E, List[A]] = traverse(l)(identity) + def sequence_[E](l: Iterable[F[E, Unit]]): F[E, Unit] = void(traverse(l)(identity)) + def flatTraverse[E, A, B](l: Iterable[A])(f: A => F[E, Iterable[B]]): F[E, List[B]] = map(traverse(l)(f))(_.flatten) + def flatSequence[E, A](l: Iterable[F[E, Iterable[A]]]): F[E, List[A]] = flatTraverse(l)(identity) + def collect[E, A, B](l: Iterable[A])(f: A => F[E, Option[B]]): F[E, List[B]] = map(traverse(l)(f))(_.flatten) + def filter[E, A](l: Iterable[A])(f: A => F[E, Boolean]): F[E, List[A]] = collect(l)(a => map(f(a))(if (_) Some(a) else None)) + + def unit: F[Nothing, Unit] = pure(()) + @inline final def traverse[E, A, B](o: Option[A])(f: A => F[E, B]): F[E, Option[B]] = o match { + case Some(a) => map(f(a))(Some(_)) + case None => pure(None) + } + @inline final def when[E](cond: Boolean)(ifTrue: => F[E, Unit]): F[E, Unit] = if (cond) ifTrue else unit + @inline final def unless[E](cond: Boolean)(ifFalse: => F[E, Unit]): F[E, Unit] = if (cond) unit else ifFalse + @inline final def ifThenElse[E, A](cond: Boolean)(ifTrue: => F[E, A], ifFalse: => F[E, A]): F[E, A] = if (cond) ifTrue else ifFalse +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Applicative3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Applicative3.scala deleted file mode 100644 index bdc8f3ba35..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Applicative3.scala +++ /dev/null @@ -1,36 +0,0 @@ -package izumi.functional.bio - -import scala.collection.immutable.Queue - -trait Applicative3[F[-_, +_, +_]] extends Functor3[F] { - def pure[A](a: A): F[Any, Nothing, A] - - /** execute two operations in order, map their results */ - def map2[R, E, A, B, C](firstOp: F[R, E, A], secondOp: => F[R, E, B])(f: (A, B) => C): F[R, E, C] - - /** execute two operations in order, return result of second operation */ - def *>[R, E, A, B](firstOp: F[R, E, A], secondOp: => F[R, E, B]): F[R, E, B] = map2(firstOp, secondOp)((_, b) => b) - - /** execute two operations in order, same as `*>`, but return result of first operation */ - def <*[R, E, A, B](firstOp: F[R, E, A], secondOp: => F[R, E, B]): F[R, E, A] = map2(firstOp, secondOp)((a, _) => a) - - def traverse[R, E, A, B](l: Iterable[A])(f: A => F[R, E, B]): F[R, E, List[B]] = { - map( - l.foldLeft(pure(Queue.empty[B]): F[R, E, Queue[B]])((q, a) => map2(q, f(a))(_ :+ _)) - )(_.toList) - } - - @inline final def forever[R, E, A](r: F[R, E, A]): F[R, E, Nothing] = *>(r, forever(r)) - def traverse_[R, E, A](l: Iterable[A])(f: A => F[R, E, Unit]): F[R, E, Unit] = void(traverse(l)(f)) - def sequence[R, E, A](l: Iterable[F[R, E, A]]): F[R, E, List[A]] = traverse(l)(identity) - def sequence_[R, E](l: Iterable[F[R, E, Unit]]): F[R, E, Unit] = void(traverse(l)(identity)) - - def unit: F[Any, Nothing, Unit] = pure(()) - @inline final def traverse[R, E, A, B](o: Option[A])(f: A => F[R, E, B]): F[R, E, Option[B]] = o match { - case Some(a) => map(f(a))(Some(_)) - case None => pure(None) - } - @inline final def when[R, E](cond: Boolean)(ifTrue: => F[R, E, Unit]): F[R, E, Unit] = if (cond) ifTrue else unit - @inline final def unless[R, E](cond: Boolean)(ifFalse: => F[R, E, Unit]): F[R, E, Unit] = if (cond) unit else ifFalse - @inline final def ifThenElse[R, E, A](cond: Boolean)(ifTrue: => F[R, E, A], ifFalse: => F[R, E, A]): F[R, E, A] = if (cond) ifTrue else ifFalse -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ApplicativeError2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ApplicativeError2.scala new file mode 100644 index 0000000000..af2000dff8 --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ApplicativeError2.scala @@ -0,0 +1,20 @@ +package izumi.functional.bio + +import scala.util.Try + +trait ApplicativeError2[F[+_, +_]] extends Guarantee2[F] with Bifunctor2[F] { + override def InnerF: Functor2[F] = this + + def fail[E](v: => E): F[E, Nothing] + + /** map errors from two operations into a new error if both fail */ + def leftMap2[E, A, E2, E3](firstOp: F[E, A], secondOp: => F[E2, A])(f: (E, E2) => E3): F[E3, A] + + /** execute second operation only if the first one fails */ + def orElse[E, A, E2](r: F[E, A], f: => F[E2, A]): F[E2, A] + + // from* ops must suspend `effect` + def fromEither[E, V](effect: => Either[E, V]): F[E, V] + def fromOption[E, A](errorOnNone: => E)(effect: => Option[A]): F[E, A] + def fromTry[A](effect: => Try[A]): F[Throwable, A] +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ApplicativeError3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ApplicativeError3.scala deleted file mode 100644 index a8b706b298..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ApplicativeError3.scala +++ /dev/null @@ -1,20 +0,0 @@ -package izumi.functional.bio - -import scala.util.Try - -trait ApplicativeError3[F[-_, +_, +_]] extends Guarantee3[F] with Bifunctor3[F] { - override def InnerF: Functor3[F] = this - - def fail[E](v: => E): F[Any, E, Nothing] - - /** map errors from two operations into a new error if both fail */ - def leftMap2[R, E, A, E2, E3](firstOp: F[R, E, A], secondOp: => F[R, E2, A])(f: (E, E2) => E3): F[R, E3, A] - - /** execute second operation only if the first one fails */ - def orElse[R, E, A, E2](r: F[R, E, A], f: => F[R, E2, A]): F[R, E2, A] - - // from* ops must suspend `effect` - def fromEither[E, V](effect: => Either[E, V]): F[Any, E, V] - def fromOption[E, A](errorOnNone: => E)(effect: => Option[A]): F[Any, E, A] - def fromTry[A](effect: => Try[A]): F[Any, Throwable, A] -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Arrow3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Arrow3.scala deleted file mode 100644 index e62fbfbcc6..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Arrow3.scala +++ /dev/null @@ -1,7 +0,0 @@ -package izumi.functional.bio - -trait Arrow3[FR[-_, +_, +_]] extends Profunctor3[FR] { - def askWith[R, A](f: R => A): FR[R, Nothing, A] - def andThen[R, R1, E, A](f: FR[R, E, R1], g: FR[R1, E, A]): FR[R, E, A] - def asking[R, E, A](f: FR[R, E, A]): FR[R, E, (A, R)] -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ArrowChoice3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ArrowChoice3.scala deleted file mode 100644 index a2b235edc0..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ArrowChoice3.scala +++ /dev/null @@ -1,8 +0,0 @@ -package izumi.functional.bio - -trait ArrowChoice3[FR[-_, +_, +_]] extends Arrow3[FR] { - def choose[R1, R2, E, A, B](f: FR[R1, E, A], g: FR[R2, E, B]): FR[Either[R1, R2], E, Either[A, B]] - - // defaults - def choice[R1, R2, E, A](f: FR[R1, E, A], g: FR[R2, E, A]): FR[Either[R1, R2], E, A] = dimap(choose(f, g))(identity[Either[R1, R2]])(_.merge) -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Ask3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Ask3.scala deleted file mode 100644 index 3fae1919aa..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Ask3.scala +++ /dev/null @@ -1,9 +0,0 @@ -package izumi.functional.bio - -trait Ask3[FR[-_, +_, +_]] extends RootTrifunctor[FR] { - def InnerF: Applicative3[FR] - def ask[R]: FR[R, Nothing, R] - - // defaults - def askWith[R, A](f: R => A): FR[R, Nothing, A] = InnerF.map[R, Nothing, R, A](ask)(f) -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Async2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Async2.scala new file mode 100644 index 0000000000..a290335708 --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Async2.scala @@ -0,0 +1,26 @@ +package izumi.functional.bio + +import java.util.concurrent.CompletionStage + +import scala.concurrent.{ExecutionContext, Future} + +trait Async2[F[+_, +_]] extends Concurrent2[F] with IO2[F] { + override def InnerF: Panic2[F] = this + + final type Canceler = F[Nothing, Unit] + + def async[E, A](register: (Either[E, A] => Unit) => Unit): F[E, A] + def asyncF[E, A](register: (Either[E, A] => Unit) => F[E, Unit]): F[E, A] + def asyncCancelable[E, A](register: (Either[E, A] => Unit) => Canceler): F[E, A] + + def fromFuture[A](mkFuture: ExecutionContext => Future[A]): F[Throwable, A] + def fromFutureJava[A](javaFuture: => CompletionStage[A]): F[Throwable, A] + + def currentEC: F[Nothing, ExecutionContext] + def onEC[E, A](ec: ExecutionContext)(f: F[E, A]): F[E, A] + + // defaults + override def never: F[Nothing, Nothing] = async(_ => ()) + + @inline final def fromFuture[A](mkFuture: => Future[A]): F[Throwable, A] = fromFuture(_ => mkFuture) +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Async3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Async3.scala deleted file mode 100644 index ef262d9083..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Async3.scala +++ /dev/null @@ -1,26 +0,0 @@ -package izumi.functional.bio - -import java.util.concurrent.CompletionStage - -import scala.concurrent.{ExecutionContext, Future} - -trait Async3[F[-_, +_, +_]] extends Concurrent3[F] with IO3[F] { - override def InnerF: Panic3[F] = this - - final type Canceler = F[Any, Nothing, Unit] - - def async[E, A](register: (Either[E, A] => Unit) => Unit): F[Any, E, A] - def asyncF[R, E, A](register: (Either[E, A] => Unit) => F[R, E, Unit]): F[R, E, A] - def asyncCancelable[E, A](register: (Either[E, A] => Unit) => Canceler): F[Any, E, A] - - def fromFuture[A](mkFuture: ExecutionContext => Future[A]): F[Any, Throwable, A] - def fromFutureJava[A](javaFuture: => CompletionStage[A]): F[Any, Throwable, A] - - def currentEC: F[Any, Nothing, ExecutionContext] - def onEC[R, E, A](ec: ExecutionContext)(f: F[R, E, A]): F[R, E, A] - - // defaults - override def never: F[Any, Nothing, Nothing] = async(_ => ()) - - @inline final def fromFuture[A](mkFuture: => Future[A]): F[Any, Throwable, A] = fromFuture(_ => mkFuture) -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bifunctor2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bifunctor2.scala new file mode 100644 index 0000000000..d00442ec99 --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bifunctor2.scala @@ -0,0 +1,14 @@ +package izumi.functional.bio + +import scala.annotation.unused + +trait Bifunctor2[F[+_, +_]] extends RootBifunctor[F] { + def InnerF: Functor2[F] + + def bimap[E, A, E2, A2](r: F[E, A])(f: E => E2, g: A => A2): F[E2, A2] + def leftMap[E, A, E2](r: F[E, A])(f: E => E2): F[E2, A] = bimap(r)(f, identity) + + @inline final def widenError[E, A, E1](r: F[E, A])(implicit @unused ev: E <:< E1): F[E1, A] = r.asInstanceOf[F[E1, A]] + @inline final def widenBoth[E, A, E1, A1](r: F[E, A])(implicit @unused ev: E <:< E1, @unused ev2: A <:< A1): F[E1, A1] = + r.asInstanceOf[F[E1, A1]] +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bifunctor3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bifunctor3.scala deleted file mode 100644 index 65aa7fc881..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bifunctor3.scala +++ /dev/null @@ -1,14 +0,0 @@ -package izumi.functional.bio - -import scala.annotation.unused - -trait Bifunctor3[F[-_, +_, +_]] extends RootBifunctor[F] { - def InnerF: Functor3[F] - - def bimap[R, E, A, E2, A2](r: F[R, E, A])(f: E => E2, g: A => A2): F[R, E2, A2] - def leftMap[R, E, A, E2](r: F[R, E, A])(f: E => E2): F[R, E2, A] = bimap(r)(f, identity) - - @inline final def widenError[R, E, A, E1](r: F[R, E, A])(implicit @unused ev: E <:< E1): F[R, E1, A] = r.asInstanceOf[F[R, E1, A]] - @inline final def widenBoth[R, E, A, E1, A1](r: F[R, E, A])(implicit @unused ev: E <:< E1, @unused ev2: A <:< A1): F[R, E1, A1] = - r.asInstanceOf[F[R, E1, A1]] -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bracket2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bracket2.scala new file mode 100644 index 0000000000..12300c9fee --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bracket2.scala @@ -0,0 +1,61 @@ +package izumi.functional.bio + +trait Bracket2[F[+_, +_]] extends Error2[F] { + def bracketCase[E, A, B](acquire: F[E, A])(release: (A, Exit[E, B]) => F[Nothing, Unit])(use: A => F[E, B]): F[E, B] + + def bracket[E, A, B](acquire: F[E, A])(release: A => F[Nothing, Unit])(use: A => F[E, B]): F[E, B] = { + bracketCase(acquire)((a, _: Exit[E, B]) => release(a))(use) + } + + def guaranteeCase[E, A](f: F[E, A], cleanup: Exit[E, A] => F[Nothing, Unit]): F[E, A] = { + bracketCase(unit: F[E, Unit])((_, e: Exit[E, A]) => cleanup(e))(_ => f) + } + + /** + * Run release action only on a failure – _any failure_, INCLUDING interruption. + * Do not run release action if `use` finished successfully. + */ + final def bracketOnFailure[E, A, B](acquire: F[E, A])(cleanupOnFailure: (A, Exit.Failure[E]) => F[Nothing, Unit])(use: A => F[E, B]): F[E, B] = { + bracketCase[E, A, B](acquire) { + case (a, e: Exit.Failure[E]) => cleanupOnFailure(a, e) + case _ => unit + }(use) + } + + /** + * Run cleanup only on a failure – _any failure_, INCLUDING interruption. + * Do not run cleanup if `use` finished successfully. + */ + final def guaranteeOnFailure[E, A](f: F[E, A], cleanupOnFailure: Exit.Failure[E] => F[Nothing, Unit]): F[E, A] = { + guaranteeCase[E, A](f, { case e: Exit.Failure[E] => cleanupOnFailure(e); case _ => unit }) + } + + /** + * Run cleanup only on interruption. + * Do not run cleanup if `use` finished successfully. + */ + final def guaranteeOnInterrupt[E, A](f: F[E, A], cleanupOnInterruption: Exit.Interruption => F[Nothing, Unit]): F[E, A] = { + guaranteeCase[E, A](f, { case e: Exit.Interruption => cleanupOnInterruption(e); case _ => unit }) + } + + /** Run cleanup on both _success_ and _failure_, if the failure IS NOT an interruption. */ + final def guaranteeExceptOnInterrupt[E, A]( + f: F[E, A], + cleanupOnNonInterruption: Either[Exit.Termination, Either[Exit.Error[E], Exit.Success[A]]] => F[Nothing, Unit], + ): F[E, A] = { + guaranteeCase[E, A]( + f, + { + case e: Exit.Termination => cleanupOnNonInterruption(Left(e)) + case e: Exit.Error[E] => cleanupOnNonInterruption(Right(Left(e))) + case e: Exit.Success[A] => cleanupOnNonInterruption(Right(Right(e))) + case _: Exit.Interruption => unit + }, + ) + } + + // defaults + override def guarantee[E, A](f: F[E, A], cleanup: F[Nothing, Unit]): F[E, A] = { + bracket(unit: F[E, Unit])(_ => cleanup)(_ => f) + } +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bracket3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bracket3.scala deleted file mode 100644 index 5f97dec40b..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Bracket3.scala +++ /dev/null @@ -1,61 +0,0 @@ -package izumi.functional.bio - -trait Bracket3[F[-_, +_, +_]] extends Error3[F] { - def bracketCase[R, E, A, B](acquire: F[R, E, A])(release: (A, Exit[E, B]) => F[R, Nothing, Unit])(use: A => F[R, E, B]): F[R, E, B] - - def bracket[R, E, A, B](acquire: F[R, E, A])(release: A => F[R, Nothing, Unit])(use: A => F[R, E, B]): F[R, E, B] = { - bracketCase(acquire)((a, _: Exit[E, B]) => release(a))(use) - } - - def guaranteeCase[R, E, A](f: F[R, E, A], cleanup: Exit[E, A] => F[R, Nothing, Unit]): F[R, E, A] = { - bracketCase(unit: F[R, E, Unit])((_, e: Exit[E, A]) => cleanup(e))(_ => f) - } - - /** - * Run release action only on a failure – _any failure_, INCLUDING interruption. - * Do not run release action if `use` finished successfully. - */ - final def bracketOnFailure[R, E, A, B](acquire: F[R, E, A])(cleanupOnFailure: (A, Exit.Failure[E]) => F[R, Nothing, Unit])(use: A => F[R, E, B]): F[R, E, B] = { - bracketCase[R, E, A, B](acquire) { - case (a, e: Exit.Failure[E]) => cleanupOnFailure(a, e) - case _ => unit - }(use) - } - - /** - * Run cleanup only on a failure – _any failure_, INCLUDING interruption. - * Do not run cleanup if `use` finished successfully. - */ - final def guaranteeOnFailure[R, E, A](f: F[R, E, A], cleanupOnFailure: Exit.Failure[E] => F[R, Nothing, Unit]): F[R, E, A] = { - guaranteeCase[R, E, A](f, { case e: Exit.Failure[E] => cleanupOnFailure(e); case _ => unit }) - } - - /** - * Run cleanup only on interruption. - * Do not run cleanup if `use` finished successfully. - */ - final def guaranteeOnInterrupt[R, E, A](f: F[R, E, A], cleanupOnInterruption: Exit.Interruption => F[R, Nothing, Unit]): F[R, E, A] = { - guaranteeCase[R, E, A](f, { case e: Exit.Interruption => cleanupOnInterruption(e); case _ => unit }) - } - - /** Run cleanup on both _success_ and _failure_, if the failure IS NOT an interruption. */ - final def guaranteeExceptOnInterrupt[R, E, A]( - f: F[R, E, A], - cleanupOnNonInterruption: Either[Exit.Termination, Either[Exit.Error[E], Exit.Success[A]]] => F[R, Nothing, Unit], - ): F[R, E, A] = { - guaranteeCase[R, E, A]( - f, - { - case e: Exit.Termination => cleanupOnNonInterruption(Left(e)) - case e: Exit.Error[E] => cleanupOnNonInterruption(Right(Left(e))) - case e: Exit.Success[A] => cleanupOnNonInterruption(Right(Right(e))) - case _: Exit.Interruption => unit - }, - ) - } - - // defaults - override def guarantee[R, E, A](f: F[R, E, A], cleanup: F[R, Nothing, Unit]): F[R, E, A] = { - bracket(unit: F[R, E, Unit])(_ => cleanup)(_ => f) - } -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Clock1.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Clock1.scala index 20e9b65b31..6e1ba6dab7 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Clock1.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Clock1.scala @@ -96,10 +96,10 @@ object Clock1 extends LowPriorityClockInstances { * * @see https://github.com/scala/bug/issues/11427 */ - @inline implicit final def limitedCovariance2[C[f[_]] <: Clock1[f], FR[_, _], R0]( - implicit F: C[FR[Nothing, _]] { type Divergence = Nondivergent } - ): Divergent.Of[C[FR[R0, _]]] = { - Divergent(F.asInstanceOf[C[FR[R0, _]]]) + @inline implicit final def limitedCovariance2[C[f[_]] <: Clock1[f], F[_, _], E]( + implicit F: C[F[Nothing, _]] { type Divergence = Nondivergent } + ): Divergent.Of[C[F[E, _]]] = { + Divergent(F.asInstanceOf[C[F[E, _]]]) } @inline implicit final def limitedCovariance3[C[f[_]] <: Clock1[f], FR[_, _, _], R0, E]( diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Concurrent3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Concurrent2.scala similarity index 50% rename from fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Concurrent3.scala rename to fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Concurrent2.scala index aa02557d39..8638f62568 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Concurrent3.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Concurrent2.scala @@ -1,19 +1,19 @@ package izumi.functional.bio -trait Concurrent3[F[-_, +_, +_]] extends Parallel3[F] { - override def InnerF: Panic3[F] +trait Concurrent2[F[+_, +_]] extends Parallel2[F] { + override def InnerF: Panic2[F] /** Race two actions, the winner is the first action to TERMINATE, whether by success or failure */ - def race[R, E, A](r1: F[R, E, A], r2: F[R, E, A]): F[R, E, A] + def race[E, A](r1: F[E, A], r2: F[E, A]): F[E, A] /** * Race two actions, the winner is the first action to TERMINATE, whether by success or failure * * Unlike [[race]], the loser is not interrupted after the winner has terminated - whether by success or failure. */ - def racePairUnsafe[R, E, A, B](fa: F[R, E, A], fb: F[R, E, B]): F[R, E, Either[(Exit[E, A], Fiber3[F, E, B]), (Fiber3[F, E, A], Exit[E, B])]] + def racePairUnsafe[E, A, B](fa: F[E, A], fb: F[E, B]): F[E, Either[(Exit[E, A], Fiber2[F, E, B]), (Fiber2[F, E, A], Exit[E, B])]] - def yieldNow: F[Any, Nothing, Unit] + def yieldNow: F[Nothing, Unit] - def never: F[Any, Nothing, Nothing] + def never: F[Nothing, Nothing] } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Entropy1.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Entropy1.scala index 07a1192c6f..556b79f86e 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Entropy1.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Entropy1.scala @@ -106,10 +106,10 @@ object Entropy1 extends LowPriorityEntropyInstances { * * @see https://github.com/scala/bug/issues/11427 */ - @inline implicit final def limitedCovariance2[C[f[_]] <: Entropy1[f], FR[_, _], R0]( - implicit F: C[FR[Nothing, _]] { type Divergence = Nondivergent } - ): Divergent.Of[C[FR[R0, _]]] = { - Divergent(F.asInstanceOf[C[FR[R0, _]]]) + @inline implicit final def limitedCovariance2[C[f[_]] <: Entropy1[f], F[_, _], E]( + implicit F: C[F[Nothing, _]] { type Divergence = Nondivergent } + ): Divergent.Of[C[F[E, _]]] = { + Divergent(F.asInstanceOf[C[F[E, _]]]) } @inline implicit final def limitedCovariance3[C[f[_]] <: Entropy1[f], FR[_, _, _], R0, E]( diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Error2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Error2.scala new file mode 100644 index 0000000000..24aeb714cd --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Error2.scala @@ -0,0 +1,93 @@ +package izumi.functional.bio + +import izumi.fundamentals.platform.language.SourceFilePositionMaterializer + +import scala.annotation.nowarn + +trait Error2[F[+_, +_]] extends ApplicativeError2[F] with Monad2[F] with ErrorAccumulatingOps2[F] { + + def catchAll[E, A, E2](r: F[E, A])(f: E => F[E2, A]): F[E2, A] + + def catchSome[E, A, E1 >: E](r: F[E, A])(f: PartialFunction[E, F[E1, A]]): F[E1, A] = { + catchAll(r)(e => f.applyOrElse(e, (_: E) => fail(e))) + } + + def redeem[E, A, E2, B](r: F[E, A])(err: E => F[E2, B], succ: A => F[E2, B]): F[E2, B] = { + flatMap(attempt(r))(_.fold(err, succ)) + } + def redeemPure[E, A, B](r: F[E, A])(err: E => B, succ: A => B): F[Nothing, B] = catchAll(map(r)(succ))(e => pure(err(e))) + def attempt[E, A](r: F[E, A]): F[Nothing, Either[E, A]] = redeemPure(r)(Left(_), Right(_)) + + def tapError[E, A, E1 >: E](r: F[E, A])(f: E => F[E1, Unit]): F[E1, A] = { + catchAll(r)(e => *>(f(e), fail(e))) + } + + def flip[E, A](r: F[E, A]): F[A, E] = { + redeem(r)(pure, fail(_)) + } + def leftFlatMap[E, A, E2](r: F[E, A])(f: E => F[Nothing, E2]): F[E2, A] = { + redeem(r)(e => flatMap(f(e))(fail(_)), pure) + } + def tapBoth[E, A, E1 >: E](r: F[E, A])(err: E => F[E1, Unit], succ: A => F[E1, Unit]): F[E1, A] = { + tap(tapError[E, A, E1](r)(err), succ) + } + + /** Extracts the optional value or fails with the `errorOnNone` error */ + def fromOption[E, A](errorOnNone: => E, r: F[E, Option[A]]): F[E, A] = { + flatMap(r) { + case Some(value) => pure(value) + case None => fail(errorOnNone) + } + } + + /** Retries this effect while its error satisfies the specified predicate. */ + def retryWhile[E, A](r: F[E, A])(f: E => Boolean): F[E, A] = { + retryWhileF(r)(e => pure(f(e))) + } + /** Retries this effect while its error satisfies the specified effectful predicate. */ + def retryWhileF[E, A](r: F[E, A])(f: E => F[Nothing, Boolean]): F[E, A] = { + catchAll(r: F[E, A])(e => flatMap(f(e))(if (_) retryWhileF(r)(f) else fail(e))) + } + + /** Retries this effect until its error satisfies the specified predicate. */ + def retryUntil[E, A](r: F[E, A])(f: E => Boolean): F[E, A] = { + retryUntilF(r)(e => pure(f(e))) + } + /** Retries this effect until its error satisfies the specified effectful predicate. */ + def retryUntilF[E, A](r: F[E, A])(f: E => F[Nothing, Boolean]): F[E, A] = { + catchAll(r: F[E, A])(e => flatMap(f(e))(if (_) fail(e) else retryUntilF(r)(f))) + } + + @nowarn("msg=Unused import") + def partition[E, A](l: Iterable[F[E, A]]): F[Nothing, (List[E], List[A])] = { + import scala.collection.compat.* + map(traverse(l)(attempt[E, A]))(_.partitionMap(identity)) + } + + /** for-comprehensions sugar: + * + * {{{ + * for { + * (1, 2) <- F.pure((2, 1)) + * } yield () + * }}} + * + * Use [[widenError]] to for pattern matching with non-Throwable errors: + * + * {{{ + * val f = for { + * (1, 2) <- F.pure((2, 1)).widenError[Option[Unit]] + * } yield () + * // f: F[Option[Unit], Unit] = F.fail(Some(()) + * }}} + */ + @inline final def withFilter[E, A](r: F[E, A])(predicate: A => Boolean)(implicit filter: WithFilter[E], pos: SourceFilePositionMaterializer): F[E, A] = { + flatMap(r)(a => if (predicate(a)) pure(a) else fail(filter.error(a, pos.get))) + } + + // defaults + override def bimap[E, A, E2, B](r: F[E, A])(f: E => E2, g: A => B): F[E2, B] = catchAll(map(r)(g))(e => fail(f(e))) + override def leftMap2[E, A, E2, E3](firstOp: F[E, A], secondOp: => F[E2, A])(f: (E, E2) => E3): F[E3, A] = + catchAll(firstOp)(e => leftMap(secondOp)(f(e, _))) + override def orElse[E, A, E2](r: F[E, A], f: => F[E2, A]): F[E2, A] = catchAll(r)(_ => f) +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Error3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Error3.scala deleted file mode 100644 index c02706c485..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Error3.scala +++ /dev/null @@ -1,85 +0,0 @@ -package izumi.functional.bio - -import izumi.fundamentals.platform.language.SourceFilePositionMaterializer - -trait Error3[F[-_, +_, +_]] extends ApplicativeError3[F] with Monad3[F] { - - def catchAll[R, E, A, E2](r: F[R, E, A])(f: E => F[R, E2, A]): F[R, E2, A] - - def catchSome[R, E, A, E1 >: E](r: F[R, E, A])(f: PartialFunction[E, F[R, E1, A]]): F[R, E1, A] = { - catchAll(r)(e => f.applyOrElse(e, (_: E) => fail(e))) - } - - def redeem[R, E, A, E2, B](r: F[R, E, A])(err: E => F[R, E2, B], succ: A => F[R, E2, B]): F[R, E2, B] = { - flatMap(attempt(r))(_.fold(err, succ)) - } - def redeemPure[R, E, A, B](r: F[R, E, A])(err: E => B, succ: A => B): F[R, Nothing, B] = catchAll(map(r)(succ))(e => pure(err(e))) - def attempt[R, E, A](r: F[R, E, A]): F[R, Nothing, Either[E, A]] = redeemPure(r)(Left(_), Right(_)) - - def tapError[R, E, A, E1 >: E](r: F[R, E, A])(f: E => F[R, E1, Unit]): F[R, E1, A] = { - catchAll(r)(e => *>(f(e), fail(e))) - } - - def flip[R, E, A](r: F[R, E, A]): F[R, A, E] = { - redeem(r)(pure, fail(_)) - } - def leftFlatMap[R, E, A, E2](r: F[R, E, A])(f: E => F[R, Nothing, E2]): F[R, E2, A] = { - redeem(r)(e => flatMap(f(e))(fail(_)), pure) - } - def tapBoth[R, E, A, E1 >: E](r: F[R, E, A])(err: E => F[R, E1, Unit], succ: A => F[R, E1, Unit]): F[R, E1, A] = { - tap(tapError[R, E, A, E1](r)(err), succ) - } - - /** Extracts the optional value or fails with the `errorOnNone` error */ - def fromOption[R, E, A](errorOnNone: => E, r: F[R, E, Option[A]]): F[R, E, A] = { - flatMap(r) { - case Some(value) => pure(value) - case None => fail(errorOnNone) - } - } - - /** Retries this effect while its error satisfies the specified predicate. */ - def retryWhile[R, E, A](r: F[R, E, A])(f: E => Boolean): F[R, E, A] = { - retryWhileF(r)(e => pure(f(e))) - } - /** Retries this effect while its error satisfies the specified effectful predicate. */ - def retryWhileF[R, R1 <: R, E, A](r: F[R, E, A])(f: E => F[R1, Nothing, Boolean]): F[R1, E, A] = { - catchAll(r: F[R1, E, A])(e => flatMap(f(e))(if (_) retryWhileF(r)(f) else fail(e))) - } - - /** Retries this effect until its error satisfies the specified predicate. */ - def retryUntil[R, E, A](r: F[R, E, A])(f: E => Boolean): F[R, E, A] = { - retryUntilF(r)(e => pure(f(e))) - } - /** Retries this effect until its error satisfies the specified effectful predicate. */ - def retryUntilF[R, R1 <: R, E, A](r: F[R, E, A])(f: E => F[R1, Nothing, Boolean]): F[R1, E, A] = { - catchAll(r: F[R1, E, A])(e => flatMap(f(e))(if (_) fail(e) else retryUntilF(r)(f))) - } - - /** for-comprehensions sugar: - * - * {{{ - * for { - * (1, 2) <- F.pure((2, 1)) - * } yield () - * }}} - * - * Use [[widenError]] to for pattern matching with non-Throwable errors: - * - * {{{ - * val f = for { - * (1, 2) <- F.pure((2, 1)).widenError[Option[Unit]] - * } yield () - * // f: F[Option[Unit], Unit] = F.fail(Some(()) - * }}} - */ - @inline final def withFilter[R, E, A](r: F[R, E, A])(predicate: A => Boolean)(implicit filter: WithFilter[E], pos: SourceFilePositionMaterializer): F[R, E, A] = { - flatMap(r)(a => if (predicate(a)) pure(a) else fail(filter.error(a, pos.get))) - } - - // defaults - override def bimap[R, E, A, E2, B](r: F[R, E, A])(f: E => E2, g: A => B): F[R, E2, B] = catchAll(map(r)(g))(e => fail(f(e))) - override def leftMap2[R, E, A, E2, E3](firstOp: F[R, E, A], secondOp: => F[R, E2, A])(f: (E, E2) => E3): F[R, E3, A] = - catchAll(firstOp)(e => leftMap(secondOp)(f(e, _))) - override def orElse[R, E, A, E2](r: F[R, E, A], f: => F[R, E2, A]): F[R, E2, A] = catchAll(r)(_ => f) -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ErrorAccumulatingOps2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ErrorAccumulatingOps2.scala new file mode 100644 index 0000000000..3a13063f3d --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/ErrorAccumulatingOps2.scala @@ -0,0 +1,147 @@ +package izumi.functional.bio + +import izumi.fundamentals.collections.nonempty.NEList + +import scala.collection.compat.* +import scala.collection.compat.immutable.LazyList +import scala.collection.compat.immutable.LazyList.#:: +import scala.collection.immutable.Queue + +trait ErrorAccumulatingOps2[F[+_, +_]] { this: Error2[F] => + + /** `traverse` with error accumulation */ + def traverseAccumErrors[ColR[x] <: IterableOnce[x], ColL[_], E, A, B]( + col: ColR[A] + )(f: A => F[ColL[E], B] + )(implicit + buildR: Factory[B, ColR[B]], + buildL: Factory[E, ColL[E]], + iterL: ColL[E] => IterableOnce[E], + ): F[ColL[E], ColR[B]] = { + accumulateErrorsImpl(col)( + effect = f, + onLeft = (l: ColL[E]) => iterL(l), + init = Queue.empty[B], + onRight = (acc: Queue[B], v: B) => acc :+ v, + end = (acc: Queue[B]) => acc.to(buildR), + ) + } + + /** `traverse_` with error accumulation */ + def traverseAccumErrors_[ColR[x] <: IterableOnce[x], ColL[_], E, A]( + col: ColR[A] + )(f: A => F[ColL[E], Unit] + )(implicit + buildL: Factory[E, ColL[E]], + iterL: ColL[E] => IterableOnce[E], + ): F[ColL[E], Unit] = { + accumulateErrorsImpl(col)( + effect = f, + onLeft = (l: ColL[E]) => iterL(l), + init = (), + onRight = (acc: Unit, _: Unit) => acc, + end = (acc: Unit) => acc, + ) + } + + /** `sequence` with error accumulation */ + def sequenceAccumErrors[ColR[x] <: IterableOnce[x], ColL[_], E, A]( + col: ColR[F[ColL[E], A]] + )(implicit + buildR: Factory[A, ColR[A]], + buildL: Factory[E, ColL[E]], + iterL: ColL[E] => IterableOnce[E], + ): F[ColL[E], ColR[A]] = { + traverseAccumErrors(col)(identity) + } + + /** `sequence_` with error accumulation */ + def sequenceAccumErrors_[ColR[x] <: IterableOnce[x], ColL[_], E, A]( + col: ColR[F[ColL[E], A]] + )(implicit + buildL: Factory[E, ColL[E]], + iterL: ColL[E] => IterableOnce[E], + ): F[ColL[E], Unit] = { + traverseAccumErrors_(col)(void(_)) + } + + /** `sequence` with error accumulation */ + def sequenceAccumErrorsNEList[ColR[x] <: IterableOnce[x], E, A]( + col: ColR[F[E, A]] + )(implicit buildR: Factory[A, ColR[A]] + ): F[NEList[E], ColR[A]] = { + accumulateErrorsImpl(col)( + effect = identity, + onLeft = (e: E) => Seq(e), + init = Queue.empty[A], + onRight = (ac: Queue[A], a: A) => ac :+ a, + end = (ac: Queue[A]) => ac.to(buildR), + ) + } + + /** `flatTraverse` with error accumulation */ + def flatTraverseAccumErrors[ColR[x] <: IterableOnce[x], ColIn[x] <: IterableOnce[x], ColL[_], E, A, B]( + col: ColR[A] + )(f: A => F[ColL[E], ColIn[B]] + )(implicit + buildR: Factory[B, ColR[B]], + buildL: Factory[E, ColL[E]], + iterL: ColL[E] => IterableOnce[E], + ): F[ColL[E], ColR[B]] = { + accumulateErrorsImpl(col)( + effect = f, + onLeft = (l: ColL[E]) => iterL(l), + init = Queue.empty[B], + onRight = (acc: Queue[B], v: IterableOnce[B]) => acc ++ v, + end = (acc: Queue[B]) => acc.to(buildR), + ) + } + + /** `flatSequence` with error accumulation */ + def flatSequenceAccumErrors[ColR[x] <: IterableOnce[x], ColIn[x] <: IterableOnce[x], ColL[_], E, A]( + col: ColR[F[ColL[E], ColIn[A]]] + )(implicit + buildR: Factory[A, ColR[A]], + buildL: Factory[E, ColL[E]], + iterL: ColL[E] => IterableOnce[E], + ): F[ColL[E], ColR[A]] = { + flatTraverseAccumErrors(col)(identity) + } + + protected[this] def accumulateErrorsImpl[ColL[_], ColR[x] <: IterableOnce[x], E, E1, A, B, B1, AC]( + col: ColR[A] + )(effect: A => F[E, B], + onLeft: E => IterableOnce[E1], + init: AC, + onRight: (AC, B) => AC, + end: AC => B1, + )(implicit buildL: Factory[E1, ColL[E1]] + ): F[ColL[E1], B1] = { + def go( + bad: Queue[E1], + good: AC, + lazyList: LazyList[A], + allGood: Boolean, + ): F[ColL[E1], B1] = { + lazyList match { + case h #:: tail => + redeem(effect(h))( + e => go(bad ++ onLeft(e), good, tail, allGood = false), + v => { + val newGood = onRight(good, v) + go(bad, newGood, tail, allGood) + }, + ) + case _ => + if (allGood) { + pure(end(good)) + } else { + fail(bad.to(buildL)) + } + } + } + + go(Queue.empty[E1], init, col.iterator.to(LazyList), allGood = true) + } + +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Exit.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Exit.scala index d6c7ac9a08..521f0904ab 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Exit.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Exit.scala @@ -221,7 +221,7 @@ object Exit { // object MonixExit { // def toExit[E, A](exit: Either[Option[bio.Cause[E]], A]): Exit[E, A] = { // exit match { -// case Left(None) => Interruption(new InterruptedException("The task was cancelled."), Trace.empty) +// case Left(None) => Interruption(new InterruptedException("The task was cancelled."), Trace.forUnknownError) // case Left(Some(error)) => toExit(error) // case Right(value) => Success(value) // } @@ -236,8 +236,8 @@ object Exit { // // def toExit[E](cause: bio.Cause[E]): Exit.Failure[E] = { // cause match { -// case bio.Cause.Error(value) => Exit.Error(value, Trace.empty) -// case bio.Cause.Termination(value) => Exit.Termination(value, Trace.empty) +// case bio.Cause.Error(value) => Exit.Error(value, Trace.forUnknownError) +// case bio.Cause.Termination(value) => Exit.Termination(value, Trace.forUnknownError) // } // } // } @@ -257,10 +257,10 @@ object Exit { implicit lazy val ExitInstances: Monad2[Exit] & Bifunctor2[Exit] = new Monad2[Exit] with Bifunctor2[Exit] { override final val InnerF: Functor2[Exit] = this override final def pure[A](a: A): Exit[Nothing, A] = Exit.Success(a) - override final def map[R, E, A, B](r: Exit[E, A])(f: A => B): Exit[E, B] = r.map(f) - override final def bimap[R, E, A, E2, A2](r: Exit[E, A])(f: E => E2, g: A => A2): Exit[E2, A2] = r.leftMap(f).map(g) - override final def leftMap[R, E, A, E2](r: Exit[E, A])(f: E => E2): Exit[E2, A] = r.leftMap(f) - override final def flatMap[R, E, A, B](r: Exit[E, A])(f: A => Exit[E, B]): Exit[E, B] = r.flatMap(f) + override final def map[E, A, B](r: Exit[E, A])(f: A => B): Exit[E, B] = r.map(f) + override final def bimap[E, A, E2, A2](r: Exit[E, A])(f: E => E2, g: A => A2): Exit[E2, A2] = r.leftMap(f).map(g) + override final def leftMap[E, A, E2](r: Exit[E, A])(f: E => E2): Exit[E2, A] = r.leftMap(f) + override final def flatMap[E, A, B](r: Exit[E, A])(f: A => Exit[E, B]): Exit[E, B] = r.flatMap(f) } disableAutoTrace.discard() diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Fork2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Fork2.scala new file mode 100644 index 0000000000..89ba9180a3 --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Fork2.scala @@ -0,0 +1,26 @@ +package izumi.functional.bio + +import izumi.functional.bio.PredefinedHelper.Predefined +import izumi.fundamentals.orphans.`zio.ZIO` + +import scala.concurrent.ExecutionContext + +trait Fork2[F[+_, +_]] extends RootBifunctor[F] with ForkInstances { + def fork[E, A](f: F[E, A]): F[Nothing, Fiber2[F, E, A]] + def forkOn[E, A](ec: ExecutionContext)(f: F[E, A]): F[Nothing, Fiber2[F, E, A]] +} + +private[bio] sealed trait ForkInstances +object ForkInstances extends LowPriorityForkInstances { + /** + * This instance uses 'no more orphans' trick to provide an Optional instance + * only IFF you have zio-core as a dependency without REQUIRING a zio-core dependency. + * + * Optional instance via https://blog.7mind.io/no-more-orphans.html + */ + @inline implicit def ForkZio[ZIO[-_, +_, +_]: `zio.ZIO`]: Predefined.Of[Fork2[ZIO[Any, +_, +_]]] = Predefined(impl.ForkZio.asInstanceOf[Fork2[ZIO[Any, +_, +_]]]) +} +sealed trait LowPriorityForkInstances { + @inline implicit def ForkZioR[ZIO[-_, +_, +_]: `zio.ZIO`, R]: Predefined.Of[Fork2[ZIO[R, +_, +_]]] = Predefined(impl.ForkZio.asInstanceOf[Fork2[ZIO[R, +_, +_]]]) +// @inline implicit def ForkMonix[MonixBIO[+_, +_]: `monix.bio.IO`]: Predefined.Of[Fork2[MonixBIO]] = impl.ForkMonix.asInstanceOf[Predefined.Of[Fork2[MonixBIO]]] +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Fork3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Fork3.scala deleted file mode 100644 index 698f0a784e..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Fork3.scala +++ /dev/null @@ -1,19 +0,0 @@ -package izumi.functional.bio - -import izumi.functional.bio.PredefinedHelper.Predefined -import zio.ZIO - -import scala.concurrent.ExecutionContext - -trait Fork3[F[-_, +_, +_]] extends RootBifunctor[F] with ForkInstances { - def fork[R, E, A](f: F[R, E, A]): F[R, Nothing, Fiber3[F, E, A]] - def forkOn[R, E, A](ec: ExecutionContext)(f: F[R, E, A]): F[R, Nothing, Fiber3[F, E, A]] -} - -private[bio] sealed trait ForkInstances -object ForkInstances extends LowPriorityForkInstances { - @inline implicit def ForkZio: Predefined.Of[Fork3[ZIO]] = Predefined(impl.ForkZio) -} -sealed trait LowPriorityForkInstances { -// @inline implicit def ForkMonix[MonixBIO[+_, +_]: `monix.bio.IO`]: Predefined.Of[Fork2[MonixBIO]] = impl.ForkMonix.asInstanceOf[Predefined.Of[Fork2[MonixBIO]]] -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Functor2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Functor2.scala new file mode 100644 index 0000000000..9020fba6e4 --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Functor2.scala @@ -0,0 +1,15 @@ +package izumi.functional.bio + +import scala.annotation.unused + +trait Functor2[F[+_, +_]] extends RootBifunctor[F] { + def map[E, A, B](r: F[E, A])(f: A => B): F[E, B] + + def as[E, A, B](r: F[E, A])(v: => B): F[E, B] = map(r)(_ => v) + def void[E, A](r: F[E, A]): F[E, Unit] = map(r)(_ => ()) + + /** Extracts the optional value, or returns the given `valueOnNone` value */ + def fromOptionOr[E, A](valueOnNone: => A, r: F[E, Option[A]]): F[E, A] = map(r)(_.getOrElse(valueOnNone)) + + @inline final def widen[E, A, A1](r: F[E, A])(implicit @unused ev: A <:< A1): F[E, A1] = r.asInstanceOf[F[E, A1]] +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Functor3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Functor3.scala deleted file mode 100644 index d3d29abc28..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Functor3.scala +++ /dev/null @@ -1,15 +0,0 @@ -package izumi.functional.bio - -import scala.annotation.unused - -trait Functor3[F[-_, +_, +_]] extends RootBifunctor[F] { - def map[R, E, A, B](r: F[R, E, A])(f: A => B): F[R, E, B] - - def as[R, E, A, B](r: F[R, E, A])(v: => B): F[R, E, B] = map(r)(_ => v) - def void[R, E, A](r: F[R, E, A]): F[R, E, Unit] = map(r)(_ => ()) - - /** Extracts the optional value, or returns the given `valueOnNone` value */ - def fromOptionOr[R, E, A](valueOnNone: => A, r: F[R, E, Option[A]]): F[R, E, A] = map(r)(_.getOrElse(valueOnNone)) - - @inline final def widen[R, E, A, A1](r: F[R, E, A])(implicit @unused ev: A <:< A1): F[R, E, A1] = r.asInstanceOf[F[R, E, A1]] -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Guarantee2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Guarantee2.scala new file mode 100644 index 0000000000..bf90187309 --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Guarantee2.scala @@ -0,0 +1,5 @@ +package izumi.functional.bio + +trait Guarantee2[F[+_, +_]] extends Applicative2[F] { + def guarantee[E, A](f: F[E, A], cleanup: F[Nothing, Unit]): F[E, A] +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Guarantee3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Guarantee3.scala deleted file mode 100644 index 8dd168bc4d..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Guarantee3.scala +++ /dev/null @@ -1,5 +0,0 @@ -package izumi.functional.bio - -trait Guarantee3[F[-_, +_, +_]] extends Applicative3[F] { - def guarantee[R, E, A](f: F[R, E, A], cleanup: F[R, Nothing, Unit]): F[R, E, A] -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/IO2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/IO2.scala new file mode 100644 index 0000000000..04e4d74ea2 --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/IO2.scala @@ -0,0 +1,98 @@ +package izumi.functional.bio + +import scala.collection.compat.* +import scala.util.Try + +trait IO2[F[+_, +_]] extends Panic2[F] { + + /** + * Capture a side-effectful block of code that can throw exceptions + * + * @note `sync` means `synchronous`, that is, a blocking CPU effect, as opposed to a non-blocking + * [[izumi.functional.bio.Async3#async asynchronous]] effect or a + * long blocking I/O effect ([[izumi.functional.bio.BlockingIO2#syncBlocking]]) + */ + def syncThrowable[A](effect: => A): F[Throwable, A] + + /** + * Capture an _exception-safe_ side-effect such as memory mutation or randomness + * + * @example + * {{{ + * import izumi.functional.bio.F + * + * val exceptionSafeArrayAllocation: F[Nothing, Array[Byte]] = { + * F.sync(new Array(256)) + * } + * }}} + * @note If you're not completely sure that a captured block can't throw, use [[syncThrowable]] + * @note `sync` means `synchronous`, that is, a blocking CPU effect, as opposed to a non-blocking + * [[izumi.functional.bio.Async3#async asynchronous]] effect or a + * long blocking I/O effect ([[izumi.functional.bio.BlockingIO2#syncBlocking]]) + */ + def sync[A](effect: => A): F[Nothing, A] + + /** Capture a side-effectful block of code that can throw exceptions and returns another effect */ + def suspend[A](effect: => F[Throwable, A]): F[Throwable, A] = flatten(syncThrowable(effect)) + + /** Capture an _exception-safe_ side-effect that returns another effect */ + def suspendSafe[E, A](effect: => F[E, A]): F[E, A] = flatten(sync(effect)) + + @inline final def apply[A](effect: => A): F[Throwable, A] = syncThrowable(effect) + + // defaults + override def fromEither[E, A](effect: => Either[E, A]): F[E, A] = flatMap(sync(effect)) { + case Left(e) => fail(e): F[E, A] + case Right(v) => pure(v): F[E, A] + } + override def fromOption[E, A](errorOnNone: => E)(effect: => Option[A]): F[E, A] = { + flatMap(sync(effect))(opt => opt.fold(fail(errorOnNone): F[E, A])(pure)) + } + override def fromTry[A](effect: => Try[A]): F[Throwable, A] = { + syncThrowable(effect.get) + } + + override protected[this] def accumulateErrorsImpl[ColL[_], ColR[x] <: IterableOnce[x], E, E1, A, B, B1, AC]( + col: ColR[A] + )(effect: A => F[E, B], + onLeft: E => IterableOnce[E1], + init: AC, + onRight: (AC, B) => AC, + end: AC => B1, + )(implicit buildL: Factory[E1, ColL[E1]] + ): F[ColL[E1], B1] = { + suspendSafe { + val bad = buildL.newBuilder + + val iterator = col.iterator + var good = init + var allGood = true + + def go(): F[ColL[E1], B1] = { + suspendSafe(if (iterator.hasNext) { + redeem(effect(iterator.next()))( + e => + suspendSafe { + allGood = false + bad ++= onLeft(e) + go() + }, + v => + suspendSafe { + good = onRight(good, v) + go() + }, + ) + } else { + if (allGood) { + pure(end(good)) + } else { + fail(bad.result()) + } + }) + } + + go() + } + } +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/IO3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/IO3.scala deleted file mode 100644 index e0c2c8021b..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/IO3.scala +++ /dev/null @@ -1,52 +0,0 @@ -package izumi.functional.bio - -import scala.util.Try - -trait IO3[F[-_, +_, +_]] extends Panic3[F] { - final type Or[+E, +A] = F[Any, E, A] - final type Just[+A] = F[Any, Nothing, A] - - /** - * Capture a side-effectful block of code that can throw exceptions - * - * @note `sync` means `synchronous`, that is, a blocking CPU effect, as opposed to a non-blocking - * [[izumi.functional.bio.Async3#async asynchronous]] effect or a - * long blocking I/O effect ([[izumi.functional.bio.BlockingIO3#syncBlocking]]) - */ - def syncThrowable[A](effect: => A): F[Any, Throwable, A] - - /** - * Capture an _exception-safe_ side-effect such as memory mutation or randomness - * - * @example - * {{{ - * import izumi.functional.bio.F - * - * val referentiallyTransparentArrayAllocation: F[Nothing, Array[Byte]] = { - * F.sync(new Array(256)) - * } - * }}} - * - * @note If you're not completely sure that a captured block can't throw, use [[syncThrowable]] - * @note `sync` means `synchronous`, that is, a blocking CPU effect, as opposed to a non-blocking - * [[izumi.functional.bio.Async3#async asynchronous]] effect or a - * long blocking I/O effect ([[izumi.functional.bio.BlockingIO3#syncBlocking]]) - */ - def sync[A](effect: => A): F[Any, Nothing, A] - - def suspend[R, A](effect: => F[R, Throwable, A]): F[R, Throwable, A] = flatten(syncThrowable(effect)) - - @inline final def apply[A](effect: => A): F[Any, Throwable, A] = syncThrowable(effect) - - // defaults - override def fromEither[E, A](effect: => Either[E, A]): F[Any, E, A] = flatMap(sync(effect)) { - case Left(e) => fail(e): F[Any, E, A] - case Right(v) => pure(v): F[Any, E, A] - } - override def fromOption[E, A](errorOnNone: => E)(effect: => Option[A]): F[Any, E, A] = { - flatMap(sync(effect))(opt => opt.fold(fail(errorOnNone): F[Any, E, A])(pure)) - } - override def fromTry[A](effect: => Try[A]): F[Any, Throwable, A] = { - syncThrowable(effect.get) - } -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Local3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Local3.scala deleted file mode 100644 index 6a07eed30f..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Local3.scala +++ /dev/null @@ -1,27 +0,0 @@ -package izumi.functional.bio - -import cats.data.Kleisli - -trait Local3[FR[-_, +_, +_]] extends MonadAsk3[FR] with ArrowChoice3[FR] with LocalInstances { - override def InnerF: Monad3[FR] - - def provide[R, E, A](fr: FR[R, E, A])(env: => R): FR[Any, E, A] = contramap(fr)((_: Any) => env) - - // defaults - override def dimap[R1, E, A1, R2, A2](fab: FR[R1, E, A1])(f: R2 => R1)(g: A1 => A2): FR[R2, E, A2] = InnerF.map(contramap(fab)(f))(g) - override def choose[RL, RR, E, AL, AR](f: FR[RL, E, AL], g: FR[RR, E, AR]): FR[Either[RL, RR], E, Either[AL, AR]] = access { - case Left(a) => InnerF.map(provide(f)(a))(Left(_)) - case Right(b) => InnerF.map(provide(g)(b))(Right(_)): FR[Either[RL, RR], E, Either[AL, AR]] - } - override def askWith[R, A](f: R => A): FR[R, Nothing, A] = InnerF.map(ask[R])(f) - override def andThen[R, R1, E, A](f: FR[R, E, R1], g: FR[R1, E, A]): FR[R, E, A] = InnerF.flatMap(f)(provide(g)(_)) - override def asking[R, E, A](f: FR[R, E, A]): FR[R, E, (A, R)] = InnerF.map2(ask[R], f)((r, a) => (a, r)) - override def access[R, E, A](f: R => FR[R, E, A]): FR[R, E, A] = InnerF.flatMap(ask[R])(f) -} - -private[bio] sealed trait LocalInstances -object LocalInstances { - implicit final class ToKleisliSyntaxLocal[FR[-_, +_, +_]](private val FR: Local3[FR]) extends AnyVal { - @inline final def toKleisli[R, E, A](fr: FR[R, E, A]): Kleisli[FR[Any, E, _], R, A] = Kleisli(FR.provide(fr)(_)) - } -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Monad2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Monad2.scala new file mode 100644 index 0000000000..11ecaa943d --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Monad2.scala @@ -0,0 +1,121 @@ +package izumi.functional.bio + +import scala.annotation.unused + +trait Monad2[F[+_, +_]] extends Applicative2[F] { + def flatMap[E, A, B](r: F[E, A])(f: A => F[E, B]): F[E, B] + def flatten[E, A](r: F[E, F[E, A]]): F[E, A] = flatMap(r)(identity) + + def tailRecM[E, A, B](a: A)(f: A => F[E, Either[A, B]]): F[E, B] = { + def loop(a: A): F[E, B] = flatMap(f(a)) { + case Left(next) => loop(next) + case Right(res) => pure(res) + } + + flatMap[E, Unit, B](unit)(_ => loop(a)) // https://github.com/zio/interop-cats/pull/460 + } + + def tap[E, A](r: F[E, A], f: A => F[E, Unit]): F[E, A] = flatMap(r)(a => as(f(a))(a)) + + @inline final def when[E, E1](cond: F[E, Boolean])(ifTrue: => F[E1, Unit])(implicit ev: E <:< E1): F[E1, Unit] = { + ifThenElse(cond)(ifTrue, unit) + } + @inline final def unless[E, E1](cond: F[E, Boolean])(ifFalse: => F[E1, Unit])(implicit ev: E <:< E1): F[E1, Unit] = { + ifThenElse(cond)(unit, ifFalse) + } + @inline final def ifThenElse[E, E1, A]( + cond: F[E, Boolean] + )(ifTrue: => F[E1, A], + ifFalse: => F[E1, A], + )(implicit @unused ev: E <:< E1 + ): F[E1, A] = { + flatMap(cond.asInstanceOf[F[E1, Boolean]])(if (_) ifTrue else ifFalse) + } + + /** Extracts the optional value, or executes the `fallbackOnNone` effect */ + def fromOptionF[E, A](fallbackOnNone: => F[E, A], r: F[E, Option[A]]): F[E, A] = { + flatMap(r) { + case Some(value) => pure(value) + case None => fallbackOnNone + } + } + + def foldLeft[E, A, AC](l: Iterable[A])(z: AC)(f: (AC, A) => F[E, AC]): F[E, AC] = { + def go(l: List[A], ac: AC): F[E, AC] = { + l match { + case head :: tail => flatMap(f(ac, head))(go(tail, _)) + case Nil => pure(ac) + } + } + go(l.toList, z) + } + + def find[E, A](l: Iterable[A])(f: A => F[E, Boolean]): F[E, Option[A]] = { + def go(l: List[A]): F[E, Option[A]] = { + l match { + case head :: tail => flatMap(f(head))(if (_) pure(Some(head)) else go(tail)) + case Nil => pure(None) + } + } + go(l.toList) + } + + def collectFirst[E, A, B](l: Iterable[A])(f: A => F[E, Option[B]]): F[E, Option[B]] = { + def go(l: List[A]): F[E, Option[B]] = { + l match { + case head :: tail => + flatMap(f(head)) { + case res @ Some(_) => pure(res) + case None => go(tail) + } + case Nil => pure(None) + } + } + go(l.toList) + } + + /** + * Execute an action repeatedly until its result fails to satisfy the given predicate + * and return that result, discarding all others. + */ + def iterateWhile[E, A](r: F[E, A])(p: A => Boolean): F[E, A] = { + flatMap(r)(i => iterateWhileF(i)(_ => r)(p)) + } + + /** + * Execute an action repeatedly until its result satisfies the given predicate + * and return that result, discarding all others. + */ + def iterateUntil[E, A](r: F[E, A])(p: A => Boolean): F[E, A] = { + flatMap(r)(i => iterateUntilF(i)(_ => r)(p)) + } + + /** + * Apply an effectful function iteratively until its result fails + * to satisfy the given predicate and return that result. + */ + def iterateWhileF[E, A](init: A)(f: A => F[E, A])(p: A => Boolean): F[E, A] = { + tailRecM(init) { + a => + if (p(a)) { + map(f(a))(Left(_)) + } else { + pure(Right(a)) + } + } + } + + /** + * Apply an effectful function iteratively until its result satisfies + * the given predicate and return that result. + */ + def iterateUntilF[E, A](init: A)(f: A => F[E, A])(p: A => Boolean): F[E, A] = { + iterateWhileF(init)(f)(!p(_)) + } + + // defaults + override def map[E, A, B](r: F[E, A])(f: A => B): F[E, B] = flatMap(r)(a => pure(f(a))) + override def *>[E, A, B](f: F[E, A], next: => F[E, B]): F[E, B] = flatMap(f)(_ => next) + override def <*[E, A, B](f: F[E, A], next: => F[E, B]): F[E, A] = flatMap(f)(a => map(next)(_ => a)) + override def map2[E, A, B, C](r1: F[E, A], r2: => F[E, B])(f: (A, B) => C): F[E, C] = flatMap(r1)(a => map(r2)(b => f(a, b))) +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Monad3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Monad3.scala deleted file mode 100644 index e23b707594..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Monad3.scala +++ /dev/null @@ -1,87 +0,0 @@ -package izumi.functional.bio - -import scala.annotation.unused - -trait Monad3[F[-_, +_, +_]] extends Applicative3[F] { - def flatMap[R, E, A, B](r: F[R, E, A])(f: A => F[R, E, B]): F[R, E, B] - def flatten[R, E, A](r: F[R, E, F[R, E, A]]): F[R, E, A] = flatMap(r)(identity) - - def tailRecM[R, E, A, B](a: A)(f: A => F[R, E, Either[A, B]]): F[R, E, B] = { - def loop(a: A): F[R, E, B] = flatMap(f(a)) { - case Left(next) => loop(next) - case Right(res) => pure(res) - } - - flatMap[R, E, Unit, B](unit)(_ => loop(a)) // https://github.com/zio/interop-cats/pull/460 - } - - def tap[R, E, A](r: F[R, E, A], f: A => F[R, E, Unit]): F[R, E, A] = flatMap(r)(a => as(f(a))(a)) - - @inline final def when[R, E, E1](cond: F[R, E, Boolean])(ifTrue: => F[R, E1, Unit])(implicit ev: E <:< E1): F[R, E1, Unit] = { - ifThenElse(cond)(ifTrue, unit) - } - @inline final def unless[R, E, E1](cond: F[R, E, Boolean])(ifFalse: => F[R, E1, Unit])(implicit ev: E <:< E1): F[R, E1, Unit] = { - ifThenElse(cond)(unit, ifFalse) - } - @inline final def ifThenElse[R, E, E1, A]( - cond: F[R, E, Boolean] - )(ifTrue: => F[R, E1, A], - ifFalse: => F[R, E1, A], - )(implicit @unused ev: E <:< E1 - ): F[R, E1, A] = { - flatMap(cond.asInstanceOf[F[R, E1, Boolean]])(if (_) ifTrue else ifFalse) - } - - /** Extracts the optional value, or executes the `fallbackOnNone` effect */ - def fromOptionF[R, E, A](fallbackOnNone: => F[R, E, A], r: F[R, E, Option[A]]): F[R, E, A] = { - flatMap(r) { - case Some(value) => pure(value) - case None => fallbackOnNone - } - } - - /** - * Execute an action repeatedly until its result fails to satisfy the given predicate - * and return that result, discarding all others. - */ - def iterateWhile[R, E, A](r: F[R, E, A])(p: A => Boolean): F[R, E, A] = { - flatMap(r)(i => iterateWhileF(i)(_ => r)(p)) - } - - /** - * Execute an action repeatedly until its result satisfies the given predicate - * and return that result, discarding all others. - */ - def iterateUntil[R, E, A](r: F[R, E, A])(p: A => Boolean): F[R, E, A] = { - flatMap(r)(i => iterateUntilF(i)(_ => r)(p)) - } - - /** - * Apply an effectful function iteratively until its result fails - * to satisfy the given predicate and return that result. - */ - def iterateWhileF[R, E, A](init: A)(f: A => F[R, E, A])(p: A => Boolean): F[R, E, A] = { - tailRecM(init) { - a => - if (p(a)) { - map(f(a))(Left(_)) - } else { - pure(Right(a)) - } - } - } - - /** - * Apply an effectful function iteratively until its result satisfies - * the given predicate and return that result. - */ - def iterateUntilF[R, E, A](init: A)(f: A => F[R, E, A])(p: A => Boolean): F[R, E, A] = { - iterateWhileF(init)(f)(!p(_)) - } - - // defaults - override def map[R, E, A, B](r: F[R, E, A])(f: A => B): F[R, E, B] = flatMap(r)(a => pure(f(a))) - override def *>[R, E, A, B](f: F[R, E, A], next: => F[R, E, B]): F[R, E, B] = flatMap(f)(_ => next) - override def <*[R, E, A, B](f: F[R, E, A], next: => F[R, E, B]): F[R, E, A] = flatMap(f)(a => map(next)(_ => a)) - override def map2[R, E, A, B, C](r1: F[R, E, A], r2: => F[R, E, B])(f: (A, B) => C): F[R, E, C] = flatMap(r1)(a => map(r2)(b => f(a, b))) -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/MonadAsk3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/MonadAsk3.scala deleted file mode 100644 index 5a790c6f41..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/MonadAsk3.scala +++ /dev/null @@ -1,16 +0,0 @@ -package izumi.functional.bio - -import cats.data.Kleisli - -trait MonadAsk3[FR[-_, +_, +_]] extends Ask3[FR] with MonadAskSyntax { - override def InnerF: Monad3[FR] - - def access[R, E, A](f: R => FR[R, E, A]): FR[R, E, A] -} - -private[bio] sealed trait MonadAskSyntax -object MonadAskSyntax { - implicit final class KleisliSyntaxMonadAsk[FR[-_, +_, +_]](private val FR: MonadAsk3[FR]) extends AnyVal { - @inline def fromKleisli[R, E, A](k: Kleisli[FR[Any, E, _], R, A]): FR[R, E, A] = FR.access(k.run) - } -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Panic3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Panic2.scala similarity index 71% rename from fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Panic3.scala rename to fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Panic2.scala index 0990b6c910..90fa9739cb 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Panic3.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Panic2.scala @@ -1,14 +1,14 @@ package izumi.functional.bio import cats.~> -import izumi.functional.bio.data.RestoreInterruption3 +import izumi.functional.bio.data.RestoreInterruption2 -trait Panic3[F[-_, +_, +_]] extends Bracket3[F] with PanicSyntax { - def terminate(v: => Throwable): F[Any, Nothing, Nothing] +trait Panic2[F[+_, +_]] extends Bracket2[F] with PanicSyntax { + def terminate(v: => Throwable): F[Nothing, Nothing] /** @note Will return either [[Exit.Error]] or [[Exit.Termination]] in the error channel. * [[Exit.Interruption]] cannot be sandboxed. Use [[guaranteeOnInterrupt]] for cleanups on interruptions. */ - def sandbox[R, E, A](r: F[R, E, A]): F[R, Exit.Failure[E], A] + def sandbox[E, A](r: F[E, A]): F[Exit.Failure[E], A] /** * Signal interruption to this fiber. @@ -16,7 +16,7 @@ trait Panic3[F[-_, +_, +_]] extends Bracket3[F] with PanicSyntax { * This is _NOT_ the same as * * {{{ - * F.halt(Exit.Interrupted(Trace.empty)) + * F.halt(Exit.Interrupted(Trace.forUnknownError)) * }}} * * The code above exits with `Exit.Interrupted` failure *unconditionally*, @@ -25,7 +25,7 @@ trait Panic3[F[-_, +_, +_]] extends Bracket3[F] with PanicSyntax { * * {{{ * F.uninterruptible { - * F.halt(Exit.Interrupted(Trace.empty)) *> + * F.halt(Exit.Interrupted(Trace.forUnknownError)) *> * F.sync(println("Hello!")) // interrupted above. Hello _not_ printed * } * }}} @@ -43,9 +43,9 @@ trait Panic3[F[-_, +_, +_]] extends Bracket3[F] with PanicSyntax { * - [[https://github.com/zio/interop-cats/issues/503]] - History of supporting this method in ZIO * - [[https://github.com/zio/zio/issues/6911]] - related issue */ - def sendInterruptToSelf: F[Any, Nothing, Unit] + def sendInterruptToSelf: F[Nothing, Unit] - def uninterruptible[R, E, A](r: F[R, E, A]): F[R, E, A] = { + def uninterruptible[E, A](r: F[E, A]): F[E, A] = { uninterruptibleExcept(_ => r) } @@ -82,28 +82,28 @@ trait Panic3[F[-_, +_, +_]] extends Bracket3[F] with PanicSyntax { * `F.uninterruptible { F.uninterruptibleExcept { restore => restore(F.sleep(1.second)) }` * is fully uninterruptible throughout */ - def uninterruptibleExcept[R, E, A](r: RestoreInterruption3[F] => F[R, E, A]): F[R, E, A] + def uninterruptibleExcept[E, A](r: RestoreInterruption2[F] => F[E, A]): F[E, A] /** Like [[bracketCase]], but `acquire` can contain marked interruptible regions as in [[uninterruptibleExcept]] */ - def bracketExcept[R, E, A, B](acquire: RestoreInterruption3[F] => F[R, E, A])(release: (A, Exit[E, B]) => F[R, Nothing, Unit])(use: A => F[R, E, B]): F[R, E, B] + def bracketExcept[E, A, B](acquire: RestoreInterruption2[F] => F[E, A])(release: (A, Exit[E, B]) => F[Nothing, Unit])(use: A => F[E, B]): F[E, B] - @inline final def orTerminate[R, A](r: F[R, Throwable, A]): F[R, Nothing, A] = { + @inline final def orTerminate[A](r: F[Throwable, A]): F[Nothing, A] = { catchAll(r)(terminate(_)) } /** @note Will return either [[Exit.Error]] or [[Exit.Termination]]. [[Exit.Interruption]] cannot be sandboxed. * Use [[guaranteeOnInterrupt]] for cleanups on interruptions. */ - @inline final def sandboxExit[R, E, A](r: F[R, E, A]): F[R, Nothing, Exit[E, A]] = { + @inline final def sandboxExit[E, A](r: F[E, A]): F[Nothing, Exit[E, A]] = { redeemPure(sandbox(r))(identity, Exit.Success(_)) } } private[bio] sealed trait PanicSyntax object PanicSyntax { - implicit final class PanicOrTerminateK[F[-_, +_, +_]](private val F: Panic3[F]) extends AnyVal { - def orTerminateK[R]: F[R, Throwable, _] ~> F[R, Nothing, _] = { - new (F[R, Throwable, _] ~> F[R, Nothing, _]) { - override def apply[A](fa: F[R, Throwable, A]): F[R, Nothing, A] = F.orTerminate(fa) + implicit final class PanicOrTerminateK[F[+_, +_]](private val F: Panic2[F]) extends AnyVal { + def orTerminateK[R]: F[Throwable, _] ~> F[Nothing, _] = { + new (F[Throwable, _] ~> F[Nothing, _]) { + override def apply[A](fa: F[Throwable, A]): F[Nothing, A] = F.orTerminate(fa) } } } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Parallel2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Parallel2.scala new file mode 100644 index 0000000000..b61c685c6b --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Parallel2.scala @@ -0,0 +1,42 @@ +package izumi.functional.bio + +trait Parallel2[F[+_, +_]] extends RootBifunctor[F] { + def InnerF: Monad2[F] + + def parTraverse[E, A, B](l: Iterable[A])(f: A => F[E, B]): F[E, List[B]] + def parTraverseN[E, A, B](maxConcurrent: Int)(l: Iterable[A])(f: A => F[E, B]): F[E, List[B]] + /** [[parTraverseN]] with `maxConcurrent` set to the number of cores, or 2 when on single-core processor */ + def parTraverseNCore[E, A, B](l: Iterable[A])(f: A => F[E, B]): F[E, List[B]] + def parTraverse_[E, A, B](l: Iterable[A])(f: A => F[E, B]): F[E, Unit] = InnerF.void(parTraverse(l)(f)) + def parTraverseN_[E, A, B](maxConcurrent: Int)(l: Iterable[A])(f: A => F[E, B]): F[E, Unit] = InnerF.void(parTraverseN(maxConcurrent)(l)(f)) + def parTraverseNCore_[E, A, B](l: Iterable[A])(f: A => F[E, B]): F[E, Unit] = InnerF.void(parTraverseNCore(l)(f)) + + /** + * Returns an effect that executes both effects, + * in parallel, combining their results with the specified `f` function. If + * either side fails, then the other side will be interrupted. + */ + def zipWithPar[E, A, B, C](fa: F[E, A], fb: F[E, B])(f: (A, B) => C): F[E, C] + + // defaults + /** + * Returns an effect that executes both effects, + * in parallel, combining their results into a tuple. If either side fails, + * then the other side will be interrupted. + */ + def zipPar[E, A, B](fa: F[E, A], fb: F[E, B]): F[E, (A, B)] = zipWithPar(fa, fb)((a, b) => (a, b)) + + /** + * Returns an effect that executes both effects, + * in parallel, the left effect result is returned. If either side fails, + * then the other side will be interrupted. + */ + def zipParLeft[E, A, B](fa: F[E, A], fb: F[E, B]): F[E, A] = zipWithPar(fa, fb)((a, _) => a) + + /** + * Returns an effect that executes both effects, + * in parallel, the right effect result is returned. If either side fails, + * then the other side will be interrupted. + */ + def zipParRight[E, A, B](fa: F[E, A], fb: F[E, B]): F[E, B] = zipWithPar(fa, fb)((_, b) => b) +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Parallel3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Parallel3.scala deleted file mode 100644 index d70f5c0970..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Parallel3.scala +++ /dev/null @@ -1,42 +0,0 @@ -package izumi.functional.bio - -trait Parallel3[F[-_, +_, +_]] extends RootBifunctor[F] { - def InnerF: Monad3[F] - - def parTraverse[R, E, A, B](l: Iterable[A])(f: A => F[R, E, B]): F[R, E, List[B]] - def parTraverseN[R, E, A, B](maxConcurrent: Int)(l: Iterable[A])(f: A => F[R, E, B]): F[R, E, List[B]] - /** [[parTraverseN]] with `maxConcurrent` set to the number of cores, or 2 when on single-core processor */ - def parTraverseNCore[R, E, A, B](l: Iterable[A])(f: A => F[R, E, B]): F[R, E, List[B]] - def parTraverse_[R, E, A, B](l: Iterable[A])(f: A => F[R, E, B]): F[R, E, Unit] = InnerF.void(parTraverse(l)(f)) - def parTraverseN_[R, E, A, B](maxConcurrent: Int)(l: Iterable[A])(f: A => F[R, E, B]): F[R, E, Unit] = InnerF.void(parTraverseN(maxConcurrent)(l)(f)) - def parTraverseNCore_[R, E, A, B](l: Iterable[A])(f: A => F[R, E, B]): F[R, E, Unit] = InnerF.void(parTraverseNCore(l)(f)) - - /** - * Returns an effect that executes both effects, - * in parallel, combining their results with the specified `f` function. If - * either side fails, then the other side will be interrupted. - */ - def zipWithPar[R, E, A, B, C](fa: F[R, E, A], fb: F[R, E, B])(f: (A, B) => C): F[R, E, C] - - // defaults - /** - * Returns an effect that executes both effects, - * in parallel, combining their results into a tuple. If either side fails, - * then the other side will be interrupted. - */ - def zipPar[R, E, A, B](fa: F[R, E, A], fb: F[R, E, B]): F[R, E, (A, B)] = zipWithPar(fa, fb)((a, b) => (a, b)) - - /** - * Returns an effect that executes both effects, - * in parallel, the left effect result is returned. If either side fails, - * then the other side will be interrupted. - */ - def zipParLeft[R, E, A, B](fa: F[R, E, A], fb: F[R, E, B]): F[R, E, A] = zipWithPar(fa, fb)((a, _) => a) - - /** - * Returns an effect that executes both effects, - * in parallel, the right effect result is returned. If either side fails, - * then the other side will be interrupted. - */ - def zipParRight[R, E, A, B](fa: F[R, E, A], fb: F[R, E, B]): F[R, E, B] = zipWithPar(fa, fb)((_, b) => b) -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Primitives2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Primitives2.scala index 0141580dc4..b1c8b6804b 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Primitives2.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Primitives2.scala @@ -25,9 +25,10 @@ object Primitives2 { } private[bio] sealed trait PrimitivesInstances -object PrimitivesInstances { - @inline implicit def PrimitivesZio[F[-_, +_, +_]: `zio.ZIO`]: Primitives2[F[Any, +_, +_]] = impl.PrimitivesZio.asInstanceOf[Primitives3[F]] +object PrimitivesInstances extends PrimitivesInstancesLowPriority { + @inline implicit def PrimitivesZio[F[-_, +_, +_]: `zio.ZIO`]: Primitives2[F[Any, +_, +_]] = impl.PrimitivesZio.asInstanceOf[Primitives2[F[Any, +_, +_]]] +} - // do not use Primitives3 alias here because it confuses both Scala 2 and Scala 3 typechecker in certain cases -// @inline implicit def PrimitivesZio[F[-_, +_, +_]: `zio.ZIO`]: Primitives3[F] = impl.PrimitivesZio.asInstanceOf[Primitives3[F]] +sealed trait PrimitivesInstancesLowPriority { + @inline implicit def PrimitivesZioR[F[-_, +_, +_]: `zio.ZIO`, R]: Primitives2[F[R, +_, +_]] = impl.PrimitivesZio.asInstanceOf[Primitives2[F[R, +_, +_]]] } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/PrimitivesM2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/PrimitivesM2.scala index 565c5d23dd..8469efa33c 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/PrimitivesM2.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/PrimitivesM2.scala @@ -20,13 +20,14 @@ object PrimitivesM2 { } private[bio] sealed trait PrimitivesMInstances -object PrimitivesMInstances extends PrimitivesMLowPriorityInstances { - @inline implicit def PrimitivesZio[F[-_, +_, +_]: `zio.ZIO`]: PrimitivesM2[F[Any, +_, +_]] = impl.PrimitivesMZio.asInstanceOf[PrimitivesM3[F]] - - // do not use PrimitivesM3 alias here because it confuses both Scala 2 and Scala 3 typechecker in certain cases -// @inline implicit def PrimitivesZio[F[-_, +_, +_]: `zio.ZIO`]: PrimitivesM3[F] = impl.PrimitivesMZio.asInstanceOf[PrimitivesM3[F]] +object PrimitivesMInstances extends PrimitivesMLowPriorityInstances1 { + @inline implicit def PrimitivesZio[F[-_, +_, +_]: `zio.ZIO`]: PrimitivesM2[F[Any, +_, +_]] = impl.PrimitivesMZio.asInstanceOf[PrimitivesM2[F[Any, +_, +_]]] } -sealed trait PrimitivesMLowPriorityInstances { +sealed trait PrimitivesMLowPriorityInstances1 extends PrimitivesMLowPriorityInstances2 { + @inline implicit def PrimitivesZioR[F[-_, +_, +_]: `zio.ZIO`, R]: PrimitivesM2[F[R, +_, +_]] = impl.PrimitivesMZio.asInstanceOf[PrimitivesM2[F[R, +_, +_]]] + +} +sealed trait PrimitivesMLowPriorityInstances2 { @inline implicit def PrimitivesFromBIO[F[+_, +_]: Bracket2: Primitives2]: PrimitivesM2[F] = new PrimitivesMFromBIO[F] } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Profunctor3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Profunctor3.scala deleted file mode 100644 index 3b806a8a40..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Profunctor3.scala +++ /dev/null @@ -1,10 +0,0 @@ -package izumi.functional.bio - -trait Profunctor3[FR[-_, +_, +_]] extends RootTrifunctor[FR] { - def InnerF: Functor3[FR] - - def dimap[R1, E, A1, R2, A2](fra: FR[R1, E, A1])(fr: R2 => R1)(fa: A1 => A2): FR[R2, E, A2] - - // defaults - def contramap[R, E, A, R0](fr: FR[R, E, A])(f: R0 => R): FR[R0, E, A] = dimap(fr)(f)(identity) -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/RefM2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/RefM2.scala index c77fcf7901..d951897c74 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/RefM2.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/RefM2.scala @@ -3,7 +3,7 @@ package izumi.functional.bio import izumi.functional.bio.data.Isomorphism2 import izumi.fundamentals.platform.language.Quirks.Discarder import zio.internal.stacktracer.{InteropTracer, Tracer} -import zio.{IO, Ref} +import zio.{Ref, ZIO} import zio.stacktracer.TracingImplicits.disableAutoTrace trait RefM2[F[+_, +_], A] { @@ -16,13 +16,13 @@ trait RefM2[F[+_, +_], A] { } object RefM2 { - def fromZIO[A](ref: Ref.Synchronized[A]): RefM2[IO, A] = - new RefM2[IO, A] { - override def get: IO[Nothing, A] = ref.get(Tracer.newTrace) - override def set(a: A): IO[Nothing, Unit] = ref.set(a)(Tracer.newTrace) - override def modify[E, B](f: A => IO[E, (B, A)]): IO[E, B] = ref.modifyZIO(f)(InteropTracer.newTrace(f)) - override def update[E](f: A => IO[E, A]): IO[E, A] = ref.updateAndGetZIO(f)(InteropTracer.newTrace(f)) - override def update_[E](f: A => IO[E, A]): IO[E, Unit] = ref.updateZIO(f)(InteropTracer.newTrace(f)) + def fromZIO[R, A](ref: Ref.Synchronized[A]): RefM2[ZIO[R, +_, +_], A] = + new RefM2[ZIO[R, +_, +_], A] { + override def get: ZIO[R, Nothing, A] = ref.get(Tracer.newTrace) + override def set(a: A): ZIO[R, Nothing, Unit] = ref.set(a)(Tracer.newTrace) + override def modify[E, B](f: A => ZIO[R, E, (B, A)]): ZIO[R, E, B] = ref.modifyZIO(f)(InteropTracer.newTrace(f)) + override def update[E](f: A => ZIO[R, E, A]): ZIO[R, E, A] = ref.updateAndGetZIO(f)(InteropTracer.newTrace(f)) + override def update_[E](f: A => ZIO[R, E, A]): ZIO[R, E, Unit] = ref.updateZIO(f)(InteropTracer.newTrace(f)) } def createFromBIO[F[+_, +_]: Bracket2: Primitives2, A](a: A): F[Nothing, RefM2[F, A]] = { diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Root.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Root.scala index 62375a8331..778e00f8a4 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Root.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Root.scala @@ -1,105 +1,87 @@ package izumi.functional.bio -import cats.data.Kleisli import izumi.functional.bio.PredefinedHelper.{NotPredefined, Predefined} import izumi.functional.bio.SpecificityHelper.* -import izumi.functional.bio.impl.{AsyncZio, BioEither, BioIdentity3} -import izumi.functional.bio.retry.Scheduler3 -import izumi.functional.bio.syntax.Syntax3 -import izumi.fundamentals.platform.functional.{Identity2, Identity3} +import izumi.functional.bio.impl.{AsyncZio, BioEither, BioIdentity2} +import izumi.functional.bio.retry.Scheduler2 +import izumi.functional.bio.syntax.Syntax2 +import izumi.fundamentals.orphans.`zio.ZIO` +import izumi.fundamentals.platform.functional.Identity2 import scala.annotation.unused -import zio.ZIO - import scala.language.implicitConversions -trait RootBifunctor[F[-_, +_, +_]] extends Root - -trait RootTrifunctor[F[-_, +_, +_]] extends Root +trait RootBifunctor[F[+_, +_]] extends Root trait Root extends DivergenceHelper with PredefinedHelper object Root extends RootInstancesLowPriority1 { - @inline implicit final def ConvertFromConcurrent[FR[-_, +_, +_]](implicit Concurrent: NotPredefined.Of[Concurrent3[FR]]): Predefined.Of[Panic3[FR] & S1] = + @inline implicit final def ConvertFromConcurrent[F[+_, +_]](implicit Concurrent: NotPredefined.Of[Concurrent2[F]]): Predefined.Of[Panic2[F] & S1] = Predefined(S1(Concurrent.InnerF)) - @inline implicit final def AttachLocal[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Local: Local3[FR]): Local.type = Local - @inline implicit final def AttachPrimitives3[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Primitives: Primitives3[FR]): Primitives.type = + @inline implicit final def AttachPrimitives[F[+_, +_]](@unused self: Functor2[F])(implicit Primitives: Primitives2[F]): Primitives.type = Primitives - @inline implicit final def AttachScheduler3[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Scheduler: Scheduler3[FR]): Scheduler.type = Scheduler - @inline implicit final def AttachPrimitivesM3[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit PrimitivesM: PrimitivesM3[FR]): PrimitivesM.type = + @inline implicit final def AttachScheduler[F[+_, +_]](@unused self: Functor2[F])(implicit Scheduler: Scheduler2[F]): Scheduler.type = Scheduler + @inline implicit final def AttachPrimitivesM[F[+_, +_]](@unused self: Functor2[F])(implicit PrimitivesM: PrimitivesM2[F]): PrimitivesM.type = PrimitivesM - @inline implicit final def AttachFork3[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Fork: Fork3[FR]): Fork.type = Fork - @inline implicit final def AttachBlockingIO3[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit BlockingIO: BlockingIO3[FR]): BlockingIO.type = BlockingIO - - implicit final class KleisliSyntaxAttached[FR[-_, +_, +_]](private val FR0: Functor3[FR]) extends AnyVal { - @inline def fromKleisli[R, E, A](k: Kleisli[FR[Any, E, _], R, A])(implicit FR: MonadAsk3[FR]): FR[R, E, A] = FR.fromKleisli(k) - @inline def toKleisli[R, E, A](fr: FR[R, E, A])(implicit FR: Local3[FR]): Kleisli[FR[Any, E, _], R, A] = { - val _ = FR0 - FR.toKleisli(fr) - } - } + @inline implicit final def AttachFork[F[+_, +_]](@unused self: Functor2[F])(implicit Fork: Fork2[F]): Fork.type = Fork + @inline implicit final def AttachBlockingIO[F[+_, +_]](@unused self: Functor2[F])(implicit BlockingIO: BlockingIO2[F]): BlockingIO.type = BlockingIO } sealed trait RootInstancesLowPriority1 extends RootInstancesLowPriority2 { - @inline implicit final def ConvertFromTemporal[FR[-_, +_, +_]](implicit Temporal: NotPredefined.Of[Temporal3[FR]]): Predefined.Of[Error3[FR] & S2] = + @inline implicit final def ConvertFromTemporal[F[+_, +_]](implicit Temporal: NotPredefined.Of[Temporal2[F]]): Predefined.Of[Error2[F] & S2] = Predefined(S2(Temporal.InnerF)) - @inline implicit final def AttachArrowChoice[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit ArrowChoice: ArrowChoice3[FR]): ArrowChoice.type = - ArrowChoice - @inline implicit final def AttachMonadAsk[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit MonadAsk: MonadAsk3[FR]): MonadAsk.type = MonadAsk - @inline implicit final def AttachConcurrent[FR[-_, +_, +_]](@unused self: Concurrent3[FR])(implicit Concurrent: Concurrent3[FR]): Concurrent.type = + @inline implicit final def AttachConcurrent[F[+_, +_]](@unused self: Concurrent2[F])(implicit Concurrent: Concurrent2[F]): Concurrent.type = Concurrent } sealed trait RootInstancesLowPriority2 extends RootInstancesLowPriority3 { - @inline implicit final def ConvertFromParallel[FR[-_, +_, +_]](implicit Parallel: NotPredefined.Of[Parallel3[FR]]): Predefined.Of[Monad3[FR] & S3] = + @inline implicit final def ConvertFromParallel[F[+_, +_]](implicit Parallel: NotPredefined.Of[Parallel2[F]]): Predefined.Of[Monad2[F] & S3] = Predefined(S3(Parallel.InnerF)) - @inline implicit final def AttachArrow[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Arrow: Arrow3[FR]): Arrow.type = Arrow - @inline implicit final def AttachBifunctor[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Bifunctor: Bifunctor3[FR]): Bifunctor.type = + @inline implicit final def AttachBifunctor[F[+_, +_]](@unused self: Functor2[F])(implicit Bifunctor: Bifunctor2[F]): Bifunctor.type = Bifunctor - @inline implicit final def AttachConcurrent[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Concurrent: Concurrent3[FR]): Concurrent.type = + @inline implicit final def AttachConcurrent[F[+_, +_]](@unused self: Functor2[F])(implicit Concurrent: Concurrent2[F]): Concurrent.type = Concurrent } sealed trait RootInstancesLowPriority3 extends RootInstancesLowPriority4 { - @inline implicit final def ConvertFromMonadAsk[FR[-_, +_, +_]](implicit MonadAsk: NotPredefined.Of[MonadAsk3[FR]]): Predefined.Of[Monad3[FR] & S4] = - Predefined(S4(MonadAsk.InnerF)) - - @inline implicit final def AttachAsk[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Ask: Ask3[FR]): Ask.type = Ask - @inline implicit final def AttachProfunctor[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Profunctor: Profunctor3[FR]): Profunctor.type = - Profunctor - @inline implicit final def AttachParallel[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Parallel: Parallel3[FR]): Parallel.type = Parallel + @inline implicit final def AttachParallel[F[+_, +_]](@unused self: Functor2[F])(implicit Parallel: Parallel2[F]): Parallel.type = Parallel } sealed trait RootInstancesLowPriority4 extends RootInstancesLowPriority5 { - @inline implicit final def ConvertFromAsk[FR[-_, +_, +_]](implicit Ask: NotPredefined.Of[Ask3[FR]]): Predefined.Of[Applicative3[FR] & S5] = Predefined(S5(Ask.InnerF)) - - @inline implicit final def AttachTemporal[FR[-_, +_, +_]](@unused self: Functor3[FR])(implicit Temporal: Temporal3[FR]): Temporal3[FR] = Temporal + @inline implicit final def AttachTemporal[F[+_, +_]](@unused self: Functor2[F])(implicit Temporal: Temporal2[F]): Temporal2[F] = Temporal } -sealed trait RootInstancesLowPriority5 extends RootInstancesLowPriority6 { - @inline implicit final def ConvertFromProfunctor[FR[-_, +_, +_]](implicit Profunctor: NotPredefined.Of[Profunctor3[FR]]): Predefined.Of[Functor3[FR] & S6] = - Predefined(S6(Profunctor.InnerF)) -} +sealed trait RootInstancesLowPriority5 extends RootInstancesLowPriority6 {} sealed trait RootInstancesLowPriority6 extends RootInstancesLowPriority7 { - @inline implicit final def ConvertFromBifunctor[FR[-_, +_, +_]](implicit Bifunctor: NotPredefined.Of[Bifunctor3[FR]]): Predefined.Of[Functor3[FR] & S7] = + @inline implicit final def ConvertFromBifunctor[F[+_, +_]](implicit Bifunctor: NotPredefined.Of[Bifunctor2[F]]): Predefined.Of[Functor2[F] & S7] = Predefined(S7(Bifunctor.InnerF)) } sealed trait RootInstancesLowPriority7 extends RootInstancesLowPriority8 { - @inline implicit final def AttachClockAccessor[FR[-_, +_, +_]](@unused self: Functor3[FR]): Syntax3.ClockAccessor[FR] = new Syntax3.ClockAccessor[FR](false) - @inline implicit final def AttachEntropyAccessor[FR[-_, +_, +_]](@unused self: Functor3[FR]): Syntax3.EntropyAccessor[FR] = new Syntax3.EntropyAccessor[FR](false) + @inline implicit final def AttachClockAccessor[F[+_, +_]](@unused self: Functor2[F]): Syntax2.ClockAccessor[F] = new Syntax2.ClockAccessor[F](false) + @inline implicit final def AttachEntropyAccessor[F[+_, +_]](@unused self: Functor2[F]): Syntax2.EntropyAccessor[F] = new Syntax2.EntropyAccessor[F](false) } sealed trait RootInstancesLowPriority8 extends RootInstancesLowPriority9 { -// @inline implicit final def Local3ZIO: Predefined.Of[Local3[ZIO]] = Predefined(AsyncZio) - @inline implicit final def BIOZIO: Predefined.Of[Async3[ZIO]] = Predefined(AsyncZio) + /** + * This instance uses 'no more orphans' trick to provide an Optional instance + * only IFF you have zio-core as a dependency without REQUIRING a zio-core dependency. + * + * Optional instance via https://blog.7mind.io/no-more-orphans.html + */ + // since removing trifunctor, ZIO instances now also require no-more-orphans machinery + @inline implicit final def BIOZIO[ZIO[-_, +_, +_]: `zio.ZIO`]: Predefined.Of[Async2[ZIO[Any, +_, +_]]] = Predefined(AsyncZio.asInstanceOf[Async2[ZIO[Any, +_, +_]]]) } sealed trait RootInstancesLowPriority9 extends RootInstancesLowPriority10 { + @inline implicit final def BIOZIOR[ZIO[-_, +_, +_]: `zio.ZIO`, R]: Predefined.Of[Async2[ZIO[R, +_, +_]]] = Predefined(AsyncZio.asInstanceOf[Async2[ZIO[R, +_, +_]]]) +} + +sealed trait RootInstancesLowPriority10 extends RootInstancesLowPriority11 { /** * This instance uses 'no more orphans' trick to provide an Optional instance * only IFF you have monix-bio as a dependency without REQUIRING a monix-bio dependency. @@ -112,14 +94,10 @@ sealed trait RootInstancesLowPriority9 extends RootInstancesLowPriority10 { // AsyncMonix.asInstanceOf[Predefined.Of[Async2[MonixBIO]]] } -sealed trait RootInstancesLowPriority10 extends RootInstancesLowPriority11 { - @inline implicit final def BIOEither: Predefined.Of[Error2[Either]] = Predefined(BioEither) -} - sealed trait RootInstancesLowPriority11 extends RootInstancesLowPriority12 { - @inline implicit final def BIOIdentity2: Predefined.Of[Monad2[Identity2]] = BioIdentity3.asInstanceOf[Predefined.Of[Monad2[Identity2]]] + @inline implicit final def BIOEither: Predefined.Of[Error2[Either]] = Predefined(BioEither) } -sealed trait RootInstancesLowPriority12 extends RootInstancesLowPriorityVersionSpecific { - @inline implicit final def BIOIdentity3: Predefined.Of[Monad3[Identity3]] = Predefined(BioIdentity3) +sealed trait RootInstancesLowPriority12 { + @inline implicit final def BIOIdentity2: Predefined.Of[Monad2[Identity2]] = BioIdentity2.asInstanceOf[Predefined.Of[Monad2[Identity2]]] } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Semaphore1.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Semaphore1.scala index 5d83e9b3f6..424ca6a5a5 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Semaphore1.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Semaphore1.scala @@ -3,7 +3,7 @@ package izumi.functional.bio import cats.effect.std.Semaphore import izumi.functional.bio.data.~> import zio.ZIO -import zio.stm.{USTM, ZSTM} +import zio.stm.USTM trait Semaphore1[+F[_]] { def acquire: F[Unit] @@ -22,14 +22,14 @@ object Semaphore1 { override def releaseN(n: Long): F[Nothing, Unit] = semaphore.releaseN(n).orTerminate } - def fromZIO(tSemaphore: zio.stm.TSemaphore): Semaphore3[ZIO] = new Semaphore3[ZIO] { + def fromZIO(tSemaphore: zio.stm.TSemaphore): Semaphore2[zio.IO] = new Semaphore2[zio.IO] { override def acquire: ZIO[Any, Nothing, Unit] = tSemaphore.acquire.commit override def release: ZIO[Any, Nothing, Unit] = tSemaphore.release.commit override def acquireN(n: Long): ZIO[Any, Nothing, Unit] = tSemaphore.acquireN(n).commit override def releaseN(n: Long): ZIO[Any, Nothing, Unit] = tSemaphore.releaseN(n).commit } - def fromSTM(tSemaphore: zio.stm.TSemaphore): Semaphore3[ZSTM] = new Semaphore3[ZSTM] { + def fromSTM(tSemaphore: zio.stm.TSemaphore): Semaphore2[zio.stm.STM] = new Semaphore2[zio.stm.STM] { override def acquire: USTM[Unit] = tSemaphore.acquire override def release: USTM[Unit] = tSemaphore.release override def acquireN(n: Long): USTM[Unit] = tSemaphore.acquireN(n) diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/SyncSafe1.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/SyncSafe1.scala index 3e30884d43..cd2ccdf73c 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/SyncSafe1.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/SyncSafe1.scala @@ -32,20 +32,13 @@ object SyncSafe1 extends LowPrioritySyncSafeInstances0 { } trait LowPrioritySyncSafeInstances0 extends LowPrioritySyncSafeInstances1 { - implicit final def fromBIO3[F[-_, +_, +_]: IO3]: SyncSafe1[F[Any, Nothing, _]] = - new SyncSafe1[F[Any, Nothing, _]] { - override def syncSafe[A](f: => A): F[Any, Nothing, A] = F.sync(f) - } -} - -trait LowPrioritySyncSafeInstances1 extends LowPrioritySyncSafeInstances2 { implicit final def fromBIO[F[+_, +_]: IO2]: SyncSafe1[F[Nothing, _]] = new SyncSafe1[F[Nothing, _]] { override def syncSafe[A](f: => A): F[Nothing, A] = F.sync(f) } } -trait LowPrioritySyncSafeInstances2 { +trait LowPrioritySyncSafeInstances1 { /** * Emulate covariance. We're forced to employ these because * we can't make SyncSafe covariant, because covariant implicits @@ -55,10 +48,10 @@ trait LowPrioritySyncSafeInstances2 { * * @see https://github.com/scala/bug/issues/11427 */ - @inline implicit final def limitedCovariance2[C[f[_]] <: SyncSafe1[f], FR[_, _], R0]( + @inline implicit final def limitedCovariance2[C[f[_]] <: SyncSafe1[f], FR[_, _], E]( implicit F: C[FR[Nothing, _]] { type Divergence = Nondivergent } - ): Divergent.Of[C[FR[R0, _]]] = { - Divergent(F.asInstanceOf[C[FR[R0, _]]]) + ): Divergent.Of[C[FR[E, _]]] = { + Divergent(F.asInstanceOf[C[FR[E, _]]]) } @inline implicit final def limitedCovariance3[C[f[_]] <: SyncSafe1[f], FR[_, _, _], R0, E]( diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Temporal2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Temporal2.scala new file mode 100644 index 0000000000..cd739cf95b --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Temporal2.scala @@ -0,0 +1,57 @@ +package izumi.functional.bio + +import izumi.functional.bio.PredefinedHelper.Predefined +import izumi.functional.bio.impl.TemporalZio +import izumi.fundamentals.orphans.`zio.ZIO` + +import scala.concurrent.duration.{Duration, FiniteDuration} + +trait Temporal2[F[+_, +_]] extends RootBifunctor[F] with TemporalInstances { + def InnerF: Error2[F] + + def sleep(duration: Duration): F[Nothing, Unit] + + def timeout[E, A](duration: Duration)(r: F[E, A]): F[E, Option[A]] + + @inline final def timeoutFail[E, A](duration: Duration)(e: => E, r: F[E, A]): F[E, A] = { + InnerF.flatMap(timeout(duration)(r))(_.fold[F[E, A]](InnerF.fail(e))(InnerF.pure)) + } + + @inline final def repeatUntil[E, A](action: F[E, Option[A]])(tooManyAttemptsError: => E, sleep: FiniteDuration, maxAttempts: Int): F[E, A] = { + def go(n: Int): F[E, A] = { + InnerF.flatMap(action) { + case Some(value) => + InnerF.pure(value) + case None => + if (n <= maxAttempts) { + InnerF.*>(this.sleep(sleep), go(n + 1)) + } else { + InnerF.fail(tooManyAttemptsError) + } + } + } + + go(0) + } +} + +private[bio] sealed trait TemporalInstances +object TemporalInstances extends TemporalInstancesLowPriority1 { + /** + * This instance uses 'no more orphans' trick to provide an Optional instance + * only IFF you have zio-core as a dependency without REQUIRING a zio-core dependency. + * + * Optional instance via https://blog.7mind.io/no-more-orphans.html + */ + @inline implicit final def Temporal2Zio[ZIO[-_, +_, +_]: `zio.ZIO`]: Predefined.Of[Temporal2[ZIO[Any, +_, +_]]] = + Predefined(TemporalZio.asInstanceOf[Temporal2[ZIO[Any, +_, +_]]]) +} +sealed trait TemporalInstancesLowPriority1 { + @inline implicit final def Temporal2ZioR[ZIO[-_, +_, +_]: `zio.ZIO`, R]: Predefined.Of[Temporal2[ZIO[R, +_, +_]]] = + Predefined(TemporalZio.asInstanceOf[Temporal2[ZIO[R, +_, +_]]]) + +// @inline implicit final def TemporalMonix[MonixBIO[+_, +_]: `monix.bio.IO`, Timer[_[_]]: `cats.effect.kernel.Clock`]( +// implicit +// timer: Timer[MonixBIO[Nothing, _]] +// ): Predefined.Of[Temporal2[MonixBIO]] = new TemporalMonix(timer.asInstanceOf[cats.effect.kernel.Clock[monix.bio.UIO]]).asInstanceOf[Predefined.Of[Temporal2[MonixBIO]]] +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Temporal3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Temporal3.scala deleted file mode 100644 index b2779e3774..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/Temporal3.scala +++ /dev/null @@ -1,47 +0,0 @@ -package izumi.functional.bio - -import izumi.functional.bio.PredefinedHelper.Predefined -import izumi.functional.bio.impl.TemporalZio -import zio.ZIO - -import scala.concurrent.duration.{Duration, FiniteDuration} - -trait Temporal3[F[-_, +_, +_]] extends RootBifunctor[F] with TemporalInstances { - def InnerF: Error3[F] - - def sleep(duration: Duration): F[Any, Nothing, Unit] - - def timeout[R, E, A](duration: Duration)(r: F[R, E, A]): F[R, E, Option[A]] - - @inline final def timeoutFail[R, E, A](duration: Duration)(e: => E, r: F[R, E, A]): F[R, E, A] = { - InnerF.flatMap(timeout(duration)(r))(_.fold[F[R, E, A]](InnerF.fail(e))(InnerF.pure)) - } - - @inline final def repeatUntil[R, E, A](action: F[R, E, Option[A]])(tooManyAttemptsError: => E, sleep: FiniteDuration, maxAttempts: Int): F[R, E, A] = { - def go(n: Int): F[R, E, A] = { - InnerF.flatMap(action) { - case Some(value) => - InnerF.pure(value) - case None => - if (n <= maxAttempts) { - InnerF.*>(this.sleep(sleep), go(n + 1)) - } else { - InnerF.fail(tooManyAttemptsError) - } - } - } - - go(0) - } -} - -private[bio] sealed trait TemporalInstances -object TemporalInstances extends TemporalInstancesLowPriority1 { - @inline implicit final def Temporal3Zio: Predefined.Of[Temporal3[ZIO]] = Predefined(new TemporalZio) -} -sealed trait TemporalInstancesLowPriority1 { -// @inline implicit final def TemporalMonix[MonixBIO[+_, +_]: `monix.bio.IO`, Timer[_[_]]: `cats.effect.kernel.Clock`]( -// implicit -// timer: Timer[MonixBIO[Nothing, _]] -// ): Predefined.Of[Temporal2[MonixBIO]] = new TemporalMonix(timer.asInstanceOf[cats.effect.kernel.Clock[monix.bio.UIO]]).asInstanceOf[Predefined.Of[Temporal2[MonixBIO]]] -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/Free.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/Free.scala index d51b2528c3..bd08170bd9 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/Free.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/Free.scala @@ -50,11 +50,11 @@ object Free { object Monad2Instance extends Monad2Instance[Nothing] class Monad2Instance[S[_, _]] extends Monad2[Free[S, +_, +_]] { - @inline override def flatMap[R, E, A, B](r: Free[S, E, A])(f: A => Free[S, E, B]): Free[S, E, B] = r.flatMap(f) + @inline override def flatMap[E, A, B](r: Free[S, E, A])(f: A => Free[S, E, B]): Free[S, E, B] = r.flatMap(f) @inline override def pure[A](a: A): Free[S, Nothing, A] = Free.pure(a) - @inline override def *>[R, E, A, B](f: Free[S, E, A], next: => Free[S, E, B]): Free[S, E, B] = f *> next - @inline override def <*[R, E, A, B](f: Free[S, E, A], next: => Free[S, E, B]): Free[S, E, A] = f <* next - @inline override def as[R, E, A, B](r: Free[S, E, A])(v: => B): Free[S, E, B] = r.as(v) - @inline override def void[R, E, A](r: Free[S, E, A]): Free[S, E, Unit] = r.void + @inline override def *>[E, A, B](f: Free[S, E, A], next: => Free[S, E, B]): Free[S, E, B] = f *> next + @inline override def <*[E, A, B](f: Free[S, E, A], next: => Free[S, E, B]): Free[S, E, A] = f <* next + @inline override def as[E, A, B](r: Free[S, E, A])(v: => B): Free[S, E, B] = r.as(v) + @inline override def void[E, A](r: Free[S, E, A]): Free[S, E, Unit] = r.void } } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/FreeError.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/FreeError.scala index d4e13afba8..51fdd9c7ac 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/FreeError.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/FreeError.scala @@ -81,17 +81,17 @@ object FreeError { object Error2Instance extends Error2Instance[Nothing] class Error2Instance[S[_, _]] extends Error2[FreeError[S, +_, +_]] { - @inline override final def flatMap[R, E, A, B](r: FreeError[S, E, A])(f: A => FreeError[S, E, B]): FreeError[S, E, B] = r.flatMap(f) - @inline override final def *>[R, E, A, B](f: FreeError[S, E, A], next: => FreeError[S, E, B]): FreeError[S, E, B] = f *> next - @inline override final def <*[R, E, A, B](f: FreeError[S, E, A], next: => FreeError[S, E, B]): FreeError[S, E, A] = f <* next - @inline override final def as[R, E, A, B](r: FreeError[S, E, A])(v: => B): FreeError[S, E, B] = r.as(v) - @inline override final def void[R, E, A](r: FreeError[S, E, A]): FreeError[S, E, Unit] = r.void - @inline override final def catchAll[R, E, A, E2](r: FreeError[S, E, A])(f: E => FreeError[S, E2, A]): FreeError[S, E2, A] = r.catchAll(f) - @inline override final def catchSome[R, E, A, E1 >: E](r: FreeError[S, E, A])(f: PartialFunction[E, FreeError[S, E1, A]]): FreeError[S, E1, A] = r.catchSome(f) + @inline override final def flatMap[E, A, B](r: FreeError[S, E, A])(f: A => FreeError[S, E, B]): FreeError[S, E, B] = r.flatMap(f) + @inline override final def *>[E, A, B](f: FreeError[S, E, A], next: => FreeError[S, E, B]): FreeError[S, E, B] = f *> next + @inline override final def <*[E, A, B](f: FreeError[S, E, A], next: => FreeError[S, E, B]): FreeError[S, E, A] = f <* next + @inline override final def as[E, A, B](r: FreeError[S, E, A])(v: => B): FreeError[S, E, B] = r.as(v) + @inline override final def void[E, A](r: FreeError[S, E, A]): FreeError[S, E, Unit] = r.void + @inline override final def catchAll[E, A, E2](r: FreeError[S, E, A])(f: E => FreeError[S, E2, A]): FreeError[S, E2, A] = r.catchAll(f) + @inline override final def catchSome[E, A, E1 >: E](r: FreeError[S, E, A])(f: PartialFunction[E, FreeError[S, E1, A]]): FreeError[S, E1, A] = r.catchSome(f) @inline override final def pure[A](a: A): FreeError[S, Nothing, A] = FreeError.pure(a) @inline override final def fail[E](v: => E): FreeError[S, E, Nothing] = FreeError.fail(v) - @inline override final def guarantee[R, E, A](f: FreeError[S, E, A], cleanup: FreeError[S, Nothing, Unit]): FreeError[S, E, A] = { + @inline override final def guarantee[E, A](f: FreeError[S, E, A], cleanup: FreeError[S, Nothing, Unit]): FreeError[S, E, A] = { f.redeem(e => cleanup *> fail(e), cleanup.as(_)) } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/FreePanic.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/FreePanic.scala index fce8bb0410..b53b7bce9c 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/FreePanic.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/data/FreePanic.scala @@ -191,24 +191,24 @@ object FreePanic { object Panic2Instance extends Panic2Instance[Nothing] class Panic2Instance[S[_, _]] extends Panic2[FreePanic[S, +_, +_]] { - @inline override final def flatMap[R, E, A, B](r: FreePanic[S, E, A])(f: A => FreePanic[S, E, B]): FreePanic[S, E, B] = r.flatMap(f) - @inline override final def *>[R, E, A, B](f: FreePanic[S, E, A], next: => FreePanic[S, E, B]): FreePanic[S, E, B] = f *> next - @inline override final def <*[R, E, A, B](f: FreePanic[S, E, A], next: => FreePanic[S, E, B]): FreePanic[S, E, A] = f <* next - @inline override final def as[R, E, A, B](r: FreePanic[S, E, A])(v: => B): FreePanic[S, E, B] = r.as(v) - @inline override final def void[R, E, A](r: FreePanic[S, E, A]): FreePanic[S, E, Unit] = r.void - @inline override final def sandbox[R, E, A](r: FreePanic[S, E, A]): FreePanic[S, Exit.Failure[E], A] = r.sandbox - @inline override final def catchAll[R, E, A, E2](r: FreePanic[S, E, A])(f: E => FreePanic[S, E2, A]): FreePanic[S, E2, A] = r.catchAll(f) - @inline override final def catchSome[R, E, A, E1 >: E](r: FreePanic[S, E, A])(f: PartialFunction[E, FreePanic[S, E1, A]]): FreePanic[S, E1, A] = r.catchSome(f) - @inline override final def bracketCase[R, E, A, B]( + @inline override final def flatMap[E, A, B](r: FreePanic[S, E, A])(f: A => FreePanic[S, E, B]): FreePanic[S, E, B] = r.flatMap(f) + @inline override final def *>[E, A, B](f: FreePanic[S, E, A], next: => FreePanic[S, E, B]): FreePanic[S, E, B] = f *> next + @inline override final def <*[E, A, B](f: FreePanic[S, E, A], next: => FreePanic[S, E, B]): FreePanic[S, E, A] = f <* next + @inline override final def as[E, A, B](r: FreePanic[S, E, A])(v: => B): FreePanic[S, E, B] = r.as(v) + @inline override final def void[E, A](r: FreePanic[S, E, A]): FreePanic[S, E, Unit] = r.void + @inline override final def sandbox[E, A](r: FreePanic[S, E, A]): FreePanic[S, Exit.Failure[E], A] = r.sandbox + @inline override final def catchAll[E, A, E2](r: FreePanic[S, E, A])(f: E => FreePanic[S, E2, A]): FreePanic[S, E2, A] = r.catchAll(f) + @inline override final def catchSome[E, A, E1 >: E](r: FreePanic[S, E, A])(f: PartialFunction[E, FreePanic[S, E1, A]]): FreePanic[S, E1, A] = r.catchSome(f) + @inline override final def bracketCase[E, A, B]( acquire: FreePanic[S, E, A] )(release: (A, Exit[E, B]) => FreePanic[S, Nothing, Unit] )(use: A => FreePanic[S, E, B] ): FreePanic[S, E, B] = acquire.bracketCase(release)(use) - @inline override final def guarantee[R, E, A](f: FreePanic[S, E, A], cleanup: FreePanic[S, Nothing, Unit]): FreePanic[S, E, A] = f.guarantee(cleanup) - @inline override final def uninterruptible[R, E, A](r: FreePanic[S, E, A]): FreePanic[S, E, A] = FreePanic.Uninterruptible(r) - @inline override final def uninterruptibleExcept[R, E, A](r: RestoreInterruption2[FreePanic[S, +_, +_]] => FreePanic[S, E, A]): FreePanic[S, E, A] = + @inline override final def guarantee[E, A](f: FreePanic[S, E, A], cleanup: FreePanic[S, Nothing, Unit]): FreePanic[S, E, A] = f.guarantee(cleanup) + @inline override final def uninterruptible[E, A](r: FreePanic[S, E, A]): FreePanic[S, E, A] = FreePanic.Uninterruptible(r) + @inline override final def uninterruptibleExcept[E, A](r: RestoreInterruption2[FreePanic[S, +_, +_]] => FreePanic[S, E, A]): FreePanic[S, E, A] = FreePanic.UninterruptibleExcept(r) - @inline override final def bracketExcept[R, E, A, B]( + @inline override final def bracketExcept[E, A, B]( acquire: RestoreInterruption2[FreePanic[S, +_, +_]] => FreePanic[S, E, A] )(release: (A, Exit[E, B]) => FreePanic[S, Nothing, Unit] )(use: A => FreePanic[S, E, B] diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/AsyncZio.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/AsyncZio.scala index c040f0503b..8472052c72 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/AsyncZio.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/AsyncZio.scala @@ -1,22 +1,22 @@ package izumi.functional.bio.impl import izumi.functional.bio.Exit.ZIOExit -import izumi.functional.bio.data.{Morphism3, RestoreInterruption3} -import izumi.functional.bio.{Async3, Exit, Fiber2, Fiber3, __PlatformSpecific} +import izumi.functional.bio.data.{Morphism3, RestoreInterruption2} +import izumi.functional.bio.{Async2, Exit, Fiber2, __PlatformSpecific} import izumi.fundamentals.platform.language.Quirks.Discarder import zio._izumicompat_.{__ZIORaceCompat, __ZIOWithFiberRuntime} import zio.internal.stacktracer.{InteropTracer, Tracer} import zio.stacktracer.TracingImplicits.disableAutoTrace -import zio.{ZEnvironment, ZIO} +import zio.ZIO import java.util.concurrent.CompletionStage import java.util.concurrent.atomic.AtomicBoolean import scala.concurrent.{ExecutionContext, Future} import scala.util.Try -object AsyncZio extends AsyncZio +object AsyncZio extends AsyncZio[Any] -open class AsyncZio extends Async3[ZIO] /*with Local3[ZIO]*/ { +open class AsyncZio[R] extends Async2[ZIO[R, +_, +_]] { @inline override final def InnerF: this.type = this @inline override final def unit: ZIO[Any, Nothing, Unit] = ZIO.unit @@ -33,12 +33,18 @@ open class AsyncZio extends Async3[ZIO] /*with Local3[ZIO]*/ { ZIO.attempt(effect) } - @inline override final def suspend[R, A](effect: => ZIO[R, Throwable, A]): ZIO[R, Throwable, A] = { + @inline override final def suspend[A](effect: => ZIO[R, Throwable, A]): ZIO[R, Throwable, A] = { val byName: () => ZIO[R, Throwable, A] = () => effect implicit val trace: zio.Trace = InteropTracer.newTrace(byName) ZIO.suspend(effect) } + @inline override final def suspendSafe[E, A](effect: => ZIO[R, E, A]): ZIO[R, E, A] = { + val byName: () => ZIO[R, E, A] = () => effect + implicit val trace: zio.Trace = InteropTracer.newTrace(byName) + + ZIO.suspendSucceed(effect) + } @inline override final def fail[E](v: => E): ZIO[Any, E, Nothing] = { val byName: () => E = () => v @@ -72,99 +78,99 @@ open class AsyncZio extends Async3[ZIO] /*with Local3[ZIO]*/ { ZIO.fromTry(effect) } - @inline override final def void[R, E, A](r: ZIO[R, E, A]): ZIO[R, E, Unit] = r.unit(Tracer.instance.empty) - @inline override final def map[R, E, A, B](r: ZIO[R, E, A])(f: A => B): ZIO[R, E, B] = r.map(f)(InteropTracer.newTrace(f)) - @inline override final def as[R, E, A, B](r: ZIO[R, E, A])(v: => B): ZIO[R, E, B] = { + @inline override final def void[E, A](r: ZIO[R, E, A]): ZIO[R, E, Unit] = r.unit(Tracer.instance.empty) + @inline override final def map[E, A, B](r: ZIO[R, E, A])(f: A => B): ZIO[R, E, B] = r.map(f)(InteropTracer.newTrace(f)) + @inline override final def as[E, A, B](r: ZIO[R, E, A])(v: => B): ZIO[R, E, B] = { val byName: () => B = () => v implicit val trace: zio.Trace = InteropTracer.newTrace(byName) r.as(v) } - @inline override final def tapError[R, E, A, E1 >: E](r: ZIO[R, E, A])(f: E => ZIO[R, E1, Unit]): ZIO[R, E1, A] = r.tapError(f)(implicitly, InteropTracer.newTrace(f)) - @inline override final def leftMap[R, E, A, E2](r: ZIO[R, E, A])(f: E => E2): ZIO[R, E2, A] = r.mapError(f)(implicitly, InteropTracer.newTrace(f)) - @inline override final def leftFlatMap[R, E, A, E2](r: ZIO[R, E, A])(f: E => ZIO[R, Nothing, E2]): ZIO[R, E2, A] = + @inline override final def tapError[E, A, E1 >: E](r: ZIO[R, E, A])(f: E => ZIO[R, E1, Unit]): ZIO[R, E1, A] = r.tapError(f)(implicitly, InteropTracer.newTrace(f)) + @inline override final def leftMap[E, A, E2](r: ZIO[R, E, A])(f: E => E2): ZIO[R, E2, A] = r.mapError(f)(implicitly, InteropTracer.newTrace(f)) + @inline override final def leftFlatMap[E, A, E2](r: ZIO[R, E, A])(f: E => ZIO[R, Nothing, E2]): ZIO[R, E2, A] = r.flatMapError(f)(implicitly, InteropTracer.newTrace(f)) - @inline override final def flip[R, E, A](r: ZIO[R, E, A]): ZIO[R, A, E] = r.flip(Tracer.instance.empty) - @inline override final def bimap[R, E, A, E2, B](r: ZIO[R, E, A])(f: E => E2, g: A => B): ZIO[R, E2, B] = r.mapBoth(f, g)(implicitly, InteropTracer.newTrace(f)) + @inline override final def flip[E, A](r: ZIO[R, E, A]): ZIO[R, A, E] = r.flip(Tracer.instance.empty) + @inline override final def bimap[E, A, E2, B](r: ZIO[R, E, A])(f: E => E2, g: A => B): ZIO[R, E2, B] = r.mapBoth(f, g)(implicitly, InteropTracer.newTrace(f)) - @inline override final def flatMap[R, E, A, B](r: ZIO[R, E, A])(f0: A => ZIO[R, E, B]): ZIO[R, E, B] = r.flatMap(f0)(InteropTracer.newTrace(f0)) - @inline override final def tap[R, E, A](r: ZIO[R, E, A], f: A => ZIO[R, E, Unit]): ZIO[R, E, A] = r.tap(f)(InteropTracer.newTrace(f)) - @inline override final def tapBoth[R, E, A, E1 >: E](r: ZIO[R, E, A])(err: E => ZIO[R, E1, Unit], succ: A => ZIO[R, E1, Unit]): ZIO[R, E1, A] = + @inline override final def flatMap[E, A, B](r: ZIO[R, E, A])(f0: A => ZIO[R, E, B]): ZIO[R, E, B] = r.flatMap(f0)(InteropTracer.newTrace(f0)) + @inline override final def tap[E, A](r: ZIO[R, E, A], f: A => ZIO[R, E, Unit]): ZIO[R, E, A] = r.tap(f)(InteropTracer.newTrace(f)) + @inline override final def tapBoth[E, A, E1 >: E](r: ZIO[R, E, A])(err: E => ZIO[R, E1, Unit], succ: A => ZIO[R, E1, Unit]): ZIO[R, E1, A] = r.tapBoth(err, succ)(implicitly, InteropTracer.newTrace(err)) - @inline override final def flatten[R, E, A](r: ZIO[R, E, ZIO[R, E, A]]): ZIO[R, E, A] = ZIO.flatten(r)(Tracer.instance.empty) - @inline override final def *>[R, E, A, B](f: ZIO[R, E, A], next: => ZIO[R, E, B]): ZIO[R, E, B] = { + @inline override final def flatten[E, A](r: ZIO[R, E, ZIO[R, E, A]]): ZIO[R, E, A] = ZIO.flatten(r)(Tracer.instance.empty) + @inline override final def *>[E, A, B](f: ZIO[R, E, A], next: => ZIO[R, E, B]): ZIO[R, E, B] = { val byName: () => ZIO[R, E, B] = () => next implicit val trace: zio.Trace = InteropTracer.newTrace(byName) f *> next } - @inline override final def <*[R, E, A, B](f: ZIO[R, E, A], next: => ZIO[R, E, B]): ZIO[R, E, A] = { + @inline override final def <*[E, A, B](f: ZIO[R, E, A], next: => ZIO[R, E, B]): ZIO[R, E, A] = { val byName: () => ZIO[R, E, B] = () => next implicit val trace: zio.Trace = InteropTracer.newTrace(byName) f <* next } - @inline override final def map2[R, E, A, B, C](r1: ZIO[R, E, A], r2: => ZIO[R, E, B])(f: (A, B) => C): ZIO[R, E, C] = { + @inline override final def map2[E, A, B, C](r1: ZIO[R, E, A], r2: => ZIO[R, E, B])(f: (A, B) => C): ZIO[R, E, C] = { val byName: () => ZIO[R, E, B] = () => r2 implicit val trace: zio.Trace = InteropTracer.newTrace(byName) r1.zipWith(r2)(f) } - @inline override final def iterateWhile[R, E, A](r: ZIO[R, E, A])(p: A => Boolean): ZIO[R, E, A] = r.repeatWhile(p)(InteropTracer.newTrace(p)) - @inline override final def iterateUntil[R, E, A](r: ZIO[R, E, A])(p: A => Boolean): ZIO[R, E, A] = r.repeatUntil(p)(InteropTracer.newTrace(p)) + @inline override final def iterateWhile[E, A](r: ZIO[R, E, A])(p: A => Boolean): ZIO[R, E, A] = r.repeatWhile(p)(InteropTracer.newTrace(p)) + @inline override final def iterateUntil[E, A](r: ZIO[R, E, A])(p: A => Boolean): ZIO[R, E, A] = r.repeatUntil(p)(InteropTracer.newTrace(p)) - @inline override final def leftMap2[R, E, A, E2, E3](firstOp: ZIO[R, E, A], secondOp: => ZIO[R, E2, A])(f: (E, E2) => E3): ZIO[R, E3, A] = { + @inline override final def leftMap2[E, A, E2, E3](firstOp: ZIO[R, E, A], secondOp: => ZIO[R, E2, A])(f: (E, E2) => E3): ZIO[R, E3, A] = { val byName: () => ZIO[R, E2, A] = () => secondOp implicit val trace: zio.Trace = InteropTracer.newTrace(byName) firstOp.catchAll(e => secondOp.mapError(f(e, _))) } - @inline override final def orElse[R, E, A, E2](r: ZIO[R, E, A], f: => ZIO[R, E2, A]): ZIO[R, E2, A] = { + @inline override final def orElse[E, A, E2](r: ZIO[R, E, A], f: => ZIO[R, E2, A]): ZIO[R, E2, A] = { val byName: () => ZIO[R, E2, A] = () => f implicit val trace: zio.Trace = InteropTracer.newTrace(byName) r.orElse(f) } - @inline override final def redeem[R, E, A, E2, B](r: ZIO[R, E, A])(err: E => ZIO[R, E2, B], succ: A => ZIO[R, E2, B]): ZIO[R, E2, B] = + @inline override final def redeem[E, A, E2, B](r: ZIO[R, E, A])(err: E => ZIO[R, E2, B], succ: A => ZIO[R, E2, B]): ZIO[R, E2, B] = r.foldZIO(err, succ)(implicitly, InteropTracer.newTrace(err)) - @inline override final def catchAll[R, E, A, E2](r: ZIO[R, E, A])(f: E => ZIO[R, E2, A]): ZIO[R, E2, A] = r.catchAll(f)(implicitly, InteropTracer.newTrace(f)) - @inline override final def catchSome[R, E, A, E1 >: E](r: ZIO[R, E, A])(f: PartialFunction[E, ZIO[R, E1, A]]): ZIO[R, E1, A] = + @inline override final def catchAll[E, A, E2](r: ZIO[R, E, A])(f: E => ZIO[R, E2, A]): ZIO[R, E2, A] = r.catchAll(f)(implicitly, InteropTracer.newTrace(f)) + @inline override final def catchSome[E, A, E1 >: E](r: ZIO[R, E, A])(f: PartialFunction[E, ZIO[R, E1, A]]): ZIO[R, E1, A] = r.catchSome(f)(implicitly, InteropTracer.newTrace(f)) - @inline override final def guarantee[R, E, A](f: ZIO[R, E, A], cleanup: ZIO[R, Nothing, Unit]): ZIO[R, E, A] = f.ensuring(cleanup)(Tracer.instance.empty) - @inline override final def attempt[R, E, A](r: ZIO[R, E, A]): ZIO[R, Nothing, Either[E, A]] = r.either(implicitly, Tracer.instance.empty) - @inline override final def redeemPure[R, E, A, B](r: ZIO[R, E, A])(err: E => B, succ: A => B): ZIO[R, Nothing, B] = + @inline override final def guarantee[E, A](f: ZIO[R, E, A], cleanup: ZIO[R, Nothing, Unit]): ZIO[R, E, A] = f.ensuring(cleanup)(Tracer.instance.empty) + @inline override final def attempt[E, A](r: ZIO[R, E, A]): ZIO[R, Nothing, Either[E, A]] = r.either(implicitly, Tracer.instance.empty) + @inline override final def redeemPure[E, A, B](r: ZIO[R, E, A])(err: E => B, succ: A => B): ZIO[R, Nothing, B] = r.fold(err, succ)(implicitly, InteropTracer.newTrace(err)) - @inline override final def retryWhile[R, E, A](r: ZIO[R, E, A])(f: E => Boolean): ZIO[R, E, A] = r.retryWhile(f)(implicitly, InteropTracer.newTrace(f)) - @inline override final def retryWhileF[R, R1 <: R, E, A](r: ZIO[R, E, A])(f: E => ZIO[R1, Nothing, Boolean]): ZIO[R1, E, A] = + @inline override final def retryWhile[E, A](r: ZIO[R, E, A])(f: E => Boolean): ZIO[R, E, A] = r.retryWhile(f)(implicitly, InteropTracer.newTrace(f)) + @inline override final def retryWhileF[E, A](r: ZIO[R, E, A])(f: E => ZIO[R, Nothing, Boolean]): ZIO[R, E, A] = r.retryWhileZIO(f)(implicitly, InteropTracer.newTrace(f)) - @inline override final def retryUntil[R, E, A](r: ZIO[R, E, A])(f: E => Boolean): ZIO[R, E, A] = r.retryUntil(f)(implicitly, InteropTracer.newTrace(f)) - @inline override final def retryUntilF[R, R1 <: R, E, A](r: ZIO[R, E, A])(f: E => ZIO[R1, Nothing, Boolean]): ZIO[R1, E, A] = + @inline override final def retryUntil[E, A](r: ZIO[R, E, A])(f: E => Boolean): ZIO[R, E, A] = r.retryUntil(f)(implicitly, InteropTracer.newTrace(f)) + @inline override final def retryUntilF[E, A](r: ZIO[R, E, A])(f: E => ZIO[R, Nothing, Boolean]): ZIO[R, E, A] = r.retryUntilZIO(f)(implicitly, InteropTracer.newTrace(f)) - @inline override final def fromOptionOr[R, E, A](valueOnNone: => A, r: ZIO[R, E, Option[A]]): ZIO[R, E, A] = { + @inline override final def fromOptionOr[E, A](valueOnNone: => A, r: ZIO[R, E, Option[A]]): ZIO[R, E, A] = { val byName: () => A = () => valueOnNone implicit val trace: zio.Trace = InteropTracer.newTrace(byName) r.someOrElse(valueOnNone) } - @inline override final def fromOptionF[R, E, A](fallbackOnNone: => ZIO[R, E, A], r: ZIO[R, E, Option[A]]): ZIO[R, E, A] = { + @inline override final def fromOptionF[E, A](fallbackOnNone: => ZIO[R, E, A], r: ZIO[R, E, Option[A]]): ZIO[R, E, A] = { val byName: () => ZIO[R, E, A] = () => fallbackOnNone implicit val trace: zio.Trace = InteropTracer.newTrace(byName) r.someOrElseZIO(fallbackOnNone) } - @inline override final def bracket[R, E, A, B](acquire: ZIO[R, E, A])(release: A => ZIO[R, Nothing, Unit])(use: A => ZIO[R, E, B]): ZIO[R, E, B] = { + @inline override final def bracket[E, A, B](acquire: ZIO[R, E, A])(release: A => ZIO[R, Nothing, Unit])(use: A => ZIO[R, E, B]): ZIO[R, E, B] = { ZIO.acquireReleaseWith(acquire)(release)(use)(InteropTracer.newTrace(release)) } - @inline override final def bracketCase[R, E, A, B]( + @inline override final def bracketCase[E, A, B]( acquire: ZIO[R, E, A] )(release: (A, Exit[E, B]) => ZIO[R, Nothing, Unit] )(use: A => ZIO[R, E, B] @@ -173,19 +179,34 @@ open class AsyncZio extends Async3[ZIO] /*with Local3[ZIO]*/ { ZIO.acquireReleaseExitWith[R, E, A](acquire)((a, exit: zio.Exit[E, B]) => ZIOExit.withIsInterruptedF(i => release(a, ZIOExit.toExit(exit)(i))))(use) } - @inline override final def guaranteeCase[R, E, A](f: ZIO[R, E, A], cleanup: Exit[E, A] => ZIO[R, Nothing, Unit]): ZIO[R, E, A] = { + @inline override final def guaranteeCase[E, A](f: ZIO[R, E, A], cleanup: Exit[E, A] => ZIO[R, Nothing, Unit]): ZIO[R, E, A] = { implicit val trace: zio.Trace = InteropTracer.newTrace(cleanup) f.onExit(exit => ZIOExit.withIsInterruptedF(cleanup apply ZIOExit.toExit(exit)(_))) } - @inline override final def traverse[R, E, A, B](l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, List[B]] = + @inline override final def traverse[E, A, B](l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, List[B]] = ZIO.foreach(l.toList)(f)(implicitly, InteropTracer.newTrace(f)) - @inline override final def sequence[R, E, A](l: Iterable[ZIO[R, E, A]]): ZIO[R, E, List[A]] = ZIO.collectAll(l.toList)(implicitly, Tracer.instance.empty) - @inline override final def traverse_[R, E, A](l: Iterable[A])(f: A => ZIO[R, E, Unit]): ZIO[R, E, Unit] = ZIO.foreachDiscard(l)(f)(InteropTracer.newTrace(f)) - @inline override final def sequence_[R, E](l: Iterable[ZIO[R, E, Unit]]): ZIO[R, E, Unit] = ZIO.foreachDiscard(l)(identity)(Tracer.instance.empty) + @inline override final def sequence[E, A](l: Iterable[ZIO[R, E, A]]): ZIO[R, E, List[A]] = ZIO.collectAll(l.toList)(implicitly, Tracer.instance.empty) + @inline override final def traverse_[E, A](l: Iterable[A])(f: A => ZIO[R, E, Unit]): ZIO[R, E, Unit] = ZIO.foreachDiscard(l)(f)(InteropTracer.newTrace(f)) + @inline override final def sequence_[E](l: Iterable[ZIO[R, E, Unit]]): ZIO[R, E, Unit] = ZIO.foreachDiscard(l)(identity)(Tracer.instance.empty) + @inline override final def filter[E, A](l: Iterable[A])(f: A => ZIO[R, E, Boolean]): ZIO[R, E, List[A]] = ZIO.filter(l.toList)(f)(implicitly, InteropTracer.newTrace(f)) + + @inline override final def foldLeft[E, A, AC](l: Iterable[A])(z: AC)(f: (AC, A) => ZIO[R, E, AC]): ZIO[R, E, AC] = { + ZIO.foldLeft(l)(z)(f)(InteropTracer.newTrace(f)) + } + + @inline override final def find[E, A](l: Iterable[A])(f: A => ZIO[R, E, Boolean]): ZIO[R, E, Option[A]] = { + val trace = InteropTracer.newTrace(f) + + ZIO.collectFirst(l)(a => f(a).map(if (_) Some(a) else None)(trace))(trace) + } + + @inline override final def collectFirst[E, A, B](l: Iterable[A])(f: A => ZIO[R, E, Option[B]]): ZIO[R, E, Option[B]] = { + ZIO.collectFirst(l)(f)(InteropTracer.newTrace(f)) + } - @inline override final def sandbox[R, E, A](r: ZIO[R, E, A]): ZIO[R, Exit.Failure[E], A] = { + @inline override final def sandbox[E, A](r: ZIO[R, E, A]): ZIO[R, Exit.Failure[E], A] = { implicit val trace: zio.Trace = Tracer.instance.empty r.sandbox.flatMapError(ZIOExit withIsInterrupted ZIOExit.toExit(_)) @@ -199,15 +220,15 @@ open class AsyncZio extends Async3[ZIO] /*with Local3[ZIO]*/ { ZIO.async(cb => register(cb apply _.fold(ZIO.fail(_), ZIO.succeed(_)))) } - @inline override final def asyncF[R, E, A](register: (Either[E, A] => Unit) => ZIO[R, E, Unit]): ZIO[R, E, A] = { + @inline override final def asyncF[E, A](register: (Either[E, A] => Unit) => ZIO[R, E, Unit]): ZIO[R, E, A] = { implicit val trace: zio.Trace = InteropTracer.newTrace(register) ZIO.asyncZIO(cb => register(cb apply _.fold(ZIO.fail(_), ZIO.succeed(_)))) } - @inline override final def asyncCancelable[E, A](register: (Either[E, A] => Unit) => Canceler): ZIO[Any, E, A] = { + @inline override final def asyncCancelable[E, A](register: (Either[E, A] => Unit) => Canceler): ZIO[R, E, A] = { implicit val trace: zio.Trace = InteropTracer.newTrace(register) - ZIO.asyncInterrupt[Any, E, A] { + ZIO.asyncInterrupt[R, E, A] { cb => val canceler = register(cb apply _.fold(ZIO.fail(_), ZIO.succeed(_))) Left(canceler) @@ -221,18 +242,18 @@ open class AsyncZio extends Async3[ZIO] /*with Local3[ZIO]*/ { __PlatformSpecific.fromFutureJava(javaFuture) } - @inline override final def uninterruptible[R, E, A](r: ZIO[R, E, A]): ZIO[R, E, A] = r.uninterruptible(Tracer.instance.empty) + @inline override final def uninterruptible[E, A](r: ZIO[R, E, A]): ZIO[R, E, A] = r.uninterruptible(Tracer.instance.empty) - @inline override final def race[R, E, A](r1: ZIO[R, E, A], r2: ZIO[R, E, A]): ZIO[R, E, A] = { + @inline override final def race[E, A](r1: ZIO[R, E, A], r2: ZIO[R, E, A]): ZIO[R, E, A] = { implicit val trace: zio.Trace = Tracer.instance.empty __ZIORaceCompat.raceFirst(r1.interruptible, r2.interruptible) } - @inline override final def racePairUnsafe[R, E, A, B]( + @inline override final def racePairUnsafe[E, A, B]( r1: ZIO[R, E, A], r2: ZIO[R, E, B], - ): ZIO[R, E, Either[(Exit[E, A], Fiber3[ZIO, E, B]), (Fiber3[ZIO, E, A], Exit[E, B])]] = { + ): ZIO[R, E, Either[(Exit[E, A], Fiber2[ZIO[R, +_, +_], E, B]), (Fiber2[ZIO[R, +_, +_], E, A], Exit[E, B])]] = { implicit val trace: zio.Trace = Tracer.instance.empty val interrupted1 = new AtomicBoolean(true) @@ -246,87 +267,52 @@ open class AsyncZio extends Async3[ZIO] /*with Local3[ZIO]*/ { ) } - @inline override final def parTraverseN[R, E, A, B](maxConcurrent: Int)(l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, List[B]] = { + @inline override final def parTraverseN[E, A, B](maxConcurrent: Int)(l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, List[B]] = { implicit val trace: zio.Trace = InteropTracer.newTrace(f) ZIO .foreachPar(l.toList)(f(_).interruptible) .withParallelism(maxConcurrent) } - @inline override final def parTraverseN_[R, E, A, B](maxConcurrent: Int)(l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, Unit] = { + @inline override final def parTraverseN_[E, A, B](maxConcurrent: Int)(l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, Unit] = { implicit val trace: zio.Trace = InteropTracer.newTrace(f) ZIO .foreachParDiscard(l)(f(_).interruptible) .withParallelism(maxConcurrent) } - @inline override final def parTraverseNCore[R, E, A, B](l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, List[B]] = { + @inline override final def parTraverseNCore[E, A, B](l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, List[B]] = { ZIO.suspendSucceed(parTraverseN(java.lang.Runtime.getRuntime.availableProcessors() max 2)(l)(f))(InteropTracer.newTrace(f)) } - @inline override final def parTraverseNCore_[R, E, A, B](l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, Unit] = { + @inline override final def parTraverseNCore_[E, A, B](l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, Unit] = { ZIO.suspendSucceed(parTraverseN_(java.lang.Runtime.getRuntime.availableProcessors() max 2)(l)(f))(InteropTracer.newTrace(f)) } - @inline override final def parTraverse[R, E, A, B](l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, List[B]] = { + @inline override final def parTraverse[E, A, B](l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, List[B]] = { implicit val trace: zio.Trace = InteropTracer.newTrace(f) // do not force unlimited parallelism here, obey 'regional parallelism' (unlimited by default) ZIO.foreachPar(l.toList)(f(_).interruptible) } - @inline override final def parTraverse_[R, E, A, B](l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, Unit] = { + @inline override final def parTraverse_[E, A, B](l: Iterable[A])(f: A => ZIO[R, E, B]): ZIO[R, E, Unit] = { implicit val trace: zio.Trace = InteropTracer.newTrace(f) // do not force unlimited parallelism here, obey 'regional parallelism' (unlimited by default) ZIO.foreachParDiscard(l)(f(_).interruptible) } - @inline override final def zipWithPar[R, E, A, B, C](fa: ZIO[R, E, A], fb: ZIO[R, E, B])(f: (A, B) => C): ZIO[R, E, C] = { + @inline override final def zipWithPar[E, A, B, C](fa: ZIO[R, E, A], fb: ZIO[R, E, B])(f: (A, B) => C): ZIO[R, E, C] = { fa.zipWithPar(fb)(f)(InteropTracer.newTrace(f)) } - @inline override final def zipPar[R, E, A, B](fa: ZIO[R, E, A], fb: ZIO[R, E, B]): ZIO[R, E, (A, B)] = { + @inline override final def zipPar[E, A, B](fa: ZIO[R, E, A], fb: ZIO[R, E, B]): ZIO[R, E, (A, B)] = { fa.<&>(fb)(zio.Zippable.Zippable2[A, B], Tracer.instance.empty) } - @inline override final def zipParLeft[R, E, A, B](fa: ZIO[R, E, A], fb: ZIO[R, E, B]): ZIO[R, E, A] = { + @inline override final def zipParLeft[E, A, B](fa: ZIO[R, E, A], fb: ZIO[R, E, B]): ZIO[R, E, A] = { fa.<&(fb)(Tracer.instance.empty) } - @inline override final def zipParRight[R, E, A, B](fa: ZIO[R, E, A], fb: ZIO[R, E, B]): ZIO[R, E, B] = { + @inline override final def zipParRight[E, A, B](fa: ZIO[R, E, A], fb: ZIO[R, E, B]): ZIO[R, E, B] = { fa.&>(fb)(Tracer.instance.empty) } - // Trifunctor implementations with Tag evidence - // Probably unworkable at all: we end up floating a combinatoric explosion of evidences for every type we encounter - def andThen[R, R1: zio.Tag, E, A](f: ZIO[R, E, R1], g: ZIO[R1, E, A]): ZIO[R, E, A] = { - implicit val trace: zio.Trace = Tracer.instance.empty - - f.flatMap(r1 => g.provideEnvironment(ZEnvironment(r1))) - } - def choice[RL: zio.Tag, RR: zio.Tag, E, A](f: ZIO[RL, E, A], g: ZIO[RR, E, A])(implicit t: zio.Tag[Either[RL, RR]]): ZIO[Either[RL, RR], E, A] = { - implicit val trace: zio.Trace = Tracer.instance.empty - - ZIO.serviceWithZIO[Either[RL, RR]] { - case Left(l) => f.provideEnvironment(ZEnvironment(l)) - case Right(r) => g.provideEnvironment(ZEnvironment(r)) - } - } - // - - // Reader methods are impossible to implement as-is, only with a newtype (essentially a transformer, because a boundary will be required[?]) -// @inline override final def ask[R]: ZIO[R, Nothing, R] = ZIO.environment -// @inline override final def askWith[R, A](f: R => A): ZIO[R, Nothing, A] = ZIO.environmentWith(f) -// -// @inline override final def provide[R, E, A](fr: ZIO[R, E, A])(r: => R): ZIO[Any, E, A] = fr.provideEnvironment(ZEnvironment(r)) -// @inline override final def contramap[R, E, A, R0](fr: ZIO[R, E, A])(f: R0 => R): ZIO[R0, E, A] = fr.provideSome(f) -// -// @inline override final def access[R, E, A](f: R => ZIO[R, E, A]): ZIO[R, E, A] = ZIO.environmentWithZIO(f) -// -// @inline override final def dimap[R1, E, A1, R2, A2](fab: ZIO[R1, E, A1])(f: R2 => R1)(g: A1 => A2): ZIO[R2, E, A2] = fab.provideSome(f).map(g) -// -// @inline override final def andThen[R, R1, E, A](f: ZIO[R, E, R1], g: ZIO[R1, E, A]): ZIO[R, E, A] = f >>> g -// -// @inline override final def asking[R, E, A](f: ZIO[R, E, A]): ZIO[R, E, (A, R)] = f.onFirst -// -// @inline override final def choice[RL, RR, E, A](f: ZIO[RL, E, A], g: ZIO[RR, E, A]): ZIO[Either[RL, RR], E, A] = (f +++ g).map(_.merge) -// @inline override final def choose[RL, RR, E, AL, AR](f: ZIO[RL, E, AL], g: ZIO[RR, E, AR]): ZIO[Either[RL, RR], E, Either[AL, AR]] = f +++ g - @inline override final def sendInterruptToSelf: ZIO[Any, Nothing, Unit] = { implicit val trace: zio.Trace = Tracer.instance.empty @@ -340,9 +326,9 @@ open class AsyncZio extends Async3[ZIO] /*with Local3[ZIO]*/ { } @inline override final def currentEC: ZIO[Any, Nothing, ExecutionContext] = ZIO.executor(Tracer.instance.empty).map(_.asExecutionContext)(Tracer.instance.empty) - @inline override final def onEC[R, E, A](ec: ExecutionContext)(f: ZIO[R, E, A]): ZIO[R, E, A] = f.onExecutionContext(ec)(Tracer.instance.empty) + @inline override final def onEC[E, A](ec: ExecutionContext)(f: ZIO[R, E, A]): ZIO[R, E, A] = f.onExecutionContext(ec)(Tracer.instance.empty) - @inline override final def uninterruptibleExcept[R, E, A](r: Morphism3[ZIO, ZIO] => ZIO[R, E, A]): ZIO[R, E, A] = { + @inline override final def uninterruptibleExcept[E, A](r: RestoreInterruption2[ZIO[R, +_, +_]] => ZIO[R, E, A]): ZIO[R, E, A] = { implicit val trace: zio.Trace = InteropTracer.newTrace(r) ZIO.uninterruptibleMask { @@ -352,8 +338,8 @@ open class AsyncZio extends Async3[ZIO] /*with Local3[ZIO]*/ { } } - @inline override final def bracketExcept[R, E, A, B]( - acquire: RestoreInterruption3[ZIO] => ZIO[R, E, A] + @inline override final def bracketExcept[E, A, B]( + acquire: RestoreInterruption2[ZIO[R, +_, +_]] => ZIO[R, E, A] )(release: (A, Exit[E, B]) => ZIO[R, Nothing, Unit] )(use: A => ZIO[R, E, B] ): ZIO[R, E, B] = { diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BioEither.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BioEither.scala index 93c4758e38..8ea43de1c5 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BioEither.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BioEither.scala @@ -2,30 +2,137 @@ package izumi.functional.bio.impl import izumi.functional.bio.Error2 +import scala.collection.compat.* import scala.util.Try object BioEither extends BioEither open class BioEither extends Error2[Either] { - @inline override def pure[A](a: A): Either[Nothing, A] = Right(a) - @inline override def map[R, E, A, B](r: Either[E, A])(f: A => B): Either[E, B] = r.map(f) + @inline override final def pure[A](a: A): Either[Nothing, A] = Right(a) + @inline override final def map[E, A, B](r: Either[E, A])(f: A => B): Either[E, B] = r.map(f) /** execute two operations in order, map their results */ - @inline override def map2[R, E, A, B, C](firstOp: Either[E, A], secondOp: => Either[E, B])(f: (A, B) => C): Either[E, C] = { + @inline override final def map2[E, A, B, C](firstOp: Either[E, A], secondOp: => Either[E, B])(f: (A, B) => C): Either[E, C] = { firstOp.flatMap(a => secondOp.map(b => f(a, b))) } - @inline override def flatMap[R, E, A, B](r: Either[E, A])(f: A => Either[E, B]): Either[E, B] = r.flatMap(f) + @inline override final def flatMap[E, A, B](r: Either[E, A])(f: A => Either[E, B]): Either[E, B] = r.flatMap(f) - @inline override def catchAll[R, E, A, E2](r: Either[E, A])(f: E => Either[E2, A]): Either[E2, A] = r.left.flatMap(f) - @inline override def fail[E](v: => E): Either[E, Nothing] = Left(v) + @inline override final def catchAll[E, A, E2](r: Either[E, A])(f: E => Either[E2, A]): Either[E2, A] = r.left.flatMap(f) + @inline override final def fail[E](v: => E): Either[E, Nothing] = Left(v) - @inline override def fromEither[E, V](effect: => Either[E, V]): Either[E, V] = effect - @inline override def fromOption[E, A](errorOnNone: => E)(effect: => Option[A]): Either[E, A] = effect match { + @inline override final def fromEither[E, V](effect: => Either[E, V]): Either[E, V] = effect + @inline override final def fromOption[E, A](errorOnNone: => E)(effect: => Option[A]): Either[E, A] = effect match { case Some(value) => Right(value) case None => Left(errorOnNone) } - @inline override def fromTry[A](effect: => Try[A]): Either[Throwable, A] = effect.toEither + @inline override final def fromTry[A](effect: => Try[A]): Either[Throwable, A] = effect.toEither + + @inline override final def guarantee[E, A](f: Either[E, A], cleanup: Either[Nothing, Unit]): Either[E, A] = f + + override def traverse[E, A, B](l: Iterable[A])(f: A => Either[E, B]): Either[E, List[B]] = { + val b = List.newBuilder[B] + val i = l.iterator + + while (i.hasNext) { + f(i.next()) match { + case Left(error) => + return Left(error) + case Right(v) => + b += v + } + } + Right(b.result()) + } + + override def foldLeft[E, A, AC](col: Iterable[A])(z: AC)(op: (AC, A) => Either[E, AC]): Either[E, AC] = { + val i = col.iterator + var acc: Either[E, AC] = Right(z) + + while (i.hasNext && acc.isRight) { + val nxt = i.next() + (acc, nxt) match { + case (Right(a), n) => + acc = op(a, n) + case _ => + } + } + acc + } + + override def find[E, A](l: Iterable[A])(f: A => Either[E, Boolean]): Either[E, Option[A]] = { + val i = l.iterator + + while (i.hasNext) { + val a = i.next() + f(a) match { + case Left(value) => + return Left(value) + case Right(true) => + return Right(Some(a)) + case Right(_) => + } + } + Right(None) + } + + override def collectFirst[E, A, B](l: Iterable[A])(f: A => Either[E, Option[B]]): Either[E, Option[B]] = { + val i = l.iterator + + while (i.hasNext) { + val a = i.next() + f(a) match { + case Left(value) => + return Left(value) + case Right(res @ Some(_)) => + return Right(res) + case Right(_) => + } + } + Right(None) + } + + override def partition[E, A](l: Iterable[Either[E, A]]): Right[Nothing, (List[E], List[A])] = { + val bad = List.newBuilder[E] + val good = List.newBuilder[A] + + l.iterator.foreach { + case Left(e) => bad += e + case Right(v) => good += v + } + + Right((bad.result(), good.result())) + } + + override protected[this] def accumulateErrorsImpl[ColL[_], ColR[x] <: IterableOnce[x], E, E1, A, B, B1, AC]( + col: ColR[A] + )(effect: A => Either[E, B], + onLeft: E => IterableOnce[E1], + init: AC, + onRight: (AC, B) => AC, + end: AC => B1, + )(implicit buildL: Factory[E1, ColL[E1]] + ): Either[ColL[E1], B1] = { + val bad = buildL.newBuilder + + val iterator = col.iterator + var good = init + var allGood = true + while (iterator.hasNext) { + effect(iterator.next()) match { + case Left(e) => + allGood = false + bad ++= onLeft(e) + case Right(v) => + good = onRight(good, v) + } + } + + if (allGood) { + Right(end(good)) + } else { + Left(bad.result()) + } + } - @inline override def guarantee[R, E, A](f: Either[E, A], cleanup: Either[Nothing, Unit]): Either[E, A] = f } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BioIdentity2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BioIdentity2.scala new file mode 100644 index 0000000000..92d20c9868 --- /dev/null +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BioIdentity2.scala @@ -0,0 +1,31 @@ +package izumi.functional.bio.impl + +import izumi.functional.bio.Monad2 +import izumi.fundamentals.platform.functional.Identity2 + +object BioIdentity2 extends BioIdentity2 + +open class BioIdentity2 extends Monad2[Identity2] { + + override def pure[A](a: A): Identity2[Nothing, A] = { + a + } + override def flatMap[E, A, B](r: Identity2[E, A])(f: A => Identity2[E, B]): Identity2[E, B] = { + f(r) + } + override def map2[E, A, B, C](firstOp: Identity2[E, A], secondOp: => Identity2[E, B])(f: (A, B) => C): Identity2[E, C] = { + f(firstOp, secondOp) + } + override def *>[E, A, B](firstOp: Identity2[E, A], secondOp: => Identity2[E, B]): Identity2[E, B] = { + val _ = firstOp; secondOp + } + override def <*[E, A, B](firstOp: Identity2[E, A], secondOp: => Identity2[E, B]): Identity2[E, A] = { + secondOp; firstOp + } + override def traverse[E, A, B](l: Iterable[A])(f: A => Identity2[E, B]): Identity2[E, List[B]] = { + l.map(f).toList + } + override def map[E, A, B](r: Identity2[E, A])(f: A => B): Identity2[E, B] = { + f(r) + } +} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BioIdentity3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BioIdentity3.scala deleted file mode 100644 index 393e762786..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BioIdentity3.scala +++ /dev/null @@ -1,31 +0,0 @@ -package izumi.functional.bio.impl - -import izumi.functional.bio.Monad3 -import izumi.fundamentals.platform.functional.Identity3 - -object BioIdentity3 extends BioIdentity3 - -open class BioIdentity3 extends Monad3[Identity3] { - - override def pure[A](a: A): Identity3[Any, Nothing, A] = { - a - } - override def flatMap[R, E, A, B](r: Identity3[R, E, A])(f: A => Identity3[R, E, B]): Identity3[R, E, B] = { - f(r) - } - override def map2[R, E, A, B, C](firstOp: Identity3[R, E, A], secondOp: => Identity3[R, E, B])(f: (A, B) => C): Identity3[R, E, C] = { - f(firstOp, secondOp) - } - override def *>[R, E, A, B](firstOp: Identity3[R, E, A], secondOp: => Identity3[R, E, B]): Identity3[R, E, B] = { - val _ = firstOp; secondOp - } - override def <*[R, E, A, B](firstOp: Identity3[R, E, A], secondOp: => Identity3[R, E, B]): Identity3[R, E, A] = { - secondOp; firstOp - } - override def traverse[R, E, A, B](l: Iterable[A])(f: A => Identity3[R, E, B]): Identity3[R, E, List[B]] = { - l.map(f).toList - } - override def map[R, E, A, B](r: Identity3[R, E, A])(f: A => B): Identity3[R, E, B] = { - f(r) - } -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/ForkZio.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/ForkZio.scala index fc6165c65c..4a0bfb6208 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/ForkZio.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/ForkZio.scala @@ -1,20 +1,20 @@ package izumi.functional.bio.impl import izumi.functional.bio.Exit.ZIOExit -import izumi.functional.bio.{Fiber2, Fiber3, Fork3} +import izumi.functional.bio.{Fiber2, Fork2} import izumi.fundamentals.platform.language.Quirks.Discarder import zio.internal.stacktracer.Tracer -import zio.{IO, ZIO} import zio.stacktracer.TracingImplicits.disableAutoTrace +import zio.ZIO import java.util.concurrent.atomic.AtomicBoolean import scala.concurrent.ExecutionContext -object ForkZio extends ForkZio +object ForkZio extends ForkZio[Any] -open class ForkZio extends Fork3[ZIO] { +open class ForkZio[R] extends Fork2[ZIO[R, +_, +_]] { - override def fork[R, E, A](f: ZIO[R, E, A]): ZIO[R, Nothing, Fiber2[IO, E, A]] = { + override def fork[E, A](f: ZIO[R, E, A]): ZIO[R, Nothing, Fiber2[ZIO[R, +_, +_], E, A]] = { implicit val trace: zio.Trace = Tracer.instance.empty val interrupted = new AtomicBoolean(true) // fiber could be interrupted before executing a single op @@ -29,7 +29,7 @@ open class ForkZio extends Fork3[ZIO] { .map(Fiber2.fromZIO(ZIO.succeed(interrupted.get()))) } - override def forkOn[R, E, A](ec: ExecutionContext)(f: ZIO[R, E, A]): ZIO[R, Nothing, Fiber3[ZIO, E, A]] = { + override def forkOn[E, A](ec: ExecutionContext)(f: ZIO[R, E, A]): ZIO[R, Nothing, Fiber2[ZIO[R, +_, +_], E, A]] = { implicit val trace: zio.Trace = Tracer.instance.empty val interrupted = new AtomicBoolean(true) // fiber could be interrupted before executing a single op diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/MiniBIO.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/MiniBIO.scala index fd34ba3b0f..210f45fb0c 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/MiniBIO.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/MiniBIO.scala @@ -120,7 +120,7 @@ object MiniBIO { implicit val BIOMiniBIO: IO2[MiniBIO] with BlockingIO2[MiniBIO] = new IO2[MiniBIO] with BlockingIO2[MiniBIO] { override def pure[A](a: A): MiniBIO[Nothing, A] = sync(a) - override def flatMap[R, E, A, B](r: MiniBIO[E, A])(f: A => MiniBIO[E, B]): MiniBIO[E, B] = FlatMap(r, f) + override def flatMap[E, A, B](r: MiniBIO[E, A])(f: A => MiniBIO[E, B]): MiniBIO[E, B] = FlatMap(r, f) override def fail[E](v: => E): MiniBIO[E, Nothing] = Fail(() => Exit.Error(v, Trace.forTypedError(v))) override def terminate(v: => Throwable): MiniBIO[Nothing, Nothing] = Fail.terminate(v) override def sendInterruptToSelf: MiniBIO[Nothing, Unit] = unit @@ -133,7 +133,7 @@ object MiniBIO { } override def sync[A](effect: => A): MiniBIO[Nothing, A] = Sync(() => Exit.Success(effect)) - override def redeem[R, E, A, E2, B](r: MiniBIO[E, A])(err: E => MiniBIO[E2, B], succ: A => MiniBIO[E2, B]): MiniBIO[E2, B] = { + override def redeem[E, A, E2, B](r: MiniBIO[E, A])(err: E => MiniBIO[E2, B], succ: A => MiniBIO[E2, B]): MiniBIO[E2, B] = { Redeem[E, A, E2, B]( r, { @@ -145,9 +145,9 @@ object MiniBIO { ) } - override def catchAll[R, E, A, E2](r: MiniBIO[E, A])(f: E => MiniBIO[E2, A]): MiniBIO[E2, A] = redeem(r)(f, pure) + override def catchAll[E, A, E2](r: MiniBIO[E, A])(f: E => MiniBIO[E2, A]): MiniBIO[E2, A] = redeem(r)(f, pure) - override def bracketCase[R, E, A, B](acquire: MiniBIO[E, A])(release: (A, Exit[E, B]) => MiniBIO[Nothing, Unit])(use: A => MiniBIO[E, B]): MiniBIO[E, B] = { + override def bracketCase[E, A, B](acquire: MiniBIO[E, A])(release: (A, Exit[E, B]) => MiniBIO[Nothing, Unit])(use: A => MiniBIO[E, B]): MiniBIO[E, B] = { // does not propagate error raised in release if `use` failed, in that case only error from `use` is preserved flatMap(acquire)( a => @@ -159,11 +159,11 @@ object MiniBIO { ) } - override def sandbox[R, E, A](r: MiniBIO[E, A]): MiniBIO[Exit.Failure[E], A] = { + override def sandbox[E, A](r: MiniBIO[E, A]): MiniBIO[Exit.Failure[E], A] = { Redeem[E, A, Exit.Failure[E], A](r, e => fail(e), pure) } - override def traverse[R, E, A, B](l: Iterable[A])(f: A => MiniBIO[E, B]): MiniBIO[E, List[B]] = { + override def traverse[E, A, B](l: Iterable[A])(f: A => MiniBIO[E, B]): MiniBIO[E, List[B]] = { val x = l.foldLeft(pure(Nil): MiniBIO[E, List[B]]) { (acc, a) => flatMap(acc)(list => map(f(a))(_ :: list)) @@ -171,14 +171,14 @@ object MiniBIO { map(x)(_.reverse) } - override def shiftBlocking[R, E, A](f: MiniBIO[E, A]): MiniBIO[E, A] = f + override def shiftBlocking[E, A](f: MiniBIO[E, A]): MiniBIO[E, A] = f override def syncBlocking[A](f: => A): MiniBIO[Throwable, A] = sync(f) override def syncInterruptibleBlocking[A](f: => A): MiniBIO[Throwable, A] = sync(f) - override def uninterruptible[R, E, A](r: MiniBIO[E, A]): MiniBIO[E, A] = r - override def uninterruptibleExcept[R, E, A](r: RestoreInterruption2[MiniBIO] => MiniBIO[E, A]): MiniBIO[E, A] = r(Morphism2(identity(_))) - override def bracketExcept[R, E, A, B]( + override def uninterruptible[E, A](r: MiniBIO[E, A]): MiniBIO[E, A] = r + override def uninterruptibleExcept[E, A](r: RestoreInterruption2[MiniBIO] => MiniBIO[E, A]): MiniBIO[E, A] = r(Morphism2(identity(_))) + override def bracketExcept[E, A, B]( acquire: RestoreInterruption2[MiniBIO] => MiniBIO[E, A] )(release: (A, Exit[E, B]) => MiniBIO[Nothing, Unit] )(use: A => MiniBIO[E, B] diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/PrimitivesMZio.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/PrimitivesMZio.scala index 9edd1bfe4c..0cb3011838 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/PrimitivesMZio.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/PrimitivesMZio.scala @@ -3,19 +3,19 @@ package izumi.functional.bio.impl import izumi.functional.bio.{Mutex2, PrimitivesM2, RefM2} import izumi.fundamentals.platform.language.Quirks.Discarder import zio.internal.stacktracer.Tracer -import zio.{IO, Ref} import zio.stacktracer.TracingImplicits.disableAutoTrace +import zio.{Ref, ZIO} -object PrimitivesMZio extends PrimitivesMZio +object PrimitivesMZio extends PrimitivesMZio[Any] -open class PrimitivesMZio extends PrimitivesM2[IO] { - override def mkRefM[A](a: A): IO[Nothing, RefM2[IO, A]] = { +open class PrimitivesMZio[R] extends PrimitivesM2[ZIO[R, +_, +_]] { + override def mkRefM[A](a: A): ZIO[R, Nothing, RefM2[ZIO[R, +_, +_], A]] = { implicit val trace: zio.Trace = Tracer.newTrace Ref.Synchronized.make(a).map(RefM2.fromZIO) } - override def mkMutex: IO[Nothing, Mutex2[IO]] = { - Mutex2.createFromBIO + override def mkMutex: ZIO[R, Nothing, Mutex2[ZIO[R, +_, +_]]] = { + Mutex2.createFromBIO[ZIO[R, +_, +_]] } disableAutoTrace.discard() diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/PrimitivesZio.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/PrimitivesZio.scala index 6aaeac4a0d..0c5e26e1ea 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/PrimitivesZio.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/PrimitivesZio.scala @@ -5,22 +5,22 @@ import izumi.fundamentals.platform.language.Quirks.Discarder import zio.internal.stacktracer.Tracer import zio.stacktracer.TracingImplicits.disableAutoTrace import zio.stm.TSemaphore -import zio.{IO, Promise, Ref} +import zio.{Promise, Ref, ZIO} -object PrimitivesZio extends PrimitivesZio +object PrimitivesZio extends PrimitivesZio[Any] -open class PrimitivesZio extends Primitives2[IO] { - override def mkRef[A](a: A): IO[Nothing, Ref2[IO, A]] = { +open class PrimitivesZio[R] extends Primitives2[ZIO[R, +_, +_]] { + override def mkRef[A](a: A): ZIO[R, Nothing, Ref2[ZIO[R, +_, +_], A]] = { implicit val trace: zio.Trace = Tracer.newTrace Ref.make(a).map(Ref2.fromZIO) } - override def mkPromise[E, A]: IO[Nothing, Promise2[IO, E, A]] = { + override def mkPromise[E, A]: ZIO[R, Nothing, Promise2[ZIO[R, +_, +_], E, A]] = { implicit val trace: zio.Trace = Tracer.newTrace Promise.make[E, A].map(Promise2.fromZIO) } - override def mkSemaphore(permits: Long): IO[Nothing, Semaphore2[IO]] = { + override def mkSemaphore(permits: Long): ZIO[R, Nothing, Semaphore2[ZIO[R, +_, +_]]] = { implicit val trace: zio.Trace = Tracer.newTrace TSemaphore.make(permits).map(Semaphore2.fromZIO).commit diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/SchedulerImpl.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/SchedulerImpl.scala index 88b6c07430..6ce494d72e 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/SchedulerImpl.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/SchedulerImpl.scala @@ -45,16 +45,16 @@ open class SchedulerImpl[F[+_, +_]: Temporal2](implicit clock: Clock2[F]) extend override def retryOrElseUntil[E, A, E2](r: F[E, A])(duration: FiniteDuration, orElse: E => F[E2, A]): F[E2, A] = { def loop(maxTime: ZonedDateTime): F[E2, A] = { - F.redeem[Any, E, A, E2, A](r)( + F.redeem[E, A, E2, A](r)( err = error => - F.flatMap[Any, E2, ZonedDateTime, A]( + F.flatMap[E2, ZonedDateTime, A]( clock.now(ClockAccuracy.MILLIS) )(now => if (now.isBefore(maxTime)) loop(maxTime) else orElse(error)), succ = F.pure(_), ) } - F.flatMap[Any, E2, ZonedDateTime, A]( + F.flatMap[E2, ZonedDateTime, A]( clock.now(ClockAccuracy.MILLIS) )(now => loop(maxTime = now.plus(duration.toMillis, ChronoUnit.MILLIS))) } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/TemporalZio.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/TemporalZio.scala index 785ee714d7..22d1401d2e 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/TemporalZio.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/TemporalZio.scala @@ -1,6 +1,6 @@ package izumi.functional.bio.impl -import izumi.functional.bio.Temporal3 +import izumi.functional.bio.Temporal2 import izumi.fundamentals.platform.language.Quirks.Discarder import zio.Duration.fromScala import zio.ZIO @@ -9,17 +9,19 @@ import zio.stacktracer.TracingImplicits.disableAutoTrace import scala.concurrent.duration.Duration -open class TemporalZio - extends AsyncZio // use own implementation of timeout to match CE race behavior - with Temporal3[ZIO] { +object TemporalZio extends TemporalZio[Any] - @inline override final def sleep(duration: Duration): ZIO[Any, Nothing, Unit] = { +open class TemporalZio[R] + extends AsyncZio[R] // use own implementation of timeout to match CE race behavior + with Temporal2[ZIO[R, +_, +_]] { + + @inline override final def sleep(duration: Duration): ZIO[R, Nothing, Unit] = { implicit val trace: zio.Trace = Tracer.newTrace zio.Clock.sleep(fromScala(duration)) } - @inline override final def timeout[R, E, A](duration: Duration)(r: ZIO[R, E, A]): ZIO[R, E, Option[A]] = { + @inline override final def timeout[E, A](duration: Duration)(r: ZIO[R, E, A]): ZIO[R, E, Option[A]] = { implicit val trace: zio.Trace = Tracer.newTrace this.race(r.map(Some(_)).interruptible, this.sleep(duration).as(None).interruptible) diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala index d685545018..9e5ae833c3 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala @@ -1,40 +1,30 @@ package izumi.functional import izumi.functional.bio.data.Isomorphism2 -import izumi.functional.bio.syntax.{Syntax2, Syntax3} +import izumi.functional.bio.syntax.Syntax2 /** * Current hierarchy (use http://www.nomnoml.com/ to render, rendered: https://izumi.7mind.io/bio/media/bio-relationship-hierarchy.svg) * * {{{ - * [Functor3]<--[Bifunctor3] - * [Bifunctor3]<--[ApplicativeError3] - * [Functor3]<--[Applicative3] - * [Applicative3]<--[Guarantee3] - * [Applicative3]<--[Monad3] - * [Guarantee3]<--[ApplicativeError3] - * [ApplicativeError3]<--[Error3] - * [Monad3]<--[Error3] - * [Error3]<--[Bracket3] - * [Bracket3]<--[Panic3] - * [Panic3]<--[IO3] - * [IO3]<--[Async3] - * - * [Monad3]<--[Parallel3] - * [Parallel3]<--[Concurrent3] - * [Concurrent3]<--[Async3] - * - * [Error3]<--[Temporal3] - * - * [Functor3]<--[Profunctor3] - * [Profunctor3]<--[Arrow3] - * [Arrow3]<--[ArrowChoice3] - * [ArrowChoice3]<--[Local3] - * - * [Applicative3]<--[Ask3] - * [Monad3]<--[MonadAsk3] - * [Ask3]<--[MonadAsk3] - * [MonadAsk3]<--[Local3] + * [Functor2]<--[Bifunctor2] + * [Bifunctor2]<--[ApplicativeError2] + * [Functor2]<--[Applicative2] + * [Applicative2]<--[Guarantee2] + * [Applicative2]<--[Monad2] + * [Guarantee2]<--[ApplicativeError2] + * [ApplicativeError2]<--[Error2] + * [Monad2]<--[Error2] + * [Error2]<--[Bracket2] + * [Bracket2]<--[Panic2] + * [Panic2]<--[IO2] + * [IO2]<--[Async2] + * + * [Monad2]<--[Parallel2] + * [Parallel2]<--[Concurrent2] + * [Concurrent2]<--[Async2] + * + * [Error2]<--[Temporal2] * }}} * * Auxiliary algebras: @@ -42,46 +32,45 @@ import izumi.functional.bio.syntax.{Syntax2, Syntax3} * {{{ * [cats.effect.*]<:--[CatsConversions] * - * [Fork3]<:--[Fiber3] + * [Fork2]<:--[Fiber2] * - * [BlockingIO3]<:--[BlockingIO2] + * [BlockingIO2] * - * [Primitives2]<:--[Primitives3] - * [Primitives3]<:--[Ref3] + * [Primitives2] * - * [Primitives3]<:--[Semaphore3] - * [Primitives3]<:--[Promise3] + * [Primitives2]<:--[Ref2] + * [Primitives2]<:--[Semaphore2] + * [Primitives2]<:--[Promise2] * - * [Entropy]<:--[Entropy2] + * [PrimitivesM2] + * [PrimitivesM2]<:--[RefM2] + * [PrimitivesM2]<:--[Mutex2] * - * [Entropy2]<:--[Entropy3] + * [Entropy1]<:--[Entropy2] + * [Clock1]<:--[Clock2] + * + * [UnsafeRun2] * }}} * - * inheritance hierarchy: + * Raw inheritance hierarchy: * * {{{ - * [Functor3]<--[Applicative3] - * [Applicative3]<--[Guarantee3] - * [Applicative3]<--[Monad3] - * [Guarantee3]<--[ApplicativeError3] - * [Bifunctor3]<--[ApplicativeError3] - * [ApplicativeError3]<--[Error3] - * [Monad3]<--[Error3] - * [Error3]<--[Bracket3] - * [Bracket3]<--[Panic3] - * [Panic3]<--[IO3] - * - * [Parallel3]<--[Concurrent3] - * [Concurrent3]<--[Async3] - * [IO3]<--[Async3] - * - * [Temporal3] - * - * [Profunctor3]<--[Arrow3] - * [Arrow3]<--[ArrowChoice3] - * [ArrowChoice3]<--[Local3] - * [Ask3]<--[MonadAsk3] - * [MonadAsk3]<--[Local3] + * [Functor2]<--[Applicative2] + * [Applicative2]<--[Guarantee2] + * [Applicative2]<--[Monad2] + * [Guarantee2]<--[ApplicativeError2] + * [Bifunctor2]<--[ApplicativeError2] + * [ApplicativeError2]<--[Error2] + * [Monad2]<--[Error2] + * [Error2]<--[Bracket2] + * [Bracket2]<--[Panic2] + * [Panic2]<--[IO2] + * + * [Parallel2]<--[Concurrent2] + * [Concurrent2]<--[Async2] + * [IO2]<--[Async2] + * + * [Temporal2] * }}} * * current hierarchy roots: @@ -92,14 +81,11 @@ import izumi.functional.bio.syntax.{Syntax2, Syntax3} * - Parallel3 * - Temporal3 * - * trifunctor: - * - Profunctor3 - * - Ask3 - * * standalone: * - Fork3 * - BlockingIO3 * - Primitives3 + * - PrimitivesM3 */ /* New BIO typeclass checklist: @@ -110,7 +96,7 @@ import izumi.functional.bio.syntax.{Syntax2, Syntax3} [ ] - add conversion BIOConvertToBIONewRoot in BIORootInstanceLowPriorityN (conversions implicit priority: from most specific InnerF to least specific) */ -package object bio extends Syntax3 with Syntax2 { +package object bio extends Syntax2 { /** * A convenient dependent summoner for BIO hierarchy. @@ -124,76 +110,21 @@ package object bio extends Syntax3 with Syntax2 { * } * }}} */ - @inline override final def F[FR[-_, +_, +_]](implicit FR: Functor3[FR]): FR.type = FR - - type Functor2[F[+_, +_]] = Functor3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Bifunctor2[F[+_, +_]] = Bifunctor3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Applicative2[F[+_, +_]] = Applicative3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Guarantee2[F[+_, +_]] = Guarantee3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type ApplicativeError2[F[+_, +_]] = ApplicativeError3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Monad2[F[+_, +_]] = Monad3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Error2[F[+_, +_]] = Error3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Bracket2[F[+_, +_]] = Bracket3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Panic2[F[+_, +_]] = Panic3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type IO2[F[+_, +_]] = IO3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Parallel2[F[+_, +_]] = Parallel3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Concurrent2[F[+_, +_]] = Concurrent3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Async2[F[+_, +_]] = Async3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - type Temporal2[F[+_, +_]] = Temporal3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - - type Fork2[F[+_, +_]] = Fork3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - - type Primitives3[F[-_, +_, +_]] = Primitives2[F[Any, +_, +_]] - object Primitives3 { - @inline def apply[F[-_, +_, +_]: Primitives3]: Primitives3[F] = implicitly - } - - type PrimitivesM3[F[-_, +_, +_]] = PrimitivesM2[F[Any, +_, +_]] - object PrimitivesM3 { - @inline def apply[F[-_, +_, +_]: PrimitivesM3]: PrimitivesM3[F] = implicitly - } - - type BlockingIO2[F[+_, +_]] = BlockingIO3[λ[(`-R`, `+E`, `+A`) => F[E, A]]] - object BlockingIO2 { - @inline def apply[F[+_, +_]: BlockingIO2]: BlockingIO2[F] = implicitly - } + @inline override final def F[F[+_, +_]](implicit F: Functor2[F]): F.type = F type TransZio[F[_, _]] = Isomorphism2[F, zio.IO] object TransZio { @inline def apply[F[_, _]: TransZio]: TransZio[F] = implicitly } - type Fiber3[+F[-_, +_, +_], +E, +A] = Fiber2[F[Any, +_, +_], E, A] - lazy val Fiber3: Fiber2.type = Fiber2 - - type RefM3[F[_, +_, +_], A] = RefM2[F[Any, +_, +_], A] - lazy val RefM3: RefM2.type = RefM2 - - type Mutex3[F[_, +_, +_], A] = Mutex2[F[Any, +_, +_]] - lazy val Mutex3: Mutex2.type = Mutex2 - type Ref2[+F[_, _], A] = Ref1[F[Nothing, _], A] lazy val Ref2: Ref1.type = Ref1 - type Ref3[+F[_, _, _], A] = Ref1[F[Any, Nothing, _], A] - lazy val Ref3: Ref1.type = Ref1 - - type Promise3[+F[-_, +_, +_], E, A] = Promise2[F[Any, +_, +_], E, A] - lazy val Promise3: Promise2.type = Promise2 type Latch2[+F[+_, +_]] = Promise2[F, Nothing, Unit] lazy val Latch2: Promise2.type = Promise2 - type Latch3[+F[-_, +_, +_]] = Promise3[F, Nothing, Unit] - lazy val Latch3: Promise2.type = Promise2 type Semaphore2[+F[_, _]] = Semaphore1[F[Nothing, _]] lazy val Semaphore2: Semaphore1.type = Semaphore1 - type Semaphore3[+F[_, _, _]] = Semaphore1[F[Any, Nothing, _]] - lazy val Semaphore3: Semaphore1.type = Semaphore1 - - type UnsafeRun3[F[_, _, _]] = UnsafeRun2[F[Any, _, _]] - object UnsafeRun3 { - @inline def apply[F[_, _, _]: UnsafeRun3]: UnsafeRun3[F] = implicitly - } type SyncSafe2[F[_, _]] = SyncSafe1[F[Nothing, _]] object SyncSafe2 { @@ -222,9 +153,4 @@ package object bio extends Syntax3 with Syntax2 { @inline def apply[F[_, _, _]: Entropy3]: Entropy3[F] = implicitly } - @inline private[bio] final def cast3To2[C[_[-_, +_, +_]], FR[-_, +_, +_], R]( - instance: C[FR] - ): C[λ[(`-R0`, `+E`, `+A`) => FR[R, E, A]]] = { - instance.asInstanceOf[C[λ[(`-R0`, `+E`, `+A`) => FR[R, E, A]]]] - } } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/retry/package.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/retry/package.scala deleted file mode 100644 index fe81849e9a..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/retry/package.scala +++ /dev/null @@ -1,10 +0,0 @@ -package izumi.functional.bio - -package object retry { - - type Scheduler3[F[-_, +_, +_]] = Scheduler2[F[Any, +_, +_]] - object Scheduler3 { - @inline def apply[F[-_, +_, +_]: Scheduler3]: Scheduler3[F] = implicitly - } - -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/Syntax2.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/Syntax2.scala index 79b9c4bab5..b63664c088 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/Syntax2.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/Syntax2.scala @@ -8,7 +8,52 @@ import scala.annotation.unused import scala.concurrent.duration.{Duration, FiniteDuration} import scala.language.implicitConversions -trait Syntax2 extends ImplicitPuns +/** + * All implicit syntax in BIO is available automatically without wildcard imports + * with the help of so-called "implicit punning", as in the following example: + * + * {{{ + * import izumi.functional.bio.Monad2 + * + * def loop[F[+_, +_]: Monad2]: F[Nothing, Nothing] = { + * val unitEffect: F[Nothing, Unit] = Monad2[F].unit + * unitEffect.flatMap(loop) + * } + * }}} + * + * Note that a `.flatMap` method is available on the `unitEffect` value of an abstract type parameter `F`, + * even though we did not import any syntax implicits using a wildcard import. + * + * The `flatMap` method was added by the implicit punning on the `Monad2` name. + * In short, implicit punning just means that instead of creating a companion object for a type with the same name as the type, + * we create "companion" implicit conversions with the same name. So that whenever you import the type, + * you are also always importing the syntax-providing implicit conversions. + * + * This happens to be a great fit for Tagless Final Style, since nearly all TF code will import the names of the used typeclasses. + * + * Implicit Punning for typeclass syntax relieves the programmer from having to manually import syntax implicits in every file in their codebase. + * + * @note The order of conversions is such to allow otherwise conflicting type classes to not conflict, + * e.g. code using constraints such as `def x[F[+_, +_]: Functor2: Applicative2: Monad2]` will compile and run + * normally when using syntax, despite ambiguity of implicits caused by all 3 implicits inheriting from Functor2. + * This is because, due to the priority order being from most-specific to least-specific, the `Monad2` syntax + * will be used in such a case, where the `Monad2[F]` implicit is actually unambiguous. + */ +trait Syntax2 extends ImplicitPuns { + /** + * A convenient dependent summoner for BIO hierarchy. + * Auto-narrows to the most powerful available class: + * + * {{{ + * import izumi.functional.bio.{F, Temporal2} + * + * def y[F[+_, +_]: Temporal2] = { + * F.timeout(5.seconds)(F.forever(F.unit)) + * } + * }}} + */ + def F[F[+_, +_]](implicit F: Functor2[F]): F.type = F +} object Syntax2 { @@ -64,7 +109,7 @@ object Syntax2 { } final class MonadOps[F[+_, +_], +E, +A](override protected[this] val r: F[E, A])(implicit override protected[this] val F: Monad2[F]) extends ApplicativeOps(r)(F) { - @inline final def flatMap[E1 >: E, B](f0: A => F[E1, B]): F[E1, B] = F.flatMap[Any, E1, A, B](r)(f0) + @inline final def flatMap[E1 >: E, B](f0: A => F[E1, B]): F[E1, B] = F.flatMap[E1, A, B](r)(f0) @inline final def tap[E1 >: E](f0: A => F[E1, Unit]): F[E1, A] = F.tap(r, f0) @inline final def flatten[E1 >: E, A1](implicit ev: A <:< F[E1, A1]): F[E1, A1] = F.flatten(r.widen) @@ -77,7 +122,7 @@ object Syntax2 { class ErrorOps[F[+_, +_], +E, +A](override protected[this] val r: F[E, A])(implicit override protected[this] val F: Error2[F]) extends ApplicativeErrorOps(r)(F) { // duplicated from MonadOps - @inline final def flatMap[E1 >: E, B](f0: A => F[E1, B]): F[E1, B] = F.flatMap[Any, E1, A, B](r)(f0) + @inline final def flatMap[E1 >: E, B](f0: A => F[E1, B]): F[E1, B] = F.flatMap[E1, A, B](r)(f0) @inline final def tap[E1 >: E](f0: A => F[E1, Unit]): F[E1, A] = F.tap(r, f0) @inline final def flatten[E1 >: E, A1](implicit ev: A <:< F[E1, A1]): F[E1, A1] = F.flatten(r.widen) @@ -88,22 +133,22 @@ object Syntax2 { @inline final def fromOptionF[E1 >: E, B](fallbackOnNone: => F[E1, B])(implicit ev: A <:< Option[B]): F[E1, B] = F.fromOptionF(fallbackOnNone, r.widen) // duplicated from MonadOps - @inline final def catchAll[E2, A2 >: A](h: E => F[E2, A2]): F[E2, A2] = F.catchAll[Any, E, A2, E2](r)(h) - @inline final def catchSome[E1 >: E, A2 >: A](h: PartialFunction[E, F[E1, A2]]): F[E1, A2] = F.catchSome[Any, E, A2, E1](r)(h) + @inline final def catchAll[E2, A2 >: A](h: E => F[E2, A2]): F[E2, A2] = F.catchAll[E, A2, E2](r)(h) + @inline final def catchSome[E1 >: E, A2 >: A](h: PartialFunction[E, F[E1, A2]]): F[E1, A2] = F.catchSome[E, A2, E1](r)(h) - @inline final def redeem[E2, B](err: E => F[E2, B], succ: A => F[E2, B]): F[E2, B] = F.redeem[Any, E, A, E2, B](r)(err, succ) + @inline final def redeem[E2, B](err: E => F[E2, B], succ: A => F[E2, B]): F[E2, B] = F.redeem[E, A, E2, B](r)(err, succ) @inline final def redeemPure[B](err: E => B, succ: A => B): F[Nothing, B] = F.redeemPure(r)(err, succ) @inline final def attempt: F[Nothing, Either[E, A]] = F.attempt(r) - @inline final def tapError[E1 >: E](f: E => F[E1, Unit]): F[E1, A] = F.tapError[Any, E, A, E1](r)(f) + @inline final def tapError[E1 >: E](f: E => F[E1, Unit]): F[E1, A] = F.tapError[E, A, E1](r)(f) @inline final def leftFlatMap[E2](f: E => F[Nothing, E2]): F[E2, A] = F.leftFlatMap(r)(f) @inline final def flip: F[A, E] = F.flip(r) - @inline final def tapBoth[E1 >: E, E2 >: E1](err: E => F[E1, Unit])(succ: A => F[E2, Unit]): F[E2, A] = F.tapBoth[Any, E, A, E2](r)(err, succ) + @inline final def tapBoth[E1 >: E, E2 >: E1](err: E => F[E1, Unit])(succ: A => F[E2, Unit]): F[E2, A] = F.tapBoth[E, A, E2](r)(err, succ) - @inline final def fromEither[E1 >: E, A1](implicit ev: A <:< Either[E1, A1]): F[E1, A1] = F.flatMap[Any, E1, A, A1](r)(F.fromEither[E1, A1](_)) + @inline final def fromEither[E1 >: E, A1](implicit ev: A <:< Either[E1, A1]): F[E1, A1] = F.flatMap[E1, A, A1](r)(F.fromEither[E1, A1](_)) @inline final def fromOption[E1 >: E, A1](errorOnNone: => E1)(implicit ev1: A <:< Option[A1]): F[E1, A1] = F.fromOption(errorOnNone, r.widen) @inline final def retryWhile(f: E => Boolean): F[E, A] = F.retryWhile(r)(f) @@ -130,7 +175,7 @@ object Syntax2 { * }}} */ @inline final def withFilter[A1 >: A, E1 >: E](predicate: A => Boolean)(implicit filter: WithFilter[E1], pos: SourceFilePositionMaterializer): F[E1, A] = - F.withFilter[Any, E1, A](r)(predicate) + F.withFilter[E1, A](r)(predicate) } class BracketOps[F[+_, +_], +E, +A](override protected[this] val r: F[E, A])(implicit override protected[this] val F: Bracket2[F]) extends ErrorOps(r)(F) { @@ -173,7 +218,7 @@ object Syntax2 { class IOOps[F[+_, +_], +E, +A](override protected[this] val r: F[E, A])(implicit override protected[this] val F: IO2[F]) extends PanicOps(r)(F) { @inline final def bracketAuto[E1 >: E, B](use: A => F[E1, B])(implicit ev: A <:< AutoCloseable): F[E1, B] = - F.bracket[Any, E1, A, B](r)(c => F.sync(c.close()))(use) + F.bracket[E1, A, B](r)(c => F.sync(c.close()))(use) } class ParallelOps[F[+_, +_], +E, +A](protected[this] val r: F[E, A])(implicit protected[this] val F: Parallel2[F]) { @@ -203,7 +248,7 @@ object Syntax2 { final class TemporalOps[F[+_, +_], +E, +A](protected[this] val r: F[E, A])(implicit protected[this] val F: Temporal2[F]) { @inline final def repeatUntil[E2 >: E, A2](tooManyAttemptsError: => E2, sleep: FiniteDuration, maxAttempts: Int)(implicit ev: A <:< Option[A2]): F[E2, A2] = - F.repeatUntil[Any, E2, A2](new FunctorOps(r)(F.InnerF).widen)(tooManyAttemptsError, sleep, maxAttempts) + F.repeatUntil[E2, A2](new FunctorOps(r)(F.InnerF).widen)(tooManyAttemptsError, sleep, maxAttempts) @inline final def timeout(duration: Duration): F[E, Option[A]] = F.timeout(duration)(r) @inline final def timeoutFail[E1 >: E](e: => E1)(duration: Duration): F[E1, A] = F.timeoutFail(duration)(e, r) @@ -238,7 +283,7 @@ object Syntax2 { trait ImplicitPuns4 extends ImplicitPuns5 { @inline implicit final def IO2[F[+_, +_]: IO2, E, A](self: F[E, A]): IOOps[F, E, A] = new IOOps[F, E, A](self) /** - * Shorthand for [[IO3#syncThrowable]] + * Shorthand for [[IO2#syncThrowable]] * * {{{ * IO2(println("Hello world!")) @@ -285,4 +330,12 @@ object Syntax2 { @inline final def Functor2[F[+_, +_]: Functor2]: Functor2[F] = implicitly } + final class ClockAccessor[F[+_, +_]](@unused private val dummy: Boolean = false) extends AnyVal { + def clock(implicit clock: Clock2[F]): clock.type = clock + } + + final class EntropyAccessor[F[+_, +_]](@unused private val dummy: Boolean = false) extends AnyVal { + def entropy(implicit entropy: Entropy2[F]): entropy.type = entropy + } + } diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/Syntax3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/Syntax3.scala deleted file mode 100644 index b534520ec7..0000000000 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/Syntax3.scala +++ /dev/null @@ -1,421 +0,0 @@ -package izumi.functional.bio.syntax - -import cats.data.Kleisli -import izumi.functional.bio.syntax.Syntax3.ImplicitPuns -import izumi.functional.bio.{Applicative3, ApplicativeError3, Arrow3, ArrowChoice3, Ask3, Async3, Bifunctor3, Bracket3, Clock3, Concurrent3, Entropy3, Error3, Exit, Fiber3, Fork3, Functor3, Guarantee3, IO3, Local3, Monad3, MonadAsk3, Panic3, Parallel3, Profunctor3, Temporal3, WithFilter} -import izumi.fundamentals.platform.language.SourceFilePositionMaterializer - -import scala.annotation.unused -import scala.concurrent.duration.{Duration, FiniteDuration} -import scala.language.implicitConversions - -/** - * All implicit syntax in BIO is available automatically without wildcard imports - * with the help of so-called "implicit punning", as in the following example: - * - * {{{ - * import izumi.functional.bio.Monad2 - * - * def loop[F[+_, +_]: Monad2]: F[Nothing, Nothing] = { - * val unitEffect: F[Nothing, Unit] = Monad2[F].unit - * unitEffect.flatMap(loop) - * } - * }}} - * - * Note that a `.flatMap` method is available on the `unitEffect` value of an abstract type parameter `F`, - * even though we did not import any syntax implicits using a wildcard import. - * - * The `flatMap` method was added by the implicit punning on the `Monad2` name. - * In short, implicit punning just means that instead of creating a companion object for a type with the same name as the type, - * we create "companion" implicit conversions with the same name. So that whenever you import the type, - * you are also always importing the syntax-providing implicit conversions. - * - * This happens to be a great fit for Tagless Final Style, since nearly all TF code will import the names of the used typeclasses. - * - * Implicit Punning for typeclass syntax relieves the programmer from having to manually import syntax implicits in every file in their codebase. - * - * @note The order of conversions is such to allow otherwise conflicting type classes to not conflict, - * e.g. code using constraints such as `def x[F[+_, +_]: Functor2: Applicative2: Monad2]` will compile and run - * normally when using syntax, despite ambiguity of implicits caused by all 3 implicits inheriting from Functor2. - * This is because, due to the priority order being from most-specific to least-specific, the `Monad2` syntax - * will be used in such a case, where the `Monad2[F]` implicit is actually unambiguous. - */ -trait Syntax3 extends ImplicitPuns { - /** - * A convenient dependent summoner for BIO hierarchy. - * Auto-narrows to the most powerful available class: - * - * {{{ - * import izumi.functional.bio.{F, Temporal2} - * - * def y[F[+_, +_]: Temporal2] = { - * F.timeout(5.seconds)(F.forever(F.unit)) - * } - * }}} - */ - def F[FR[-_, +_, +_]](implicit FR: Functor3[FR]): FR.type = FR -} - -object Syntax3 { - - class FunctorOps[FR[-_, +_, +_], -R, +E, +A](protected[this] val r: FR[R, E, A])(implicit protected[this] val F: Functor3[FR]) { - @inline final def map[B](f: A => B): FR[R, E, B] = F.map(r)(f) - - @inline final def as[B](b: => B): FR[R, E, B] = F.map(r)(_ => b) - @inline final def void: FR[R, E, Unit] = F.void(r) - @inline final def widen[A1](implicit @unused ev: A <:< A1): FR[R, E, A1] = r.asInstanceOf[FR[R, E, A1]] - - @inline final def fromOptionOr[B](valueOnNone: => B)(implicit ev: A <:< Option[B]): FR[R, E, B] = F.fromOptionOr(valueOnNone, widen) - } - - final class BifunctorOps[FR[-_, +_, +_], -R, +E, +A](protected[this] val r: FR[R, E, A])(implicit protected[this] val F: Bifunctor3[FR]) { - @inline final def leftMap[E2](f: E => E2): FR[R, E2, A] = F.leftMap(r)(f) - @inline final def bimap[E2, B](f: E => E2, g: A => B): FR[R, E2, B] = F.bimap(r)(f, g) - - @inline final def widenError[E1 >: E]: FR[R, E1, A] = r - @inline final def widenBoth[E1 >: E, A1](implicit @unused ev2: A <:< A1): FR[R, E1, A1] = r.asInstanceOf[FR[R, E1, A1]] - } - - class ApplicativeOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: Applicative3[FR]) - extends FunctorOps(r)(F) { - - /** execute two operations in order, return result of second operation */ - @inline final def *>[R1 <: R, E1 >: E, B](f0: => FR[R1, E1, B]): FR[R1, E1, B] = F.*>(r, f0) - - /** execute two operations in order, same as `*>`, but return result of first operation */ - @inline final def <*[R1 <: R, E1 >: E, B](f0: => FR[R1, E1, B]): FR[R1, E1, A] = F.<*(r, f0) - - /** execute two operations in order, return result of both operations */ - @inline final def zip[R1 <: R, E1 >: E, B, C](r2: => FR[R1, E1, B]): FR[R1, E1, (A, B)] = F.map2(r, r2)(_ -> _) - - /** execute two operations in order, map their results */ - @inline final def map2[R1 <: R, E1 >: E, B, C](r2: => FR[R1, E1, B])(f: (A, B) => C): FR[R1, E1, C] = F.map2(r, r2)(f) - - @inline final def forever: FR[R, E, Nothing] = F.forever(r) - } - - class GuaranteeOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: Guarantee3[FR]) - extends ApplicativeOps(r)(F) { - @inline final def guarantee[R1 <: R](cleanup: FR[R1, Nothing, Unit]): FR[R1, E, A] = F.guarantee(r, cleanup) - } - - final class MonadOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: Monad3[FR]) - extends ApplicativeOps(r)(F) { - @inline final def flatMap[R1 <: R, E1 >: E, B](f0: A => FR[R1, E1, B]): FR[R1, E1, B] = F.flatMap[R1, E1, A, B](r)(f0) - @inline final def tap[R1 <: R, E1 >: E](f0: A => FR[R1, E1, Unit]): FR[R1, E1, A] = F.tap(r, f0) - - @inline final def flatten[R1 <: R, E1 >: E, A1](implicit ev: A <:< FR[R1, E1, A1]): FR[R1, E1, A1] = F.flatten(F.widen(r)) - - @inline final def iterateWhile(p: A => Boolean): FR[R, E, A] = F.iterateWhile(r)(p) - @inline final def iterateUntil(p: A => Boolean): FR[R, E, A] = F.iterateUntil(r)(p) - - @inline final def fromOptionF[R1 <: R, E1 >: E, B](fallbackOnNone: => FR[R1, E1, B])(implicit ev: A <:< Option[B]): FR[R1, E1, B] = - F.fromOptionF(fallbackOnNone, r.widen) - } - - class ApplicativeErrorOps[FR[-_, +_, +_], -R, +E, +A]( - override protected[this] val r: FR[R, E, A] - )(implicit override protected[this] val F: ApplicativeError3[FR] - ) extends GuaranteeOps(r)(F) { - @inline final def leftMap[E2](f: E => E2): FR[R, E2, A] = F.leftMap(r)(f) - @inline final def bimap[E2, B](f: E => E2, g: A => B): FR[R, E2, B] = F.bimap(r)(f, g) - - @inline final def orElse[R1 <: R, E2, A1 >: A](r2: => FR[R1, E2, A1]): FR[R1, E2, A1] = F.orElse(r, r2) - @inline final def leftMap2[R1 <: R, E2, A1 >: A, E3](r2: => FR[R1, E2, A1])(f: (E, E2) => E3): FR[R1, E3, A1] = F.leftMap2(r, r2)(f) - - @inline final def widenError[E1 >: E]: FR[R, E1, A] = r - @inline final def widenBoth[E1 >: E, A1](implicit @unused ev2: A <:< A1): FR[R, E1, A1] = r.asInstanceOf[FR[R, E1, A1]] - } - - class ErrorOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: Error3[FR]) - extends ApplicativeErrorOps(r)(F) { - // duplicated from MonadOps - @inline final def flatMap[R1 <: R, E1 >: E, B](f0: A => FR[R1, E1, B]): FR[R1, E1, B] = F.flatMap[R1, E1, A, B](r)(f0) - @inline final def tap[R1 <: R, E1 >: E](f0: A => FR[R1, E1, Unit]): FR[R1, E1, A] = F.tap(r, f0) - - @inline final def flatten[R1 <: R, E1 >: E, A1](implicit ev: A <:< FR[R1, E1, A1]): FR[R1, E1, A1] = F.flatten(F.widen(r)) - - @inline final def iterateWhile(p: A => Boolean): FR[R, E, A] = F.iterateWhile(r)(p) - @inline final def iterateUntil(p: A => Boolean): FR[R, E, A] = F.iterateUntil(r)(p) - - @inline final def fromOptionF[R1 <: R, E1 >: E, B](fallbackOnNone: => FR[R1, E1, B])(implicit ev: A <:< Option[B]): FR[R1, E1, B] = - F.fromOptionF(fallbackOnNone, r.widen) - // duplicated from MonadOps - - @inline final def catchAll[R1 <: R, E2, A2 >: A](h: E => FR[R1, E2, A2]): FR[R1, E2, A2] = F.catchAll[R1, E, A2, E2](r)(h) - @inline final def catchSome[R1 <: R, E1 >: E, A2 >: A](h: PartialFunction[E, FR[R1, E1, A2]]): FR[R1, E1, A2] = F.catchSome[R1, E, A2, E1](r)(h) - - @inline final def redeem[R1 <: R, E2, B](err: E => FR[R1, E2, B], succ: A => FR[R1, E2, B]): FR[R1, E2, B] = F.redeem[R1, E, A, E2, B](r)(err, succ) - @inline final def redeemPure[B](err: E => B, succ: A => B): FR[R, Nothing, B] = F.redeemPure(r)(err, succ) - - @inline final def attempt: FR[R, Nothing, Either[E, A]] = F.attempt(r) - - @inline final def tapError[R1 <: R, E1 >: E](f: E => FR[R1, E1, Unit]): FR[R1, E1, A] = F.tapError[R1, E, A, E1](r)(f) - - @inline final def leftFlatMap[R1 <: R, E2](f: E => FR[R1, Nothing, E2]): FR[R1, E2, A] = F.leftFlatMap[R1, E, A, E2](r)(f) - @inline final def flip: FR[R, A, E] = F.flip(r) - - @inline final def tapBoth[R1 <: R, E1 >: E, E2 >: E1](err: E => FR[R1, E1, Unit])(succ: A => FR[R1, E2, Unit]): FR[R1, E2, A] = F.tapBoth[R1, E, A, E2](r)(err, succ) - - @inline final def fromEither[R1 <: R, E1 >: E, A1](implicit ev: A <:< Either[E1, A1]): FR[R1, E1, A1] = F.flatMap[R1, E1, A, A1](r)(F.fromEither[E1, A1](_)) - @inline final def fromOption[R1 <: R, E1 >: E, A1](errorOnNone: => E1)(implicit ev1: A <:< Option[A1]): FR[R1, E1, A1] = F.fromOption(errorOnNone, r.widen) - - @inline final def retryWhile(f: E => Boolean): FR[R, E, A] = F.retryWhile(r)(f) - @inline final def retryWhileF[R1 <: R](f: E => FR[R1, Nothing, Boolean]): FR[R1, E, A] = F.retryWhileF(r)(f) - - @inline final def retryUntil(f: E => Boolean): FR[R, E, A] = F.retryUntil(r)(f) - @inline final def retryUntilF[R1 <: R](f: E => FR[R1, Nothing, Boolean]): FR[R1, E, A] = F.retryUntilF(r)(f) - - /** for-comprehensions sugar: - * - * {{{ - * for { - * (1, 2) <- F.pure((2, 1)) - * } yield () - * }}} - * - * Use [[widenError]] to for pattern matching with non-Throwable errors: - * - * {{{ - * val f = for { - * (1, 2) <- F.pure((2, 1)).widenError[Option[Unit]] - * } yield () - * // f: F[Option[Unit], Unit] = F.fail(Some(()) - * }}} - */ - @inline final def withFilter[E1 >: E](predicate: A => Boolean)(implicit filter: WithFilter[E1], pos: SourceFilePositionMaterializer): FR[R, E1, A] = - F.withFilter[R, E1, A](r)(predicate) - } - - class BracketOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: Bracket3[FR]) - extends ErrorOps(r)(F) { - @inline final def bracket[R1 <: R, E1 >: E, B](release: A => FR[R1, Nothing, Unit])(use: A => FR[R1, E1, B]): FR[R1, E1, B] = - F.bracket(r: FR[R1, E1, A])(release)(use) - - @inline final def bracketCase[R1 <: R, E1 >: E, B](release: (A, Exit[E1, B]) => FR[R1, Nothing, Unit])(use: A => FR[R1, E1, B]): FR[R1, E1, B] = - F.bracketCase(r: FR[R1, E1, A])(release)(use) - @inline final def guaranteeCase[R1 <: R](cleanup: Exit[E, A] => FR[R1, Nothing, Unit]): FR[R1, E, A] = F.guaranteeCase(r, cleanup) - - @inline final def bracketOnFailure[R1 <: R, E1 >: E, B](cleanupOnFailure: (A, Exit.Failure[E1]) => FR[R1, Nothing, Unit])(use: A => FR[R1, E1, B]): FR[R1, E1, B] = - F.bracketOnFailure(r: FR[R1, E1, A])(cleanupOnFailure)(use) - @inline final def guaranteeOnFailure[R1 <: R](cleanupOnFailure: Exit.Failure[E] => FR[R1, Nothing, Unit]): FR[R1, E, A] = F.guaranteeOnFailure(r, cleanupOnFailure) - @inline final def guaranteeOnInterrupt[R1 <: R](cleanupOnInterruption: Exit.Interruption => FR[R1, Nothing, Unit]): FR[R1, E, A] = - F.guaranteeOnInterrupt(r, cleanupOnInterruption) - @inline final def guaranteeExceptOnInterrupt[R1 <: R]( - cleanupOnNonInterruption: Either[Exit.Termination, Either[Exit.Error[E], Exit.Success[A]]] => FR[R1, Nothing, Unit] - ): FR[R1, E, A] = - F.guaranteeExceptOnInterrupt(r, cleanupOnNonInterruption) - } - - class PanicOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: Panic3[FR]) extends BracketOps(r)(F) { - @inline final def sandbox: FR[R, Exit.Failure[E], A] = F.sandbox(r) - @inline final def sandboxExit: FR[R, Nothing, Exit[E, A]] = F.sandboxExit(r) - - /** - * Catch all _defects_ in this effect and convert them to Throwable - * Example: - * - * {{{ - * F.pure(1) - * .map(_ => ???) - * .sandboxThrowable - * .catchAll(_ => IO3(println("Caught error!"))) - * }}} - */ - @inline final def sandboxToThrowable(implicit ev: E <:< Throwable): FR[R, Throwable, A] = - F.leftMap(F.sandbox(r))(_.toThrowable) - - /** Convert Throwable typed error into a defect */ - @inline final def orTerminate(implicit ev: E <:< Throwable): FR[R, Nothing, A] = F.catchAll(r)(F.terminate(_)) - @inline final def uninterruptible: FR[R, E, A] = F.uninterruptible(r) - } - - class IOOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: IO3[FR]) extends PanicOps(r)(F) { - @inline final def bracketAuto[R1 <: R, E1 >: E, B](use: A => FR[R1, E1, B])(implicit ev: A <:< AutoCloseable): FR[R1, E1, B] = - F.bracket(r: FR[R1, E1, A])(c => F.sync(c.close()))(use) - } - - class ParallelOps[FR[-_, +_, +_], -R, +E, +A](protected[this] val r: FR[R, E, A])(implicit protected[this] val F: Parallel3[FR]) { - @inline final def zipWithPar[R1 <: R, E1 >: E, B, C](that: FR[R1, E1, B])(f: (A, B) => C): FR[R1, E1, C] = F.zipWithPar(r, that)(f) - @inline final def zipPar[R1 <: R, E1 >: E, B](that: FR[R1, E1, B]): FR[R1, E1, (A, B)] = F.zipPar(r, that) - @inline final def zipParLeft[R1 <: R, E1 >: E, B](that: FR[R1, E1, B]): FR[R1, E1, A] = F.zipParLeft(r, that) - @inline final def zipParRight[R1 <: R, E1 >: E, B](that: FR[R1, E1, B]): FR[R1, E1, B] = F.zipParRight(r, that) - } - - class ConcurrentOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: Concurrent3[FR]) - extends ParallelOps(r)(F) { - @inline final def race[R1 <: R, E1 >: E, A1 >: A](that: FR[R1, E1, A1]): FR[R1, E1, A1] = F.race(r, that) - @inline final def racePairUnsafe[R1 <: R, E1 >: E, A1 >: A]( - that: FR[R1, E1, A1] - ): FR[R1, E1, Either[(Exit[E1, A], Fiber3[FR, E1, A1]), (Fiber3[FR, E1, A], Exit[E1, A1])]] = - F.racePairUnsafe(r, that) - } - - class AsyncOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: Async3[FR]) extends IOOps(r)(F) { - @inline final def zipWithPar[R1 <: R, E1 >: E, B, C](that: FR[R1, E1, B])(f: (A, B) => C): FR[R1, E1, C] = F.zipWithPar(r, that)(f) - @inline final def zipPar[R1 <: R, E1 >: E, B](that: FR[R1, E1, B]): FR[R1, E1, (A, B)] = F.zipPar(r, that) - @inline final def zipParLeft[R1 <: R, E1 >: E, B](that: FR[R1, E1, B]): FR[R1, E1, A] = F.zipParLeft(r, that) - @inline final def zipParRight[R1 <: R, E1 >: E, B](that: FR[R1, E1, B]): FR[R1, E1, B] = F.zipParRight(r, that) - - @inline final def race[R1 <: R, E1 >: E, A1 >: A](that: FR[R1, E1, A1]): FR[R1, E1, A1] = F.race(r, that) - @inline final def racePairUnsafe[R1 <: R, E1 >: E, A1 >: A]( - that: FR[R1, E1, A1] - ): FR[R1, E1, Either[(Exit[E1, A], Fiber3[FR, E1, A1]), (Fiber3[FR, E1, A], Exit[E1, A1])]] = - F.racePairUnsafe(r, that) - } - - final class TemporalOps[FR[-_, +_, +_], -R, +E, +A](protected[this] val r: FR[R, E, A])(implicit protected[this] val F: Temporal3[FR]) { - @inline final def repeatUntil[E1 >: E, A2](tooManyAttemptsError: => E1, sleep: FiniteDuration, maxAttempts: Int)(implicit ev: A <:< Option[A2]): FR[R, E1, A2] = - F.repeatUntil[R, E1, A2](new FunctorOps(r)(F.InnerF).widen)(tooManyAttemptsError, sleep, maxAttempts) - - @inline final def timeout(duration: Duration): FR[R, E, Option[A]] = F.timeout(duration)(r) - @inline final def timeoutFail[E1 >: E](e: => E1)(duration: Duration): FR[R, E1, A] = F.timeoutFail(duration)(e, r) - } - - final class ForkOps[FR[-_, +_, +_], -R, +E, +A](private val r: FR[R, E, A])(implicit private val F: Fork3[FR]) { - @inline final def fork: FR[R, Nothing, Fiber3[FR, E, A]] = F.fork(r) - } - - class ProfunctorOps[FR[-_, +_, +_], -R, +E, +A](protected[this] val r: FR[R, E, A])(implicit protected[this] val F: Profunctor3[FR]) { - @inline final def dimap[R1, A1](f: R1 => R)(g: A => A1): FR[R1, E, A1] = F.dimap(r)(f)(g) - } - - class ArrowOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: Arrow3[FR]) - extends ProfunctorOps(r)(F) { - @inline final def andThen[E1 >: E, A1](g: FR[A, E1, A1]): FR[R, E1, A1] = F.andThen(r, g) - @inline final def compose[E1 >: E, R1](g: FR[R1, E1, R]): FR[R1, E1, A] = F.andThen(g, r) - } - - class ArrowChoiceOps[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: ArrowChoice3[FR]) - extends ArrowOps(r)(F) { - @inline final def choice[R1 <: R, E1 >: E, A1 >: A, R2](g: FR[R2, E1, A1]): FR[Either[R1, R2], E1, A1] = F.choice(r, g) - @inline final def choose[R1 <: R, E1 >: E, R2, A1](g: FR[R2, E1, A1]): FR[Either[R1, R2], E1, Either[A, A1]] = F.choose(r, g) - } - - final class LocalOps[FR[-_, +_, +_], -R, +E, +A](protected[this] override val r: FR[R, E, A])(implicit override protected[this] val F: Local3[FR]) - extends ArrowChoiceOps(r)(F) { - @inline final def provide(env: => R): FR[Any, E, A] = F.provide(r)(env) - @inline final def contramap[R0 <: R](f: R0 => R): FR[R0, E, A] = F.contramap(r)(f) - } - - final class LocalOpsKleisliSyntax[FR[-_, +_, +_], R, E, A](private val r: FR[R, E, A])(implicit private val F: Local3[FR]) { - @inline final def toKleisli: Kleisli[FR[Any, E, _], R, A] = F.toKleisli(r) - } - - final class ClockAccessor[FR[-_, +_, +_]](@unused private val dummy: Boolean = false) extends AnyVal { - def clock(implicit clock: Clock3[FR]): clock.type = clock - } - - final class EntropyAccessor[FR[-_, +_, +_]](@unused private val dummy: Boolean = false) extends AnyVal { - def entropy(implicit entropy: Entropy3[FR]): entropy.type = entropy - } - - trait ImplicitPuns extends ImplicitPuns1 { - @inline implicit final def Temporal3[FR[-_, +_, +_]: Temporal3, R, E, A](self: FR[R, E, A]): TemporalOps[FR, R, E, A] = new TemporalOps[FR, R, E, A](self) - @inline implicit final def Temporal3[FR[-_, +_, +_]: Error3, R, E, A](self: FR[R, E, A]): ErrorOps[FR, R, E, A] = new ErrorOps[FR, R, E, A](self) - @inline final def Temporal3[FR[-_, +_, +_]: Temporal3]: Temporal3[FR] = implicitly - - @inline implicit final def Fork3[FR[-_, +_, +_]: Fork3, R, E, A](self: FR[R, E, A]): ForkOps[FR, R, E, A] = new ForkOps[FR, R, E, A](self) - @inline final def Fork3[FR[-_, +_, +_]: Fork3]: Fork3[FR] = implicitly - } - trait ImplicitPuns1 extends ImplicitPuns2 { - @inline implicit final def Async3[FR[-_, +_, +_]: Async3, R, E, A](self: FR[R, E, A]): AsyncOps[FR, R, E, A] = new AsyncOps[FR, R, E, A](self) - @inline final def Async3[FR[-_, +_, +_]: Async3]: Async3[FR] = implicitly - } - trait ImplicitPuns2 extends ImplicitPuns3 { - @inline implicit final def Concurrent3[FR[-_, +_, +_]: Concurrent3, R, E, A](self: FR[R, E, A]): ConcurrentOps[FR, R, E, A] = new ConcurrentOps[FR, R, E, A](self) - @inline implicit final def Concurrent3[FR[-_, +_, +_]: Panic3, R, E, A](self: FR[R, E, A]): PanicOps[FR, R, E, A] = new PanicOps[FR, R, E, A](self) - @inline final def Concurrent3[FR[-_, +_, +_]: Concurrent3]: Concurrent3[FR] = implicitly - } - trait ImplicitPuns3 extends ImplicitPuns4 { - @inline implicit final def Parallel3[FR[-_, +_, +_]: Parallel3, R, E, A](self: FR[R, E, A]): ParallelOps[FR, R, E, A] = new ParallelOps[FR, R, E, A](self) - @inline implicit final def Parallel3[F[-_, +_, +_]: Monad3, R, E, A](self: F[R, E, A]): MonadOps[F, R, E, A] = new MonadOps[F, R, E, A](self) - @inline final def Parallel3[FR[-_, +_, +_]: Parallel3]: Parallel3[FR] = implicitly - } - trait ImplicitPuns4 extends ImplicitPuns5 { - @inline implicit final def IO3[FR[-_, +_, +_]: IO3, R, E, A](self: FR[R, E, A]): IOOps[FR, R, E, A] = new IOOps[FR, R, E, A](self) - /** - * Shorthand for [[IO3#syncThrowable]] - * - * {{{ - * IO3(println("Hello world!")) - * }}} - */ - @inline final def IO3[FR[-_, +_, +_], A](effect: => A)(implicit F: IO3[FR]): FR[Any, Throwable, A] = F.syncThrowable(effect) - @inline final def IO3[FR[-_, +_, +_]: IO3]: IO3[FR] = implicitly - } - trait ImplicitPuns5 extends ImplicitPuns6 { - @inline implicit final def Panic3[FR[-_, +_, +_]: Panic3, R, E, A](self: FR[R, E, A]): PanicOps[FR, R, E, A] = new PanicOps[FR, R, E, A](self) - @inline final def Panic3[FR[-_, +_, +_]: Panic3]: Panic3[FR] = implicitly - } - trait ImplicitPuns6 extends ImplicitPuns7 { - @inline implicit final def Bracket3[FR[-_, +_, +_]: Bracket3, R, E, A](self: FR[R, E, A]): BracketOps[FR, R, E, A] = new BracketOps[FR, R, E, A](self) - @inline final def Bracket3[FR[-_, +_, +_]: Bracket3]: Bracket3[FR] = implicitly - } - trait ImplicitPuns7 extends ImplicitPuns8 { - @inline implicit final def Error3[FR[-_, +_, +_]: Error3, R, E, A](self: FR[R, E, A]): ErrorOps[FR, R, E, A] = new ErrorOps[FR, R, E, A](self) - @inline final def Error3[FR[-_, +_, +_]: Error3]: Error3[FR] = implicitly - } - trait ImplicitPuns8 extends ImplicitPuns9 { - @inline implicit final def ApplicativeError3[FR[-_, +_, +_]: ApplicativeError3, R, E, A](self: FR[R, E, A]): ApplicativeErrorOps[FR, R, E, A] = - new ApplicativeErrorOps[FR, R, E, A](self) - @inline final def ApplicativeError3[FR[-_, +_, +_]: ApplicativeError3]: ApplicativeError3[FR] = implicitly - } - trait ImplicitPuns9 extends ImplicitPuns10 { - @inline implicit final def Guarantee3[FR[-_, +_, +_]: Guarantee3, R, E, A](self: FR[R, E, A]): GuaranteeOps[FR, R, E, A] = new GuaranteeOps[FR, R, E, A](self) - @inline final def Guarantee3[FR[-_, +_, +_]: Guarantee3]: Guarantee3[FR] = implicitly - } - trait ImplicitPuns10 extends ImplicitPuns11 { - // Note, as long as these auxilary conversions to Monad/Applicative/Functor syntaxes etc. - // have the same output type as Monad3/etc conversions above, they will avoid the specificity rule - // and _will not_ clash (because the outputs are equal, not <:<). - // If you merge them into `LocalOps with MonadOps`, they _will_ start clashing - - @inline implicit final def Local3[FR[-_, +_, +_]: Local3, R, E, A](self: FR[R, E, A]): LocalOps[FR, R, E, A] = new LocalOps[FR, R, E, A](self) - @inline implicit final def Local3[FR[-_, +_, +_]: Monad3, R, E, A](self: FR[R, E, A]): MonadOps[FR, R, E, A] = new MonadOps[FR, R, E, A](self) - @inline implicit final def Local3[FR[-_, +_, +_]: Local3, R, E, A](self: FR[R, E, A])(implicit d: DummyImplicit): LocalOpsKleisliSyntax[FR, R, E, A] = - new LocalOpsKleisliSyntax[FR, R, E, A](self) - @inline final def Local3[FR[-_, +_, +_]: Local3]: Local3[FR] = implicitly - } - trait ImplicitPuns11 extends ImplicitPuns12 { - @inline implicit final def MonadAsk3[FR[-_, +_, +_]: Monad3, R, E, A](self: FR[R, E, A]): MonadOps[FR, R, E, A] = new MonadOps[FR, R, E, A](self) - @inline final def MonadAsk3[FR[-_, +_, +_]: MonadAsk3]: MonadAsk3[FR] = implicitly - } - trait ImplicitPuns12 extends ImplicitPuns13 { - @inline implicit final def Ask3[FR[-_, +_, +_]: Applicative3, R, E, A](self: FR[R, E, A]): ApplicativeOps[FR, R, E, A] = new ApplicativeOps[FR, R, E, A](self) - @inline final def Ask3[FR[-_, +_, +_]: Ask3]: Ask3[FR] = implicitly - } - trait ImplicitPuns13 extends ImplicitPuns14 { - @inline implicit final def ArrowChoice3[FR[-_, +_, +_]: ArrowChoice3, R, E, A](self: FR[R, E, A]): ArrowChoiceOps[FR, R, E, A] = new ArrowChoiceOps[FR, R, E, A](self) - @inline implicit final def ArrowChoice3[FR[-_, +_, +_]: Functor3, R, E, A](self: FR[R, E, A]): FunctorOps[FR, R, E, A] = new FunctorOps[FR, R, E, A](self) - @inline final def ArrowChoice3[FR[-_, +_, +_]: ArrowChoice3]: ArrowChoice3[FR] = implicitly - } - trait ImplicitPuns14 extends ImplicitPuns15 { - @inline implicit final def Arrow3[FR[-_, +_, +_]: Arrow3, R, E, A](self: FR[R, E, A]): ArrowOps[FR, R, E, A] = new ArrowOps[FR, R, E, A](self) - @inline implicit final def Arrow3[FR[-_, +_, +_]: Functor3, R, E, A](self: FR[R, E, A]): FunctorOps[FR, R, E, A] = new FunctorOps[FR, R, E, A](self) - @inline final def Arrow3[FR[-_, +_, +_]: Arrow3]: Arrow3[FR] = implicitly - } - trait ImplicitPuns15 extends ImplicitPuns16 { - @inline implicit final def Profunctor3[FR[-_, +_, +_]: Profunctor3, R, E, A](self: FR[R, E, A]): ProfunctorOps[FR, R, E, A] = new ProfunctorOps[FR, R, E, A](self) - @inline implicit final def Profunctor3[FR[-_, +_, +_]: Functor3, R, E, A](self: FR[R, E, A]): FunctorOps[FR, R, E, A] = new FunctorOps[FR, R, E, A](self) - @inline final def Profunctor3[FR[-_, +_, +_]: Profunctor3]: Profunctor3[FR] = implicitly - } - trait ImplicitPuns16 extends ImplicitPuns17 { - @inline implicit final def Monad3[FR[-_, +_, +_]: Monad3, R, E, A](self: FR[R, E, A]): MonadOps[FR, R, E, A] = new MonadOps[FR, R, E, A](self) - @inline final def Monad3[FR[-_, +_, +_]: Monad3]: Monad3[FR] = implicitly - } - trait ImplicitPuns17 extends ImplicitPuns18 { - @inline implicit final def Applicative3[FR[-_, +_, +_]: Applicative3, R, E, A](self: FR[R, E, A]): ApplicativeOps[FR, R, E, A] = new ApplicativeOps[FR, R, E, A](self) - @inline final def Applicative3[FR[-_, +_, +_]: Applicative3]: Applicative3[FR] = implicitly - } - trait ImplicitPuns18 extends ImplicitPuns19 { - @inline implicit final def Bifunctor3[FR[-_, +_, +_]: Bifunctor3, R, E, A](self: FR[R, E, A]): BifunctorOps[FR, R, E, A] = new BifunctorOps[FR, R, E, A](self) - @inline implicit final def Bifunctor3[FR[-_, +_, +_]: Functor3, R, E, A](self: FR[R, E, A]): FunctorOps[FR, R, E, A] = new FunctorOps[FR, R, E, A](self) - @inline final def Bifunctor3[FR[-_, +_, +_]: Bifunctor3]: Bifunctor3[FR] = implicitly - } - trait ImplicitPuns19 { - @inline implicit final def Functor3[FR[-_, +_, +_]: Functor3, R, E, A](self: FR[R, E, A]): FunctorOps[FR, R, E, A] = new FunctorOps[FR, R, E, A](self) - @inline final def Functor3[FR[-_, +_, +_]: Functor3]: Functor3[FR] = implicitly - } - -} diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/lifecycle/Lifecycle.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/lifecycle/Lifecycle.scala index cc08d8fc19..0fd283ec2d 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/lifecycle/Lifecycle.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/lifecycle/Lifecycle.scala @@ -5,7 +5,7 @@ import cats.effect.kernel import cats.effect.kernel.{GenConcurrent, Resource, Sync} import izumi.functional.quasi.* import izumi.functional.bio.data.Morphism1 -import izumi.functional.bio.{Fiber2, Fork2, Functor2, Functor3} +import izumi.functional.bio.{Fiber2, Fork2, Functor2, Monad2} import izumi.fundamentals.orphans.{`cats.Functor`, `cats.Monad`, `cats.kernel.Monoid`} import izumi.fundamentals.platform.functional.Identity import izumi.fundamentals.platform.language.Quirks.* @@ -925,18 +925,17 @@ object Lifecycle extends LifecycleInstances { } private[izumi] sealed trait LifecycleInstances extends LifecycleCatsInstances { - implicit final def functor2ForLifecycle[F[+_, +_]: Functor2]: Functor2[Lifecycle2[F, +_, +_]] = new Functor2[Lifecycle2[F, +_, +_]] { - override def map[R, E, A, B](r: Lifecycle[F[E, _], A])(f: A => B): Lifecycle[F[E, _], B] = r.map(f) - } - - implicit final def functor3ForLifecycle[F[-_, +_, +_]: Functor3]: Functor3[Lifecycle3[F, -_, +_, +_]] = new Functor3[Lifecycle3[F, -_, +_, +_]] { - override def map[R, E, A, B](r: Lifecycle[F[R, E, _], A])(f: A => B): Lifecycle[F[R, E, _], B] = r.map(f) - } + implicit final def monad2ForLifecycle[F[+_, +_]: Functor2](implicit P: QuasiPrimitives[F[Any, +_]]): Monad2[Lifecycle2[F, +_, +_]] = + new Monad2[Lifecycle2[F, +_, +_]] { + override def map[E, A, B](r: Lifecycle[F[E, _], A])(f: A => B): Lifecycle[F[E, _], B] = r.map(f) + override def flatMap[E, A, B](r: Lifecycle2[F, E, A])(f: A => Lifecycle2[F, E, B]): Lifecycle2[F, E, B] = r.flatMap(f)(P.asInstanceOf[QuasiPrimitives[F[E, +_]]]) + override def pure[A](a: A): Lifecycle2[F, Nothing, A] = Lifecycle.pure[F[Nothing, _]](a)(P.asInstanceOf[QuasiPrimitives[F[Nothing, +_]]]) + } } private[izumi] sealed trait LifecycleCatsInstances extends LifecycleCatsInstancesLowPriority { implicit final def catsMonadForLifecycle[Monad[_[_]]: `cats.Monad`, F[_]]( - implicit F: QuasiPrimitives[F] + implicit P: QuasiPrimitives[F] ): Monad[Lifecycle[F, _]] = { new cats.StackSafeMonad[Lifecycle[F, _]] { override def pure[A](x: A): Lifecycle[F, A] = Lifecycle.pure[F](x) diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/quasi/QuasiIO.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/quasi/QuasiIO.scala index f02034b871..d825b6ad2f 100644 --- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/quasi/QuasiIO.scala +++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/quasi/QuasiIO.scala @@ -196,7 +196,7 @@ private[quasi] sealed trait LowPriorityQuasiIOInstances extends LowPriorityQuasi } 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, E, A, B](acquire = F.suspend(acquire))(release = { + F.bracketCase[E, A, B](acquire = F.suspend(acquire))(release = { case (a, exit) => exit match { case Exit.Success(_) => release(a, None).orTerminate diff --git a/fundamentals/fundamentals-bio/src/test/scala/izumi/functional/bio/ErrorAccumulatingOpsTest.scala b/fundamentals/fundamentals-bio/src/test/scala/izumi/functional/bio/ErrorAccumulatingOpsTest.scala new file mode 100644 index 0000000000..b5aee73353 --- /dev/null +++ b/fundamentals/fundamentals-bio/src/test/scala/izumi/functional/bio/ErrorAccumulatingOpsTest.scala @@ -0,0 +1,118 @@ +package izumi.functional.bio + +import izumi.fundamentals.collections.nonempty.{NEList, NESet} +import org.scalatest.wordspec.AnyWordSpec + +import scala.annotation.{nowarn, unused} + +final class ErrorAccumulatingOpsTestEither extends ErrorAccumulatingOpsTest[Either] { + override implicit def F: Error2[Either] = Root.BIOEither + override def unsafeRun[E, A](f: Either[E, A]): Either[E, A] = f +} + +@nowarn("msg=Unused import") +abstract class ErrorAccumulatingOpsTest[F[+_, +_]] extends AnyWordSpec { + import scala.collection.compat.* + + type BuilderFail + type IzType + type Result[+T] = F[List[BuilderFail], T] + type TList = Result[List[IzType]] + + def listTList: List[TList] = Nil + def x(@unused t: TList): Result[Unit] = F.unit + + implicit def F: Error2[F] + def unsafeRun[E, A](f: F[E, A]): Either[E, A] + + implicit final class Run[+E, +A](f: F[E, A]) { + def run(): Either[E, A] = unsafeRun(f) + } + + "ErrorAccumulatingOps" should { + + "have biFlatAggregate callable with typealiases" in { + + def test: F[List[BuilderFail], Unit] = { + val ret = F.flatSequenceAccumErrors(listTList) + x(ret) + } + + assert(test.run().isRight) + } + + "support NonEmptyCollections" in { + val nel0 = List(F.pure(1), F.pure(2), F.fail(NEList("error"))) + + assert(implicitly[Factory[String, NEList[String]]] ne null) + assert(F.sequenceAccumErrors(nel0).run() == Left(NEList("error"))) + + val nes0 = List(F.pure(()), F.fail(NESet("error"))) + assert(F.sequenceAccumErrors(nes0).run() == Left(NESet("error"))) + + assert( + F.traverseAccumErrors_(nes0)(_.attempt.flatMap { + case Left(value) => F.fail(value + "error1") + case Right(value) => F.pure(value) + }).run() == Left(NESet("error", "error1")) + ) + + val nel1 = List(F.pure(List(1)), F.fail(NEList("error"))) + assert(F.flatSequenceAccumErrors(nel1).run() == Left(NEList("error"))) + + assert( + F.traverseAccumErrors(nel1)(_.attempt.flatMap { + case Left(value) => F.fail(Set(value ++ NEList("a"))) + case Right(value) => F.pure(Set(value ++ List(1))) + }).run() == Left(Set(NEList("error", "a"))) + ) + + val nel2 = List(F.pure(1), F.fail(NEList("error"))) + assert(F.flatTraverseAccumErrors(nel2)(_.map(i => List(i))).run() == Left(NEList("error"))) + assert(F.flatTraverseAccumErrors(nel2)(_.map(i => List(i))).map(_.to(Set)).run() == Left(NEList("error"))) + } + + "support the happy path" in { + val l0: Seq[F[List[String], Int]] = List(F.pure(1), F.pure(2), F.pure(3)) + assert(F.sequenceAccumErrors(l0).run() == Right(Seq(1, 2, 3))) + assert(F.sequenceAccumErrors_(l0).run() == Right(())) + assert(F.traverseAccumErrors(l0)(identity).run() == Right(Seq(1, 2, 3))) + assert(F.traverseAccumErrors(l0)(identity).map(_.to(List)).run() == Right(List(1, 2, 3))) + assert(F.traverseAccumErrors_(l0)(_.map(_ => ())).run() == Right(())) + + val l1: Seq[F[List[String], Seq[Int]]] = List(F.pure(Seq(1)), F.pure(Seq(2)), F.pure(Seq(3))) + assert(F.flatTraverseAccumErrors(l1)(identity).run() == Right(Seq(1, 2, 3))) + assert(F.flatTraverseAccumErrors(l1)(identity).map(_.to(List)).run() == Right(List(1, 2, 3))) + + assert(F.flatSequenceAccumErrors(l1).run() == Right(List(1, 2, 3))) + + val l2: Seq[F[String, Int]] = List(F.pure(1), F.pure(2), F.pure(3), F.fail("error")) + assert(F.sequenceAccumErrorsNEList(l2).run() == Left(NEList("error"))) + + val l3: Seq[F[List[String], Int]] = List(F.pure(1), F.pure(2), F.pure(3), F.fail(List("error"))) + assert(F.sequenceAccumErrors(l3).run() == Left(List("error"))) + + assert(Right(Seq(1, 2, 3)).map(_.to(List)) == Right(List(1, 2, 3))) + } + + "support search" in { + assert(F.find(List(1, 2, 3))(i => F.pure(i == 2)).run() == Right(Some(2))) + assert(F.find(List(1, 2, 3))(i => F.pure(i == 4)).run() == Right(None)) + assert(F.find(List(1, 2, 3))(_ => F.fail("error")).run() == Left("error")) + } + + "support fold" in { + assert(F.foldLeft(List(1, 2, 3))("") { case (acc, v) => F.pure(s"$acc.$v") }.run() == Right(".1.2.3")) + assert(F.foldLeft(List(1, 2, 3))("") { case (_, _) => F.fail("error") }.run() == Left("error")) + } + + "support partitioning" in { + val lst = List(F.pure(1), F.pure(2), F.fail(3)) + val Right((l, r)) = F.partition(lst).run(): @unchecked + assert(l == List(3)) + assert(r == List(1, 2)) + } + + } + +} diff --git a/fundamentals/fundamentals-collections/src/main/scala/izumi/functional/IzEither.scala b/fundamentals/fundamentals-collections/src/main/scala/izumi/functional/IzEither.scala index 29f1eb81d4..8eed4bd3bc 100644 --- a/fundamentals/fundamentals-collections/src/main/scala/izumi/functional/IzEither.scala +++ b/fundamentals/fundamentals-collections/src/main/scala/izumi/functional/IzEither.scala @@ -85,10 +85,10 @@ object IzEither extends IzEither { } final class EitherBiAggregate[L, R, ColL[_], ColR[x] <: IterableOnce[x]](private val col: ColR[Either[ColL[L], R]]) extends AnyVal { - /** `sequence` with error accumulation */ @deprecated("use .biSequence") def biAggregate(implicit iterL: ColL[L] => IterableOnce[L], buildR: Factory[R, ColR[R]], buildL: Factory[L, ColL[L]]): Either[ColL[L], ColR[R]] = { biSequence } + /** `sequence` with error accumulation */ def biSequence(implicit iterL: ColL[L] => IterableOnce[L], buildR: Factory[R, ColR[R]], buildL: Factory[L, ColL[L]]): Either[ColL[L], ColR[R]] = { val good = buildR.newBuilder col.accumulateErrors(identity, (l: ColL[L]) => iterL(l), (v: R) => good += v, () => good.result()) @@ -105,16 +105,16 @@ object IzEither extends IzEither { } } - /** `sequence` with error accumulation */ final class EitherScalarOps[L, R, ColR[x] <: IterableOnce[x]](private val col: ColR[Either[L, R]]) extends AnyVal { @deprecated("use .biSequenceScalar") def biAggregateScalar(implicit buildR: Factory[R, ColR[R]]): Either[NEList[L], ColR[R]] = { biSequenceScalar } + /** `sequence` with error accumulation */ def biSequenceScalar(implicit buildR: Factory[R, ColR[R]]): Either[NEList[L], ColR[R]] = { val good = buildR.newBuilder - col.accumulateErrors(identity, (l: L) => Seq(l), (v: R) => good ++= Seq(v), () => good.result()) + col.accumulateErrors(identity, (l: L) => Seq(l), (v: R) => good += v, () => good.result()) } } @@ -175,8 +175,6 @@ object IzEither extends IzEither { } final class EitherBiFlatMapAggregate[ColR[x] <: IterableOnce[x], T](private val col: ColR[T]) extends AnyVal { - /** `flatTraverse` with error accumulation */ - @deprecated("use .biFlatTraverse") def biFlatMapAggregate[ColL[_], L, A]( f: T => Either[ColL[L], IterableOnce[A]] @@ -187,6 +185,7 @@ object IzEither extends IzEither { biFlatTraverse(f) } + /** `flatTraverse` with error accumulation */ def biFlatTraverse[ColL[_], L, A]( f: T => Either[ColL[L], IterableOnce[A]] )(implicit buildR: Factory[A, ColR[A]], @@ -211,11 +210,11 @@ object IzEither extends IzEither { final class EitherBiFlatAggregate[L, R, ColR[x] <: IterableOnce[x], ColIn[x] <: IterableOnce[x], ColL[_]](private val col: ColR[Either[ColL[L], ColIn[R]]]) extends AnyVal { - /** `flatSequence` with error accumulation */ @deprecated("use .biFlatten") def biFlatAggregate(implicit buildR: Factory[R, ColR[R]], buildL: Factory[L, ColL[L]], iterL: ColL[L] => IterableOnce[L]): Either[ColL[L], ColR[R]] = biFlatten + /** `flatSequence` with error accumulation */ def biFlatten(implicit buildR: Factory[R, ColR[R]], buildL: Factory[L, ColL[L]], iterL: ColL[L] => IterableOnce[L]): Either[ColL[L], ColR[R]] = { col.biFlatTraverse(identity) } @@ -238,7 +237,7 @@ object IzEither extends IzEither { Right(None) } - /** monadic `foldLeft` with error accumulation */ + /** monadic `foldLeft` with short-circuiting error */ def biFoldLeft[E, A](z: A)(op: (A, T) => Either[E, A]): Either[E, A] = { val i = col.iterator var acc: Either[E, A] = Right(z) diff --git a/fundamentals/fundamentals-collections/src/test/scala/izumi/functional/IzEitherTest.scala b/fundamentals/fundamentals-collections/src/test/scala/izumi/functional/IzEitherTest.scala index eb5015f918..1ffc951e37 100644 --- a/fundamentals/fundamentals-collections/src/test/scala/izumi/functional/IzEitherTest.scala +++ b/fundamentals/fundamentals-collections/src/test/scala/izumi/functional/IzEitherTest.scala @@ -32,7 +32,7 @@ class IzEitherTest extends AnyWordSpec { "support NonEmptyCollections" in { val nel0 = List(Right(1), Right(2), Left(NEList("error"))) - implicitly[Factory[String, NEList[String]]] + assert(implicitly[Factory[String, NEList[String]]] ne null) assert(nel0.biSequence == Left(NEList("error"))) val nes0 = List(Right(()), Left(NESet("error"))) diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/LogRouter.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/LogRouter.scala index 59a9f2f4ca..86d427cb44 100644 --- a/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/LogRouter.scala +++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/LogRouter.scala @@ -16,10 +16,10 @@ trait LogRouter extends AutoCloseable { object LogRouter { def apply( - threshold: Log.Level = Log.Level.Trace, - sink: LogSink = ConsoleSink.ColoredConsoleSink, - levels: Map[String, Log.Level] = Map.empty, - buffer: LogQueue = LogQueue.Immediate, + threshold: Log.Level = Log.Level.Trace, + sink: LogSink = ConsoleSink.ColoredConsoleSink, + levels: Map[String, Log.Level] = Map.empty, + buffer: LogQueue = LogQueue.Immediate, ): ConfigurableLogRouter = { ConfigurableLogRouter(threshold, Seq(sink), levels, buffer) } diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/routing/ConfigurableLogRouter.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/routing/ConfigurableLogRouter.scala index a7d353f08e..a614133d87 100644 --- a/logstage/logstage-core/src/main/scala/izumi/logstage/api/routing/ConfigurableLogRouter.scala +++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/routing/ConfigurableLogRouter.scala @@ -41,10 +41,10 @@ class ConfigurableLogRouter( object ConfigurableLogRouter { def apply( - threshold: Log.Level = Log.Level.Trace, - sink: LogSink = ConsoleSink.ColoredConsoleSink, - levels: Map[String, Log.Level] = Map.empty, - buffer: LogQueue = LogQueue.Immediate, + threshold: Log.Level = Log.Level.Trace, + sink: LogSink = ConsoleSink.ColoredConsoleSink, + levels: Map[String, Log.Level] = Map.empty, + buffer: LogQueue = LogQueue.Immediate, ): ConfigurableLogRouter = { ConfigurableLogRouter(threshold, Seq(sink), levels, buffer) } diff --git a/logstage/logstage-core/src/main/scala/logstage/LogIO.scala b/logstage/logstage-core/src/main/scala/logstage/LogIO.scala index 284bb51b1f..596f8d5d08 100644 --- a/logstage/logstage-core/src/main/scala/logstage/LogIO.scala +++ b/logstage/logstage-core/src/main/scala/logstage/LogIO.scala @@ -1,12 +1,11 @@ package logstage -import izumi.functional.bio.{Error2, MonadAsk3, Panic2, SyncSafe1, SyncSafe2, SyncSafe3} +import izumi.functional.bio.{Error2, Panic2, SyncSafe1, SyncSafe2, SyncSafe3} import izumi.fundamentals.platform.language.CodePositionMaterializer import izumi.logstage.api.Log.* import izumi.logstage.api.logger import izumi.logstage.api.logger.{AbstractLogger, AbstractLoggerF, AbstractMacroLogIO} import izumi.logstage.api.rendering.{AnyEncoded, RenderingPolicy} -import logstage.LogIO3Ask.LogIO3AskImpl import logstage.UnsafeLogIO.{UnsafeLogIOSyncSafeInstance, UnsafeLogIOSyncSafeInstanceF} import scala.annotation.unused @@ -68,10 +67,6 @@ object LogIO extends LowPriorityLogIOInstances { } } - // FIXME wtf -// implicit def fromBIOMonadAsk[F[-_, +_, +_]: MonadAsk3](implicit t: Tag[LogIO3[F]]): LogIO3Ask[F] = new LogIO3AskImpl[F](_.get[LogIO3[F]](implicitly, t)) - implicit def fromBIOMonadAsk[F[-_, +_, +_]: MonadAsk3]: LogIO3Ask[F] = new LogIO3AskImpl[F](identity) - implicit def covarianceConversion[G[_], F[_]](log: LogIO[F])(implicit ev: F[AnyRef] <:< G[AnyRef]): LogIO[G] = log.widen implicit final class LogIO2Syntax[F[+_, +_]](private val log: LogIO2[F]) extends AnyVal { diff --git a/logstage/logstage-core/src/main/scala/logstage/LogIO3Ask.scala b/logstage/logstage-core/src/main/scala/logstage/LogIO3Ask.scala deleted file mode 100644 index ad6d88449f..0000000000 --- a/logstage/logstage-core/src/main/scala/logstage/LogIO3Ask.scala +++ /dev/null @@ -1,59 +0,0 @@ -package logstage - -import izumi.functional.bio.MonadAsk3 -import izumi.fundamentals.platform.language.CodePositionMaterializer -import izumi.logstage.api.Log.CustomContext - -object LogIO3Ask { - type Service[F[_, _, _]] = LogIO3[F] - - @inline def apply[F[_, _, _]: LogIO3Ask]: LogIO3Ask[F] = implicitly - - /** - * Lets you carry LogIO3 capability in environment - * - * {{{ - * import logstage.{LogIO3, LogIO3Ask} - * import logstage.LogIO3Ask.log - * import zio.ZIO - * - * def fn[F[-_, +_, +_]: LogIO3Ask]: F[LogIO3Ask.Service[F], Unit] = { - * log.info(s"I'm logging with ${log}stage!") - * } - * - * fn[ZIO] - * }}} - */ - @inline def log[F[-_, +_, +_]](implicit l: LogIO3Ask[F]): l.type = l - - //FIXME wtf -// @inline def make[F[-_, +_, +_]: MonadAsk3](implicit t: Tag[LogIO3[F]]): LogIO3Ask[F] = LogIO.fromBIOMonadAsk - @inline def make[F[-_, +_, +_]: MonadAsk3]: LogIO3Ask[F] = LogIO.fromBIOMonadAsk - - class LogIO3AskImpl[F[-_, +_, +_]]( - // FIXME wtf -// get: Has[LogIO3[F]] => LogIO3[F] - get: LogIO3[F] => LogIO3[F] - )(implicit - F: MonadAsk3[F] - ) extends LogIO3Ask[F] { - override final def log(entry: Log.Entry): F[LogIO3[F], Nothing, Unit] = - F.access(get(_).log(entry)) - override final def log(logLevel: Level)(messageThunk: => Log.Message)(implicit pos: CodePositionMaterializer): F[LogIO3[F], Nothing, Unit] = - F.access(get(_).log(logLevel)(messageThunk)) - override final def unsafeLog(entry: Log.Entry): F[LogIO3[F], Nothing, Unit] = - F.access(get(_).log(entry)) - override final def acceptable(loggerId: Log.LoggerId, logLevel: Level): F[LogIO3[F], Nothing, Boolean] = - F.access(get(_).acceptable(loggerId, logLevel)) - override final def acceptable(logLevel: Level)(implicit pos: CodePositionMaterializer): F[LogIO3[F], Nothing, Boolean] = - F.access(get(_).acceptable(logLevel)) - override final def createEntry(logLevel: Level, message: Log.Message)(implicit pos: CodePositionMaterializer): F[LogIO3[F], Nothing, Log.Entry] = - F.access(get(_).createEntry(logLevel, message)) - override final def createContext(logLevel: Level, customContext: CustomContext)(implicit pos: CodePositionMaterializer): F[LogIO3[F], Nothing, Log.Context] = - F.access(get(_).createContext(logLevel, customContext)) - override final def withCustomContext(context: CustomContext): LogIO2[F[LogIO3[F], _, _]] = { - new LogIO3AskImpl[F](get(_).withCustomContext(context)) - } - } - -} diff --git a/logstage/logstage-core/src/main/scala/logstage/LogZIO.scala b/logstage/logstage-core/src/main/scala/logstage/LogZIO.scala index 52976055f9..1cbb58d31f 100644 --- a/logstage/logstage-core/src/main/scala/logstage/LogZIO.scala +++ b/logstage/logstage-core/src/main/scala/logstage/LogZIO.scala @@ -25,9 +25,6 @@ object LogZIO { * } * }}} */ - // FIXME wtf -// object log extends LogIO3Ask.LogIO3AskImpl[ZIO](_.get[LogIO3[ZIO]]) -// object log extends LogIO3Ask.LogIO3AskImpl[ZIO](identity) object log extends LogZIOImpl(identity) private[LogZIO] class LogZIOImpl(get: LogIO3[ZIO] => LogIO3[ZIO]) extends LogIO3Ask[ZIO] { diff --git a/logstage/logstage-core/src/main/scala/logstage/strict/LogIO3AskStrict.scala b/logstage/logstage-core/src/main/scala/logstage/strict/LogIO3AskStrict.scala deleted file mode 100644 index 8700686aee..0000000000 --- a/logstage/logstage-core/src/main/scala/logstage/strict/LogIO3AskStrict.scala +++ /dev/null @@ -1,78 +0,0 @@ -package logstage.strict - -import izumi.functional.bio.MonadAsk3 -import izumi.fundamentals.platform.language.CodePositionMaterializer -import izumi.logstage.api.Log -import izumi.logstage.api.Log.{CustomContext, Level} - -object LogIO3AskStrict { - @inline def apply[F[_, _, _]: LogIO3AskStrict]: LogIO3AskStrict[F] = implicitly - - /** - * Lets you carry LogIO3 capability in environment - * - * {{{ - * import logstage.{LogIO3Strict, LogIO3AskStrict} - * import logstage.LogIO3AskStrict.log - * import zio.{Has. ZIO} - * - * class Service[F[-_, +_, +_]: LogIO3AskStrict] { - * val fn: F[Has[LogIO3Strict[F]], Nothing, Unit] = { - * log.info(s"I'm logging with ${log}stage!") - * } - * } - * - * new Service[ZIO](LogIO3AskStrict.make) - * }}} - */ - // FIXME wtf -// @inline def make[F[-_, +_, +_]: MonadAsk3](implicit t: Tag[LogIO3Strict[F]]): LogIO3AskStrict[F] = new LogIO3AskStrictImpl[F](_.get[LogIO3Strict[F]]) - @inline def make[F[-_, +_, +_]: MonadAsk3]: LogIO3AskStrict[F] = new LogIO3AskStrictImpl[F](identity) - - /** - * Lets you carry LogIO3 capability in environment - * - * {{{ - * import logstage.{LogIO3Strict, LogIO3AskStrict} - * import logstage.LogIO3AskStrict.log - * import zio.{Has. ZIO} - * - * class Service[F[-_, +_, +_]: LogIO3AskStrict] { - * val fn: F[Has[LogIO3Strict[F]], Nothing, Unit] = { - * log.info(s"I'm logging with ${log}stage!") - * } - * } - * - * new Service[ZIO](LogIO3AskStrict.make) - * }}} - */ - @inline def log[F[-_, +_, +_]](implicit l: LogIO3AskStrict[F]): l.type = l - - class LogIO3AskStrictImpl[F[-_, +_, +_]]( - get: LogIO3Strict[F] => LogIO3Strict[F] - )(implicit - F: MonadAsk3[F] - ) extends LogIO3AskStrict[F] { - override final def log(entry: Log.Entry): F[LogIO3Strict[F], Nothing, Unit] = - F.access(get(_).log(entry)) - override final def log(logLevel: Level)(messageThunk: => Log.Message)(implicit pos: CodePositionMaterializer): F[LogIO3Strict[F], Nothing, Unit] = - F.access(get(_).log(logLevel)(messageThunk)) - override final def unsafeLog(entry: Log.Entry): F[LogIO3Strict[F], Nothing, Unit] = - F.access(get(_).log(entry)) - override final def acceptable(loggerId: Log.LoggerId, logLevel: Level): F[LogIO3Strict[F], Nothing, Boolean] = - F.access(get(_).acceptable(loggerId, logLevel)) - override final def acceptable(logLevel: Level)(implicit pos: CodePositionMaterializer): F[LogIO3Strict[F], Nothing, Boolean] = - F.access(get(_).acceptable(logLevel)) - override final def createEntry(logLevel: Level, message: Log.Message)(implicit pos: CodePositionMaterializer): F[LogIO3Strict[F], Nothing, Log.Entry] = - F.access(get(_).createEntry(logLevel, message)) - override final def createContext( - logLevel: Level, - customContext: CustomContext, - )(implicit pos: CodePositionMaterializer - ): F[LogIO3Strict[F], Nothing, Log.Context] = - F.access(get(_).createContext(logLevel, customContext)) - override final def withCustomContext(context: CustomContext): LogIO2Strict[F[LogIO3Strict[F], _, _]] = { - new LogIO3AskStrictImpl[F](get(_).withCustomContext(context)) - } - } -} diff --git a/logstage/logstage-core/src/main/scala/logstage/strict/LogZIOStrict.scala b/logstage/logstage-core/src/main/scala/logstage/strict/LogZIOStrict.scala index 01fb502723..a9459a871a 100644 --- a/logstage/logstage-core/src/main/scala/logstage/strict/LogZIOStrict.scala +++ b/logstage/logstage-core/src/main/scala/logstage/strict/LogZIOStrict.scala @@ -23,9 +23,6 @@ object LogZIOStrict { * } * }}} */ - // FIXME wtf -// object log extends LogIO3AskStrictImpl[ZIO](_.get[LogIO3Strict[ZIO]]) -// object log extends LogIO3AskStrictImpl[ZIO](identity) def withFiberIdStrict(logger: AbstractLogger): LogIO2Strict[IO] = { new WrappedLogIOStrict[IO[Nothing, _]](logger)(SyncSafe2[IO]) { diff --git a/logstage/logstage-core/src/main/scala/logstage/strict/LogstageStrict.scala b/logstage/logstage-core/src/main/scala/logstage/strict/LogstageStrict.scala index c97b200b79..9838fa952b 100644 --- a/logstage/logstage-core/src/main/scala/logstage/strict/LogstageStrict.scala +++ b/logstage/logstage-core/src/main/scala/logstage/strict/LogstageStrict.scala @@ -12,8 +12,6 @@ trait LogstageStrict { val LogIOStrict2: LogIO2Strict.type = LogIO2Strict type LogIOStrict3[F[_, _, _]] = LogIO3Strict[F] val LogIOStrict3: LogIO3Strict.type = LogIO3Strict - type LogIOStrict3Ask[F[_, _, _]] = LogIO3AskStrict[F] - val LogIOStrict3Ask: LogIO3AskStrict.type = LogIO3AskStrict type LogZIOStrict = LogIO3Strict[ZIO] diff --git a/logstage/logstage-core/src/main/scala/logstage/strict/package.scala b/logstage/logstage-core/src/main/scala/logstage/strict/package.scala index 265618b1b8..4050149275 100644 --- a/logstage/logstage-core/src/main/scala/logstage/strict/package.scala +++ b/logstage/logstage-core/src/main/scala/logstage/strict/package.scala @@ -12,8 +12,6 @@ package object strict extends LogstageStrict { override val LogIOStrict2: LogIO2Strict.type = LogIO2Strict override type LogIOStrict3[F[_, _, _]] = LogIO3Strict[F] override val LogIOStrict3: LogIO3Strict.type = LogIO3Strict - override type LogIOStrict3Ask[F[_, _, _]] = LogIO3AskStrict[F] - override val LogIOStrict3Ask: LogIO3AskStrict.type = LogIO3AskStrict override type LogZIOStrict = LogIO3Strict[ZIO] diff --git a/logstage/logstage-core/src/test/scala/logstage/LogZIOSpec.scala b/logstage/logstage-core/src/test/scala/logstage/LogZIOSpec.scala index b8510f0756..c6061e9031 100644 --- a/logstage/logstage-core/src/test/scala/logstage/LogZIOSpec.scala +++ b/logstage/logstage-core/src/test/scala/logstage/LogZIOSpec.scala @@ -116,22 +116,6 @@ class LogZIOSpec extends AnyWordSpec { ) } - // FIXME wtf -// "doc example works" in { -// import logstage.LogIO3Ask.log -// import zio.ZIO -// -// def fn[F[-_, +_, +_]: LogIO3Ask]: F[LogIO3[F], Nothing, Unit] = { -// log.info(s"I'm logging with ${log}stage!"): @nowarn -// } -// -// val logger = LogIO3.fromLogger(IzLogger()) -// -// zio.Runtime.default.unsafeRun { -// fn[ZIO].provide(Has(logger)) -// } -// } - } private def withTestSink[U](thunk: RIO[LogZIO, U]): TestSink = { diff --git a/project/Deps.sc b/project/Deps.sc index a9ddc28603..29709017ef 100644 --- a/project/Deps.sc +++ b/project/Deps.sc @@ -149,7 +149,7 @@ object Izumi { // DON'T REMOVE, these variables are read from CI build (build.sh) final val scala212 = ScalaVersion("2.12.18") - final val scala213 = ScalaVersion("2.13.11") + final val scala213 = ScalaVersion("2.13.12") final val scala300 = ScalaVersion("3.3.1") object Groups { @@ -304,6 +304,7 @@ object Izumi { "scalacOptions" += "-Wconf:msg=legacy-binding:silent", "scalacOptions" += "-Wconf:msg=nowarn:silent", "scalacOptions" += "-Wconf:msg=parameter.*x\\\\$4.in.anonymous.function.is.never.used:silent", + "scalacOptions" += "-Wconf:msg=constructor.modifiers.are.assumed.by.synthetic.*method:silent", "scalacOptions" += "-Wconf:msg=package.object.inheritance:silent", "scalacOptions" += "-Wconf:cat=lint-eta-sam:silent", "scalacOptions" in SettingScope.Raw("Compile / sbt.Keys.doc") -= "-Wconf:any:error", @@ -401,6 +402,11 @@ object Izumi { "fork" in (SettingScope.Test, Platform.Jvm) := true ) + private val disableScaladocOnScala3 = ("sources" in SettingScope.Raw("Compile / doc")) := Seq( + SettingKey(Some(scala300), None) := Const.EmptySeq, + SettingKey.Default := "(Compile / doc / sources).value".raw, + ) + final lazy val fundamentals = Aggregate( name = Projects.fundamentals.id, artifacts = Seq( @@ -430,10 +436,7 @@ object Izumi { libs = allMonadsOptional ++ Seq(zio_interop_cats in Scope.Optional.all), depends = Seq.empty, settings = Seq( - ("sources" in SettingScope.Raw("Compile / doc")) := Seq( - SettingKey(Some(scala300), None) := Const.EmptySeq, - SettingKey.Default := "(Compile / doc / sources).value".raw, - ) + disableScaladocOnScala3 ), ), Artifact( @@ -503,6 +506,12 @@ object Izumi { depends = Seq( Projects.fundamentals.language, Projects.fundamentals.orphans, + Projects.fundamentals.collections, + ), + settings = Seq( + // DottyDoc crashes on fundamentals-bio (https://github.com/lampepfl/dotty/issues/18832) + // since trifunctor was removed (d3deae9aa3aed329dff03fa3b531d33843a5982a) + disableScaladocOnScala3 ), ), ), @@ -767,8 +776,10 @@ object Izumi { (ghpagesRepository.value / "paradox.json").getCanonicalPath == f.getCanonicalPath || (ghpagesRepository.value / "CNAME").getCanonicalPath == f.getCanonicalPath || (ghpagesRepository.value / ".nojekyll").getCanonicalPath == f.getCanonicalPath || - (ghpagesRepository.value / "index.html").getCanonicalPath == f.getCanonicalPath || - (ghpagesRepository.value / "README.md").getCanonicalPath == f.getCanonicalPath + (ghpagesRepository.value / "README.md").getCanonicalPath == f.getCanonicalPath || ( + f.toPath.getParent.toAbsolutePath == (ghpagesRepository.value / "index.html").toPath.getParent.toAbsolutePath && + f.getCanonicalPath.endsWith(".html") + ) } }""" ), diff --git a/project/Versions.scala b/project/Versions.scala index 4f786b391d..fad1f2ff1a 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -7,10 +7,10 @@ object V { val kind_projector = "0.13.2" - val scalatest = "3.2.16" + val scalatest = "3.2.17" val cats = "2.8.0" - val cats_effect = "3.4.11" + val cats_effect = "3.5.2" val discipline = "1.5.1" val discipline_scalatest = "2.2.0" @@ -21,23 +21,23 @@ object V { val monix = "3.4.0" val monix_bio = "1.2.0" - val circe = "0.14.5" + val circe = "0.14.6" val circe_derivation = "0.13.0-M5" val pureconfig = "0.17.4" - val magnolia = "1.1.3" + val magnolia = "1.1.6" val jawn = "1.5.1" // good to drop - scala val scala_java_time = "2.5.0" // java-only dependencies below - val classgraph = "4.8.160" - val slf4j = "2.0.7" + val classgraph = "4.8.165" + val slf4j = "2.0.9" val typesafe_config = "1.4.0" // good to drop - java - val bytebuddy = "1.14.5" - val docker_java = "3.3.1" + val bytebuddy = "1.14.10" + val docker_java = "3.3.4" // microsite-only val doobie = "1.0.0-RC2" diff --git a/project/build.properties b/project/build.properties index 62499c3df2..b19d4e1ed7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.9.1 +sbt.version = 1.9.7 diff --git a/project/project/PluginVersions.scala b/project/project/PluginVersions.scala index 0da3c346c0..7b10c9e02b 100644 --- a/project/project/PluginVersions.scala +++ b/project/project/PluginVersions.scala @@ -1,13 +1,13 @@ object PV { - val scala_js_version = "1.13.0" + val scala_js_version = "1.14.0" - val sbt_mdoc = "2.3.6" + val sbt_mdoc = "2.5.1" val sbt_paradox_material_theme = "0.6.0" - val sbt_paradox = "0.10.3" + val sbt_paradox = "0.10.5" val sbt_ghpages = "0.8.0" val sbt_site = "1.4.1" val sbt_unidoc = "0.4.3" - val sbt_scoverage = "2.0.8" + val sbt_scoverage = "2.0.9" val sbt_pgp = "2.1.1" val sbt_assembly = "0.14.9" } diff --git a/version.sbt b/version.sbt index 7378fbfeae..a9354972e6 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -ThisBuild / version := "1.1.1-SNAPSHOT" +ThisBuild / version := "1.2.4-SNAPSHOT"