Skip to content

Commit

Permalink
Strict derivation for Empty
Browse files Browse the repository at this point in the history
  • Loading branch information
joroKr21 committed Dec 17, 2023
1 parent 122933d commit cdabbcc
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 18 deletions.
14 changes: 12 additions & 2 deletions core/src/main/scala-3/cats/derived/DerivedEmpty.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,22 @@ Make sure that A satisfies one of the following conditions:
type DerivedEmpty[A] = Derived[Empty[A]]
object DerivedEmpty:
type Or[A] = Derived.Or[Empty[A]]

inline def apply[A]: Empty[A] =
import DerivedEmpty.given
summonInline[DerivedEmpty[A]].instance

inline def strict[A]: Empty[A] =
import DerivedEmpty.given
import Strict.product
summonInline[DerivedEmpty[A]].instance

given product[A](using inst: K0.ProductInstances[Or, A]): DerivedEmpty[A] =
Empty(inst.unify.construct([a] => (_: Empty[a]).empty))
Strict.product(using inst.unify)

inline given coproduct[A](using gen: K0.CoproductGeneric[A]): DerivedEmpty[A] =
Empty(gen.withOnly[Or, A]([a <: A] => (_: Or[a]).unify.empty))
Empty(gen.withOnly[Or, A]([a <: A] => (A: Or[a]) => A.unify.empty))

object Strict:
given product[A](using inst: K0.ProductInstances[Empty, A]): DerivedEmpty[A] =
Empty(inst.construct([a] => (A: Empty[a]) => A.empty))
3 changes: 1 addition & 2 deletions core/src/main/scala-3/cats/derived/DerivedEq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ object DerivedEq:
Eq.allEqual

given product[A](using inst: => K0.ProductInstances[Or, A]): DerivedEq[A] =
given K0.ProductInstances[Eq, A] = inst.unify
new Product[Eq, A] {}
Strict.product(using inst.unify)

given coproduct[A](using inst: => K0.CoproductInstances[Or, A]): DerivedEq[A] =
given K0.CoproductInstances[Eq, A] = inst.unify
Expand Down
3 changes: 1 addition & 2 deletions core/src/main/scala-3/cats/derived/DerivedHash.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ object DerivedHash:
given symbol[A <: Symbol]: DerivedHash[A] = Hash.fromUniversalHashCode

given product[A <: scala.Product](using inst: => K0.ProductInstances[Or, A]): DerivedHash[A] =
given K0.ProductInstances[Hash, A] = inst.unify
new Product[Hash, A] {}
Strict.product(using inst.unify)

given coproduct[A](using inst: => K0.CoproductInstances[Or, A]): DerivedHash[A] =
given K0.CoproductInstances[Hash, A] = inst.unify
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala-3/cats/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@ object semiauto:
object strict:
extension (x: Eq.type) inline def derived[A]: Eq[A] = DerivedEq.strict[A]
extension (x: Hash.type) inline def derived[A]: Hash[A] = DerivedHash.strict[A]
extension (x: Empty.type) inline def derived[A]: Empty[A] = DerivedEmpty.strict[A]

object semiauto:
inline def eq[A]: Eq[A] = DerivedEq.strict[A]
inline def hash[A]: Hash[A] = DerivedHash.strict[A]
inline def empty[A]: Empty[A] = DerivedEmpty.strict[A]

object auto:
object eq:
Expand Down
36 changes: 27 additions & 9 deletions core/src/test/scala-3/cats/derived/EmptySuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,30 @@ class EmptySuite extends KittensSuite:
test(s"$instance respects existing instances")(assert(empty[Box[Mask]] == Box(Mask(0xffffffff))))
checkAll(s"$instance is Serializable", SerializableTests.serializable(summonInline[Empty[Foo]]))

locally {
locally:
import auto.empty.given
validate("auto.empty")
testNoInstance("Empty", "IList[Int]")
testNoInstance("Empty", "Snoc[Int]")
testNoInstance("Empty", "Rgb")
}

locally {
locally:
import semiInstances.given
validate("semiauto.empty")
testNoInstance("Empty", "IList[Int]")
testNoInstance("Empty", "Snoc[Int]")
testNoInstance("Empty", "Rgb")
}
testNoInstance("semiauto.empty", "IList[Int]")
testNoInstance("semiauto.empty", "Snoc[Int]")
testNoInstance("semiauto.empty", "Rgb")

locally {
locally:
import strictInstances.given
validate("strict.semiauto.empty")
testNoInstance("strict.semiauto.empty", "Rgb")
testNoInstance("strict.semiauto.empty", "Top")
test("No strict.semiauto.empty for IList[Int] or Snoc[Int]"):
assertNoInstance(compileErrors("given Empty[IList[Int]] = strict.semiauto.empty"))
assertNoInstance(compileErrors("given Empty[Snoc[Int]] = strict.semiauto.empty"))

locally:
import derivedInstances.*
val instance = "derived.empty"
test(s"$instance[Foo]")(assert(empty[Foo].x == ADTs.Foo(0, None)))
Expand All @@ -65,7 +72,6 @@ class EmptySuite extends KittensSuite:
test(s"$instance[Snoc[Dummy]]")(assert(empty[Snoc[Int]].x == SNil()))
test(s"$instance respects existing instances")(assert(empty[BoxMask].x == Box(Mask(0xffffffff))))
checkAll(s"$instance is Serializable", SerializableTests.serializable(Empty[Foo]))
}

end EmptySuite

Expand All @@ -85,6 +91,18 @@ object EmptySuite:
given Empty[Box[Mask]] = semiauto.empty
given Empty[Chain] = semiauto.empty

object strictInstances:
given Empty[Foo] = strict.semiauto.empty
given Empty[Outer] =
given Empty[Inner] = strict.semiauto.empty
strict.semiauto.empty
given Empty[Interleaved[String]] = strict.semiauto.empty
given Empty[Recursive] = strict.semiauto.empty
given Empty[IList[Dummy]] = strict.semiauto.empty
given Empty[Snoc[Dummy]] = strict.semiauto.empty
given Empty[Box[Mask]] = strict.semiauto.empty
given Empty[Chain] = strict.semiauto.empty

object derivedInstances:
case class Foo(x: ADTs.Foo) derives Empty
case class Outer(x: ADTs.Outer) derives Empty
Expand Down
8 changes: 5 additions & 3 deletions core/src/test/scala-3/cats/derived/KittensSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ object KittensSuite:
given [A <: Product](using mirror: Mirror.ProductOf[A], via: Cogen[mirror.MirroredElemTypes]): Cogen[A] =
via.contramap(Tuple.fromProductTyped)

inline def testNoInstance(inline tc: String, target: String): Unit =
val errors = compileErrors(tc + "[" + target + "]")
def assertNoInstance(errors: String): Unit =
val message = "No given instance of type"
test(s"No $tc for $target")(assert(errors.contains(message), s"$errors did not contain $message"))
assert(errors.contains(message), s"$errors did not contain $message")

inline def testNoInstance(inline tc: String, target: String): Unit =
test(s"No $tc for $target")(assertNoInstance(compileErrors(tc + "[" + target + "]")))

0 comments on commit cdabbcc

Please sign in to comment.