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..7934301 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-handling methods is not always desirable. + +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..49d5986 --- /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 is not always desirable. " + + 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() +}