diff --git a/kyo-prelude/jvm/src/test/scala/kyo2/MonadLawsTest.scala b/kyo-prelude/jvm/src/test/scala/kyo2/MonadLawsTest.scala index a4d77ef58..9aee19b45 100644 --- a/kyo-prelude/jvm/src/test/scala/kyo2/MonadLawsTest.scala +++ b/kyo-prelude/jvm/src/test/scala/kyo2/MonadLawsTest.scala @@ -1,5 +1,6 @@ package kyo2 +import Flat.unsafe.bypass import kyo2.Result import zio.Trace import zio.prelude.Equal @@ -11,7 +12,7 @@ import zio.test.laws.* object MonadLawsTest extends ZIOSpecDefault: - case class Myo[+A](v: A < (Env[String] & Abort[String] & Emit[Int] & Var[Boolean])) + type Myo[+A] = A < (Env[String] & Abort[String] & Emit[Int] & Var[Boolean]) val listGenF: GenF[Any, Myo] = new GenF[Any, Myo]: @@ -48,17 +49,17 @@ object MonadLawsTest extends ZIOSpecDefault: _ <- Abort.when(s.length() > 10)("length exceeded") yield v ) - ).map(Myo(_)) + ) end apply given CovariantDeriveEqualIdentityFlatten[Myo] = new CovariantDeriveEqualIdentityFlatten[Myo]: override def flatten[A](ffa: Myo[Myo[A]]): Myo[A] = - Myo(ffa.v.flatMap(_.v)) + ffa.flatten override def any: Myo[Any] = - Myo(()) + () override def map[A, B](f: A => B): Myo[A] => Myo[B] = - m => Myo[B](m.v.map(f(_))) + _.map(f(_)) override def derive[A: Equal]: Equal[Myo[A]] = new Equal[Myo[A]]: protected def checkEqual(l: Myo[A], r: Myo[A]): Boolean = @@ -66,12 +67,15 @@ object MonadLawsTest extends ZIOSpecDefault: Var.run(true)( Emit.run( Abort.run( - Env.run("test")(m.v) + Env.run("test")(m) ) ) ).eval._2 - - run(l).equals(run(r)) + (run(l), run(r)) match + case (Result.Success(l), Result.Success(r)) => summon[Equal[A]].equal(l, r) + case (Result.Fail(l), Result.Fail(r)) => l == r + case _ => false + end match end checkEqual def spec = suite("MonadLawsTest")( diff --git a/kyo-prelude/jvm/src/test/scala/kyo2/kernel/BytecodeTest.scala b/kyo-prelude/jvm/src/test/scala/kyo2/kernel/BytecodeTest.scala index fd551e014..ad8632d79 100644 --- a/kyo-prelude/jvm/src/test/scala/kyo2/kernel/BytecodeTest.scala +++ b/kyo-prelude/jvm/src/test/scala/kyo2/kernel/BytecodeTest.scala @@ -22,22 +22,22 @@ class BytecodeTest extends Test: "suspend" in { val map = methodBytecodeSize[TestSuspend] - assert(map == Map("test" -> 20)) + assert(map == Map("test" -> 16)) } "suspendMap" in { val map = methodBytecodeSize[TestSuspendMap] - assert(map == Map("test" -> 20)) + assert(map == Map("test" -> 16)) } "map" in { val map = methodBytecodeSize[TestMap] - assert(map == Map("test" -> 24, "anonfun" -> 14, "mapLoop" -> 161)) + assert(map == Map("test" -> 22, "anonfun" -> 10, "mapLoop" -> 138)) } "handle" in { val map = methodBytecodeSize[TestHandle] - assert(map == Map("test" -> 30, "anonfun" -> 15, "handleLoop" -> 267)) + assert(map == Map("test" -> 20, "anonfun" -> 8, "handleLoop" -> 240)) } def methodBytecodeSize[A](using ct: ClassTag[A]): Map[String, Int] = diff --git a/kyo-prelude/jvm/src/test/scala/kyo2/kernel/SafepointTest.scala b/kyo-prelude/jvm/src/test/scala/kyo2/kernel/SafepointTest.scala index f244696b0..bdc7bec8a 100644 --- a/kyo-prelude/jvm/src/test/scala/kyo2/kernel/SafepointTest.scala +++ b/kyo-prelude/jvm/src/test/scala/kyo2/kernel/SafepointTest.scala @@ -10,7 +10,7 @@ import scala.concurrent.Future class SafepointTest extends Test: - def fork[A](f: => A < Any): A < Any = + def fork[A: Flat](f: => A < Any): A < Any = Effect.defer { val future = Future(f.eval) Await.result(future, timeout.toScala): A diff --git a/kyo-prelude/shared/src/main/scala/kyo2/Abort.scala b/kyo-prelude/shared/src/main/scala/kyo2/Abort.scala index 36612374e..969dd7bfb 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/Abort.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/Abort.scala @@ -1,6 +1,5 @@ package kyo2 -import kernel.< import kernel.Const import kernel.Effect import kernel.Frame @@ -58,7 +57,7 @@ object Abort: inline def get[E >: Nothing]: GetOps[E] = GetOps(()) final class RunOps[E >: Nothing](dummy: Unit) extends AnyVal: - def apply[A, S, ES, ER](v: => A < (Abort[E | ER] & S))( + def apply[A: Flat, S, ES, ER](v: => A < (Abort[E | ER] & S))( using ct: ClassTag[E], tag: Tag[E], // TODO Used only to ensure E isn't a type union. There should be a more lightweight solution for this. diff --git a/kyo-prelude/shared/src/main/scala/kyo2/Emit.scala b/kyo-prelude/shared/src/main/scala/kyo2/Emit.scala index bf3b27a40..d8a30a710 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/Emit.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/Emit.scala @@ -10,6 +10,7 @@ object Emit: opaque type Ack = Int object Ack: given CanEqual[Ack, Ack] = CanEqual.derived + inline given Flat[Ack] = Flat.unsafe.bypass extension (ack: Ack) def maxItems(n: Int): Ack = @@ -40,7 +41,7 @@ object Emit: Effect.suspendMap[Any](tag, value)(f(_)) final class RunOps[V](dummy: Unit) extends AnyVal: - def apply[A, S](v: A < (Emit[V] & S))(using tag: Tag[Emit[V]], frame: Frame): (Chunk[V], A) < S = + def apply[A: Flat, S](v: A < (Emit[V] & S))(using tag: Tag[Emit[V]], frame: Frame): (Chunk[V], A) < S = Effect.handle.state(tag, Chunk.empty[V], v)( handle = [C] => (input, state, cont) => (state.append(input), cont(Ack.Continue())), done = (state, res) => (state, res) @@ -50,7 +51,7 @@ object Emit: inline def run[V >: Nothing]: RunOps[V] = RunOps(()) final class RunFoldOps[V](dummy: Unit) extends AnyVal: - def apply[A, S, B, S2](acc: A)(f: (A, V) => A < S)(v: B < (Emit[V] & S2))( + def apply[A, S, B: Flat, S2](acc: A)(f: (A, V) => A < S)(v: B < (Emit[V] & S2))( using tag: Tag[Emit[V]], frame: Frame @@ -66,7 +67,7 @@ object Emit: inline def runFold[V >: Nothing]: RunFoldOps[V] = RunFoldOps(()) final class RunDiscardOps[V](dummy: Unit) extends AnyVal: - def apply[A, S](v: A < (Emit[V] & S))(using tag: Tag[Emit[V]], frame: Frame): A < S = + def apply[A: Flat, S](v: A < (Emit[V] & S))(using tag: Tag[Emit[V]], frame: Frame): A < S = Effect.handle(tag, v)( handle = [C] => (input, cont) => cont(Ack.Stop) ) @@ -75,7 +76,7 @@ object Emit: inline def runDiscard[V >: Nothing]: RunDiscardOps[V] = RunDiscardOps(()) final class RunAckOps[V](dummy: Unit) extends AnyVal: - def apply[A, S, S2](v: A < (Emit[V] & S))(f: V => Ack < S2)(using tag: Tag[Emit[V]], frame: Frame): A < (S & S2) = + def apply[A: Flat, S, S2](v: A < (Emit[V] & S))(f: V => Ack < S2)(using tag: Tag[Emit[V]], frame: Frame): A < (S & S2) = Effect.handle(tag, v)( [C] => (input, cont) => f(input).map(cont) ) diff --git a/kyo-prelude/shared/src/main/scala/kyo2/Env.scala b/kyo-prelude/shared/src/main/scala/kyo2/Env.scala index c42cdf473..b33356245 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/Env.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/Env.scala @@ -15,14 +15,14 @@ object Env: inline def get[R](using inline tag: Tag[R])(using inline frame: Frame): R < Env[R] = use[R](identity) - def run[R >: Nothing: Tag, A, S, VR](env: R)(v: A < (Env[R & VR] & S))( + def run[R >: Nothing: Tag, A: Flat, S, VR](env: R)(v: A < (Env[R & VR] & S))( using reduce: Reducible[Env[VR]], frame: Frame ): A < (S & reduce.SReduced) = runTypeMap(TypeMap(env))(v) - def runTypeMap[R >: Nothing, A, S, VR](env: TypeMap[R])(v: A < (Env[R & VR] & S))( + def runTypeMap[R >: Nothing, A: Flat, S, VR](env: TypeMap[R])(v: A < (Env[R & VR] & S))( using reduce: Reducible[Env[VR]], frame: Frame diff --git a/kyo-prelude/shared/src/main/scala/kyo2/Kyo.scala b/kyo-prelude/shared/src/main/scala/kyo2/Kyo.scala index 885d8df17..e2f68afc9 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/Kyo.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/Kyo.scala @@ -1,6 +1,5 @@ package kyo2 -import kernel.< import kernel.Frame import kernel.Loop import scala.annotation.tailrec diff --git a/kyo-prelude/shared/src/main/scala/kyo2/Maybe.scala b/kyo-prelude/shared/src/main/scala/kyo2/Maybe.scala index 16df49fd0..f7f88bd1f 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/Maybe.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/Maybe.scala @@ -8,6 +8,7 @@ opaque type Maybe[+A] >: (Empty | Defined[A]) = Empty | Defined[A] object Maybe: inline given [A, B](using inline ce: CanEqual[A, B]): CanEqual[Maybe[A], Maybe[B]] = CanEqual.derived given [A]: Conversion[Maybe[A], IterableOnce[A]] = _.iterator + inline given [A]: Flat[Maybe[A]] = Flat.unsafe.bypass def apply[A](v: A): Maybe[A] = if isNull(v) then Empty diff --git a/kyo-prelude/shared/src/main/scala/kyo2/Memo.scala b/kyo-prelude/shared/src/main/scala/kyo2/Memo.scala index c28456a20..5dab34eaa 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/Memo.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/Memo.scala @@ -39,7 +39,7 @@ object Memo: } end apply - def run[A, S](v: A < (Memo & S)): A < S = + def run[A: Flat, S](v: A < (Memo & S)): A < S = Var.run(empty)(v) end Memo diff --git a/kyo-prelude/shared/src/main/scala/kyo2/Result.scala b/kyo-prelude/shared/src/main/scala/kyo2/Result.scala index 139feee2b..6d98877d9 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/Result.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/Result.scala @@ -13,6 +13,7 @@ object Result: inline given [E, A](using inline ce: CanEqual[A, A]): CanEqual[Result[E, A], Result[E, A]] = CanEqual.derived given [E, A]: CanEqual[Result[E, A], Panic] = CanEqual.derived + inline given [E, A]: Flat[Result[E, A]] = Flat.unsafe.bypass inline def apply[A](expr: => A): Result[Nothing, A] = try diff --git a/kyo-prelude/shared/src/main/scala/kyo2/TypeMap.scala b/kyo-prelude/shared/src/main/scala/kyo2/TypeMap.scala index f02ba3e53..d49ebfc11 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/TypeMap.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/TypeMap.scala @@ -7,6 +7,8 @@ import scala.collection.immutable.HashMap opaque type TypeMap[+A] = HashMap[Tag[Any], Any] object TypeMap: + inline given [A]: Flat[TypeMap[A]] = Flat.unsafe.bypass + extension [A](self: TypeMap[A]) private inline def fatal[B](using t: Tag[B]): Nothing = diff --git a/kyo-prelude/shared/src/main/scala/kyo2/Var.scala b/kyo-prelude/shared/src/main/scala/kyo2/Var.scala index 58b53dd96..abc94b860 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/Var.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/Var.scala @@ -40,7 +40,7 @@ object Var: inline def updateDiscard[V](inline f: V => V)(using inline tag: Tag[Var[V]], inline frame: Frame): Unit < Var[V] = Effect.suspendMap[Unit](tag, (v => f(v)): Update[V])(_ => ()) - private inline def runWith[V, A, S, B, S2](state: V)(v: A < (Var[V] & S))( + private inline def runWith[V, A: Flat, S, B, S2](state: V)(v: A < (Var[V] & S))( inline f: (V, A) => B < S2 )(using inline tag: Tag[Var[V]], inline frame: Frame): B < (S & S2) = Effect.handle.state(tag, state, v)( @@ -58,11 +58,11 @@ object Var: ) /** Handles the effect and discards the 'Var' state. */ - def run[V, A, S](state: V)(v: A < (Var[V] & S))(using Tag[Var[V]], Frame): A < S = + def run[V, A: Flat, S](state: V)(v: A < (Var[V] & S))(using Tag[Var[V]], Frame): A < S = runWith(state)(v)((_, result) => result) /** Handles the effect and returns a tuple with the final `Var` state and the computation's result. */ - def runTuple[V, A, S](state: V)(v: A < (Var[V] & S))(using Tag[Var[V]], Frame): (V, A) < S = + def runTuple[V, A: Flat, S](state: V)(v: A < (Var[V] & S))(using Tag[Var[V]], Frame): (V, A) < S = runWith(state)(v)((state, result) => (state, result)) object internal: diff --git a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Context.scala b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Context.scala index a5ad334ac..198d15a80 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Context.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Context.scala @@ -6,6 +6,8 @@ import kyo2.bug opaque type Context = Map[Tag[Any], AnyRef] object Context: + inline given Flat[Context] = Flat.unsafe.bypass + val empty: Context = Map.empty extension (context: Context) diff --git a/kyo-prelude/shared/src/main/scala/kyo2/kernel/ContextEffect.scala b/kyo-prelude/shared/src/main/scala/kyo2/kernel/ContextEffect.scala index 32b084a91..931b88a5f 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/kernel/ContextEffect.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/kernel/ContextEffect.scala @@ -44,11 +44,13 @@ object ContextEffect: inline ifUndefined: A, inline ifDefined: A => A )(v: B < (E & S))( - using inline _frame: Frame + using + inline _frame: Frame, + inline flat: Flat[A] ): B < S = def handleLoop(v: B < (E & S))(using Safepoint): B < S = v match - case <(kyo: KyoSuspend[IX, OX, EX, Any, B, S] @unchecked) => + case kyo: KyoSuspend[IX, OX, EX, Any, B, S] @unchecked => new KyoContinue[IX, OX, EX, Any, B, S](kyo): def frame = _frame def apply(v: OX[Any], context: Context)(using Safepoint) = @@ -58,7 +60,7 @@ object ContextEffect: else context.set(tag, ifDefined(context.get(tag))) handleLoop(kyo(v, updated)) end apply - case <(kyo) => + case kyo => kyo.asInstanceOf[B] handleLoop(v) end handle diff --git a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Effect.scala b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Effect.scala index ae651a33f..98add081c 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Effect.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Effect.scala @@ -59,22 +59,22 @@ object Effect: inline handle: Safepoint ?=> [C] => (I[C], O[C] => A < (E & S & S2)) => A < (E & S & S2), inline done: A => B < S3 = (v: A) => v, inline accept: [C] => I[C] => Boolean = [C] => (v: I[C]) => true - )(using inline _frame: Frame, safepoint: Safepoint): B < (S & S2 & S3) = + )(using inline _frame: Frame, inline flat: Flat[A], safepoint: Safepoint): B < (S & S2 & S3) = def handleLoop(v: A < (E & S & S2 & S3), context: Context)(using Safepoint): B < (S & S2 & S3) = v match - case <(kyo: KyoSuspend[I, O, E, Any, A, E & S & S2] @unchecked) if tag =:= kyo.tag && accept(kyo.input) => + case kyo: KyoSuspend[I, O, E, Any, A, E & S & S2] @unchecked if tag =:= kyo.tag && accept(kyo.input) => Safepoint.handle(kyo.input)( eval = handle[Any](kyo.input, kyo(_, context)), continue = handleLoop(_, context), suspend = handleLoop(kyo, context) ) - case <(kyo: KyoSuspend[IX, OX, EX, Any, A, E & S & S2 & S3] @unchecked) => + case kyo: KyoSuspend[IX, OX, EX, Any, A, E & S & S2 & S3] @unchecked => new KyoContinue[IX, OX, EX, Any, B, S & S2 & S3](kyo): def frame = _frame def apply(v: OX[Any], context: Context)(using Safepoint) = handleLoop(kyo(v, context), context) end new - case <(kyo) => + case kyo => done(kyo.asInstanceOf[A]) end match end handleLoop @@ -88,28 +88,28 @@ object Effect: )( inline handle1: Safepoint ?=> [C] => (I1[C], O1[C] => A < (E1 & E2 & S & S2)) => A < (E1 & E2 & S & S2), inline handle2: Safepoint ?=> [C] => (I2[C], O2[C] => A < (E1 & E2 & S & S2)) => A < (E1 & E2 & S & S2) - )(using inline _frame: Frame, safepoint: Safepoint): A < (S & S2) = + )(using inline _frame: Frame, inline flat: Flat[A], safepoint: Safepoint): A < (S & S2) = def handle2Loop(kyo: A < (E1 & E2 & S & S2), context: Context)(using Safepoint): A < (S & S2) = kyo match - case <(kyo: KyoSuspend[I1, O1, E1, Any, A, E1 & E2 & S & S2] @unchecked) if tag1 =:= kyo.tag => + case kyo: KyoSuspend[I1, O1, E1, Any, A, E1 & E2 & S & S2] @unchecked if tag1 =:= kyo.tag => Safepoint.handle(kyo.input)( eval = handle1[Any](kyo.input, kyo(_, context)), suspend = handle2Loop(kyo, context), continue = handle2Loop(_, context) ) - case <(kyo: KyoSuspend[I2, O2, E2, Any, A, E1 & E2 & S & S2] @unchecked) if tag2 =:= kyo.tag => + case kyo: KyoSuspend[I2, O2, E2, Any, A, E1 & E2 & S & S2] @unchecked if tag2 =:= kyo.tag => Safepoint.handle(kyo.input)( eval = handle2[Any](kyo.input, kyo(_, context)), suspend = handle2Loop(kyo, context), continue = handle2Loop(_, context) ) - case <(kyo: KyoSuspend[IX, OX, EX, Any, A, E1 & E2 & S & S2] @unchecked) => + case kyo: KyoSuspend[IX, OX, EX, Any, A, E1 & E2 & S & S2] @unchecked => new KyoContinue[IX, OX, EX, Any, A, S & S2](kyo): def frame = _frame def apply(v: OX[Any], context: Context)(using Safepoint) = handle2Loop(kyo(v, context), context) end new - case <(kyo) => + case kyo => kyo.asInstanceOf[A] end match end handle2Loop @@ -128,34 +128,34 @@ object Effect: inline handle1: Safepoint ?=> [C] => (I1[C], O1[C] => A < (E1 & E2 & E3 & S & S2)) => A < (E1 & E2 & E3 & S & S2), inline handle2: Safepoint ?=> [C] => (I2[C], O2[C] => A < (E1 & E2 & E3 & S & S2)) => A < (E1 & E2 & E3 & S & S2), inline handle3: Safepoint ?=> [C] => (I3[C], O3[C] => A < (E1 & E2 & E3 & S & S2)) => A < (E1 & E2 & E3 & S & S2) - )(using inline _frame: Frame, safepoint: Safepoint): A < (S & S2) = + )(using inline _frame: Frame, inline flat: Flat[A], safepoint: Safepoint): A < (S & S2) = def handle3Loop(v: A < (E1 & E2 & E3 & S & S2), context: Context)(using Safepoint): A < (S & S2) = v match - case <(kyo: KyoSuspend[I1, O1, E1, Any, A, E1 & E2 & E3 & S & S2] @unchecked) if tag1 =:= kyo.tag => + case kyo: KyoSuspend[I1, O1, E1, Any, A, E1 & E2 & E3 & S & S2] @unchecked if tag1 =:= kyo.tag => Safepoint.handle(kyo.input)( eval = handle1[Any](kyo.input, kyo(_, context)), suspend = handle3Loop(kyo, context), continue = handle3Loop(_, context) ) - case <(kyo: KyoSuspend[I2, O2, E2, Any, A, E1 & E2 & E3 & S & S2] @unchecked) if tag2 =:= kyo.tag => + case kyo: KyoSuspend[I2, O2, E2, Any, A, E1 & E2 & E3 & S & S2] @unchecked if tag2 =:= kyo.tag => Safepoint.handle(kyo.input)( eval = handle2[Any](kyo.input, kyo(_, context)), suspend = handle3Loop(kyo, context), continue = handle3Loop(_, context) ) - case <(kyo: KyoSuspend[I3, O3, E3, Any, A, E1 & E2 & E3 & S & S2] @unchecked) if tag3 =:= kyo.tag => + case kyo: KyoSuspend[I3, O3, E3, Any, A, E1 & E2 & E3 & S & S2] @unchecked if tag3 =:= kyo.tag => Safepoint.handle(kyo.input)( eval = handle3[Any](kyo.input, kyo(_, context)), suspend = handle3Loop(kyo, context), continue = handle3Loop(_, context) ) - case <(kyo: KyoSuspend[IX, OX, EX, Any, A, E1 & E2 & E3 & S & S2] @unchecked) => + case kyo: KyoSuspend[IX, OX, EX, Any, A, E1 & E2 & E3 & S & S2] @unchecked => new KyoContinue[IX, OX, EX, Any, A, S & S2](kyo): def frame = _frame def apply(v: OX[Any], context: Context)(using Safepoint) = handle3Loop(kyo(v, context), context) end new - case <(kyo) => + case kyo => kyo.asInstanceOf[A] end match end handle3Loop @@ -176,12 +176,12 @@ object Effect: inline handle1: Safepoint ?=> [C] => (I1[C], O1[C] => A < (E1 & E2 & E3 & S & S2)) => A < (E1 & E2 & E3 & S & S2), inline handle2: Safepoint ?=> [C] => (I2[C], O2[C] => A < (E1 & E2 & E3 & S & S2)) => A < (E1 & E2 & E3 & S & S2), inline handle3: Safepoint ?=> [C] => (I3[C], O3[C] => A < (E1 & E2 & E3 & S & S2)) => A < (E1 & E2 & E3 & S & S2) - )(using inline _frame: Frame, safepoint: Safepoint): A < (E1 & E2 & E3 & S & S2) = + )(using inline _frame: Frame, inline flat: Flat[A], safepoint: Safepoint): A < (E1 & E2 & E3 & S & S2) = def partialLoop(v: A < (E1 & E2 & E3 & S & S2), context: Context)(using safepoint: Safepoint): A < (E1 & E2 & E3 & S & S2) = if stop then v else v match - case <(kyo: KyoSuspend[?, ?, ?, ?, ?, ?]) => + case kyo: KyoSuspend[?, ?, ?, ?, ?, ?] => type Suspend[I[_], O[_], E <: Effect[I, O]] = KyoSuspend[I, O, E, Any, A, E1 & E2 & E3 & S & S2] if kyo.tag =:= Tag[Defer] then val k = kyo.asInstanceOf[Suspend[Const[Unit], Const[Unit], Defer]] @@ -216,21 +216,21 @@ object Effect: inline handle: Safepoint ?=> [C] => (I[C], State, O[C] => A < (E & S & S2)) => (State, A < (E & S & S2)) < S3, inline done: (State, A) => B < (S & S2 & S3) = (_: State, v: A) => v, inline accept: [C] => I[C] => Boolean = [C] => (v: I[C]) => true - )(using inline _frame: Frame, safepoint: Safepoint): B < (S & S2 & S3) = + )(using inline _frame: Frame, inline flat: Flat[A], safepoint: Safepoint): B < (S & S2 & S3) = def handleLoop(state: State, v: A < (E & S & S2 & S3), context: Context)(using Safepoint): B < (S & S2 & S3) = v match - case <(kyo: KyoSuspend[I, O, E, Any, A, E & S & S2] @unchecked) if tag =:= kyo.tag && accept(kyo.input) => + case kyo: KyoSuspend[I, O, E, Any, A, E & S & S2] @unchecked if tag =:= kyo.tag && accept(kyo.input) => Safepoint.handle(kyo.input)( suspend = handleLoop(state, kyo, context), continue = handle(kyo.input, state, kyo(_, context)).map(handleLoop(_, _, context)) ) - case <(kyo: KyoSuspend[IX, OX, EX, Any, A, E & S & S2] @unchecked) => + case kyo: KyoSuspend[IX, OX, EX, Any, A, E & S & S2] @unchecked => new KyoContinue[IX, OX, EX, Any, B, S & S2 & S3](kyo): def frame = _frame def apply(v: OX[Any], context: Context)(using Safepoint) = handleLoop(state, kyo(v, context), context) end new - case <(kyo) => + case kyo => done(state, kyo.asInstanceOf[A]) end match end handleLoop @@ -245,16 +245,16 @@ object Effect: inline done: A => B < S3 = (v: A) => v, inline accept: [C] => I[C] => Boolean = [C] => (v: I[C]) => true, inline recover: Throwable => B < (S & S2 & S3) - )(using inline _frame: Frame, safepoint: Safepoint): B < (S & S2 & S3) = + )(using inline _frame: Frame, inline flat: Flat[A], safepoint: Safepoint): B < (S & S2 & S3) = def handleLoop(v: A < (E & S & S2 & S3), context: Context)(using Safepoint): B < (S & S2 & S3) = v match - case <(kyo: KyoSuspend[I, O, E, Any, A, E & S & S2] @unchecked) if tag =:= kyo.tag && accept(kyo.input) => + case kyo: KyoSuspend[I, O, E, Any, A, E & S & S2] @unchecked if tag =:= kyo.tag && accept(kyo.input) => Safepoint.handle(kyo.input)( eval = handle[Any](kyo.input, kyo(_, context)), continue = handleLoop(_, context), suspend = handleLoop(kyo, context) ) - case <(kyo: KyoSuspend[IX, OX, EX, Any, A, E & S & S2 & S3] @unchecked) => + case kyo: KyoSuspend[IX, OX, EX, Any, A, E & S & S2 & S3] @unchecked => new KyoContinue[IX, OX, EX, Any, B, S & S2 & S3](kyo): def frame = _frame def apply(v: OX[Any], context: Context)(using Safepoint) = @@ -265,7 +265,7 @@ object Effect: recover(ex) end apply end new - case <(kyo) => + case kyo => done(kyo.asInstanceOf[A]) end match end handleLoop @@ -285,7 +285,7 @@ object Effect: )(using inline _frame: Frame, safepoint: Safepoint): B < (S & S2) = def catchingLoop(v: B < (S & S2))(using Safepoint): B < (S & S2) = (v: @unchecked) match - case <(kyo: KyoSuspend[IX, OX, EX, Any, B, S & S2] @unchecked) => + case kyo: KyoSuspend[IX, OX, EX, Any, B, S & S2] @unchecked => new KyoContinue[IX, OX, EX, Any, B, S & S2](kyo): def frame = _frame def apply(v: OX[Any], context: Context)(using Safepoint) = diff --git a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Flat.scala b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Flat.scala new file mode 100644 index 000000000..5bf8b3802 --- /dev/null +++ b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Flat.scala @@ -0,0 +1,88 @@ +package kyo2.kernel + +import kyo.Tag +import scala.quoted.* + +opaque type Flat[T] = Null + +object Flat: + object unsafe: + inline given bypass[T]: Flat[T] = null + end unsafe + + inline given infer[T]: Flat[T] = FlatMacro.infer +end Flat + +private object FlatMacro: + + inline def infer[T]: Flat[T] = ${ macroImpl[T] } + + def macroImpl[T: Type](using Quotes): Expr[Flat[T]] = + import quotes.reflect.* + + val t = TypeRepr.of[T].dealias + + object Kyo: + def unapply(tpe: TypeRepr): Option[(TypeRepr, TypeRepr)] = + tpe match + case AppliedType(_, List(t, u)) + if (tpe.typeSymbol eq TypeRepr.of[<].typeSymbol) => + Some((t.dealias, u.dealias)) + case _ => None + end Kyo + + def code(str: String) = + s"${scala.Console.YELLOW}'$str'${scala.Console.RESET}" + + def print(t: TypeRepr): String = + t match + case Kyo(t, s) => + s"${print(t)} < ${print(s)}" + case _ => t.show + + def fail(msg: String) = + report.errorAndAbort(s"Method doesn't accept nested Kyo computations.\n$msg") + + def isAny(t: TypeRepr) = + t.typeSymbol eq TypeRepr.of[Any].typeSymbol + + def isConcrete(t: TypeRepr) = + t.typeSymbol.isClassDef + + def hasTag(t: TypeRepr): Boolean = + t.asType match + case '[t] => + Expr.summon[Tag.Full[t]].isDefined + + def check(t: TypeRepr): Unit = + t match + case OrType(a, b) => + check(a) + check(b) + case AndType(a, b) => + check(a) + check(b) + case _ => + if isAny(t) || (!isConcrete(t.dealias) && !hasTag(t)) then + fail( + s"Cannot prove ${code(print(t))} isn't nested. " + + s"This error can be reported an unsupported pending effect is passed to a method. " + + s"If that's not the case, provide an implicit evidence ${code(s"kyo.Flat[${print(t)}]")}." + ) + + t match + case Kyo(Kyo(nt, s1), s2) => + val mismatch = + if print(s1) != print(s2) then + s"\nPossible pending effects mismatch: Expected ${code(print(s2))}, found ${code(print(s1))}." + else + "" + fail( + s"Detected: ${code(print(t))}. Consider using ${code("flatten")} to resolve. " + mismatch + ) + case t => + check(t) + end match + '{ Flat.unsafe.bypass[T] } + end macroImpl +end FlatMacro diff --git a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Loop.scala b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Loop.scala index d9cbdcf1f..e58680637 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Loop.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Loop.scala @@ -72,16 +72,16 @@ object Loop: def _loop(i1: A): O < S = loop(i1) @tailrec def loop(i1: A): O < S = run(i1) match - case <(next: Continue[A] @unchecked) => + case next: Continue[A] @unchecked => loop(next._1) - case kyo @ <(_: Kyo[O | Continue[A], S] @unchecked) => + case kyo: Kyo[O | Continue[A], S] @unchecked => kyo.map { case next: Continue[A] @unchecked => _loop(next._1) case res => res.asInstanceOf[O] } - case <(res) => + case res => res.asInstanceOf[O] loop(input) end apply @@ -90,16 +90,16 @@ object Loop: def _loop(i1: A, i2: B): O < S = loop(i1, i2) @tailrec def loop(i1: A, i2: B): O < S = run(i1, i2) match - case <(next: Continue2[A, B] @unchecked) => + case next: Continue2[A, B] @unchecked => loop(next._1, next._2) - case kyo @ <(_: Kyo[o | Continue2[A, B], S] @unchecked) => + case kyo: Kyo[o | Continue2[A, B], S] @unchecked => kyo.map { case next: Continue2[A, B] @unchecked => _loop(next._1, next._2) case res => res.asInstanceOf[O] } - case <(res) => + case res => res.asInstanceOf[O] loop(input1, input2) end apply @@ -110,16 +110,16 @@ object Loop: def _loop(i1: A, i2: B, i3: C): O < S = loop(i1, i2, i3) @tailrec def loop(i1: A, i2: B, i3: C): O < S = run(i1, i2, i3) match - case <(next: Continue3[A, B, C] @unchecked) => + case next: Continue3[A, B, C] @unchecked => loop(next._1, next._2, next._3) - case kyo @ <(_: Kyo[O | Continue3[A, B, C], S] @unchecked) => + case kyo: Kyo[O | Continue3[A, B, C], S] @unchecked => kyo.map { case next: Continue3[A, B, C] @unchecked => _loop(next._1, next._2, next._3) case res => res.asInstanceOf[O] } - case <(res) => + case res => res.asInstanceOf[O] loop(input1, input2, input3) end apply @@ -130,16 +130,16 @@ object Loop: def _loop(i1: A, i2: B, i3: C, i4: D): O < S = loop(i1, i2, i3, i4) @tailrec def loop(i1: A, i2: B, i3: C, i4: D): O < S = run(i1, i2, i3, i4) match - case <(next: Continue4[A, B, C, D] @unchecked) => + case next: Continue4[A, B, C, D] @unchecked => loop(next._1, next._2, next._3, next._4) - case kyo @ <(_: Kyo[O | Continue4[A, B, C, D], S] @unchecked) => + case kyo: Kyo[O | Continue4[A, B, C, D], S] @unchecked => kyo.map { case next: Continue4[A, B, C, D] @unchecked => _loop(next._1, next._2, next._3, next._4) case res => res.asInstanceOf[O] } - case <(res) => + case res => res.asInstanceOf[O] loop(input1, input2, input3, input4) end apply @@ -148,16 +148,16 @@ object Loop: def _loop(idx: Int): O < S = loop(idx) @tailrec def loop(idx: Int): O < S = run(idx) match - case <(next: Continue[Unit] @unchecked) => + case next: Continue[Unit] @unchecked => loop(idx + 1) - case kyo @ <(_: Kyo[O | Continue[Unit], S] @unchecked) => + case kyo: Kyo[O | Continue[Unit], S] @unchecked => kyo.map { case next: Continue[Unit] @unchecked => _loop(idx + 1) case res => res.asInstanceOf[O] } - case <(res) => + case res => res.asInstanceOf[O] loop(0) end indexed @@ -166,16 +166,16 @@ object Loop: def _loop(idx: Int, i1: A): O < S = loop(idx, i1) @tailrec def loop(idx: Int, i1: A): O < S = run(idx, i1) match - case <(next: Continue[A] @unchecked) => + case next: Continue[A] @unchecked => loop(idx + 1, next._1) - case kyo @ <(_: Kyo[O | Continue[A], S] @unchecked) => + case kyo: Kyo[O | Continue[A], S] @unchecked => kyo.map { case next: Continue[A] @unchecked => _loop(idx + 1, next._1) case res => res.asInstanceOf[O] } - case <(res) => + case res => res.asInstanceOf[O] loop(0, input) end indexed @@ -184,16 +184,16 @@ object Loop: def _loop(idx: Int, i1: A, i2: B): O < S = loop(idx, i1, i2) @tailrec def loop(idx: Int, i1: A, i2: B): O < S = run(idx, i1, i2) match - case <(next: Continue2[A, B] @unchecked) => + case next: Continue2[A, B] @unchecked => loop(idx + 1, next._1, next._2) - case kyo @ <(_: Kyo[O | Continue2[A, B], S] @unchecked) => + case kyo: Kyo[O | Continue2[A, B], S] @unchecked => kyo.map { case next: Continue2[A, B] @unchecked => _loop(idx + 1, next._1, next._2) case res => res.asInstanceOf[O] } - case <(res) => + case res => res.asInstanceOf[O] loop(0, input1, input2) end indexed @@ -204,16 +204,16 @@ object Loop: def _loop(idx: Int, i1: A, i2: B, i3: C): O < S = loop(idx, i1, i2, i3) @tailrec def loop(idx: Int, i1: A, i2: B, i3: C): O < S = run(idx, i1, i2, i3) match - case <(next: Continue3[A, B, C] @unchecked) => + case next: Continue3[A, B, C] @unchecked => loop(idx + 1, next._1, next._2, next._3) - case kyo @ <(_: Kyo[O | Continue3[A, B, C], S] @unchecked) => + case kyo: Kyo[O | Continue3[A, B, C], S] @unchecked => kyo.map { case next: Continue3[A, B, C] @unchecked => _loop(idx + 1, next._1, next._2, next._3) case res => res.asInstanceOf[O] } - case <(res) => + case res => res.asInstanceOf[O] loop(0, input1, input2, input3) end indexed @@ -224,16 +224,16 @@ object Loop: def _loop(idx: Int, i1: A, i2: B, i3: C, i4: D): O < S = loop(idx, i1, i2, i3, i4) @tailrec def loop(idx: Int, i1: A, i2: B, i3: C, i4: D): O < S = run(idx, i1, i2, i3, i4) match - case <(next: Continue4[A, B, C, D] @unchecked) => + case next: Continue4[A, B, C, D] @unchecked => loop(idx + 1, next._1, next._2, next._3, next._4) - case kyo @ <(_: Kyo[O | Continue4[A, B, C, D], S] @unchecked) => + case kyo: Kyo[O | Continue4[A, B, C, D], S] @unchecked => kyo.map { case next: Continue4[A, B, C, D] @unchecked => _loop(idx + 1, next._1, next._2, next._3, next._4) case res => res.asInstanceOf[O] } - case <(res) => + case res => res.asInstanceOf[O] loop(0, input1, input2, input3, input4) end indexed @@ -242,9 +242,9 @@ object Loop: def _loop(): Unit < S = loop() @tailrec def loop(): Unit < S = run match - case <(next: Continue[Unit] @unchecked) => + case next: Continue[Unit] @unchecked => loop() - case kyo @ <(_: Kyo[Unit | Continue[Unit], S] @unchecked) => + case kyo: Kyo[Unit | Continue[Unit], S] @unchecked => kyo.map { case next: Continue[Unit] => _loop() @@ -262,7 +262,7 @@ object Loop: if i == n then () else run match - case kyo @ <(_: Kyo[Unit, S] @unchecked) => + case kyo: Kyo[Unit, S] @unchecked => kyo.andThen(_loop(i + 1)) case _ => loop(i + 1) @@ -273,7 +273,7 @@ object Loop: def _loop(): Unit < S = loop() @tailrec def loop(): Unit < S = run match - case kyo @ <(_: Kyo[Unit, S] @unchecked) => + case kyo: Kyo[Unit, S] @unchecked => kyo.andThen(_loop()) case _ => loop() diff --git a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Pending.scala b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Pending.scala index 14fb965e9..3944222e6 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Pending.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Pending.scala @@ -7,15 +7,12 @@ import scala.annotation.tailrec import scala.language.implicitConversions import scala.util.NotGiven -// TODO Constructor should be private but there's an issue with inlining -case class <[+A, -S](private val curr: A | Kyo[A, S]) extends AnyVal +opaque type <[+A, -S] = A | Kyo[A, S] object `<`: - implicit private[kyo2] inline def apply[A, S](p: Kyo[A, S]): A < S = new <(p) - - implicit inline def lift[A, S](v: A)(using inline ng: NotGiven[A <:< (Any < Nothing)]): A < S = - <(v) + implicit private[kernel] inline def fromKyo[A, S](v: Kyo[A, S]): A < S = v + implicit inline def lift[A, S](v: A)(using inline ng: NotGiven[A <:< (Any < Nothing)]): A < S = v implicit inline def liftPureFunction1[A1, B](inline f: A1 => B)( using inline ng: NotGiven[B <:< (Any < Nothing)] @@ -39,9 +36,6 @@ object `<`: extension [A, S](inline v: A < S) - inline def unit(using inline frame: Frame): Unit < S = - map(_ => ()) - inline def andThen[B, S2](inline f: => B < S2)(using inline ev: A => Unit, inline frame: Frame): B < (S & S2) = map(_ => f) @@ -51,20 +45,17 @@ object `<`: inline def pipe[B, S2](inline f: A < S => B < S2)(using inline frame: Frame): B < S2 = f(v) - inline def repeat(i: Int)(using inline ev: A => Unit, inline frame: Frame): Unit < S = - if i <= 0 then () else andThen(repeat(i - 1)) - inline def map[B, S2](inline f: Safepoint ?=> A => B < S2)( using inline _frame: Frame )(using Safepoint): B < (S & S2) = def mapLoop(v: A < S)(using Safepoint): B < (S & S2) = v match - case <(kyo: KyoSuspend[IX, OX, EX, Any, A, S] @unchecked) => + case kyo: KyoSuspend[IX, OX, EX, Any, A, S] @unchecked => new KyoContinue[IX, OX, EX, Any, B, S & S2](kyo): def frame = _frame def apply(v: OX[Any], context: Context)(using Safepoint) = mapLoop(kyo(v, context)) - case <(v) => + case v => val value = v.asInstanceOf[A] Safepoint.handle(value)( suspend = mapLoop(value), @@ -73,27 +64,37 @@ object `<`: mapLoop(v) end map - inline def evalNow: Maybe[A] = + inline def evalNow(using inline flat: Flat[A]): Maybe[A] = v match - case <(kyo: Kyo[?, ?]) => Maybe.empty - case <(v) => Maybe(v.asInstanceOf[A]) + case kyo: Kyo[?, ?] => Maybe.empty + case v => Maybe(v.asInstanceOf[A]) + + end extension + + // TODO Compiler crash if inlined + extension [A, S](v: A < S) + def repeat(i: Int)(using ev: A => Unit, frame: Frame): Unit < S = + if i <= 0 then () else v.andThen(repeat(i - 1)) + def unit(using frame: Frame): Unit < S = + v.map(_ => ()) end extension - extension [A, S, S2](inline kyo: A < S < S2) - inline def flatten(using inline frame: Frame): A < (S & S2) = - kyo.map(identity) + // TODO Compiler crash if inlined + extension [A, S, S2](v: A < S < S2) + def flatten(using frame: Frame): A < (S & S2) = + v.map(identity) extension [A](inline v: A < Any) - inline def eval(using inline frame: Frame): A = + inline def eval(using inline frame: Frame, inline flat: Flat[A]): A = @tailrec def evalLoop(kyo: A < Any)(using Safepoint): A = kyo match - case <(kyo: KyoSuspend[Const[Unit], Const[Unit], Defer, Any, A, Any] @unchecked) + case kyo: KyoSuspend[Const[Unit], Const[Unit], Defer, Any, A, Any] @unchecked if kyo.tag =:= Tag[Defer] => evalLoop(kyo((), Context.empty)) - case <(kyo: Kyo[A, Any] @unchecked) => + case kyo: Kyo[A, Any] @unchecked => kyo2.bug.failTag(kyo, Tag[Any]) - case <(v) => + case v => v.asInstanceOf[A] end match end evalLoop diff --git a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Safepoint.scala b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Safepoint.scala index daaab3aa6..f0506d155 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/kernel/Safepoint.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/kernel/Safepoint.scala @@ -73,7 +73,7 @@ object Safepoint: ): A < S = def loop(v: A < S): A < S = v match - case <(kyo: KyoSuspend[IX, OX, EX, Any, A, S] @unchecked) => + case kyo: KyoSuspend[IX, OX, EX, Any, A, S] @unchecked => new KyoContinue[IX, OX, EX, Any, A, S](kyo): def frame = _frame def apply(v: OX[Any], context: Context)(using Safepoint): A < S = @@ -113,7 +113,7 @@ object Safepoint: def ensureLoop(v: A < S)(using safepoint: Safepoint): A < S = v match - case <(kyo: KyoSuspend[IX, OX, EX, Any, A, S] @unchecked) => + case kyo: KyoSuspend[IX, OX, EX, Any, A, S] @unchecked => new KyoContinue[IX, OX, EX, Any, A, S](kyo): def frame = _frame def apply(v: OX[Any], context: Context)(using Safepoint): A < S = diff --git a/kyo-prelude/shared/src/main/scala/kyo2/package.scala b/kyo-prelude/shared/src/main/scala/kyo2/package.scala index dbec99cc3..a66909755 100644 --- a/kyo-prelude/shared/src/main/scala/kyo2/package.scala +++ b/kyo-prelude/shared/src/main/scala/kyo2/package.scala @@ -6,8 +6,12 @@ import kyo2.kernel.Safepoint private[kyo2] type Frame = kernel.Frame private[kyo2] inline def Frame = kernel.Frame -export kernel.< -export kernel.Loop +type Flat[A] = kernel.Flat[A] +val Flat = kernel.Flat + +type <[+A, -S] = kernel.<[A, S] + +val Loop = kernel.Loop private[kyo2] inline def isNull[A](v: A): Boolean = v.asInstanceOf[AnyRef] eq null diff --git a/kyo-prelude/shared/src/test/scala/kyo2/KyoTest.scala b/kyo-prelude/shared/src/test/scala/kyo2/KyoTest.scala index 832ec1ae6..5630fd6ad 100644 --- a/kyo-prelude/shared/src/test/scala/kyo2/KyoTest.scala +++ b/kyo-prelude/shared/src/test/scala/kyo2/KyoTest.scala @@ -9,19 +9,19 @@ class KyoTest extends Test: "toString JVM" taggedAs jvmOnly in run { assert(Env.use[Int](_ + 1).toString() == - "<(Kyo(Tag[kyo2.kernel.package$.internal$.Defer], Input(()), KyoTest.scala:11:35, assert(Env.use[Int](_ + 1)))") + "Kyo(Tag[kyo2.kernel.package$.internal$.Defer], Input(()), KyoTest.scala:11:35, assert(Env.use[Int](_ + 1))") assert( Env.get[Int].map(_ + 1).toString() == - "<(Kyo(Tag[kyo2.kernel.package$.internal$.Defer], Input(()), KyoTest.scala:14:36, Env.get[Int].map(_ + 1)))" + "Kyo(Tag[kyo2.kernel.package$.internal$.Defer], Input(()), KyoTest.scala:14:36, Env.get[Int].map(_ + 1))" ) } "toString JS" taggedAs jsOnly in run { assert(Env.use[Int](_ + 1).toString() == - "<(Kyo(Tag[kyo2.kernel.package$.internal$.Defer], Input(undefined), KyoTest.scala:20:35, assert(Env.use[Int](_ + 1)))") + "Kyo(Tag[kyo2.kernel.package$.internal$.Defer], Input(undefined), KyoTest.scala:20:35, assert(Env.use[Int](_ + 1))") assert( Env.get[Int].map(_ + 1).toString() == - "<(Kyo(Tag[kyo2.kernel.package$.internal$.Defer], Input(undefined), KyoTest.scala:23:36, Env.get[Int].map(_ + 1)))" + "Kyo(Tag[kyo2.kernel.package$.internal$.Defer], Input(undefined), KyoTest.scala:23:36, Env.get[Int].map(_ + 1))" ) } @@ -32,8 +32,7 @@ class KyoTest extends Test: } "eval widened" in { - val v = widen(Env.use[Int](_ + 1)).eval - assert(Env.run(1)(v).eval == 2) + assertDoesNotCompile("widen(Env.use[Int](_ + 1)).eval") } "map" in { @@ -82,9 +81,8 @@ class KyoTest extends Test: val b: Int < Env[Unit] = a.flatten assert(Env.run(())(b).eval == 2) } - "eval nested" in { - val a: Int < Env[Unit] = Env.run(())(io).eval - assert(Env.run(())(a).eval == 1) + "eval doesn't compile" in { + assertDoesNotCompile("Env.run(())(io).eval") } } diff --git a/kyo-prelude/shared/src/test/scala/kyo2/kernel/FlatTest.scala b/kyo-prelude/shared/src/test/scala/kyo2/kernel/FlatTest.scala new file mode 100644 index 000000000..221cc8b79 --- /dev/null +++ b/kyo-prelude/shared/src/test/scala/kyo2/kernel/FlatTest.scala @@ -0,0 +1,71 @@ +package kyo2.kernel + +import kyo.Tag +import kyo2.* + +class FlatTest extends Test: + + "ok" - { + "concrete" in { + implicitly[Flat[Int]] + implicitly[Flat[String]] + implicitly[Flat[Thread]] + succeed + } + "derived from Tag" in { + def test[T: Tag] = + implicitly[Flat[T]] + succeed + test[Int] + } + } + + "nok" - { + + "pending type" in { + assertDoesNotCompile("implicitly[Flat[Int < Any]]") + assertDoesNotCompile("implicitly[Flat[Int < Options]]") + assertDoesNotCompile("implicitly[Flat[Int < Nothing]]") + } + + "nested" in { + assertDoesNotCompile("implicitly[Flat[Int < IOs < IOs]]") + assertDoesNotCompile("implicitly[Flat[Any < IOs < IOs]]") + } + + "nested w/ mismatch" in { + assertDoesNotCompile("implicitly[Flat[Int < Options < IOs]]") + assertDoesNotCompile("implicitly[Flat[Int < IOs < Options]]") + } + + "generic" in { + def test1[T] = + assertDoesNotCompile("implicitly[Flat[T]]") + assertDoesNotCompile("implicitly[Flat[T | Int]]") + assertDoesNotCompile("implicitly[Flat[T < Options]]") + assertDoesNotCompile("implicitly[Flat[T < Any]]") + end test1 + test1[Int] + succeed + } + + "effect mismatch" in { + def test[T: Flat](v: T < Abort[Int]): T < Abort[Int] = v + test(1) + test(1: Int < Abort[Int]) + assertDoesNotCompile("test(1: Int < Memo)") + } + + "flat flat" in { + def test[T](v: T < Memo)(using Flat[T]): T < Memo = v + test(1) + test(1: Int < Memo) + assertDoesNotCompile("test(1: Int < Abort[Int])") + } + + "any" in { + assertDoesNotCompile("implicitly[Flat[Any]]") + assertDoesNotCompile("implicitly[Flat[Any < IOs]]") + } + } +end FlatTest diff --git a/kyo-prelude/shared/src/test/scala/kyo2/kernel/PendingTest.scala b/kyo-prelude/shared/src/test/scala/kyo2/kernel/PendingTest.scala index c1a24f115..285b62289 100644 --- a/kyo-prelude/shared/src/test/scala/kyo2/kernel/PendingTest.scala +++ b/kyo-prelude/shared/src/test/scala/kyo2/kernel/PendingTest.scala @@ -80,9 +80,9 @@ class PendingTest extends Test: assert(x.eval == 5) } - "prevents lifting nested kyo computations" - { - "method effect mismatch" in { - def test1(v: Int < Any) = v.map(_ + 1) + "nested computation" - { + "generic method effect mismatch" in { + def test1[T](v: T < Any) = v assertDoesNotCompile("test1(effect)") } "inference widening" in { @@ -156,9 +156,8 @@ class PendingTest extends Test: assert(x.evalNow == Maybe.empty) } - "returns Defined for nested pure values" in { - val x: Int < Any < Any = <(1: Int < Any) - assert(x.evalNow.flatMap(_.evalNow) == Maybe(1)) + "doesn't accept nested computations" in { + assertDoesNotCompile("def test(x: Int < Any < Any) = x.evalNow") } }