From 397979710313ccebb21d35387b7776ca8bbdc91d Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 23 Oct 2025 20:49:35 +0300 Subject: [PATCH 01/39] Add a ` TypelevelMTLSubmarine ` rule --- .github/workflows/ci.yml | 6 +- .scalafmt.conf | 6 + README.md | 34 + build.sbt | 19 +- .../main/scala-2/fix/MTLSubmarineTest.scala | 474 +++++++++++++ .../main/scala-3/fix/MTLSubmarineTest.scala | 658 ++++++++++++++++++ .../META-INF/services/scalafix.v1.Rule | 1 + .../org/typelevel/fix/MTLSubmarine.scala | 219 ++++++ .../scala/org/typelevel/fix/RuleSuite.scala | 24 + 9 files changed, 1436 insertions(+), 5 deletions(-) create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala create mode 100644 modules/mtl/rules/src/main/resources/META-INF/services/scalafix.v1.Rule create mode 100644 modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala create mode 100644 modules/mtl/tests/src/test/scala/org/typelevel/fix/RuleSuite.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f980df..7aab5d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,11 +74,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p modules/fs2/rules/target target/rules-aggregate/target modules/cats-effect/rules/target modules/http4s/rules/target modules/cats/rules/target project/target + run: mkdir -p modules/fs2/rules/target target/rules-aggregate/target modules/cats-effect/rules/target modules/http4s/rules/target modules/mtl/rules/target modules/cats/rules/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar modules/fs2/rules/target target/rules-aggregate/target modules/cats-effect/rules/target modules/http4s/rules/target modules/cats/rules/target project/target + run: tar cf targets.tar modules/fs2/rules/target target/rules-aggregate/target modules/cats-effect/rules/target modules/http4s/rules/target modules/mtl/rules/target modules/cats/rules/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') @@ -195,7 +195,7 @@ jobs: - name: Submit Dependencies uses: scalacenter/sbt-dependency-submission@v2 with: - modules-ignore: cats-output_2.13 cats-output_2.12 http4s_2.13 http4s_2.12 fs2_2.13 fs2_2.12 cats-effect_2.13 cats-effect_2.12 cats-tests_2.13 cats-tests_2.12 fs2-tests_2.13 fs2-tests_2.12 cats-effect-tests_2.13 cats-effect-tests_2.12 typelevel-scalafix_2.13 typelevel-scalafix_2.12 fs2-output_2.13 fs2-output_2.12 fs2-input_2.13 fs2-input_2.12 cats-effect-input_2.13 cats-effect-input_2.12 http4s-tests_2.13 http4s-tests_2.12 http4s-input_2.13 http4s-input_2.12 cats-effect-output_2.13 cats-effect-output_2.12 http4s-output_2.13 http4s-output_2.12 cats_2.13 cats_2.12 cats-input_2.13 cats-input_2.12 + modules-ignore: mtl-output_2.13 mtl-output_2.12 cats-output_2.13 cats-output_2.12 http4s_2.13 http4s_2.12 fs2_2.13 fs2_2.12 mtl-tests_2.13 mtl-tests_2.12 cats-effect_2.13 cats-effect_2.12 cats-tests_2.13 cats-tests_2.12 fs2-tests_2.13 fs2-tests_2.12 cats-effect-tests_2.13 cats-effect-tests_2.12 typelevel-scalafix_2.13 typelevel-scalafix_2.12 fs2-output_2.13 fs2-output_2.12 fs2-input_2.13 fs2-input_2.12 cats-effect-input_2.13 cats-effect-input_2.12 http4s-tests_2.13 http4s-tests_2.12 mtl-input_2.13 mtl-input_3 http4s-input_2.13 http4s-input_2.12 mtl_2.13 mtl_2.12 cats-effect-output_2.13 cats-effect-output_2.12 http4s-output_2.13 http4s-output_2.12 cats_2.13 cats_2.12 cats-input_2.13 cats-input_2.12 configs-ignore: test scala-tool scala-doc-tool test-internal validate-steward: diff --git a/.scalafmt.conf b/.scalafmt.conf index 9c7b741..0b77021 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -11,3 +11,9 @@ indent { defnSite = 2 extendSite = 2 } + +fileOverride { + "glob:**/scala-3/**" { + runner.dialect = scala3 + } +} \ No newline at end of file diff --git a/README.md b/README.md index d978515..718cac9 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix" % "0 ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats" % "0.2.0" // To add only cats-effect Scalafix rules ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats-effect" % "0.2.0" +// To add only cats-mtl Scalafix rules +ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats-mtl" % "0.2.0" // To add only fs2 Scalafix rules ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-fs2" % "0.2.0" // To add only http4s Scalafix rules @@ -41,6 +43,7 @@ rules = [ TypelevelFs2SyncCompiler TypelevelHttp4sLiteralsSyntax TypelevelIORandomUUID + TypelevelMTLSubmarine ] ``` @@ -62,6 +65,7 @@ Not all rules function with Scala 3 yet. | TypelevelUnusedShowInterpolator | :white_check_mark: | :x: | | TypelevelFs2SyncCompiler | :white_check_mark: | :x: | | TypelevelHttp4sLiteralsSyntax | :white_check_mark: | :white_check_mark: | +| TypelevelMTLSubmarine | :white_check_mark: | :white_check_mark: | ## Rules for cats @@ -170,6 +174,36 @@ val test = IO.randomUUID This rule works on variable declarations, usaged within methods as well as for comprehensions. +## Rules for cats-mtl + +### TypelevelMTLSubmarine + +See https://typelevel.org/blog/2025/09/02/custom-error-types.html. + +This rule forbids calling error-handling methods (`handleError`, `recover`, `onError`, etc.) +on expressions that require `cats.mtl.Raise[F, E]`. +`Raise` provided by `Handle.allow` uses a traceless exception type called `cats.mtl.Handle#Submarine`, +so handling it through `ApplicativeError`, `MonadError`, or `IO` error methods might lead to unexpected results. + +For example: +```scala +import cats.effect.IO +import cats.mtl.{Raise, Handle} + +def raiseError: Raise[IO, String] ?=> IO[Unit] = r => + r.raise("boom") + +def standardError: IO[Unit] = + IO.raiseError(new RuntimeException("boom")) + +Handle.allow[String] { + for { + _ <- raiseError.onError(e => IO.println("Error: " + e)) // forbidden + _ <- standardError.onError(e => IO.println("Error: " + e)) // allowed + } yield +} +``` + ## Rules for fs2 ### TypelevelFs2SyncCompiler diff --git a/build.sbt b/build.sbt index 1b2c06a..3608a1f 100644 --- a/build.sbt +++ b/build.sbt @@ -7,6 +7,7 @@ lazy val CatsVersion = "2.12.0" lazy val CatsEffectVersion = "3.6.3" lazy val Fs2Version = "3.12.2" lazy val Http4sVersion = "0.23.32" +lazy val MtlVersion = "1.6.0" ThisBuild / startYear := Some(2022) ThisBuild / developers ++= List( @@ -19,12 +20,12 @@ ThisBuild / scalafixScalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaV lazy val `typelevel-scalafix` = project .in(file(".")) - .aggregate(`typelevel-scalafix-rules`, cats.all, catsEffect.all, fs2.all, http4s.all) + .aggregate(`typelevel-scalafix-rules`, cats.all, catsEffect.all, fs2.all, http4s.all, mtl.all) .enablePlugins(NoPublishPlugin) lazy val `typelevel-scalafix-rules` = project .in(file("target/rules-aggregate")) - .dependsOn(cats.rules, catsEffect.rules, fs2.rules, http4s.rules) + .dependsOn(cats.rules, catsEffect.rules, fs2.rules, http4s.rules, mtl.rules) .settings( moduleName := "typelevel-scalafix", tlVersionIntroduced ++= List("2.12", "2.13").map(_ -> "0.1.2").toMap, @@ -83,3 +84,17 @@ lazy val http4s = scalafixProject("http4s") "org.http4s" %% "http4s-core" % Http4sVersion ) ) + +// typelevel/mtl Scalafix rules +lazy val mtl = scalafixProject("mtl") + .rulesSettings( + tlVersionIntroduced ++= List("2.12", "2.13").map(_ -> "0.6.0").toMap + ) + .inputSettings( + scalaVersion := "3.7.2", + crossScalaVersions := Seq(V.scala213, "3.7.2"), + libraryDependencies ++= Seq( + "org.typelevel" %% "cats-effect" % CatsEffectVersion, + "org.typelevel" %% "cats-mtl" % MtlVersion + ) + ) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala new file mode 100644 index 0000000..0da7768 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -0,0 +1,474 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.mtl.syntax.raise._ +import cats.effect._ +import cats.syntax.all._ + +// scalafmt: { maxColumn = 160 } +object MTLSubmarineTest { + + object RaiseInstance { + + def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + // ApplicativeError syntax + r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + r.raise[String, Unit]("").tupleRight("") + r.raise[String, Unit]("").void + } + + def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + r.raise[String, Unit]("").tupleRight("") + r.raise[String, Unit]("").void + } + + def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].handleError(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit](r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT(r.raise[String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem(r.raise[String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError(r.raise[String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(r.raise[String, Unit](""), "") + Async[F].void(r.raise[String, Unit]("")) + } + + def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].ensure(r.raise[String, Unit](""))(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensureOr(r.raise[String, Unit](""))(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeemWith(r.raise[String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(r.raise[String, Unit](""), "") + Async[F].void(r.raise[String, Unit]("")) + } + + def io(implicit r: Raise[IO, String]): IO[Unit] = { + // applicative error + r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + r.raise[String, Unit]("").void + r.raise[String, Unit]("").debug() + } + + } + + object RaiseObject { + + def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + // ApplicativeError syntax + Raise.raise[F, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Raise.raise[F, String, Unit]("").tupleRight("") + Raise.raise[F, String, Unit]("").void + } + + def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Raise.raise[F, String, Unit]("").tupleRight("") + Raise.raise[F, String, Unit]("").void + } + + def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].handleError(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit](Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT(Raise.raise[F, String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem(Raise.raise[F, String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError(Raise.raise[F, String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") + Async[F].void(Raise.raise[F, String, Unit]("")) + } + + def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeemWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") + Async[F].void(Raise.raise[F, String, Unit]("")) + } + + def io(implicit r: Raise[IO, String]): IO[Unit] = { + // applicative error + Raise.raise[IO, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Raise.raise[IO, String, Unit]("").void + Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) + Raise.raise[IO, String, Unit]("").debug() + } + + } + + object RaiseSyntax { + + def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + // ApplicativeError syntax + "".raise[F, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + "".raise[F, Unit].tupleRight("") + "".raise[F, Unit].void + } + + def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + "".raise[F, Unit].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + "".raise[F, Unit].tupleRight("") + "".raise[F, Unit].void + } + + def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit]("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT("".raise[F, Unit]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem("".raise[F, Unit])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError("".raise[F, Unit])(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight("".raise[F, Unit], "") + Async[F].void("".raise[F, Unit]) + } + + def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeemWith("".raise[F, Unit])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight("".raise[F, Unit], "") + Async[F].void("".raise[F, Unit]) + } + + def io(implicit r: Raise[IO, String]): IO[Unit] = { + // applicative error + "".raise[IO, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + "".raise[IO, Unit].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + "".raise[IO, Unit].void + "".raise[IO, Unit].debug() + } + + } + + object HandleAllowF { + + def methodRaise[F[_]](implicit r: Raise[F, String]): F[Unit] = + r.raise("something went wrong") + + def method[F[_]: Async]: F[Unit] = + Async[F].unit + + def applicativeErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { _ => + for { + _ <- method[F].handleError(_ => ()) + _ <- method[F].handleErrorWith(_ => Async[F].unit) + _ <- method[F].attempt + _ <- method[F].attemptNarrow[RuntimeException] + _ <- method[F].attemptT.value + _ <- method[F].recover { case _ => () } + _ <- method[F].recoverWith { case _ => Async[F].unit } + _ <- method[F].redeem(_ => (), _ => ()) + _ <- method[F].onError { case _ => Async[F].unit } + _ <- method[F].orElse(Async[F].unit) + _ <- method[F].adaptErr { case _ => new Exception("") } + _ <- method[F].orRaise(new Exception("")) + _ <- method[F].voidError + } yield () + } + + def applicativeErrorDirect[F[_]: Async] = Handle.allowF[F, String] { _ => + for { + _ <- Async[F].handleError(method[F])(_ => ()) + _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) + _ <- Async[F].attempt(method[F]) + _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) + _ <- Async[F].attemptT(method[F]).value + _ <- Async[F].recover(method[F]) { case _ => () } + _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } + _ <- Async[F].redeem(method[F])(_ => (), _ => ()) + _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } + _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } + _ <- Async[F].voidError(method[F]) + } yield () + } + + def monadErrorDirect[F[_]: Async] = Handle.allowF[F, String] { _ => + for { + _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) + _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) + _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) + _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) + } yield () + } + + def monadErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { _ => + for { + _ <- method[F].ensure(new Exception(""))(_ => false) + _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow + _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) + _ <- method[F].attemptTap(_ => Async[F].unit) + _ <- method[F].adaptError { case _ => new Exception("") } + _ <- method[F].reject { case _ => new Exception("") } + } yield () + } + + def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { implicit h => + for { + _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + _ <- methodRaise[F].product(methodRaise[F]) + } yield () + } + + def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allowF[F, String] { implicit h => + for { + _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + _ <- Async[F].product(methodRaise[F], methodRaise[F]) + } yield () + } + + def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allowF[F, String] { implicit h => + for { + _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + } yield () + } + + def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { implicit h => + for { + _ <- methodRaise[F].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + } yield () + } + + def withIO = Handle.allowF[IO, String] { _ => + for { + // applicative error + _ <- method[IO].handleError(_ => ()) + _ <- method[IO].handleErrorWith(_ => IO.unit) + _ <- method[IO].attempt + _ <- method[IO].attemptNarrow[RuntimeException] + _ <- method[IO].attemptT.value + _ <- method[IO].recover { case _ => () } + _ <- method[IO].recoverWith { case _ => IO.unit } + _ <- method[IO].redeem(_ => (), _ => ()) + _ <- method[IO].onError { case _ => IO.unit } + _ <- method[IO].orElse(IO.unit) + _ <- method[IO].adaptErr { case _ => new Exception("") } + _ <- method[IO].orRaise(new Exception("")) + _ <- method[IO].voidError + // monad error + _ <- method[IO].ensure(new Exception(""))(_ => false) + _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) + _ <- method[IO].attemptTap(_ => IO.unit) + _ <- method[IO].adaptError { case _ => new Exception("") } + _ <- method[IO].reject { case _ => new Exception("") } + } yield () + } + + def raiseWithIO = Handle.allowF[IO, String] { implicit h => + for { + // applicative error + _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[IO].void + _ <- methodRaise[IO].debug() + } yield () + } + + } + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala new file mode 100644 index 0000000..8ea7787 --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -0,0 +1,658 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.mtl.syntax.raise.* +import cats.effect.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object MTLSubmarineTest { + + object RaiseInstance { + + def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + r.raise[String, Unit]("").tupleRight("") + r.raise[String, Unit]("").void + } + + def monadErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + r.raise[String, Unit]("").tupleRight("") + r.raise[String, Unit]("").void + } + + def applicativeErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].handleError(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit](r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT(r.raise[String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem(r.raise[String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError(r.raise[String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(r.raise[String, Unit](""), "") + Async[F].void(r.raise[String, Unit]("")) + } + + def monadErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].ensure(r.raise[String, Unit](""))(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensureOr(r.raise[String, Unit](""))(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeemWith(r.raise[String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(r.raise[String, Unit](""), "") + Async[F].void(r.raise[String, Unit]("")) + } + + def io(using r: Raise[IO, String]): IO[Unit] = { + // applicative error + r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + r.raise[String, Unit]("").void + r.raise[String, Unit]("").product(r.raise[String, Unit]("")) + r.raise[String, Unit]("").debug() + } + + } + + object RaiseObject { + + def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Raise.raise[F, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Raise.raise[F, String, Unit]("").tupleRight("") + Raise.raise[F, String, Unit]("").void + } + + def monadErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Raise.raise[F, String, Unit]("").tupleRight("") + Raise.raise[F, String, Unit]("").void + } + + def applicativeErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].handleError(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit](Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT(Raise.raise[F, String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem(Raise.raise[F, String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError(Raise.raise[F, String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") + Async[F].void(Raise.raise[F, String, Unit]("")) + } + + def monadErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeemWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") + Async[F].void(Raise.raise[F, String, Unit]("")) + } + + def io(using r: Raise[IO, String]): IO[Unit] = { + // applicative error + Raise.raise[IO, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Raise.raise[IO, String, Unit]("").void + Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) + Raise.raise[IO, String, Unit]("").debug() + } + + } + + object RaiseSyntax { + + def applicativeErrorSyntax[F[_]: Async](using Raise[F, String]): F[Unit] = { + "".raise[F, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + "".raise[F, Unit].tupleRight("") + "".raise[F, Unit].void + } + + def monadErrorSyntax[F[_]: Async](using Raise[F, String]): F[Unit] = { + "".raise[F, Unit].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + "".raise[F, Unit].tupleRight("") + "".raise[F, Unit].void + } + + def applicativeErrorDirect[F[_]: Async](using Raise[F, String]): F[Unit] = { + Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit]("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT("".raise[F, Unit]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem("".raise[F, Unit])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError("".raise[F, Unit])(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight("".raise[F, Unit], "") + Async[F].void("".raise[F, Unit]) + } + + def monadErrorDirect[F[_]: Async](using Raise[F, String]): F[Unit] = { + Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeemWith("".raise[F, Unit])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight("".raise[F, Unit], "") + Async[F].void("".raise[F, Unit]) + } + + def io(using Raise[IO, String]): IO[Unit] = { + // applicative error + "".raise[IO, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + "".raise[IO, Unit].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + "".raise[IO, Unit].void + "".raise[IO, Unit].debug() + } + + } + + object HandleAllowGiven { + + def methodRaise[F[_]](using r: Raise[F, String]): F[Unit] = + r.raise("something went wrong") + + def method[F[_]: Async]: F[Unit] = + Async[F].unit + + def applicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- method[F].handleError(_ => ()) + _ <- method[F].handleErrorWith(_ => Async[F].unit) + _ <- method[F].attempt + _ <- method[F].attemptNarrow[RuntimeException] + _ <- method[F].attemptT.value + _ <- method[F].recover { case _ => () } + _ <- method[F].recoverWith { case _ => Async[F].unit } + _ <- method[F].redeem(_ => (), _ => ()) + _ <- method[F].onError { case _ => Async[F].unit } + _ <- method[F].orElse(Async[F].unit) + _ <- method[F].adaptErr { case _ => new Exception("") } + _ <- method[F].orRaise(new Exception("")) + _ <- method[F].voidError + } yield () + } + + def applicativeErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].handleError(method[F])(_ => ()) + _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) + _ <- Async[F].attempt(method[F]) + _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) + _ <- Async[F].attemptT(method[F]).value + _ <- Async[F].recover(method[F]) { case _ => () } + _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } + _ <- Async[F].redeem(method[F])(_ => (), _ => ()) + _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } + _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } + _ <- Async[F].voidError(method[F]) + } yield () + } + + def monadErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) + _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) + _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) + _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) + } yield () + } + + def monadErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- method[F].ensure(new Exception(""))(_ => false) + _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow + _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) + _ <- method[F].attemptTap(_ => Async[F].unit) + _ <- method[F].adaptError { case _ => new Exception("") } + _ <- method[F].reject { case _ => new Exception("") } + } yield () + } + + def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + _ <- methodRaise[F].product(methodRaise[F]) + } yield () + } + + def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + _ <- Async[F].product(methodRaise[F], methodRaise[F]) + } yield () + } + + def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + } yield () + } + + def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- methodRaise[F].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + } yield () + } + + def withIO = Handle.allow[String] { + for { + // applicative error + _ <- method[IO].handleError(_ => ()) + _ <- method[IO].handleErrorWith(_ => IO.unit) + _ <- method[IO].attempt + _ <- method[IO].attemptNarrow[RuntimeException] + _ <- method[IO].attemptT.value + _ <- method[IO].recover { case _ => () } + _ <- method[IO].recoverWith { case _ => IO.unit } + _ <- method[IO].redeem(_ => (), _ => ()) + _ <- method[IO].onError { case _ => IO.unit } + _ <- method[IO].orElse(IO.unit) + _ <- method[IO].adaptErr { case _ => new Exception("") } + _ <- method[IO].orRaise(new Exception("")) + _ <- method[IO].voidError + // monad error + _ <- method[IO].ensure(new Exception(""))(_ => false) + _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) + _ <- method[IO].attemptTap(_ => IO.unit) + _ <- method[IO].adaptError { case _ => new Exception("") } + _ <- method[IO].reject { case _ => new Exception("") } + } yield () + } + + def raiseWithIO = Handle.allow[String] { + for { + // applicative error + _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[IO].void + _ <- methodRaise[IO].debug() + } yield () + } + + } + + object HandleAllowContextFunction { + + def methodRaise[F[_]]: Raise[F, String] ?=> F[Unit] = r ?=> r.raise("something went wrong") + + def method[F[_]: Async]: F[Unit] = + Async[F].unit + + def applicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- method[F].handleError(_ => ()) + _ <- method[F].handleErrorWith(_ => Async[F].unit) + _ <- method[F].attempt + _ <- method[F].attemptNarrow[RuntimeException] + _ <- method[F].attemptT.value + _ <- method[F].recover { case _ => () } + _ <- method[F].recoverWith { case _ => Async[F].unit } + _ <- method[F].redeem(_ => (), _ => ()) + _ <- method[F].onError { case _ => Async[F].unit } + _ <- method[F].orElse(Async[F].unit) + _ <- method[F].adaptErr { case _ => new Exception("") } + _ <- method[F].orRaise(new Exception("")) + _ <- method[F].voidError + } yield () + } + + def applicativeErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].handleError(method[F])(_ => ()) + _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) + _ <- Async[F].attempt(method[F]) + _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) + _ <- Async[F].attemptT(method[F]).value + _ <- Async[F].recover(method[F]) { case _ => () } + _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } + _ <- Async[F].redeem(method[F])(_ => (), _ => ()) + _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } + _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } + _ <- Async[F].voidError(method[F]) + } yield () + } + + def monadErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) + _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) + _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) + _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) + } yield () + } + + def monadErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- method[F].ensure(new Exception(""))(_ => false) + _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow + _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) + _ <- method[F].attemptTap(_ => Async[F].unit) + _ <- method[F].adaptError { case _ => new Exception("") } + _ <- method[F].reject { case _ => new Exception("") } + } yield () + } + + def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + } yield () + } + + def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + } yield () + } + + def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + } yield () + } + + def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- methodRaise[F].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + } yield () + } + + def withIO = Handle.allow[String] { + for { + // applicative error + _ <- method[IO].handleError(_ => ()) + _ <- method[IO].handleErrorWith(_ => IO.unit) + _ <- method[IO].attempt + _ <- method[IO].attemptNarrow[RuntimeException] + _ <- method[IO].attemptT.value + _ <- method[IO].recover { case _ => () } + _ <- method[IO].recoverWith { case _ => IO.unit } + _ <- method[IO].redeem(_ => (), _ => ()) + _ <- method[IO].onError { case _ => IO.unit } + _ <- method[IO].orElse(IO.unit) + _ <- method[IO].adaptErr { case _ => new Exception("") } + _ <- method[IO].orRaise(new Exception("")) + _ <- method[IO].voidError + // monad error + _ <- method[IO].ensure(new Exception(""))(_ => false) + _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) + _ <- method[IO].attemptTap(_ => IO.unit) + _ <- method[IO].adaptError { case _ => new Exception("") } + _ <- method[IO].reject { case _ => new Exception("") } + } yield () + } + + def raiseWithIO = Handle.allow[String] { + for { + // applicative error + _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[IO].void + _ <- methodRaise[IO].debug() + } yield () + } + + } + +} diff --git a/modules/mtl/rules/src/main/resources/META-INF/services/scalafix.v1.Rule b/modules/mtl/rules/src/main/resources/META-INF/services/scalafix.v1.Rule new file mode 100644 index 0000000..51bd4be --- /dev/null +++ b/modules/mtl/rules/src/main/resources/META-INF/services/scalafix.v1.Rule @@ -0,0 +1 @@ +org.typelevel.fix.MTLSubmarine diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala new file mode 100644 index 0000000..99497c6 --- /dev/null +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -0,0 +1,219 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.fix + +import scalafix.v1._ + +import scala.meta._ + +class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { + + private val Syntax_M = + SymbolMatcher.exact("cats/syntax/ApplicativeErrorOps#") + + SymbolMatcher.exact("cats/syntax/ApplicativeErrorFUnitOps#") + + SymbolMatcher.exact("cats/syntax/MonadErrorOps#") + + private val Direct_M = + SymbolMatcher.exact("cats/ApplicativeError#") + + SymbolMatcher.exact("cats/MonadError#") + + private val Raise_M = + SymbolMatcher.exact("cats/mtl/Raise#") + + private val RaiseAll_M = + SymbolMatcher.exact("cats/mtl/Raise#") + + SymbolMatcher.exact("cats/mtl/Raise.") + + SymbolMatcher.exact("cats/mtl/syntax/RaiseOps#") + + private val ContextFunction_M = + (1 to 22).map(i => SymbolMatcher.exact(s"scala/ContextFunction$i#")).reduce(_ + _) + + private val IO_M = + SymbolMatcher.normalized("cats/effect/IO#handleError().") + + SymbolMatcher.normalized("cats/effect/IO#handleErrorWith().") + + SymbolMatcher.normalized("cats/effect/IO#recover().") + + SymbolMatcher.normalized("cats/effect/IO#recoverWith().") + + SymbolMatcher.normalized("cats/effect/IO#attempt().") + + SymbolMatcher.normalized("cats/effect/IO#redeem().") + + SymbolMatcher.normalized("cats/effect/IO#redeemWith().") + + SymbolMatcher.normalized("cats/effect/IO#adaptError().") + + SymbolMatcher.normalized("cats/effect/IO#onError().") + + SymbolMatcher.normalized("cats/effect/IO#orElse().") + + SymbolMatcher.normalized("cats/effect/IO#voidError().") + + SymbolMatcher.normalized("cats/effect/IO#attemptTap().") + + override def fix(implicit doc: SemanticDocument): Patch = + doc.tree.collect { + // syntax, e.g. `raise.onError { e => ??? }` + case t @ Term.Apply.After_4_6_0(Term.Select(qual, Term.Name(name)), _) + if Syntax_M.matches(t.symbol.owner) && producedByRaise(qual) => + Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) + + // syntax, e.g. `raise.voidError` + case t @ Term.Select(qual, Term.Name(name)) + if Syntax_M.matches(t.symbol.owner) && producedByRaise(qual) => + Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) + + // direct, e.g. `Async[F].onError(raise) { e => ??? }` + case t @ Term.Apply.After_4_6_0(_, _) + if isOwner(t.symbol, Direct_M) && anyArgComesFromRaise(t) => + Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, None)) + + // IO direct methods, e.g. `raise.onError { e => ??? }` + case t @ Term.Apply.After_4_6_0(sel @ Term.Select(qual, Term.Name(name)), _) + if IO_M.matches(sel) && producedByRaise(qual) => + Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) + + case t @ Term.Select(qual, Term.Name(name)) if IO_M.matches(t) && producedByRaise(qual) => + Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) + }.asPatch + + private def producedByRaise(qual: Term)(implicit doc: SemanticDocument) = + methodRequiresImplicitRaise(qual) || originatesFromRaiseOperation(qual) + + private def originatesFromRaiseOperation(term: Term)(implicit doc: SemanticDocument) = + calleeSymbol(term).exists(sym => isOwner(sym, RaiseAll_M)) + + private def anyArgComesFromRaise(term: Term)(implicit doc: SemanticDocument): Boolean = { + @annotation.tailrec + def loop(t: Term): Boolean = + t match { + case Term.Apply.After_4_6_0(fun, args) => + if (args.exists(producedByRaise)) true + else loop(fun) + + case Term.ApplyType.After_4_6_0(fun, _) => + loop(fun) + + case _ => + false + } + + loop(term) + } + + private def isOwner(sym: Symbol, matcher: SymbolMatcher): Boolean = { + @annotation.tailrec + def loop(s: Symbol): Boolean = + if (s == Symbol.None) false + else { + val o = s.owner + if (matcher.matches(o)) true + else loop(o) + } + loop(sym) + } + + private def methodRequiresImplicitRaise(qual: Term)(implicit doc: SemanticDocument): Boolean = + calleeSymbol(qual).flatMap(sym => doc.info(sym)).exists { info => + info.signature match { + case m: MethodSignature => + requiresRaiseViaParams(m) || requiresRaiseViaContextFunction(m) + + case _ => + false + } + } + + // any implicit parameter whose type constructor is Raise + private def requiresRaiseViaParams(m: MethodSignature)(implicit doc: SemanticDocument): Boolean = + m.parameterLists.exists(_.exists { p => + doc.info(p.symbol).exists { pi => + val isUsingOrImplicit = pi.isImplicit + isUsingOrImplicit && paramIsRaise(pi.signature) + } + }) + + // Scala 3: handle def f: Raise[F, E] ?=> R + private def requiresRaiseViaContextFunction( + m: MethodSignature + )(implicit doc: SemanticDocument): Boolean = + m.returnType match { + case TypeRef(_, sym, args) if ContextFunction_M.matches(sym) => + args.exists(typeIsRaise) + + case _ => + false + } + + private def paramIsRaise(sig: Signature)(implicit doc: SemanticDocument): Boolean = + sig match { + case ValueSignature(tpe) => typeIsRaise(tpe) + case _ => false + } + + private def typeIsRaise(tpe: SemanticType)(implicit doc: SemanticDocument): Boolean = + tpe match { + // matches Raise[F, E] for any F and E + case TypeRef(_, sym, _) if Raise_M.matches(sym) => + true + + // handle aliases like type R[F, E] = Raise[F, E] + case TypeRef(_, _, args) if args.nonEmpty => + args.exists(typeIsRaise) + + case AnnotatedType(_, underlying) => + typeIsRaise(underlying) + + case _ => + false + } + + private def calleeSymbol(term: Term)(implicit doc: SemanticDocument): Option[Symbol] = + term match { + case Term.Apply.After_4_6_0(fun, _) => calleeSymbol(fun) + case Term.ApplyType.After_4_6_0(fun, _) => calleeSymbol(fun) + case Term.Select(_, n) => Some(n.symbol) + case n: Term.Name => Some(n.symbol) + case Term.Block(stats) => + stats.lastOption.collect { case t: Term => t }.flatMap(calleeSymbol) + + case Term.If.After_4_4_0(_, thn, els, _) => + calleeSymbol(thn).orElse(calleeSymbol(els)) + + case Term.Match.After_4_9_9(_, cases, _) => + cases.view.flatMap(c => calleeSymbol(c.body)).headOption + + case _ => None + } + +} + +object MTLSubmarine { + + final class SubmarineErrorHandlingDiagnostic( + tree: Tree, + method: Option[String] + ) extends Diagnostic { + + override def message: String = { + val suchAs = method.fold("")(m => s", such as `$m`,") + s"Avoid calling error-handling methods$suchAs " + + s"on expressions that require `cats.mtl.Raise[F, *]`. " + + s"Errors raised through `Raise` represented by a traceless exception type `cats.mtl.Handle#Submarine`. " + + s"Handling them with `ApplicativeError`, `MonadError`, or `IO` error-handling " + + s"methods might lead to unexpected results. " + + s"Use `cats.mtl.Handle[F, *].handle` or `cats.mtl.Handle[F, *].handleWith` " + + s"to manage these cases explicitly." + } + + def position: Position = tree.pos + + override def categoryID: String = "mtlSubmarineErrorHandling" + } + +} diff --git a/modules/mtl/tests/src/test/scala/org/typelevel/fix/RuleSuite.scala b/modules/mtl/tests/src/test/scala/org/typelevel/fix/RuleSuite.scala new file mode 100644 index 0000000..5231846 --- /dev/null +++ b/modules/mtl/tests/src/test/scala/org/typelevel/fix/RuleSuite.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.fix + +import scalafix.testkit._ +import org.scalatest.funsuite.AnyFunSuiteLike + +class RuleSuite extends AbstractSemanticRuleSuite with AnyFunSuiteLike { + runAllTests() +} From fd896cbd16c5067b6b911b5e3cf2f403b730999e Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 24 Oct 2025 17:28:35 +0300 Subject: [PATCH 02/39] docs: better wording --- README.md | 2 +- .../rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 718cac9..7934301 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ See https://typelevel.org/blog/2025/09/02/custom-error-types.html. This rule forbids calling error-handling methods (`handleError`, `recover`, `onError`, etc.) on expressions that require `cats.mtl.Raise[F, E]`. `Raise` provided by `Handle.allow` uses a traceless exception type called `cats.mtl.Handle#Submarine`, -so handling it through `ApplicativeError`, `MonadError`, or `IO` error methods might lead to unexpected results. +so handling it through `ApplicativeError`, `MonadError`, or `IO` error-handling methods is not always desirable. For example: ```scala diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 99497c6..49d5986 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -206,7 +206,7 @@ object MTLSubmarine { s"on expressions that require `cats.mtl.Raise[F, *]`. " + s"Errors raised through `Raise` represented by a traceless exception type `cats.mtl.Handle#Submarine`. " + s"Handling them with `ApplicativeError`, `MonadError`, or `IO` error-handling " + - s"methods might lead to unexpected results. " + + s"methods is not always desirable. " + s"Use `cats.mtl.Handle[F, *].handle` or `cats.mtl.Handle[F, *].handleWith` " + s"to manage these cases explicitly." } From 3bcc9f1384ecf1305f3460a1556cfb735adac348 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 3 Sep 2026 10:49:55 +0100 Subject: [PATCH 03/39] detect Handle-dependent submarine effects --- .../main/scala-2/fix/MTLSubmarineTest.scala | 16 +++++++++++++ .../main/scala-3/fix/MTLSubmarineTest.scala | 23 +++++++++++++++++++ .../org/typelevel/fix/MTLSubmarine.scala | 7 ++++-- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 0da7768..6010315 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -101,6 +101,22 @@ object MTLSubmarineTest { } + object HandleRequirement { + + def method[F[_]](implicit h: Handle[F, String]): F[Unit] = + h.raise("something went wrong") + + def applicativeErrorSyntax[F[_]: Async](implicit h: Handle[F, String]): F[Unit] = + method[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def applicativeErrorDirect[F[_]: Async](implicit h: Handle[F, String]): F[Unit] = + Async[F].attempt(method[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def io(implicit h: Handle[IO, String]): IO[Unit] = + method[IO].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 8ea7787..83456b1 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -101,6 +101,29 @@ object MTLSubmarineTest { } + object HandleRequirement { + + def method[F[_]](using h: Handle[F, String]): F[Unit] = + h.raise("something went wrong") + + def contextFunctionMethod[F[_]]: Handle[F, String] ?=> F[Unit] = + summon[Handle[F, String]].raise("something went wrong") + + def applicativeErrorSyntax[F[_]: Async](using h: Handle[F, String]): F[Unit] = + method[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def applicativeErrorDirect[F[_]: Async](using h: Handle[F, String]): F[Unit] = + Async[F].attempt(method[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def io(using h: Handle[IO, String]): IO[Unit] = + method[IO].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def contextFunction = Handle.allow[String] { + contextFunctionMethod[IO].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 49d5986..d2d5ad6 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -34,6 +34,9 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { private val Raise_M = SymbolMatcher.exact("cats/mtl/Raise#") + private val Handle_M = + SymbolMatcher.exact("cats/mtl/Handle#") + private val RaiseAll_M = SymbolMatcher.exact("cats/mtl/Raise#") + SymbolMatcher.exact("cats/mtl/Raise.") + @@ -158,8 +161,8 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { private def typeIsRaise(tpe: SemanticType)(implicit doc: SemanticDocument): Boolean = tpe match { - // matches Raise[F, E] for any F and E - case TypeRef(_, sym, _) if Raise_M.matches(sym) => + // matches Raise[F, E] or Handle[F, E] for any F and E + case TypeRef(_, sym, _) if Raise_M.matches(sym) || Handle_M.matches(sym) => true // handle aliases like type R[F, E] = Raise[F, E] From 2e1529392d52b4ef1ea43f3795fce60d89d92099 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 3 Sep 2026 10:59:51 +0100 Subject: [PATCH 04/39] Detect custom Raise and Handle subtypes --- .../main/scala-2/fix/MTLSubmarineTest.scala | 26 +++++++++++++++++++ .../main/scala-3/fix/MTLSubmarineTest.scala | 26 +++++++++++++++++++ .../org/typelevel/fix/MTLSubmarine.scala | 24 ++++++++++++++--- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 6010315..547b0d0 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -117,6 +117,32 @@ object MTLSubmarineTest { } + object CustomRaiseSubtype { + + trait CustomRaise[F[_], E] extends Raise[F, E] + trait IndirectRaise[F[_], E] extends CustomRaise[F, E] + trait CustomHandle[F[_], E] extends Handle[F, E] + + def customRaise[F[_]](implicit r: CustomRaise[F, String]): F[Unit] = + r.raise("something went wrong") + + def indirectRaise[F[_]](implicit r: IndirectRaise[F, String]): F[Unit] = + r.raise("something went wrong") + + def customHandle[F[_]](implicit h: CustomHandle[F, String]): F[Unit] = + h.raise("something went wrong") + + def customRaiseHandling[F[_]: Async](implicit r: CustomRaise[F, String]): F[Unit] = + customRaise[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def indirectRaiseHandling[F[_]: Async](implicit r: IndirectRaise[F, String]): F[Unit] = + Async[F].attempt(indirectRaise[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def customHandleHandling[F[_]: Async](implicit h: CustomHandle[F, String]): F[Unit] = + customHandle[F].handleErrorWith(_ => Async[F].unit).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 83456b1..1a9e0ec 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -124,6 +124,32 @@ object MTLSubmarineTest { } + object CustomRaiseSubtype { + + trait CustomRaise[F[_], E] extends Raise[F, E] + trait IndirectRaise[F[_], E] extends CustomRaise[F, E] + trait CustomHandle[F[_], E] extends Handle[F, E] + + def customRaise[F[_]](using r: CustomRaise[F, String]): F[Unit] = + r.raise("something went wrong") + + def indirectRaise[F[_]](using r: IndirectRaise[F, String]): F[Unit] = + r.raise("something went wrong") + + def customHandle[F[_]](using h: CustomHandle[F, String]): F[Unit] = + h.raise("something went wrong") + + def customRaiseHandling[F[_]: Async](using r: CustomRaise[F, String]): F[Unit] = + customRaise[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def indirectRaiseHandling[F[_]: Async](using r: IndirectRaise[F, String]): F[Unit] = + Async[F].attempt(indirectRaise[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def customHandleHandling[F[_]: Async](using h: CustomHandle[F, String]): F[Unit] = + customHandle[F].handleErrorWith(_ => Async[F].unit).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index d2d5ad6..7fd30d6 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -160,17 +160,33 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { } private def typeIsRaise(tpe: SemanticType)(implicit doc: SemanticDocument): Boolean = + typeIsRaise(tpe, Set.empty) + + private def typeIsRaise( + tpe: SemanticType, + visited: Set[Symbol] + )(implicit doc: SemanticDocument): Boolean = tpe match { // matches Raise[F, E] or Handle[F, E] for any F and E case TypeRef(_, sym, _) if Raise_M.matches(sym) || Handle_M.matches(sym) => true - // handle aliases like type R[F, E] = Raise[F, E] - case TypeRef(_, _, args) if args.nonEmpty => - args.exists(typeIsRaise) + case TypeRef(_, sym, args) => + val inherited = + !visited(sym) && doc.info(sym).exists { + _.signature match { + case c: ClassSignature => + c.parents.exists(typeIsRaise(_, visited + sym)) + + case _ => + false + } + } + + inherited || args.exists(typeIsRaise(_, visited)) case AnnotatedType(_, underlying) => - typeIsRaise(underlying) + typeIsRaise(underlying, visited) case _ => false From dcd6902bd0f22b4b534acc305409dec7a344ab89 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 3 Sep 2026 11:03:51 +0100 Subject: [PATCH 05/39] Resolve Raise and Handle type aliases --- .../main/scala-2/fix/MTLSubmarineTest.scala | 33 +++++++++++++++++++ .../main/scala-3/fix/MTLSubmarineTest.scala | 33 +++++++++++++++++++ .../org/typelevel/fix/MTLSubmarine.scala | 3 ++ 3 files changed, 69 insertions(+) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 547b0d0..f012042 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -143,6 +143,39 @@ object MTLSubmarineTest { } + object RaiseAlias { + + type AliasedRaise[F[_], E] = Raise[F, E] + type ChainedRaiseAlias[F[_], E] = AliasedRaise[F, E] + type AliasedHandle[F[_], E] = Handle[F, E] + type ChainedHandleAlias[F[_], E] = AliasedHandle[F, E] + + def aliasedRaise[F[_]](implicit r: AliasedRaise[F, String]): F[Unit] = + r.raise("something went wrong") + + def chainedRaise[F[_]](implicit r: ChainedRaiseAlias[F, String]): F[Unit] = + r.raise("something went wrong") + + def aliasedHandle[F[_]](implicit h: AliasedHandle[F, String]): F[Unit] = + h.raise("something went wrong") + + def chainedHandle[F[_]](implicit h: ChainedHandleAlias[F, String]): F[Unit] = + h.raise("something went wrong") + + def aliasedRaiseHandling[F[_]: Async](implicit r: AliasedRaise[F, String]): F[Unit] = + aliasedRaise[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def chainedRaiseHandling[F[_]: Async](implicit r: ChainedRaiseAlias[F, String]): F[Unit] = + Async[F].attempt(chainedRaise[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def aliasedHandleHandling[F[_]: Async](implicit h: AliasedHandle[F, String]): F[Unit] = + aliasedHandle[F].recover(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def chainedHandleHandling[F[_]: Async](implicit h: ChainedHandleAlias[F, String]): F[Unit] = + Async[F].handleError(chainedHandle[F])(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 1a9e0ec..ca84bc5 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -150,6 +150,39 @@ object MTLSubmarineTest { } + object RaiseAlias { + + type AliasedRaise[F[_], E] = Raise[F, E] + type ChainedRaiseAlias[F[_], E] = AliasedRaise[F, E] + type AliasedHandle[F[_], E] = Handle[F, E] + type ChainedHandleAlias[F[_], E] = AliasedHandle[F, E] + + def aliasedRaise[F[_]](using r: AliasedRaise[F, String]): F[Unit] = + r.raise("something went wrong") + + def chainedRaise[F[_]](using r: ChainedRaiseAlias[F, String]): F[Unit] = + r.raise("something went wrong") + + def aliasedHandle[F[_]](using h: AliasedHandle[F, String]): F[Unit] = + h.raise("something went wrong") + + def chainedHandle[F[_]](using h: ChainedHandleAlias[F, String]): F[Unit] = + h.raise("something went wrong") + + def aliasedRaiseHandling[F[_]: Async](using r: AliasedRaise[F, String]): F[Unit] = + aliasedRaise[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def chainedRaiseHandling[F[_]: Async](using r: ChainedRaiseAlias[F, String]): F[Unit] = + Async[F].attempt(chainedRaise[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def aliasedHandleHandling[F[_]: Async](using h: AliasedHandle[F, String]): F[Unit] = + aliasedHandle[F].recover(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def chainedHandleHandling[F[_]: Async](using h: ChainedHandleAlias[F, String]): F[Unit] = + Async[F].handleError(chainedHandle[F])(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 7fd30d6..354c926 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -178,6 +178,9 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case c: ClassSignature => c.parents.exists(typeIsRaise(_, visited + sym)) + case t: TypeSignature => + typeIsRaise(t.upperBound, visited + sym) + case _ => false } From f5c2aa9476aeb85739bd12a94c7adb40b2725e2b Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 3 Sep 2026 11:14:29 +0100 Subject: [PATCH 06/39] Only flag error-observing methods --- .../main/scala-2/fix/MTLSubmarineTest.scala | 88 +++++++------- .../main/scala-3/fix/MTLSubmarineTest.scala | 110 +++++++++--------- .../org/typelevel/fix/MTLSubmarine.scala | 41 ++++++- 3 files changed, 137 insertions(+), 102 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index f012042..878e869 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -34,12 +34,12 @@ object MTLSubmarineTest { } def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) r.raise[String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").reject(_ => new Exception("")) // ensure other methods aren't affected r.raise[String, Unit]("").tupleRight("") r.raise[String, Unit]("").void @@ -63,8 +63,8 @@ object MTLSubmarineTest { } def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].ensure(r.raise[String, Unit](""))(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].ensureOr(r.raise[String, Unit](""))(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensure(r.raise[String, Unit](""))(new Exception(""))(_ => false) + Async[F].ensureOr(r.raise[String, Unit](""))(_ => new Exception(""))(_ => false) Async[F].redeemWith(r.raise[String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].attemptTap(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // ensure other methods aren't affected @@ -88,12 +88,12 @@ object MTLSubmarineTest { r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // monad error - r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").reject(_ => new Exception("")) // ensure other methods aren't affected r.raise[String, Unit]("").void r.raise[String, Unit]("").debug() @@ -199,12 +199,12 @@ object MTLSubmarineTest { } def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) + Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").reject(_ => new Exception("")) // ensure other methods aren't affected Raise.raise[F, String, Unit]("").tupleRight("") Raise.raise[F, String, Unit]("").void @@ -228,8 +228,8 @@ object MTLSubmarineTest { } def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) + Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) Async[F].redeemWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].attemptTap(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // ensure other methods aren't affected @@ -253,12 +253,12 @@ object MTLSubmarineTest { Raise.raise[IO, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[IO, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // monad error - Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) + Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").reject(_ => new Exception("")) // ensure other methods aren't affected Raise.raise[IO, String, Unit]("").void Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) @@ -290,12 +290,12 @@ object MTLSubmarineTest { } def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - "".raise[F, Unit].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].ensure(new Exception(""))(_ => false) + "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].reject(_ => new Exception("")) // ensure other methods aren't affected "".raise[F, Unit].tupleRight("") "".raise[F, Unit].void @@ -319,8 +319,8 @@ object MTLSubmarineTest { } def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) + Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) Async[F].redeemWith("".raise[F, Unit])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].attemptTap("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // ensure other methods aren't affected @@ -344,12 +344,12 @@ object MTLSubmarineTest { "".raise[IO, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[IO, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // monad error - "".raise[IO, Unit].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].ensure(new Exception(""))(_ => false) + "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) + "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].reject(_ => new Exception("")) // ensure other methods aren't affected "".raise[IO, Unit].void "".raise[IO, Unit].debug() @@ -465,8 +465,8 @@ object MTLSubmarineTest { def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allowF[F, String] { implicit h => for { - _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // ensure other methods aren't affected @@ -477,12 +477,12 @@ object MTLSubmarineTest { def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { implicit h => for { - _ <- methodRaise[F].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].ensure(new Exception(""))(_ => false) + _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[F].void _ <- methodRaise[F].tupleRight("") @@ -532,12 +532,12 @@ object MTLSubmarineTest { _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // monad error - _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) + _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[IO].void _ <- methodRaise[IO].debug() diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index ca84bc5..f0fed69 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -33,12 +33,12 @@ object MTLSubmarineTest { } def monadErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) r.raise[String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").reject(_ => new Exception("")) // ensure other methods aren't affected r.raise[String, Unit]("").tupleRight("") r.raise[String, Unit]("").void @@ -62,8 +62,8 @@ object MTLSubmarineTest { } def monadErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Async[F].ensure(r.raise[String, Unit](""))(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].ensureOr(r.raise[String, Unit](""))(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensure(r.raise[String, Unit](""))(new Exception(""))(_ => false) + Async[F].ensureOr(r.raise[String, Unit](""))(_ => new Exception(""))(_ => false) Async[F].redeemWith(r.raise[String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].attemptTap(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // ensure other methods aren't affected @@ -87,12 +87,12 @@ object MTLSubmarineTest { r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // monad error - r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").reject(_ => new Exception("")) // ensure other methods aren't affected r.raise[String, Unit]("").void r.raise[String, Unit]("").product(r.raise[String, Unit]("")) @@ -205,12 +205,12 @@ object MTLSubmarineTest { } def monadErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) + Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").reject(_ => new Exception("")) // ensure other methods aren't affected Raise.raise[F, String, Unit]("").tupleRight("") Raise.raise[F, String, Unit]("").void @@ -234,8 +234,8 @@ object MTLSubmarineTest { } def monadErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) + Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) Async[F].redeemWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].attemptTap(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // ensure other methods aren't affected @@ -259,12 +259,12 @@ object MTLSubmarineTest { Raise.raise[IO, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[IO, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // monad error - Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) + Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").reject(_ => new Exception("")) // ensure other methods aren't affected Raise.raise[IO, String, Unit]("").void Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) @@ -295,12 +295,12 @@ object MTLSubmarineTest { } def monadErrorSyntax[F[_]: Async](using Raise[F, String]): F[Unit] = { - "".raise[F, Unit].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].ensure(new Exception(""))(_ => false) + "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].reject(_ => new Exception("")) // ensure other methods aren't affected "".raise[F, Unit].tupleRight("") "".raise[F, Unit].void @@ -324,8 +324,8 @@ object MTLSubmarineTest { } def monadErrorDirect[F[_]: Async](using Raise[F, String]): F[Unit] = { - Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) + Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) Async[F].redeemWith("".raise[F, Unit])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].attemptTap("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // ensure other methods aren't affected @@ -349,12 +349,12 @@ object MTLSubmarineTest { "".raise[IO, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[IO, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // monad error - "".raise[IO, Unit].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].reject(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].ensure(new Exception(""))(_ => false) + "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) + "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].reject(_ => new Exception("")) // ensure other methods aren't affected "".raise[IO, Unit].void "".raise[IO, Unit].debug() @@ -470,8 +470,8 @@ object MTLSubmarineTest { def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allow[String] { for { - _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // ensure other methods aren't affected @@ -482,12 +482,12 @@ object MTLSubmarineTest { def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allow[String] { for { - _ <- methodRaise[F].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].ensure(new Exception(""))(_ => false) + _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[F].void _ <- methodRaise[F].tupleRight("") @@ -537,12 +537,12 @@ object MTLSubmarineTest { _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // monad error - _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) + _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[IO].void _ <- methodRaise[IO].debug() @@ -656,8 +656,8 @@ object MTLSubmarineTest { def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allow[String] { for { - _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // ensure other methods aren't affected @@ -668,12 +668,12 @@ object MTLSubmarineTest { def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allow[String] { for { - _ <- methodRaise[F].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].ensure(new Exception(""))(_ => false) + _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[F].void _ <- methodRaise[F].tupleRight("") @@ -723,12 +723,12 @@ object MTLSubmarineTest { _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling // monad error - _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].reject { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) + _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[IO].void _ <- methodRaise[IO].debug() diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 354c926..76b67c0 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -31,6 +31,25 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { SymbolMatcher.exact("cats/ApplicativeError#") + SymbolMatcher.exact("cats/MonadError#") + private val ErrorHandlingMethods = Set( + "adaptErr", + "adaptError", + "attempt", + "attemptNarrow", + "attemptT", + "attemptTap", + "handleError", + "handleErrorWith", + "onError", + "orElse", + "orRaise", + "recover", + "recoverWith", + "redeem", + "redeemWith", + "voidError" + ) + private val Raise_M = SymbolMatcher.exact("cats/mtl/Raise#") @@ -63,17 +82,23 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { doc.tree.collect { // syntax, e.g. `raise.onError { e => ??? }` case t @ Term.Apply.After_4_6_0(Term.Select(qual, Term.Name(name)), _) - if Syntax_M.matches(t.symbol.owner) && producedByRaise(qual) => + if Syntax_M.matches(t.symbol.owner) && + ErrorHandlingMethods(name) && + producedByRaise(qual) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) // syntax, e.g. `raise.voidError` case t @ Term.Select(qual, Term.Name(name)) - if Syntax_M.matches(t.symbol.owner) && producedByRaise(qual) => + if Syntax_M.matches(t.symbol.owner) && + ErrorHandlingMethods(name) && + producedByRaise(qual) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) // direct, e.g. `Async[F].onError(raise) { e => ??? }` case t @ Term.Apply.After_4_6_0(_, _) - if isOwner(t.symbol, Direct_M) && anyArgComesFromRaise(t) => + if isOwner(t.symbol, Direct_M) && + calleeName(t).exists(ErrorHandlingMethods) && + anyArgComesFromRaise(t) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, None)) // IO direct methods, e.g. `raise.onError { e => ??? }` @@ -213,6 +238,16 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case _ => None } + @annotation.tailrec + private def calleeName(term: Term): Option[String] = + term match { + case Term.Apply.After_4_6_0(fun, _) => calleeName(fun) + case Term.ApplyType.After_4_6_0(fun, _) => calleeName(fun) + case Term.Select(_, Term.Name(methodName)) => Some(methodName) + case Term.Name(methodName) => Some(methodName) + case _ => None + } + } object MTLSubmarine { From 3d55dfc3b3ab79df76650e071da58d784da3b45b Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 3 Sep 2026 11:18:57 +0100 Subject: [PATCH 07/39] Inspect only the protected effect argument --- .../src/main/scala-2/fix/MTLSubmarineTest.scala | 12 ++++++++++++ .../src/main/scala-3/fix/MTLSubmarineTest.scala | 12 ++++++++++++ .../scala/org/typelevel/fix/MTLSubmarine.scala | 15 +++++++-------- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 878e869..b8ce4a5 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -176,6 +176,18 @@ object MTLSubmarineTest { } + object DirectProtectedArgument { + + def raiseDependentRecovery[F[_]](error: Throwable)(implicit r: Raise[F, String]): F[Unit] = + r.raise(error.getMessage) + + def normalSource[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].handleErrorWith(Async[F].unit)(raiseDependentRecovery[F]) + Async[F].redeemWith(Async[F].unit)(raiseDependentRecovery[F], _ => Async[F].unit) + } + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index f0fed69..8df952d 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -183,6 +183,18 @@ object MTLSubmarineTest { } + object DirectProtectedArgument { + + def raiseDependentRecovery[F[_]](error: Throwable)(using r: Raise[F, String]): F[Unit] = + r.raise(error.getMessage) + + def normalSource[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].handleErrorWith(Async[F].unit)(raiseDependentRecovery[F]) + Async[F].redeemWith(Async[F].unit)(raiseDependentRecovery[F], _ => Async[F].unit) + } + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 76b67c0..87f78ff 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -98,7 +98,7 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case t @ Term.Apply.After_4_6_0(_, _) if isOwner(t.symbol, Direct_M) && calleeName(t).exists(ErrorHandlingMethods) && - anyArgComesFromRaise(t) => + protectedEffectArg(t).exists(producedByRaise) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, None)) // IO direct methods, e.g. `raise.onError { e => ??? }` @@ -116,22 +116,21 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { private def originatesFromRaiseOperation(term: Term)(implicit doc: SemanticDocument) = calleeSymbol(term).exists(sym => isOwner(sym, RaiseAll_M)) - private def anyArgComesFromRaise(term: Term)(implicit doc: SemanticDocument): Boolean = { + private def protectedEffectArg(term: Term): Option[Term] = { @annotation.tailrec - def loop(t: Term): Boolean = + def loop(t: Term, candidate: Option[Term]): Option[Term] = t match { case Term.Apply.After_4_6_0(fun, args) => - if (args.exists(producedByRaise)) true - else loop(fun) + loop(fun, args.headOption.orElse(candidate)) case Term.ApplyType.After_4_6_0(fun, _) => - loop(fun) + loop(fun, candidate) case _ => - false + candidate } - loop(term) + loop(term, None) } private def isOwner(sym: Symbol, matcher: SymbolMatcher): Boolean = { From 5c99eb7b54c544dba93cbffd1da043c208d04ba7 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 3 Sep 2026 11:30:49 +0100 Subject: [PATCH 08/39] Detect context-function values requiring Raise --- .../src/main/scala-3/fix/MTLSubmarineTest.scala | 15 +++++++++++++++ .../scala/org/typelevel/fix/MTLSubmarine.scala | 10 +++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 8df952d..59245ad 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -195,6 +195,21 @@ object MTLSubmarineTest { } + object ContextFunctionValue { + + def valueHandling[F[_]: Async](using Raise[F, String]): F[Unit] = { + val action: Raise[F, String] ?=> F[Unit] = r ?=> r.raise("something went wrong") + + action.attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def parameterHandling[F[_]: Async]( + action: Raise[F, String] ?=> F[Unit] + )(using Raise[F, String]): F[Unit] = + action.recover(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 87f78ff..4b56d24 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -151,6 +151,9 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case m: MethodSignature => requiresRaiseViaParams(m) || requiresRaiseViaContextFunction(m) + case ValueSignature(tpe) => + requiresRaiseViaContextFunction(tpe) + case _ => false } @@ -169,7 +172,12 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { private def requiresRaiseViaContextFunction( m: MethodSignature )(implicit doc: SemanticDocument): Boolean = - m.returnType match { + requiresRaiseViaContextFunction(m.returnType) + + private def requiresRaiseViaContextFunction( + tpe: SemanticType + )(implicit doc: SemanticDocument): Boolean = + tpe match { case TypeRef(_, sym, args) if ContextFunction_M.matches(sym) => args.exists(typeIsRaise) From f9d4ab353d06b464e664b109d02f8e29ee6b8dc5 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 4 Sep 2026 17:12:27 +0100 Subject: [PATCH 09/39] Track submarine provenance through Cats combinators --- .../main/scala-2/fix/MTLSubmarineTest.scala | 34 ++++++ .../main/scala-3/fix/MTLSubmarineTest.scala | 34 ++++++ .../org/typelevel/fix/MTLSubmarine.scala | 105 +++++++++++++++++- 3 files changed, 168 insertions(+), 5 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index b8ce4a5..154dd5f 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -188,6 +188,40 @@ object MTLSubmarineTest { } + object EffectPropagation { + + def methodRaise[F[_]](implicit r: Raise[F, String]): F[Unit] = + r.raise("something went wrong") + + def syntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + methodRaise[F].map(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].void.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].as(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit *> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (methodRaise[F] <* Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit >> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].product(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.product(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.map2(methodRaise[F])((_, _) => ()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.map(identity).attempt + (for { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].unit + _ <- methodRaise[F] + } yield ()).attempt.void + } + + def direct[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].attempt(Async[F].map(methodRaise[F])(identity)).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => methodRaise[F])).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 59245ad..fba7d45 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -210,6 +210,40 @@ object MTLSubmarineTest { } + object EffectPropagation { + + def methodRaise[F[_]](using r: Raise[F, String]): F[Unit] = + r.raise("something went wrong") + + def syntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + methodRaise[F].map(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].void.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].as(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit *> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (methodRaise[F] <* Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit >> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].product(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.product(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.map2(methodRaise[F])((_, _) => ()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.map(identity).attempt + (for { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].unit + _ <- methodRaise[F] + } yield ()).attempt.void + } + + def direct[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].attempt(Async[F].map(methodRaise[F])(identity)).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => methodRaise[F])).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 4b56d24..2db134b 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -31,6 +31,27 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { SymbolMatcher.exact("cats/ApplicativeError#") + SymbolMatcher.exact("cats/MonadError#") + private val PropagationDirect_M = + SymbolMatcher.exact("cats/Functor#") + + SymbolMatcher.exact("cats/FlatMap#") + + SymbolMatcher.exact("cats/Apply#") + + SymbolMatcher.exact("cats/Applicative#") + + SymbolMatcher.exact("cats/Monad#") + + SymbolMatcher.exact("cats/Semigroupal#") + + private val UnaryPropagationMethods = Set("map", "void", "as") + private val BinaryPropagationMethods = + Set( + "product", + "productL", + "productR", + "map2", + ">>", + "*>", + "<*" + ) + private val FlatMapPropagationMethods = Set("flatMap", ">>=", "flatTap", "mproduct") + private val ErrorHandlingMethods = Set( "adaptErr", "adaptError", @@ -111,11 +132,84 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { }.asPatch private def producedByRaise(qual: Term)(implicit doc: SemanticDocument) = - methodRequiresImplicitRaise(qual) || originatesFromRaiseOperation(qual) + methodRequiresImplicitRaise(qual) || + originatesFromRaiseOperation(qual) || + originatesFromPropagation(qual) || + originatesFromForComprehension(qual) private def originatesFromRaiseOperation(term: Term)(implicit doc: SemanticDocument) = calleeSymbol(term).exists(sym => isOwner(sym, RaiseAll_M)) + private def originatesFromPropagation(term: Term)(implicit doc: SemanticDocument): Boolean = + appliedCall(term).exists { case (receiver, method, argumentLists) => + calleeSymbol(term).exists { symbol => + val isCatsOperation = symbol.value.startsWith("cats/") + val isDirect = isOwner(symbol, PropagationDirect_M) + val firstArguments = argumentLists.headOption.getOrElse(Nil) + + if (!isCatsOperation) false + else if (UnaryPropagationMethods(method)) { + val source = if (isDirect) firstArguments.headOption else Some(receiver) + source.exists(producedByRaise) + } else if (BinaryPropagationMethods(method)) { + val sources = if (isDirect) firstArguments.take(2) else receiver :: firstArguments.take(1) + sources.exists(producedByRaise) + } else if (FlatMapPropagationMethods(method)) { + val source = if (isDirect) firstArguments.headOption else Some(receiver) + val callback = + if (isDirect) argumentLists.drop(1).headOption.flatMap(_.headOption) + else firstArguments.headOption + + source.exists(producedByRaise) || callback.exists(callbackProducesRaise) + } else false + } + } + + private def callbackProducesRaise(term: Term)(implicit doc: SemanticDocument): Boolean = + term match { + case Term.Function.After_4_6_0(_, body: Term) => producedByRaise(body) + case other => producedByRaise(other) + } + + private def originatesFromForComprehension( + term: Term + )(implicit doc: SemanticDocument): Boolean = { + def generatorsProduceRaise(enumerators: List[Enumerator]): Boolean = + enumerators.exists { + case Enumerator.Generator(_, rhs) => producedByRaise(rhs) + case _ => false + } + + term match { + case Term.For.After_4_9_9(enumerators, _) => generatorsProduceRaise(enumerators) + case Term.ForYield.After_4_9_9(enumerators, _) => generatorsProduceRaise(enumerators) + case _ => false + } + } + + private def appliedCall(term: Term): Option[(Term, String, List[List[Term]])] = { + @annotation.tailrec + def loop(t: Term, argumentLists: List[List[Term]]): Option[(Term, String, List[List[Term]])] = + t match { + case Term.Apply.After_4_6_0(fun, args) => + loop(fun, args :: argumentLists) + + case Term.ApplyType.After_4_6_0(fun, _) => + loop(fun, argumentLists) + + case Term.ApplyInfix.Initial(receiver, Term.Name(method), _, args) => + Some((receiver, method, List(args))) + + case Term.Select(receiver, Term.Name(method)) => + Some((receiver, method, argumentLists)) + + case _ => + None + } + + loop(term, Nil) + } + private def protectedEffectArg(term: Term): Option[Term] = { @annotation.tailrec def loop(t: Term, candidate: Option[Term]): Option[Term] = @@ -229,10 +323,11 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { private def calleeSymbol(term: Term)(implicit doc: SemanticDocument): Option[Symbol] = term match { - case Term.Apply.After_4_6_0(fun, _) => calleeSymbol(fun) - case Term.ApplyType.After_4_6_0(fun, _) => calleeSymbol(fun) - case Term.Select(_, n) => Some(n.symbol) - case n: Term.Name => Some(n.symbol) + case Term.Apply.After_4_6_0(fun, _) => calleeSymbol(fun) + case Term.ApplyType.After_4_6_0(fun, _) => calleeSymbol(fun) + case Term.ApplyInfix.Initial(_, op, _, _) => Some(op.symbol) + case Term.Select(_, n) => Some(n.symbol) + case n: Term.Name => Some(n.symbol) case Term.Block(stats) => stats.lastOption.collect { case t: Term => t }.flatMap(calleeSymbol) From b2015ae3be6dcbcfbac4426415747189098aadea Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 4 Sep 2026 17:15:57 +0100 Subject: [PATCH 10/39] Preserve submarine provenance through type ascriptions --- .../src/main/scala-2/fix/MTLSubmarineTest.scala | 6 ++++++ .../src/main/scala-3/fix/MTLSubmarineTest.scala | 6 ++++++ .../scala/org/typelevel/fix/MTLSubmarine.scala | 14 +++++++++----- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 154dd5f..ede9704 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -220,6 +220,12 @@ object MTLSubmarineTest { Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => methodRaise[F])).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling } + def expressionWrappers[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + (methodRaise[F]: F[Unit]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (((methodRaise[F]))).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit: F[Unit]).attempt.void + } + } object RaiseObject { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index fba7d45..111379c 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -242,6 +242,12 @@ object MTLSubmarineTest { Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => methodRaise[F])).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling } + def expressionWrappers[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + (methodRaise[F]: F[Unit]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (((methodRaise[F]))).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit: F[Unit]).attempt.void + } + } object RaiseObject { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 2db134b..b381bdd 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -131,11 +131,15 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) }.asPatch - private def producedByRaise(qual: Term)(implicit doc: SemanticDocument) = - methodRequiresImplicitRaise(qual) || - originatesFromRaiseOperation(qual) || - originatesFromPropagation(qual) || - originatesFromForComprehension(qual) + private def producedByRaise(term: Term)(implicit doc: SemanticDocument): Boolean = + term match { + case Term.Ascribe(expr, _) => producedByRaise(expr) + case _ => + methodRequiresImplicitRaise(term) || + originatesFromRaiseOperation(term) || + originatesFromPropagation(term) || + originatesFromForComprehension(term) + } private def originatesFromRaiseOperation(term: Term)(implicit doc: SemanticDocument) = calleeSymbol(term).exists(sym => isOwner(sym, RaiseAll_M)) From 6977d832bf5bcc66215e2406ba5bba524f24ee04 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 4 Sep 2026 17:38:55 +0100 Subject: [PATCH 11/39] Inspect both branches for submarine provenance --- .../mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala | 6 ++++++ .../mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala | 6 ++++++ .../src/main/scala/org/typelevel/fix/MTLSubmarine.scala | 2 ++ 3 files changed, 14 insertions(+) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index ede9704..e49baad 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -226,6 +226,12 @@ object MTLSubmarineTest { (Async[F].unit: F[Unit]).attempt.void } + def conditional[F[_]: Async](condition: Boolean)(implicit r: Raise[F, String]): F[Unit] = { + (if (condition) Async[F].unit else methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (if (condition) methodRaise[F] else Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (if (condition) Async[F].unit else Async[F].unit).attempt.void + } + } object RaiseObject { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 111379c..5a05c47 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -248,6 +248,12 @@ object MTLSubmarineTest { (Async[F].unit: F[Unit]).attempt.void } + def conditional[F[_]: Async](condition: Boolean)(using r: Raise[F, String]): F[Unit] = { + (if (condition) Async[F].unit else methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (if (condition) methodRaise[F] else Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (if (condition) Async[F].unit else Async[F].unit).attempt.void + } + } object RaiseObject { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index b381bdd..2dffdcb 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -134,6 +134,8 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { private def producedByRaise(term: Term)(implicit doc: SemanticDocument): Boolean = term match { case Term.Ascribe(expr, _) => producedByRaise(expr) + case Term.If.After_4_4_0(_, thenBranch, elseBranch, _) => + producedByRaise(thenBranch) || producedByRaise(elseBranch) case _ => methodRequiresImplicitRaise(term) || originatesFromRaiseOperation(term) || From 5a7a89ce75c812441952c053e64e7fb6f7e57aa6 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 4 Sep 2026 17:42:16 +0100 Subject: [PATCH 12/39] Inspect all match cases for submarine provenance --- .../src/main/scala-2/fix/MTLSubmarineTest.scala | 15 +++++++++++++++ .../src/main/scala-3/fix/MTLSubmarineTest.scala | 15 +++++++++++++++ .../scala/org/typelevel/fix/MTLSubmarine.scala | 2 ++ 3 files changed, 32 insertions(+) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index e49baad..224b14c 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -232,6 +232,21 @@ object MTLSubmarineTest { (if (condition) Async[F].unit else Async[F].unit).attempt.void } + def matched[F[_]: Async](condition: Boolean)(implicit r: Raise[F, String]): F[Unit] = { + (condition match { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + case true => Async[F].unit + case false => methodRaise[F] + }).attempt + (condition match { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + case true => methodRaise[F] + case false => Async[F].unit + }).attempt + (condition match { + case true => Async[F].unit + case false => Async[F].unit + }).attempt.void + } + } object RaiseObject { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 5a05c47..80ad40c 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -254,6 +254,21 @@ object MTLSubmarineTest { (if (condition) Async[F].unit else Async[F].unit).attempt.void } + def matched[F[_]: Async](condition: Boolean)(using r: Raise[F, String]): F[Unit] = { + (condition match { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + case true => Async[F].unit + case false => methodRaise[F] + }).attempt + (condition match { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + case true => methodRaise[F] + case false => Async[F].unit + }).attempt + (condition match { + case true => Async[F].unit + case false => Async[F].unit + }).attempt.void + } + } object RaiseObject { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 2dffdcb..0dadc2e 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -136,6 +136,8 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case Term.Ascribe(expr, _) => producedByRaise(expr) case Term.If.After_4_4_0(_, thenBranch, elseBranch, _) => producedByRaise(thenBranch) || producedByRaise(elseBranch) + case Term.Match.After_4_9_9(_, cases, _) => + cases.exists(c => producedByRaise(c.body)) case _ => methodRequiresImplicitRaise(term) || originatesFromRaiseOperation(term) || From bf16ac0639434206bc2890c1a84c01ed791b8448 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sat, 5 Sep 2026 12:02:58 +0100 Subject: [PATCH 13/39] Remove custom Raise/Handle subtype discovery and corresponding test --- .../main/scala-2/fix/MTLSubmarineTest.scala | 49 +++++++++---------- .../main/scala-3/fix/MTLSubmarineTest.scala | 49 +++++++++---------- .../org/typelevel/fix/MTLSubmarine.scala | 24 ++++----- 3 files changed, 56 insertions(+), 66 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 224b14c..311f768 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -117,32 +117,6 @@ object MTLSubmarineTest { } - object CustomRaiseSubtype { - - trait CustomRaise[F[_], E] extends Raise[F, E] - trait IndirectRaise[F[_], E] extends CustomRaise[F, E] - trait CustomHandle[F[_], E] extends Handle[F, E] - - def customRaise[F[_]](implicit r: CustomRaise[F, String]): F[Unit] = - r.raise("something went wrong") - - def indirectRaise[F[_]](implicit r: IndirectRaise[F, String]): F[Unit] = - r.raise("something went wrong") - - def customHandle[F[_]](implicit h: CustomHandle[F, String]): F[Unit] = - h.raise("something went wrong") - - def customRaiseHandling[F[_]: Async](implicit r: CustomRaise[F, String]): F[Unit] = - customRaise[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def indirectRaiseHandling[F[_]: Async](implicit r: IndirectRaise[F, String]): F[Unit] = - Async[F].attempt(indirectRaise[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def customHandleHandling[F[_]: Async](implicit h: CustomHandle[F, String]): F[Unit] = - customHandle[F].handleErrorWith(_ => Async[F].unit).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - } - object RaiseAlias { type AliasedRaise[F[_], E] = Raise[F, E] @@ -176,6 +150,18 @@ object MTLSubmarineTest { } + object WrappedRaise { + + trait Wrapper[A] + + def normal[F[_]: Async](implicit wrapper: Wrapper[Raise[F, String]]): F[Unit] = + Async[F].unit + + def handlingNormal[F[_]: Async](implicit wrapper: Wrapper[Raise[F, String]]): F[Unit] = + normal[F].attempt.void + + } + object DirectProtectedArgument { def raiseDependentRecovery[F[_]](error: Throwable)(implicit r: Raise[F, String]): F[Unit] = @@ -247,6 +233,17 @@ object MTLSubmarineTest { }).attempt.void } + def blocks[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + ({ // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit + methodRaise[F] + }).attempt + ({ + methodRaise[F] + Async[F].unit + }).attempt.void + } + } object RaiseObject { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 80ad40c..57c6259 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -124,32 +124,6 @@ object MTLSubmarineTest { } - object CustomRaiseSubtype { - - trait CustomRaise[F[_], E] extends Raise[F, E] - trait IndirectRaise[F[_], E] extends CustomRaise[F, E] - trait CustomHandle[F[_], E] extends Handle[F, E] - - def customRaise[F[_]](using r: CustomRaise[F, String]): F[Unit] = - r.raise("something went wrong") - - def indirectRaise[F[_]](using r: IndirectRaise[F, String]): F[Unit] = - r.raise("something went wrong") - - def customHandle[F[_]](using h: CustomHandle[F, String]): F[Unit] = - h.raise("something went wrong") - - def customRaiseHandling[F[_]: Async](using r: CustomRaise[F, String]): F[Unit] = - customRaise[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def indirectRaiseHandling[F[_]: Async](using r: IndirectRaise[F, String]): F[Unit] = - Async[F].attempt(indirectRaise[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def customHandleHandling[F[_]: Async](using h: CustomHandle[F, String]): F[Unit] = - customHandle[F].handleErrorWith(_ => Async[F].unit).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - } - object RaiseAlias { type AliasedRaise[F[_], E] = Raise[F, E] @@ -183,6 +157,18 @@ object MTLSubmarineTest { } + object WrappedRaise { + + trait Wrapper[A] + + def normal[F[_]: Async](using wrapper: Wrapper[Raise[F, String]]): F[Unit] = + Async[F].unit + + def handlingNormal[F[_]: Async](using wrapper: Wrapper[Raise[F, String]]): F[Unit] = + normal[F].attempt.void + + } + object DirectProtectedArgument { def raiseDependentRecovery[F[_]](error: Throwable)(using r: Raise[F, String]): F[Unit] = @@ -269,6 +255,17 @@ object MTLSubmarineTest { }).attempt.void } + def blocks[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + ({ // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit + methodRaise[F] + }).attempt + ({ + methodRaise[F] + Async[F].unit + }).attempt.void + } + } object RaiseObject { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 0dadc2e..803cc01 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -134,6 +134,8 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { private def producedByRaise(term: Term)(implicit doc: SemanticDocument): Boolean = term match { case Term.Ascribe(expr, _) => producedByRaise(expr) + case Term.Block(stats) => + stats.lastOption.collect { case result: Term => producedByRaise(result) }.getOrElse(false) case Term.If.After_4_4_0(_, thenBranch, elseBranch, _) => producedByRaise(thenBranch) || producedByRaise(elseBranch) case Term.Match.After_4_9_9(_, cases, _) => @@ -305,22 +307,16 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case TypeRef(_, sym, _) if Raise_M.matches(sym) || Handle_M.matches(sym) => true - case TypeRef(_, sym, args) => - val inherited = - !visited(sym) && doc.info(sym).exists { - _.signature match { - case c: ClassSignature => - c.parents.exists(typeIsRaise(_, visited + sym)) - - case t: TypeSignature => - typeIsRaise(t.upperBound, visited + sym) + case TypeRef(_, sym, _) => + !visited(sym) && doc.info(sym).exists { + _.signature match { + case t: TypeSignature => + typeIsRaise(t.upperBound, visited + sym) - case _ => - false - } + case _ => + false } - - inherited || args.exists(typeIsRaise(_, visited)) + } case AnnotatedType(_, underlying) => typeIsRaise(underlying, visited) From 77b673dc0a9d975efee77da0202a74e5d979ce9c Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 08:01:30 +0100 Subject: [PATCH 14/39] Track submarine provenance through local methods --- .../main/scala-2/fix/MTLSubmarineTest.scala | 25 +++++++ .../main/scala-3/fix/MTLSubmarineTest.scala | 29 +++++++ .../org/typelevel/fix/MTLSubmarine.scala | 75 ++++++++++++++----- 3 files changed, 110 insertions(+), 19 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 311f768..977d628 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -246,6 +246,31 @@ object MTLSubmarineTest { } + object LocalMethodCapture { + + def captured = Handle.allowF[IO, String] { implicit h => + def boom: IO[Unit] = h.raise("boom") + + boom.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def chainedCapture = Handle.allowF[IO, String] { implicit h => + def boom: IO[Unit] = h.raise("boom") + def indirect: IO[Unit] = boom + + indirect.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def normal = Handle.allowF[IO, String] { _ => + def safe: IO[Unit] = IO.unit + def recursive(remaining: Int): IO[Unit] = + if (remaining <= 0) IO.unit else recursive(remaining - 1) + + safe.attempt *> recursive(1).attempt + } + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 57c6259..367cf94 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -268,6 +268,35 @@ object MTLSubmarineTest { } + object LocalMethodCapture { + + def captured = Handle.allow[String] { + val h = summon[Handle[IO, String]] + + def boom: IO[Unit] = h.raise("boom") + + boom.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def chainedCapture = Handle.allow[String] { + val h = summon[Handle[IO, String]] + + def boom: IO[Unit] = h.raise("boom") + def indirect: IO[Unit] = boom + + indirect.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def normal = Handle.allow[String] { + def safe: IO[Unit] = IO.unit + def recursive(remaining: Int): IO[Unit] = + if remaining <= 0 then IO.unit else recursive(remaining - 1) + + safe.attempt *> recursive(1).attempt + } + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 803cc01..c205813 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -22,6 +22,8 @@ import scala.meta._ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { + private final class LocalMethodBodies(val bySymbol: Map[Symbol, Term]) + private val Syntax_M = SymbolMatcher.exact("cats/syntax/ApplicativeErrorOps#") + SymbolMatcher.exact("cats/syntax/ApplicativeErrorFUnitOps#") + @@ -99,7 +101,14 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { SymbolMatcher.normalized("cats/effect/IO#voidError().") + SymbolMatcher.normalized("cats/effect/IO#attemptTap().") - override def fix(implicit doc: SemanticDocument): Patch = + override def fix(implicit doc: SemanticDocument): Patch = { + implicit val localMethodBodies: LocalMethodBodies = new LocalMethodBodies( + doc.tree.collect { + case d: Defn.Def if d.parent.exists(_.isInstanceOf[Term.Block]) => + d.symbol -> d.body + }.toMap + ) + doc.tree.collect { // syntax, e.g. `raise.onError { e => ??? }` case t @ Term.Apply.After_4_6_0(Term.Select(qual, Term.Name(name)), _) @@ -119,7 +128,7 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case t @ Term.Apply.After_4_6_0(_, _) if isOwner(t.symbol, Direct_M) && calleeName(t).exists(ErrorHandlingMethods) && - protectedEffectArg(t).exists(producedByRaise) => + protectedEffectArg(t).exists(producedByRaise(_)) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, None)) // IO direct methods, e.g. `raise.onError { e => ??? }` @@ -130,27 +139,49 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case t @ Term.Select(qual, Term.Name(name)) if IO_M.matches(t) && producedByRaise(qual) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) }.asPatch + } - private def producedByRaise(term: Term)(implicit doc: SemanticDocument): Boolean = + private def producedByRaise( + term: Term, + visitedLocalMethods: Set[Symbol] = Set.empty + )(implicit doc: SemanticDocument, localMethodBodies: LocalMethodBodies): Boolean = term match { - case Term.Ascribe(expr, _) => producedByRaise(expr) + case Term.Ascribe(expr, _) => producedByRaise(expr, visitedLocalMethods) case Term.Block(stats) => - stats.lastOption.collect { case result: Term => producedByRaise(result) }.getOrElse(false) + stats.lastOption + .collect { case result: Term => producedByRaise(result, visitedLocalMethods) } + .getOrElse(false) case Term.If.After_4_4_0(_, thenBranch, elseBranch, _) => - producedByRaise(thenBranch) || producedByRaise(elseBranch) + producedByRaise(thenBranch, visitedLocalMethods) || + producedByRaise(elseBranch, visitedLocalMethods) case Term.Match.After_4_9_9(_, cases, _) => - cases.exists(c => producedByRaise(c.body)) + cases.exists(c => producedByRaise(c.body, visitedLocalMethods)) case _ => methodRequiresImplicitRaise(term) || originatesFromRaiseOperation(term) || - originatesFromPropagation(term) || - originatesFromForComprehension(term) + originatesFromLocalMethod(term, visitedLocalMethods) || + originatesFromPropagation(term, visitedLocalMethods) || + originatesFromForComprehension(term, visitedLocalMethods) + } + + private def originatesFromLocalMethod( + term: Term, + visitedLocalMethods: Set[Symbol] + )(implicit doc: SemanticDocument, localMethodBodies: LocalMethodBodies): Boolean = + calleeSymbol(term).exists { symbol => + !visitedLocalMethods(symbol) && + localMethodBodies.bySymbol + .get(symbol) + .exists(producedByRaise(_, visitedLocalMethods + symbol)) } private def originatesFromRaiseOperation(term: Term)(implicit doc: SemanticDocument) = calleeSymbol(term).exists(sym => isOwner(sym, RaiseAll_M)) - private def originatesFromPropagation(term: Term)(implicit doc: SemanticDocument): Boolean = + private def originatesFromPropagation( + term: Term, + visitedLocalMethods: Set[Symbol] + )(implicit doc: SemanticDocument, localMethodBodies: LocalMethodBodies): Boolean = appliedCall(term).exists { case (receiver, method, argumentLists) => calleeSymbol(term).exists { symbol => val isCatsOperation = symbol.value.startsWith("cats/") @@ -160,33 +191,39 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { if (!isCatsOperation) false else if (UnaryPropagationMethods(method)) { val source = if (isDirect) firstArguments.headOption else Some(receiver) - source.exists(producedByRaise) + source.exists(producedByRaise(_, visitedLocalMethods)) } else if (BinaryPropagationMethods(method)) { val sources = if (isDirect) firstArguments.take(2) else receiver :: firstArguments.take(1) - sources.exists(producedByRaise) + sources.exists(producedByRaise(_, visitedLocalMethods)) } else if (FlatMapPropagationMethods(method)) { val source = if (isDirect) firstArguments.headOption else Some(receiver) val callback = if (isDirect) argumentLists.drop(1).headOption.flatMap(_.headOption) else firstArguments.headOption - source.exists(producedByRaise) || callback.exists(callbackProducesRaise) + source.exists(producedByRaise(_, visitedLocalMethods)) || + callback.exists(callbackProducesRaise(_, visitedLocalMethods)) } else false } } - private def callbackProducesRaise(term: Term)(implicit doc: SemanticDocument): Boolean = + private def callbackProducesRaise( + term: Term, + visitedLocalMethods: Set[Symbol] + )(implicit doc: SemanticDocument, localMethodBodies: LocalMethodBodies): Boolean = term match { - case Term.Function.After_4_6_0(_, body: Term) => producedByRaise(body) - case other => producedByRaise(other) + case Term.Function.After_4_6_0(_, body: Term) => + producedByRaise(body, visitedLocalMethods) + case other => producedByRaise(other, visitedLocalMethods) } private def originatesFromForComprehension( - term: Term - )(implicit doc: SemanticDocument): Boolean = { + term: Term, + visitedLocalMethods: Set[Symbol] + )(implicit doc: SemanticDocument, localMethodBodies: LocalMethodBodies): Boolean = { def generatorsProduceRaise(enumerators: List[Enumerator]): Boolean = enumerators.exists { - case Enumerator.Generator(_, rhs) => producedByRaise(rhs) + case Enumerator.Generator(_, rhs) => producedByRaise(rhs, visitedLocalMethods) case _ => false } From 1ee7cb7c288f0feea25dda72b1c5a13257ab98e1 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 08:05:11 +0100 Subject: [PATCH 15/39] Track submarine provenance through local values --- .../main/scala-2/fix/MTLSubmarineTest.scala | 23 ++++++ .../main/scala-3/fix/MTLSubmarineTest.scala | 26 +++++++ .../org/typelevel/fix/MTLSubmarine.scala | 72 ++++++++++--------- 3 files changed, 88 insertions(+), 33 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 977d628..aa97ca6 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -271,6 +271,29 @@ object MTLSubmarineTest { } + object LocalValueCapture { + + def captured = Handle.allowF[IO, String] { implicit h => + val direct: IO[Unit] = h.raise("boom") + + direct.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def capturedMethod = Handle.allowF[IO, String] { implicit h => + def boom: IO[Unit] = h.raise("boom") + lazy val action: IO[Unit] = boom + + action.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def normal = Handle.allowF[IO, String] { _ => + val safe: IO[Unit] = IO.unit + + safe.attempt + } + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 367cf94..6810dfc 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -297,6 +297,32 @@ object MTLSubmarineTest { } + object LocalValueCapture { + + def captured = Handle.allow[String] { + val h = summon[Handle[IO, String]] + val direct: IO[Unit] = h.raise("boom") + + direct.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def capturedMethod = Handle.allow[String] { + val h = summon[Handle[IO, String]] + + def boom: IO[Unit] = h.raise("boom") + lazy val action: IO[Unit] = boom + + action.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def normal = Handle.allow[String] { + val safe: IO[Unit] = IO.unit + + safe.attempt + } + + } + object RaiseObject { def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index c205813..8029bfa 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -22,7 +22,7 @@ import scala.meta._ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { - private final class LocalMethodBodies(val bySymbol: Map[Symbol, Term]) + private final class LocalDefinitions(val bySymbol: Map[Symbol, Term]) private val Syntax_M = SymbolMatcher.exact("cats/syntax/ApplicativeErrorOps#") + @@ -102,11 +102,17 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { SymbolMatcher.normalized("cats/effect/IO#attemptTap().") override def fix(implicit doc: SemanticDocument): Patch = { - implicit val localMethodBodies: LocalMethodBodies = new LocalMethodBodies( + implicit val localDefinitions: LocalDefinitions = new LocalDefinitions( doc.tree.collect { case d: Defn.Def if d.parent.exists(_.isInstanceOf[Term.Block]) => - d.symbol -> d.body - }.toMap + List(d.symbol -> d.body) + + case d: Defn.Val if d.parent.exists(_.isInstanceOf[Term.Block]) => + d.pats match { + case List(p: Pat.Var) => List(p.symbol -> d.rhs) + case _ => Nil + } + }.flatten.toMap ) doc.tree.collect { @@ -143,36 +149,36 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { private def producedByRaise( term: Term, - visitedLocalMethods: Set[Symbol] = Set.empty - )(implicit doc: SemanticDocument, localMethodBodies: LocalMethodBodies): Boolean = + visitedLocalDefinitions: Set[Symbol] = Set.empty + )(implicit doc: SemanticDocument, localDefinitions: LocalDefinitions): Boolean = term match { - case Term.Ascribe(expr, _) => producedByRaise(expr, visitedLocalMethods) + case Term.Ascribe(expr, _) => producedByRaise(expr, visitedLocalDefinitions) case Term.Block(stats) => stats.lastOption - .collect { case result: Term => producedByRaise(result, visitedLocalMethods) } + .collect { case result: Term => producedByRaise(result, visitedLocalDefinitions) } .getOrElse(false) case Term.If.After_4_4_0(_, thenBranch, elseBranch, _) => - producedByRaise(thenBranch, visitedLocalMethods) || - producedByRaise(elseBranch, visitedLocalMethods) + producedByRaise(thenBranch, visitedLocalDefinitions) || + producedByRaise(elseBranch, visitedLocalDefinitions) case Term.Match.After_4_9_9(_, cases, _) => - cases.exists(c => producedByRaise(c.body, visitedLocalMethods)) + cases.exists(c => producedByRaise(c.body, visitedLocalDefinitions)) case _ => methodRequiresImplicitRaise(term) || originatesFromRaiseOperation(term) || - originatesFromLocalMethod(term, visitedLocalMethods) || - originatesFromPropagation(term, visitedLocalMethods) || - originatesFromForComprehension(term, visitedLocalMethods) + originatesFromLocalDefinition(term, visitedLocalDefinitions) || + originatesFromPropagation(term, visitedLocalDefinitions) || + originatesFromForComprehension(term, visitedLocalDefinitions) } - private def originatesFromLocalMethod( + private def originatesFromLocalDefinition( term: Term, - visitedLocalMethods: Set[Symbol] - )(implicit doc: SemanticDocument, localMethodBodies: LocalMethodBodies): Boolean = + visitedLocalDefinitions: Set[Symbol] + )(implicit doc: SemanticDocument, localDefinitions: LocalDefinitions): Boolean = calleeSymbol(term).exists { symbol => - !visitedLocalMethods(symbol) && - localMethodBodies.bySymbol + !visitedLocalDefinitions(symbol) && + localDefinitions.bySymbol .get(symbol) - .exists(producedByRaise(_, visitedLocalMethods + symbol)) + .exists(producedByRaise(_, visitedLocalDefinitions + symbol)) } private def originatesFromRaiseOperation(term: Term)(implicit doc: SemanticDocument) = @@ -180,8 +186,8 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { private def originatesFromPropagation( term: Term, - visitedLocalMethods: Set[Symbol] - )(implicit doc: SemanticDocument, localMethodBodies: LocalMethodBodies): Boolean = + visitedLocalDefinitions: Set[Symbol] + )(implicit doc: SemanticDocument, localDefinitions: LocalDefinitions): Boolean = appliedCall(term).exists { case (receiver, method, argumentLists) => calleeSymbol(term).exists { symbol => val isCatsOperation = symbol.value.startsWith("cats/") @@ -191,39 +197,39 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { if (!isCatsOperation) false else if (UnaryPropagationMethods(method)) { val source = if (isDirect) firstArguments.headOption else Some(receiver) - source.exists(producedByRaise(_, visitedLocalMethods)) + source.exists(producedByRaise(_, visitedLocalDefinitions)) } else if (BinaryPropagationMethods(method)) { val sources = if (isDirect) firstArguments.take(2) else receiver :: firstArguments.take(1) - sources.exists(producedByRaise(_, visitedLocalMethods)) + sources.exists(producedByRaise(_, visitedLocalDefinitions)) } else if (FlatMapPropagationMethods(method)) { val source = if (isDirect) firstArguments.headOption else Some(receiver) val callback = if (isDirect) argumentLists.drop(1).headOption.flatMap(_.headOption) else firstArguments.headOption - source.exists(producedByRaise(_, visitedLocalMethods)) || - callback.exists(callbackProducesRaise(_, visitedLocalMethods)) + source.exists(producedByRaise(_, visitedLocalDefinitions)) || + callback.exists(callbackProducesRaise(_, visitedLocalDefinitions)) } else false } } private def callbackProducesRaise( term: Term, - visitedLocalMethods: Set[Symbol] - )(implicit doc: SemanticDocument, localMethodBodies: LocalMethodBodies): Boolean = + visitedLocalDefinitions: Set[Symbol] + )(implicit doc: SemanticDocument, localDefinitions: LocalDefinitions): Boolean = term match { case Term.Function.After_4_6_0(_, body: Term) => - producedByRaise(body, visitedLocalMethods) - case other => producedByRaise(other, visitedLocalMethods) + producedByRaise(body, visitedLocalDefinitions) + case other => producedByRaise(other, visitedLocalDefinitions) } private def originatesFromForComprehension( term: Term, - visitedLocalMethods: Set[Symbol] - )(implicit doc: SemanticDocument, localMethodBodies: LocalMethodBodies): Boolean = { + visitedLocalDefinitions: Set[Symbol] + )(implicit doc: SemanticDocument, localDefinitions: LocalDefinitions): Boolean = { def generatorsProduceRaise(enumerators: List[Enumerator]): Boolean = enumerators.exists { - case Enumerator.Generator(_, rhs) => producedByRaise(rhs, visitedLocalMethods) + case Enumerator.Generator(_, rhs) => producedByRaise(rhs, visitedLocalDefinitions) case _ => false } From 1831e9d989cd47fc287d252d82067500bf2eb922 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 08:11:21 +0100 Subject: [PATCH 16/39] Document MTL submarine wrapper limitations --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 7934301..8181e55 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,12 @@ on expressions that require `cats.mtl.Raise[F, E]`. `Raise` provided by `Handle.allow` uses a traceless exception type called `cats.mtl.Handle#Submarine`, so handling it through `ApplicativeError`, `MonadError`, or `IO` error-handling methods is not always desirable. +The rule recognizes supported Cats and `IO` error-handling operations at their call +sites. It cannot reliably discover the same operations hidden inside arbitrary +user-defined wrappers. For example, a call such as `tolerate(raiseError)` is not reported +when `tolerate` calls `attempt` internally. Detecting that generally would require +unrestricted interprocedural analysis. + For example: ```scala import cats.effect.IO From d5d448bcbb3ef5dfa8ee7c39e8c035ea3f7c9034 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 08:19:10 +0100 Subject: [PATCH 17/39] Match partial and narrow submarine handlers precisely --- README.md | 6 + .../main/scala-2/fix/MTLSubmarineTest.scala | 24 ++++ .../main/scala-3/fix/MTLSubmarineTest.scala | 24 ++++ .../org/typelevel/fix/MTLSubmarine.scala | 122 +++++++++++++++--- 4 files changed, 161 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8181e55..cac5ecd 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,12 @@ user-defined wrappers. For example, a call such as `tolerate(raiseError)` is not when `tolerate` calls `attempt` internally. Detecting that generally would require unrestricted interprocedural analysis. +For partial and narrow handlers, the rule warns only when the handler can match +`Handle.Submarine`, which extends `RuntimeException`. Wildcards and patterns typed as +`RuntimeException`, `Exception`, or `Throwable` are reported, while provably disjoint +concrete classes such as `IOException` are allowed. Unresolved and otherwise uncertain +patterns are reported conservatively. + For example: ```scala import cats.effect.IO diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index aa97ca6..46e039f 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -8,6 +8,8 @@ import cats.mtl.syntax.raise._ import cats.effect._ import cats.syntax.all._ +import java.io.IOException + // scalafmt: { maxColumn = 160 } object MTLSubmarineTest { @@ -246,6 +248,28 @@ object MTLSubmarineTest { } + object PartialAndNarrowHandlers { + + def methodRaise[F[_]](implicit r: Raise[F, String]): F[Unit] = + r.raise("boom") + + def precise[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].attemptNarrow[IOException] + Async[F].attemptNarrow[IOException, Unit](methodRaise[F]) + methodRaise[F].recover { case _: RuntimeException => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].recover { case _: IOException => () } + Async[F].recover(methodRaise[F]) { case _: IOException => () } + methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def unknown[F[_]: Async](handler: PartialFunction[Throwable, Unit])(implicit + r: Raise[F, String] + ): F[Unit] = + methodRaise[F].recover(handler) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + } + object LocalMethodCapture { def captured = Handle.allowF[IO, String] { implicit h => diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 6810dfc..e15990f 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -8,6 +8,8 @@ import cats.mtl.syntax.raise.* import cats.effect.* import cats.syntax.all.* +import java.io.IOException + // scalafmt: { maxColumn = 160 } object MTLSubmarineTest { @@ -268,6 +270,28 @@ object MTLSubmarineTest { } + object PartialAndNarrowHandlers { + + def methodRaise[F[_]](using r: Raise[F, String]): F[Unit] = + r.raise("boom") + + def precise[F[_]: Async](using Raise[F, String]): F[Unit] = { + methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].attemptNarrow[IOException] + Async[F].attemptNarrow[IOException, Unit](methodRaise[F]) + methodRaise[F].recover { case _: RuntimeException => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].recover { case _: IOException => () } + Async[F].recover(methodRaise[F]) { case _: IOException => () } + methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def unknown[F[_]: Async](handler: PartialFunction[Throwable, Unit])(using + Raise[F, String] + ): F[Unit] = + methodRaise[F].recover(handler) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + } + object LocalMethodCapture { def captured = Handle.allow[String] { diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 8029bfa..b9eb6f6 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -73,6 +73,20 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { "voidError" ) + private val ParameterlessErrorHandlingMethods = + Set("attempt", "attemptNarrow", "attemptT", "voidError") + + private val PartialErrorHandlingMethods = + Set("adaptErr", "adaptError", "onError", "recover", "recoverWith") + + private val SubmarineClassSupertypes_M = + SymbolMatcher.exact("java/lang/RuntimeException#") + + SymbolMatcher.exact("java/lang/Exception#") + + SymbolMatcher.exact("java/lang/Throwable#") + + SymbolMatcher.exact("java/lang/Object#") + + SymbolMatcher.exact("scala/Any#") + + SymbolMatcher.exact("scala/AnyRef#") + private val Raise_M = SymbolMatcher.exact("cats/mtl/Raise#") @@ -103,16 +117,19 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { override def fix(implicit doc: SemanticDocument): Patch = { implicit val localDefinitions: LocalDefinitions = new LocalDefinitions( - doc.tree.collect { - case d: Defn.Def if d.parent.exists(_.isInstanceOf[Term.Block]) => - List(d.symbol -> d.body) - - case d: Defn.Val if d.parent.exists(_.isInstanceOf[Term.Block]) => - d.pats match { - case List(p: Pat.Var) => List(p.symbol -> d.rhs) - case _ => Nil - } - }.flatten.toMap + doc.tree + .collect { + case d: Defn.Def if d.parent.exists(_.isInstanceOf[Term.Block]) => + List(d.symbol -> d.body) + + case d: Defn.Val if d.parent.exists(_.isInstanceOf[Term.Block]) => + d.pats match { + case List(p: Pat.Var) => List(p.symbol -> d.rhs) + case _ => Nil + } + } + .flatten + .toMap ) doc.tree.collect { @@ -120,33 +137,108 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case t @ Term.Apply.After_4_6_0(Term.Select(qual, Term.Name(name)), _) if Syntax_M.matches(t.symbol.owner) && ErrorHandlingMethods(name) && + handlerMayObserveSubmarine(t, name) && producedByRaise(qual) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) // syntax, e.g. `raise.voidError` case t @ Term.Select(qual, Term.Name(name)) if Syntax_M.matches(t.symbol.owner) && - ErrorHandlingMethods(name) && + ParameterlessErrorHandlingMethods(name) && + handlerMayObserveSubmarine(t, name) && producedByRaise(qual) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) // direct, e.g. `Async[F].onError(raise) { e => ??? }` case t @ Term.Apply.After_4_6_0(_, _) if isOwner(t.symbol, Direct_M) && - calleeName(t).exists(ErrorHandlingMethods) && + calleeName(t) + .exists(name => ErrorHandlingMethods(name) && handlerMayObserveSubmarine(t, name)) && protectedEffectArg(t).exists(producedByRaise(_)) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, None)) // IO direct methods, e.g. `raise.onError { e => ??? }` case t @ Term.Apply.After_4_6_0(sel @ Term.Select(qual, Term.Name(name)), _) - if IO_M.matches(sel) && producedByRaise(qual) => + if IO_M.matches(sel) && + handlerMayObserveSubmarine(t, name) && + producedByRaise(qual) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) - case t @ Term.Select(qual, Term.Name(name)) if IO_M.matches(t) && producedByRaise(qual) => + case t @ Term.Select(qual, Term.Name(name)) + if IO_M.matches(t) && + ParameterlessErrorHandlingMethods(name) && + handlerMayObserveSubmarine(t, name) && + producedByRaise(qual) => Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) }.asPatch } + private def handlerMayObserveSubmarine( + term: Term, + method: String + )(implicit doc: SemanticDocument): Boolean = + if (method == "attemptNarrow") + narrowErrorType(term).forall(typePatternMayMatchSubmarine) + else if (PartialErrorHandlingMethods(method)) + partialHandlerArgument(term).exists(partialFunctionMayMatchSubmarine) + else true + + private def narrowErrorType(term: Term): Option[Type] = { + @annotation.tailrec + def fromCall(t: Term): Option[Type] = + t match { + case Term.Apply.After_4_6_0(fun, _) => fromCall(fun) + case Term.ApplyType.After_4_6_0(_, tpes) => tpes.values.headOption + case Term.Select(_, _) | Term.Name(_) => fromParent(t) + case _ => None + } + + def fromParent(t: Term): Option[Type] = + t.parent.collect { case Term.ApplyType.After_4_6_0(_, tpes) => + tpes.values.headOption + }.flatten + + fromCall(term) + } + + private def partialHandlerArgument( + term: Term + )(implicit doc: SemanticDocument): Option[Term] = + appliedCall(term).flatMap { case (_, _, argumentLists) => + calleeSymbol(term).flatMap { symbol => + if (isOwner(symbol, Direct_M)) + argumentLists.drop(1).headOption.flatMap(_.headOption) + else argumentLists.headOption.flatMap(_.headOption) + } + } + + private def partialFunctionMayMatchSubmarine( + term: Term + )(implicit doc: SemanticDocument): Boolean = + term match { + case Term.PartialFunction(cases) => cases.exists(c => patternMayMatchSubmarine(c.pat)) + case _ => true + } + + private def patternMayMatchSubmarine(pat: Pat)(implicit doc: SemanticDocument): Boolean = + pat match { + case Pat.Wildcard() => true + case Pat.Typed(_, tpe) => typePatternMayMatchSubmarine(tpe) + case Pat.Alternative(a, b) => patternMayMatchSubmarine(a) || patternMayMatchSubmarine(b) + case Pat.Bind(_, nested) => patternMayMatchSubmarine(nested) + case _: Lit => false + case _ => true + } + + private def typePatternMayMatchSubmarine( + tpe: Type + )(implicit doc: SemanticDocument): Boolean = { + val symbol = tpe.symbol + + SubmarineClassSupertypes_M.matches(symbol) || + doc.info(symbol).forall(info => !info.isClass) + } + private def producedByRaise( term: Term, visitedLocalDefinitions: Set[Symbol] = Set.empty @@ -159,7 +251,7 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { .getOrElse(false) case Term.If.After_4_4_0(_, thenBranch, elseBranch, _) => producedByRaise(thenBranch, visitedLocalDefinitions) || - producedByRaise(elseBranch, visitedLocalDefinitions) + producedByRaise(elseBranch, visitedLocalDefinitions) case Term.Match.After_4_9_9(_, cases, _) => cases.exists(c => producedByRaise(c.body, visitedLocalDefinitions)) case _ => From 58400d1ff6e8685319e502d1e1513fb47722c5e5 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 08:20:26 +0100 Subject: [PATCH 18/39] Document conservative Raise capability detection --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index cac5ecd..af20a27 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,13 @@ on expressions that require `cats.mtl.Raise[F, E]`. `Raise` provided by `Handle.allow` uses a traceless exception type called `cats.mtl.Handle#Submarine`, so handling it through `ApplicativeError`, `MonadError`, or `IO` error-handling methods is not always desirable. +Detection is intentionally based on the declared `Raise` capability rather than the +specific instance selected at a call site. Consequently, the rule may also report code +where `Raise` is satisfied by an ordinary `ApplicativeError`-backed `Handle` which raises +the underlying error directly and does not use `Handle.Submarine`. This conservative +boundary keeps the rule consistent across methods and source modules, where instance +provenance is not reliably available. + The rule recognizes supported Cats and `IO` error-handling operations at their call sites. It cannot reliably discover the same operations hidden inside arbitrary user-defined wrappers. For example, a call such as `tolerate(raiseError)` is not reported From b2b349e3466b89e29da4872347d6e053741f6e9c Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 08:22:00 +0100 Subject: [PATCH 19/39] Document cross-version MTL submarine coverage --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index af20a27..d4dfb26 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,11 @@ For partial and narrow handlers, the rule warns only when the handler can match concrete classes such as `IOException` are allowed. Unresolved and otherwise uncertain patterns are reported conservatively. +The supported behavior is consistent across Scala 2.13 and Scala 3 for constructs +available in both versions. Scala 3 additionally supports capabilities represented as +context-function methods or values, such as `Raise[F, E] ?=> F[A]`; Scala 2.13 has no +equivalent language representation. + For example: ```scala import cats.effect.IO From 113ffa23a61201d1e077c13861c5c5bcd076a3c7 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 08:25:01 +0100 Subject: [PATCH 20/39] Document MTL submarine test matrix --- modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala | 4 ++++ modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 46e039f..34d222f 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -13,6 +13,10 @@ import java.io.IOException // scalafmt: { maxColumn = 160 } object MTLSubmarineTest { + // Test matrix: objects select the provenance source or propagation shape; methods + // select the handler form and effect type; this file selects Scala 2.13; an `assert` + // comment means a diagnostic is expected, while its absence means the call is safe. + object RaiseInstance { def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index e15990f..3488e1c 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -13,6 +13,10 @@ import java.io.IOException // scalafmt: { maxColumn = 160 } object MTLSubmarineTest { + // Test matrix: objects select the provenance source or propagation shape; methods + // select the handler form and effect type; this file selects Scala 3; an `assert` + // comment means a diagnostic is expected, while its absence means the call is safe. + object RaiseInstance { def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { From 2c3e93470a61d780b605314971afc54375c4faa1 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 08:28:22 +0100 Subject: [PATCH 21/39] Add paired submarine provenance tests --- .../main/scala-2/fix/MTLSubmarineTest.scala | 25 ++++++++++++++ .../main/scala-3/fix/MTLSubmarineTest.scala | 34 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 34d222f..42f38da 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -194,6 +194,8 @@ object MTLSubmarineTest { (Async[F].unit >> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling methodRaise[F].product(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.product(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].productL(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.productR(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.map2(methodRaise[F])((_, _) => ()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling @@ -210,6 +212,29 @@ object MTLSubmarineTest { def direct[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { Async[F].attempt(Async[F].map(methodRaise[F])(identity)).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => methodRaise[F])).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Async[F].map(Async[F].unit)(identity)).void + Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => Async[F].unit)).void + } + + def safeSyntax[F[_]: Async]: F[Unit] = { + Async[F].unit.map(identity).attempt + Async[F].unit.void.recover(_ => ()) + Async[F].unit.as(()).attempt + (Async[F].unit *> Async[F].unit).attempt + (Async[F].unit <* Async[F].unit).attempt + (Async[F].unit >> Async[F].unit).attempt + Async[F].unit.product(Async[F].unit).attempt + Async[F].unit.productL(Async[F].unit).attempt + Async[F].unit.productR(Async[F].unit).attempt + Async[F].unit.map2(Async[F].unit)((_, _) => ()).attempt + Async[F].unit.flatMap(_ => Async[F].unit).attempt + Async[F].unit.flatTap(_ => Async[F].unit).attempt + (Async[F].unit >>= (_ => Async[F].unit)).attempt + Async[F].unit.mproduct(_ => Async[F].unit).attempt + (for { + _ <- Async[F].unit + _ <- Async[F].unit + } yield ()).attempt.void } def expressionWrappers[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 3488e1c..e46b703 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -115,6 +115,9 @@ object MTLSubmarineTest { def contextFunctionMethod[F[_]]: Handle[F, String] ?=> F[Unit] = summon[Handle[F, String]].raise("something went wrong") + def normalContextFunctionMethod[F[_]]: Async[F] ?=> F[Unit] = + summon[Async[F]].unit + def applicativeErrorSyntax[F[_]: Async](using h: Handle[F, String]): F[Unit] = method[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling @@ -128,6 +131,9 @@ object MTLSubmarineTest { contextFunctionMethod[IO].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling } + def normalContextFunction(using Async[IO]): IO[Unit] = + normalContextFunctionMethod[IO].attempt.void + } object RaiseAlias { @@ -200,6 +206,9 @@ object MTLSubmarineTest { )(using Raise[F, String]): F[Unit] = action.recover(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + def normalParameter[F[_]: Async](action: Async[F] ?=> F[Unit]): F[Unit] = + action.attempt.void + } object EffectPropagation { @@ -216,6 +225,8 @@ object MTLSubmarineTest { (Async[F].unit >> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling methodRaise[F].product(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.product(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].productL(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.productR(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.map2(methodRaise[F])((_, _) => ()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling @@ -232,6 +243,29 @@ object MTLSubmarineTest { def direct[F[_]: Async](using r: Raise[F, String]): F[Unit] = { Async[F].attempt(Async[F].map(methodRaise[F])(identity)).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => methodRaise[F])).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Async[F].map(Async[F].unit)(identity)).void + Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => Async[F].unit)).void + } + + def safeSyntax[F[_]: Async]: F[Unit] = { + Async[F].unit.map(identity).attempt + Async[F].unit.void.recover(_ => ()) + Async[F].unit.as(()).attempt + (Async[F].unit *> Async[F].unit).attempt + (Async[F].unit <* Async[F].unit).attempt + (Async[F].unit >> Async[F].unit).attempt + Async[F].unit.product(Async[F].unit).attempt + Async[F].unit.productL(Async[F].unit).attempt + Async[F].unit.productR(Async[F].unit).attempt + Async[F].unit.map2(Async[F].unit)((_, _) => ()).attempt + Async[F].unit.flatMap(_ => Async[F].unit).attempt + Async[F].unit.flatTap(_ => Async[F].unit).attempt + (Async[F].unit >>= (_ => Async[F].unit)).attempt + Async[F].unit.mproduct(_ => Async[F].unit).attempt + (for { + _ <- Async[F].unit + _ <- Async[F].unit + } yield ()).attempt.void } def expressionWrappers[F[_]: Async](using r: Raise[F, String]): F[Unit] = { From d3b73ccd9619b5fa7a4ba6742f15573a4af0a9d6 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 10:39:25 +0100 Subject: [PATCH 22/39] Align submarine documentation with detection policy --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d4dfb26..ea87736 100644 --- a/README.md +++ b/README.md @@ -180,10 +180,11 @@ This rule works on variable declarations, usaged within methods as well as for c See https://typelevel.org/blog/2025/09/02/custom-error-types.html. -This rule forbids calling error-handling methods (`handleError`, `recover`, `onError`, etc.) -on expressions that require `cats.mtl.Raise[F, E]`. -`Raise` provided by `Handle.allow` uses a traceless exception type called `cats.mtl.Handle#Submarine`, -so handling it through `ApplicativeError`, `MonadError`, or `IO` error-handling methods is not always desirable. +This rule reports supported error-handling methods (`handleError`, `recover`, `onError`, +etc.) on expressions it identifies as `cats.mtl.Raise[F, E]`-capable. `Raise` provided +by `Handle.allow` uses a traceless exception type called `cats.mtl.Handle#Submarine`, so +handling it through `ApplicativeError`, `MonadError`, or `IO` error-handling methods is +not always desirable. Detection is intentionally based on the declared `Raise` capability rather than the specific instance selected at a call site. Consequently, the rule may also report code From ec04a1eb5cf973acdcd801853a824dce484ccf7b Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 10:40:12 +0100 Subject: [PATCH 23/39] Explain safe Handle error handling --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index ea87736..e507281 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,12 @@ the underlying error directly and does not use `Handle.Submarine`. This conserva boundary keeps the rule consistent across methods and source modules, where instance provenance is not reliably available. +Inside `Handle.allow`, prefer `Handle.handle` or `Handle.handleWith`. The active +`Handle` associates each raised value with a private marker and recognizes only +`Handle.Submarine` failures carrying that same marker. Other throwables—including +submarines belonging to a different `Handle.allow` boundary—propagate unchanged, so +typed error handling does not become general throwable handling. + The rule recognizes supported Cats and `IO` error-handling operations at their call sites. It cannot reliably discover the same operations hidden inside arbitrary user-defined wrappers. For example, a call such as `tolerate(raiseError)` is not reported From 7c1a096d688a3ae00b9b448db8af93988916cfa0 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 10:41:04 +0100 Subject: [PATCH 24/39] Document submarine rule limitations --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index e507281..1871064 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,13 @@ user-defined wrappers. For example, a call such as `tolerate(raiseError)` is not when `tolerate` calls `attempt` internally. Detecting that generally would require unrestricted interprocedural analysis. +Concrete member matching is limited to `IO`. Direct error-handling members on `SyncIO` +and other effects such as `fs2.Stream` are not recognized, and provenance is not traced +through constructors such as `Stream.eval`. Cats typeclass methods and extension syntax +remain supported when the protected expression's provenance is otherwise visible. Type +aliases of `Raise` and `Handle` are supported, but arbitrary user-defined subtypes are +not inferred as capabilities. + For partial and narrow handlers, the rule warns only when the handler can match `Handle.Submarine`, which extends `RuntimeException`. Wildcards and patterns typed as `RuntimeException`, `Exception`, or `Throwable` are reported, while provably disjoint From d8fca271821945a60b1cb0d24aee1b698f88672a Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 10:45:03 +0100 Subject: [PATCH 25/39] Focus submarine diagnostics on handler methods --- .../main/scala-2/fix/MTLSubmarineTest.scala | 16 ++++---- .../main/scala-3/fix/MTLSubmarineTest.scala | 16 ++++---- .../org/typelevel/fix/MTLSubmarine.scala | 38 +++++++++++-------- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 42f38da..575fd05 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -203,10 +203,10 @@ object MTLSubmarineTest { (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.map(identity).attempt - (for { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (for { _ <- Async[F].unit _ <- methodRaise[F] - } yield ()).attempt.void + } yield ()).attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling } def direct[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { @@ -250,14 +250,14 @@ object MTLSubmarineTest { } def matched[F[_]: Async](condition: Boolean)(implicit r: Raise[F, String]): F[Unit] = { - (condition match { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (condition match { case true => Async[F].unit case false => methodRaise[F] - }).attempt - (condition match { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (condition match { case true => methodRaise[F] case false => Async[F].unit - }).attempt + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling (condition match { case true => Async[F].unit case false => Async[F].unit @@ -265,10 +265,10 @@ object MTLSubmarineTest { } def blocks[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - ({ // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + ({ Async[F].unit methodRaise[F] - }).attempt + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling ({ methodRaise[F] Async[F].unit diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index e46b703..3d9d776 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -234,10 +234,10 @@ object MTLSubmarineTest { (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.map(identity).attempt - (for { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (for { _ <- Async[F].unit _ <- methodRaise[F] - } yield ()).attempt.void + } yield ()).attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling } def direct[F[_]: Async](using r: Raise[F, String]): F[Unit] = { @@ -281,14 +281,14 @@ object MTLSubmarineTest { } def matched[F[_]: Async](condition: Boolean)(using r: Raise[F, String]): F[Unit] = { - (condition match { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (condition match { case true => Async[F].unit case false => methodRaise[F] - }).attempt - (condition match { // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (condition match { case true => methodRaise[F] case false => Async[F].unit - }).attempt + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling (condition match { case true => Async[F].unit case false => Async[F].unit @@ -296,10 +296,10 @@ object MTLSubmarineTest { } def blocks[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - ({ // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + ({ Async[F].unit methodRaise[F] - }).attempt + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling ({ methodRaise[F] Async[F].unit diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index b9eb6f6..138fdd2 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -134,20 +134,20 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { doc.tree.collect { // syntax, e.g. `raise.onError { e => ??? }` - case t @ Term.Apply.After_4_6_0(Term.Select(qual, Term.Name(name)), _) + case t @ Term.Apply.After_4_6_0(Term.Select(qual, method @ Term.Name(name)), _) if Syntax_M.matches(t.symbol.owner) && ErrorHandlingMethods(name) && handlerMayObserveSubmarine(t, name) && producedByRaise(qual) => - Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) + Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(method, Some(name))) // syntax, e.g. `raise.voidError` - case t @ Term.Select(qual, Term.Name(name)) + case t @ Term.Select(qual, method @ Term.Name(name)) if Syntax_M.matches(t.symbol.owner) && ParameterlessErrorHandlingMethods(name) && handlerMayObserveSubmarine(t, name) && producedByRaise(qual) => - Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) + Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(method, Some(name))) // direct, e.g. `Async[F].onError(raise) { e => ??? }` case t @ Term.Apply.After_4_6_0(_, _) @@ -155,21 +155,26 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { calleeName(t) .exists(name => ErrorHandlingMethods(name) && handlerMayObserveSubmarine(t, name)) && protectedEffectArg(t).exists(producedByRaise(_)) => - Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, None)) + Patch.lint( + new MTLSubmarine.SubmarineErrorHandlingDiagnostic( + calleeNameTerm(t).getOrElse(t), + calleeName(t) + ) + ) // IO direct methods, e.g. `raise.onError { e => ??? }` - case t @ Term.Apply.After_4_6_0(sel @ Term.Select(qual, Term.Name(name)), _) + case t @ Term.Apply.After_4_6_0(sel @ Term.Select(qual, method @ Term.Name(name)), _) if IO_M.matches(sel) && handlerMayObserveSubmarine(t, name) && producedByRaise(qual) => - Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) + Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(method, Some(name))) - case t @ Term.Select(qual, Term.Name(name)) + case t @ Term.Select(qual, method @ Term.Name(name)) if IO_M.matches(t) && ParameterlessErrorHandlingMethods(name) && handlerMayObserveSubmarine(t, name) && producedByRaise(qual) => - Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(t, Some(name))) + Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(method, Some(name))) }.asPatch } @@ -479,14 +484,17 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case _ => None } - @annotation.tailrec private def calleeName(term: Term): Option[String] = + calleeNameTerm(term).map(_.value) + + @annotation.tailrec + private def calleeNameTerm(term: Term): Option[Term.Name] = term match { - case Term.Apply.After_4_6_0(fun, _) => calleeName(fun) - case Term.ApplyType.After_4_6_0(fun, _) => calleeName(fun) - case Term.Select(_, Term.Name(methodName)) => Some(methodName) - case Term.Name(methodName) => Some(methodName) - case _ => None + case Term.Apply.After_4_6_0(fun, _) => calleeNameTerm(fun) + case Term.ApplyType.After_4_6_0(fun, _) => calleeNameTerm(fun) + case Term.Select(_, method: Term.Name) => Some(method) + case method: Term.Name => Some(method) + case _ => None } } From 790306b2752c068d1df6cd4a47f4cf363457b138 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 11:00:02 +0100 Subject: [PATCH 26/39] Improve submarine diagnostic wording --- README.md | 2 +- .../main/scala/org/typelevel/fix/MTLSubmarine.scala | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1871064..f6a7299 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ See https://typelevel.org/blog/2025/09/02/custom-error-types.html. This rule reports supported error-handling methods (`handleError`, `recover`, `onError`, etc.) on expressions it identifies as `cats.mtl.Raise[F, E]`-capable. `Raise` provided -by `Handle.allow` uses a traceless exception type called `cats.mtl.Handle#Submarine`, so +by `Handle.allow` uses a traceless exception type called `cats.mtl.Handle.Submarine`, so handling it through `ApplicativeError`, `MonadError`, or `IO` error-handling methods is not always desirable. diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index 138fdd2..bc2d252 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -507,14 +507,14 @@ object MTLSubmarine { ) extends Diagnostic { override def message: String = { - val suchAs = method.fold("")(m => s", such as `$m`,") - s"Avoid calling error-handling methods$suchAs " + + val operation = method.fold("error-handling methods")(m => s"the error-handling method `$m`") + s"Avoid calling $operation " + s"on expressions that require `cats.mtl.Raise[F, *]`. " + - s"Errors raised through `Raise` represented by a traceless exception type `cats.mtl.Handle#Submarine`. " + - s"Handling them with `ApplicativeError`, `MonadError`, or `IO` error-handling " + - s"methods is not always desirable. " + + s"Errors raised through `Raise` may be represented by the traceless " + + s"`cats.mtl.Handle.Submarine` exception. Handling that exception with " + + s"`ApplicativeError`, `MonadError`, or `IO` can bypass marker-aware typed error handling. " + s"Use `cats.mtl.Handle[F, *].handle` or `cats.mtl.Handle[F, *].handleWith` " + - s"to manage these cases explicitly." + s"instead." } def position: Position = tree.pos From 94bee6a456c09693c9eda267ea232dce1539f699 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 12:33:23 +0100 Subject: [PATCH 27/39] Readme: update rule version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6a7299..f8a2e5e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats" // To add only cats-effect Scalafix rules ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats-effect" % "0.2.0" // To add only cats-mtl Scalafix rules -ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats-mtl" % "0.2.0" +ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-mtl" % "0.6.0" // To add only fs2 Scalafix rules ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-fs2" % "0.2.0" // To add only http4s Scalafix rules From 4cbcb914816ad0e53845dcef8fcaab0c2cb396fe Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 12:34:32 +0100 Subject: [PATCH 28/39] Readme: fix example --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f8a2e5e..137cecc 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,7 @@ For example: import cats.effect.IO import cats.mtl.{Raise, Handle} -def raiseError: Raise[IO, String] ?=> IO[Unit] = r => +def raiseError: Raise[IO, String] ?=> IO[Unit] = r ?=> r.raise("boom") def standardError: IO[Unit] = @@ -238,7 +238,7 @@ Handle.allow[String] { for { _ <- raiseError.onError(e => IO.println("Error: " + e)) // forbidden _ <- standardError.onError(e => IO.println("Error: " + e)) // allowed - } yield + } yield () } ``` From 69e7018293c2cfd5f42c914967828af646b5e85e Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 6 Sep 2026 12:39:46 +0100 Subject: [PATCH 29/39] Track more cats combinators --- .../main/scala-2/fix/MTLSubmarineTest.scala | 13 ++++++++++ .../main/scala-3/fix/MTLSubmarineTest.scala | 24 +++++++++++++++---- .../org/typelevel/fix/MTLSubmarine.scala | 4 +++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 575fd05..06ac996 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -6,6 +6,7 @@ package fix import cats.mtl._ import cats.mtl.syntax.raise._ import cats.effect._ +import cats.effect.implicits._ import cats.syntax.all._ import java.io.IOException @@ -197,6 +198,13 @@ object MTLSubmarineTest { methodRaise[F].productL(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.productR(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.map2(methodRaise[F])((_, _) => ()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].fproduct(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].fproductLeft(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].tupleLeft(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].tupleRight(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].as((_: Unit) => ()) + .<&>(Async[F].unit) + .attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling @@ -227,6 +235,11 @@ object MTLSubmarineTest { Async[F].unit.productL(Async[F].unit).attempt Async[F].unit.productR(Async[F].unit).attempt Async[F].unit.map2(Async[F].unit)((_, _) => ()).attempt + Async[F].unit.fproduct(identity).attempt + Async[F].unit.fproductLeft(identity).attempt + Async[F].unit.tupleLeft(()).attempt + Async[F].unit.tupleRight(()).attempt + Async[F].pure((_: Unit) => ()).<&>(Async[F].unit).attempt Async[F].unit.flatMap(_ => Async[F].unit).attempt Async[F].unit.flatTap(_ => Async[F].unit).attempt (Async[F].unit >>= (_ => Async[F].unit)).attempt diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 3d9d776..25b05db 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -6,6 +6,7 @@ package fix import cats.mtl.* import cats.mtl.syntax.raise.* import cats.effect.* +import cats.effect.implicits.* import cats.syntax.all.* import java.io.IOException @@ -228,11 +229,19 @@ object MTLSubmarineTest { methodRaise[F].productL(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.productR(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.map2(methodRaise[F])((_, _) => ()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].fproduct(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].fproductLeft(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].tupleLeft(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].tupleRight(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F] + .as((_: Unit) => ()) + .<&>(Async[F].unit) + .attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.map(identity).attempt (for { _ <- Async[F].unit @@ -258,6 +267,11 @@ object MTLSubmarineTest { Async[F].unit.productL(Async[F].unit).attempt Async[F].unit.productR(Async[F].unit).attempt Async[F].unit.map2(Async[F].unit)((_, _) => ()).attempt + Async[F].unit.fproduct(identity).attempt + Async[F].unit.fproductLeft(identity).attempt + Async[F].unit.tupleLeft(()).attempt + Async[F].unit.tupleRight(()).attempt + Async[F].pure((_: Unit) => ()).<&>(Async[F].unit).attempt Async[F].unit.flatMap(_ => Async[F].unit).attempt Async[F].unit.flatTap(_ => Async[F].unit).attempt (Async[F].unit >>= (_ => Async[F].unit)).attempt diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index bc2d252..a5303fe 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -41,13 +41,15 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { SymbolMatcher.exact("cats/Monad#") + SymbolMatcher.exact("cats/Semigroupal#") - private val UnaryPropagationMethods = Set("map", "void", "as") + private val UnaryPropagationMethods = + Set("map", "void", "as", "fproduct", "fproductLeft", "tupleLeft", "tupleRight") private val BinaryPropagationMethods = Set( "product", "productL", "productR", "map2", + "<&>", ">>", "*>", "<*" From 7954e36f090ecc95cb676d96d6a80d0cc36f45b3 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 7 Sep 2026 07:41:18 +0100 Subject: [PATCH 30/39] Add Call and HandlerCall utility classes --- .../org/typelevel/fix/MTLSubmarine.scala | 265 +++++++----------- 1 file changed, 109 insertions(+), 156 deletions(-) diff --git a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala index a5303fe..d4f73a7 100644 --- a/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala +++ b/modules/mtl/rules/src/main/scala/org/typelevel/fix/MTLSubmarine.scala @@ -24,6 +24,23 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { private final class LocalDefinitions(val bySymbol: Map[Symbol, Term]) + private final class Call( + val receiver: Option[Term], + val method: Term.Name, + val argumentLists: List[List[Term]], + val typeArguments: List[Type], + val callee: Term + ) { + def name: String = method.value + def symbol(implicit doc: SemanticDocument): Symbol = method.symbol + } + + private final class HandlerCall( + val call: Call, + val protectedEffect: Term, + val handler: Option[Term] + ) + private val Syntax_M = SymbolMatcher.exact("cats/syntax/ApplicativeErrorOps#") + SymbolMatcher.exact("cats/syntax/ApplicativeErrorFUnitOps#") + @@ -134,90 +151,43 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { .toMap ) - doc.tree.collect { - // syntax, e.g. `raise.onError { e => ??? }` - case t @ Term.Apply.After_4_6_0(Term.Select(qual, method @ Term.Name(name)), _) - if Syntax_M.matches(t.symbol.owner) && - ErrorHandlingMethods(name) && - handlerMayObserveSubmarine(t, name) && - producedByRaise(qual) => - Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(method, Some(name))) - - // syntax, e.g. `raise.voidError` - case t @ Term.Select(qual, method @ Term.Name(name)) - if Syntax_M.matches(t.symbol.owner) && - ParameterlessErrorHandlingMethods(name) && - handlerMayObserveSubmarine(t, name) && - producedByRaise(qual) => - Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(method, Some(name))) - - // direct, e.g. `Async[F].onError(raise) { e => ??? }` - case t @ Term.Apply.After_4_6_0(_, _) - if isOwner(t.symbol, Direct_M) && - calleeName(t) - .exists(name => ErrorHandlingMethods(name) && handlerMayObserveSubmarine(t, name)) && - protectedEffectArg(t).exists(producedByRaise(_)) => - Patch.lint( - new MTLSubmarine.SubmarineErrorHandlingDiagnostic( - calleeNameTerm(t).getOrElse(t), - calleeName(t) + def diagnostic(term: Term): Option[Patch] = + handlerCall(term) + .filter(handlerMayObserveSubmarine) + .filter(call => producedByRaise(call.protectedEffect)) + .map { handlerCall => + val call = handlerCall.call + Patch.lint( + new MTLSubmarine.SubmarineErrorHandlingDiagnostic(call.method, Some(call.name)) ) - ) - - // IO direct methods, e.g. `raise.onError { e => ??? }` - case t @ Term.Apply.After_4_6_0(sel @ Term.Select(qual, method @ Term.Name(name)), _) - if IO_M.matches(sel) && - handlerMayObserveSubmarine(t, name) && - producedByRaise(qual) => - Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(method, Some(name))) - - case t @ Term.Select(qual, method @ Term.Name(name)) - if IO_M.matches(t) && - ParameterlessErrorHandlingMethods(name) && - handlerMayObserveSubmarine(t, name) && - producedByRaise(qual) => - Patch.lint(new MTLSubmarine.SubmarineErrorHandlingDiagnostic(method, Some(name))) - }.asPatch + } + + doc.tree + .collect { + case t: Term.Apply => diagnostic(t) + case t: Term.ApplyType + if normalizedCall(t).exists(call => ParameterlessErrorHandlingMethods(call.name)) => + diagnostic(t) + case t: Term.Select + if !t.parent.exists(_.isInstanceOf[Term.ApplyType]) && + normalizedCall(t).exists(call => ParameterlessErrorHandlingMethods(call.name)) => + diagnostic(t) + } + .flatten + .asPatch } private def handlerMayObserveSubmarine( - term: Term, - method: String + handlerCall: HandlerCall )(implicit doc: SemanticDocument): Boolean = - if (method == "attemptNarrow") - narrowErrorType(term).forall(typePatternMayMatchSubmarine) - else if (PartialErrorHandlingMethods(method)) - partialHandlerArgument(term).exists(partialFunctionMayMatchSubmarine) + if (handlerCall.call.name == "attemptNarrow") + narrowErrorType(handlerCall.call).forall(typePatternMayMatchSubmarine) + else if (PartialErrorHandlingMethods(handlerCall.call.name)) + handlerCall.handler.exists(partialFunctionMayMatchSubmarine) else true - private def narrowErrorType(term: Term): Option[Type] = { - @annotation.tailrec - def fromCall(t: Term): Option[Type] = - t match { - case Term.Apply.After_4_6_0(fun, _) => fromCall(fun) - case Term.ApplyType.After_4_6_0(_, tpes) => tpes.values.headOption - case Term.Select(_, _) | Term.Name(_) => fromParent(t) - case _ => None - } - - def fromParent(t: Term): Option[Type] = - t.parent.collect { case Term.ApplyType.After_4_6_0(_, tpes) => - tpes.values.headOption - }.flatten - - fromCall(term) - } - - private def partialHandlerArgument( - term: Term - )(implicit doc: SemanticDocument): Option[Term] = - appliedCall(term).flatMap { case (_, _, argumentLists) => - calleeSymbol(term).flatMap { symbol => - if (isOwner(symbol, Direct_M)) - argumentLists.drop(1).headOption.flatMap(_.headOption) - else argumentLists.headOption.flatMap(_.headOption) - } - } + private def narrowErrorType(call: Call): Option[Type] = + call.typeArguments.headOption private def partialFunctionMayMatchSubmarine( term: Term @@ -262,48 +232,50 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { case Term.Match.After_4_9_9(_, cases, _) => cases.exists(c => producedByRaise(c.body, visitedLocalDefinitions)) case _ => - methodRequiresImplicitRaise(term) || - originatesFromRaiseOperation(term) || - originatesFromLocalDefinition(term, visitedLocalDefinitions) || - originatesFromPropagation(term, visitedLocalDefinitions) || + val call = normalizedCall(term) + methodRequiresImplicitRaise(call) || + originatesFromRaiseOperation(call) || + originatesFromLocalDefinition(call, visitedLocalDefinitions) || + originatesFromPropagation(call, visitedLocalDefinitions) || originatesFromForComprehension(term, visitedLocalDefinitions) } private def originatesFromLocalDefinition( - term: Term, + call: Option[Call], visitedLocalDefinitions: Set[Symbol] )(implicit doc: SemanticDocument, localDefinitions: LocalDefinitions): Boolean = - calleeSymbol(term).exists { symbol => + call.exists { call => + val symbol = call.symbol !visitedLocalDefinitions(symbol) && localDefinitions.bySymbol .get(symbol) .exists(producedByRaise(_, visitedLocalDefinitions + symbol)) } - private def originatesFromRaiseOperation(term: Term)(implicit doc: SemanticDocument) = - calleeSymbol(term).exists(sym => isOwner(sym, RaiseAll_M)) + private def originatesFromRaiseOperation(call: Option[Call])(implicit doc: SemanticDocument) = + call.exists(call => isOwner(call.symbol, RaiseAll_M)) private def originatesFromPropagation( - term: Term, + call: Option[Call], visitedLocalDefinitions: Set[Symbol] )(implicit doc: SemanticDocument, localDefinitions: LocalDefinitions): Boolean = - appliedCall(term).exists { case (receiver, method, argumentLists) => - calleeSymbol(term).exists { symbol => - val isCatsOperation = symbol.value.startsWith("cats/") - val isDirect = isOwner(symbol, PropagationDirect_M) - val firstArguments = argumentLists.headOption.getOrElse(Nil) + call.exists { call => + call.receiver.exists { receiver => + val isCatsOperation = call.symbol.value.startsWith("cats/") + val isDirect = isOwner(call.symbol, PropagationDirect_M) + val firstArguments = call.argumentLists.headOption.getOrElse(Nil) if (!isCatsOperation) false - else if (UnaryPropagationMethods(method)) { + else if (UnaryPropagationMethods(call.name)) { val source = if (isDirect) firstArguments.headOption else Some(receiver) source.exists(producedByRaise(_, visitedLocalDefinitions)) - } else if (BinaryPropagationMethods(method)) { + } else if (BinaryPropagationMethods(call.name)) { val sources = if (isDirect) firstArguments.take(2) else receiver :: firstArguments.take(1) sources.exists(producedByRaise(_, visitedLocalDefinitions)) - } else if (FlatMapPropagationMethods(method)) { + } else if (FlatMapPropagationMethods(call.name)) { val source = if (isDirect) firstArguments.headOption else Some(receiver) val callback = - if (isDirect) argumentLists.drop(1).headOption.flatMap(_.headOption) + if (isDirect) call.argumentLists.drop(1).headOption.flatMap(_.headOption) else firstArguments.headOption source.exists(producedByRaise(_, visitedLocalDefinitions)) || @@ -339,44 +311,55 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { } } - private def appliedCall(term: Term): Option[(Term, String, List[List[Term]])] = { + private def handlerCall(term: Term)(implicit doc: SemanticDocument): Option[HandlerCall] = + normalizedCall(term) + .filter(call => ErrorHandlingMethods(call.name)) + .flatMap { call => + val isDirect = isOwner(call.symbol, Direct_M) + val isSyntax = Syntax_M.matches(call.symbol.owner) + val isIO = IO_M.matches(call.callee) + + if (isDirect) + call.argumentLists.headOption.flatMap(_.headOption).map { protectedEffect => + val handler = call.argumentLists.drop(1).headOption.flatMap(_.headOption) + new HandlerCall(call, protectedEffect, handler) + } + else if (isSyntax || isIO) + call.receiver.map { protectedEffect => + val handler = call.argumentLists.headOption.flatMap(_.headOption) + new HandlerCall(call, protectedEffect, handler) + } + else None + } + + private def normalizedCall(term: Term): Option[Call] = { @annotation.tailrec - def loop(t: Term, argumentLists: List[List[Term]]): Option[(Term, String, List[List[Term]])] = + def loop( + t: Term, + argumentLists: List[List[Term]], + typeArguments: List[Type] + ): Option[Call] = t match { case Term.Apply.After_4_6_0(fun, args) => - loop(fun, args :: argumentLists) + loop(fun, args :: argumentLists, typeArguments) - case Term.ApplyType.After_4_6_0(fun, _) => - loop(fun, argumentLists) + case Term.ApplyType.After_4_6_0(fun, tpes) => + loop(fun, argumentLists, tpes.values.toList ::: typeArguments) - case Term.ApplyInfix.Initial(receiver, Term.Name(method), _, args) => - Some((receiver, method, List(args))) + case Term.ApplyInfix.Initial(receiver, method: Term.Name, _, args) => + Some(new Call(Some(receiver), method, List(args), typeArguments, method)) - case Term.Select(receiver, Term.Name(method)) => - Some((receiver, method, argumentLists)) + case select @ Term.Select(receiver, method: Term.Name) => + Some(new Call(Some(receiver), method, argumentLists, typeArguments, select)) - case _ => - None - } - - loop(term, Nil) - } - - private def protectedEffectArg(term: Term): Option[Term] = { - @annotation.tailrec - def loop(t: Term, candidate: Option[Term]): Option[Term] = - t match { - case Term.Apply.After_4_6_0(fun, args) => - loop(fun, args.headOption.orElse(candidate)) - - case Term.ApplyType.After_4_6_0(fun, _) => - loop(fun, candidate) + case method: Term.Name => + Some(new Call(None, method, argumentLists, typeArguments, method)) case _ => - candidate + None } - loop(term, None) + loop(term, Nil, Nil) } private def isOwner(sym: Symbol, matcher: SymbolMatcher): Boolean = { @@ -391,8 +374,10 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { loop(sym) } - private def methodRequiresImplicitRaise(qual: Term)(implicit doc: SemanticDocument): Boolean = - calleeSymbol(qual).flatMap(sym => doc.info(sym)).exists { info => + private def methodRequiresImplicitRaise( + call: Option[Call] + )(implicit doc: SemanticDocument): Boolean = + call.flatMap(call => doc.info(call.symbol)).exists { info => info.signature match { case m: MethodSignature => requiresRaiseViaParams(m) || requiresRaiseViaContextFunction(m) @@ -467,38 +452,6 @@ class MTLSubmarine extends SemanticRule("TypelevelMTLSubmarine") { false } - private def calleeSymbol(term: Term)(implicit doc: SemanticDocument): Option[Symbol] = - term match { - case Term.Apply.After_4_6_0(fun, _) => calleeSymbol(fun) - case Term.ApplyType.After_4_6_0(fun, _) => calleeSymbol(fun) - case Term.ApplyInfix.Initial(_, op, _, _) => Some(op.symbol) - case Term.Select(_, n) => Some(n.symbol) - case n: Term.Name => Some(n.symbol) - case Term.Block(stats) => - stats.lastOption.collect { case t: Term => t }.flatMap(calleeSymbol) - - case Term.If.After_4_4_0(_, thn, els, _) => - calleeSymbol(thn).orElse(calleeSymbol(els)) - - case Term.Match.After_4_9_9(_, cases, _) => - cases.view.flatMap(c => calleeSymbol(c.body)).headOption - - case _ => None - } - - private def calleeName(term: Term): Option[String] = - calleeNameTerm(term).map(_.value) - - @annotation.tailrec - private def calleeNameTerm(term: Term): Option[Term.Name] = - term match { - case Term.Apply.After_4_6_0(fun, _) => calleeNameTerm(fun) - case Term.ApplyType.After_4_6_0(fun, _) => calleeNameTerm(fun) - case Term.Select(_, method: Term.Name) => Some(method) - case method: Term.Name => Some(method) - case _ => None - } - } object MTLSubmarine { From fbda2e6810ff1ce9bfcc0a8e983f9c5987856095 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 7 Sep 2026 07:53:18 +0100 Subject: [PATCH 31/39] Address Scala 2 warnings --- .../main/scala-2/fix/MTLSubmarineTest.scala | 20 ++++++------- .../main/scala-3/fix/MTLSubmarineTest.scala | 28 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala index 06ac996..cf9d9b8 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala @@ -46,7 +46,7 @@ object MTLSubmarineTest { r.raise[String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").reject(_ => new Exception("")) + r.raise[String, String]("").reject(_ => new Exception("")) // ensure other methods aren't affected r.raise[String, Unit]("").tupleRight("") r.raise[String, Unit]("").void @@ -100,7 +100,7 @@ object MTLSubmarineTest { r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").reject(_ => new Exception("")) + r.raise[String, String]("").reject(_ => new Exception("")) // ensure other methods aren't affected r.raise[String, Unit]("").void r.raise[String, Unit]("").debug() @@ -388,7 +388,7 @@ object MTLSubmarineTest { Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").reject(_ => new Exception("")) + Raise.raise[F, String, String]("").reject(_ => new Exception("")) // ensure other methods aren't affected Raise.raise[F, String, Unit]("").tupleRight("") Raise.raise[F, String, Unit]("").void @@ -442,7 +442,7 @@ object MTLSubmarineTest { Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").reject(_ => new Exception("")) + Raise.raise[IO, String, String]("").reject(_ => new Exception("")) // ensure other methods aren't affected Raise.raise[IO, String, Unit]("").void Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) @@ -479,7 +479,7 @@ object MTLSubmarineTest { "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].reject(_ => new Exception("")) + "".raise[F, String].reject(_ => new Exception("")) // ensure other methods aren't affected "".raise[F, Unit].tupleRight("") "".raise[F, Unit].void @@ -533,7 +533,7 @@ object MTLSubmarineTest { "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].reject(_ => new Exception("")) + "".raise[IO, String].reject(_ => new Exception("")) // ensure other methods aren't affected "".raise[IO, Unit].void "".raise[IO, Unit].debug() @@ -601,7 +601,7 @@ object MTLSubmarineTest { _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) _ <- method[F].attemptTap(_ => Async[F].unit) _ <- method[F].adaptError { case _ => new Exception("") } - _ <- method[F].reject { case _ => new Exception("") } + _ <- method[F].as("").reject { case _ => new Exception("") } } yield () } @@ -666,7 +666,7 @@ object MTLSubmarineTest { _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].reject { case _ => new Exception("") } + _ <- methodRaise[F].as("").reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[F].void _ <- methodRaise[F].tupleRight("") @@ -695,7 +695,7 @@ object MTLSubmarineTest { _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) _ <- method[IO].attemptTap(_ => IO.unit) _ <- method[IO].adaptError { case _ => new Exception("") } - _ <- method[IO].reject { case _ => new Exception("") } + _ <- method[IO].as("").reject { case _ => new Exception("") } } yield () } @@ -721,7 +721,7 @@ object MTLSubmarineTest { _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].reject { case _ => new Exception("") } + _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[IO].void _ <- methodRaise[IO].debug() diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala index 25b05db..248f9b7 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala @@ -45,7 +45,7 @@ object MTLSubmarineTest { r.raise[String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").reject(_ => new Exception("")) + r.raise[String, String]("").reject(_ => new Exception("")) // ensure other methods aren't affected r.raise[String, Unit]("").tupleRight("") r.raise[String, Unit]("").void @@ -99,7 +99,7 @@ object MTLSubmarineTest { r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").reject(_ => new Exception("")) + r.raise[String, String]("").reject(_ => new Exception("")) // ensure other methods aren't affected r.raise[String, Unit]("").void r.raise[String, Unit]("").product(r.raise[String, Unit]("")) @@ -426,7 +426,7 @@ object MTLSubmarineTest { Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").reject(_ => new Exception("")) + Raise.raise[F, String, String]("").reject(_ => new Exception("")) // ensure other methods aren't affected Raise.raise[F, String, Unit]("").tupleRight("") Raise.raise[F, String, Unit]("").void @@ -480,7 +480,7 @@ object MTLSubmarineTest { Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").reject(_ => new Exception("")) + Raise.raise[IO, String, String]("").reject(_ => new Exception("")) // ensure other methods aren't affected Raise.raise[IO, String, Unit]("").void Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) @@ -516,7 +516,7 @@ object MTLSubmarineTest { "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].reject(_ => new Exception("")) + "".raise[F, String].reject(_ => new Exception("")) // ensure other methods aren't affected "".raise[F, Unit].tupleRight("") "".raise[F, Unit].void @@ -570,7 +570,7 @@ object MTLSubmarineTest { "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].reject(_ => new Exception("")) + "".raise[IO, String].reject(_ => new Exception("")) // ensure other methods aren't affected "".raise[IO, Unit].void "".raise[IO, Unit].debug() @@ -638,7 +638,7 @@ object MTLSubmarineTest { _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) _ <- method[F].attemptTap(_ => Async[F].unit) _ <- method[F].adaptError { case _ => new Exception("") } - _ <- method[F].reject { case _ => new Exception("") } + _ <- method[F].as("").reject { case _ => new Exception("") } } yield () } @@ -703,7 +703,7 @@ object MTLSubmarineTest { _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].reject { case _ => new Exception("") } + _ <- methodRaise[F].as("").reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[F].void _ <- methodRaise[F].tupleRight("") @@ -732,7 +732,7 @@ object MTLSubmarineTest { _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) _ <- method[IO].attemptTap(_ => IO.unit) _ <- method[IO].adaptError { case _ => new Exception("") } - _ <- method[IO].reject { case _ => new Exception("") } + _ <- method[IO].as("").reject { case _ => new Exception("") } } yield () } @@ -758,7 +758,7 @@ object MTLSubmarineTest { _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].reject { case _ => new Exception("") } + _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[IO].void _ <- methodRaise[IO].debug() @@ -826,7 +826,7 @@ object MTLSubmarineTest { _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) _ <- method[F].attemptTap(_ => Async[F].unit) _ <- method[F].adaptError { case _ => new Exception("") } - _ <- method[F].reject { case _ => new Exception("") } + _ <- method[F].as("").reject { case _ => new Exception("") } } yield () } @@ -889,7 +889,7 @@ object MTLSubmarineTest { _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].reject { case _ => new Exception("") } + _ <- methodRaise[F].as("").reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[F].void _ <- methodRaise[F].tupleRight("") @@ -918,7 +918,7 @@ object MTLSubmarineTest { _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) _ <- method[IO].attemptTap(_ => IO.unit) _ <- method[IO].adaptError { case _ => new Exception("") } - _ <- method[IO].reject { case _ => new Exception("") } + _ <- method[IO].as("").reject { case _ => new Exception("") } } yield () } @@ -944,7 +944,7 @@ object MTLSubmarineTest { _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].reject { case _ => new Exception("") } + _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } // ensure other methods aren't affected _ <- methodRaise[IO].void _ <- methodRaise[IO].debug() From 3972608cf86a99974dbecf605289108860d64188 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 7 Sep 2026 07:57:56 +0100 Subject: [PATCH 32/39] Split tests into different categories --- ...SubmarineDirectProtectedArgumentTest.scala | 21 + .../MTLSubmarineEffectPropagationTest.scala | 120 +++ .../fix/MTLSubmarineHandleAllowFTest.scala | 199 ++++ .../MTLSubmarineHandleRequirementTest.scala | 26 + .../MTLSubmarineLocalMethodCaptureTest.scala | 34 + .../MTLSubmarineLocalValueCaptureTest.scala | 32 + ...ubmarinePartialAndNarrowHandlersTest.scala | 33 + .../fix/MTLSubmarineRaiseAliasTest.scala | 43 + .../fix/MTLSubmarineRaiseInstanceTest.scala | 100 ++ .../fix/MTLSubmarineRaiseObjectTest.scala | 101 ++ .../fix/MTLSubmarineRaiseSyntaxTest.scala | 101 ++ .../main/scala-2/fix/MTLSubmarineTest.scala | 733 -------------- .../fix/MTLSubmarineWrappedRaiseTest.scala | 22 + ...MTLSubmarineContextFunctionValueTest.scala | 27 + ...SubmarineDirectProtectedArgumentTest.scala | 20 + .../MTLSubmarineEffectPropagationTest.scala | 120 +++ ...marineHandleAllowContextFunctionTest.scala | 195 ++++ .../MTLSubmarineHandleAllowGivenTest.scala | 198 ++++ .../MTLSubmarineHandleRequirementTest.scala | 38 + .../MTLSubmarineLocalMethodCaptureTest.scala | 37 + .../MTLSubmarineLocalValueCaptureTest.scala | 34 + ...ubmarinePartialAndNarrowHandlersTest.scala | 33 + .../fix/MTLSubmarineRaiseAliasTest.scala | 42 + .../fix/MTLSubmarineRaiseInstanceTest.scala | 99 ++ .../fix/MTLSubmarineRaiseObjectTest.scala | 99 ++ .../fix/MTLSubmarineRaiseSyntaxTest.scala | 99 ++ .../main/scala-3/fix/MTLSubmarineTest.scala | 956 ------------------ .../fix/MTLSubmarineWrappedRaiseTest.scala | 21 + 28 files changed, 1894 insertions(+), 1689 deletions(-) create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineDirectProtectedArgumentTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineEffectPropagationTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleRequirementTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalMethodCaptureTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalValueCaptureTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarinePartialAndNarrowHandlersTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseAliasTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseInstanceTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseObjectTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala delete mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala create mode 100644 modules/mtl/input/src/main/scala-2/fix/MTLSubmarineWrappedRaiseTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineContextFunctionValueTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineDirectProtectedArgumentTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineEffectPropagationTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowGivenTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleRequirementTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineLocalMethodCaptureTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineLocalValueCaptureTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarinePartialAndNarrowHandlersTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseAliasTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseInstanceTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseObjectTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseSyntaxTest.scala delete mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala create mode 100644 modules/mtl/input/src/main/scala-3/fix/MTLSubmarineWrappedRaiseTest.scala diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineDirectProtectedArgumentTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineDirectProtectedArgumentTest.scala new file mode 100644 index 0000000..fdbd818 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineDirectProtectedArgumentTest.scala @@ -0,0 +1,21 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ + + +// scalafmt: { maxColumn = 160 } +object DirectProtectedArgument { + + def raiseDependentRecovery[F[_]](error: Throwable)(implicit r: Raise[F, String]): F[Unit] = + r.raise(error.getMessage) + + def normalSource[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].handleErrorWith(Async[F].unit)(raiseDependentRecovery[F]) + Async[F].redeemWith(Async[F].unit)(raiseDependentRecovery[F], _ => Async[F].unit) + } + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineEffectPropagationTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineEffectPropagationTest.scala new file mode 100644 index 0000000..f8841a6 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineEffectPropagationTest.scala @@ -0,0 +1,120 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ +import cats.effect.implicits._ +import cats.syntax.all._ + + +// scalafmt: { maxColumn = 160 } +object EffectPropagation { + + def methodRaise[F[_]](implicit r: Raise[F, String]): F[Unit] = + r.raise("something went wrong") + + def syntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + methodRaise[F].map(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].void.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].as(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit *> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (methodRaise[F] <* Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit >> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].product(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.product(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].productL(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.productR(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.map2(methodRaise[F])((_, _) => ()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].fproduct(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].fproductLeft(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].tupleLeft(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].tupleRight(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].as((_: Unit) => ()) + .<&>(Async[F].unit) + .attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.map(identity).attempt + (for { + _ <- Async[F].unit + _ <- methodRaise[F] + } yield ()).attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def direct[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].attempt(Async[F].map(methodRaise[F])(identity)).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => methodRaise[F])).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Async[F].map(Async[F].unit)(identity)).void + Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => Async[F].unit)).void + } + + def safeSyntax[F[_]: Async]: F[Unit] = { + Async[F].unit.map(identity).attempt + Async[F].unit.void.recover(_ => ()) + Async[F].unit.as(()).attempt + (Async[F].unit *> Async[F].unit).attempt + (Async[F].unit <* Async[F].unit).attempt + (Async[F].unit >> Async[F].unit).attempt + Async[F].unit.product(Async[F].unit).attempt + Async[F].unit.productL(Async[F].unit).attempt + Async[F].unit.productR(Async[F].unit).attempt + Async[F].unit.map2(Async[F].unit)((_, _) => ()).attempt + Async[F].unit.fproduct(identity).attempt + Async[F].unit.fproductLeft(identity).attempt + Async[F].unit.tupleLeft(()).attempt + Async[F].unit.tupleRight(()).attempt + Async[F].pure((_: Unit) => ()).<&>(Async[F].unit).attempt + Async[F].unit.flatMap(_ => Async[F].unit).attempt + Async[F].unit.flatTap(_ => Async[F].unit).attempt + (Async[F].unit >>= (_ => Async[F].unit)).attempt + Async[F].unit.mproduct(_ => Async[F].unit).attempt + (for { + _ <- Async[F].unit + _ <- Async[F].unit + } yield ()).attempt.void + } + + def expressionWrappers[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + (methodRaise[F]: F[Unit]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (((methodRaise[F]))).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit: F[Unit]).attempt.void + } + + def conditional[F[_]: Async](condition: Boolean)(implicit r: Raise[F, String]): F[Unit] = { + (if (condition) Async[F].unit else methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (if (condition) methodRaise[F] else Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (if (condition) Async[F].unit else Async[F].unit).attempt.void + } + + def matched[F[_]: Async](condition: Boolean)(implicit r: Raise[F, String]): F[Unit] = { + (condition match { + case true => Async[F].unit + case false => methodRaise[F] + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (condition match { + case true => methodRaise[F] + case false => Async[F].unit + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (condition match { + case true => Async[F].unit + case false => Async[F].unit + }).attempt.void + } + + def blocks[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + ({ + Async[F].unit + methodRaise[F] + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + ({ + methodRaise[F] + Async[F].unit + }).attempt.void + } + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala new file mode 100644 index 0000000..84ddc57 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala @@ -0,0 +1,199 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ +import cats.syntax.all._ + + +// scalafmt: { maxColumn = 160 } +object HandleAllowF { + + def methodRaise[F[_]](implicit r: Raise[F, String]): F[Unit] = + r.raise("something went wrong") + + def method[F[_]: Async]: F[Unit] = + Async[F].unit + + def applicativeErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { _ => + for { + _ <- method[F].handleError(_ => ()) + _ <- method[F].handleErrorWith(_ => Async[F].unit) + _ <- method[F].attempt + _ <- method[F].attemptNarrow[RuntimeException] + _ <- method[F].attemptT.value + _ <- method[F].recover { case _ => () } + _ <- method[F].recoverWith { case _ => Async[F].unit } + _ <- method[F].redeem(_ => (), _ => ()) + _ <- method[F].onError { case _ => Async[F].unit } + _ <- method[F].orElse(Async[F].unit) + _ <- method[F].adaptErr { case _ => new Exception("") } + _ <- method[F].orRaise(new Exception("")) + _ <- method[F].voidError + } yield () + } + + def applicativeErrorDirect[F[_]: Async] = Handle.allowF[F, String] { _ => + for { + _ <- Async[F].handleError(method[F])(_ => ()) + _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) + _ <- Async[F].attempt(method[F]) + _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) + _ <- Async[F].attemptT(method[F]).value + _ <- Async[F].recover(method[F]) { case _ => () } + _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } + _ <- Async[F].redeem(method[F])(_ => (), _ => ()) + _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } + _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } + _ <- Async[F].voidError(method[F]) + } yield () + } + + def monadErrorDirect[F[_]: Async] = Handle.allowF[F, String] { _ => + for { + _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) + _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) + _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) + _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) + } yield () + } + + def monadErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { _ => + for { + _ <- method[F].ensure(new Exception(""))(_ => false) + _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow + _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) + _ <- method[F].attemptTap(_ => Async[F].unit) + _ <- method[F].adaptError { case _ => new Exception("") } + _ <- method[F].as("").reject { case _ => new Exception("") } + } yield () + } + + def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { implicit h => + for { + _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + _ <- methodRaise[F].product(methodRaise[F]) + } yield () + } + + def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allowF[F, String] { implicit h => + for { + _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + _ <- Async[F].product(methodRaise[F], methodRaise[F]) + } yield () + } + + def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allowF[F, String] { implicit h => + for { + _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) + _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + } yield () + } + + def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { implicit h => + for { + _ <- methodRaise[F].ensure(new Exception(""))(_ => false) + _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) + _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].as("").reject { case _ => new Exception("") } + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + } yield () + } + + def withIO = Handle.allowF[IO, String] { _ => + for { + // applicative error + _ <- method[IO].handleError(_ => ()) + _ <- method[IO].handleErrorWith(_ => IO.unit) + _ <- method[IO].attempt + _ <- method[IO].attemptNarrow[RuntimeException] + _ <- method[IO].attemptT.value + _ <- method[IO].recover { case _ => () } + _ <- method[IO].recoverWith { case _ => IO.unit } + _ <- method[IO].redeem(_ => (), _ => ()) + _ <- method[IO].onError { case _ => IO.unit } + _ <- method[IO].orElse(IO.unit) + _ <- method[IO].adaptErr { case _ => new Exception("") } + _ <- method[IO].orRaise(new Exception("")) + _ <- method[IO].voidError + // monad error + _ <- method[IO].ensure(new Exception(""))(_ => false) + _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) + _ <- method[IO].attemptTap(_ => IO.unit) + _ <- method[IO].adaptError { case _ => new Exception("") } + _ <- method[IO].as("").reject { case _ => new Exception("") } + } yield () + } + + def raiseWithIO = Handle.allowF[IO, String] { implicit h => + for { + // applicative error + _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) + _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } + // ensure other methods aren't affected + _ <- methodRaise[IO].void + _ <- methodRaise[IO].debug() + } yield () + } + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleRequirementTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleRequirementTest.scala new file mode 100644 index 0000000..deee4b3 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleRequirementTest.scala @@ -0,0 +1,26 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ +import cats.syntax.all._ + + +// scalafmt: { maxColumn = 160 } +object HandleRequirement { + + def method[F[_]](implicit h: Handle[F, String]): F[Unit] = + h.raise("something went wrong") + + def applicativeErrorSyntax[F[_]: Async](implicit h: Handle[F, String]): F[Unit] = + method[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def applicativeErrorDirect[F[_]: Async](implicit h: Handle[F, String]): F[Unit] = + Async[F].attempt(method[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def io(implicit h: Handle[IO, String]): IO[Unit] = + method[IO].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalMethodCaptureTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalMethodCaptureTest.scala new file mode 100644 index 0000000..3e3ed3d --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalMethodCaptureTest.scala @@ -0,0 +1,34 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ + + +// scalafmt: { maxColumn = 160 } +object LocalMethodCapture { + + def captured = Handle.allowF[IO, String] { implicit h => + def boom: IO[Unit] = h.raise("boom") + + boom.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def chainedCapture = Handle.allowF[IO, String] { implicit h => + def boom: IO[Unit] = h.raise("boom") + def indirect: IO[Unit] = boom + + indirect.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def normal = Handle.allowF[IO, String] { _ => + def safe: IO[Unit] = IO.unit + def recursive(remaining: Int): IO[Unit] = + if (remaining <= 0) IO.unit else recursive(remaining - 1) + + safe.attempt *> recursive(1).attempt + } + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalValueCaptureTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalValueCaptureTest.scala new file mode 100644 index 0000000..b403965 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalValueCaptureTest.scala @@ -0,0 +1,32 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ + + +// scalafmt: { maxColumn = 160 } +object LocalValueCapture { + + def captured = Handle.allowF[IO, String] { implicit h => + val direct: IO[Unit] = h.raise("boom") + + direct.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def capturedMethod = Handle.allowF[IO, String] { implicit h => + def boom: IO[Unit] = h.raise("boom") + lazy val action: IO[Unit] = boom + + action.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def normal = Handle.allowF[IO, String] { _ => + val safe: IO[Unit] = IO.unit + + safe.attempt + } + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarinePartialAndNarrowHandlersTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarinePartialAndNarrowHandlersTest.scala new file mode 100644 index 0000000..7644008 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarinePartialAndNarrowHandlersTest.scala @@ -0,0 +1,33 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ +import cats.syntax.all._ + +import java.io.IOException + +// scalafmt: { maxColumn = 160 } +object PartialAndNarrowHandlers { + + def methodRaise[F[_]](implicit r: Raise[F, String]): F[Unit] = + r.raise("boom") + + def precise[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].attemptNarrow[IOException] + Async[F].attemptNarrow[IOException, Unit](methodRaise[F]) + methodRaise[F].recover { case _: RuntimeException => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].recover { case _: IOException => () } + Async[F].recover(methodRaise[F]) { case _: IOException => () } + methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def unknown[F[_]: Async](handler: PartialFunction[Throwable, Unit])(implicit + r: Raise[F, String] + ): F[Unit] = + methodRaise[F].recover(handler) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseAliasTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseAliasTest.scala new file mode 100644 index 0000000..a5f9bb0 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseAliasTest.scala @@ -0,0 +1,43 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ +import cats.syntax.all._ + + +// scalafmt: { maxColumn = 160 } +object RaiseAlias { + + type AliasedRaise[F[_], E] = Raise[F, E] + type ChainedRaiseAlias[F[_], E] = AliasedRaise[F, E] + type AliasedHandle[F[_], E] = Handle[F, E] + type ChainedHandleAlias[F[_], E] = AliasedHandle[F, E] + + def aliasedRaise[F[_]](implicit r: AliasedRaise[F, String]): F[Unit] = + r.raise("something went wrong") + + def chainedRaise[F[_]](implicit r: ChainedRaiseAlias[F, String]): F[Unit] = + r.raise("something went wrong") + + def aliasedHandle[F[_]](implicit h: AliasedHandle[F, String]): F[Unit] = + h.raise("something went wrong") + + def chainedHandle[F[_]](implicit h: ChainedHandleAlias[F, String]): F[Unit] = + h.raise("something went wrong") + + def aliasedRaiseHandling[F[_]: Async](implicit r: AliasedRaise[F, String]): F[Unit] = + aliasedRaise[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def chainedRaiseHandling[F[_]: Async](implicit r: ChainedRaiseAlias[F, String]): F[Unit] = + Async[F].attempt(chainedRaise[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def aliasedHandleHandling[F[_]: Async](implicit h: AliasedHandle[F, String]): F[Unit] = + aliasedHandle[F].recover(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def chainedHandleHandling[F[_]: Async](implicit h: ChainedHandleAlias[F, String]): F[Unit] = + Async[F].handleError(chainedHandle[F])(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseInstanceTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseInstanceTest.scala new file mode 100644 index 0000000..d381c0f --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseInstanceTest.scala @@ -0,0 +1,100 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ +import cats.syntax.all._ + + +// scalafmt: { maxColumn = 160 } +object RaiseInstance { + + def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + // ApplicativeError syntax + r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + r.raise[String, Unit]("").tupleRight("") + r.raise[String, Unit]("").void + } + + def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + r.raise[String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, String]("").reject(_ => new Exception("")) + // ensure other methods aren't affected + r.raise[String, Unit]("").tupleRight("") + r.raise[String, Unit]("").void + } + + def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].handleError(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit](r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT(r.raise[String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem(r.raise[String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError(r.raise[String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(r.raise[String, Unit](""), "") + Async[F].void(r.raise[String, Unit]("")) + } + + def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].ensure(r.raise[String, Unit](""))(new Exception(""))(_ => false) + Async[F].ensureOr(r.raise[String, Unit](""))(_ => new Exception(""))(_ => false) + Async[F].redeemWith(r.raise[String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(r.raise[String, Unit](""), "") + Async[F].void(r.raise[String, Unit]("")) + } + + def io(implicit r: Raise[IO, String]): IO[Unit] = { + // applicative error + r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, String]("").reject(_ => new Exception("")) + // ensure other methods aren't affected + r.raise[String, Unit]("").void + r.raise[String, Unit]("").debug() + } + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseObjectTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseObjectTest.scala new file mode 100644 index 0000000..6050a57 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseObjectTest.scala @@ -0,0 +1,101 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ +import cats.syntax.all._ + + +// scalafmt: { maxColumn = 160 } +object RaiseObject { + + def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + // ApplicativeError syntax + Raise.raise[F, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Raise.raise[F, String, Unit]("").tupleRight("") + Raise.raise[F, String, Unit]("").void + } + + def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) + Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, String]("").reject(_ => new Exception("")) + // ensure other methods aren't affected + Raise.raise[F, String, Unit]("").tupleRight("") + Raise.raise[F, String, Unit]("").void + } + + def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].handleError(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit](Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT(Raise.raise[F, String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem(Raise.raise[F, String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError(Raise.raise[F, String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") + Async[F].void(Raise.raise[F, String, Unit]("")) + } + + def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) + Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) + Async[F].redeemWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") + Async[F].void(Raise.raise[F, String, Unit]("")) + } + + def io(implicit r: Raise[IO, String]): IO[Unit] = { + // applicative error + Raise.raise[IO, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) + Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, String]("").reject(_ => new Exception("")) + // ensure other methods aren't affected + Raise.raise[IO, String, Unit]("").void + Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) + Raise.raise[IO, String, Unit]("").debug() + } + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala new file mode 100644 index 0000000..8d82a31 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala @@ -0,0 +1,101 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.mtl.syntax.raise._ +import cats.effect._ +import cats.syntax.all._ + + +// scalafmt: { maxColumn = 160 } +object RaiseSyntax { + + def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + // ApplicativeError syntax + "".raise[F, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + "".raise[F, Unit].tupleRight("") + "".raise[F, Unit].void + } + + def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + "".raise[F, Unit].ensure(new Exception(""))(_ => false) + "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) + "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, String].reject(_ => new Exception("")) + // ensure other methods aren't affected + "".raise[F, Unit].tupleRight("") + "".raise[F, Unit].void + } + + def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit]("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT("".raise[F, Unit]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem("".raise[F, Unit])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError("".raise[F, Unit])(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight("".raise[F, Unit], "") + Async[F].void("".raise[F, Unit]) + } + + def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) + Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) + Async[F].redeemWith("".raise[F, Unit])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight("".raise[F, Unit], "") + Async[F].void("".raise[F, Unit]) + } + + def io(implicit r: Raise[IO, String]): IO[Unit] = { + // applicative error + "".raise[IO, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + "".raise[IO, Unit].ensure(new Exception(""))(_ => false) + "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) + "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, String].reject(_ => new Exception("")) + // ensure other methods aren't affected + "".raise[IO, Unit].void + "".raise[IO, Unit].debug() + } + +} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala deleted file mode 100644 index cf9d9b8..0000000 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineTest.scala +++ /dev/null @@ -1,733 +0,0 @@ -/* -rule = TypelevelMTLSubmarine - */ -package fix - -import cats.mtl._ -import cats.mtl.syntax.raise._ -import cats.effect._ -import cats.effect.implicits._ -import cats.syntax.all._ - -import java.io.IOException - -// scalafmt: { maxColumn = 160 } -object MTLSubmarineTest { - - // Test matrix: objects select the provenance source or propagation shape; methods - // select the handler form and effect type; this file selects Scala 2.13; an `assert` - // comment means a diagnostic is expected, while its absence means the call is safe. - - object RaiseInstance { - - def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - // ApplicativeError syntax - r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - r.raise[String, Unit]("").tupleRight("") - r.raise[String, Unit]("").void - } - - def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) - r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - r.raise[String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - r.raise[String, Unit]("").tupleRight("") - r.raise[String, Unit]("").void - } - - def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].handleError(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].handleErrorWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptNarrow[RuntimeException, Unit](r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptT(r.raise[String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recover(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recoverWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].redeem(r.raise[String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].onError(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].adaptError(r.raise[String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].voidError(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(r.raise[String, Unit](""), "") - Async[F].void(r.raise[String, Unit]("")) - } - - def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].ensure(r.raise[String, Unit](""))(new Exception(""))(_ => false) - Async[F].ensureOr(r.raise[String, Unit](""))(_ => new Exception(""))(_ => false) - Async[F].redeemWith(r.raise[String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptTap(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(r.raise[String, Unit](""), "") - Async[F].void(r.raise[String, Unit]("")) - } - - def io(implicit r: Raise[IO, String]): IO[Unit] = { - // applicative error - r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) - r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - r.raise[String, Unit]("").void - r.raise[String, Unit]("").debug() - } - - } - - object HandleRequirement { - - def method[F[_]](implicit h: Handle[F, String]): F[Unit] = - h.raise("something went wrong") - - def applicativeErrorSyntax[F[_]: Async](implicit h: Handle[F, String]): F[Unit] = - method[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def applicativeErrorDirect[F[_]: Async](implicit h: Handle[F, String]): F[Unit] = - Async[F].attempt(method[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def io(implicit h: Handle[IO, String]): IO[Unit] = - method[IO].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - } - - object RaiseAlias { - - type AliasedRaise[F[_], E] = Raise[F, E] - type ChainedRaiseAlias[F[_], E] = AliasedRaise[F, E] - type AliasedHandle[F[_], E] = Handle[F, E] - type ChainedHandleAlias[F[_], E] = AliasedHandle[F, E] - - def aliasedRaise[F[_]](implicit r: AliasedRaise[F, String]): F[Unit] = - r.raise("something went wrong") - - def chainedRaise[F[_]](implicit r: ChainedRaiseAlias[F, String]): F[Unit] = - r.raise("something went wrong") - - def aliasedHandle[F[_]](implicit h: AliasedHandle[F, String]): F[Unit] = - h.raise("something went wrong") - - def chainedHandle[F[_]](implicit h: ChainedHandleAlias[F, String]): F[Unit] = - h.raise("something went wrong") - - def aliasedRaiseHandling[F[_]: Async](implicit r: AliasedRaise[F, String]): F[Unit] = - aliasedRaise[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def chainedRaiseHandling[F[_]: Async](implicit r: ChainedRaiseAlias[F, String]): F[Unit] = - Async[F].attempt(chainedRaise[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def aliasedHandleHandling[F[_]: Async](implicit h: AliasedHandle[F, String]): F[Unit] = - aliasedHandle[F].recover(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def chainedHandleHandling[F[_]: Async](implicit h: ChainedHandleAlias[F, String]): F[Unit] = - Async[F].handleError(chainedHandle[F])(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - } - - object WrappedRaise { - - trait Wrapper[A] - - def normal[F[_]: Async](implicit wrapper: Wrapper[Raise[F, String]]): F[Unit] = - Async[F].unit - - def handlingNormal[F[_]: Async](implicit wrapper: Wrapper[Raise[F, String]]): F[Unit] = - normal[F].attempt.void - - } - - object DirectProtectedArgument { - - def raiseDependentRecovery[F[_]](error: Throwable)(implicit r: Raise[F, String]): F[Unit] = - r.raise(error.getMessage) - - def normalSource[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].handleErrorWith(Async[F].unit)(raiseDependentRecovery[F]) - Async[F].redeemWith(Async[F].unit)(raiseDependentRecovery[F], _ => Async[F].unit) - } - - } - - object EffectPropagation { - - def methodRaise[F[_]](implicit r: Raise[F, String]): F[Unit] = - r.raise("something went wrong") - - def syntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - methodRaise[F].map(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].void.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].as(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (Async[F].unit *> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (methodRaise[F] <* Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (Async[F].unit >> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].product(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.product(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].productL(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.productR(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.map2(methodRaise[F])((_, _) => ()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].fproduct(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].fproductLeft(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].tupleLeft(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].tupleRight(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].as((_: Unit) => ()) - .<&>(Async[F].unit) - .attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.map(identity).attempt - (for { - _ <- Async[F].unit - _ <- methodRaise[F] - } yield ()).attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def direct[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].attempt(Async[F].map(methodRaise[F])(identity)).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => methodRaise[F])).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt(Async[F].map(Async[F].unit)(identity)).void - Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => Async[F].unit)).void - } - - def safeSyntax[F[_]: Async]: F[Unit] = { - Async[F].unit.map(identity).attempt - Async[F].unit.void.recover(_ => ()) - Async[F].unit.as(()).attempt - (Async[F].unit *> Async[F].unit).attempt - (Async[F].unit <* Async[F].unit).attempt - (Async[F].unit >> Async[F].unit).attempt - Async[F].unit.product(Async[F].unit).attempt - Async[F].unit.productL(Async[F].unit).attempt - Async[F].unit.productR(Async[F].unit).attempt - Async[F].unit.map2(Async[F].unit)((_, _) => ()).attempt - Async[F].unit.fproduct(identity).attempt - Async[F].unit.fproductLeft(identity).attempt - Async[F].unit.tupleLeft(()).attempt - Async[F].unit.tupleRight(()).attempt - Async[F].pure((_: Unit) => ()).<&>(Async[F].unit).attempt - Async[F].unit.flatMap(_ => Async[F].unit).attempt - Async[F].unit.flatTap(_ => Async[F].unit).attempt - (Async[F].unit >>= (_ => Async[F].unit)).attempt - Async[F].unit.mproduct(_ => Async[F].unit).attempt - (for { - _ <- Async[F].unit - _ <- Async[F].unit - } yield ()).attempt.void - } - - def expressionWrappers[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - (methodRaise[F]: F[Unit]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (((methodRaise[F]))).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (Async[F].unit: F[Unit]).attempt.void - } - - def conditional[F[_]: Async](condition: Boolean)(implicit r: Raise[F, String]): F[Unit] = { - (if (condition) Async[F].unit else methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (if (condition) methodRaise[F] else Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (if (condition) Async[F].unit else Async[F].unit).attempt.void - } - - def matched[F[_]: Async](condition: Boolean)(implicit r: Raise[F, String]): F[Unit] = { - (condition match { - case true => Async[F].unit - case false => methodRaise[F] - }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (condition match { - case true => methodRaise[F] - case false => Async[F].unit - }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (condition match { - case true => Async[F].unit - case false => Async[F].unit - }).attempt.void - } - - def blocks[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - ({ - Async[F].unit - methodRaise[F] - }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - ({ - methodRaise[F] - Async[F].unit - }).attempt.void - } - - } - - object PartialAndNarrowHandlers { - - def methodRaise[F[_]](implicit r: Raise[F, String]): F[Unit] = - r.raise("boom") - - def precise[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].attemptNarrow[IOException] - Async[F].attemptNarrow[IOException, Unit](methodRaise[F]) - methodRaise[F].recover { case _: RuntimeException => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].recover { case _: IOException => () } - Async[F].recover(methodRaise[F]) { case _: IOException => () } - methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def unknown[F[_]: Async](handler: PartialFunction[Throwable, Unit])(implicit - r: Raise[F, String] - ): F[Unit] = - methodRaise[F].recover(handler) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - } - - object LocalMethodCapture { - - def captured = Handle.allowF[IO, String] { implicit h => - def boom: IO[Unit] = h.raise("boom") - - boom.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def chainedCapture = Handle.allowF[IO, String] { implicit h => - def boom: IO[Unit] = h.raise("boom") - def indirect: IO[Unit] = boom - - indirect.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def normal = Handle.allowF[IO, String] { _ => - def safe: IO[Unit] = IO.unit - def recursive(remaining: Int): IO[Unit] = - if (remaining <= 0) IO.unit else recursive(remaining - 1) - - safe.attempt *> recursive(1).attempt - } - - } - - object LocalValueCapture { - - def captured = Handle.allowF[IO, String] { implicit h => - val direct: IO[Unit] = h.raise("boom") - - direct.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def capturedMethod = Handle.allowF[IO, String] { implicit h => - def boom: IO[Unit] = h.raise("boom") - lazy val action: IO[Unit] = boom - - action.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def normal = Handle.allowF[IO, String] { _ => - val safe: IO[Unit] = IO.unit - - safe.attempt - } - - } - - object RaiseObject { - - def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - // ApplicativeError syntax - Raise.raise[F, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Raise.raise[F, String, Unit]("").tupleRight("") - Raise.raise[F, String, Unit]("").void - } - - def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) - Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - Raise.raise[F, String, Unit]("").tupleRight("") - Raise.raise[F, String, Unit]("").void - } - - def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].handleError(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].handleErrorWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptNarrow[RuntimeException, Unit](Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptT(Raise.raise[F, String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recover(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recoverWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].redeem(Raise.raise[F, String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].onError(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].adaptError(Raise.raise[F, String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].voidError(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") - Async[F].void(Raise.raise[F, String, Unit]("")) - } - - def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) - Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) - Async[F].redeemWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptTap(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") - Async[F].void(Raise.raise[F, String, Unit]("")) - } - - def io(implicit r: Raise[IO, String]): IO[Unit] = { - // applicative error - Raise.raise[IO, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) - Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - Raise.raise[IO, String, Unit]("").void - Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) - Raise.raise[IO, String, Unit]("").debug() - } - - } - - object RaiseSyntax { - - def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - // ApplicativeError syntax - "".raise[F, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - "".raise[F, Unit].tupleRight("") - "".raise[F, Unit].void - } - - def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - "".raise[F, Unit].ensure(new Exception(""))(_ => false) - "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) - "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, String].reject(_ => new Exception("")) - // ensure other methods aren't affected - "".raise[F, Unit].tupleRight("") - "".raise[F, Unit].void - } - - def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].handleErrorWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptNarrow[RuntimeException, Unit]("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptT("".raise[F, Unit]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recover("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recoverWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].redeem("".raise[F, Unit])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].onError("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].adaptError("".raise[F, Unit])(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].voidError("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight("".raise[F, Unit], "") - Async[F].void("".raise[F, Unit]) - } - - def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) - Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) - Async[F].redeemWith("".raise[F, Unit])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptTap("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight("".raise[F, Unit], "") - Async[F].void("".raise[F, Unit]) - } - - def io(implicit r: Raise[IO, String]): IO[Unit] = { - // applicative error - "".raise[IO, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - "".raise[IO, Unit].ensure(new Exception(""))(_ => false) - "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) - "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, String].reject(_ => new Exception("")) - // ensure other methods aren't affected - "".raise[IO, Unit].void - "".raise[IO, Unit].debug() - } - - } - - object HandleAllowF { - - def methodRaise[F[_]](implicit r: Raise[F, String]): F[Unit] = - r.raise("something went wrong") - - def method[F[_]: Async]: F[Unit] = - Async[F].unit - - def applicativeErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { _ => - for { - _ <- method[F].handleError(_ => ()) - _ <- method[F].handleErrorWith(_ => Async[F].unit) - _ <- method[F].attempt - _ <- method[F].attemptNarrow[RuntimeException] - _ <- method[F].attemptT.value - _ <- method[F].recover { case _ => () } - _ <- method[F].recoverWith { case _ => Async[F].unit } - _ <- method[F].redeem(_ => (), _ => ()) - _ <- method[F].onError { case _ => Async[F].unit } - _ <- method[F].orElse(Async[F].unit) - _ <- method[F].adaptErr { case _ => new Exception("") } - _ <- method[F].orRaise(new Exception("")) - _ <- method[F].voidError - } yield () - } - - def applicativeErrorDirect[F[_]: Async] = Handle.allowF[F, String] { _ => - for { - _ <- Async[F].handleError(method[F])(_ => ()) - _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) - _ <- Async[F].attempt(method[F]) - _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) - _ <- Async[F].attemptT(method[F]).value - _ <- Async[F].recover(method[F]) { case _ => () } - _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } - _ <- Async[F].redeem(method[F])(_ => (), _ => ()) - _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } - _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } - _ <- Async[F].voidError(method[F]) - } yield () - } - - def monadErrorDirect[F[_]: Async] = Handle.allowF[F, String] { _ => - for { - _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) - _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) - _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) - } yield () - } - - def monadErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { _ => - for { - _ <- method[F].ensure(new Exception(""))(_ => false) - _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow - _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) - _ <- method[F].attemptTap(_ => Async[F].unit) - _ <- method[F].adaptError { case _ => new Exception("") } - _ <- method[F].as("").reject { case _ => new Exception("") } - } yield () - } - - def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { implicit h => - for { - _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - _ <- methodRaise[F].product(methodRaise[F]) - } yield () - } - - def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allowF[F, String] { implicit h => - for { - _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - _ <- Async[F].product(methodRaise[F], methodRaise[F]) - } yield () - } - - def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allowF[F, String] { implicit h => - for { - _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - } yield () - } - - def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { implicit h => - for { - _ <- methodRaise[F].ensure(new Exception(""))(_ => false) - _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected - _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - } yield () - } - - def withIO = Handle.allowF[IO, String] { _ => - for { - // applicative error - _ <- method[IO].handleError(_ => ()) - _ <- method[IO].handleErrorWith(_ => IO.unit) - _ <- method[IO].attempt - _ <- method[IO].attemptNarrow[RuntimeException] - _ <- method[IO].attemptT.value - _ <- method[IO].recover { case _ => () } - _ <- method[IO].recoverWith { case _ => IO.unit } - _ <- method[IO].redeem(_ => (), _ => ()) - _ <- method[IO].onError { case _ => IO.unit } - _ <- method[IO].orElse(IO.unit) - _ <- method[IO].adaptErr { case _ => new Exception("") } - _ <- method[IO].orRaise(new Exception("")) - _ <- method[IO].voidError - // monad error - _ <- method[IO].ensure(new Exception(""))(_ => false) - _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) - _ <- method[IO].attemptTap(_ => IO.unit) - _ <- method[IO].adaptError { case _ => new Exception("") } - _ <- method[IO].as("").reject { case _ => new Exception("") } - } yield () - } - - def raiseWithIO = Handle.allowF[IO, String] { implicit h => - for { - // applicative error - _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) - _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected - _ <- methodRaise[IO].void - _ <- methodRaise[IO].debug() - } yield () - } - - } - -} diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineWrappedRaiseTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineWrappedRaiseTest.scala new file mode 100644 index 0000000..0874628 --- /dev/null +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineWrappedRaiseTest.scala @@ -0,0 +1,22 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl._ +import cats.effect._ +import cats.syntax.all._ + + +// scalafmt: { maxColumn = 160 } +object WrappedRaise { + + trait Wrapper[A] + + def normal[F[_]: Async](implicit wrapper: Wrapper[Raise[F, String]]): F[Unit] = + Async[F].unit + + def handlingNormal[F[_]: Async](implicit wrapper: Wrapper[Raise[F, String]]): F[Unit] = + normal[F].attempt.void + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineContextFunctionValueTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineContextFunctionValueTest.scala new file mode 100644 index 0000000..9cbae96 --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineContextFunctionValueTest.scala @@ -0,0 +1,27 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object ContextFunctionValue { + + def valueHandling[F[_]: Async](using Raise[F, String]): F[Unit] = { + val action: Raise[F, String] ?=> F[Unit] = r ?=> r.raise("something went wrong") + + action.attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def parameterHandling[F[_]: Async]( + action: Raise[F, String] ?=> F[Unit] + )(using Raise[F, String]): F[Unit] = + action.recover(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def normalParameter[F[_]: Async](action: Async[F] ?=> F[Unit]): F[Unit] = + action.attempt.void + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineDirectProtectedArgumentTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineDirectProtectedArgumentTest.scala new file mode 100644 index 0000000..4cb09a2 --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineDirectProtectedArgumentTest.scala @@ -0,0 +1,20 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* + +// scalafmt: { maxColumn = 160 } +object DirectProtectedArgument { + + def raiseDependentRecovery[F[_]](error: Throwable)(using r: Raise[F, String]): F[Unit] = + r.raise(error.getMessage) + + def normalSource[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].handleErrorWith(Async[F].unit)(raiseDependentRecovery[F]) + Async[F].redeemWith(Async[F].unit)(raiseDependentRecovery[F], _ => Async[F].unit) + } + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineEffectPropagationTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineEffectPropagationTest.scala new file mode 100644 index 0000000..001a39f --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineEffectPropagationTest.scala @@ -0,0 +1,120 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* +import cats.effect.implicits.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object EffectPropagation { + + def methodRaise[F[_]](using r: Raise[F, String]): F[Unit] = + r.raise("something went wrong") + + def syntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + methodRaise[F].map(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].void.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].as(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit *> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (methodRaise[F] <* Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit >> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].product(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.product(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].productL(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.productR(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.map2(methodRaise[F])((_, _) => ()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].fproduct(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].fproductLeft(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].tupleLeft(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].tupleRight(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F] + .as((_: Unit) => ()) + .<&>(Async[F].unit) + .attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.map(identity).attempt + (for { + _ <- Async[F].unit + _ <- methodRaise[F] + } yield ()).attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def direct[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].attempt(Async[F].map(methodRaise[F])(identity)).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => methodRaise[F])).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Async[F].map(Async[F].unit)(identity)).void + Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => Async[F].unit)).void + } + + def safeSyntax[F[_]: Async]: F[Unit] = { + Async[F].unit.map(identity).attempt + Async[F].unit.void.recover(_ => ()) + Async[F].unit.as(()).attempt + (Async[F].unit *> Async[F].unit).attempt + (Async[F].unit <* Async[F].unit).attempt + (Async[F].unit >> Async[F].unit).attempt + Async[F].unit.product(Async[F].unit).attempt + Async[F].unit.productL(Async[F].unit).attempt + Async[F].unit.productR(Async[F].unit).attempt + Async[F].unit.map2(Async[F].unit)((_, _) => ()).attempt + Async[F].unit.fproduct(identity).attempt + Async[F].unit.fproductLeft(identity).attempt + Async[F].unit.tupleLeft(()).attempt + Async[F].unit.tupleRight(()).attempt + Async[F].pure((_: Unit) => ()).<&>(Async[F].unit).attempt + Async[F].unit.flatMap(_ => Async[F].unit).attempt + Async[F].unit.flatTap(_ => Async[F].unit).attempt + (Async[F].unit >>= (_ => Async[F].unit)).attempt + Async[F].unit.mproduct(_ => Async[F].unit).attempt + (for { + _ <- Async[F].unit + _ <- Async[F].unit + } yield ()).attempt.void + } + + def expressionWrappers[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + (methodRaise[F]: F[Unit]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (((methodRaise[F]))).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit: F[Unit]).attempt.void + } + + def conditional[F[_]: Async](condition: Boolean)(using r: Raise[F, String]): F[Unit] = { + (if (condition) Async[F].unit else methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (if (condition) methodRaise[F] else Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (if (condition) Async[F].unit else Async[F].unit).attempt.void + } + + def matched[F[_]: Async](condition: Boolean)(using r: Raise[F, String]): F[Unit] = { + (condition match { + case true => Async[F].unit + case false => methodRaise[F] + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (condition match { + case true => methodRaise[F] + case false => Async[F].unit + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (condition match { + case true => Async[F].unit + case false => Async[F].unit + }).attempt.void + } + + def blocks[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + ({ + Async[F].unit + methodRaise[F] + }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + ({ + methodRaise[F] + Async[F].unit + }).attempt.void + } + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala new file mode 100644 index 0000000..609a5cd --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala @@ -0,0 +1,195 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object HandleAllowContextFunction { + + def methodRaise[F[_]]: Raise[F, String] ?=> F[Unit] = r ?=> r.raise("something went wrong") + + def method[F[_]: Async]: F[Unit] = + Async[F].unit + + def applicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- method[F].handleError(_ => ()) + _ <- method[F].handleErrorWith(_ => Async[F].unit) + _ <- method[F].attempt + _ <- method[F].attemptNarrow[RuntimeException] + _ <- method[F].attemptT.value + _ <- method[F].recover { case _ => () } + _ <- method[F].recoverWith { case _ => Async[F].unit } + _ <- method[F].redeem(_ => (), _ => ()) + _ <- method[F].onError { case _ => Async[F].unit } + _ <- method[F].orElse(Async[F].unit) + _ <- method[F].adaptErr { case _ => new Exception("") } + _ <- method[F].orRaise(new Exception("")) + _ <- method[F].voidError + } yield () + } + + def applicativeErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].handleError(method[F])(_ => ()) + _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) + _ <- Async[F].attempt(method[F]) + _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) + _ <- Async[F].attemptT(method[F]).value + _ <- Async[F].recover(method[F]) { case _ => () } + _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } + _ <- Async[F].redeem(method[F])(_ => (), _ => ()) + _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } + _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } + _ <- Async[F].voidError(method[F]) + } yield () + } + + def monadErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) + _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) + _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) + _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) + } yield () + } + + def monadErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- method[F].ensure(new Exception(""))(_ => false) + _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow + _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) + _ <- method[F].attemptTap(_ => Async[F].unit) + _ <- method[F].adaptError { case _ => new Exception("") } + _ <- method[F].as("").reject { case _ => new Exception("") } + } yield () + } + + def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + } yield () + } + + def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + } yield () + } + + def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) + _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + } yield () + } + + def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- methodRaise[F].ensure(new Exception(""))(_ => false) + _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) + _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].as("").reject { case _ => new Exception("") } + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + } yield () + } + + def withIO = Handle.allow[String] { + for { + // applicative error + _ <- method[IO].handleError(_ => ()) + _ <- method[IO].handleErrorWith(_ => IO.unit) + _ <- method[IO].attempt + _ <- method[IO].attemptNarrow[RuntimeException] + _ <- method[IO].attemptT.value + _ <- method[IO].recover { case _ => () } + _ <- method[IO].recoverWith { case _ => IO.unit } + _ <- method[IO].redeem(_ => (), _ => ()) + _ <- method[IO].onError { case _ => IO.unit } + _ <- method[IO].orElse(IO.unit) + _ <- method[IO].adaptErr { case _ => new Exception("") } + _ <- method[IO].orRaise(new Exception("")) + _ <- method[IO].voidError + // monad error + _ <- method[IO].ensure(new Exception(""))(_ => false) + _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) + _ <- method[IO].attemptTap(_ => IO.unit) + _ <- method[IO].adaptError { case _ => new Exception("") } + _ <- method[IO].as("").reject { case _ => new Exception("") } + } yield () + } + + def raiseWithIO = Handle.allow[String] { + for { + // applicative error + _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) + _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } + // ensure other methods aren't affected + _ <- methodRaise[IO].void + _ <- methodRaise[IO].debug() + } yield () + } + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowGivenTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowGivenTest.scala new file mode 100644 index 0000000..0070257 --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowGivenTest.scala @@ -0,0 +1,198 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object HandleAllowGiven { + + def methodRaise[F[_]](using r: Raise[F, String]): F[Unit] = + r.raise("something went wrong") + + def method[F[_]: Async]: F[Unit] = + Async[F].unit + + def applicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- method[F].handleError(_ => ()) + _ <- method[F].handleErrorWith(_ => Async[F].unit) + _ <- method[F].attempt + _ <- method[F].attemptNarrow[RuntimeException] + _ <- method[F].attemptT.value + _ <- method[F].recover { case _ => () } + _ <- method[F].recoverWith { case _ => Async[F].unit } + _ <- method[F].redeem(_ => (), _ => ()) + _ <- method[F].onError { case _ => Async[F].unit } + _ <- method[F].orElse(Async[F].unit) + _ <- method[F].adaptErr { case _ => new Exception("") } + _ <- method[F].orRaise(new Exception("")) + _ <- method[F].voidError + } yield () + } + + def applicativeErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].handleError(method[F])(_ => ()) + _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) + _ <- Async[F].attempt(method[F]) + _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) + _ <- Async[F].attemptT(method[F]).value + _ <- Async[F].recover(method[F]) { case _ => () } + _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } + _ <- Async[F].redeem(method[F])(_ => (), _ => ()) + _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } + _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } + _ <- Async[F].voidError(method[F]) + } yield () + } + + def monadErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) + _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) + _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) + _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) + } yield () + } + + def monadErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- method[F].ensure(new Exception(""))(_ => false) + _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow + _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) + _ <- method[F].attemptTap(_ => Async[F].unit) + _ <- method[F].adaptError { case _ => new Exception("") } + _ <- method[F].as("").reject { case _ => new Exception("") } + } yield () + } + + def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + _ <- methodRaise[F].product(methodRaise[F]) + } yield () + } + + def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + _ <- Async[F].product(methodRaise[F], methodRaise[F]) + } yield () + } + + def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allow[String] { + for { + _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) + _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) + _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + _ <- Async[F].void(methodRaise[F]) + _ <- Async[F].tupleRight(methodRaise[F], "") + } yield () + } + + def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allow[String] { + for { + _ <- methodRaise[F].ensure(new Exception(""))(_ => false) + _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) + _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].as("").reject { case _ => new Exception("") } + // ensure other methods aren't affected + _ <- methodRaise[F].void + _ <- methodRaise[F].tupleRight("") + } yield () + } + + def withIO = Handle.allow[String] { + for { + // applicative error + _ <- method[IO].handleError(_ => ()) + _ <- method[IO].handleErrorWith(_ => IO.unit) + _ <- method[IO].attempt + _ <- method[IO].attemptNarrow[RuntimeException] + _ <- method[IO].attemptT.value + _ <- method[IO].recover { case _ => () } + _ <- method[IO].recoverWith { case _ => IO.unit } + _ <- method[IO].redeem(_ => (), _ => ()) + _ <- method[IO].onError { case _ => IO.unit } + _ <- method[IO].orElse(IO.unit) + _ <- method[IO].adaptErr { case _ => new Exception("") } + _ <- method[IO].orRaise(new Exception("")) + _ <- method[IO].voidError + // monad error + _ <- method[IO].ensure(new Exception(""))(_ => false) + _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) + _ <- method[IO].attemptTap(_ => IO.unit) + _ <- method[IO].adaptError { case _ => new Exception("") } + _ <- method[IO].as("").reject { case _ => new Exception("") } + } yield () + } + + def raiseWithIO = Handle.allow[String] { + for { + // applicative error + _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) + _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) + _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } + // ensure other methods aren't affected + _ <- methodRaise[IO].void + _ <- methodRaise[IO].debug() + } yield () + } + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleRequirementTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleRequirementTest.scala new file mode 100644 index 0000000..93332c0 --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleRequirementTest.scala @@ -0,0 +1,38 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object HandleRequirement { + + def method[F[_]](using h: Handle[F, String]): F[Unit] = + h.raise("something went wrong") + + def contextFunctionMethod[F[_]]: Handle[F, String] ?=> F[Unit] = + summon[Handle[F, String]].raise("something went wrong") + + def normalContextFunctionMethod[F[_]]: Async[F] ?=> F[Unit] = + summon[Async[F]].unit + + def applicativeErrorSyntax[F[_]: Async](using h: Handle[F, String]): F[Unit] = + method[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def applicativeErrorDirect[F[_]: Async](using h: Handle[F, String]): F[Unit] = + Async[F].attempt(method[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def io(using h: Handle[IO, String]): IO[Unit] = + method[IO].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def contextFunction = Handle.allow[String] { + contextFunctionMethod[IO].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def normalContextFunction(using Async[IO]): IO[Unit] = + normalContextFunctionMethod[IO].attempt.void + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineLocalMethodCaptureTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineLocalMethodCaptureTest.scala new file mode 100644 index 0000000..7923c8d --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineLocalMethodCaptureTest.scala @@ -0,0 +1,37 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* + +// scalafmt: { maxColumn = 160 } +object LocalMethodCapture { + + def captured = Handle.allow[String] { + val h = summon[Handle[IO, String]] + + def boom: IO[Unit] = h.raise("boom") + + boom.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def chainedCapture = Handle.allow[String] { + val h = summon[Handle[IO, String]] + + def boom: IO[Unit] = h.raise("boom") + def indirect: IO[Unit] = boom + + indirect.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def normal = Handle.allow[String] { + def safe: IO[Unit] = IO.unit + def recursive(remaining: Int): IO[Unit] = + if remaining <= 0 then IO.unit else recursive(remaining - 1) + + safe.attempt *> recursive(1).attempt + } + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineLocalValueCaptureTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineLocalValueCaptureTest.scala new file mode 100644 index 0000000..9a185b3 --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineLocalValueCaptureTest.scala @@ -0,0 +1,34 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* + +// scalafmt: { maxColumn = 160 } +object LocalValueCapture { + + def captured = Handle.allow[String] { + val h = summon[Handle[IO, String]] + val direct: IO[Unit] = h.raise("boom") + + direct.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def capturedMethod = Handle.allow[String] { + val h = summon[Handle[IO, String]] + + def boom: IO[Unit] = h.raise("boom") + lazy val action: IO[Unit] = boom + + action.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def normal = Handle.allow[String] { + val safe: IO[Unit] = IO.unit + + safe.attempt + } + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarinePartialAndNarrowHandlersTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarinePartialAndNarrowHandlersTest.scala new file mode 100644 index 0000000..0a014db --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarinePartialAndNarrowHandlersTest.scala @@ -0,0 +1,33 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* +import cats.syntax.all.* + +import java.io.IOException + +// scalafmt: { maxColumn = 160 } +object PartialAndNarrowHandlers { + + def methodRaise[F[_]](using r: Raise[F, String]): F[Unit] = + r.raise("boom") + + def precise[F[_]: Async](using Raise[F, String]): F[Unit] = { + methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].attemptNarrow[IOException] + Async[F].attemptNarrow[IOException, Unit](methodRaise[F]) + methodRaise[F].recover { case _: RuntimeException => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].recover { case _: IOException => () } + Async[F].recover(methodRaise[F]) { case _: IOException => () } + methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + } + + def unknown[F[_]: Async](handler: PartialFunction[Throwable, Unit])(using + Raise[F, String] + ): F[Unit] = + methodRaise[F].recover(handler) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseAliasTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseAliasTest.scala new file mode 100644 index 0000000..a83f7e3 --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseAliasTest.scala @@ -0,0 +1,42 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object RaiseAlias { + + type AliasedRaise[F[_], E] = Raise[F, E] + type ChainedRaiseAlias[F[_], E] = AliasedRaise[F, E] + type AliasedHandle[F[_], E] = Handle[F, E] + type ChainedHandleAlias[F[_], E] = AliasedHandle[F, E] + + def aliasedRaise[F[_]](using r: AliasedRaise[F, String]): F[Unit] = + r.raise("something went wrong") + + def chainedRaise[F[_]](using r: ChainedRaiseAlias[F, String]): F[Unit] = + r.raise("something went wrong") + + def aliasedHandle[F[_]](using h: AliasedHandle[F, String]): F[Unit] = + h.raise("something went wrong") + + def chainedHandle[F[_]](using h: ChainedHandleAlias[F, String]): F[Unit] = + h.raise("something went wrong") + + def aliasedRaiseHandling[F[_]: Async](using r: AliasedRaise[F, String]): F[Unit] = + aliasedRaise[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def chainedRaiseHandling[F[_]: Async](using r: ChainedRaiseAlias[F, String]): F[Unit] = + Async[F].attempt(chainedRaise[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def aliasedHandleHandling[F[_]: Async](using h: AliasedHandle[F, String]): F[Unit] = + aliasedHandle[F].recover(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + + def chainedHandleHandling[F[_]: Async](using h: ChainedHandleAlias[F, String]): F[Unit] = + Async[F].handleError(chainedHandle[F])(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseInstanceTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseInstanceTest.scala new file mode 100644 index 0000000..88c91ee --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseInstanceTest.scala @@ -0,0 +1,99 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object RaiseInstance { + + def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + r.raise[String, Unit]("").tupleRight("") + r.raise[String, Unit]("").void + } + + def monadErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + r.raise[String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, String]("").reject(_ => new Exception("")) + // ensure other methods aren't affected + r.raise[String, Unit]("").tupleRight("") + r.raise[String, Unit]("").void + } + + def applicativeErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].handleError(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit](r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT(r.raise[String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem(r.raise[String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError(r.raise[String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(r.raise[String, Unit](""), "") + Async[F].void(r.raise[String, Unit]("")) + } + + def monadErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].ensure(r.raise[String, Unit](""))(new Exception(""))(_ => false) + Async[F].ensureOr(r.raise[String, Unit](""))(_ => new Exception(""))(_ => false) + Async[F].redeemWith(r.raise[String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(r.raise[String, Unit](""), "") + Async[F].void(r.raise[String, Unit]("")) + } + + def io(using r: Raise[IO, String]): IO[Unit] = { + // applicative error + r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) + r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + r.raise[String, String]("").reject(_ => new Exception("")) + // ensure other methods aren't affected + r.raise[String, Unit]("").void + r.raise[String, Unit]("").product(r.raise[String, Unit]("")) + r.raise[String, Unit]("").debug() + } + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseObjectTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseObjectTest.scala new file mode 100644 index 0000000..b9e5f80 --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseObjectTest.scala @@ -0,0 +1,99 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object RaiseObject { + + def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Raise.raise[F, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Raise.raise[F, String, Unit]("").tupleRight("") + Raise.raise[F, String, Unit]("").void + } + + def monadErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) + Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[F, String, String]("").reject(_ => new Exception("")) + // ensure other methods aren't affected + Raise.raise[F, String, Unit]("").tupleRight("") + Raise.raise[F, String, Unit]("").void + } + + def applicativeErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].handleError(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit](Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT(Raise.raise[F, String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem(Raise.raise[F, String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError(Raise.raise[F, String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") + Async[F].void(Raise.raise[F, String, Unit]("")) + } + + def monadErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { + Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) + Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) + Async[F].redeemWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") + Async[F].void(Raise.raise[F, String, Unit]("")) + } + + def io(using r: Raise[IO, String]): IO[Unit] = { + // applicative error + Raise.raise[IO, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) + Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) + Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, String]("").reject(_ => new Exception("")) + // ensure other methods aren't affected + Raise.raise[IO, String, Unit]("").void + Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) + Raise.raise[IO, String, Unit]("").debug() + } + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseSyntaxTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseSyntaxTest.scala new file mode 100644 index 0000000..320efc4 --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseSyntaxTest.scala @@ -0,0 +1,99 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.mtl.syntax.raise.* +import cats.effect.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object RaiseSyntax { + + def applicativeErrorSyntax[F[_]: Async](using Raise[F, String]): F[Unit] = { + "".raise[F, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + "".raise[F, Unit].tupleRight("") + "".raise[F, Unit].void + } + + def monadErrorSyntax[F[_]: Async](using Raise[F, String]): F[Unit] = { + "".raise[F, Unit].ensure(new Exception(""))(_ => false) + "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) + "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, String].reject(_ => new Exception("")) + // ensure other methods aren't affected + "".raise[F, Unit].tupleRight("") + "".raise[F, Unit].void + } + + def applicativeErrorDirect[F[_]: Async](using Raise[F, String]): F[Unit] = { + Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleErrorWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attempt("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptNarrow[RuntimeException, Unit]("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptT("".raise[F, Unit]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recoverWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].redeem("".raise[F, Unit])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].onError("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].adaptError("".raise[F, Unit])(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].voidError("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight("".raise[F, Unit], "") + Async[F].void("".raise[F, Unit]) + } + + def monadErrorDirect[F[_]: Async](using Raise[F, String]): F[Unit] = { + Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) + Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) + Async[F].redeemWith("".raise[F, Unit])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].attemptTap("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // ensure other methods aren't affected + Async[F].tupleRight("".raise[F, Unit], "") + Async[F].void("".raise[F, Unit]) + } + + def io(using Raise[IO, String]): IO[Unit] = { + // applicative error + "".raise[IO, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + // monad error + "".raise[IO, Unit].ensure(new Exception(""))(_ => false) + "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) + "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, String].reject(_ => new Exception("")) + // ensure other methods aren't affected + "".raise[IO, Unit].void + "".raise[IO, Unit].debug() + } + +} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala deleted file mode 100644 index 248f9b7..0000000 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineTest.scala +++ /dev/null @@ -1,956 +0,0 @@ -/* -rule = TypelevelMTLSubmarine - */ -package fix - -import cats.mtl.* -import cats.mtl.syntax.raise.* -import cats.effect.* -import cats.effect.implicits.* -import cats.syntax.all.* - -import java.io.IOException - -// scalafmt: { maxColumn = 160 } -object MTLSubmarineTest { - - // Test matrix: objects select the provenance source or propagation shape; methods - // select the handler form and effect type; this file selects Scala 3; an `assert` - // comment means a diagnostic is expected, while its absence means the call is safe. - - object RaiseInstance { - - def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - r.raise[String, Unit]("").tupleRight("") - r.raise[String, Unit]("").void - } - - def monadErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) - r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - r.raise[String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - r.raise[String, Unit]("").tupleRight("") - r.raise[String, Unit]("").void - } - - def applicativeErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Async[F].handleError(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].handleErrorWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptNarrow[RuntimeException, Unit](r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptT(r.raise[String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recover(r.raise[String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recoverWith(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].redeem(r.raise[String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].onError(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].adaptError(r.raise[String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].voidError(r.raise[String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(r.raise[String, Unit](""), "") - Async[F].void(r.raise[String, Unit]("")) - } - - def monadErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Async[F].ensure(r.raise[String, Unit](""))(new Exception(""))(_ => false) - Async[F].ensureOr(r.raise[String, Unit](""))(_ => new Exception(""))(_ => false) - Async[F].redeemWith(r.raise[String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptTap(r.raise[String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(r.raise[String, Unit](""), "") - Async[F].void(r.raise[String, Unit]("")) - } - - def io(using r: Raise[IO, String]): IO[Unit] = { - // applicative error - r.raise[String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - r.raise[String, Unit]("").ensure(new Exception(""))(_ => false) - r.raise[String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - r.raise[String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - r.raise[String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - r.raise[String, Unit]("").void - r.raise[String, Unit]("").product(r.raise[String, Unit]("")) - r.raise[String, Unit]("").debug() - } - - } - - object HandleRequirement { - - def method[F[_]](using h: Handle[F, String]): F[Unit] = - h.raise("something went wrong") - - def contextFunctionMethod[F[_]]: Handle[F, String] ?=> F[Unit] = - summon[Handle[F, String]].raise("something went wrong") - - def normalContextFunctionMethod[F[_]]: Async[F] ?=> F[Unit] = - summon[Async[F]].unit - - def applicativeErrorSyntax[F[_]: Async](using h: Handle[F, String]): F[Unit] = - method[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def applicativeErrorDirect[F[_]: Async](using h: Handle[F, String]): F[Unit] = - Async[F].attempt(method[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def io(using h: Handle[IO, String]): IO[Unit] = - method[IO].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def contextFunction = Handle.allow[String] { - contextFunctionMethod[IO].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def normalContextFunction(using Async[IO]): IO[Unit] = - normalContextFunctionMethod[IO].attempt.void - - } - - object RaiseAlias { - - type AliasedRaise[F[_], E] = Raise[F, E] - type ChainedRaiseAlias[F[_], E] = AliasedRaise[F, E] - type AliasedHandle[F[_], E] = Handle[F, E] - type ChainedHandleAlias[F[_], E] = AliasedHandle[F, E] - - def aliasedRaise[F[_]](using r: AliasedRaise[F, String]): F[Unit] = - r.raise("something went wrong") - - def chainedRaise[F[_]](using r: ChainedRaiseAlias[F, String]): F[Unit] = - r.raise("something went wrong") - - def aliasedHandle[F[_]](using h: AliasedHandle[F, String]): F[Unit] = - h.raise("something went wrong") - - def chainedHandle[F[_]](using h: ChainedHandleAlias[F, String]): F[Unit] = - h.raise("something went wrong") - - def aliasedRaiseHandling[F[_]: Async](using r: AliasedRaise[F, String]): F[Unit] = - aliasedRaise[F].attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def chainedRaiseHandling[F[_]: Async](using r: ChainedRaiseAlias[F, String]): F[Unit] = - Async[F].attempt(chainedRaise[F]).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def aliasedHandleHandling[F[_]: Async](using h: AliasedHandle[F, String]): F[Unit] = - aliasedHandle[F].recover(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def chainedHandleHandling[F[_]: Async](using h: ChainedHandleAlias[F, String]): F[Unit] = - Async[F].handleError(chainedHandle[F])(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - } - - object WrappedRaise { - - trait Wrapper[A] - - def normal[F[_]: Async](using wrapper: Wrapper[Raise[F, String]]): F[Unit] = - Async[F].unit - - def handlingNormal[F[_]: Async](using wrapper: Wrapper[Raise[F, String]]): F[Unit] = - normal[F].attempt.void - - } - - object DirectProtectedArgument { - - def raiseDependentRecovery[F[_]](error: Throwable)(using r: Raise[F, String]): F[Unit] = - r.raise(error.getMessage) - - def normalSource[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Async[F].handleErrorWith(Async[F].unit)(raiseDependentRecovery[F]) - Async[F].redeemWith(Async[F].unit)(raiseDependentRecovery[F], _ => Async[F].unit) - } - - } - - object ContextFunctionValue { - - def valueHandling[F[_]: Async](using Raise[F, String]): F[Unit] = { - val action: Raise[F, String] ?=> F[Unit] = r ?=> r.raise("something went wrong") - - action.attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def parameterHandling[F[_]: Async]( - action: Raise[F, String] ?=> F[Unit] - )(using Raise[F, String]): F[Unit] = - action.recover(_ => ()).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - def normalParameter[F[_]: Async](action: Async[F] ?=> F[Unit]): F[Unit] = - action.attempt.void - - } - - object EffectPropagation { - - def methodRaise[F[_]](using r: Raise[F, String]): F[Unit] = - r.raise("something went wrong") - - def syntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - methodRaise[F].map(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].void.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].as(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (Async[F].unit *> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (methodRaise[F] <* Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (Async[F].unit >> methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].product(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.product(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].productL(Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.productR(methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.map2(methodRaise[F])((_, _) => ()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].fproduct(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].fproductLeft(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].tupleLeft(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].tupleRight(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F] - .as((_: Unit) => ()) - .<&>(Async[F].unit) - .attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.map(identity).attempt - (for { - _ <- Async[F].unit - _ <- methodRaise[F] - } yield ()).attempt.void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def direct[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Async[F].attempt(Async[F].map(methodRaise[F])(identity)).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => methodRaise[F])).void // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt(Async[F].map(Async[F].unit)(identity)).void - Async[F].attempt(Async[F].flatMap(Async[F].unit)(_ => Async[F].unit)).void - } - - def safeSyntax[F[_]: Async]: F[Unit] = { - Async[F].unit.map(identity).attempt - Async[F].unit.void.recover(_ => ()) - Async[F].unit.as(()).attempt - (Async[F].unit *> Async[F].unit).attempt - (Async[F].unit <* Async[F].unit).attempt - (Async[F].unit >> Async[F].unit).attempt - Async[F].unit.product(Async[F].unit).attempt - Async[F].unit.productL(Async[F].unit).attempt - Async[F].unit.productR(Async[F].unit).attempt - Async[F].unit.map2(Async[F].unit)((_, _) => ()).attempt - Async[F].unit.fproduct(identity).attempt - Async[F].unit.fproductLeft(identity).attempt - Async[F].unit.tupleLeft(()).attempt - Async[F].unit.tupleRight(()).attempt - Async[F].pure((_: Unit) => ()).<&>(Async[F].unit).attempt - Async[F].unit.flatMap(_ => Async[F].unit).attempt - Async[F].unit.flatTap(_ => Async[F].unit).attempt - (Async[F].unit >>= (_ => Async[F].unit)).attempt - Async[F].unit.mproduct(_ => Async[F].unit).attempt - (for { - _ <- Async[F].unit - _ <- Async[F].unit - } yield ()).attempt.void - } - - def expressionWrappers[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - (methodRaise[F]: F[Unit]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (((methodRaise[F]))).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (Async[F].unit: F[Unit]).attempt.void - } - - def conditional[F[_]: Async](condition: Boolean)(using r: Raise[F, String]): F[Unit] = { - (if (condition) Async[F].unit else methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (if (condition) methodRaise[F] else Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (if (condition) Async[F].unit else Async[F].unit).attempt.void - } - - def matched[F[_]: Async](condition: Boolean)(using r: Raise[F, String]): F[Unit] = { - (condition match { - case true => Async[F].unit - case false => methodRaise[F] - }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (condition match { - case true => methodRaise[F] - case false => Async[F].unit - }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (condition match { - case true => Async[F].unit - case false => Async[F].unit - }).attempt.void - } - - def blocks[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - ({ - Async[F].unit - methodRaise[F] - }).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - ({ - methodRaise[F] - Async[F].unit - }).attempt.void - } - - } - - object PartialAndNarrowHandlers { - - def methodRaise[F[_]](using r: Raise[F, String]): F[Unit] = - r.raise("boom") - - def precise[F[_]: Async](using Raise[F, String]): F[Unit] = { - methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].attemptNarrow[IOException] - Async[F].attemptNarrow[IOException, Unit](methodRaise[F]) - methodRaise[F].recover { case _: RuntimeException => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].recover { case _: IOException => () } - Async[F].recover(methodRaise[F]) { case _: IOException => () } - methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def unknown[F[_]: Async](handler: PartialFunction[Throwable, Unit])(using - Raise[F, String] - ): F[Unit] = - methodRaise[F].recover(handler) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - - } - - object LocalMethodCapture { - - def captured = Handle.allow[String] { - val h = summon[Handle[IO, String]] - - def boom: IO[Unit] = h.raise("boom") - - boom.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def chainedCapture = Handle.allow[String] { - val h = summon[Handle[IO, String]] - - def boom: IO[Unit] = h.raise("boom") - def indirect: IO[Unit] = boom - - indirect.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def normal = Handle.allow[String] { - def safe: IO[Unit] = IO.unit - def recursive(remaining: Int): IO[Unit] = - if remaining <= 0 then IO.unit else recursive(remaining - 1) - - safe.attempt *> recursive(1).attempt - } - - } - - object LocalValueCapture { - - def captured = Handle.allow[String] { - val h = summon[Handle[IO, String]] - val direct: IO[Unit] = h.raise("boom") - - direct.attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def capturedMethod = Handle.allow[String] { - val h = summon[Handle[IO, String]] - - def boom: IO[Unit] = h.raise("boom") - lazy val action: IO[Unit] = boom - - action.recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - } - - def normal = Handle.allow[String] { - val safe: IO[Unit] = IO.unit - - safe.attempt - } - - } - - object RaiseObject { - - def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Raise.raise[F, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Raise.raise[F, String, Unit]("").tupleRight("") - Raise.raise[F, String, Unit]("").void - } - - def monadErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) - Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - Raise.raise[F, String, Unit]("").tupleRight("") - Raise.raise[F, String, Unit]("").void - } - - def applicativeErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Async[F].handleError(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].handleErrorWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptNarrow[RuntimeException, Unit](Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptT(Raise.raise[F, String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recover(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recoverWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].redeem(Raise.raise[F, String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].onError(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].adaptError(Raise.raise[F, String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].voidError(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") - Async[F].void(Raise.raise[F, String, Unit]("")) - } - - def monadErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) - Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) - Async[F].redeemWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptTap(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") - Async[F].void(Raise.raise[F, String, Unit]("")) - } - - def io(using r: Raise[IO, String]): IO[Unit] = { - // applicative error - Raise.raise[IO, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) - Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - Raise.raise[IO, String, Unit]("").void - Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) - Raise.raise[IO, String, Unit]("").debug() - } - - } - - object RaiseSyntax { - - def applicativeErrorSyntax[F[_]: Async](using Raise[F, String]): F[Unit] = { - "".raise[F, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - "".raise[F, Unit].tupleRight("") - "".raise[F, Unit].void - } - - def monadErrorSyntax[F[_]: Async](using Raise[F, String]): F[Unit] = { - "".raise[F, Unit].ensure(new Exception(""))(_ => false) - "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) - "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, String].reject(_ => new Exception("")) - // ensure other methods aren't affected - "".raise[F, Unit].tupleRight("") - "".raise[F, Unit].void - } - - def applicativeErrorDirect[F[_]: Async](using Raise[F, String]): F[Unit] = { - Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].handleErrorWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptNarrow[RuntimeException, Unit]("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptT("".raise[F, Unit]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recover("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recoverWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].redeem("".raise[F, Unit])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].onError("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].adaptError("".raise[F, Unit])(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].voidError("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight("".raise[F, Unit], "") - Async[F].void("".raise[F, Unit]) - } - - def monadErrorDirect[F[_]: Async](using Raise[F, String]): F[Unit] = { - Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) - Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) - Async[F].redeemWith("".raise[F, Unit])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptTap("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight("".raise[F, Unit], "") - Async[F].void("".raise[F, Unit]) - } - - def io(using Raise[IO, String]): IO[Unit] = { - // applicative error - "".raise[IO, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - "".raise[IO, Unit].ensure(new Exception(""))(_ => false) - "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) - "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, String].reject(_ => new Exception("")) - // ensure other methods aren't affected - "".raise[IO, Unit].void - "".raise[IO, Unit].debug() - } - - } - - object HandleAllowGiven { - - def methodRaise[F[_]](using r: Raise[F, String]): F[Unit] = - r.raise("something went wrong") - - def method[F[_]: Async]: F[Unit] = - Async[F].unit - - def applicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- method[F].handleError(_ => ()) - _ <- method[F].handleErrorWith(_ => Async[F].unit) - _ <- method[F].attempt - _ <- method[F].attemptNarrow[RuntimeException] - _ <- method[F].attemptT.value - _ <- method[F].recover { case _ => () } - _ <- method[F].recoverWith { case _ => Async[F].unit } - _ <- method[F].redeem(_ => (), _ => ()) - _ <- method[F].onError { case _ => Async[F].unit } - _ <- method[F].orElse(Async[F].unit) - _ <- method[F].adaptErr { case _ => new Exception("") } - _ <- method[F].orRaise(new Exception("")) - _ <- method[F].voidError - } yield () - } - - def applicativeErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].handleError(method[F])(_ => ()) - _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) - _ <- Async[F].attempt(method[F]) - _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) - _ <- Async[F].attemptT(method[F]).value - _ <- Async[F].recover(method[F]) { case _ => () } - _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } - _ <- Async[F].redeem(method[F])(_ => (), _ => ()) - _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } - _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } - _ <- Async[F].voidError(method[F]) - } yield () - } - - def monadErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) - _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) - _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) - } yield () - } - - def monadErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- method[F].ensure(new Exception(""))(_ => false) - _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow - _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) - _ <- method[F].attemptTap(_ => Async[F].unit) - _ <- method[F].adaptError { case _ => new Exception("") } - _ <- method[F].as("").reject { case _ => new Exception("") } - } yield () - } - - def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - _ <- methodRaise[F].product(methodRaise[F]) - } yield () - } - - def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - _ <- Async[F].product(methodRaise[F], methodRaise[F]) - } yield () - } - - def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - } yield () - } - - def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- methodRaise[F].ensure(new Exception(""))(_ => false) - _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected - _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - } yield () - } - - def withIO = Handle.allow[String] { - for { - // applicative error - _ <- method[IO].handleError(_ => ()) - _ <- method[IO].handleErrorWith(_ => IO.unit) - _ <- method[IO].attempt - _ <- method[IO].attemptNarrow[RuntimeException] - _ <- method[IO].attemptT.value - _ <- method[IO].recover { case _ => () } - _ <- method[IO].recoverWith { case _ => IO.unit } - _ <- method[IO].redeem(_ => (), _ => ()) - _ <- method[IO].onError { case _ => IO.unit } - _ <- method[IO].orElse(IO.unit) - _ <- method[IO].adaptErr { case _ => new Exception("") } - _ <- method[IO].orRaise(new Exception("")) - _ <- method[IO].voidError - // monad error - _ <- method[IO].ensure(new Exception(""))(_ => false) - _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) - _ <- method[IO].attemptTap(_ => IO.unit) - _ <- method[IO].adaptError { case _ => new Exception("") } - _ <- method[IO].as("").reject { case _ => new Exception("") } - } yield () - } - - def raiseWithIO = Handle.allow[String] { - for { - // applicative error - _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) - _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected - _ <- methodRaise[IO].void - _ <- methodRaise[IO].debug() - } yield () - } - - } - - object HandleAllowContextFunction { - - def methodRaise[F[_]]: Raise[F, String] ?=> F[Unit] = r ?=> r.raise("something went wrong") - - def method[F[_]: Async]: F[Unit] = - Async[F].unit - - def applicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- method[F].handleError(_ => ()) - _ <- method[F].handleErrorWith(_ => Async[F].unit) - _ <- method[F].attempt - _ <- method[F].attemptNarrow[RuntimeException] - _ <- method[F].attemptT.value - _ <- method[F].recover { case _ => () } - _ <- method[F].recoverWith { case _ => Async[F].unit } - _ <- method[F].redeem(_ => (), _ => ()) - _ <- method[F].onError { case _ => Async[F].unit } - _ <- method[F].orElse(Async[F].unit) - _ <- method[F].adaptErr { case _ => new Exception("") } - _ <- method[F].orRaise(new Exception("")) - _ <- method[F].voidError - } yield () - } - - def applicativeErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].handleError(method[F])(_ => ()) - _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) - _ <- Async[F].attempt(method[F]) - _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) - _ <- Async[F].attemptT(method[F]).value - _ <- Async[F].recover(method[F]) { case _ => () } - _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } - _ <- Async[F].redeem(method[F])(_ => (), _ => ()) - _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } - _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } - _ <- Async[F].voidError(method[F]) - } yield () - } - - def monadErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) - _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) - _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) - } yield () - } - - def monadErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- method[F].ensure(new Exception(""))(_ => false) - _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow - _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) - _ <- method[F].attemptTap(_ => Async[F].unit) - _ <- method[F].adaptError { case _ => new Exception("") } - _ <- method[F].as("").reject { case _ => new Exception("") } - } yield () - } - - def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - } yield () - } - - def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - } yield () - } - - def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - } yield () - } - - def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- methodRaise[F].ensure(new Exception(""))(_ => false) - _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected - _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - } yield () - } - - def withIO = Handle.allow[String] { - for { - // applicative error - _ <- method[IO].handleError(_ => ()) - _ <- method[IO].handleErrorWith(_ => IO.unit) - _ <- method[IO].attempt - _ <- method[IO].attemptNarrow[RuntimeException] - _ <- method[IO].attemptT.value - _ <- method[IO].recover { case _ => () } - _ <- method[IO].recoverWith { case _ => IO.unit } - _ <- method[IO].redeem(_ => (), _ => ()) - _ <- method[IO].onError { case _ => IO.unit } - _ <- method[IO].orElse(IO.unit) - _ <- method[IO].adaptErr { case _ => new Exception("") } - _ <- method[IO].orRaise(new Exception("")) - _ <- method[IO].voidError - // monad error - _ <- method[IO].ensure(new Exception(""))(_ => false) - _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) - _ <- method[IO].attemptTap(_ => IO.unit) - _ <- method[IO].adaptError { case _ => new Exception("") } - _ <- method[IO].as("").reject { case _ => new Exception("") } - } yield () - } - - def raiseWithIO = Handle.allow[String] { - for { - // applicative error - _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) - _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected - _ <- methodRaise[IO].void - _ <- methodRaise[IO].debug() - } yield () - } - - } - -} diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineWrappedRaiseTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineWrappedRaiseTest.scala new file mode 100644 index 0000000..f2971be --- /dev/null +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineWrappedRaiseTest.scala @@ -0,0 +1,21 @@ +/* +rule = TypelevelMTLSubmarine + */ +package fix + +import cats.mtl.* +import cats.effect.* +import cats.syntax.all.* + +// scalafmt: { maxColumn = 160 } +object WrappedRaise { + + trait Wrapper[A] + + def normal[F[_]: Async](using wrapper: Wrapper[Raise[F, String]]): F[Unit] = + Async[F].unit + + def handlingNormal[F[_]: Async](using wrapper: Wrapper[Raise[F, String]]): F[Unit] = + normal[F].attempt.void + +} From d9f03ebb60c9892b32c955d67055e78cf581d19f Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 7 Sep 2026 08:02:28 +0100 Subject: [PATCH 33/39] Simplify tests --- .../fix/MTLSubmarineHandleAllowFTest.scala | 173 +--------------- .../fix/MTLSubmarineRaiseObjectTest.scala | 84 +------- .../fix/MTLSubmarineRaiseSyntaxTest.scala | 83 +------- ...marineHandleAllowContextFunctionTest.scala | 184 ++---------------- .../MTLSubmarineHandleAllowGivenTest.scala | 172 +--------------- .../fix/MTLSubmarineRaiseObjectTest.scala | 84 +------- .../fix/MTLSubmarineRaiseSyntaxTest.scala | 81 +------- 7 files changed, 45 insertions(+), 816 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala index 84ddc57..1c4c6e7 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala @@ -7,7 +7,6 @@ import cats.mtl._ import cats.effect._ import cats.syntax.all._ - // scalafmt: { maxColumn = 160 } object HandleAllowF { @@ -17,183 +16,27 @@ object HandleAllowF { def method[F[_]: Async]: F[Unit] = Async[F].unit - def applicativeErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { _ => + def safeGeneric[F[_]: Async] = Handle.allowF[F, String] { _ => for { - _ <- method[F].handleError(_ => ()) - _ <- method[F].handleErrorWith(_ => Async[F].unit) _ <- method[F].attempt - _ <- method[F].attemptNarrow[RuntimeException] - _ <- method[F].attemptT.value - _ <- method[F].recover { case _ => () } - _ <- method[F].recoverWith { case _ => Async[F].unit } - _ <- method[F].redeem(_ => (), _ => ()) - _ <- method[F].onError { case _ => Async[F].unit } - _ <- method[F].orElse(Async[F].unit) - _ <- method[F].adaptErr { case _ => new Exception("") } - _ <- method[F].orRaise(new Exception("")) - _ <- method[F].voidError - } yield () - } - - def applicativeErrorDirect[F[_]: Async] = Handle.allowF[F, String] { _ => - for { - _ <- Async[F].handleError(method[F])(_ => ()) - _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) - _ <- Async[F].attempt(method[F]) - _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) - _ <- Async[F].attemptT(method[F]).value _ <- Async[F].recover(method[F]) { case _ => () } - _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } - _ <- Async[F].redeem(method[F])(_ => (), _ => ()) - _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } - _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } - _ <- Async[F].voidError(method[F]) - } yield () - } - - def monadErrorDirect[F[_]: Async] = Handle.allowF[F, String] { _ => - for { - _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) - _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) - _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) - } yield () - } - - def monadErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { _ => - for { _ <- method[F].ensure(new Exception(""))(_ => false) - _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow - _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) - _ <- method[F].attemptTap(_ => Async[F].unit) - _ <- method[F].adaptError { case _ => new Exception("") } - _ <- method[F].as("").reject { case _ => new Exception("") } - } yield () - } - - def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { implicit h => - for { - _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - _ <- methodRaise[F].product(methodRaise[F]) - } yield () - } - - def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allowF[F, String] { implicit h => - for { - _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - _ <- Async[F].product(methodRaise[F], methodRaise[F]) - } yield () - } - - def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allowF[F, String] { implicit h => - for { - _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") } yield () } - def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allowF[F, String] { implicit h => + def dangerousGeneric[F[_]: Async] = Handle.allowF[F, String] { implicit h => for { + _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].ensure(new Exception(""))(_ => false) - _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - } yield () - } - - def withIO = Handle.allowF[IO, String] { _ => - for { - // applicative error - _ <- method[IO].handleError(_ => ()) - _ <- method[IO].handleErrorWith(_ => IO.unit) - _ <- method[IO].attempt - _ <- method[IO].attemptNarrow[RuntimeException] - _ <- method[IO].attemptT.value - _ <- method[IO].recover { case _ => () } - _ <- method[IO].recoverWith { case _ => IO.unit } - _ <- method[IO].redeem(_ => (), _ => ()) - _ <- method[IO].onError { case _ => IO.unit } - _ <- method[IO].orElse(IO.unit) - _ <- method[IO].adaptErr { case _ => new Exception("") } - _ <- method[IO].orRaise(new Exception("")) - _ <- method[IO].voidError - // monad error - _ <- method[IO].ensure(new Exception(""))(_ => false) - _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) - _ <- method[IO].attemptTap(_ => IO.unit) - _ <- method[IO].adaptError { case _ => new Exception("") } - _ <- method[IO].as("").reject { case _ => new Exception("") } } yield () } - def raiseWithIO = Handle.allowF[IO, String] { implicit h => - for { - // applicative error - _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) - _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected - _ <- methodRaise[IO].void - _ <- methodRaise[IO].debug() - } yield () + def io = Handle.allowF[IO, String] { implicit h => + method[IO].onError(_ => IO.unit) + methodRaise[IO].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[IO].debug() } } diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseObjectTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseObjectTest.scala index 6050a57..dfef498 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseObjectTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseObjectTest.scala @@ -7,94 +7,18 @@ import cats.mtl._ import cats.effect._ import cats.syntax.all._ - // scalafmt: { maxColumn = 160 } object RaiseObject { - def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - // ApplicativeError syntax - Raise.raise[F, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Raise.raise[F, String, Unit]("").tupleRight("") - Raise.raise[F, String, Unit]("").void - } - - def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) - Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - Raise.raise[F, String, Unit]("").tupleRight("") + def generic[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + Raise.raise[F, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover(Raise.raise[F, String, Unit]("")) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[F, String, Unit]("").void } - def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].handleError(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].handleErrorWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptNarrow[RuntimeException, Unit](Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptT(Raise.raise[F, String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recover(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recoverWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].redeem(Raise.raise[F, String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].onError(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].adaptError(Raise.raise[F, String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].voidError(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") - Async[F].void(Raise.raise[F, String, Unit]("")) - } - - def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) - Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) - Async[F].redeemWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptTap(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") - Async[F].void(Raise.raise[F, String, Unit]("")) - } - def io(implicit r: Raise[IO, String]): IO[Unit] = { - // applicative error - Raise.raise[IO, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) - Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Raise.raise[IO, String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[IO, String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - Raise.raise[IO, String, Unit]("").void - Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) Raise.raise[IO, String, Unit]("").debug() } diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala index 8d82a31..16ba253 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala @@ -8,93 +8,18 @@ import cats.mtl.syntax.raise._ import cats.effect._ import cats.syntax.all._ - // scalafmt: { maxColumn = 160 } object RaiseSyntax { - def applicativeErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - // ApplicativeError syntax - "".raise[F, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - "".raise[F, Unit].tupleRight("") - "".raise[F, Unit].void - } - - def monadErrorSyntax[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - "".raise[F, Unit].ensure(new Exception(""))(_ => false) - "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) - "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, String].reject(_ => new Exception("")) - // ensure other methods aren't affected - "".raise[F, Unit].tupleRight("") + def generic[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { + "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].void } - def applicativeErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].handleErrorWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptNarrow[RuntimeException, Unit]("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptT("".raise[F, Unit]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recover("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recoverWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].redeem("".raise[F, Unit])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].onError("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].adaptError("".raise[F, Unit])(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].voidError("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight("".raise[F, Unit], "") - Async[F].void("".raise[F, Unit]) - } - - def monadErrorDirect[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) - Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) - Async[F].redeemWith("".raise[F, Unit])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptTap("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight("".raise[F, Unit], "") - Async[F].void("".raise[F, Unit]) - } - def io(implicit r: Raise[IO, String]): IO[Unit] = { - // applicative error - "".raise[IO, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - "".raise[IO, Unit].ensure(new Exception(""))(_ => false) - "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) - "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[IO, String].reject(_ => new Exception("")) - // ensure other methods aren't affected - "".raise[IO, Unit].void "".raise[IO, Unit].debug() } diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala index 609a5cd..3a4871c 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala @@ -10,186 +10,26 @@ import cats.syntax.all.* // scalafmt: { maxColumn = 160 } object HandleAllowContextFunction { - def methodRaise[F[_]]: Raise[F, String] ?=> F[Unit] = r ?=> r.raise("something went wrong") + def methodRaise[F[_]]: Raise[F, String] ?=> F[Unit] = + summon[Raise[F, String]].raise("something went wrong") def method[F[_]: Async]: F[Unit] = Async[F].unit - def applicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- method[F].handleError(_ => ()) - _ <- method[F].handleErrorWith(_ => Async[F].unit) - _ <- method[F].attempt - _ <- method[F].attemptNarrow[RuntimeException] - _ <- method[F].attemptT.value - _ <- method[F].recover { case _ => () } - _ <- method[F].recoverWith { case _ => Async[F].unit } - _ <- method[F].redeem(_ => (), _ => ()) - _ <- method[F].onError { case _ => Async[F].unit } - _ <- method[F].orElse(Async[F].unit) - _ <- method[F].adaptErr { case _ => new Exception("") } - _ <- method[F].orRaise(new Exception("")) - _ <- method[F].voidError - } yield () + def safeGeneric[F[_]: Async] = Handle.allow[String] { + method[F].attempt } - def applicativeErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].handleError(method[F])(_ => ()) - _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) - _ <- Async[F].attempt(method[F]) - _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) - _ <- Async[F].attemptT(method[F]).value - _ <- Async[F].recover(method[F]) { case _ => () } - _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } - _ <- Async[F].redeem(method[F])(_ => (), _ => ()) - _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } - _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } - _ <- Async[F].voidError(method[F]) - } yield () + def dangerousGeneric[F[_]: Async] = Handle.allow[String] { + methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].ensure(new Exception(""))(_ => false) + methodRaise[F].void } - def monadErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) - _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) - _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) - } yield () - } - - def monadErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- method[F].ensure(new Exception(""))(_ => false) - _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow - _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) - _ <- method[F].attemptTap(_ => Async[F].unit) - _ <- method[F].adaptError { case _ => new Exception("") } - _ <- method[F].as("").reject { case _ => new Exception("") } - } yield () - } - - def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - } yield () - } - - def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - } yield () - } - - def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - } yield () - } - - def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- methodRaise[F].ensure(new Exception(""))(_ => false) - _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected - _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - } yield () - } - - def withIO = Handle.allow[String] { - for { - // applicative error - _ <- method[IO].handleError(_ => ()) - _ <- method[IO].handleErrorWith(_ => IO.unit) - _ <- method[IO].attempt - _ <- method[IO].attemptNarrow[RuntimeException] - _ <- method[IO].attemptT.value - _ <- method[IO].recover { case _ => () } - _ <- method[IO].recoverWith { case _ => IO.unit } - _ <- method[IO].redeem(_ => (), _ => ()) - _ <- method[IO].onError { case _ => IO.unit } - _ <- method[IO].orElse(IO.unit) - _ <- method[IO].adaptErr { case _ => new Exception("") } - _ <- method[IO].orRaise(new Exception("")) - _ <- method[IO].voidError - // monad error - _ <- method[IO].ensure(new Exception(""))(_ => false) - _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) - _ <- method[IO].attemptTap(_ => IO.unit) - _ <- method[IO].adaptError { case _ => new Exception("") } - _ <- method[IO].as("").reject { case _ => new Exception("") } - } yield () - } - - def raiseWithIO = Handle.allow[String] { - for { - // applicative error - _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) - _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected - _ <- methodRaise[IO].void - _ <- methodRaise[IO].debug() - } yield () + def io = Handle.allow[String] { + methodRaise[IO].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[IO].debug() } } diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowGivenTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowGivenTest.scala index 0070257..5202929 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowGivenTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowGivenTest.scala @@ -16,183 +16,27 @@ object HandleAllowGiven { def method[F[_]: Async]: F[Unit] = Async[F].unit - def applicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { + def safeGeneric[F[_]: Async] = Handle.allow[String] { for { - _ <- method[F].handleError(_ => ()) - _ <- method[F].handleErrorWith(_ => Async[F].unit) _ <- method[F].attempt - _ <- method[F].attemptNarrow[RuntimeException] - _ <- method[F].attemptT.value - _ <- method[F].recover { case _ => () } - _ <- method[F].recoverWith { case _ => Async[F].unit } - _ <- method[F].redeem(_ => (), _ => ()) - _ <- method[F].onError { case _ => Async[F].unit } - _ <- method[F].orElse(Async[F].unit) - _ <- method[F].adaptErr { case _ => new Exception("") } - _ <- method[F].orRaise(new Exception("")) - _ <- method[F].voidError - } yield () - } - - def applicativeErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].handleError(method[F])(_ => ()) - _ <- Async[F].handleErrorWith(method[F])(_ => Async[F].unit) - _ <- Async[F].attempt(method[F]) - _ <- Async[F].attemptNarrow[RuntimeException, Unit](method[F]) - _ <- Async[F].attemptT(method[F]).value _ <- Async[F].recover(method[F]) { case _ => () } - _ <- Async[F].recoverWith(method[F]) { case _ => Async[F].unit } - _ <- Async[F].redeem(method[F])(_ => (), _ => ()) - _ <- Async[F].onError(method[F]) { case _ => Async[F].unit } - _ <- Async[F].adaptError(method[F]) { case _ => new Exception("") } - _ <- Async[F].voidError(method[F]) - } yield () - } - - def monadErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].ensure(method[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(method[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].rethrow(method[F].map(_ => Either.right[Throwable, Unit](()))) - _ <- Async[F].redeemWith(method[F])(_ => Async[F].unit, _ => Async[F].unit) - _ <- Async[F].attemptTap(method[F])(_ => Async[F].unit) - } yield () - } - - def monadErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { _ <- method[F].ensure(new Exception(""))(_ => false) - _ <- method[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[F].map(_ => Either.right[Throwable, Unit](())).rethrow - _ <- method[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) - _ <- method[F].attemptTap(_ => Async[F].unit) - _ <- method[F].adaptError { case _ => new Exception("") } - _ <- method[F].as("").reject { case _ => new Exception("") } } yield () } - def raiseWithApplicativeErrorSyntax[F[_]: Async] = Handle.allow[String] { - for { - _ <- methodRaise[F].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].recoverWith { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].onError { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") - _ <- methodRaise[F].product(methodRaise[F]) - } yield () - } - - def raiseWithApplicativeErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].handleErrorWith(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attempt(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptNarrow[RuntimeException, Unit](methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptT(methodRaise[F]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].recoverWith(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].redeem(methodRaise[F])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].onError(methodRaise[F]) { case _ => Async[F].unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].adaptError(methodRaise[F]) { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].voidError(methodRaise[F]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - _ <- Async[F].product(methodRaise[F], methodRaise[F]) - } yield () - } - - def raiseWithMonadErrorDirect[F[_]: Async] = Handle.allow[String] { - for { - _ <- Async[F].ensure(methodRaise[F])(new Exception(""))(_ => false) - _ <- Async[F].ensureOr(methodRaise[F])(_ => new Exception(""))(_ => false) - _ <- Async[F].redeemWith(methodRaise[F])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- Async[F].attemptTap(methodRaise[F])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - _ <- Async[F].void(methodRaise[F]) - _ <- Async[F].tupleRight(methodRaise[F], "") - } yield () - } - - def raiseWithMonadErrorSyntax[F[_]: Async] = Handle.allow[String] { + def dangerousGeneric[F[_]: Async] = Handle.allow[String] { for { + _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].ensure(new Exception(""))(_ => false) - _ <- methodRaise[F].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[F].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[F].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected _ <- methodRaise[F].void - _ <- methodRaise[F].tupleRight("") } yield () } - def withIO = Handle.allow[String] { - for { - // applicative error - _ <- method[IO].handleError(_ => ()) - _ <- method[IO].handleErrorWith(_ => IO.unit) - _ <- method[IO].attempt - _ <- method[IO].attemptNarrow[RuntimeException] - _ <- method[IO].attemptT.value - _ <- method[IO].recover { case _ => () } - _ <- method[IO].recoverWith { case _ => IO.unit } - _ <- method[IO].redeem(_ => (), _ => ()) - _ <- method[IO].onError { case _ => IO.unit } - _ <- method[IO].orElse(IO.unit) - _ <- method[IO].adaptErr { case _ => new Exception("") } - _ <- method[IO].orRaise(new Exception("")) - _ <- method[IO].voidError - // monad error - _ <- method[IO].ensure(new Exception(""))(_ => false) - _ <- method[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- method[IO].redeemWith(_ => IO.unit, _ => IO.unit) - _ <- method[IO].attemptTap(_ => IO.unit) - _ <- method[IO].adaptError { case _ => new Exception("") } - _ <- method[IO].as("").reject { case _ => new Exception("") } - } yield () - } - - def raiseWithIO = Handle.allow[String] { - for { - // applicative error - _ <- methodRaise[IO].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recover { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].recoverWith { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].onError { case _ => IO.unit } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptErr { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - _ <- methodRaise[IO].ensure(new Exception(""))(_ => false) - _ <- methodRaise[IO].ensureOr(_ => new Exception(""))(_ => false) - _ <- methodRaise[IO].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].adaptError { case _ => new Exception("") } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - _ <- methodRaise[IO].as("").reject { case _ => new Exception("") } - // ensure other methods aren't affected - _ <- methodRaise[IO].void - _ <- methodRaise[IO].debug() - } yield () + def io = Handle.allow[String] { + method[IO].onError(_ => IO.unit) + methodRaise[IO].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[IO].debug() } } diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseObjectTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseObjectTest.scala index b9e5f80..694ea7e 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseObjectTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseObjectTest.scala @@ -10,89 +10,15 @@ import cats.syntax.all.* // scalafmt: { maxColumn = 160 } object RaiseObject { - def applicativeErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Raise.raise[F, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Raise.raise[F, String, Unit]("").tupleRight("") + def generic[F[_]: Async](using Raise[F, String]): F[Unit] = { + Raise.raise[F, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].recover(Raise.raise[F, String, Unit]("")) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[F, String, Unit]("").void } - def monadErrorSyntax[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Raise.raise[F, String, Unit]("").ensure(new Exception(""))(_ => false) - Raise.raise[F, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - Raise.raise[F, String, Unit]("").redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[F, String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - Raise.raise[F, String, Unit]("").tupleRight("") - Raise.raise[F, String, Unit]("").void - } - - def applicativeErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Async[F].handleError(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].handleErrorWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptNarrow[RuntimeException, Unit](Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptT(Raise.raise[F, String, Unit]("")).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recover(Raise.raise[F, String, Unit](""))(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recoverWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].redeem(Raise.raise[F, String, Unit](""))(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].onError(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].adaptError(Raise.raise[F, String, Unit](""))(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].voidError(Raise.raise[F, String, Unit]("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") - Async[F].void(Raise.raise[F, String, Unit]("")) - } - - def monadErrorDirect[F[_]: Async](using r: Raise[F, String]): F[Unit] = { - Async[F].ensure(Raise.raise[F, String, Unit](""))(new Exception(""))(_ => false) - Async[F].ensureOr(Raise.raise[F, String, Unit](""))(_ => new Exception(""))(_ => false) - Async[F].redeemWith(Raise.raise[F, String, Unit](""))(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptTap(Raise.raise[F, String, Unit](""))(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight(Raise.raise[F, String, Unit](""), "") - Async[F].void(Raise.raise[F, String, Unit]("")) - } - - def io(using r: Raise[IO, String]): IO[Unit] = { - // applicative error - Raise.raise[IO, String, Unit]("").handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - Raise.raise[IO, String, Unit]("").ensure(new Exception(""))(_ => false) - Raise.raise[IO, String, Unit]("").ensureOr(_ => new Exception(""))(_ => false) - Raise.raise[IO, String, Unit]("").redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Raise.raise[IO, String, Unit]("").adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + def io(using Raise[IO, String]): IO[Unit] = { + Raise.raise[IO, String, Unit]("").onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Raise.raise[IO, String, String]("").reject(_ => new Exception("")) - // ensure other methods aren't affected - Raise.raise[IO, String, Unit]("").void - Raise.raise[IO, String, Unit]("").product(Raise.raise[IO, String, Unit]("")) Raise.raise[IO, String, Unit]("").debug() } diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseSyntaxTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseSyntaxTest.scala index 320efc4..e29e1ce 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseSyntaxTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineRaiseSyntaxTest.scala @@ -11,88 +11,15 @@ import cats.syntax.all.* // scalafmt: { maxColumn = 160 } object RaiseSyntax { - def applicativeErrorSyntax[F[_]: Async](using Raise[F, String]): F[Unit] = { - "".raise[F, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].handleErrorWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].recoverWith(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].onError(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].orElse(Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - "".raise[F, Unit].tupleRight("") + def generic[F[_]: Async](using Raise[F, String]): F[Unit] = { + "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].void } - def monadErrorSyntax[F[_]: Async](using Raise[F, String]): F[Unit] = { - "".raise[F, Unit].ensure(new Exception(""))(_ => false) - "".raise[F, Unit].ensureOr(_ => new Exception(""))(_ => false) - "".raise[F, Unit].redeemWith(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].attemptTap(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[F, String].reject(_ => new Exception("")) - // ensure other methods aren't affected - "".raise[F, Unit].tupleRight("") - "".raise[F, Unit].void - } - - def applicativeErrorDirect[F[_]: Async](using Raise[F, String]): F[Unit] = { - Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].handleErrorWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attempt("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptNarrow[RuntimeException, Unit]("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptT("".raise[F, Unit]).value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recover("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].recoverWith("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].redeem("".raise[F, Unit])(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].onError("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].adaptError("".raise[F, Unit])(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].voidError("".raise[F, Unit]) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight("".raise[F, Unit], "") - Async[F].void("".raise[F, Unit]) - } - - def monadErrorDirect[F[_]: Async](using Raise[F, String]): F[Unit] = { - Async[F].ensure("".raise[F, Unit])(new Exception(""))(_ => false) - Async[F].ensureOr("".raise[F, Unit])(_ => new Exception(""))(_ => false) - Async[F].redeemWith("".raise[F, Unit])(_ => Async[F].unit, _ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].attemptTap("".raise[F, Unit])(_ => Async[F].unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // ensure other methods aren't affected - Async[F].tupleRight("".raise[F, Unit], "") - Async[F].void("".raise[F, Unit]) - } - def io(using Raise[IO, String]): IO[Unit] = { - // applicative error - "".raise[IO, Unit].handleError(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].handleErrorWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptNarrow[RuntimeException] // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptT.value // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].recover(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].recoverWith(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].redeem(_ => (), _ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].orElse(IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].adaptErr(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].orRaise(new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].voidError // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - // monad error - "".raise[IO, Unit].ensure(new Exception(""))(_ => false) - "".raise[IO, Unit].ensureOr(_ => new Exception(""))(_ => false) - "".raise[IO, Unit].redeemWith(_ => IO.unit, _ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].attemptTap(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - "".raise[IO, Unit].adaptError(_ => new Exception("")) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[IO, Unit].onError(_ => IO.unit) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[IO, String].reject(_ => new Exception("")) - // ensure other methods aren't affected - "".raise[IO, Unit].void "".raise[IO, Unit].debug() } From 3ce183b8fbadb1f828b3aaeaba57b3e3f7e140f1 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 7 Sep 2026 08:20:16 +0100 Subject: [PATCH 34/39] Rewrite README.md --- README.md | 79 ++++++++++++++++++------------------------------------- 1 file changed, 26 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 137cecc..dfd3bac 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,18 @@ Then you can add the *typelevel-scalafix* rules to your sbt project using the `s ```scala // To add all Scalafix rules -ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix" % "0.2.0" +ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix" % "0.6.0" // To add only cats Scalafix rules -ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats" % "0.2.0" +ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats" % "0.6.0" // To add only cats-effect Scalafix rules -ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats-effect" % "0.2.0" +ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats-effect" % "0.6.0" // To add only cats-mtl Scalafix rules ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-mtl" % "0.6.0" // To add only fs2 Scalafix rules -ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-fs2" % "0.2.0" +ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-fs2" % "0.6.0" // To add only http4s Scalafix rules -ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-http4s" % "0.2.0" +ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-http4s" % "0.6.0" ``` ## Usage @@ -178,52 +178,25 @@ This rule works on variable declarations, usaged within methods as well as for c ### TypelevelMTLSubmarine -See https://typelevel.org/blog/2025/09/02/custom-error-types.html. - -This rule reports supported error-handling methods (`handleError`, `recover`, `onError`, -etc.) on expressions it identifies as `cats.mtl.Raise[F, E]`-capable. `Raise` provided -by `Handle.allow` uses a traceless exception type called `cats.mtl.Handle.Submarine`, so -handling it through `ApplicativeError`, `MonadError`, or `IO` error-handling methods is -not always desirable. - -Detection is intentionally based on the declared `Raise` capability rather than the -specific instance selected at a call site. Consequently, the rule may also report code -where `Raise` is satisfied by an ordinary `ApplicativeError`-backed `Handle` which raises -the underlying error directly and does not use `Handle.Submarine`. This conservative -boundary keeps the rule consistent across methods and source modules, where instance -provenance is not reliably available. - -Inside `Handle.allow`, prefer `Handle.handle` or `Handle.handleWith`. The active -`Handle` associates each raised value with a private marker and recognizes only -`Handle.Submarine` failures carrying that same marker. Other throwables—including -submarines belonging to a different `Handle.allow` boundary—propagate unchanged, so -typed error handling does not become general throwable handling. - -The rule recognizes supported Cats and `IO` error-handling operations at their call -sites. It cannot reliably discover the same operations hidden inside arbitrary -user-defined wrappers. For example, a call such as `tolerate(raiseError)` is not reported -when `tolerate` calls `attempt` internally. Detecting that generally would require -unrestricted interprocedural analysis. - -Concrete member matching is limited to `IO`. Direct error-handling members on `SyncIO` -and other effects such as `fs2.Stream` are not recognized, and provenance is not traced -through constructors such as `Stream.eval`. Cats typeclass methods and extension syntax -remain supported when the protected expression's provenance is otherwise visible. Type -aliases of `Raise` and `Handle` are supported, but arbitrary user-defined subtypes are -not inferred as capabilities. - -For partial and narrow handlers, the rule warns only when the handler can match -`Handle.Submarine`, which extends `RuntimeException`. Wildcards and patterns typed as -`RuntimeException`, `Exception`, or `Throwable` are reported, while provably disjoint -concrete classes such as `IOException` are allowed. Unresolved and otherwise uncertain -patterns are reported conservatively. - -The supported behavior is consistent across Scala 2.13 and Scala 3 for constructs -available in both versions. Scala 3 additionally supports capabilities represented as -context-function methods or values, such as `Raise[F, E] ?=> F[A]`; Scala 2.13 has no -equivalent language representation. - -For example: +This rule reports Cats and `IO` error handlers used on expressions that require +`cats.mtl.Raise[F, E]`. When `Raise` comes from `Handle.allow`, raised values travel as +traceless `Handle.Submarine` exceptions. A general error handler can catch one before +`Handle` sees it. + +The rule covers Cats typeclass methods and syntax, plus concrete `IO` methods. It does +not follow user-defined wrappers or constructors such as `Stream.eval`, and it does not +match concrete `SyncIO` or `fs2.Stream` handlers. `Raise` and `Handle` aliases are +supported; user-defined subtypes are not. + +Partial and narrow handlers are reported when they can match `Handle.Submarine`, a +`RuntimeException`. This includes wildcards and the standard exception supertypes, but +not disjoint types such as `IOException`. Unknown patterns are reported. + +Scala 2.13 and Scala 3 are supported. Scala 3 also supports context-function values such +as `Raise[F, E] ?=> F[A]`. + +Example: + ```scala import cats.effect.IO import cats.mtl.{Raise, Handle} @@ -236,8 +209,8 @@ def standardError: IO[Unit] = Handle.allow[String] { for { - _ <- raiseError.onError(e => IO.println("Error: " + e)) // forbidden - _ <- standardError.onError(e => IO.println("Error: " + e)) // allowed + _ <- raiseError.onError(e => IO.println("Error: " + e)) // reported + _ <- standardError.onError(e => IO.println("Error: " + e)) // not reported } yield () } ``` From 2c08a5f6a8e783559429414e4dee5d5a7452c2b5 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 7 Sep 2026 08:33:12 +0100 Subject: [PATCH 35/39] Adjust formatting --- ...MTLSubmarineDirectProtectedArgumentTest.scala | 1 - .../fix/MTLSubmarineEffectPropagationTest.scala | 16 ++++++++-------- .../fix/MTLSubmarineHandleAllowFTest.scala | 2 +- .../fix/MTLSubmarineHandleRequirementTest.scala | 1 - .../fix/MTLSubmarineLocalMethodCaptureTest.scala | 1 - .../fix/MTLSubmarineLocalValueCaptureTest.scala | 1 - .../scala-2/fix/MTLSubmarineRaiseAliasTest.scala | 1 - .../fix/MTLSubmarineRaiseInstanceTest.scala | 1 - .../fix/MTLSubmarineRaiseSyntaxTest.scala | 2 +- .../fix/MTLSubmarineWrappedRaiseTest.scala | 1 - 10 files changed, 10 insertions(+), 17 deletions(-) diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineDirectProtectedArgumentTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineDirectProtectedArgumentTest.scala index fdbd818..9782b7a 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineDirectProtectedArgumentTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineDirectProtectedArgumentTest.scala @@ -6,7 +6,6 @@ package fix import cats.mtl._ import cats.effect._ - // scalafmt: { maxColumn = 160 } object DirectProtectedArgument { diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineEffectPropagationTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineEffectPropagationTest.scala index f8841a6..e2794d7 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineEffectPropagationTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineEffectPropagationTest.scala @@ -8,7 +8,6 @@ import cats.effect._ import cats.effect.implicits._ import cats.syntax.all._ - // scalafmt: { maxColumn = 160 } object EffectPropagation { @@ -31,14 +30,15 @@ object EffectPropagation { methodRaise[F].fproductLeft(identity).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling methodRaise[F].tupleLeft(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling methodRaise[F].tupleRight(()).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].as((_: Unit) => ()) + methodRaise[F] + .as((_: Unit) => ()) .<&>(Async[F].unit) - .attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling - Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + .attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + methodRaise[F].flatMap(_ => Async[F].unit).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatMap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.flatTap(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + (Async[F].unit >>= (_ => methodRaise[F])).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + Async[F].unit.mproduct(_ => methodRaise[F]).attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].unit.map(identity).attempt (for { _ <- Async[F].unit diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala index 1c4c6e7..52874cc 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala @@ -26,7 +26,7 @@ object HandleAllowF { def dangerousGeneric[F[_]: Async] = Handle.allowF[F, String] { implicit h => for { - _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- Async[F].recover(methodRaise[F]) { case _ => () } // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling _ <- methodRaise[F].ensure(new Exception(""))(_ => false) _ <- methodRaise[F].void diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleRequirementTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleRequirementTest.scala index deee4b3..091a636 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleRequirementTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleRequirementTest.scala @@ -7,7 +7,6 @@ import cats.mtl._ import cats.effect._ import cats.syntax.all._ - // scalafmt: { maxColumn = 160 } object HandleRequirement { diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalMethodCaptureTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalMethodCaptureTest.scala index 3e3ed3d..a11547e 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalMethodCaptureTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalMethodCaptureTest.scala @@ -6,7 +6,6 @@ package fix import cats.mtl._ import cats.effect._ - // scalafmt: { maxColumn = 160 } object LocalMethodCapture { diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalValueCaptureTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalValueCaptureTest.scala index b403965..5f8d7ad 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalValueCaptureTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineLocalValueCaptureTest.scala @@ -6,7 +6,6 @@ package fix import cats.mtl._ import cats.effect._ - // scalafmt: { maxColumn = 160 } object LocalValueCapture { diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseAliasTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseAliasTest.scala index a5f9bb0..ecafc60 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseAliasTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseAliasTest.scala @@ -7,7 +7,6 @@ import cats.mtl._ import cats.effect._ import cats.syntax.all._ - // scalafmt: { maxColumn = 160 } object RaiseAlias { diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseInstanceTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseInstanceTest.scala index d381c0f..e061eb5 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseInstanceTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseInstanceTest.scala @@ -7,7 +7,6 @@ import cats.mtl._ import cats.effect._ import cats.syntax.all._ - // scalafmt: { maxColumn = 160 } object RaiseInstance { diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala index 16ba253..24fe543 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineRaiseSyntaxTest.scala @@ -12,7 +12,7 @@ import cats.syntax.all._ object RaiseSyntax { def generic[F[_]: Async](implicit r: Raise[F, String]): F[Unit] = { - "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling + "".raise[F, Unit].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].handleError("".raise[F, Unit])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling "".raise[F, Unit].void } diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineWrappedRaiseTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineWrappedRaiseTest.scala index 0874628..bc6ee8c 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineWrappedRaiseTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineWrappedRaiseTest.scala @@ -7,7 +7,6 @@ import cats.mtl._ import cats.effect._ import cats.syntax.all._ - // scalafmt: { maxColumn = 160 } object WrappedRaise { From ec357d4cd662124fb39dea5049547d2ae16d6416 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 7 Sep 2026 11:16:51 +0100 Subject: [PATCH 36/39] Update cats-mtl to 1.7.0 --- build.sbt | 2 +- .../main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala | 7 +++++++ .../fix/MTLSubmarineHandleAllowContextFunctionTest.scala | 7 +++++++ project/plugins.sbt | 4 ++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 3608a1f..79cdca8 100644 --- a/build.sbt +++ b/build.sbt @@ -7,7 +7,7 @@ lazy val CatsVersion = "2.12.0" lazy val CatsEffectVersion = "3.6.3" lazy val Fs2Version = "3.12.2" lazy val Http4sVersion = "0.23.32" -lazy val MtlVersion = "1.6.0" +lazy val MtlVersion = "1.7.0" ThisBuild / startYear := Some(2022) ThisBuild / developers ++= List( diff --git a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala index 52874cc..15e829a 100644 --- a/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala +++ b/modules/mtl/input/src/main/scala-2/fix/MTLSubmarineHandleAllowFTest.scala @@ -24,6 +24,13 @@ object HandleAllowF { } yield () } + def attemptedGeneric[F[_]: Async]: F[Either[String, Unit]] = + Handle + .allowF[F, String] { implicit h => + methodRaise[F] + } + .attempt + def dangerousGeneric[F[_]: Async] = Handle.allowF[F, String] { implicit h => for { _ <- methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling diff --git a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala index 3a4871c..1aa7552 100644 --- a/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala +++ b/modules/mtl/input/src/main/scala-3/fix/MTLSubmarineHandleAllowContextFunctionTest.scala @@ -20,6 +20,13 @@ object HandleAllowContextFunction { method[F].attempt } + def attemptedGeneric[F[_]: Async]: F[Either[String, Unit]] = + Handle + .allow[String] { + methodRaise[F] + } + .attempt + def dangerousGeneric[F[_]: Async] = Handle.allow[String] { methodRaise[F].attempt // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling Async[F].handleError(methodRaise[F])(_ => ()) // assert: TypelevelMTLSubmarine.mtlSubmarineErrorHandling diff --git a/project/plugins.sbt b/project/plugins.sbt index 8ab61aa..dd60981 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,2 @@ -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.0") -addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.8.1") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.5") +addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.8.4") From c6d42262b02364ebb03f087a2525b9524cee5115 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 7 Sep 2026 11:20:08 +0100 Subject: [PATCH 37/39] Generate GitHub workflow --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7aab5d5..346846b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: timeout-minutes: 60 steps: - name: Checkout current branch (full) - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -82,7 +82,7 @@ jobs: - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }} path: targets.tar @@ -98,7 +98,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -119,7 +119,7 @@ jobs: run: sbt +update - name: Download target directories (2.13) - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: name: target-${{ matrix.os }}-${{ matrix.java }}-2.13 @@ -129,7 +129,7 @@ jobs: rm targets.tar - name: Download target directories (2.12) - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: name: target-${{ matrix.os }}-${{ matrix.java }}-2.12 @@ -172,7 +172,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -207,7 +207,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (fast) - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Java (temurin@11) id: setup-java-temurin-11 From b4522d7fc924ee7022e293eabe8aff6283943046 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 7 Sep 2026 12:00:17 +0100 Subject: [PATCH 38/39] Update sbt-typelevel to 0.8.7 --- .github/workflows/ci.yml | 10 +++++----- project/plugins.sbt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 346846b..185f767 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -203,19 +203,19 @@ jobs: strategy: matrix: os: [ubuntu-22.04] - java: [temurin@11] + java: [temurin@17] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (fast) uses: actions/checkout@v6 - - name: Setup Java (temurin@11) - id: setup-java-temurin-11 - if: matrix.java == 'temurin@11' + - name: Setup Java (temurin@17) + id: setup-java-temurin-17 + if: matrix.java == 'temurin@17' uses: actions/setup-java@v5 with: distribution: temurin - java-version: 11 + java-version: 17 - uses: coursier/setup-action@v1 with: diff --git a/project/plugins.sbt b/project/plugins.sbt index dd60981..dd1c887 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,2 @@ addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.5") -addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.8.4") +addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.8.7") From 4b4c1597f39b13e1de6b395e779b80768f1ec97c Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 7 Sep 2026 12:29:15 +0100 Subject: [PATCH 39/39] Fix UnusedIO compilation issue --- .../rules/src/main/scala/org/typelevel/fix/UnusedIO.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/cats-effect/rules/src/main/scala/org/typelevel/fix/UnusedIO.scala b/modules/cats-effect/rules/src/main/scala/org/typelevel/fix/UnusedIO.scala index e1624ce..57b90e4 100644 --- a/modules/cats-effect/rules/src/main/scala/org/typelevel/fix/UnusedIO.scala +++ b/modules/cats-effect/rules/src/main/scala/org/typelevel/fix/UnusedIO.scala @@ -51,17 +51,15 @@ class UnusedIO extends SemanticRule("TypelevelUnusedIO") { checkSignature(outer, ref) case Term.ApplyInfix.Initial(_, op, _, _) => checkSignature(outer, op) - case Term.Apply.Initial(fn @ Term.Name(_), _) if IOCompanionSym.matches(fn) => + case Term.Apply.After_4_6_0(fn @ Term.Name(_), _) if IOCompanionSym.matches(fn) => Patch.lint(UnusedIODiagnostic(outer)) - case Term.Apply.Initial(fn @ Term.Name(_), _) => + case Term.Apply.After_4_6_0(fn @ Term.Name(_), _) => checkSignature(outer, fn) case Term.ApplyUnary(fn @ Term.Name(_), _) => checkSignature(outer, fn) - case Term.ApplyUsing.Initial(fn @ Term.Name(_), _) => - checkSignature(outer, fn) case Term.Select(_, prop @ Term.Name(_)) => checkSignature(outer, prop) - case Term.Apply.Initial(Term.Select(_, method), _) => + case Term.Apply.After_4_6_0(Term.Select(_, method), _) => checkSignature(outer, method) case Term.Annotate(expr, _) => checkInner(expr)