Skip to content

Commit

Permalink
flat check: tests + still improving error message
Browse files Browse the repository at this point in the history
  • Loading branch information
fwbrasil committed Nov 24, 2023
1 parent 279c1e3 commit 65c5de6
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 124 deletions.
28 changes: 14 additions & 14 deletions kyo-cache/shared/src/main/scala/kyo/concurrent/caches.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ object caches {
f: T => U > S
)(implicit
id: sourcecode.Enclosing,
ft: Flat[T, Any],
fu: Flat[U, Any]
ft: Flat[T > Any],
fu: Flat[U > Any]
): T => U > (Fibers with S) = {
(v: T) =>
Fibers.initPromise[U].map { p =>
Expand Down Expand Up @@ -58,9 +58,9 @@ object caches {
f: (T1, T2) => U > S
)(implicit
id: sourcecode.Enclosing,
ft1: Flat[T1, Any],
ft2: Flat[T2, Any],
fu: Flat[U, Any]
ft1: Flat[T1 > Any],
ft2: Flat[T2 > Any],
fu: Flat[U > Any]
): (T1, T2) => U > (Fibers with S) = {
val m = memo[(T1, T2), U, S](f.tupled)
(t1, t2) => m((t1, t2))
Expand All @@ -70,10 +70,10 @@ object caches {
f: (T1, T2, T3) => U > S
)(implicit
id: sourcecode.Enclosing,
ft1: Flat[T1, Any],
ft2: Flat[T2, Any],
ft3: Flat[T3, Any],
fu: Flat[U, Any]
ft1: Flat[T1 > Any],
ft2: Flat[T2 > Any],
ft3: Flat[T3 > Any],
fu: Flat[U > Any]
): (T1, T2, T3) => U > (Fibers with S) = {
val m = memo[(T1, T2, T3), U, S](f.tupled)
(t1, t2, t3) => m((t1, t2, t3))
Expand All @@ -83,11 +83,11 @@ object caches {
f: (T1, T2, T3, T4) => U > S
)(implicit
id: sourcecode.Enclosing,
ft1: Flat[T1, Any],
ft2: Flat[T2, Any],
ft3: Flat[T3, Any],
ft4: Flat[T4, Any],
fu: Flat[U, Any]
ft1: Flat[T1 > Any],
ft2: Flat[T2 > Any],
ft3: Flat[T3 > Any],
ft4: Flat[T4 > Any],
fu: Flat[U > Any]
): (T1, T2, T3, T4) => U > (Fibers with S) = {
val m = memo[(T1, T2, T3, T4), U, S](f.tupled)
(t1, t2, t3, t4) => m((t1, t2, t3, t4))
Expand Down
56 changes: 34 additions & 22 deletions kyo-core/shared/src/main/scala-2/internal/FlatImplicits.scala
Original file line number Diff line number Diff line change
@@ -1,42 +1,54 @@
package kyo.internal

import kyo.Flat
import scala.language.experimental.macros
import scala.reflect.macros.blackbox._
import scala.reflect.macros.blackbox.Context
import kyo._

trait FlatImplicits0 {
implicit def infer[T]: Flat[T] = macro FlatMacros.inferMacro[T]
implicit def infer[T]: Flat[T] = macro FlatImplicits.inferMacro[T]
}

trait FlatImplicits1 extends FlatImplicits0 {
implicit def derived[T](implicit f: Flat[T]): Flat[T] =
Flat.unsafe.derived[T]
implicit def product[T <: Product]: Flat[T] = Flat.unsafe.checked[T]
}

trait FlatImplicits2 extends FlatImplicits1 {
implicit def product[T <: Product]: Flat[T] =
Flat.unsafe.checked[T]
trait FlatImplicits extends FlatImplicits1 {
implicit def anyVal[T <: AnyVal]: Flat[T] = Flat.unsafe.checked[T]
}

trait FlatImplicits extends FlatImplicits2 {
implicit def anyVal[T <: AnyVal]: Flat[T] =
Flat.unsafe.checked[T]
}

object FlatMacros {
def inferMacro[T: c.WeakTypeTag](c: Context): c.Expr[Flat[T]] = {
object FlatImplicits {
def inferMacro[T](c: Context)(implicit t: c.WeakTypeTag[T]): c.Expr[Flat[T]] = {
import c.universe._

val t = weakTypeOf[T]
println(t)

object Kyo {
def unapply(tpe: Type): Option[(Type, Type)] =
tpe match {
case TypeRef(_, sym, List(t, u)) if sym.asType.fullName == "kyo.>" =>
Some((t, u))
case _ =>
None
}
}

def canDerive(t: Type): Boolean = {
val flatType = appliedType(typeOf[Flat[_]].typeConstructor, List(t))
c.inferImplicitValue(flatType, silent = true) != EmptyTree
}

c.abort(c.enclosingPosition, "AAA " + t)
def ok = c.Expr[Flat[T]](q"kyo.Flat.unsafe.checked[$t]")

val isConcrete = t.typeSymbol.isClass
def fail(msg: String) = c.abort(c.enclosingPosition, msg)

if (isConcrete || t.toString.startsWith("kyo.concurrent.fibers.Fiber")) {
c.Expr[Flat[T]](q"Flat.unsafe.checked[$t]")
} else {
c.abort(c.enclosingPosition, "not pure: " + t.toString)
t match {
case Kyo(Kyo(_, _), _) =>
fail("Nested Kyo computations are not allowed.")
case Kyo(nt, _) if !nt.typeSymbol.isClass && !canDerive(nt) =>
fail(s"Cannot prove ${nt.toString} isn't nested. Provide an implicit evidence.")
case _ =>
println(t)
ok
}
}
}
41 changes: 20 additions & 21 deletions kyo-core/shared/src/main/scala-3/kyo/internal/FlatImplicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ trait FlatImplicits extends FlatImplicits1 {
}

object FlatImplicits {

def inferMacro[T: Type](using Quotes): Expr[Flat[T]] = {
import quotes.reflect._

Expand All @@ -44,46 +45,44 @@ object FlatImplicits {
}
}

def fail(msg: String) =
report.errorAndAbort(s"Method doesn't accept nested Kyo computations.\n$msg")

def canDerive(t: TypeRepr) =
t.asType match {
case '[nt] =>
Expr.summon[Flat[nt]]
.orElse(Expr.summon[Flat[nt > Nothing]])
.orElse(Expr.summon[Flat[nt > Any]])
.isDefined
}

def ok = '{ Flat.unsafe.checked[T] }
def isConcrete(t: TypeRepr) =
t.typeSymbol.isClassDef && t.typeSymbol != TypeRepr.of[Any].typeSymbol

def fail(msg: String) =
report.errorAndAbort(s"Method doesn't accept nested Kyo computations.\n$msg")
def check(t: TypeRepr): Expr[Flat[T]] =
if (!isConcrete(t) && !canDerive(t)) {
fail(
s"Cannot prove ${code(print(t))} isn't nested. Provide an implicit evidence ${code(s"kyo.Flat[${print(t)}]")}."
)
} else {
'{ Flat.unsafe.checked[T] }
}

t match {
case Kyo(Kyo(nt, s1), s2) =>
val potentialMismatch =
if (s1 != s2) {
val mismatch =
if (print(s1) != print(s2)) {
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. " + potentialMismatch
s"Detected: ${code(print(t))}. Consider using ${code("flatten")} to resolve. " + mismatch
)
case Kyo(nt, s) =>
if (!nt.typeSymbol.isClassDef && !canDerive(nt)) {
fail(
s"Cannot prove ${code(print(nt))} isn't nested. Provide an implicit evidence ${code(s"kyo.Flat[${print(nt)}]")}."
)
} else {
ok
}
check(nt)
case t =>
if (!t.typeSymbol.isClassDef) {
fail(
s"Cannot prove ${code(print(t))} isn't nested. Provide an implicit evidence ${code(s"kyo.Flat[${print(t)}]")}."
)
} else {
ok
}
check(t)
}
}
}
2 changes: 2 additions & 0 deletions kyo-core/shared/src/main/scala/kyo/Flat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ object Flat extends FlatImplicits {
private val cachedUnchecked = new Unchecked[Any] {}
private val cachedDerived = new Derived[Any] {}

implicit def unit[S]: Flat[Unit > S] = unsafe.checked[Unit > S]

object unsafe {
def checked[T]: Checked[T] =
cachedChecked.asInstanceOf[Checked[T]]
Expand Down
4 changes: 2 additions & 2 deletions kyo-core/shared/src/main/scala/kyo/concurrent/fibers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ object fibers {

implicit class FiberOps[T](private val state: Fiber[T]) extends AnyVal {

private implicit def flat: Flat[T] = Flat.unsafe.checked[T]
private implicit def flat[S]: Flat[T > S] = Flat.unsafe.checked[T > S]

def isDone: Boolean > IOs =
state match {
Expand Down Expand Up @@ -235,7 +235,7 @@ object fibers {
val j = i
fiber.onComplete { r =>
try {
results(j) = IOs.run(r)
results(j) = IOs.run(r)(Flat.unsafe.checked)
if (pending.decrementAndGet() == 0) {
p.complete(ArraySeq.unsafeWrapArray(results))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.util.concurrent.locks.Lock
private[kyo] class IOPromise[T](state: State[T])
extends AtomicReference(state) {

private implicit def flat: Flat[T] = Flat.unsafe.checked[T]
private implicit def flat[S]: Flat[T > S] = Flat.unsafe.checked[T > S]

def this() = this(Pending())

Expand Down
3 changes: 0 additions & 3 deletions kyo-core/shared/src/main/scala/kyo/ios.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ object ios {
def fail[T](msg: String): T > IOs =
fail(new Exception(msg))

def attempt[T, S](v: => T > S)(implicit f: Flat[T > S]): Try[T] > S =
Tries.run[T, S](v)

/*inline*/
def apply[T, S](
/*inline*/ f: => T > (IOs with S)
Expand Down
4 changes: 2 additions & 2 deletions kyo-core/shared/src/main/scala/kyo/tries.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ object tries {
def run[T, S](v: => T > (Tries with S))(implicit f: Flat[T > (Tries with S)]): Try[T] > S =
aborts.run[T, S](v).map(_.toTry)

def handle[T, S](v: => T > S)(f: PartialFunction[Throwable, T > S])(
implicit flat: Flat[T > S]
def handle[T, S](v: => T > (Tries with S))(f: PartialFunction[Throwable, T > S])(
implicit flat: Flat[T > (Tries with S)]
): T > S =
run[T, S](v).map {
case Failure(e) if (f.isDefinedAt(e)) =>
Expand Down
91 changes: 56 additions & 35 deletions kyo-core/shared/src/test/scala/kyoTest/FlatTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,62 @@ import kyo.concurrent.fibers._

class FlatTest extends KyoTest {

// "ok" in {
// implicitly[Flat[Int, Any]]
// implicitly[Flat[Int, Options]]
// implicitly[Flat[Int, Nothing]]
// implicitly[Flat[Fiber[Int], Options]]
// succeed
// }

// "nok" in {
// def test1[T] = {
// assertDoesNotCompile("implicitly[Flat[T, Options]]")
// assertDoesNotCompile("implicitly[Flat[T, Any]]")
// }
// def test2[T](v: T > IOs)(implicit f: Flat[T, IOs]) = {}
// assertDoesNotCompile("test2(1: Int > Options)")
// assertDoesNotCompile("test2(1: Int > IOs > IOs)")
// assertDoesNotCompile("implicitly[Flat[Int > Options, Any]]")
// assertDoesNotCompile("implicitly[Flat[Int > Options, IOs]]")
// }

"a" in {
def test1[T] = {
implicitly[Flat[T]]
implicitly[Flat[T > Options]]
}
implicitly[Flat[Int > IOs > IOs]]
def test2[T](v: T > IOs)(implicit f: Flat[T > IOs]) = {}
test2(1: Int > Options)
def test3[T](implicit f: Flat[T]) =
implicitly[Flat[T > IOs]]
def test4[T](implicit f: Flat[T > Options]) =
implicitly[Flat[T > IOs]]
def test5[T] =
implicitly[Flat[T > IOs]]
succeed
"ok" - {
"concrete" in {
implicitly[Flat[Int]]
implicitly[Flat[Int > Any]]
implicitly[Flat[Int > Options]]
implicitly[Flat[Int > Nothing]]
succeed
}
"fiber" in {
implicitly[Flat[Fiber[Int] > Options]]
succeed
}
"by evidence" in {
def test1[T: Flat] = {
implicitly[Flat[T > IOs]]
implicitly[Flat[T > Options]]
implicitly[Flat[T > Any]]
}
succeed
}
"derived" in {
def test2[T](implicit f: Flat[T > IOs]) = {
implicitly[Flat[T]]
implicitly[Flat[T > Options]]
implicitly[Flat[T > Any]]
}
succeed
}
}

"nok" - {

"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 > Options]]")
assertDoesNotCompile("implicitly[Flat[T > Any]]")
}
test1[Int]
succeed
}

"any" in {
assertDoesNotCompile("implicitly[Flat[Any]]")
assertDoesNotCompile("implicitly[Flat[Any > IOs]]")
}
}

}
19 changes: 0 additions & 19 deletions kyo-core/shared/src/test/scala/kyoTest/iosTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -126,25 +126,6 @@ class iosTest extends KyoTest {
}
}

"attempt" - {
"success" in run {
val io = IOs(1).map(_ + 1)
checkEquals[Try[Int], Nothing](
IOs.run[Try[Int]](IOs.attempt(io)),
Success(2)
)
}
"failure" in run {
val ex = new Exception
def fail: Int = throw ex
val io = IOs(fail)
checkEquals[Try[Int], Nothing](
IOs.run[Try[Int]](IOs.attempt(io)),
Try(fail)
)
}
}

"ensure" - {
"success" in {
var called = false
Expand Down
2 changes: 1 addition & 1 deletion kyo-direct/src/main/scala-3/kyo/TestSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import kyo.direct._

object TestSupport {
transparent inline def runLiftTest[T, U](inline expected: T)(inline body: U)(implicit
f: Flat[U, Any]
f: Flat[U > Any]
) = {
val actual: U = IOs.run(defer(body).asInstanceOf[U > IOs])
if (expected != actual)
Expand Down
Loading

0 comments on commit 65c5de6

Please sign in to comment.