From 6c3d93f7aea4d46b8c65073237f37c4616f22d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bia=C5=82y?= Date: Mon, 8 Apr 2024 21:23:56 +0200 Subject: [PATCH] add nice compile error for wrong usage of flatMap and for missing instance of Result.ToFuture --- .../main/scala/besom/internal/Output.scala | 10 ++++ .../main/scala/besom/internal/Result.scala | 8 +++ .../scala/besom/internal/OutputTest.scala | 50 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/core/src/main/scala/besom/internal/Output.scala b/core/src/main/scala/besom/internal/Output.scala index ffb77950..d6cec146 100644 --- a/core/src/main/scala/besom/internal/Output.scala +++ b/core/src/main/scala/besom/internal/Output.scala @@ -33,6 +33,16 @@ class Output[+A] private[internal] (using private[besom] val ctx: Context)( yield nested.flatten ) + /** Mock variant of flatMap that will fail at compile time if used with a function that returns a value instead of an Output. + * + * @param f + * function to apply to the value of the Output + */ + inline def flatMap[B](f: A => B): Nothing = scala.compiletime.error( + """Output#flatMap can only be used with functions that return an Output or a structure like scala.concurrent.Future, cats.effect.IO or zio.Task. +If you want to map over the value of an Output, use the map method instead.""" + ) + def zip[B](that: => Output[B])(using z: Zippable[A, B]): Output[z.Out] = Output.ofData(dataResult.zip(that.getData).map((a, b) => a.zip(b))) diff --git a/core/src/main/scala/besom/internal/Result.scala b/core/src/main/scala/besom/internal/Result.scala index 78017dd3..bc9d5a94 100644 --- a/core/src/main/scala/besom/internal/Result.scala +++ b/core/src/main/scala/besom/internal/Result.scala @@ -381,6 +381,14 @@ object Result: _ <- finalizersRef.update(_.updatedWith(scope)(finalizers => Some(release(a) :: finalizers.toList.flatten))) yield a + @implicitNotFound( + """Could not find a given ToFuture instance for type ${F}. + +Besom offers the following instances: + * besom-core provides a ToFuture instance for scala.concurrent.Future + * besom-zio provides a ToFuture instance for zio.Task + * besom-cats provides a ToFuture instance for cats.effect.IO""" + ) trait ToFuture[F[_]]: def eval[A](fa: => F[A]): () => Future[A] end Result diff --git a/core/src/test/scala/besom/internal/OutputTest.scala b/core/src/test/scala/besom/internal/OutputTest.scala index 8dca1794..6ab9daa8 100644 --- a/core/src/test/scala/besom/internal/OutputTest.scala +++ b/core/src/test/scala/besom/internal/OutputTest.scala @@ -226,6 +226,56 @@ class OutputTest extends munit.FunSuite: Context().waitForAllTasks.unsafeRunSync() } + test("Output#flatMap on non supported datatypes have nice and informative compile error") { + val shouldCompile = scala.compiletime.testing.typeCheckErrors( + """import besom.* + import besom.internal.{Output, DummyContext} + import besom.internal.RunOutput.{*, given} + given besom.internal.Context = DummyContext().unsafeRunSync() + + val out: Output[Int] = Output(1).flatMap(x => Output(x + 1))""" + ) + + assert(shouldCompile.isEmpty) + + val shouldNotCompile = scala.compiletime.testing.typeCheckErrors( + """import besom.* + import besom.internal.{Output, DummyContext} + import besom.internal.RunOutput.{*, given} + given besom.internal.Context = DummyContext().unsafeRunSync() + + val out: Output[Int] = Output(1).flatMap(x => Option(x + 1))""" + ) + + assert(shouldNotCompile.size == 1) + + val expected = """Could not find a given ToFuture instance for type Option. + | + |Besom offers the following instances: + | * besom-core provides a ToFuture instance for scala.concurrent.Future + | * besom-zio provides a ToFuture instance for zio.Task + | * besom-cats provides a ToFuture instance for cats.effect.IO""".stripMargin + + assertEquals(shouldNotCompile.head.message, expected) + + val shouldNotCompileFlatMapRawValue = scala.compiletime.testing.typeCheckErrors( + """import besom.* + import besom.internal.{Output, DummyContext} + import besom.internal.RunOutput.{*, given} + given besom.internal.Context = DummyContext().unsafeRunSync() + + val out: Output[Int] = Output(1).flatMap(_ => 1)""" + ) + + assert(shouldNotCompileFlatMapRawValue.size == 1) + + val expectedFlatMapRawValue = + """Output#flatMap can only be used with functions that return an Output or a structure like scala.concurrent.Future, cats.effect.IO or zio.Task. + |If you want to map over the value of an Output, use the map method instead.""".stripMargin + + assertEquals(shouldNotCompileFlatMapRawValue.head.message, expectedFlatMapRawValue) + } + Vector( (true, "value", Some("value")), (false, "value", None)