Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedEq.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cats.derived

import cats.Eq
import shapeless3.deriving.{Complete, K0}

import scala.compiletime.*

type DerivedEq[A] = Derived[Eq[A]]
object DerivedEq:
type Or[A] = Derived.Or[Eq[A]]
inline def apply[A]: Eq[A] =
import DerivedEq.given
summonInline[DerivedEq[A]].instance

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

given [A](using inst: => K0.CoproductInstances[Or, A]): DerivedEq[A] =
given K0.CoproductInstances[Eq, A] = inst.unify
new Coproduct[Eq, A] {}

trait Product[F[x] <: Eq[x], A](using inst: K0.ProductInstances[F, A]) extends Eq[A]:
def eqv(x: A, y: A): Boolean = inst.foldLeft2(x, y)(true: Boolean)(
[t] => (acc: Boolean, eqt: F[t], x: t, y: t) => Complete(!eqt.eqv(x, y))(false)(true)
)

trait Coproduct[F[x] <: Eq[x], A](using inst: K0.CoproductInstances[F, A]) extends Eq[A]:
def eqv(x: A, y: A): Boolean = inst.fold2(x, y)(false)(
[t] => (eqt: F[t], x: t, y: t) => eqt.eqv(x, y)
)
45 changes: 45 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedHash.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package cats.derived

import cats.Hash
import shapeless3.deriving.{K0, Continue}

import scala.compiletime.*
import scala.util.hashing.MurmurHash3

type DerivedHash[A] = Derived[Hash[A]]
object DerivedHash:
type Or[A] = Derived.Or[Hash[A]]
inline def apply[A]: Hash[A] =
import DerivedHash.given
summonInline[DerivedHash[A]].instance

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

given [A](using inst: => K0.CoproductInstances[Or, A]): DerivedHash[A] =
given K0.CoproductInstances[Hash, A] = inst.unify
new Coproduct[Hash, A] {}

trait Product[F[x] <: Hash[x], A <: scala.Product](using inst: K0.ProductInstances[F, A])
extends DerivedEq.Product[F, A],
Hash[A]:

def hash(x: A): Int =
val arity = x.productArity
val prefix = x.productPrefix.hashCode
if arity <= 0 then prefix
else
MurmurHash3.finalizeHash(
inst.foldLeft[Int](x)(MurmurHash3.mix(MurmurHash3.productSeed, prefix))(
[t] => (acc: Int, h: F[t], x: t) => Continue(MurmurHash3.mix(acc, h.hash(x)))
),
arity
)

trait Coproduct[F[x] <: Hash[x], A](using inst: K0.CoproductInstances[F, A])
extends DerivedEq.Coproduct[F, A],
Hash[A]:

def hash(x: A): Int =
inst.fold[Int](x)([t] => (h: F[t], x: t) => h.hash(x))
30 changes: 0 additions & 30 deletions core/src/main/scala-3/cats/derived/eq.scala

This file was deleted.

38 changes: 0 additions & 38 deletions core/src/main/scala-3/cats/derived/hash.scala

This file was deleted.

14 changes: 11 additions & 3 deletions core/src/main/scala-3/cats/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import cats.kernel.{CommutativeSemigroup, CommutativeMonoid}

import scala.util.NotGiven

extension (E: Eq.type) inline def derived[A]: Eq[A] = DerivedEq[A]
extension (H: Hash.type) inline def derived[A]: Hash[A] = DerivedHash[A]
extension (E: Empty.type) inline def derived[A]: Empty[A] = DerivedEmpty[A]
extension (S: Semigroup.type) inline def derived[A]: Semigroup[A] = DerivedSemigroup[A]
extension (M: Monoid.type) inline def derived[A]: Monoid[A] = DerivedMonoid[A]
Expand All @@ -19,8 +21,6 @@ extension (F: Traverse.type) inline def derived[F[_]]: Traverse[F] = DerivedTrav
object semiauto
extends ContravariantDerivation,
EmptyKDerivation,
EqDerivation,
HashDerivation,
InvariantDerivation,
MonoidKDerivation,
OrderDerivation,
Expand All @@ -29,6 +29,8 @@ object semiauto
ShowDerivation,
Instances:

inline def eq[A]: Eq[A] = DerivedEq[A]
inline def hash[A]: Hash[A] = DerivedHash[A]
inline def empty[A]: Empty[A] = DerivedEmpty[A]
inline def semigroup[A]: Semigroup[A] = DerivedSemigroup[A]
inline def monoid[A]: Monoid[A] = DerivedMonoid[A]
Expand All @@ -40,6 +42,12 @@ object semiauto
inline def traverse[F[_]]: Traverse[F] = DerivedTraverse[F]

object auto:
object eq:
inline given [A](using NotGiven[Eq[A]]): Eq[A] = DerivedEq[A]

object hash:
inline given [A](using NotGiven[Hash[A]]): Hash[A] = DerivedHash[A]

object empty:
inline given [A](using NotGiven[Empty[A]]): Empty[A] = DerivedEmpty[A]

Expand All @@ -54,7 +62,7 @@ object auto:

object commutativeMonoid:
inline given [A](using NotGiven[CommutativeMonoid[A]]): CommutativeMonoid[A] = DerivedCommutativeMonoid[A]

object functor:
inline given [F[_]](using NotGiven[Functor[F]]): Functor[F] = DerivedFunctor[F]

Expand Down
66 changes: 66 additions & 0 deletions core/src/test/scala-3/cats/derived/EqSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2015 Miles Sabin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cats.derived

import cats.Eq
import cats.kernel.laws.discipline.{EqTests, SerializableTests}

import scala.compiletime.*

class EqSuite extends KittensSuite.WithoutEq:
import EqSuite.*
import TestDefns.*

inline def eqTests[A]: EqTests[A] =
EqTests[A](summonInline)

inline def testEq(inline context: String): Unit =
checkAll(s"$context.Eq[Foo]]", eqTests[Foo].eqv)
checkAll(s"$context.Eq[IList[Int]]", eqTests[IList[Int]].eqv)
checkAll(s"$context.Eq[Inner]", eqTests[Inner].eqv)
checkAll(s"$context.Eq[Outer]", eqTests[Outer].eqv)
checkAll(s"$context.Eq[Interleaved[Int]]", eqTests[Interleaved[Int]].eqv)
checkAll(s"$context.Eq[Tree[Int]]", eqTests[Tree[Int]].eqv)
// FIXME: Doesn't work for recursive case classes.
// checkAll(s"$context.Eq[Recursive]", eqTests[Recursive].eqv)
checkAll(s"$context.Eq is Serializable", SerializableTests.serializable(summonInline[Eq[Foo]]))

locally {
import auto.eq.given
testEq("auto")
}

locally {
import semiInstances.given
testEq("semiauto")
}

end EqSuite

object EqSuite:
import TestDefns.*

object semiInstances:
given Eq[Foo] = semiauto.eq
given Eq[IList[Int]] = semiauto.eq
given Eq[Inner] = semiauto.eq
given Eq[Outer] = semiauto.eq
given Eq[Interleaved[Int]] = semiauto.eq
given Eq[Tree[Int]] = semiauto.eq
given Eq[Recursive] = semiauto.eq

end EqSuite
2 changes: 1 addition & 1 deletion core/src/test/scala-3/cats/derived/EqTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats.derived

import alleycats.*
import cats.*
import cats.derived.semiauto.*
import cats.derived.*

class EqTests { //
case class Foo(i: Int, b: Option[String]) derives Eq
Expand Down
50 changes: 50 additions & 0 deletions core/src/test/scala-3/cats/derived/HashSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cats.derived

import cats.Hash
import cats.kernel.laws.discipline.{HashTests, SerializableTests}

import scala.compiletime.*
import scala.util.hashing.MurmurHash3

class HashSuite extends KittensSuite:
import HashSuite.*
import TestDefns.*

inline def hashTests[A]: HashTests[A] =
HashTests[A](summonInline)

inline def testHash(inline context: String): Unit =
checkAll(s"$context.Hash[IList[Int]]", hashTests[IList[Int]].hash)
checkAll(s"$context.Hash[Inner]", hashTests[Inner].hash)
checkAll(s"$context.Hash[Outer]", hashTests[Outer].hash)
// FIXME: typelevel/cats#2878
// checkAll(s"$context.Hash[Interleaved[Int]]", hashTests[Interleaved[Int]].hash)
checkAll(s"$context.Hash[Tree[Int]]", hashTests[Tree[Int]].hash)
// FIXME: Doesn't work for recursive case classes.
// checkAll(s"$context.Hash[Recursive]", hashTests[Recursive].hash)
checkAll(s"$context.Hash is Serializable", SerializableTests.serializable(summonInline[Hash[Inner]]))

locally {
import auto.hash.given
testHash("auto")
}

locally {
import semiInstances.given
testHash("semiauto")
}

end HashSuite

object HashSuite:
import TestDefns.*

object semiInstances:
given Hash[IList[Int]] = semiauto.hash
given Hash[Inner] = semiauto.hash
given Hash[Outer] = semiauto.hash
given Hash[Interleaved[Int]] = semiauto.hash
given Hash[Tree[Int]] = semiauto.hash
given Hash[Recursive] = semiauto.hash

end HashSuite
2 changes: 1 addition & 1 deletion core/src/test/scala-3/cats/derived/HashTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats.derived

import alleycats.*
import cats.*
import cats.derived.semiauto.*
import cats.derived.*

class HashTests { //
case class Foo(i: Int, b: Option[String]) derives Hash
Expand Down
36 changes: 19 additions & 17 deletions core/src/test/scala-3/cats/derived/KittensSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,25 @@ import scala.quoted.*
/** An opinionated stack of traits to improve consistency and reduce boilerplate in Kittens tests. Note that unlike the
* corresponding CatsSuite in the Cat project, this trait does not mix in any instances.
*/
abstract class KittensSuite extends DisciplineSuite, AllSyntax, TestEqInstances:
override val scalaCheckTestParameters: Parameters = super.scalaCheckTestParameters
.withMinSuccessfulTests(if (Platform.isJvm) 50 else 5)
.withMaxDiscardRatio(if (Platform.isJvm) 5 else 50)
.withWorkers(if (Platform.isJvm) 2 else 1)
.withMaxSize(if (Platform.isJvm) 10 else 5)
.withMinSize(0)

given [A: Arbitrary]: Arbitrary[List[A]] =
Arbitrary.arbContainer

inline def nameOf[A]: String =
${ KittensSuite.nameOfMacro[A] }

inline def testNoInstance(inline code: String, message: String): Unit =
test(s"No $code")(assert(compileErrors(code).contains(message)))

abstract class KittensSuite extends KittensSuite.WithoutEq, TestEqInstances
object KittensSuite:
def nameOfMacro[A: Type](using Quotes) =
Expr(Type.show[A])

/** Used to test `Eq` derivation. */
abstract class WithoutEq extends DisciplineSuite, AllSyntax:
override val scalaCheckTestParameters: Parameters = super.scalaCheckTestParameters
.withMinSuccessfulTests(if (Platform.isJvm) 50 else 5)
.withMaxDiscardRatio(if (Platform.isJvm) 5 else 50)
.withWorkers(if (Platform.isJvm) 2 else 1)
.withMaxSize(if (Platform.isJvm) 10 else 5)
.withMinSize(0)

given [A: Arbitrary]: Arbitrary[List[A]] =
Arbitrary.arbContainer

inline def nameOf[A]: String =
${ KittensSuite.nameOfMacro[A] }

inline def testNoInstance(inline code: String, message: String): Unit =
test(s"No $code")(assert(compileErrors(code).contains(message)))
6 changes: 6 additions & 0 deletions core/src/test/scala-3/cats/derived/adtdefns.scala
Original file line number Diff line number Diff line change
Expand Up @@ -471,4 +471,10 @@ trait TestEqInstances {

implicit def eqCaseClassWOption[A: Eq]: Eq[CaseClassWOption[A]] =
Eq.by(_.value)

implicit val eqInner: Eq[Inner] =
Eq.fromUniversalEquals

implicit val eqOuter: Eq[Outer] =
Eq.fromUniversalEquals
}