diff --git a/core/common/src/main/scala/org/typelevel/otel4s/KindTransformer.scala b/core/common/src/main/scala/org/typelevel/otel4s/KindTransformer.scala index e6c553e4b..889dd0992 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/KindTransformer.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/KindTransformer.scala @@ -18,6 +18,7 @@ package org.typelevel.otel4s import cats.Functor import cats.Monad +import cats.arrow.FunctionK import cats.data.EitherT import cats.data.IorT import cats.data.Kleisli @@ -54,14 +55,25 @@ trait KindTransformer[F[_], G[_]] { /** Lifts a natural transformation from `F` to `F` into a natural * transformation from `G` to `G`. + * + * @note + * Implementors SHOULD NOT override this method; the only reason it is not + * final is for optimization of the identity case. */ - final def liftFunctionK(f: F ~> F): G ~> G = + def liftFunctionK(f: F ~> F): G ~> G = new (G ~> G) { def apply[A](ga: G[A]): G[A] = limitedMapK(ga)(f) } } object KindTransformer { + implicit def id[F[_]]: KindTransformer[F, F] = + new KindTransformer[F, F] { + val liftK: F ~> F = FunctionK.id + def limitedMapK[A](ga: F[A])(f: F ~> F): F[A] = f(ga) + override def liftFunctionK(f: F ~> F): F ~> F = f + } + implicit def optionT[F[_]: Functor]: KindTransformer[F, OptionT[F, *]] = new KindTransformer[F, OptionT[F, *]] { val liftK: F ~> OptionT[F, *] = OptionT.liftK diff --git a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala index b08122320..5cd39afbc 100644 --- a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala +++ b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala @@ -938,7 +938,33 @@ class TracerSuite extends CatsEffectSuite { } } - test("nested SpanOps#surround for Tracer[OptionT[IO, *]]") { + test("nested SpanOps#surround for Tracer[IO].mapK[IO]") { + TestControl.executeEmbed { + for { + now <- IO.monotonic.delayBy(1.second) // otherwise returns 0 + sdk <- makeSdk() + tracerIO <- sdk.provider.get("tracer") + tracer = tracerIO.mapK[IO] + _ <- tracer + .span("outer") + .surround { + for { + _ <- IO.sleep(NestedSurround.preBodyDuration) + _ <- tracer + .span("body-1") + .surround(IO.sleep(NestedSurround.body1Duration)) + _ <- tracer + .span("body-2") + .surround(IO.sleep(NestedSurround.body2Duration)) + } yield () + } + spans <- sdk.finishedSpans + tree <- IO.pure(SpanNode.fromSpans(spans)) + } yield assertEquals(tree, List(NestedSurround.expected(now))) + } + } + + test("nested SpanOps#surround for Tracer[IO].mapK[OptionT[IO, *]]") { TestControl.executeEmbed { for { now <- IO.monotonic.delayBy(1.second) // otherwise returns 0 @@ -965,7 +991,7 @@ class TracerSuite extends CatsEffectSuite { } } - test("nested SpanOps#surround for Tracer[EitherT[IO, String, *]]") { + test("nested SpanOps#surround for Tracer[IO].mapK[EitherT[IO, String, *]]") { TestControl.executeEmbed { for { now <- IO.monotonic.delayBy(1.second) // otherwise returns 0 @@ -992,7 +1018,7 @@ class TracerSuite extends CatsEffectSuite { } } - test("nested SpanOps#surround for Tracer[IorT[IO, String, *]]") { + test("nested SpanOps#surround for Tracer[IO].mapK[IorT[IO, String, *]]") { TestControl.executeEmbed { for { now <- IO.monotonic.delayBy(1.second) // otherwise returns 0 @@ -1019,7 +1045,7 @@ class TracerSuite extends CatsEffectSuite { } } - test("nested SpanOps#surround for Tracer[Kleisli[IO, String, *]]") { + test("nested SpanOps#surround for Tracer[IO].mapK[Kleisli[IO, String, *]]") { TestControl.executeEmbed { for { now <- IO.monotonic.delayBy(1.second) // otherwise returns 0 @@ -1046,7 +1072,7 @@ class TracerSuite extends CatsEffectSuite { } } - test("nested SpanOps#surround for Tracer[StateT[IO, Int, *]]") { + test("nested SpanOps#surround for Tracer[IO].mapK[StateT[IO, Int, *]]") { TestControl.executeEmbed { for { now <- IO.monotonic.delayBy(1.second) // otherwise returns 0 @@ -1073,7 +1099,7 @@ class TracerSuite extends CatsEffectSuite { } } - test("nested SpanOps#surround for Tracer[Resource[IO, *]]") { + test("nested SpanOps#surround for Tracer[IO].mapK[Resource[IO, *]]") { TestControl.executeEmbed { for { now <- IO.monotonic.delayBy(1.second) // otherwise returns 0