diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eac7a29a..aad1c14a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.12.16, 2.13.8] + scala: [2.12.16, 2.13.8, 3.2.0] java: [temurin@8] project: [rootJS, rootJVM, rootNative] runs-on: ${{ matrix.os }} @@ -114,7 +114,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.8] + scala: [3.2.0] java: [temurin@8] runs-on: ${{ matrix.os }} steps: @@ -211,6 +211,36 @@ jobs: tar xf targets.tar rm targets.tar + - name: Download target directories (3.2.0, rootJS) + uses: actions/download-artifact@v2 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.0-rootJS + + - name: Inflate target directories (3.2.0, rootJS) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (3.2.0, rootJVM) + uses: actions/download-artifact@v2 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.0-rootJVM + + - name: Inflate target directories (3.2.0, rootJVM) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (3.2.0, rootNative) + uses: actions/download-artifact@v2 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.0-rootNative + + - name: Inflate target directories (3.2.0, rootNative) + run: | + tar xf targets.tar + rm targets.tar + - name: Import signing key if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == '' run: echo $PGP_SECRET | base64 -di | gpg --import diff --git a/.gitignore b/.gitignore index 84783bc4..0b5fc9b7 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,10 @@ metals.sbt .*.swp .swo .*.swo + +# VS Code +.bloop +.bsp +.metals +.vscode +**/metals.sbt diff --git a/.mergify.yml b/.mergify.yml index 105c08ad..740d7a57 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -18,6 +18,9 @@ pull_request_rules: - status-success=Build and Test (ubuntu-latest, 2.13.8, temurin@8, rootJS) - status-success=Build and Test (ubuntu-latest, 2.13.8, temurin@8, rootJVM) - status-success=Build and Test (ubuntu-latest, 2.13.8, temurin@8, rootNative) + - status-success=Build and Test (ubuntu-latest, 3.2.0, temurin@8, rootJS) + - status-success=Build and Test (ubuntu-latest, 3.2.0, temurin@8, rootJVM) + - status-success=Build and Test (ubuntu-latest, 3.2.0, temurin@8, rootNative) actions: merge: {} - name: Label core PRs diff --git a/.scalafmt.conf b/.scalafmt.conf index dc48739f..d7a245b6 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -10,4 +10,7 @@ rewrite.redundantBraces.stringInterpolation = true rewrite.redundantBraces.methodBodies = true rewrite.redundantBraces.generalExpressions = true rewriteTokens = { "⇒": "=>", "→": "->", "←": "<-" } -fileOverride { "glob:**/*.sbt" { runner.dialect = scala212 } } +fileOverride { + "glob:**/*.sbt" { runner.dialect = scala212 }, + "glob:**/scala-3/**" { runner.dialect = scala3 } +} diff --git a/README.md b/README.md index e5603936..67d529c1 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ libraryDependencies += "org.typelevel" %% "kittens" % "latestVersion" // indicat [![Scala.js](http://scala-js.org/assets/badges/scalajs-1.5.0.svg)](http://scala-js.org) [![Latest version](https://img.shields.io/maven-central/v/org.typelevel/kittens_2.12.svg)](https://maven-badges.herokuapp.com/maven-central/org.typelevel/kittens_2.12) +## Scala 2 + Instance derivations are available for the following type classes: * `Eq`, `PartialOrder`, `Order`, `Hash` @@ -210,7 +212,109 @@ implicit val showFoo: Show[Foo] = semiauto.show This way the native instance for `Show[List]` would be used. -### Type class support matrix +## Scala 3 + +We also offer 3 methods of derivation for Scala 3. All of them have the same behaviour wrt to recursively defining instances: +1. Instances will always be recursively instantiated if necessary +2. Subject to the same type constructor field limitation as the Scala 2 auto and manual semi derivations + +### `derives` syntax (recommended) + +Kittens for scala 3 supports Scala 3's [derivation syntax](https://docs.scala-lang.org/scala3/reference/contextual/derivation.html). + +``` scala +import cats.derived.* + +// No instances declared for Name +case class Name(value: String) +case class Person(name: Name, age: Int) derives Eq, Show + +enum CList[+A] derives Functor: + case CNil + case CCons(head: A, tail: CList[A]) +``` + +### semiauto derivation + +This looks similar to `semiauto` for Scala 2. + +``` scala +import cats.derived.semiauto + +// No instances declared for Name +case class Name(value: String) +case class Person(name: Name, age: Int) + +object Person: + given Eq[Person] = semiauto.eq + given Show[Person] = semiauto.show + +enum CList[+A]: + case CNil + case CCons(head: A, tail: CList[A]) + +object CList: + given Functor[CList] = semiauto.functor +``` + +As with Scala 2, you can combine `auto` and `semiauto` to avoid the type constructor field limitation: + +``` scala +import cats.derived.* + +case class Name(value: String) +case class Person(name: Name, age: Int) + +case class People(people: List[Person]) +object People: + given Show[People] = + import auto.show.given + // Uses the correct List instance despite deriving an instance for Person automatically + semiauto.show +``` + +` + +### auto derivation + +This looks similar to `auto` for Scala 2. + +``` scala +import cats.derived.auto.eq.given +import cats.derived.auto.show.given +import cats.derived.auto.functor.given + +case class Name(value: String) +case class Person(name: Name, age: Int) + +enum CList[+A]: + case CNil + case CCons(head: A, tail: CList[A]) +``` + +### Caveats + +#### Nested type constructors + +We are [currently](https://github.com/typelevel/kittens/issues/473) unable to +derive instances for nested type constructors, such as `Functor[[x] =>> +List[Set[x]]]`. + +#### Stack safety + +Our derived instances are not stack-safe. This is a departure from the behaviour for Scala 2 because we didn't want to incur the performance penalty of trampolining all instances in `cats.Eval`. If your data-type is recursive or _extremely_ large then you may want to write instances by hand instead. + +#### Missing features + +Kittens for Scala 3 is built on top of [Shapeless +3](https://github.com/typelevel/shapeless-3) which has a completely different +API than [Shapeless 2](https://github.com/milessabin/shapeless) so we don't +support features like `Sequence` and `Lift`. + +`ConsK` derivation is also not supported although we expect this to be +[added](https://github.com/typelevel/kittens/issues/489) in a future release. + +## Type class support matrix Legend: - `∀` - all must satisfy a constraint @@ -277,4 +381,5 @@ Kittens is built with SBT 1.x, and its master branch is built with Scala 2.13 by + Miles Sabin [@milessabin](https://twitter.com/milessabin) + Qi Wang [Qi77Qi](http://github.com/Qi77Qi) + Kailuo Wang [@kailuowang](https://twitter.com/kailuowang) ++ Tim Spence [timwspence](https://twitter.com/timwspence) + Your name here :-) diff --git a/build.sbt b/build.sbt index cb26b5e0..e0c2de66 100644 --- a/build.sbt +++ b/build.sbt @@ -1,17 +1,23 @@ import sbt._ -ThisBuild / crossScalaVersions := Seq("2.12.16", "2.13.8") -ThisBuild / scalaVersion := "2.13.8" -ThisBuild / tlBaseVersion := "2.3" +val scala212 = "2.12.16" +val scala213 = "2.13.8" +val scala3 = "3.2.0" + +ThisBuild / crossScalaVersions := Seq(scala212, scala213, scala3) +ThisBuild / scalaVersion := scala3 +ThisBuild / tlBaseVersion := "3.0" ThisBuild / organization := "org.typelevel" val catsVersion = "2.8.0" -val disciplineMunitVersion = "1.0.9" +val munitVersion = "1.0.0-M6" +val disciplineMunitVersion = "2.0.0-M3" val kindProjectorVersion = "0.13.2" -val shapelessVersion = "2.3.9" +val shapeless2Version = "2.3.8" +val shapeless3Version = "3.1.0" lazy val commonSettings = Seq( - scalacOptions := Seq( + scalacOptions ++= Seq( "-feature", "-language:higherKinds", "-language:implicitConversions", @@ -19,24 +25,30 @@ lazy val commonSettings = Seq( "-deprecation", "-Xfatal-warnings" ), - scalacOptions ++= ( - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, v)) if v <= 12 => Seq("-Ypartial-unification") - case _ => Seq.empty - } - ), + scalacOptions ++= CrossVersion.partialVersion(scalaVersion.value).toList.flatMap { + case (3, _) => List("-Xmax-inlines", "64") + case (2, 12) => List("-Ypartial-unification") + case _ => Nil + }, resolvers ++= Resolver.sonatypeOssRepos("releases"), resolvers ++= Resolver.sonatypeOssRepos("snapshots"), libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsVersion, "org.typelevel" %%% "alleycats-core" % catsVersion, - "com.chuusai" %%% "shapeless" % shapelessVersion, "org.typelevel" %%% "cats-testkit" % catsVersion % Test, - "org.scalameta" %%% "munit" % "0.7.29" % Test, "org.typelevel" %%% "discipline-munit" % disciplineMunitVersion % Test, - "org.scala-lang" % "scala-reflect" % scalaVersion.value % Test, - compilerPlugin(("org.typelevel" %% "kind-projector" % kindProjectorVersion).cross(CrossVersion.full)) + "org.scalameta" %%% "munit" % munitVersion % Test ), + libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match { + case Some((3, _)) => + Seq("org.typelevel" %%% "shapeless3-deriving" % shapeless3Version) + case _ => + Seq( + "com.chuusai" %%% "shapeless" % shapeless2Version, + "org.scala-lang" % "scala-reflect" % scalaVersion.value % Test, + compilerPlugin(("org.typelevel" %% "kind-projector" % kindProjectorVersion).cross(CrossVersion.full)) + ) + }), Test / parallelExecution := false ) @@ -74,9 +86,11 @@ ThisBuild / licenses := Seq(License.Apache2) ThisBuild / developers := List( Developer("milessabin", "Miles Sabin", "", url("http://milessabin.com/blog")), Developer("kailuowang", "Kai(luo) Wang", "kailuo.wang@gmail.com", url("http://kailuowang.com/")), - Developer("joroKr21", "Georgi Krastev", "joro.kr.21@gmail.com", url("https://twitter.com/Joro_Kr")) + Developer("joroKr21", "Georgi Krastev", "joro.kr.21@gmail.com", url("https://twitter.com/Joro_Kr")), + Developer("TimWSpence", "Tim Spence", "timothywspence@gmail.com", url("https://twitter.com/timwspence")) ) +ThisBuild / tlCiScalafmtCheck := true ThisBuild / tlCiReleaseBranches := Seq("master") ThisBuild / mergifyStewardConfig := Some( MergifyStewardConfig( @@ -84,4 +98,3 @@ ThisBuild / mergifyStewardConfig := Some( mergeMinors = true ) ) -ThisBuild / tlCiScalafmtCheck := true diff --git a/core/src/main/scala/cats/derived/applicative.scala b/core/src/main/scala-2/cats/derived/applicative.scala similarity index 100% rename from core/src/main/scala/cats/derived/applicative.scala rename to core/src/main/scala-2/cats/derived/applicative.scala diff --git a/core/src/main/scala/cats/derived/apply.scala b/core/src/main/scala-2/cats/derived/apply.scala similarity index 100% rename from core/src/main/scala/cats/derived/apply.scala rename to core/src/main/scala-2/cats/derived/apply.scala diff --git a/core/src/main/scala/cats/derived/commutativeMonoid.scala b/core/src/main/scala-2/cats/derived/commutativeMonoid.scala similarity index 100% rename from core/src/main/scala/cats/derived/commutativeMonoid.scala rename to core/src/main/scala-2/cats/derived/commutativeMonoid.scala diff --git a/core/src/main/scala/cats/derived/commutativeSemigroup.scala b/core/src/main/scala-2/cats/derived/commutativeSemigroup.scala similarity index 100% rename from core/src/main/scala/cats/derived/commutativeSemigroup.scala rename to core/src/main/scala-2/cats/derived/commutativeSemigroup.scala diff --git a/core/src/main/scala/cats/derived/consk.scala b/core/src/main/scala-2/cats/derived/consk.scala similarity index 100% rename from core/src/main/scala/cats/derived/consk.scala rename to core/src/main/scala-2/cats/derived/consk.scala diff --git a/core/src/main/scala/cats/derived/contravariant.scala b/core/src/main/scala-2/cats/derived/contravariant.scala similarity index 100% rename from core/src/main/scala/cats/derived/contravariant.scala rename to core/src/main/scala-2/cats/derived/contravariant.scala diff --git a/core/src/main/scala/cats/derived/empty.scala b/core/src/main/scala-2/cats/derived/empty.scala similarity index 100% rename from core/src/main/scala/cats/derived/empty.scala rename to core/src/main/scala-2/cats/derived/empty.scala diff --git a/core/src/main/scala/cats/derived/emptyk.scala b/core/src/main/scala-2/cats/derived/emptyk.scala similarity index 100% rename from core/src/main/scala/cats/derived/emptyk.scala rename to core/src/main/scala-2/cats/derived/emptyk.scala diff --git a/core/src/main/scala/cats/derived/eq.scala b/core/src/main/scala-2/cats/derived/eq.scala similarity index 100% rename from core/src/main/scala/cats/derived/eq.scala rename to core/src/main/scala-2/cats/derived/eq.scala diff --git a/core/src/main/scala/cats/derived/foldable.scala b/core/src/main/scala-2/cats/derived/foldable.scala similarity index 100% rename from core/src/main/scala/cats/derived/foldable.scala rename to core/src/main/scala-2/cats/derived/foldable.scala diff --git a/core/src/main/scala/cats/derived/function.scala b/core/src/main/scala-2/cats/derived/function.scala similarity index 100% rename from core/src/main/scala/cats/derived/function.scala rename to core/src/main/scala-2/cats/derived/function.scala diff --git a/core/src/main/scala/cats/derived/functor.scala b/core/src/main/scala-2/cats/derived/functor.scala similarity index 100% rename from core/src/main/scala/cats/derived/functor.scala rename to core/src/main/scala-2/cats/derived/functor.scala diff --git a/core/src/main/scala/cats/derived/hash.scala b/core/src/main/scala-2/cats/derived/hash.scala similarity index 100% rename from core/src/main/scala/cats/derived/hash.scala rename to core/src/main/scala-2/cats/derived/hash.scala diff --git a/core/src/main/scala/cats/derived/invariant.scala b/core/src/main/scala-2/cats/derived/invariant.scala similarity index 100% rename from core/src/main/scala/cats/derived/invariant.scala rename to core/src/main/scala-2/cats/derived/invariant.scala diff --git a/core/src/main/scala/cats/derived/iterable.scala b/core/src/main/scala-2/cats/derived/iterable.scala similarity index 100% rename from core/src/main/scala/cats/derived/iterable.scala rename to core/src/main/scala-2/cats/derived/iterable.scala diff --git a/core/src/main/scala/cats/derived/monoid.scala b/core/src/main/scala-2/cats/derived/monoid.scala similarity index 100% rename from core/src/main/scala/cats/derived/monoid.scala rename to core/src/main/scala-2/cats/derived/monoid.scala diff --git a/core/src/main/scala/cats/derived/monoidk.scala b/core/src/main/scala-2/cats/derived/monoidk.scala similarity index 100% rename from core/src/main/scala/cats/derived/monoidk.scala rename to core/src/main/scala-2/cats/derived/monoidk.scala diff --git a/core/src/main/scala/cats/derived/nonEmptyTraverse.scala b/core/src/main/scala-2/cats/derived/nonEmptyTraverse.scala similarity index 100% rename from core/src/main/scala/cats/derived/nonEmptyTraverse.scala rename to core/src/main/scala-2/cats/derived/nonEmptyTraverse.scala diff --git a/core/src/main/scala/cats/derived/order.scala b/core/src/main/scala-2/cats/derived/order.scala similarity index 100% rename from core/src/main/scala/cats/derived/order.scala rename to core/src/main/scala-2/cats/derived/order.scala diff --git a/core/src/main/scala/cats/derived/package.scala b/core/src/main/scala-2/cats/derived/package.scala similarity index 100% rename from core/src/main/scala/cats/derived/package.scala rename to core/src/main/scala-2/cats/derived/package.scala diff --git a/core/src/main/scala/cats/derived/partialOrder.scala b/core/src/main/scala-2/cats/derived/partialOrder.scala similarity index 100% rename from core/src/main/scala/cats/derived/partialOrder.scala rename to core/src/main/scala-2/cats/derived/partialOrder.scala diff --git a/core/src/main/scala/cats/derived/pure.scala b/core/src/main/scala-2/cats/derived/pure.scala similarity index 100% rename from core/src/main/scala/cats/derived/pure.scala rename to core/src/main/scala-2/cats/derived/pure.scala diff --git a/core/src/main/scala/cats/derived/reducible.scala b/core/src/main/scala-2/cats/derived/reducible.scala similarity index 100% rename from core/src/main/scala/cats/derived/reducible.scala rename to core/src/main/scala-2/cats/derived/reducible.scala diff --git a/core/src/main/scala/cats/derived/semigroup.scala b/core/src/main/scala-2/cats/derived/semigroup.scala similarity index 100% rename from core/src/main/scala/cats/derived/semigroup.scala rename to core/src/main/scala-2/cats/derived/semigroup.scala diff --git a/core/src/main/scala/cats/derived/semigroupk.scala b/core/src/main/scala-2/cats/derived/semigroupk.scala similarity index 100% rename from core/src/main/scala/cats/derived/semigroupk.scala rename to core/src/main/scala-2/cats/derived/semigroupk.scala diff --git a/core/src/main/scala/cats/derived/show.scala b/core/src/main/scala-2/cats/derived/show.scala similarity index 100% rename from core/src/main/scala/cats/derived/show.scala rename to core/src/main/scala-2/cats/derived/show.scala diff --git a/core/src/main/scala/cats/derived/showPretty.scala b/core/src/main/scala-2/cats/derived/showPretty.scala similarity index 100% rename from core/src/main/scala/cats/derived/showPretty.scala rename to core/src/main/scala-2/cats/derived/showPretty.scala diff --git a/core/src/main/scala/cats/derived/traverse.scala b/core/src/main/scala-2/cats/derived/traverse.scala similarity index 100% rename from core/src/main/scala/cats/derived/traverse.scala rename to core/src/main/scala-2/cats/derived/traverse.scala diff --git a/core/src/main/scala/cats/derived/trivial.scala b/core/src/main/scala-2/cats/derived/trivial.scala similarity index 100% rename from core/src/main/scala/cats/derived/trivial.scala rename to core/src/main/scala-2/cats/derived/trivial.scala diff --git a/core/src/main/scala/cats/derived/util/fnGeneric.scala b/core/src/main/scala-2/cats/derived/util/fnGeneric.scala similarity index 100% rename from core/src/main/scala/cats/derived/util/fnGeneric.scala rename to core/src/main/scala-2/cats/derived/util/fnGeneric.scala diff --git a/core/src/main/scala/cats/derived/util/liftSome.scala b/core/src/main/scala-2/cats/derived/util/liftSome.scala similarity index 100% rename from core/src/main/scala/cats/derived/util/liftSome.scala rename to core/src/main/scala-2/cats/derived/util/liftSome.scala diff --git a/core/src/main/scala/cats/lift/lift.scala b/core/src/main/scala-2/cats/lift/lift.scala similarity index 100% rename from core/src/main/scala/cats/lift/lift.scala rename to core/src/main/scala-2/cats/lift/lift.scala diff --git a/core/src/main/scala/cats/lift/package.scala b/core/src/main/scala-2/cats/lift/package.scala similarity index 100% rename from core/src/main/scala/cats/lift/package.scala rename to core/src/main/scala-2/cats/lift/package.scala diff --git a/core/src/main/scala/cats/replicateH/package.scala b/core/src/main/scala-2/cats/replicateH/package.scala similarity index 100% rename from core/src/main/scala/cats/replicateH/package.scala rename to core/src/main/scala-2/cats/replicateH/package.scala diff --git a/core/src/main/scala/cats/replicateH/replicateH.scala b/core/src/main/scala-2/cats/replicateH/replicateH.scala similarity index 100% rename from core/src/main/scala/cats/replicateH/replicateH.scala rename to core/src/main/scala-2/cats/replicateH/replicateH.scala diff --git a/core/src/main/scala/cats/sequence/package.scala b/core/src/main/scala-2/cats/sequence/package.scala similarity index 100% rename from core/src/main/scala/cats/sequence/package.scala rename to core/src/main/scala-2/cats/sequence/package.scala diff --git a/core/src/main/scala/cats/sequence/sequence.scala b/core/src/main/scala-2/cats/sequence/sequence.scala similarity index 100% rename from core/src/main/scala/cats/sequence/sequence.scala rename to core/src/main/scala-2/cats/sequence/sequence.scala diff --git a/core/src/main/scala/cats/sequence/traverse.scala b/core/src/main/scala-2/cats/sequence/traverse.scala similarity index 100% rename from core/src/main/scala/cats/sequence/traverse.scala rename to core/src/main/scala-2/cats/sequence/traverse.scala diff --git a/core/src/main/scala-3/cats/derived/Derived.scala b/core/src/main/scala-3/cats/derived/Derived.scala new file mode 100644 index 00000000..bff69f23 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/Derived.scala @@ -0,0 +1,36 @@ +package cats.derived + +import shapeless3.deriving.* +import scala.annotation.* +import scala.compiletime.* + +@implicitNotFound("Could not derive an instance of ${A}") +opaque type Derived[A] = A +object Derived: + def apply[A](instance: A): Derived[A] = instance + extension [A](derived: Derived[A]) def instance: A = derived + given [A]: Conversion[A, Derived[A]] = apply + + type Or0[F[_]] = [x] =>> Or[F[x]] + type Or1[F[_[_]]] = [x[_]] =>> Or[F[x]] + type Or11[F[_[_[_]]]] = [x[_[_]]] =>> Or[F[x]] + type Or2[F[_[_, _]]] = [x[_, _]] =>> Or[F[x]] + + opaque type Or[A] = A + object Or extends OrInstances: + def apply[A](instance: A): Or[A] = instance + extension [A](or: Or[A]) def unify: A = or + extension [I[f[_], t] <: K0.Instances[f, t], F[_], T](inst: I[Or0[F], T]) + @targetName("unifyK0") def unify: I[F, T] = inst + extension [I[f[_[_]], t[_]] <: K1.Instances[f, t], F[_[_]], T[_]](inst: I[Or1[F], T]) + @targetName("unifyK1") def unify: I[F, T] = inst + extension [I[f[_[_[_]]], t[_[_]]] <: K11.Instances[f, t], F[_[_[_]]], T[_[_]]](inst: I[Or11[F], T]) + @targetName("unifyK11") def unify: I[F, T] = inst + extension [I[f[_[_, _]], t[_, _]] <: K2.Instances[f, t], F[_[_, _]], T[_, _]](inst: I[Or2[F], T]) + @targetName("unifyK2") def unify: I[F, T] = inst + +sealed abstract class OrInstances: + inline given [A]: Derived.Or[A] = summonFrom { + case instance: A => Derived.Or(instance) + case derived: Derived[A] => Derived.Or(derived.instance) + } diff --git a/core/src/main/scala-3/cats/derived/DerivedApplicative.scala b/core/src/main/scala-3/cats/derived/DerivedApplicative.scala new file mode 100644 index 00000000..45c05fa5 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedApplicative.scala @@ -0,0 +1,39 @@ +package cats.derived + +import shapeless3.deriving.{Const, K1} +import cats.{Applicative, Monoid} + +import scala.compiletime.* +import shapeless3.deriving.{Continue, K0, Labelling} + +import scala.annotation.implicitNotFound +import scala.deriving.Mirror + +@implicitNotFound("""Could not derive an instance of Applicative[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T where T: Monoid + * it is a nested type [x] =>> G[H[x]] where G: Applicative and H: Applicative + * it is a generic case class where all fields have an Applicative instance""") +type DerivedApplicative[F[_]] = Derived[Applicative[F]] +object DerivedApplicative: + type Or[F[_]] = Derived.Or[Applicative[F]] + + inline def apply[F[_]]: Applicative[F] = + import DerivedApplicative.given + summonInline[DerivedApplicative[F]].instance + + given [T](using T: Monoid[T]): DerivedApplicative[Const[T]] = new Applicative[Const[T]]: + def pure[A](x: A): Const[T][A] = T.empty + def ap[A, B](ff: T)(fa: T): Const[T][B] = T.combine(ff, fa) + + given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedApplicative[[x] =>> F[G[x]]] = + F.unify.compose(G.unify) + + given [F[_]](using inst: => K1.ProductInstances[Or, F]): DerivedApplicative[F] = + given K1.ProductInstances[Applicative, F] = inst.unify + new Product[Applicative, F] with DerivedApply.Product[Applicative, F] {} + + trait Product[T[x[_]] <: Applicative[x], F[_]](using inst: K1.ProductInstances[T, F]) + extends Applicative[F], + DerivedApply.Product[T, F]: + override def pure[A](x: A): F[A] = inst.construct([t[_]] => (apl: T[t]) => apl.pure[A](x)) diff --git a/core/src/main/scala-3/cats/derived/DerivedApply.scala b/core/src/main/scala-3/cats/derived/DerivedApply.scala new file mode 100644 index 00000000..31f4b56b --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedApply.scala @@ -0,0 +1,40 @@ +package cats.derived + +import shapeless3.deriving.{Const, K1} +import cats.{Apply, Semigroup} + +import scala.compiletime.* +import shapeless3.deriving.{Continue, K0, Labelling} + +import scala.annotation.implicitNotFound +import scala.deriving.Mirror + +@implicitNotFound("""Could not derive an instance of Apply[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T where T: Semigroup + * it is a nested type [x] =>> G[H[x]] where G: Apply and H: Apply + * it is a generic case class where all fields have an Apply instance""") +type DerivedApply[F[_]] = Derived[Apply[F]] +object DerivedApply: + type Or[F[_]] = Derived.Or[Apply[F]] + + inline def apply[F[_]]: Apply[F] = + import DerivedApply.given + summonInline[DerivedApply[F]].instance + + given [T](using T: Semigroup[T]): DerivedApply[Const[T]] = new Apply[Const[T]]: + def ap[A, B](ff: T)(fa: T) = T.combine(ff, fa) + def map[A, B](fa: T)(f: A => B) = fa + + given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedApply[[x] =>> F[G[x]]] = + F.unify.compose(G.unify) + + given [F[_]](using inst: => K1.ProductInstances[Or, F]): DerivedApply[F] = + given K1.ProductInstances[Apply, F] = inst.unify + new Product[Apply, F] {} + + trait Product[T[x[_]] <: Apply[x], F[_]](using inst: K1.ProductInstances[T, F]) extends Apply[F]: + override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = + inst.map2(ff, fa)([t[_]] => (apl: T[t], tt: t[A => B], ta: t[A]) => apl.ap(tt)(ta)) + override def map[A, B](fa: F[A])(f: A => B): F[B] = + inst.map(fa: F[A])([f[_]] => (tf: T[f], fa: f[A]) => tf.map(fa)(f)) diff --git a/core/src/main/scala-3/cats/derived/DerivedCommutativeMonoid.scala b/core/src/main/scala-3/cats/derived/DerivedCommutativeMonoid.scala new file mode 100644 index 00000000..973dcf42 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedCommutativeMonoid.scala @@ -0,0 +1,24 @@ +package cats.derived + +import cats.kernel.CommutativeMonoid +import shapeless3.deriving.K0 + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of CommutativeMonoid[A] where A = ${A}. +Make sure that A is a case class where all fields have a CommutativeMonoid instance.""") +type DerivedCommutativeMonoid[A] = Derived[CommutativeMonoid[A]] +object DerivedCommutativeMonoid: + type Or[A] = Derived.Or[CommutativeMonoid[A]] + inline def apply[A]: CommutativeMonoid[A] = + import DerivedCommutativeMonoid.given + summonInline[DerivedCommutativeMonoid[A]].instance + + given [A](using inst: => K0.ProductInstances[Or, A]): DerivedCommutativeMonoid[A] = + given K0.ProductInstances[CommutativeMonoid, A] = inst.unify + new Product[CommutativeMonoid, A] {} + + trait Product[F[x] <: CommutativeMonoid[x], A](using inst: K0.ProductInstances[F, A]) + extends DerivedMonoid.Product[F, A], + CommutativeMonoid[A] diff --git a/core/src/main/scala-3/cats/derived/DerivedCommutativeSemigroup.scala b/core/src/main/scala-3/cats/derived/DerivedCommutativeSemigroup.scala new file mode 100644 index 00000000..1eba6307 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedCommutativeSemigroup.scala @@ -0,0 +1,24 @@ +package cats.derived + +import cats.kernel.CommutativeSemigroup +import shapeless3.deriving.K0 + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of CommutativeSemigroup[A] where A = ${A}. +Make sure that A is a case class where all fields have a CommutativeSemigroup instance.""") +type DerivedCommutativeSemigroup[A] = Derived[CommutativeSemigroup[A]] +object DerivedCommutativeSemigroup: + type Or[A] = Derived.Or[CommutativeSemigroup[A]] + inline def apply[A]: CommutativeSemigroup[A] = + import DerivedCommutativeSemigroup.given + summonInline[DerivedCommutativeSemigroup[A]].instance + + given [A](using inst: => K0.ProductInstances[Or, A]): DerivedCommutativeSemigroup[A] = + given K0.ProductInstances[CommutativeSemigroup, A] = inst.unify + new Product[CommutativeSemigroup, A] {} + + trait Product[F[x] <: CommutativeSemigroup[x], A](using inst: K0.ProductInstances[F, A]) + extends DerivedSemigroup.Product[F, A], + CommutativeSemigroup[A] diff --git a/core/src/main/scala-3/cats/derived/DerivedContravariant.scala b/core/src/main/scala-3/cats/derived/DerivedContravariant.scala new file mode 100644 index 00000000..b3cec8e8 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedContravariant.scala @@ -0,0 +1,35 @@ +package cats.derived + +import cats.{Contravariant, Functor} +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Contravariant[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T + * it is a nested type [x] =>> G[H[x]] where G: Functor and H: Contravariant + * it is a generic case class where all fields have a Contravariant instance + * it is a generic sealed trait where all subclasses have a Contravariant instance""") +type DerivedContravariant[F[_]] = Derived[Contravariant[F]] +object DerivedContravariant: + type Or[F[_]] = Derived.Or[Contravariant[F]] + inline def apply[F[_]]: Contravariant[F] = + import DerivedContravariant.given + summonInline[DerivedContravariant[F]].instance + + given [T]: DerivedContravariant[Const[T]] = new Contravariant[Const[T]]: + def contramap[A, B](fa: T)(f: B => A): T = fa + + given [F[_], G[_]](using F: DerivedFunctor.Or[F], G: Or[G]): DerivedContravariant[[x] =>> F[G[x]]] = + given Contravariant[G] = G.unify + F.unify.composeContravariant[G] + + given [F[_]](using inst: => K1.Instances[Or, F]): DerivedContravariant[F] = + given K1.Instances[Contravariant, F] = inst.unify + new Generic[Contravariant, F] {} + + trait Generic[T[x[_]] <: Contravariant[x], F[_]](using inst: K1.Instances[T, F]) extends Contravariant[F]: + final override def contramap[A, B](fa: F[A])(f: B => A): F[B] = + inst.map(fa: F[A])([f[_]] => (tf: T[f], fa: f[A]) => tf.contramap(fa)(f)) diff --git a/core/src/main/scala-3/cats/derived/DerivedEmpty.scala b/core/src/main/scala-3/cats/derived/DerivedEmpty.scala new file mode 100644 index 00000000..c9681ccb --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedEmpty.scala @@ -0,0 +1,25 @@ +package cats.derived + +import alleycats.Empty +import cats.derived.util.Kinds +import shapeless3.deriving.K0 + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Empty[A] where A = ${A}. +Make sure that A satisfies one of the following conditions: + * it is a case class where all fields have an Empty instance + * it is a sealed trait where exactly one subclass has an Empty instance""") +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 + + given product[A](using inst: K0.ProductInstances[Or, A]): DerivedEmpty[A] = + Empty(inst.unify.construct([A] => (A: Empty[A]) => A.empty)) + + inline given coproduct[A](using gen: K0.CoproductGeneric[A]): DerivedEmpty[A] = + Kinds.summonOne0[Or, gen.MirroredElemTypes, A].unify diff --git a/core/src/main/scala-3/cats/derived/DerivedEmptyK.scala b/core/src/main/scala-3/cats/derived/DerivedEmptyK.scala new file mode 100644 index 00000000..42bcd800 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedEmptyK.scala @@ -0,0 +1,42 @@ +package cats.derived + +import alleycats.{Empty, EmptyK, Pure} +import cats.derived.util.Kinds +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.summonInline +import scala.util.NotGiven + +@implicitNotFound("""Could not derive an instance of EmptyK[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T where T: Empty + * it is a nested type [x] =>> G[H[x]] where G: EmptyK + * it is a nested type [x] =>> G[H[x]] where G: Pure and H: EmptyK + * it is a generic case class where all fields have an EmptyK instance + * it is a generic sealed trait where exactly one subclass has an EmptyK instance""") +type DerivedEmptyK[F[_]] = Derived[EmptyK[F]] +object DerivedEmptyK: + type Or[F[_]] = Derived.Or[EmptyK[F]] + inline def apply[F[_]]: EmptyK[F] = + import DerivedEmptyK.given + summonInline[DerivedEmptyK[F]].instance + + given [T](using T: Empty[T]): DerivedEmptyK[Const[T]] = + new EmptyK[Const[T]]: + def empty[A] = T.empty + + given [F[_], G[_]](using F: Or[F]): DerivedEmptyK[[x] =>> F[G[x]]] = + new EmptyK[[x] =>> F[G[x]]]: + def empty[A] = F.unify.empty + + given [F[_], G[_]](using NotGiven[Or[F]])(using F: DerivedPure.Or[F], G: Or[G]): DerivedEmptyK[[x] =>> F[G[x]]] = + new EmptyK[[x] =>> F[G[x]]]: + def empty[A] = F.unify.pure(G.unify.empty) + + given product[F[_]](using inst: K1.ProductInstances[Or, F]): DerivedEmptyK[F] = + new EmptyK[F]: + def empty[A]: F[A] = inst.unify.construct([f[_]] => (E: EmptyK[f]) => E.empty[A]) + + inline given coproduct[F[_]](using gen: K1.CoproductGeneric[F]): DerivedEmptyK[F] = + Kinds.summonOne1[Or, gen.MirroredElemTypes, F].unify diff --git a/core/src/main/scala-3/cats/derived/DerivedEq.scala b/core/src/main/scala-3/cats/derived/DerivedEq.scala new file mode 100644 index 00000000..85ebab9c --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedEq.scala @@ -0,0 +1,36 @@ +package cats.derived + +import cats.Eq +import shapeless3.deriving.{Complete, K0} + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Eq[A] where A = ${A}. +Make sure that A satisfies one of the following conditions: + * it is a case class where all fields have an Eq instance + * it is a sealed trait where all subclasses have an Eq instance""") +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 product[A](using inst: => K0.ProductInstances[Or, A]): DerivedEq[A] = + given K0.ProductInstances[Eq, A] = inst.unify + new Product[Eq, A] {} + + given coproduct[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]: + final override 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]: + final override def eqv(x: A, y: A): Boolean = inst.fold2(x, y)(false)( + [t] => (eqt: F[t], x: t, y: t) => eqt.eqv(x, y) + ) diff --git a/core/src/main/scala-3/cats/derived/DerivedFoldable.scala b/core/src/main/scala-3/cats/derived/DerivedFoldable.scala new file mode 100644 index 00000000..fc3a9de2 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedFoldable.scala @@ -0,0 +1,51 @@ +package cats.derived + +import cats.{Eval, Foldable} +import shapeless3.deriving.{Const, Continue, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Foldable[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T + * it is a nested type [x] =>> G[H[x]] where G: Foldable and H: Foldable + * it is a generic case class where all fields have a Foldable instance + * it is a generic sealed trait where all subclasses have a Foldable instance""") +type DerivedFoldable[F[_]] = Derived[Foldable[F]] +object DerivedFoldable: + type Or[F[_]] = Derived.Or[Foldable[F]] + inline def apply[F[_]]: Foldable[F] = + import DerivedFoldable.given + summonInline[DerivedFoldable[F]].instance + + given [T]: DerivedFoldable[Const[T]] = new Foldable[Const[T]]: + def foldLeft[A, B](fa: T, b: B)(f: (B, A) => B): B = b + def foldRight[A, B](fa: T, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = lb + + given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedFoldable[[x] =>> F[G[x]]] = + F.unify.compose(G.unify) + + given [F[_]](using inst: K1.ProductInstances[Or, F]): DerivedFoldable[F] = + given K1.ProductInstances[Foldable, F] = inst.unify + new Product[Foldable, F] {} + + given [F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedFoldable[F] = + given K1.CoproductInstances[Foldable, F] = inst.unify + new Coproduct[Foldable, F] {} + + trait Product[T[x[_]] <: Foldable[x], F[_]](using inst: K1.ProductInstances[T, F]) extends Foldable[F]: + final override def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B = + inst.foldLeft[A, B](fa)(b)([f[_]] => (acc: B, tf: T[f], fa: f[A]) => Continue(tf.foldLeft(fa, acc)(f))) + + final override def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + inst.foldRight[A, Eval[B]](fa)(lb)( + [f[_]] => (tf: T[f], fa: f[A], acc: Eval[B]) => Continue(Eval.defer(tf.foldRight(fa, acc)(f))) + ) + + trait Coproduct[T[x[_]] <: Foldable[x], F[_]](using inst: K1.CoproductInstances[T, F]) extends Foldable[F]: + final override def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B = + inst.fold[A, B](fa)([f[_]] => (tf: T[f], fa: f[A]) => tf.foldLeft(fa, b)(f)) + + final override def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + inst.fold[A, Eval[B]](fa)([f[_]] => (tf: T[f], fa: f[A]) => Eval.defer(tf.foldRight(fa, lb)(f))) diff --git a/core/src/main/scala-3/cats/derived/DerivedFunctor.scala b/core/src/main/scala-3/cats/derived/DerivedFunctor.scala new file mode 100644 index 00000000..07c0ecae --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedFunctor.scala @@ -0,0 +1,42 @@ +package cats.derived + +import cats.{Contravariant, Functor} +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Functor[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T + * it is a nested type [x] =>> G[H[x]] where G: Functor and H: Functor + * it is a nested type [x] =>> G[H[x]] where G: Contravariant and H: Contravariant + * it is a generic case class where all fields have a Functor instance + * it is a generic sealed trait where all subclasses have a Functor instance""") +type DerivedFunctor[F[_]] = Derived[Functor[F]] +object DerivedFunctor: + type Or[F[_]] = Derived.Or[Functor[F]] + inline def apply[F[_]]: Functor[F] = + import DerivedFunctor.given + summonInline[DerivedFunctor[F]].instance + + given [T]: DerivedFunctor[Const[T]] = new Functor[Const[T]]: + def map[A, B](fa: T)(f: A => B): T = fa + + given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedFunctor[[x] =>> F[G[x]]] = + F.unify.compose(G.unify) + + given [F[_], G[_]](using + F: DerivedContravariant.Or[F], + G: DerivedContravariant.Or[G] + ): DerivedFunctor[[x] =>> F[G[x]]] = + given Contravariant[G] = G.unify + F.unify.compose[G] + + given [F[_]](using inst: => K1.Instances[Or, F]): DerivedFunctor[F] = + given K1.Instances[Functor, F] = inst.unify + new Generic[Functor, F] {} + + trait Generic[T[x[_]] <: Functor[x], F[_]](using inst: K1.Instances[T, F]) extends Functor[F]: + final override def map[A, B](fa: F[A])(f: A => B): F[B] = + inst.map(fa: F[A])([f[_]] => (tf: T[f], fa: f[A]) => tf.map(fa)(f)) diff --git a/core/src/main/scala-3/cats/derived/DerivedHash.scala b/core/src/main/scala-3/cats/derived/DerivedHash.scala new file mode 100644 index 00000000..356417d9 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedHash.scala @@ -0,0 +1,50 @@ +package cats.derived + +import cats.Hash +import shapeless3.deriving.{K0, Continue} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.util.hashing.MurmurHash3 + +@implicitNotFound("""Could not derive an instance of Hash[A] where A = ${A}. +Make sure that A satisfies one of the following conditions: + * it is a case class where all fields have a Hash instance + * it is a sealed trait where all subclasses have a Hash instance""") +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 product[A <: scala.Product](using inst: => K0.ProductInstances[Or, A]): DerivedHash[A] = + given K0.ProductInstances[Hash, A] = inst.unify + new Product[Hash, A] {} + + given coproduct[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]: + + final override 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]: + + final override def hash(x: A): Int = + inst.fold[Int](x)([t] => (h: F[t], x: t) => h.hash(x)) diff --git a/core/src/main/scala-3/cats/derived/DerivedInvariant.scala b/core/src/main/scala-3/cats/derived/DerivedInvariant.scala new file mode 100644 index 00000000..41b1be98 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedInvariant.scala @@ -0,0 +1,37 @@ +package cats.derived + +import cats.{Contravariant, Functor, Invariant} +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.util.NotGiven + +@implicitNotFound("""Could not derive an instance of Invariant[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] => T + * it is a nested type [x] => G[H[x]] where G: Invariant and H: Invariant + * it is a generic case class where all fields have an Invariant instance + * it is a generic sealed trait where all subclasses have an Invariant instance""") +type DerivedInvariant[F[_]] = Derived[Invariant[F]] +object DerivedInvariant: + type Or[F[_]] = Derived.Or[Invariant[F]] + inline def apply[F[_]]: Invariant[F] = + import DerivedInvariant.given + summonInline[DerivedInvariant[F]].instance + + given [T]: DerivedInvariant[Const[T]] = new Invariant[Const[T]]: + def imap[A, B](fa: T)(f: A => B)(g: B => A): T = fa + + given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedInvariant[[x] =>> F[G[x]]] = + given Invariant[G] = G.unify + F.unify.compose[G] + + given [F[_]](using inst: => K1.Instances[Or, F]): DerivedInvariant[F] = + given K1.Instances[Invariant, F] = inst.unify + new Generic[Invariant, F] {} + + trait Generic[T[x[_]] <: Invariant[x], F[_]](using inst: K1.Instances[T, F]) extends Invariant[F]: + def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] = inst.map(fa)( + [t[_]] => (inv: T[t], t0: t[A]) => inv.imap(t0)(f)(g) + ) diff --git a/core/src/main/scala-3/cats/derived/DerivedMonoid.scala b/core/src/main/scala-3/cats/derived/DerivedMonoid.scala new file mode 100644 index 00000000..b2690a63 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedMonoid.scala @@ -0,0 +1,26 @@ +package cats.derived + +import cats.Monoid +import shapeless3.deriving.K0 + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Monoid[A] where A = ${A}. +Make sure that A is a case class where all fields have a Monoid instance.""") +type DerivedMonoid[A] = Derived[Monoid[A]] +object DerivedMonoid: + type Or[A] = Derived.Or[Monoid[A]] + inline def apply[A]: Monoid[A] = + import DerivedMonoid.given + summonInline[DerivedMonoid[A]].instance + + given [A](using inst: => K0.ProductInstances[Or, A]): DerivedMonoid[A] = + given K0.ProductInstances[Monoid, A] = inst.unify + new Product[Monoid, A] {} + + trait Product[F[x] <: Monoid[x], A](using inst: K0.ProductInstances[F, A]) + extends DerivedSemigroup.Product[F, A], + Monoid[A]: + final override lazy val empty: A = + inst.construct([A] => (F: F[A]) => F.empty) diff --git a/core/src/main/scala-3/cats/derived/DerivedMonoidK.scala b/core/src/main/scala-3/cats/derived/DerivedMonoidK.scala new file mode 100644 index 00000000..69cc75d0 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedMonoidK.scala @@ -0,0 +1,51 @@ +package cats.derived + +import cats.* +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.util.NotGiven + +@implicitNotFound("""Could not derive an instance of MonoidK[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T where T: Monoid + * it is a nested type [x] =>> G[H[x]] where G: MonoidK + * it is a nested type [x] =>> G[H[x]] where G: Applicative and H: MonoidK + * it is a generic case class where all fields have a MonoidK instance""") +type DerivedMonoidK[F[_]] = Derived[MonoidK[F]] +object DerivedMonoidK: + type Or[F[_]] = Derived.Or[MonoidK[F]] + inline def apply[F[_]]: MonoidK[F] = + import DerivedMonoidK.given + summonInline[DerivedMonoidK[F]].instance + + given [T](using T: Monoid[T]): DerivedMonoidK[Const[T]] = new MonoidK[Const[T]]: + final override def empty[A]: Const[T][A] = T.empty + + final override def combineK[A](x: Const[T][A], y: Const[T][A]) = T.combine(x, y) + + given [F[_], G[_]](using F: Or[F]): DerivedMonoidK[[x] =>> F[G[x]]] = + F.unify.compose[G] + + given [F[_], G[_]](using + N: NotGiven[Or[F]], + F0: DerivedApplicative.Or[F], + G0: Or[G] + ): DerivedMonoidK[[x] =>> F[G[x]]] = + new MonoidK[[x] =>> F[G[x]]]: + val F: Applicative[F] = F0.unify + val G: MonoidK[G] = G0.unify + + final override def empty[A]: F[G[A]] = F.pure(G.empty[A]) + + final override def combineK[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = F.map2(x, y)(G.combineK(_, _)) + + given [F[_]](using inst: => K1.ProductInstances[Or, F]): DerivedMonoidK[F] = + given K1.ProductInstances[MonoidK, F] = inst.unify + new Product[MonoidK, F] {} + + trait Product[T[x[_]] <: MonoidK[x], F[_]](using inst: K1.ProductInstances[T, F]) + extends MonoidK[F], + DerivedSemigroupK.Product[T, F]: + final override def empty[A]: F[A] = inst.construct([t[_]] => (emp: T[t]) => emp.empty[A]) diff --git a/core/src/main/scala-3/cats/derived/DerivedNonEmptyTraverse.scala b/core/src/main/scala-3/cats/derived/DerivedNonEmptyTraverse.scala new file mode 100644 index 00000000..fba29218 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedNonEmptyTraverse.scala @@ -0,0 +1,74 @@ +package cats.derived + +import cats.{Applicative, Apply, NonEmptyTraverse, Traverse} +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of NonEmptyTraverse[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a nested type [x] =>> G[H[x]] where G: NonEmptyTraverse and H: NonEmptyTraverse + * it is a generic case class where at least one field has a NonEmptyTraverse and the rest Traverse instances + * it is a generic sealed trait where all subclasses have a NonEmptyTraverse instance""") +type DerivedNonEmptyTraverse[F[_]] = Derived[NonEmptyTraverse[F]] +object DerivedNonEmptyTraverse: + type Or[F[_]] = Derived.Or[NonEmptyTraverse[F]] + inline def apply[F[_]]: NonEmptyTraverse[F] = + import DerivedTraverse.given + import DerivedNonEmptyTraverse.given + summonInline[DerivedNonEmptyTraverse[F]].instance + + given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedNonEmptyTraverse[[x] =>> F[G[x]]] = + F.unify.compose(G.unify) + + def product[F[_]](ev: NonEmptyTraverse[?])(using + inst: K1.ProductInstances[DerivedTraverse.Or, F] + ): DerivedNonEmptyTraverse[F] = + given K1.ProductInstances[Traverse, F] = inst.unify + new Product[Traverse, F](ev) + with DerivedReducible.Product[Traverse, F](ev) + with DerivedTraverse.Product[Traverse, F] + with DerivedFunctor.Generic[Traverse, F] {} + + inline given [F[_]](using gen: K1.ProductGeneric[F]): DerivedNonEmptyTraverse[F] = + product(K1.summonFirst[Or, gen.MirroredElemTypes, Const[Any]].unify) + + given [F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedNonEmptyTraverse[F] = + given K1.CoproductInstances[NonEmptyTraverse, F] = inst.unify + new Coproduct[NonEmptyTraverse, F] + with DerivedReducible.Coproduct[NonEmptyTraverse, F] + with DerivedTraverse.Coproduct[NonEmptyTraverse, F] + with DerivedFunctor.Generic[NonEmptyTraverse, F] {} + + trait Product[T[x[_]] <: Traverse[x], F[_]](ev: NonEmptyTraverse[?])(using + inst: K1.ProductInstances[T, F] + ) extends NonEmptyTraverse[F], + DerivedReducible.Product[T, F], + DerivedTraverse.Product[T, F]: + + final override def nonEmptyTraverse[G[_]: Apply, A, B](fa: F[A])(f: A => G[B]): G[F[B]] = + traverse[Alt[G], A, B](fa)(f.andThen(Left.apply)) match + case Left(value) => value + case Right(_) => ??? + + trait Coproduct[T[x[_]] <: NonEmptyTraverse[x], F[_]](using + inst: K1.CoproductInstances[T, F] + ) extends NonEmptyTraverse[F], + DerivedReducible.Coproduct[T, F], + DerivedTraverse.Coproduct[T, F]: + + final override def nonEmptyTraverse[G[_]: Apply, A, B](fa: F[A])(f: A => G[B]): G[F[B]] = + inst.fold(fa)([f[_]] => (tf: T[f], fa: f[A]) => tf.nonEmptyTraverse(fa)(f).asInstanceOf[G[F[B]]]) + + private type Alt[F[_]] = [A] =>> Either[F[A], A] + private given [F[_]](using F: Apply[F]): Applicative[Alt[F]] with + override def pure[A](x: A) = Right(x) + override def map[A, B](fa: Alt[F][A])(f: A => B) = fa match + case Left(fa) => Left(F.map(fa)(f)) + case Right(a) => Right(f(a)) + override def ap[A, B](ff: Alt[F][A => B])(fa: Alt[F][A]) = (ff, fa) match + case (Left(ff), Left(fa)) => Left(F.ap(ff)(fa)) + case (Left(ff), Right(a)) => Left(F.map(ff)(_(a))) + case (Right(f), Left(fa)) => Left(F.map(fa)(f)) + case (Right(f), Right(a)) => Right(f(a)) diff --git a/core/src/main/scala-3/cats/derived/DerivedOrder.scala b/core/src/main/scala-3/cats/derived/DerivedOrder.scala new file mode 100644 index 00000000..9649bada --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedOrder.scala @@ -0,0 +1,46 @@ +package cats.derived + +import cats.{Order, Show} +import shapeless3.deriving.{Complete, Continue, K0, Labelling} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.deriving.Mirror + +@implicitNotFound("""Could not derive an instance of Order[A] where A = ${A}. +Make sure that A satisfies one of the following conditions: + * it is a case class where all fields have an Order instance + * it is a sealed trait where all subclasses have an Order instance""") +type DerivedOrder[A] = Derived[Order[A]] +object DerivedOrder: + type Or[A] = Derived.Or[Order[A]] + + inline def apply[A]: Order[A] = + import DerivedOrder.given + summonInline[DerivedOrder[A]].instance + + given product[A](using inst: => K0.ProductInstances[Or, A]): DerivedOrder[A] = + given K0.ProductInstances[Order, A] = inst.unify + new Product[Order, A] {} + + given coproduct[A](using inst: => K0.CoproductInstances[Or, A]): DerivedOrder[A] = + given K0.CoproductInstances[Order, A] = inst.unify + new Coproduct[Order, A] {} + + trait Product[T[x] <: Order[x], A](using inst: K0.ProductInstances[T, A]) extends Order[A]: + + def compare(x: A, y: A): Int = + inst.foldLeft2(x, y)(0: Int)( + [t] => + (acc: Int, ord: T[t], t0: t, t1: t) => { + val cmp = ord.compare(t0, t1) + Complete(cmp != 0)(cmp)(acc) + } + ) + + trait Coproduct[T[x] <: Order[x], A](using inst: K0.CoproductInstances[T, A]) extends Order[A]: + + def compare(x: A, y: A): Int = + inst.fold2(x, y)((x: Int, y: Int) => x - y)( + [t] => (ord: T[t], t0: t, t1: t) => ord.compare(t0, t1) + ) diff --git a/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala b/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala new file mode 100644 index 00000000..01e730e3 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala @@ -0,0 +1,46 @@ +package cats.derived + +import cats.{PartialOrder, Show} +import shapeless3.deriving.{Complete, Continue, K0, Labelling} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.deriving.Mirror + +@implicitNotFound("""Could not derive an instance of PartialOrder[A] where A = ${A}. +Make sure that A satisfies one of the following conditions: + * it is a case class where all fields have a PartialOrder instance + * it is a sealed trait where all subclasses have a PartialOrder instance""") +type DerivedPartialOrder[A] = Derived[PartialOrder[A]] +object DerivedPartialOrder: + type Or[A] = Derived.Or[PartialOrder[A]] + + inline def apply[A]: PartialOrder[A] = + import DerivedPartialOrder.given + summonInline[DerivedPartialOrder[A]].instance + + given product[A](using inst: => K0.ProductInstances[Or, A]): DerivedPartialOrder[A] = + given K0.ProductInstances[PartialOrder, A] = inst.unify + new Product[PartialOrder, A] {} + + given coproduct[A](using inst: => K0.CoproductInstances[Or, A]): DerivedPartialOrder[A] = + given K0.CoproductInstances[PartialOrder, A] = inst.unify + new Coproduct[PartialOrder, A] {} + + trait Product[T[x] <: PartialOrder[x], A](using inst: K0.ProductInstances[T, A]) extends PartialOrder[A]: + + def partialCompare(x: A, y: A): Double = + inst.foldLeft2(x, y)(0: Double)( + [t] => + (acc: Double, ord: T[t], t0: t, t1: t) => { + val cmp = ord.partialCompare(t0, t1) + Complete(cmp != 0)(cmp)(acc) + } + ) + + trait Coproduct[T[x] <: PartialOrder[x], A](using inst: K0.CoproductInstances[T, A]) extends PartialOrder[A]: + + def partialCompare(x: A, y: A): Double = + inst.fold2(x, y)(Double.NaN: Double)( + [t] => (ord: T[t], t0: t, t1: t) => ord.partialCompare(t0, t1) + ) diff --git a/core/src/main/scala-3/cats/derived/DerivedPure.scala b/core/src/main/scala-3/cats/derived/DerivedPure.scala new file mode 100644 index 00000000..5a68a22f --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedPure.scala @@ -0,0 +1,32 @@ +package cats.derived + +import alleycats.{Empty, Pure} +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.summonInline + +@implicitNotFound("""Could not derive an instance of Pure[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T where T: Empty + * it is a nested type [x] =>> G[H[x]] where G: Pure and H: Pure + * it is a generic case class where all fields have a Pure instance""") +type DerivedPure[F[_]] = Derived[Pure[F]] + +object DerivedPure: + type Or[F[_]] = Derived.Or[Pure[F]] + inline def apply[F[_]]: Pure[F] = + import DerivedPure.given + summonInline[DerivedPure[F]].instance + + given [T](using T: Empty[T]): DerivedPure[Const[T]] = + new Pure[Const[T]]: + def pure[A](a: A) = T.empty + + given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedPure[[x] =>> F[G[x]]] = + new Pure[[x] =>> F[G[x]]]: + def pure[A](a: A) = F.unify.pure(G.unify.pure(a)) + + given [F[_]](using inst: K1.ProductInstances[Or, F]): DerivedPure[F] = + new Pure[F]: + def pure[A](a: A) = inst.unify.construct([f[_]] => (P: Pure[f]) => P.pure(a)) diff --git a/core/src/main/scala-3/cats/derived/DerivedReducible.scala b/core/src/main/scala-3/cats/derived/DerivedReducible.scala new file mode 100644 index 00000000..a51aedfb --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedReducible.scala @@ -0,0 +1,73 @@ +package cats.derived + +import cats.{Eval, Foldable, Reducible} +import shapeless3.deriving.{Continue, Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Reducible[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a nested type [x] =>> G[H[x]] where G: Reducible and H: Reducible + * it is a generic case class where at least one field has a Reducible and the rest Foldable instances + * it is a generic sealed trait where all subclasses have a Reducible instance""") +type DerivedReducible[F[_]] = Derived[Reducible[F]] +object DerivedReducible: + type Or[F[_]] = Derived.Or[Reducible[F]] + inline def apply[F[_]]: Reducible[F] = + import DerivedFoldable.given + import DerivedReducible.given + summonInline[DerivedReducible[F]].instance + + given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedReducible[[x] =>> F[G[x]]] = + F.unify.compose(G.unify) + + def product[F[_]](ev: Reducible[?])(using inst: K1.ProductInstances[DerivedFoldable.Or, F]): DerivedReducible[F] = + given K1.ProductInstances[Foldable, F] = inst.unify + new Product[Foldable, F](ev) {} + + inline given [F[_]](using gen: K1.ProductGeneric[F]): DerivedReducible[F] = + product(K1.summonFirst[Or, gen.MirroredElemTypes, Const[Any]].unify) + + given [F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedReducible[F] = + given K1.CoproductInstances[Reducible, F] = inst.unify + new Coproduct[Reducible, F] {} + + trait Product[T[x[_]] <: Foldable[x], F[_]](ev: Reducible[?])(using inst: K1.ProductInstances[T, F]) + extends DerivedFoldable.Product[T, F], + Reducible[F]: + + private val evalNone = Eval.now(None) + + final override def reduceLeftTo[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): B = + inst + .foldLeft[A, Option[B]](fa)(None)( + [f[_]] => + (acc: Option[B], tf: T[f], fa: f[A]) => + acc match + case Some(b) => Continue(Some(tf.foldLeft(fa, b)(g))) + case None => Continue(tf.reduceLeftToOption(fa)(f)(g)) + ) + .get + + final override def reduceRightTo[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + inst + .foldRight[A, Eval[Option[B]]](fa)(evalNone)( + [f[_]] => + (tf: T[f], fa: f[A], acc: Eval[Option[B]]) => + Continue(acc.flatMap { + case Some(b) => tf.foldRight(fa, Eval.now(b))(g).map(Some.apply) + case None => tf.reduceRightToOption(fa)(f)(g) + }) + ) + .map(_.get) + + trait Coproduct[T[x[_]] <: Reducible[x], F[_]](using inst: K1.CoproductInstances[T, F]) + extends DerivedFoldable.Coproduct[T, F], + Reducible[F]: + + final override def reduceLeftTo[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): B = + inst.fold[A, B](fa)([f[_]] => (tf: T[f], fa: f[A]) => tf.reduceLeftTo(fa)(f)(g)) + + final override def reduceRightTo[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + inst.fold[A, Eval[B]](fa)([f[_]] => (tf: T[f], fa: f[A]) => Eval.defer(tf.reduceRightTo(fa)(f)(g))) diff --git a/core/src/main/scala-3/cats/derived/DerivedSemigroup.scala b/core/src/main/scala-3/cats/derived/DerivedSemigroup.scala new file mode 100644 index 00000000..98ac3dad --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedSemigroup.scala @@ -0,0 +1,24 @@ +package cats.derived + +import cats.Semigroup +import shapeless3.deriving.K0 + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Semigroup[A] where A = ${A}. +Make sure that A is a case class where all fields have a Semigroup instance.""") +type DerivedSemigroup[A] = Derived[Semigroup[A]] +object DerivedSemigroup: + type Or[A] = Derived.Or[Semigroup[A]] + inline def apply[A]: Semigroup[A] = + import DerivedSemigroup.given + summonInline[DerivedSemigroup[A]].instance + + given [A](using inst: => K0.ProductInstances[Or, A]): DerivedSemigroup[A] = + given K0.ProductInstances[Semigroup, A] = inst.unify + new Product[Semigroup, A] {} + + trait Product[F[x] <: Semigroup[x], A](using inst: K0.ProductInstances[F, A]) extends Semigroup[A]: + final override def combine(x: A, y: A): A = + inst.map2(x, y)([A] => (F: F[A], x: A, y: A) => F.combine(x, y)) diff --git a/core/src/main/scala-3/cats/derived/DerivedSemigroupK.scala b/core/src/main/scala-3/cats/derived/DerivedSemigroupK.scala new file mode 100644 index 00000000..d3099cfb --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedSemigroupK.scala @@ -0,0 +1,39 @@ +package cats.derived + +import cats.{Semigroup, SemigroupK} +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.util.NotGiven + +@implicitNotFound("""Could not derive an instance of SemigroupK[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T where T: Semigroup + * it is a nested type [x] =>> G[H[x]] where G: SemigroupK + * it is a nested type [x] =>> G[H[x]] where G: Apply and H: SemigroupK + * it is a generic case class where all fields have a SemigroupK instance""") +type DerivedSemigroupK[F[_]] = Derived[SemigroupK[F]] +object DerivedSemigroupK: + type Or[F[_]] = Derived.Or[SemigroupK[F]] + inline def apply[F[_]]: SemigroupK[F] = + import DerivedSemigroupK.given + summonInline[DerivedSemigroupK[F]].instance + + given [T](using T: Semigroup[T]): DerivedSemigroupK[Const[T]] = new SemigroupK[Const[T]]: + final override def combineK[A](x: Const[T][A], y: Const[T][A]) = T.combine(x, y) + + given [F[_], G[_]](using F: Or[F]): DerivedSemigroupK[[x] =>> F[G[x]]] = + F.unify.compose[G] + + given [F[_], G[_]](using N: NotGiven[Or[F]], F: DerivedApply.Or[F], G: Or[G]): DerivedSemigroupK[[x] =>> F[G[x]]] = + new SemigroupK[[x] =>> F[G[x]]]: + final override def combineK[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = F.unify.map2(x, y)(G.unify.combineK(_, _)) + + given [F[_]](using inst: => K1.ProductInstances[Or, F]): DerivedSemigroupK[F] = + given K1.ProductInstances[SemigroupK, F] = inst.unify + new Product[SemigroupK, F] {} + + trait Product[T[x[_]] <: SemigroupK[x], F[_]](using inst: K1.ProductInstances[T, F]) extends SemigroupK[F]: + final override def combineK[A](x: F[A], y: F[A]): F[A] = + inst.map2[A, A, A](x, y)([t[_]] => (smgrpk: T[t], x: t[A], y: t[A]) => smgrpk.combineK(x, y)) diff --git a/core/src/main/scala-3/cats/derived/DerivedShow.scala b/core/src/main/scala-3/cats/derived/DerivedShow.scala new file mode 100644 index 00000000..9e7bea9e --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedShow.scala @@ -0,0 +1,54 @@ +package cats.derived + +import cats.Show +import shapeless3.deriving.{Continue, K0, Labelling} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.deriving.Mirror + +@implicitNotFound("""Could not derive an instance of Show[A] where A = ${A}. +Make sure that A satisfies one of the following conditions: + * it is a case class where all fields have a Show instance + * it is a sealed trait where all subclasses have a Show instance""") +type DerivedShow[A] = Derived[Show[A]] +object DerivedShow: + type Or[A] = Derived.Or[Show[A]] + + inline def apply[A]: Show[A] = + import DerivedShow.given + summonInline[DerivedShow[A]].instance + + given [A](using inst: K0.ProductInstances[Or, A], labelling: Labelling[A]): DerivedShow[A] = + given K0.ProductInstances[Show, A] = inst.unify + new Product[Show, A] {} + + given [A](using inst: => K0.CoproductInstances[Or, A]): DerivedShow[A] = + given K0.CoproductInstances[Show, A] = inst.unify + new Coproduct[Show, A] {} + + trait Product[F[x] <: Show[x], A](using inst: K0.ProductInstances[F, A], labelling: Labelling[A]) extends Show[A]: + def show(a: A): String = + val prefix = labelling.label + val labels = labelling.elemLabels + val n = labels.size + if n <= 0 then prefix + else + val sb = new StringBuilder(prefix) + sb.append('(') + var i = 0 + while i < n do + sb.append(labels(i)) + sb.append(" = ") + sb.append(inst.project(a)(i)([t] => (show: F[t], x: t) => show.show(x))) + sb.append(", ") + i += 1 + + val l = sb.length + sb.delete(l - 2, l) + sb.append(')') + sb.toString + + trait Coproduct[F[x] <: Show[x], A](using inst: K0.CoproductInstances[F, A]) extends Show[A]: + def show(a: A): String = + inst.fold(a)([t] => (st: F[t], t: t) => st.show(t)) diff --git a/core/src/main/scala-3/cats/derived/DerivedShowPretty.scala b/core/src/main/scala-3/cats/derived/DerivedShowPretty.scala new file mode 100644 index 00000000..e93ba93a --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedShowPretty.scala @@ -0,0 +1,71 @@ +package cats.derived + +import cats.Show +import shapeless3.deriving.{Continue, K0, Labelling} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.deriving.Mirror + +trait ShowPretty[A] extends Show[A]: + def showLines(a: A): List[String] + def show(a: A): String = showLines(a).mkString(System.lineSeparator) + +object ShowPretty: + inline def apply[A](using A: ShowPretty[A]): A.type = A + +@implicitNotFound("""Could not derive an instance of ShowPretty[A] where A = ${A}. +Make sure that A satisfies one of the following conditions: + * it is a case class where all fields have a Show instance + * it is a sealed trait where all subclasses have a Show instance""") +type DerivedShowPretty[A] = Derived[ShowPretty[A]] +object DerivedShowPretty: + opaque type Or[A] = A => List[String] + object Or extends OrInstances: + def apply[A](instance: A => List[String]): Or[A] = instance + extension [A](or: Or[A]) def apply(a: A): List[String] = or(a) + + sealed abstract class OrInstances: + inline given [A]: Or[A] = summonFrom { + case instance: Show[A] => Or((a: A) => instance.show(a).split(System.lineSeparator).toList) + case derived: DerivedShowPretty[A] => Or(derived.instance.showLines(_)) + } + + inline def apply[A]: ShowPretty[A] = + import DerivedShowPretty.given + summonInline[DerivedShowPretty[A]].instance + + given [A](using inst: K0.ProductInstances[Or, A], labelling: Labelling[A]): DerivedShowPretty[A] = + new Product[A] {} + + given [A](using inst: => K0.CoproductInstances[Or, A]): DerivedShowPretty[A] = + new Coproduct[A] {} + + trait Product[A](using inst: K0.ProductInstances[Or, A], labelling: Labelling[A]) extends ShowPretty[A]: + def showLines(a: A): List[String] = + val prefix = labelling.label + val labels = labelling.elemLabels + val n = labels.size + if n <= 0 then List(s"$prefix()") + else + var lines: List[String] = List(")") + val inner = inst.project(a)(n - 1)([t] => (show: Or[t], x: t) => show.apply(x)) + inner match + case Nil => lines = s" ${labels(n - 1)} = \"\"," :: lines + case h :: t => lines = s" ${labels(n - 1)} = $h" :: t.map(s => " " + s) ::: lines + var i = n - 2 + while i >= 0 do + val inner = inst.project(a)(i)([t] => (show: Or[t], x: t) => show.apply(x)) + inner match + case Nil => lines = s" ${labels(i)} = \"\"," :: lines + case v :: Nil => lines = s" ${labels(i)} = $v," :: lines + case h :: t => lines = s" ${labels(i)} = $h" :: t.init.map(s => " " + s) ::: s" ${t.last}," :: lines + i -= 1 + + lines = s"$prefix(" :: lines + + lines + + trait Coproduct[A](using inst: K0.CoproductInstances[Or, A]) extends ShowPretty[A]: + def showLines(a: A): List[String] = + inst.fold(a)([t] => (st: Or[t], t: t) => st.apply(t)) diff --git a/core/src/main/scala-3/cats/derived/DerivedTraverse.scala b/core/src/main/scala-3/cats/derived/DerivedTraverse.scala new file mode 100644 index 00000000..3467b88f --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedTraverse.scala @@ -0,0 +1,56 @@ +package cats.derived + +import cats.{Applicative, Eval, Traverse} +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Traverse[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T + * it is a nested type [x] =>> G[H[x]] where G: Traverse and H: Traverse + * it is a generic case class where all fields have a Traverse instance + * it is a generic sealed trait where all subclasses have a Traverse instance""") +type DerivedTraverse[F[_]] = Derived[Traverse[F]] +object DerivedTraverse: + type Or[F[_]] = Derived.Or[Traverse[F]] + inline def apply[F[_]]: Traverse[F] = + import DerivedTraverse.given + summonInline[DerivedTraverse[F]].instance + + given [T]: DerivedTraverse[Const[T]] = new Traverse[Const[T]]: + override def map[A, B](fa: T)(f: A => B): T = fa + override def foldLeft[A, B](fa: T, b: B)(f: (B, A) => B): B = b + override def foldRight[A, B](fa: T, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = lb + override def traverse[G[_], A, B](fa: T)(f: A => G[B])(using G: Applicative[G]): G[T] = G.pure(fa) + + given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedTraverse[[x] =>> F[G[x]]] = + F.unify.compose(G.unify) + + given [F[_]](using inst: K1.ProductInstances[Or, F]): DerivedTraverse[F] = + given K1.ProductInstances[Traverse, F] = inst.unify + new Product[Traverse, F] with DerivedFunctor.Generic[Traverse, F] {} + + given [F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedTraverse[F] = + given K1.CoproductInstances[Traverse, F] = inst.unify + new Coproduct[Traverse, F] with DerivedFunctor.Generic[Traverse, F] {} + + trait Product[T[x[_]] <: Traverse[x], F[_]](using inst: K1.ProductInstances[T, F]) + extends Traverse[F], + DerivedFunctor.Generic[T, F], + DerivedFoldable.Product[T, F]: + + final override def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(using G: Applicative[G]): G[F[B]] = + val pure = [a] => (x: a) => G.pure(x) + val map = [a, b] => (ga: G[a], f: a => b) => G.map(ga)(f) + val ap = [a, b] => (gf: G[a => b], ga: G[a]) => G.ap(gf)(ga) + inst.traverse[A, G, B](fa)(map)(pure)(ap)([f[_]] => (tf: T[f], fa: f[A]) => tf.traverse(fa)(f)) + + trait Coproduct[T[x[_]] <: Traverse[x], F[_]](using inst: K1.CoproductInstances[T, F]) + extends Traverse[F], + DerivedFunctor.Generic[T, F], + DerivedFoldable.Coproduct[T, F]: + + final override def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] = + inst.fold(fa)([f[_]] => (tf: T[f], fa: f[A]) => tf.traverse(fa)(f).asInstanceOf[G[F[B]]]) diff --git a/core/src/main/scala-3/cats/derived/package.scala b/core/src/main/scala-3/cats/derived/package.scala new file mode 100644 index 00000000..deebb012 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/package.scala @@ -0,0 +1,131 @@ +package cats.derived + +import alleycats.* +import cats.* +import cats.kernel.{CommutativeSemigroup, CommutativeMonoid} + +import scala.util.NotGiven + +extension (x: Eq.type) inline def derived[A]: Eq[A] = DerivedEq[A] +extension (x: Hash.type) inline def derived[A]: Hash[A] = DerivedHash[A] +extension (x: Empty.type) inline def derived[A]: Empty[A] = DerivedEmpty[A] +extension (x: Semigroup.type) inline def derived[A]: Semigroup[A] = DerivedSemigroup[A] +extension (x: Monoid.type) inline def derived[A]: Monoid[A] = DerivedMonoid[A] +extension (x: Order.type) inline def derived[A]: Order[A] = DerivedOrder[A] +extension (x: CommutativeSemigroup.type) inline def derived[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup[A] +extension (x: CommutativeMonoid.type) inline def derived[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid[A] +extension (x: Show.type) inline def derived[A]: Show[A] = DerivedShow[A] +extension (x: Applicative.type) inline def derived[F[_]]: Applicative[F] = DerivedApplicative[F] +extension (x: Apply.type) inline def derived[F[_]]: Apply[F] = DerivedApply[F] +extension (x: EmptyK.type) inline def derived[F[_]]: EmptyK[F] = DerivedEmptyK[F] +extension (x: Pure.type) inline def derived[F[_]]: Pure[F] = DerivedPure[F] +extension (x: Foldable.type) inline def derived[F[_]]: Foldable[F] = DerivedFoldable[F] +extension (x: Functor.type) inline def derived[F[_]]: Functor[F] = DerivedFunctor[F] +extension (x: Reducible.type) inline def derived[F[_]]: Reducible[F] = DerivedReducible[F] +extension (x: Traverse.type) inline def derived[F[_]]: Traverse[F] = DerivedTraverse[F] +extension (x: NonEmptyTraverse.type) inline def derived[F[_]]: NonEmptyTraverse[F] = DerivedNonEmptyTraverse[F] +extension (x: SemigroupK.type) inline def derived[F[_]]: SemigroupK[F] = DerivedSemigroupK[F] +extension (x: MonoidK.type) inline def derived[F[_]]: MonoidK[F] = DerivedMonoidK[F] +extension (x: Contravariant.type) inline def derived[F[_]]: Contravariant[F] = DerivedContravariant[F] +extension (x: Invariant.type) inline def derived[F[_]]: Invariant[F] = DerivedInvariant[F] +extension (x: PartialOrder.type) inline def derived[A]: PartialOrder[A] = DerivedPartialOrder[A] +extension (x: ShowPretty.type) inline def derived[A]: ShowPretty[A] = DerivedShowPretty[A] + +object semiauto: + 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] + inline def order[A]: Order[A] = DerivedOrder[A] + inline def commutativeSemigroup[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup[A] + inline def commutativeMonoid[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid[A] + inline def applicative[F[_]]: Applicative[F] = DerivedApplicative[F] + inline def apply[F[_]]: Apply[F] = DerivedApply[F] + inline def emptyK[F[_]]: EmptyK[F] = DerivedEmptyK[F] + inline def pure[F[_]]: Pure[F] = DerivedPure[F] + inline def foldable[F[_]]: Foldable[F] = DerivedFoldable[F] + inline def functor[F[_]]: Functor[F] = DerivedFunctor[F] + inline def reducible[F[_]]: Reducible[F] = DerivedReducible[F] + inline def traverse[F[_]]: Traverse[F] = DerivedTraverse[F] + inline def nonEmptyTraverse[F[_]]: NonEmptyTraverse[F] = DerivedNonEmptyTraverse[F] + inline def show[A]: Show[A] = DerivedShow[A] + inline def semigroupK[F[_]]: SemigroupK[F] = DerivedSemigroupK[F] + inline def monoidK[F[_]]: MonoidK[F] = DerivedMonoidK[F] + inline def contravariant[F[_]]: Contravariant[F] = DerivedContravariant[F] + inline def invariant[F[_]]: Invariant[F] = DerivedInvariant[F] + inline def partialOrder[A]: PartialOrder[A] = DerivedPartialOrder[A] + inline def showPretty[A]: ShowPretty[A] = DerivedShowPretty[A] + +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] + + object semigroup: + inline given [A](using NotGiven[Semigroup[A]]): Semigroup[A] = DerivedSemigroup[A] + + object monoid: + inline given [A](using NotGiven[Monoid[A]]): Monoid[A] = DerivedMonoid[A] + + object order: + inline given [A](using NotGiven[Order[A]]): Order[A] = DerivedOrder[A] + + object commutativeSemigroup: + inline given [A](using NotGiven[CommutativeSemigroup[A]]): CommutativeSemigroup[A] = DerivedCommutativeSemigroup[A] + + object commutativeMonoid: + inline given [A](using NotGiven[CommutativeMonoid[A]]): CommutativeMonoid[A] = DerivedCommutativeMonoid[A] + + object show: + inline given [A](using NotGiven[Show[A]]): Show[A] = DerivedShow[A] + + object applicative: + inline given [F[_]](using NotGiven[Applicative[F]]): Applicative[F] = DerivedApplicative[F] + + object apply: + inline given [F[_]](using NotGiven[Apply[F]]): Apply[F] = DerivedApply[F] + + object emptyK: + inline given [F[_]](using NotGiven[EmptyK[F]]): EmptyK[F] = DerivedEmptyK[F] + + object pure: + inline given [F[_]](using NotGiven[Pure[F]]): Pure[F] = DerivedPure[F] + + object functor: + inline given [F[_]](using NotGiven[Functor[F]]): Functor[F] = DerivedFunctor[F] + + object foldable: + inline given [F[_]](using NotGiven[Foldable[F]]): Foldable[F] = DerivedFoldable[F] + + object reducible: + inline given [F[_]](using NotGiven[Reducible[F]]): Reducible[F] = DerivedReducible[F] + + object traverse: + inline given [F[_]](using NotGiven[Traverse[F]]): Traverse[F] = DerivedTraverse[F] + + object nonEmptyTraverse: + inline given [F[_]](using NotGiven[NonEmptyTraverse[F]]): NonEmptyTraverse[F] = DerivedNonEmptyTraverse[F] + + object semigroupK: + inline given [F[_]](using NotGiven[SemigroupK[F]]): SemigroupK[F] = DerivedSemigroupK[F] + + object monoidK: + inline given [F[_]](using NotGiven[MonoidK[F]]): MonoidK[F] = DerivedMonoidK[F] + + object contravariant: + inline given [F[_]](using NotGiven[Contravariant[F]]): Contravariant[F] = DerivedContravariant[F] + + object invariant: + inline given [F[_]](using NotGiven[Invariant[F]]): Invariant[F] = DerivedInvariant[F] + + object partialOrder: + inline given [A](using NotGiven[PartialOrder[A]]): PartialOrder[A] = DerivedPartialOrder[A] + + object showPretty: + inline given [A](using NotGiven[Show[A]]): ShowPretty[A] = DerivedShowPretty[A] diff --git a/core/src/main/scala-3/cats/derived/util/Kinds.scala b/core/src/main/scala-3/cats/derived/util/Kinds.scala new file mode 100644 index 00000000..2182c7d1 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/util/Kinds.scala @@ -0,0 +1,37 @@ +package cats.derived.util + +import shapeless3.deriving._ + +import scala.compiletime.* +import scala.util.NotGiven + +object Kinds: + inline def summonOne0[F[_], T, U]: F[U] = + summonOneFrom[K0.LiftP[F, T]].asInstanceOf[F[U]] + + inline def summonOne1[F[_[_]], T[_], U[_]]: F[U] = + summonOneFrom[K1.LiftP[F, T]].asInstanceOf[F[U]] + + inline def summonNone0[F[_], T]: Unit = + summonNoneFrom[K0.LiftP[F, T]] + + inline def summonNone1[F[_[_]], T[_]]: Unit = + summonNoneFrom[K1.LiftP[F, T]] + + transparent inline def summonOneFrom[T <: Tuple]: Any = + inline erasedValue[T] match + case _: (a *: b) => + summonFrom { + case instance: `a` => + summonNoneFrom[b] + instance + case _ => + summonOneFrom[b] + } + + transparent inline def summonNoneFrom[T <: Tuple]: Unit = + inline erasedValue[T] match + case _: EmptyTuple => () + case _: (a *: b) => + summonInline[NotGiven[`a`]] + summonNoneFrom[b] diff --git a/core/src/test/scala/cats/derived/KittensSuite.scala b/core/src/test/scala-2/cats/derived/KittensSuite.scala similarity index 100% rename from core/src/test/scala/cats/derived/KittensSuite.scala rename to core/src/test/scala-2/cats/derived/KittensSuite.scala diff --git a/core/src/test/scala/cats/derived/adtdefns.scala b/core/src/test/scala-2/cats/derived/adtdefns.scala similarity index 100% rename from core/src/test/scala/cats/derived/adtdefns.scala rename to core/src/test/scala-2/cats/derived/adtdefns.scala diff --git a/core/src/test/scala/cats/derived/applicative.scala b/core/src/test/scala-2/cats/derived/applicative.scala similarity index 100% rename from core/src/test/scala/cats/derived/applicative.scala rename to core/src/test/scala-2/cats/derived/applicative.scala diff --git a/core/src/test/scala/cats/derived/apply.scala b/core/src/test/scala-2/cats/derived/apply.scala similarity index 100% rename from core/src/test/scala/cats/derived/apply.scala rename to core/src/test/scala-2/cats/derived/apply.scala diff --git a/core/src/test/scala/cats/derived/commutativeMonoid.scala b/core/src/test/scala-2/cats/derived/commutativeMonoid.scala similarity index 100% rename from core/src/test/scala/cats/derived/commutativeMonoid.scala rename to core/src/test/scala-2/cats/derived/commutativeMonoid.scala diff --git a/core/src/test/scala/cats/derived/commutativeSemigroup.scala b/core/src/test/scala-2/cats/derived/commutativeSemigroup.scala similarity index 100% rename from core/src/test/scala/cats/derived/commutativeSemigroup.scala rename to core/src/test/scala-2/cats/derived/commutativeSemigroup.scala diff --git a/core/src/test/scala/cats/derived/consk.scala b/core/src/test/scala-2/cats/derived/consk.scala similarity index 100% rename from core/src/test/scala/cats/derived/consk.scala rename to core/src/test/scala-2/cats/derived/consk.scala diff --git a/core/src/test/scala/cats/derived/contravariant.scala b/core/src/test/scala-2/cats/derived/contravariant.scala similarity index 100% rename from core/src/test/scala/cats/derived/contravariant.scala rename to core/src/test/scala-2/cats/derived/contravariant.scala diff --git a/core/src/test/scala/cats/derived/empty.scala b/core/src/test/scala-2/cats/derived/empty.scala similarity index 100% rename from core/src/test/scala/cats/derived/empty.scala rename to core/src/test/scala-2/cats/derived/empty.scala diff --git a/core/src/test/scala/cats/derived/emptyk.scala b/core/src/test/scala-2/cats/derived/emptyk.scala similarity index 100% rename from core/src/test/scala/cats/derived/emptyk.scala rename to core/src/test/scala-2/cats/derived/emptyk.scala diff --git a/core/src/test/scala/cats/derived/eq.scala b/core/src/test/scala-2/cats/derived/eq.scala similarity index 100% rename from core/src/test/scala/cats/derived/eq.scala rename to core/src/test/scala-2/cats/derived/eq.scala diff --git a/core/src/test/scala/cats/derived/foldable.scala b/core/src/test/scala-2/cats/derived/foldable.scala similarity index 100% rename from core/src/test/scala/cats/derived/foldable.scala rename to core/src/test/scala-2/cats/derived/foldable.scala diff --git a/core/src/test/scala/cats/derived/function.scala b/core/src/test/scala-2/cats/derived/function.scala similarity index 100% rename from core/src/test/scala/cats/derived/function.scala rename to core/src/test/scala-2/cats/derived/function.scala diff --git a/core/src/test/scala/cats/derived/functor.scala b/core/src/test/scala-2/cats/derived/functor.scala similarity index 100% rename from core/src/test/scala/cats/derived/functor.scala rename to core/src/test/scala-2/cats/derived/functor.scala diff --git a/core/src/test/scala/cats/derived/hash.scala b/core/src/test/scala-2/cats/derived/hash.scala similarity index 100% rename from core/src/test/scala/cats/derived/hash.scala rename to core/src/test/scala-2/cats/derived/hash.scala diff --git a/core/src/test/scala/cats/derived/invariant.scala b/core/src/test/scala-2/cats/derived/invariant.scala similarity index 100% rename from core/src/test/scala/cats/derived/invariant.scala rename to core/src/test/scala-2/cats/derived/invariant.scala diff --git a/core/src/test/scala/cats/derived/iterable.scala b/core/src/test/scala-2/cats/derived/iterable.scala similarity index 100% rename from core/src/test/scala/cats/derived/iterable.scala rename to core/src/test/scala-2/cats/derived/iterable.scala diff --git a/core/src/test/scala/cats/derived/monoid.scala b/core/src/test/scala-2/cats/derived/monoid.scala similarity index 100% rename from core/src/test/scala/cats/derived/monoid.scala rename to core/src/test/scala-2/cats/derived/monoid.scala diff --git a/core/src/test/scala/cats/derived/monoidk.scala b/core/src/test/scala-2/cats/derived/monoidk.scala similarity index 100% rename from core/src/test/scala/cats/derived/monoidk.scala rename to core/src/test/scala-2/cats/derived/monoidk.scala diff --git a/core/src/test/scala/cats/derived/nonEmptyTraverse.scala b/core/src/test/scala-2/cats/derived/nonEmptyTraverse.scala similarity index 100% rename from core/src/test/scala/cats/derived/nonEmptyTraverse.scala rename to core/src/test/scala-2/cats/derived/nonEmptyTraverse.scala diff --git a/core/src/test/scala/cats/derived/order.scala b/core/src/test/scala-2/cats/derived/order.scala similarity index 100% rename from core/src/test/scala/cats/derived/order.scala rename to core/src/test/scala-2/cats/derived/order.scala diff --git a/core/src/test/scala/cats/derived/partialOrder.scala b/core/src/test/scala-2/cats/derived/partialOrder.scala similarity index 100% rename from core/src/test/scala/cats/derived/partialOrder.scala rename to core/src/test/scala-2/cats/derived/partialOrder.scala diff --git a/core/src/test/scala/cats/derived/pure.scala b/core/src/test/scala-2/cats/derived/pure.scala similarity index 100% rename from core/src/test/scala/cats/derived/pure.scala rename to core/src/test/scala-2/cats/derived/pure.scala diff --git a/core/src/test/scala/cats/derived/reducible.scala b/core/src/test/scala-2/cats/derived/reducible.scala similarity index 100% rename from core/src/test/scala/cats/derived/reducible.scala rename to core/src/test/scala-2/cats/derived/reducible.scala diff --git a/core/src/test/scala/cats/derived/semigroup.scala b/core/src/test/scala-2/cats/derived/semigroup.scala similarity index 100% rename from core/src/test/scala/cats/derived/semigroup.scala rename to core/src/test/scala-2/cats/derived/semigroup.scala diff --git a/core/src/test/scala/cats/derived/semigroupk.scala b/core/src/test/scala-2/cats/derived/semigroupk.scala similarity index 100% rename from core/src/test/scala/cats/derived/semigroupk.scala rename to core/src/test/scala-2/cats/derived/semigroupk.scala diff --git a/core/src/test/scala/cats/derived/show.scala b/core/src/test/scala-2/cats/derived/show.scala similarity index 100% rename from core/src/test/scala/cats/derived/show.scala rename to core/src/test/scala-2/cats/derived/show.scala diff --git a/core/src/test/scala/cats/derived/showPretty.scala b/core/src/test/scala-2/cats/derived/showPretty.scala similarity index 100% rename from core/src/test/scala/cats/derived/showPretty.scala rename to core/src/test/scala-2/cats/derived/showPretty.scala diff --git a/core/src/test/scala/cats/derived/traverse.scala b/core/src/test/scala-2/cats/derived/traverse.scala similarity index 100% rename from core/src/test/scala/cats/derived/traverse.scala rename to core/src/test/scala-2/cats/derived/traverse.scala diff --git a/core/src/test/scala/cats/lift/LiftSuite.scala b/core/src/test/scala-2/cats/lift/LiftSuite.scala similarity index 100% rename from core/src/test/scala/cats/lift/LiftSuite.scala rename to core/src/test/scala-2/cats/lift/LiftSuite.scala diff --git a/core/src/test/scala/cats/replicateH/ReplicateHSuite.scala b/core/src/test/scala-2/cats/replicateH/ReplicateHSuite.scala similarity index 100% rename from core/src/test/scala/cats/replicateH/ReplicateHSuite.scala rename to core/src/test/scala-2/cats/replicateH/ReplicateHSuite.scala diff --git a/core/src/test/scala/cats/sequence/SequenceSuite.scala b/core/src/test/scala-2/cats/sequence/SequenceSuite.scala similarity index 100% rename from core/src/test/scala/cats/sequence/SequenceSuite.scala rename to core/src/test/scala-2/cats/sequence/SequenceSuite.scala diff --git a/core/src/test/scala/cats/sequence/TraverseSuite.scala b/core/src/test/scala-2/cats/sequence/TraverseSuite.scala similarity index 100% rename from core/src/test/scala/cats/sequence/TraverseSuite.scala rename to core/src/test/scala-2/cats/sequence/TraverseSuite.scala diff --git a/core/src/test/scala-3/cats/derived/ApplicativeSuite.scala b/core/src/test/scala-3/cats/derived/ApplicativeSuite.scala new file mode 100644 index 00000000..562d6ff1 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/ApplicativeSuite.scala @@ -0,0 +1,73 @@ +/* + * 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.Applicative +import cats.laws.discipline.* +import cats.laws.discipline.SemigroupalTests.Isomorphisms + +import scala.compiletime.* + +class ApplicativeSuite extends KittensSuite: + import ApplicativeSuite.* + import TestDefns.* + + inline def applicativeTests[F[_]](f: Isomorphisms[F] ?=> (at: ApplicativeTests[F]) => at.RuleSet) = + f(using summonInline)(ApplicativeTests[F](summonInline)) + + inline def testApplicative(inline context: String): Unit = + checkAll( + s"$context.Applicative[CaseClassWOption]", + applicativeTests[CaseClassWOption](_.applicative[Int, String, Long]) + ) + checkAll(s"$context.Applicative[OptList]", applicativeTests[OptList](_.applicative[Int, String, Long])) + checkAll(s"$context.Applicative[AndInt]", applicativeTests[AndInt](_.applicative[Int, String, Long])) + checkAll(s"$context.Applicative[Interleaved]", applicativeTests[Interleaved](_.applicative[Int, String, Long])) + checkAll(s"$context.Applicative[ListBox]", applicativeTests[ListBox](_.applicative[Int, String, Long])) + checkAll( + s"$context.Applicative is Serializable", + SerializableTests.serializable(summonInline[Applicative[Interleaved]]) + ) + + locally { + import auto.applicative.given + testApplicative("auto") + } + + locally { + import semiInstances.given + testApplicative("semiauto") + } + +end ApplicativeSuite + +object ApplicativeSuite: + import TestDefns.* + + type OptList[A] = Option[List[A]] + type AndInt[A] = (A, Int) + type ListBox[A] = List[Box[A]] + + object semiInstances: + given Applicative[Box] = semiauto.applicative + given Applicative[CaseClassWOption] = semiauto.applicative + given Applicative[OptList] = semiauto.applicative + given Applicative[AndInt] = semiauto.applicative + given Applicative[Interleaved] = semiauto.applicative + given Applicative[ListBox] = semiauto.applicative + +end ApplicativeSuite diff --git a/core/src/test/scala-3/cats/derived/ApplySuite.scala b/core/src/test/scala-3/cats/derived/ApplySuite.scala new file mode 100644 index 00000000..f75f2a42 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/ApplySuite.scala @@ -0,0 +1,69 @@ +/* + * 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.Apply +import cats.laws.discipline.{ApplyTests, SerializableTests} +import cats.laws.discipline.SemigroupalTests.Isomorphisms +import scala.compiletime.* + +class ApplySuite extends KittensSuite: + import ApplySuite.* + import TestDefns.* + + inline given [F[_]]: Isomorphisms[F] = + Isomorphisms.invariant(summonInline[Apply[F]]) + + inline def applyTests[F[_]]: ApplyTests[F] = + ApplyTests[F](summonInline) + + inline def testApply(inline context: String): Unit = + checkAll(s"$context.Apply[CaseClassWOption]", applyTests[CaseClassWOption].apply[Int, String, Long]) + checkAll(s"$context.Apply[OptList]", applyTests[OptList].apply[Int, String, Long]) + checkAll(s"$context.Apply[AndInt]", applyTests[AndInt].apply[Int, String, Long]) + checkAll(s"$context.Apply[Interleaved]", applyTests[Interleaved].apply[Int, String, Long]) + checkAll(s"$context.Apply[ListBox]", applyTests[ListBox].apply[Int, String, Long]) + checkAll(s"$context.Apply is Serializable", SerializableTests.serializable(summonInline[Apply[Interleaved]])) + + locally { + import auto.apply.given + testApply("auto") + } + + locally { + import semiInstances.given + testApply("semiauto") + } + +end ApplySuite + +object ApplySuite: + import TestDefns.* + + type OptList[A] = Option[List[A]] + type AndInt[A] = (A, Int) + type ListBox[A] = List[Box[A]] + + object semiInstances: + given Apply[Box] = semiauto.apply + given Apply[CaseClassWOption] = semiauto.apply + given Apply[OptList] = semiauto.apply + given Apply[AndInt] = semiauto.apply + given Apply[Interleaved] = semiauto.apply + given Apply[ListBox] = semiauto.apply + +end ApplySuite diff --git a/core/src/test/scala-3/cats/derived/CommutativeMonoidSuite.scala b/core/src/test/scala-3/cats/derived/CommutativeMonoidSuite.scala new file mode 100644 index 00000000..c4e14cb5 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/CommutativeMonoidSuite.scala @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016 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.{CommutativeMonoid, CommutativeSemigroup} +import cats.kernel.laws.discipline.{CommutativeMonoidTests, SerializableTests} +import org.scalacheck.Arbitrary + +import scala.compiletime.* + +class CommutativeMonoidSuite extends KittensSuite: + import CommutativeMonoidSuite.* + import TestDefns.* + + inline def commutativeMonoidTests[A]: CommutativeMonoidTests[A] = + CommutativeMonoidTests[A](summonInline) + + inline def testCommutativeMonoid(inline context: String): Unit = + checkAll(s"$context.CommutativeMonoid[Foo]", commutativeMonoidTests[CommutativeFoo].commutativeMonoid) + checkAll(s"$context.CommutativeMonoid[Recursive]", commutativeMonoidTests[Recursive].commutativeMonoid) + checkAll(s"$context.CommutativeMonoid[Box[Mul]]", commutativeMonoidTests[Box[Mul]].commutativeMonoid) + checkAll( + s"$context.CommutativeMonoid is Serializable", + SerializableTests.serializable(summonInline[CommutativeMonoid[CommutativeFoo]]) + ) + test(s"$context.CommutativeMonoid respects existing instances") { + val box = summonInline[CommutativeMonoid[Box[Mul]]] + assert(box.empty == Box(Mul(1))) + assert(box.combine(Box(Mul(5)), Box(Mul(5))) == Box(Mul(25))) + } + + locally { + import auto.commutativeMonoid.given + testCommutativeMonoid("auto") + } + + locally { + import semiInstances.given + testCommutativeMonoid("semiauto") + } + +end CommutativeMonoidSuite + +object CommutativeMonoidSuite: + import TestDefns.* + + object semiInstances: + given CommutativeMonoid[CommutativeFoo] = semiauto.commutativeMonoid + given CommutativeMonoid[Recursive] = semiauto.commutativeMonoid + given CommutativeMonoid[Box[Mul]] = semiauto.commutativeMonoid + + final case class Mul(value: Int) + object Mul: + given Eq[Mul] = Eq.fromUniversalEquals + given Arbitrary[Mul] = Arbitrary(Arbitrary.arbitrary[Int].map(apply)) + given CommutativeMonoid[Mul] with + val empty = Mul(1) + def combine(x: Mul, y: Mul) = Mul(x.value * y.value) + +end CommutativeMonoidSuite diff --git a/core/src/test/scala-3/cats/derived/CommutativeMonoidTests.scala b/core/src/test/scala-3/cats/derived/CommutativeMonoidTests.scala new file mode 100644 index 00000000..96c2914f --- /dev/null +++ b/core/src/test/scala-3/cats/derived/CommutativeMonoidTests.scala @@ -0,0 +1,8 @@ +package cats.derived + +import cats.kernel.CommutativeMonoid +import cats.derived.* + +class CommutativeMonoidTests { // + case class Foo(i: Int, b: Option[Int]) derives CommutativeMonoid +} diff --git a/core/src/test/scala-3/cats/derived/CommutativeSemigroupSuite.scala b/core/src/test/scala-3/cats/derived/CommutativeSemigroupSuite.scala new file mode 100644 index 00000000..1ce03dc2 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/CommutativeSemigroupSuite.scala @@ -0,0 +1,75 @@ +/* + * 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.CommutativeSemigroup +import cats.kernel.laws.discipline.{CommutativeSemigroupTests, SerializableTests} +import org.scalacheck.Arbitrary + +import scala.compiletime.* + +class CommutativeSemigroupSuite extends KittensSuite: + import CommutativeSemigroupSuite.* + import TestDefns.* + + inline def commutativeSemigroupTests[A]: CommutativeSemigroupTests[A] = + CommutativeSemigroupTests[A](summonInline) + + inline def testCommutativeSemigroup(inline context: String): Unit = + checkAll( + s"$context.CommutativeSemigroup[CommutativeFoo]", + commutativeSemigroupTests[CommutativeFoo].commutativeSemigroup + ) + checkAll(s"$context.CommutativeSemigroup[Recursive]", commutativeSemigroupTests[Recursive].commutativeSemigroup) + checkAll(s"$context.CommutativeSemigroup[Box[Mul]]", commutativeSemigroupTests[Box[Mul]].commutativeSemigroup) + checkAll( + s"$context.CommutativeSemigroup is Serializable", + SerializableTests.serializable(summonInline[CommutativeSemigroup[CommutativeFoo]]) + ) + test(s"$context.CommutativeSemigroup respects existing instances") { + val box = summonInline[CommutativeSemigroup[Box[Mul]]] + assert(box.combine(Box(Mul(5)), Box(Mul(5))).content.value == 25) + } + + locally { + import auto.commutativeSemigroup.given + testCommutativeSemigroup("auto") + } + + locally { + import semiInstances.given + testCommutativeSemigroup("semiauto") + } + +end CommutativeSemigroupSuite + +object CommutativeSemigroupSuite: + import TestDefns.* + + object semiInstances: + given CommutativeSemigroup[CommutativeFoo] = semiauto.commutativeSemigroup + given CommutativeSemigroup[Recursive] = semiauto.commutativeSemigroup + given CommutativeSemigroup[Box[Mul]] = semiauto.commutativeSemigroup + + final case class Mul(value: Int) + object Mul: + given Eq[Mul] = Eq.fromUniversalEquals + given Arbitrary[Mul] = Arbitrary(Arbitrary.arbitrary[Int].map(apply)) + given CommutativeSemigroup[Mul] = (x, y) => Mul(x.value * y.value) + +end CommutativeSemigroupSuite diff --git a/core/src/test/scala-3/cats/derived/CommutativeSemigroupTests.scala b/core/src/test/scala-3/cats/derived/CommutativeSemigroupTests.scala new file mode 100644 index 00000000..e790e0cf --- /dev/null +++ b/core/src/test/scala-3/cats/derived/CommutativeSemigroupTests.scala @@ -0,0 +1,8 @@ +package cats.derived + +import cats.kernel.CommutativeSemigroup +import cats.derived.* + +class CommutativeSemigroupTests { // + case class Foo(i: Int, b: Option[Int]) derives CommutativeSemigroup +} diff --git a/core/src/test/scala-3/cats/derived/ContravariantSuite.scala b/core/src/test/scala-3/cats/derived/ContravariantSuite.scala new file mode 100644 index 00000000..bcf5084c --- /dev/null +++ b/core/src/test/scala-3/cats/derived/ContravariantSuite.scala @@ -0,0 +1,119 @@ +/* + * 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 +package derived + +import cats.laws.discipline.* +import cats.laws.discipline.arbitrary.* +import cats.laws.discipline.eq.* +import scala.compiletime.* + +class ContravariantSuite extends KittensSuite: + import ContravariantSuite.* + import TestDefns.* + + inline def contravariantTests[F[_]]: ContravariantTests[F] = + ContravariantTests[F](summonInline) + + inline def testContravariant(context: String): Unit = + checkAll(s"$context.Contravariant[OptPred]", contravariantTests[OptPred].contravariant[MiniInt, String, Boolean]) + checkAll(s"$context.Contravariant[TreePred]", contravariantTests[TreePred].contravariant[MiniInt, String, Boolean]) + checkAll(s"$context.Contravariant[ListPred]", contravariantTests[ListPred].contravariant[MiniInt, String, Boolean]) + checkAll( + s"$context.Contravariant[GenericAdtPred]", + contravariantTests[GenericAdtPred].contravariant[MiniInt, String, Boolean] + ) + // TODO https://github.com/typelevel/kittens/issues/473 + // checkAll( + // s"$context.Contravariant[InterleavedPred]", + // contravariantTests[InterleavedPred].contravariant[MiniInt, String, Boolean] + // ) + checkAll( + s"$context.Contravariant[AndCharPred]", + contravariantTests[AndCharPred].contravariant[MiniInt, String, Boolean] + ) + checkAll( + s"$context.Contravariant[ListSnocF]", + contravariantTests[ListSnocF].contravariant[MiniInt, String, Boolean] + ) + checkAll( + s"$context.Contravariant[EnumK1Contra]", + contravariantTests[EnumK1Contra].contravariant[MiniInt, String, Boolean] + ) + checkAll( + s"$context.Contravariant is Serializable", + SerializableTests.serializable(summonInline[Contravariant[TreePred]]) + ) + + // TODO https://github.com/typelevel/kittens/issues/476 + // test(s"$context.Contravariant.contramap is stack safe") { + // val C = summonInline[Contravariant[ListSnocF]] + // val n = 10000 + // val largeBoxed = Snoc.fromSeq((1 until n).map((j: Int) => (i: Int) => i + j)) :: Nil + // val actualBoxed = C.contramap[Int, Int](largeBoxed)((j: Int) => j + 1).flatMap(Snoc.toList) + // val expected = (3 until n + 2).toList + // assert(actualBoxed.map(_.apply(1)) == expected) + // } + + locally { + import auto.contravariant.given + testContravariant("auto") + } + + locally { + import semiInstances.given + testContravariant("semiauto") + } + +end ContravariantSuite + +object ContravariantSuite: + import TestDefns.* + + type OptPred[A] = Option[A => Boolean] + type ListPred[A] = List[A => Boolean] + type GenericAdtPred[A] = GenericAdt[A => Boolean] + type ListSnocF[A] = List[Snoc[A => Int]] + type InterleavedPred[A] = Interleaved[A => Boolean] + type AndCharPred[A] = (A => Boolean, Char) + type TreePred[A] = Tree[A => Boolean] + + object semiInstances: + given Contravariant[OptPred] = semiauto.contravariant + given Contravariant[TreePred] = semiauto.contravariant + given Contravariant[ListPred] = semiauto.contravariant + given Contravariant[GenericAdtPred] = semiauto.contravariant + // given Contravariant[InterleavedPred] = semiauto.contravariant + given Contravariant[AndCharPred] = semiauto.contravariant + given Contravariant[ListSnocF] = semiauto.contravariant + given Contravariant[EnumK1Contra] = semiauto.contravariant + + case class Single[A](value: A => Unit) derives Contravariant + + enum Many[-A] derives Contravariant: + case Naught + case More(value: A => Unit, rest: Many[A]) + + enum AtMostOne[-A] derives Contravariant: + case Naught + case Single(value: A => Unit) + + enum AtLeastOne[-A] derives Contravariant: + case Single(value: A => Unit) + case More(value: A => Unit, rest: Option[AtLeastOne[A]]) + +end ContravariantSuite diff --git a/core/src/test/scala-3/cats/derived/EmptyKSuite.scala b/core/src/test/scala-3/cats/derived/EmptyKSuite.scala new file mode 100644 index 00000000..f14a3221 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/EmptyKSuite.scala @@ -0,0 +1,80 @@ +/* + * 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 alleycats.{EmptyK, Pure} +import alleycats.std.all.* +import cats.data.NonEmptyList +import cats.laws.discipline.SerializableTests + +import scala.compiletime.summonInline + +class EmptyKSuite extends KittensSuite: + import EmptyKSuite.* + import TestDefns.* + + given Pure[Box] with + def pure[A](a: A) = Box(a) + + inline def emptyK[F[_]] = + summonInline[EmptyK[F]].empty + + inline def testEmptyK(context: String): Unit = + test(s"$context.EmptyK[LOption]")(assert(emptyK[LOption] == Nil)) + test(s"$context.EmptyK[PList]")(assert(emptyK[PList] == (Nil, Nil))) + test(s"$context.EmptyK[CaseClassWOption]")(assert(emptyK[CaseClassWOption] == CaseClassWOption(None))) + test(s"$context.EmptyK[NelOption]")(assert(emptyK[NelOption] == NonEmptyList.one(None))) + test(s"$context.EmptyK[IList]")(assert(emptyK[IList] == INil())) + test(s"$context.EmptyK[Snoc]")(assert(emptyK[Snoc] == SNil())) + test(s"$context.EmptyK respects existing instances")(assert(emptyK[BoxColor] == Box(Color(255, 255, 255)))) + checkAll(s"$context.EmptyK is Serializable", SerializableTests.serializable(summonInline[EmptyK[LOption]])) + + locally { + import auto.emptyK.given + testEmptyK("auto") + } + + locally { + import semiInstances.given + testEmptyK("semiauto") + } + +end EmptyKSuite + +object EmptyKSuite: + import TestDefns.* + + type LOption[A] = List[Option[A]] + type PList[A] = (List[A], List[A]) + type NelOption[A] = NonEmptyList[Option[A]] + type BoxColor[A] = Box[Color[A]] + + object semiInstances: + given EmptyK[LOption] = semiauto.emptyK + given EmptyK[PList] = semiauto.emptyK + given EmptyK[CaseClassWOption] = semiauto.emptyK + given EmptyK[NelOption] = semiauto.emptyK + given EmptyK[IList] = semiauto.emptyK + given EmptyK[Snoc] = semiauto.emptyK + given EmptyK[BoxColor] = semiauto.emptyK + + final case class Color[A](r: Int, g: Int, b: Int) + object Color: + given EmptyK[Color] with + def empty[A] = Color(255, 255, 255) + +end EmptyKSuite diff --git a/core/src/test/scala-3/cats/derived/EmptyKTests.scala b/core/src/test/scala-3/cats/derived/EmptyKTests.scala new file mode 100644 index 00000000..3234d74a --- /dev/null +++ b/core/src/test/scala-3/cats/derived/EmptyKTests.scala @@ -0,0 +1,10 @@ +package cats.derived + +import alleycats.* +import alleycats.std.all.* +import cats.* +import cats.derived.* + +class EmptyKTests { // + case class Foo[A](i: String, l: List[A]) derives EmptyK +} diff --git a/core/src/test/scala-3/cats/derived/EmptySuite.scala b/core/src/test/scala-3/cats/derived/EmptySuite.scala new file mode 100644 index 00000000..2886b13f --- /dev/null +++ b/core/src/test/scala-3/cats/derived/EmptySuite.scala @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016 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 alleycats.Empty +import cats.laws.discipline.SerializableTests + +import scala.compiletime.* + +class EmptySuite extends KittensSuite: + import EmptySuite.given + import EmptySuite.* + import TestDefns.* + + inline def empty[A]: A = + summonInline[Empty[A]].empty + + inline def testEmpty(inline context: String): Unit = + test(s"$context.Empty[Foo]")(assert(empty[Foo] == Foo(0, None))) + test(s"$context.Empty[Outer]")(assert(empty[Outer] == Outer(Inner(0)))) + test(s"$context.Empty[Interleaved[String]]")(assert(empty[Interleaved[String]] == Interleaved.empty(""))) + test(s"$context.Empty[Recursive]")(assert(empty[Recursive] == Recursive(0, None))) + test(s"$context.Empty[IList[Dummy]]")(assert(empty[IList[Dummy]] == INil())) + test(s"$context.Empty[Snoc[Dummy]]")(assert(empty[Snoc[Dummy]] == SNil())) + test(s"$context.Empty respects existing instances")(assert(empty[Box[Mask]] == Box(Mask(0xffffffff)))) + checkAll(s"$context.Empty is Serializable", SerializableTests.serializable(summonInline[Empty[Foo]])) + + locally { + import auto.empty.given + testEmpty("auto") + testNoAuto("Empty", "IList[Int]") + testNoAuto("Empty", "Snoc[Int]") + testNoAuto("Empty", "Rgb") + } + + locally { + import semiInstances.given + testEmpty("semiauto") + testNoSemi("Empty", "IList[Int]") + testNoSemi("Empty", "Snoc[Int]") + testNoSemi("Empty", "Rgb") + } + +end EmptySuite + +object EmptySuite: + import TestDefns.* + + // `Monoid[Option[A]]` gives us `Empty[Option[A]]` but it requires a `Semigroup[A]`. + given [A]: Empty[Option[A]] = Empty(None) + + object semiInstances: + given Empty[Foo] = semiauto.empty + given Empty[Outer] = semiauto.empty + given Empty[Interleaved[String]] = semiauto.empty + given Empty[Recursive] = semiauto.empty + given Empty[IList[Dummy]] = semiauto.empty + given Empty[Snoc[Dummy]] = semiauto.empty + given Empty[Box[Mask]] = semiauto.empty + given Empty[Chain] = semiauto.empty + + trait Dummy + final case class Chain(head: Int, tail: Chain) + final case class Mask(bits: Int) + object Mask: + given Empty[Mask] = Empty(Mask(0xffffffff)) + +end EmptySuite diff --git a/core/src/test/scala-3/cats/derived/EmptyTests.scala b/core/src/test/scala-3/cats/derived/EmptyTests.scala new file mode 100644 index 00000000..cc1e6c83 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/EmptyTests.scala @@ -0,0 +1,11 @@ +package cats.derived + +import alleycats.* +import cats.* +import cats.derived.* + +object EmptyTests: + case class Foo(i: Int, b: IntTree) derives Empty + enum IntTree: + case Leaf + case Node(left: IntTree, value: Int, right: IntTree) diff --git a/core/src/test/scala-3/cats/derived/EqSuite.scala b/core/src/test/scala-3/cats/derived/EqSuite.scala new file mode 100644 index 00000000..adf99d51 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/EqSuite.scala @@ -0,0 +1,65 @@ +/* + * 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) + 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 diff --git a/core/src/test/scala-3/cats/derived/EqTests.scala b/core/src/test/scala-3/cats/derived/EqTests.scala new file mode 100644 index 00000000..b9a128ce --- /dev/null +++ b/core/src/test/scala-3/cats/derived/EqTests.scala @@ -0,0 +1,9 @@ +package cats.derived + +import alleycats.* +import cats.* +import cats.derived.* + +class EqTests { // + case class Foo(i: Int, b: Option[String]) derives Eq +} diff --git a/core/src/test/scala-3/cats/derived/FoldableSuite.scala b/core/src/test/scala-3/cats/derived/FoldableSuite.scala new file mode 100644 index 00000000..0b748f3a --- /dev/null +++ b/core/src/test/scala-3/cats/derived/FoldableSuite.scala @@ -0,0 +1,104 @@ +/* + * 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 +package derived + +import cats.laws.discipline.{FoldableTests, SerializableTests} +import cats.syntax.all.* +import org.scalacheck.Arbitrary +import scala.compiletime.* + +class FoldableSuite extends KittensSuite: + import FoldableSuite.* + import TestDefns.* + + inline def foldableTests[F[_]]: FoldableTests[F] = + FoldableTests[F](summonInline) + + inline def testFoldable(inline context: String): Unit = + checkAll(s"$context.Foldable[IList]", foldableTests[IList].foldable[Int, Long]) + checkAll(s"$context.Foldable[Tree]", foldableTests[Tree].foldable[Int, Long]) + checkAll(s"$context.Foldable[GenericAdt]", foldableTests[GenericAdt].foldable[Int, Long]) + checkAll(s"$context.Foldable[OptList]", foldableTests[OptList].foldable[Int, Long]) + checkAll(s"$context.Foldable[ListSnoc]", foldableTests[ListSnoc].foldable[Int, Long]) + checkAll(s"$context.Foldable[AndChar]", foldableTests[AndChar].foldable[Int, Long]) + checkAll(s"$context.Foldable[Interleaved]", foldableTests[Interleaved].foldable[Int, Long]) + checkAll(s"$context.Foldable[BoxNel]", foldableTests[BoxNel].foldable[Int, Long]) + checkAll(s"$context.Foldable[EnumK1]", foldableTests[EnumK1].foldable[Int, Long]) + checkAll(s"$context.Foldable is Serializable", SerializableTests.serializable(summonInline[Foldable[Tree]])) + + locally { + import auto.foldable.given + testFoldable("auto") + } + + locally { + import semiInstances.given + testFoldable("semiauto") + } + +end FoldableSuite + +object FoldableSuite: + import TestDefns.* + + type OptList[A] = Option[List[A]] + type ListSnoc[A] = List[Snoc[A]] + type AndChar[A] = (A, Char) + type BoxNel[A] = Box[Nel[A]] + + object semiInstances: + given Foldable[IList] = semiauto.foldable + given Foldable[Tree] = semiauto.foldable + given Foldable[GenericAdt] = semiauto.foldable + given Foldable[OptList] = semiauto.foldable + given Foldable[ListSnoc] = semiauto.foldable + given Foldable[AndChar] = semiauto.foldable + given Foldable[Interleaved] = semiauto.foldable + given Foldable[BoxNel] = semiauto.foldable + given Foldable[EnumK1] = semiauto.foldable + + final case class Nel[+A](head: A, tail: List[A]) + object Nel: + given [A: Eq]: Eq[Nel[A]] = + (x, y) => x.head === y.head && x.tail === y.tail + + given [A: Arbitrary]: Arbitrary[Nel[A]] = + Arbitrary(for + head <- Arbitrary.arbitrary[A] + tail <- Arbitrary.arbitrary[List[A]] + yield Nel(head, tail)) + + given Foldable[Nel] with + def foldLeft[A, B](fa: Nel[A], b: B)(f: (B, A) => B) = fa.tail.foldl(b)(f) + def foldRight[A, B](fa: Nel[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]) = fa.tail.foldr(lb)(f) + + case class Single[A](value: A) derives Foldable + + enum Many[+A] derives Foldable: + case Naught + case More(value: A, rest: Many[A]) + + enum AtMostOne[+A] derives Foldable: + case Naught + case Single(value: A) + + enum AtLeastOne[+A] derives Foldable: + case Single(value: A) + case More(value: A, rest: Option[AtLeastOne[A]]) + +end FoldableSuite diff --git a/core/src/test/scala-3/cats/derived/FunctorSuite.scala b/core/src/test/scala-3/cats/derived/FunctorSuite.scala new file mode 100644 index 00000000..c4d64abe --- /dev/null +++ b/core/src/test/scala-3/cats/derived/FunctorSuite.scala @@ -0,0 +1,92 @@ +/* + * 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 +package derived + +import cats.laws.discipline.* +import cats.laws.discipline.eq.* +import scala.compiletime.* + +class FunctorSuite extends KittensSuite: + import FunctorSuite.* + import TestDefns.* + + given ExhaustiveCheck[Predicate[Boolean]] = + ExhaustiveCheck.instance(List(_ => true, _ => false, identity, !_)) + + inline def functorTests[F[_]]: FunctorTests[F] = + FunctorTests[F](summonInline) + + inline def testFunctor(inline context: String): Unit = + checkAll(s"$context.Functor[IList]", functorTests[IList].functor[Int, String, Long]) + checkAll(s"$context.Functor[Tree]", functorTests[Tree].functor[Int, String, Long]) + checkAll(s"$context.Functor[GenericAdt]", functorTests[GenericAdt].functor[Int, String, Long]) + checkAll(s"$context.Functor[OptList]", functorTests[OptList].functor[Int, String, Long]) + checkAll(s"$context.Functor[ListSnoc]", functorTests[ListSnoc].functor[Int, String, Long]) + checkAll(s"$context.Functor[AndChar]", functorTests[AndChar].functor[Int, String, Long]) + checkAll(s"$context.Functor[Interleaved]", functorTests[Interleaved].functor[Int, String, Long]) + checkAll(s"$context.Functor[NestedPred]", functorTests[NestedPred].functor[Boolean, Int, Boolean]) + checkAll(s"$context.Functor[EnumK1]", functorTests[EnumK1].functor[Boolean, Int, Boolean]) + checkAll(s"$context.Functor is Serializable", SerializableTests.serializable(summonInline[Functor[Tree]])) + + locally { + import auto.functor.given + testFunctor("auto") + } + + locally { + import semiInstances.given + testFunctor("semiauto") + } + +end FunctorSuite + +object FunctorSuite: + import TestDefns.* + + type OptList[A] = Option[List[A]] + type ListSnoc[A] = List[Snoc[A]] + type AndChar[A] = (A, Char) + type Predicate[A] = A => Boolean + type NestedPred[A] = Predicate[Predicate[A]] + + object semiInstances: + given Functor[IList] = semiauto.functor + given Functor[Tree] = semiauto.functor + given Functor[GenericAdt] = semiauto.functor + given Functor[OptList] = semiauto.functor + given Functor[ListSnoc] = semiauto.functor + given Functor[AndChar] = semiauto.functor + given Functor[Interleaved] = semiauto.functor + given Functor[NestedPred] = semiauto.functor + given Functor[EnumK1] = semiauto.functor + + case class Single[A](value: A) derives Functor + + enum Many[+A] derives Functor: + case Naught + case More(value: A, rest: Many[A]) + + enum AtMostOne[+A] derives Functor: + case Naught + case Single(value: A) + + enum AtLeastOne[+A] derives Functor: + case Single(value: A) + case More(value: A, rest: Option[AtLeastOne[A]]) + +end FunctorSuite diff --git a/core/src/test/scala-3/cats/derived/HashSuite.scala b/core/src/test/scala-3/cats/derived/HashSuite.scala new file mode 100644 index 00000000..d222fbb7 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/HashSuite.scala @@ -0,0 +1,51 @@ +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) + checkAll(s"$context.Hash[Recursive]", hashTests[Recursive].hash) + checkAll(s"$context.Hash[EnumK0]", hashTests[EnumK0].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 + given Hash[EnumK0] = semiauto.hash + +end HashSuite diff --git a/core/src/test/scala-3/cats/derived/HashTests.scala b/core/src/test/scala-3/cats/derived/HashTests.scala new file mode 100644 index 00000000..49de408d --- /dev/null +++ b/core/src/test/scala-3/cats/derived/HashTests.scala @@ -0,0 +1,9 @@ +package cats.derived + +import alleycats.* +import cats.* +import cats.derived.* + +class HashTests { // + case class Foo(i: Int, b: Option[String]) derives Hash +} diff --git a/core/src/test/scala-3/cats/derived/InvariantSuite.scala b/core/src/test/scala-3/cats/derived/InvariantSuite.scala new file mode 100644 index 00000000..5a99b794 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/InvariantSuite.scala @@ -0,0 +1,108 @@ +/* + * 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 +package derived + +import cats.laws.discipline.* +import cats.laws.discipline.arbitrary.* +import cats.laws.discipline.eq.* +import cats.laws.discipline.* + +import scala.compiletime.* + +class InvariantSuite extends KittensSuite: + import InvariantSuite.* + import TestDefns.* + + inline def invariantTests[F[_]]: InvariantTests[F] = + InvariantTests[F](summonInline) + + inline def testInvariant(context: String): Unit = { + checkAll(s"$context.Invariant[TreeF]", invariantTests[TreeF].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant[GenAdtF]", invariantTests[GenericAdtF].invariant[MiniInt, String, Boolean]) + // TODO https://github.com/typelevel/kittens/issues/473 + // checkAll(s"$context.Invariant[InterleavedF]", invariantTests[InterleavedF].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant[AndCharF]", invariantTests[AndCharF].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant[ListSnoc", invariantTests[ListSnoc].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant[Bivariant]", invariantTests[Bivariant].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant[EnumK1Inv]", invariantTests[EnumK1Inv].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant is Serializable", SerializableTests.serializable(summonInline[Invariant[TreeF]])) + + // TODO https://github.com/typelevel/kittens/issues/476 + // test(s"$context.Invariant.imap is stack safe") { + // val I = summonInline[Invariant[ListSnoc]] + // val J = summonInline[Invariant[IList]] + // val n = 10000 + // val largeIList = IList.fromSeq(1 until n) + // val largeSnoc = Snoc.fromSeq(1 until n) :: Nil + // val actualIList = IList.toList(J.imap(largeIList)(_ + 1)(_ - 1)) + // val actualSnoc = I.imap(largeSnoc)(_ + 1)(_ - 1).flatMap(Snoc.toList) + // val expected = (2 until n + 1).toList + // assert(actualIList == expected) + // assert(actualSnoc == expected) + // } + } + + locally { + import auto.invariant.given + testInvariant("auto") + } + + locally { + import semiInstances.given + testInvariant("semiauto") + } + +end InvariantSuite + +object InvariantSuite: + import TestDefns.* + + type ListSnoc[A] = List[Snoc[A]] + type GenericAdtF[A] = GenericAdt[A => Boolean] + type ListFToInt[A] = List[Snoc[A => Int]] + type InterleavedF[A] = Interleaved[A => Boolean] + type AndCharF[A] = (A => Boolean, Char) + type TreeF[A] = Tree[A => Boolean] + + object semiInstances: + given Invariant[GenericAdtF] = semiauto.invariant + given Invariant[ListFToInt] = semiauto.invariant + // given Invariant[InterleavedF] = semiauto.invariant + given Invariant[AndCharF] = semiauto.invariant + given Invariant[TreeF] = semiauto.invariant + given Invariant[Pred] = semiauto.invariant + given Invariant[ListSnoc] = semiauto.invariant + given Invariant[Bivariant] = semiauto.invariant + given Invariant[IList] = semiauto.invariant + given Invariant[EnumK1Inv] = semiauto.invariant + + case class Single[A](value: A) derives Invariant + + enum Many[A] derives Invariant: + case Naught() + case More(value: A, rest: Many[A]) + + enum AtMostOne[A] derives Invariant: + case Naught() + case Single(value: A) + + enum AtLeastOne[A] derives Invariant: + case Single(value: A) + case More(value: A, rest: Option[AtLeastOne[A]]) + +end InvariantSuite diff --git a/core/src/test/scala-3/cats/derived/KittensSuite.scala b/core/src/test/scala-3/cats/derived/KittensSuite.scala new file mode 100644 index 00000000..672656be --- /dev/null +++ b/core/src/test/scala-3/cats/derived/KittensSuite.scala @@ -0,0 +1,59 @@ +/* + * 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.platform.Platform +import cats.syntax.AllSyntax +import munit.DisciplineSuite +import org.scalacheck.Arbitrary +import org.scalacheck.Test.Parameters + +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 KittensSuite.WithoutEq, TestEqInstances +object KittensSuite: + def deCapitalizeMacro(str: Expr[String])(using Quotes) = + val value = str.valueOrAbort + Expr(if (value.isEmpty) "" else value.head.toLower +: value.tail) + + inline def deCapitalize(inline str: String): String = + ${ deCapitalizeMacro('str) } + + /** 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 testNoInstance(inline tc: String, target: String, message: String): Unit = + val errors = compileErrors(tc + "[" + target + "]") + test(s"No $tc for $target")(assert(errors.contains(message), s"$errors did not contain $message")) + + inline def testNoAuto(inline tc: String, target: String): Unit = + testNoInstance(tc, target, "No given instance of type") + + inline def testNoSemi(inline tc: String, target: String): Unit = + testNoInstance("semiauto." + deCapitalize(tc), target, "Could not derive an instance of " + tc) diff --git a/core/src/test/scala-3/cats/derived/MonoidKSuite.scala b/core/src/test/scala-3/cats/derived/MonoidKSuite.scala new file mode 100644 index 00000000..84890097 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/MonoidKSuite.scala @@ -0,0 +1,62 @@ +package cats.derived + +import alleycats.* +import cats.* +import cats.laws.discipline.{MonoidKTests, SerializableTests} +import org.scalacheck.Arbitrary +import scala.compiletime.* + +class MonoidKSuite extends KittensSuite: + import MonoidKSuite.* + import TestDefns.* + + inline def monoidKTests[F[_]]: MonoidKTests[F] = + MonoidKTests[F](summonInline) + + inline def testMonoidK(context: String): Unit = + checkAll(s"$context.MonoidK[ComplexProduct]", monoidKTests[ComplexProduct].monoidK[Char]) + checkAll(s"$context.MonoidK[CaseClassWOption]", monoidKTests[CaseClassWOption].monoidK[Char]) + checkAll(s"$context.MonoidK[BoxMul]", monoidKTests[BoxMul].monoidK[Char]) + checkAll(s"$context.MonoidK is Serializable", SerializableTests.serializable(summonInline[MonoidK[ComplexProduct]])) + test(s"$context.MonoidK respects existing instances") { + val M = summonInline[MonoidK[BoxMul]] + assert(M.empty[Char] == Box(Mul[Char](1))) + assert(M.combineK(Box(Mul[Char](5)), Box(Mul[Char](5))) == Box(Mul[Char](25))) + } + + locally { + import auto.monoidK.given + testMonoidK("auto") + } + + locally { + import monInstances.given + testMonoidK("semi") + } + +end MonoidKSuite + +object MonoidKSuite: + import TestDefns.* + + type BoxMul[A] = Box[Mul[A]] + + object monInstances: + given MonoidK[ComplexProduct] = semiauto.monoidK + given MonoidK[CaseClassWOption] = semiauto.monoidK + given MonoidK[BoxMul] = semiauto.monoidK + + final case class Mul[T](value: Int) + object Mul: + given [T]: Eq[Mul[T]] = Eq.by(_.value) + + given [T]: Arbitrary[Mul[T]] = Arbitrary(Arbitrary.arbitrary[Int].map(apply)) + + given MonoidK[Mul] with + def empty[A] = Mul(1) + def combineK[A](x: Mul[A], y: Mul[A]) = Mul(x.value * y.value) + + case class Simple[A](value1: List[A], value2: Set[A]) derives MonoidK + case class Recursive[A](first: List[A], rest: Recursive[A]) derives MonoidK + +end MonoidKSuite diff --git a/core/src/test/scala-3/cats/derived/MonoidSuite.scala b/core/src/test/scala-3/cats/derived/MonoidSuite.scala new file mode 100644 index 00000000..f19417f8 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/MonoidSuite.scala @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016 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, Monoid} +import cats.kernel.laws.discipline.{MonoidTests, SerializableTests} +import org.scalacheck.Arbitrary + +import scala.compiletime.* + +class MonoidSuite extends KittensSuite: + import MonoidSuite.* + import TestDefns.* + + inline def monoidTests[A]: MonoidTests[A] = + MonoidTests[A](summonInline) + + inline def testMonoid(inline context: String): Unit = + checkAll(s"$context.Monoid[Foo]", monoidTests[Foo].monoid) + checkAll(s"$context.Monoid[Interleaved[Int]]", monoidTests[Interleaved[Int]].monoid) + checkAll(s"$context.Monoid[Box[Mul]]", monoidTests[Box[Mul]].monoid) + checkAll(s"$context.Monoid[Recursive]", monoidTests[Recursive].monoid) + checkAll(s"$context.Monoid is Serializable", SerializableTests.serializable(summonInline[Monoid[Foo]])) + test(s"$context.Monoid respects existing instances") { + val box = summonInline[Monoid[Box[Mul]]] + assert(box.empty == Box(Mul(1))) + assert(box.combine(Box(Mul(5)), Box(Mul(5))) == Box(Mul(25))) + } + + locally { + import auto.monoid.given + testMonoid("auto") + } + + locally { + import semiInstances.given + testMonoid("semiauto") + } + +end MonoidSuite + +object MonoidSuite: + import TestDefns.* + + object semiInstances: + given Monoid[Foo] = semiauto.monoid + given Monoid[Recursive] = semiauto.monoid + given Monoid[Interleaved[Int]] = semiauto.monoid + given Monoid[Box[Mul]] = semiauto.monoid + + final case class Mul(value: Int) + object Mul: + given Eq[Mul] = Eq.fromUniversalEquals + given Arbitrary[Mul] = Arbitrary(Arbitrary.arbitrary[Int].map(apply)) + given Monoid[Mul] with + val empty = Mul(1) + def combine(x: Mul, y: Mul) = Mul(x.value * y.value) + +end MonoidSuite diff --git a/core/src/test/scala-3/cats/derived/MonoidTests.scala b/core/src/test/scala-3/cats/derived/MonoidTests.scala new file mode 100644 index 00000000..412a36d1 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/MonoidTests.scala @@ -0,0 +1,9 @@ +package cats.derived + +import alleycats.* +import cats.* +import cats.derived.* + +class MonoidTests { // + case class Foo(i: Int, b: Option[String]) derives Monoid, Empty, Semigroup +} diff --git a/core/src/test/scala-3/cats/derived/NonEmptyTraverseSuite.scala b/core/src/test/scala-3/cats/derived/NonEmptyTraverseSuite.scala new file mode 100644 index 00000000..b8a37cf4 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/NonEmptyTraverseSuite.scala @@ -0,0 +1,97 @@ +/* + * 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, NonEmptyTraverse} +import cats.data.{NonEmptyList, NonEmptyVector, OneAnd} +import cats.laws.discipline.arbitrary.* +import cats.laws.discipline.{NonEmptyTraverseTests, SerializableTests} +import cats.syntax.all.* +import org.scalacheck.Arbitrary + +import scala.compiletime.* + +class NonEmptyTraverseSuite extends KittensSuite: + import NonEmptyTraverseSuite.* + import TestDefns.* + + inline def nonEmptyTraverseTests[F[_]]: NonEmptyTraverseTests[F] = + NonEmptyTraverseTests[F](summonInline) + + inline def testReducible(inline context: String): Unit = + checkAll( + s"$context.NonEmptyTraverse[ICons]", + nonEmptyTraverseTests[ICons].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] + ) + checkAll( + s"$context.NonEmptyTraverse[Tree]", + nonEmptyTraverseTests[Tree].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] + ) + checkAll( + s"$context.NonEmptyTraverse[NelSCons]", + nonEmptyTraverseTests[NelSCons].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] + ) + checkAll( + s"$context.NonEmptyTraverse[NelAndOne]", + nonEmptyTraverseTests[NelAndOne].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] + ) + checkAll( + s"$context.NonEmptyTraverse[VecAndNel]", + nonEmptyTraverseTests[VecAndNel].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] + ) + checkAll( + s"$context.NonEmptyTraverse[Interleaved]", + nonEmptyTraverseTests[Interleaved].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] + ) + checkAll( + s"$context.NonEmptyTraverse[EnumK1]", + nonEmptyTraverseTests[EnumK1].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] + ) + checkAll( + s"$context.NonEmptyTraverse is Serializable", + SerializableTests.serializable(summonInline[NonEmptyTraverse[Tree]]) + ) + + locally { + import auto.nonEmptyTraverse.given + testReducible("auto") + } + + locally { + import semiInstances.given + testReducible("semiauto") + } + +end NonEmptyTraverseSuite + +object NonEmptyTraverseSuite: + import TestDefns.* + + type NelSCons[A] = NonEmptyList[SCons[A]] + type NelAndOne[A] = NonEmptyList[OneAnd[Vector, A]] + type VecAndNel[A] = (Vector[A], NonEmptyList[A]) + + object semiInstances: + given NonEmptyTraverse[ICons] = semiauto.nonEmptyTraverse + given NonEmptyTraverse[Tree] = semiauto.nonEmptyTraverse + given NonEmptyTraverse[NelSCons] = semiauto.nonEmptyTraverse + given NonEmptyTraverse[NelAndOne] = semiauto.nonEmptyTraverse + given NonEmptyTraverse[VecAndNel] = semiauto.nonEmptyTraverse + given NonEmptyTraverse[Interleaved] = semiauto.nonEmptyTraverse + given NonEmptyTraverse[EnumK1] = semiauto.nonEmptyTraverse + +end NonEmptyTraverseSuite diff --git a/core/src/test/scala-3/cats/derived/OrderSuite.scala b/core/src/test/scala-3/cats/derived/OrderSuite.scala new file mode 100644 index 00000000..e0b7c031 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/OrderSuite.scala @@ -0,0 +1,63 @@ +/* + * 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.Order +import cats.kernel.laws.discipline.{OrderTests, SerializableTests} +import org.scalacheck.Arbitrary +import scala.compiletime.summonInline + +class OrderSuite extends KittensSuite { + import OrderSuite._ + import TestDefns._ + + inline def orderTests[A]: OrderTests[A] = + OrderTests[A](summonInline) + + inline def testOrder(context: String): Unit = { + checkAll(s"$context.Order[Inner]", orderTests[Inner].order) + checkAll(s"$context.Order[Outer]", orderTests[Outer].order) + checkAll(s"$context.Order[Interleaved[Int]]", orderTests[Interleaved[Int]].order) + checkAll(s"$context.Order[Recursive]", orderTests[Recursive].order) + checkAll(s"$context.Order[GenericAdt[Int]]", orderTests[GenericAdt[Int]].order) + checkAll(s"$context.Order[EnumK0]", orderTests[EnumK0].order) + checkAll(s"$context.Order is Serializable", SerializableTests.serializable(summonInline[Order[Interleaved[Int]]])) + } + + { + import auto.order.given + testOrder("auto") + } + + { + import semiInstances.given + testOrder("semiauto") + } +} + +object OrderSuite { + import TestDefns._ + + object semiInstances { + given Order[Inner] = semiauto.order + given Order[Outer] = semiauto.order + given Order[Interleaved[Int]] = semiauto.order + given Order[Recursive] = semiauto.order + given Order[GenericAdt[Int]] = semiauto.order + given Order[EnumK0] = semiauto.order + } +} diff --git a/core/src/test/scala-3/cats/derived/OrderTests.scala b/core/src/test/scala-3/cats/derived/OrderTests.scala new file mode 100644 index 00000000..01e7d855 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/OrderTests.scala @@ -0,0 +1,9 @@ +package cats.derived + +import alleycats.* +import cats.* +import cats.derived.* + +class OrderTests { // + case class Foo(i: Int, b: Option[String]) derives Order +} diff --git a/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala b/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala new file mode 100644 index 00000000..af35587b --- /dev/null +++ b/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala @@ -0,0 +1,88 @@ +/* + * 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 +package derived + +import cats.kernel.laws.discipline.{PartialOrderTests, SerializableTests} +import org.scalacheck.{Arbitrary, Cogen} +import scala.compiletime.* + +class PartialOrderSuite extends KittensSuite: + import PartialOrderSuite.* + import TestDefns.* + + inline def partialOrderTests[A]: PartialOrderTests[A] = + PartialOrderTests[A](summonInline) + + inline def testPartialOrder(context: String): Unit = + checkAll(s"$context.PartialOrder[IList[Int]]", partialOrderTests[IList[Int]].partialOrder) + checkAll(s"$context.PartialOrder[Inner]", partialOrderTests[Inner].partialOrder) + checkAll(s"$context.PartialOrder[Outer]", partialOrderTests[Outer].partialOrder) + checkAll(s"$context.PartialOrder[Interleaved[Int]]", partialOrderTests[Interleaved[Int]].partialOrder) + checkAll(s"$context.PartialOrder[Tree[Int]]", partialOrderTests[Tree[Int]].partialOrder) + checkAll(s"$context.PartialOrder[Recursive]", partialOrderTests[Recursive].partialOrder) + checkAll(s"$context.PartialOrder[Box[KeyValue]]", partialOrderTests[Box[KeyValue]].partialOrder) + checkAll(s"$context.PartialOrder[EnumK0]", partialOrderTests[EnumK0].partialOrder) + checkAll( + s"$context.PartialOrder is Serialiable", + SerializableTests.serializable(summonInline[PartialOrder[Tree[Int]]]) + ) + + test(s"$context.PartialOrder respects existing instances") { + val boxKeyValue = summonInline[PartialOrder[Box[KeyValue]]] + val x = Box(KeyValue("red", 1)) + val y = Box(KeyValue("red", 2)) + val z = Box(KeyValue("blue", 1)) + assert(boxKeyValue.partialCompare(x, y) < 0) + assert(boxKeyValue.partialCompare(y, z).isNaN) + } + + locally { + import auto.partialOrder.given + testPartialOrder("auto") + } + + locally { + import semiInstances.given + testPartialOrder("semiauto") + } + +end PartialOrderSuite + +object PartialOrderSuite: + import TestDefns.* + + object semiInstances: + given PartialOrder[IList[Int]] = semiauto.partialOrder + given PartialOrder[Inner] = semiauto.partialOrder + given PartialOrder[Outer] = semiauto.partialOrder + given PartialOrder[Interleaved[Int]] = semiauto.partialOrder + given PartialOrder[Tree[Int]] = semiauto.partialOrder + given PartialOrder[Recursive] = semiauto.partialOrder + given PartialOrder[Box[KeyValue]] = semiauto.partialOrder + given PartialOrder[EnumK0] = semiauto.partialOrder + + final case class KeyValue(key: String, value: Int) + object KeyValue extends ((String, Int) => KeyValue): + given Arbitrary[KeyValue] = Arbitrary(Arbitrary.arbitrary[(String, Int)].map(tupled)) + + given Cogen[KeyValue] = Cogen[(String, Int)].contramap(kv => kv.key -> kv.value) + + given PartialOrder[KeyValue] = + PartialOrder.from((x, y) => if (x.key == y.key) x.value.toDouble - y.value.toDouble else Double.NaN) + +end PartialOrderSuite diff --git a/core/src/test/scala-3/cats/derived/PureSuite.scala b/core/src/test/scala-3/cats/derived/PureSuite.scala new file mode 100644 index 00000000..8c2c9145 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/PureSuite.scala @@ -0,0 +1,79 @@ +/* + * 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 alleycats.Pure +import cats.data.NonEmptyList +import cats.laws.discipline.SerializableTests + +import scala.compiletime.summonInline + +class PureSuite extends KittensSuite: + import PureSuite.* + import TestDefns.* + + extension [A](a: A) + inline def pure[F[_]] = + summonInline[Pure[F]].pure(a) + + inline def testPure(inline context: String): Unit = + test(s"$context.Pure[LOption]")(assert(42.pure[LOption] == Some(42) :: Nil)) + test(s"$context.Pure[PList]")(assert("Scala".pure[PList] == ("Scala" :: Nil, "Scala" :: Nil))) + test(s"$context.Pure[CaseClassWOption]")(assert(3.14.pure[CaseClassWOption] == CaseClassWOption(Some(3.14)))) + test(s"$context.Pure[NelOption]")(assert(42.pure[NelOption] == NonEmptyList.of(Some(42)))) + test(s"$context.Pure[Interleaved]")(assert('x'.pure[Interleaved] == Interleaved(0, 'x', 0, Vector('x'), ""))) + test(s"$context.Pure respects existing instances")(assert(().pure[BoxColor] == Box(Color(255, 255, 255)))) + checkAll(s"$context.Pure is Serializable", SerializableTests.serializable(summonInline[Pure[Interleaved]])) + + locally { + import auto.pure.given + testPure("auto") + testNoAuto("Pure", "IList") + testNoAuto("Pure", "Snoc") + } + + locally { + import semiInstances.given + testPure("semiauto") + testNoSemi("Pure", "IList") + testNoSemi("Pure", "Snoc") + } + +end PureSuite + +object PureSuite: + import TestDefns.* + + type LOption[A] = List[Option[A]] + type PList[A] = (List[A], List[A]) + type NelOption[A] = NonEmptyList[Option[A]] + type BoxColor[A] = Box[Color[A]] + + object semiInstances: + given Pure[LOption] = semiauto.pure + given Pure[PList] = semiauto.pure + given Pure[CaseClassWOption] = semiauto.pure + given Pure[NelOption] = semiauto.pure + given Pure[Interleaved] = semiauto.pure + given Pure[BoxColor] = semiauto.pure + + final case class Color[A](r: Int, g: Int, b: Int) + object Color: + given Pure[Color] with + def pure[A](value: A) = Color(255, 255, 255) + +end PureSuite diff --git a/core/src/test/scala-3/cats/derived/ReducibleSuite.scala b/core/src/test/scala-3/cats/derived/ReducibleSuite.scala new file mode 100644 index 00000000..b3b6f8cf --- /dev/null +++ b/core/src/test/scala-3/cats/derived/ReducibleSuite.scala @@ -0,0 +1,97 @@ +/* + * 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 +package derived + +import cats.data.{NonEmptyList, OneAnd} +import cats.laws.discipline.arbitrary.* +import cats.laws.discipline.{ReducibleTests, SerializableTests} +import cats.syntax.all.* +import org.scalacheck.Arbitrary +import scala.compiletime.* + +class ReducibleSuite extends KittensSuite: + import ReducibleSuite.* + import TestDefns.* + + inline def reducibleTests[F[_]]: ReducibleTests[F] = + ReducibleTests[F](summonInline) + + inline def testReducible(context: String): Unit = + checkAll(s"$context.Reducible[ICons]", reducibleTests[ICons].reducible[Option, Int, Long]) + checkAll(s"$context.Reducible[Tree]", reducibleTests[Tree].reducible[Option, Int, Long]) + checkAll(s"$context.Reducible[NelSCons]", reducibleTests[NelSCons].reducible[Option, Int, Long]) + checkAll(s"$context.Reducible[NelAndOne]", reducibleTests[NelAndOne].reducible[Option, Int, Long]) + checkAll(s"$context.Reducible[VecAndNel]", reducibleTests[VecAndNel].reducible[Option, Int, Long]) + checkAll(s"$context.Reducible[Interleaved]", reducibleTests[Interleaved].reducible[Option, Int, Long]) + checkAll(s"$context.Reducible[BoxZipper]", reducibleTests[BoxZipper].reducible[Option, Int, Long]) + checkAll(s"$context.Reducible[EnumK1]", reducibleTests[EnumK1].reducible[Option, Int, Long]) + checkAll(s"$context.Reducible is Serializable", SerializableTests.serializable(summonInline[Reducible[Tree]])) + + locally { + import auto.reducible.given + testReducible("auto") + } + + locally { + import semiInstances.given + testReducible("semiauto") + } + +end ReducibleSuite + +object ReducibleSuite: + import TestDefns.* + + type NelSCons[A] = NonEmptyList[SCons[A]] + type NelAndOne[A] = NonEmptyList[OneAnd[Vector, A]] + type VecAndNel[A] = (Vector[A], NonEmptyList[A]) + type BoxZipper[A] = Box[Zipper[A]] + + object semiInstances: + given Reducible[ICons] = semiauto.reducible + given Reducible[Tree] = semiauto.reducible + given Reducible[NelSCons] = semiauto.reducible + given Reducible[NelAndOne] = semiauto.reducible + given Reducible[VecAndNel] = semiauto.reducible + given Reducible[Interleaved] = semiauto.reducible + given Reducible[BoxZipper] = semiauto.reducible + given Reducible[EnumK1] = semiauto.reducible + + final case class Zipper[+A](left: List[A], focus: A, right: List[A]) + object Zipper: + given [A: Eq]: Eq[Zipper[A]] = + (x, y) => x.focus === y.focus && x.left === y.left && x.right === y.right + + given [A: Arbitrary]: Arbitrary[Zipper[A]] = + Arbitrary(for + left <- Arbitrary.arbitrary[List[A]] + focus <- Arbitrary.arbitrary[A] + right <- Arbitrary.arbitrary[List[A]] + yield Zipper(left, focus, right)) + + given Reducible[Zipper] with + def reduceLeftTo[A, B](fa: Zipper[A])(f: A => B)(g: (B, A) => B) = + NonEmptyList(fa.focus, fa.right).reduceLeftTo(f)(g) + def reduceRightTo[A, B](fa: Zipper[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]) = + NonEmptyList(fa.focus, fa.right).reduceRightTo(f)(g) + def foldLeft[A, B](fa: Zipper[A], b: B)(f: (B, A) => B) = + (fa.focus :: fa.right).foldl(b)(f) + def foldRight[A, B](fa: Zipper[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]) = + (fa.focus :: fa.right).foldr(lb)(f) + +end ReducibleSuite diff --git a/core/src/test/scala-3/cats/derived/ReducibleTests.scala b/core/src/test/scala-3/cats/derived/ReducibleTests.scala new file mode 100644 index 00000000..b0595b3e --- /dev/null +++ b/core/src/test/scala-3/cats/derived/ReducibleTests.scala @@ -0,0 +1,38 @@ +package cats.derived + +import cats.Reducible +import cats.data.NonEmptyList +import cats.instances.all.* +import cats.derived.* +import cats.derived.semiauto.given + +object ReducibleTests: + case class Box[A](value: A) derives Reducible + + sealed trait OneOrMany[+A] derives Reducible + case class One[+A](value: A) extends OneOrMany[A] + case class Many[+A](values: NonEmptyList[A]) extends OneOrMany[A] + + sealed trait CList[A] derives Reducible + case class COne[A](value: A) extends CList[A] + case class CCons[A](head: A, tail: CList[A]) extends CList[A] + + case class NonEmptyTree[A](size: Int, value: A, tree: Tree[A]) + enum Tree[+A]: + case Leaf + case Node(left: Tree[A], value: A, right: Tree[A]) + + case class Foo[A](value: A, xs: List[A]) + enum MyList[+A]: + case Non + case Con(v: A, r: MyList[A]) + + import cats._ + + DerivedFunctor[MyList] + DerivedReducible[NonEmptyTree] + +@main def run() = + println( + DerivedFunctor[ReducibleTests.MyList].map(ReducibleTests.MyList.Con(42, ReducibleTests.MyList.Non))(_.toString) + ) diff --git a/core/src/test/scala-3/cats/derived/SemigroupKSuite.scala b/core/src/test/scala-3/cats/derived/SemigroupKSuite.scala new file mode 100644 index 00000000..2dd7db73 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/SemigroupKSuite.scala @@ -0,0 +1,63 @@ +package cats.derived + +import cats.* +import cats.laws.discipline.{SemigroupKTests, SerializableTests} +import org.scalacheck.Arbitrary +import scala.compiletime.* + +class SemigroupKSuite extends KittensSuite: + import SemigroupKSuite.* + import TestDefns.* + + inline def semigroupKTests[F[_]]: SemigroupKTests[F] = + SemigroupKTests[F](summonInline) + + inline def testSemigroupK(context: String): Unit = + checkAll(s"$context.SemigroupK[ComplexProduct]", semigroupKTests[ComplexProduct].semigroupK[Char]) + checkAll(s"$context.SemigroupK[CaseClassWOption]", semigroupKTests[CaseClassWOption].semigroupK[Char]) + checkAll(s"$context.SemigroupK[BoxMul]", semigroupKTests[BoxMul].semigroupK[Char]) + checkAll( + s"$context.SemigroupK is Serializable", + SerializableTests.serializable(summonInline[SemigroupK[ComplexProduct]]) + ) + + test(s"$context.SemigroupK respects existing instances") { + assert(summonInline[SemigroupK[BoxMul]].combineK(Box(Mul[Char](5)), Box(Mul[Char](5))) == Box(Mul[Char](25))) + } + + locally { + import auto.semigroupK.given + testSemigroupK("auto") + } + + locally { + import semiInstances.given + testSemigroupK("semiauto") + } + +end SemigroupKSuite + +object SemigroupKSuite: + import TestDefns.* + + type BoxMul[A] = Box[Mul[A]] + + object semiInstances: + given SemigroupK[ComplexProduct] = semiauto.semigroupK + given SemigroupK[CaseClassWOption] = semiauto.semigroupK + given SemigroupK[BoxMul] = semiauto.semigroupK + + final case class Mul[T](value: Int) + object Mul: + given [T]: Eq[Mul[T]] = Eq.by(_.value) + + given [T]: Arbitrary[Mul[T]] = + Arbitrary(Arbitrary.arbitrary[Int].map(apply)) + + given SemigroupK[Mul] with + def combineK[A](x: Mul[A], y: Mul[A]) = Mul(x.value * y.value) + + case class Simple[A](value1: List[A], value2: Set[A]) derives SemigroupK + case class Recursive[A](first: List[A], rest: Recursive[A]) derives SemigroupK + +end SemigroupKSuite diff --git a/core/src/test/scala-3/cats/derived/SemigroupSuite.scala b/core/src/test/scala-3/cats/derived/SemigroupSuite.scala new file mode 100644 index 00000000..5190dba8 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/SemigroupSuite.scala @@ -0,0 +1,70 @@ +/* + * 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, Semigroup} +import cats.kernel.laws.discipline.{SemigroupTests, SerializableTests} +import org.scalacheck.Arbitrary + +import scala.compiletime.* + +class SemigroupSuite extends KittensSuite: + import SemigroupSuite.* + import TestDefns.* + + inline def semigroupTests[A]: SemigroupTests[A] = + SemigroupTests[A](summonInline) + + inline def testSemigroup(inline context: String): Unit = + checkAll(s"$context.Semigroup[Foo]", semigroupTests[Foo].semigroup) + checkAll(s"$context.Semigroup[Interleaved[Int]]", semigroupTests[Interleaved[Int]].semigroup) + checkAll(s"$context.Semigroup[Box[Mul]]", semigroupTests[Box[Mul]].semigroup) + checkAll(s"$context.Semigroup[Recursive]", semigroupTests[Recursive].semigroup) + checkAll(s"$context.Semigroup is Serializable", SerializableTests.serializable(summonInline[Semigroup[Foo]])) + test(s"$context.Semigroup respects existing instances") { + val box = summonInline[Semigroup[Box[Mul]]] + assert(box.combine(Box(Mul(5)), Box(Mul(5))).content.value == 25) + } + + locally { + import auto.semigroup.given + testSemigroup("auto") + } + + locally { + import semiInstances.given + testSemigroup("semiauto") + } + +end SemigroupSuite + +object SemigroupSuite: + import TestDefns.* + + object semiInstances: + given Semigroup[Foo] = semiauto.semigroup + given Semigroup[Recursive] = semiauto.semigroup + given Semigroup[Interleaved[Int]] = semiauto.semigroup + given Semigroup[Box[Mul]] = semiauto.semigroup + + final case class Mul(value: Int) + object Mul: + given Eq[Mul] = Eq.fromUniversalEquals + given Arbitrary[Mul] = Arbitrary(Arbitrary.arbitrary[Int].map(apply)) + given Semigroup[Mul] = (x, y) => Mul(x.value * y.value) + +end SemigroupSuite diff --git a/core/src/test/scala-3/cats/derived/ShowPrettySuite.scala b/core/src/test/scala-3/cats/derived/ShowPrettySuite.scala new file mode 100644 index 00000000..8d51de3a --- /dev/null +++ b/core/src/test/scala-3/cats/derived/ShowPrettySuite.scala @@ -0,0 +1,193 @@ +package cats +package derived + +import cats.laws.discipline.SerializableTests +import scala.compiletime.* + +class ShowPrettySuite extends KittensSuite: + import ShowPrettySuite.* + import ShowPrettySuite.given + import TestDefns.* + + inline def showPretty[A](value: A): String = + summonInline[ShowPretty[A]].show(value) + + inline def testShowPretty(context: String): Unit = { + checkAll(s"$context.ShowPretty is Serializable", SerializableTests.serializable(summonInline[ShowPretty[IntTree]])) + + test(s"$context.ShowPretty[Foo]") { + val value = Foo(42, Option("Hello")) + val pretty = """ + |Foo( + | i = 42, + | b = Some(Hello) + |) + """.stripMargin.trim + + assertEquals(showPretty(value), pretty) + } + + test(s"$context.ShowPretty[Outer]") { + val value = Outer(Inner(3)) + val pretty = """ + |Outer( + | in = Inner( + | i = 3 + | ) + |) + """.stripMargin.trim + + assertEquals(showPretty(value), pretty) + } + + test(s"$context.ShowPretty[IntTree]") { + val value: IntTree = IntNode(IntLeaf(1), IntNode(IntNode(IntLeaf(2), IntLeaf(3)), IntLeaf(4))) + val pretty = """ + |IntNode( + | l = IntLeaf( + | t = 1 + | ), + | r = IntNode( + | l = IntNode( + | l = IntLeaf( + | t = 2 + | ), + | r = IntLeaf( + | t = 3 + | ) + | ), + | r = IntLeaf( + | t = 4 + | ) + | ) + |) + """.stripMargin.trim + + assertEquals(showPretty(value), pretty) + } + + test(s"$context.ShowPretty[GenericAdt[Int]]") { + val value: GenericAdt[Int] = GenericAdtCase(Some(1)) + val pretty = """ + |GenericAdtCase( + | value = Some(1) + |) + """.stripMargin.trim + + assertEquals(showPretty(value), pretty) + } + + test(s"$context.ShowPretty[People]") { + val value = People("Kai", ContactInfo("303-123-4567", Address("123 1st St", "New York", "NY"))) + val pretty = """ + |People( + | name = Kai, + | contactInfo = ContactInfo( + | phoneNumber = 303-123-4567, + | address = 123 1st St New York NY + | ) + |) + """.stripMargin.trim + + assertEquals(showPretty(value), pretty) + } + + test(s"$context.ShowPretty[ListField]") { + val value = ListField("a", List(ListFieldChild(1))) + val pretty = """ + |ListField( + | a = a, + | b = List(ListFieldChild( + | c = 1 + | )) + |) + """.stripMargin.trim + + assertEquals(showPretty(value), pretty) + } + + test(s"$context.ShowPretty[Interleaved[Int]]") { + val value = Interleaved(1, 2, 3, Vector(4, 5, 6), "789") + val pretty = """ + |Interleaved( + | i = 1, + | t = 2, + | l = 3, + | tt = Vector(4, 5, 6), + | s = 789 + |) + """.stripMargin.trim + + assertEquals(showPretty(value), pretty) + } + + test(s"$context.ShowPretty[Tree[Int]]") { + val value: Tree[Int] = Node(Leaf(1), Node(Node(Leaf(2), Leaf(3)), Leaf(4))) + val pretty = """ + |Node( + | left = Leaf( + | value = 1 + | ), + | right = Node( + | left = Node( + | left = Leaf( + | value = 2 + | ), + | right = Leaf( + | value = 3 + | ) + | ), + | right = Leaf( + | value = 4 + | ) + | ) + |) + """.stripMargin.trim + + assertEquals(showPretty(value), pretty) + } + + test(s"$context.ShowPretty respects existing instances") { + val value = Box(Bogus(42)) + val pretty = """ + |Box( + | content = Blah + |) + """.stripMargin.trim + + assertEquals(showPretty(value), pretty) + } + } + + locally { + import auto.showPretty.given + testShowPretty("auto") + } + + locally { + import semiInstances.given + testShowPretty("semiauto") + } + +object ShowPrettySuite: + import TestDefns.* + + given Show[Address] = Show.show { a => + List(a.street, a.city, a.state).mkString(" ") + } + + final case class Bogus(value: Int) + object Bogus: + given Show[Bogus] = Show.show(_ => "Blah") + + object semiInstances: + given ShowPretty[Foo] = semiauto.showPretty + given ShowPretty[Outer] = semiauto.showPretty + given ShowPretty[IntTree] = semiauto.showPretty + given ShowPretty[GenericAdt[Int]] = semiauto.showPretty + given ShowPretty[People] = semiauto.showPretty + given ShowPretty[ListFieldChild] = semiauto.showPretty + given ShowPretty[ListField] = semiauto.showPretty + given ShowPretty[Interleaved[Int]] = semiauto.showPretty + given ShowPretty[Tree[Int]] = semiauto.showPretty + given ShowPretty[Box[Bogus]] = semiauto.showPretty diff --git a/core/src/test/scala-3/cats/derived/ShowSuite.scala b/core/src/test/scala-3/cats/derived/ShowSuite.scala new file mode 100644 index 00000000..10ba0d53 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/ShowSuite.scala @@ -0,0 +1,120 @@ +package cats +package derived + +import cats.laws.discipline.SerializableTests +import scala.compiletime.* + +class ShowSuite extends KittensSuite: + import ShowSuite.given + import ShowSuite.* + import TestDefns.* + + inline def show[A](value: A): String = + summonInline[Show[A]].show(value) + + inline def testShow(context: String): Unit = + checkAll(s"$context.Show is Serializable", SerializableTests.serializable(summonInline[Show[Foo]])) + + test(s"$context.Show[Foo]") { + val value = Foo(42, Option("Hello")) + val shown = "Foo(i = 42, b = Some(Hello))" + assertEquals(show(value), shown) + } + + test(s"$context.Show[Outer]") { + val value = Outer(Inner(3)) + val shown = "Outer(in = Inner(i = 3))" + assertEquals(show(value), shown) + } + + test(s"$context.Show[IntTree]") { + val value: IntTree = IntNode(IntLeaf(1), IntNode(IntNode(IntLeaf(2), IntLeaf(3)), IntLeaf(4))) + val shown = + "IntNode(l = IntLeaf(t = 1), r = IntNode(l = IntNode(l = IntLeaf(t = 2), r = IntLeaf(t = 3)), r = IntLeaf(t = 4)))" + assertEquals(show(value), shown) + } + + test(s"$context.Show[GenericAdt[Int]]") { + val value: GenericAdt[Int] = GenericAdtCase(Some(1)) + val shown = "GenericAdtCase(value = Some(1))" + assertEquals(show(value), shown) + } + + test(s"$context.Show[People]") { + val value = People("Kai", ContactInfo("303-123-4567", Address("123 1st St", "New York", "NY"))) + val shown = + "People(name = Kai, contactInfo = ContactInfo(phoneNumber = 303-123-4567, address = 123 1st St New York NY))" + assertEquals(show(value), shown) + } + + test(s"$context.Show[ListField]") { + val value = ListField("a", List(ListFieldChild(1))) + val shown = "ListField(a = a, b = List(ListFieldChild(c = 1)))" + assertEquals(show(value), shown) + } + + test(s"$context.Show[Interleaved[Int]]") { + val value = Interleaved(1, 2, 3, Vector(4, 5, 6), "789") + val shown = "Interleaved(i = 1, t = 2, l = 3, tt = Vector(4, 5, 6), s = 789)" + assertEquals(show(value), shown) + } + + test(s"$context.Show[Tree[Int]]") { + val value: Tree[Int] = Node(Leaf(1), Node(Node(Leaf(2), Leaf(3)), Leaf(4))) + val shown = + "Node(left = Leaf(value = 1), right = Node(left = Node(left = Leaf(value = 2), right = Leaf(value = 3)), right = Leaf(value = 4)))" + assertEquals(show(value), shown) + } + + test(s"$context.Show[EnumK0]") { + val value: EnumK0 = EnumK0.LeafI(3) + val shown = + "LeafI(value = 3)" + assertEquals(show(value), shown) + } + + test(s"$context.Show respects existing instances") { + val value = Box(Bogus(42)) + val shown = "Box(content = Blah)" + assertEquals(show(value), shown) + } + + end testShow + + locally { + import auto.show.given + testShow("auto") + } + + locally { + import semiInstances.given + testShow("semiauto") + } + +end ShowSuite + +object ShowSuite: + import TestDefns.* + + given Show[Address] = Show.show { a => + List(a.street, a.city, a.state).mkString(" ") + } + + final case class Bogus(value: Int) + object Bogus: + given Show[Bogus] = Show.show(_ => "Blah") + + object semiInstances: + given Show[Foo] = semiauto.show + given Show[Outer] = semiauto.show + given Show[IntTree] = semiauto.show + given Show[GenericAdt[Int]] = semiauto.show + given Show[People] = semiauto.show + given Show[ListFieldChild] = semiauto.show + given Show[ListField] = semiauto.show + given Show[Interleaved[Int]] = semiauto.show + given Show[Tree[Int]] = semiauto.show + given Show[Box[Bogus]] = semiauto.show + given Show[EnumK0] = semiauto.show + +end ShowSuite diff --git a/core/src/test/scala-3/cats/derived/ShowTests.scala b/core/src/test/scala-3/cats/derived/ShowTests.scala new file mode 100644 index 00000000..1328404c --- /dev/null +++ b/core/src/test/scala-3/cats/derived/ShowTests.scala @@ -0,0 +1,9 @@ +package cats.derived + +import alleycats.* +import cats.* +import cats.derived.* + +class ShowTests { // + case class Foo(i: Int, b: Option[String]) derives Show +} diff --git a/core/src/test/scala-3/cats/derived/TraverseSuite.scala b/core/src/test/scala-3/cats/derived/TraverseSuite.scala new file mode 100644 index 00000000..27e66c96 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/TraverseSuite.scala @@ -0,0 +1,68 @@ +package cats.derived + +import cats.{Eq, Traverse} +import cats.laws.discipline.{SerializableTests, TraverseTests} +import org.scalacheck.Arbitrary + +import scala.compiletime.* + +class TraverseSuite extends KittensSuite: + import TestDefns.* + import TraverseSuite.* + + inline def traverseTests[F[_]]: TraverseTests[F] = + TraverseTests[F](summonInline) + + inline def testTraverse(inline context: String): Unit = + checkAll(s"$context.Traverse[IList]", traverseTests[IList].traverse[Int, Double, String, Long, Option, Option]) + checkAll(s"$context.Traverse[Tree]", traverseTests[Tree].traverse[Int, Double, String, Long, Option, Option]) + checkAll( + s"$context.Traverse[GenericAdt]", + traverseTests[GenericAdt].traverse[Int, Double, String, Long, Option, Option] + ) + checkAll(s"$context.Traverse[OptList]", traverseTests[OptList].traverse[Int, Double, String, Long, Option, Option]) + checkAll( + s"$context.Traverse[ListSnoc]", + traverseTests[ListSnoc].traverse[Int, Double, String, Long, Option, Option] + ) + checkAll(s"$context.Traverse[AndChar]", traverseTests[AndChar].traverse[Int, Double, String, Long, Option, Option]) + checkAll( + s"$context.Traverse[Interleaved]", + traverseTests[Interleaved].traverse[Int, Double, String, Long, Option, Option] + ) + checkAll( + s"$context.Traverse[EnumK1]", + traverseTests[EnumK1].traverse[Int, Double, String, Long, Option, Option] + ) + checkAll(s"$context.Traverse is Serializable", SerializableTests.serializable(summonInline[Traverse[Tree]])) + + locally { + import auto.traverse.given + testTraverse("auto") + } + + locally { + import semiInstances.given + testTraverse("semiauto") + } + +end TraverseSuite + +object TraverseSuite: + import TestDefns.* + + type OptList[A] = Option[List[A]] + type ListSnoc[A] = List[Snoc[A]] + type AndChar[A] = (A, Char) + + object semiInstances: + given Traverse[IList] = semiauto.traverse + given Traverse[Tree] = semiauto.traverse + given Traverse[GenericAdt] = semiauto.traverse + given Traverse[OptList] = semiauto.traverse + given Traverse[ListSnoc] = semiauto.traverse + given Traverse[AndChar] = semiauto.traverse + given Traverse[Interleaved] = semiauto.traverse + given Traverse[EnumK1] = semiauto.traverse + +end TraverseSuite diff --git a/core/src/test/scala-3/cats/derived/TraverseTests.scala b/core/src/test/scala-3/cats/derived/TraverseTests.scala new file mode 100644 index 00000000..d6c410ff --- /dev/null +++ b/core/src/test/scala-3/cats/derived/TraverseTests.scala @@ -0,0 +1,17 @@ +package cats.derived + +import cats.Traverse +import cats.derived.* + +class TraverseTests { + + case class Box[A](value: A) derives Traverse + + sealed trait Maybe[+A] derives Traverse + case object Nufin extends Maybe[Nothing] + case class Just[A](value: A) extends Maybe[A] + + sealed trait CList[A] derives Traverse + case object CNil extends CList[Nothing] + case class CCons[A](head: A, tail: CList[A]) extends CList[A] +} diff --git a/core/src/test/scala-3/cats/derived/adtdefns.scala b/core/src/test/scala-3/cats/derived/adtdefns.scala new file mode 100644 index 00000000..5de94a4d --- /dev/null +++ b/core/src/test/scala-3/cats/derived/adtdefns.scala @@ -0,0 +1,561 @@ +/* + * 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, Eval} +import cats.syntax.all.given +import org.scalacheck.rng.Seed +import org.scalacheck.{Arbitrary, Cogen, Gen} + +import scala.annotation.tailrec + +object TestDefns: + + enum EnumK0: + case LeafS(value: String) + case LeafI(value: Int) + + object EnumK0: + given Arbitrary[EnumK0] = Arbitrary( + Gen.oneOf( + Arbitrary.arbitrary[String].map(LeafS.apply), + Arbitrary.arbitrary[Int].map(LeafI.apply) + ) + ) + + given Cogen[EnumK0] = Cogen[Either[String, Int]].contramap { + case LeafS(s) => Left(s) + case LeafI(i) => Right(i) + } + + enum EnumK1[A]: + case Leaf(value: A) + case Rec(l: EnumK1[A], r: EnumK1[A]) + + object EnumK1: + given [A](using Arbitrary[A]): Arbitrary[EnumK1[A]] = Arbitrary( + Gen.recursive(rec => + Gen.sized(size => + val leaf = Arbitrary.arbitrary[A].map(Leaf.apply) + if size == 0 then leaf + else + Gen.resize( + size / 2, + Gen.oneOf( + leaf, + for + l <- rec + r <- rec + yield Rec(l, r) + ) + ) + ) + ) + ) + + enum EnumK1Contra[-A]: + case Leaf(value: A => Unit) + case Rec(l: EnumK1Contra[A], r: EnumK1Contra[A]) + + object EnumK1Contra: + given [A](using Arbitrary[A => Unit]): Arbitrary[EnumK1Contra[A]] = Arbitrary( + Gen.recursive(rec => + Gen.sized(size => + val leaf = Arbitrary.arbitrary[A => Unit].map(Leaf.apply) + if size == 0 then leaf + else + Gen.resize( + size / 2, + Gen.oneOf( + leaf, + for + l <- rec + r <- rec + yield Rec(l, r) + ) + ) + ) + ) + ) + + enum EnumK1Inv[A]: + case Leaf(cov: A, contra: A => Unit) + case Rec(l: EnumK1Inv[A], r: EnumK1Inv[A]) + + object EnumK1Inv: + given [A](using Arbitrary[A], Arbitrary[A => Unit]): Arbitrary[EnumK1Inv[A]] = Arbitrary( + Gen.recursive(rec => + Gen.sized(size => + val leaf = + for + cov <- Arbitrary.arbitrary[A] + contra <- Arbitrary.arbitrary[A => Unit] + yield Leaf(cov, contra) + if size == 0 then leaf + else + Gen.resize( + size / 2, + Gen.oneOf( + leaf, + for + l <- rec + r <- rec + yield Rec(l, r) + ) + ) + ) + ) + ) + + sealed trait Rgb + object Rgb: + case object Red extends Rgb + case object Green extends Rgb + case object Blue extends Rgb + + final case class ComplexProduct[T](lbl: String, set: Set[T], fns: Vector[() => T], opt: Eval[Option[T]]) + object ComplexProduct: + given [T: Arbitrary]: Arbitrary[ComplexProduct[T]] = Arbitrary(for + lbl <- Arbitrary.arbitrary[String] + set <- Arbitrary.arbitrary[Set[T]] + vec <- Arbitrary.arbitrary[Vector[T]] + fns = vec.map(x => () => x) + opt <- Arbitrary.arbitrary[Option[T]] + yield ComplexProduct(lbl, set, fns, Eval.now(opt))) + + final case class Box[+A](content: A) + object Box: + given [A: Arbitrary]: Arbitrary[Box[A]] = Arbitrary(Arbitrary.arbitrary[A].map(apply)) + given [A: Cogen]: Cogen[Box[A]] = Cogen[A].contramap(_.content) + + final case class Recursive(i: Int, is: Option[Recursive]) + object Recursive extends ((Int, Option[Recursive]) => Recursive): + given Arbitrary[Recursive] = + def recursive(size: Int): Gen[Recursive] = for + i <- Arbitrary.arbitrary[Int] + is <- if (size <= 0) Gen.const(None) else Gen.option(recursive(size / 2)) + yield Recursive(i, is) + Arbitrary(Gen.sized(recursive)) + + given Cogen[Recursive] = + @tailrec def perturb(seed: Seed, rec: Recursive): Seed = + val i = Cogen[Int].perturb(seed, rec.i) + rec.is match + case Some(is) => perturb(i, is) + case None => i + Cogen(perturb _) + + final case class Interleaved[T](i: Int, t: T, l: Long, tt: Vector[T], s: String) + object Interleaved: + def empty[T](t: T): Interleaved[T] = + Interleaved(0, t, 0, Vector.empty, "") + + given [T: Arbitrary]: Arbitrary[Interleaved[T]] = + Arbitrary(Arbitrary.arbitrary[(Int, T, Long, Vector[T], String)].map((apply[T] _).tupled)) + + given [T: Cogen]: Cogen[Interleaved[T]] = + Cogen[(Int, T, Long, Vector[T], String)].contramap(x => (x.i, x.t, x.l, x.tt, x.s)) + + case class Bivariant[A](run: A => Boolean, store: A) + object Bivariant: + given [A: Arbitrary]: Arbitrary[Bivariant[A]] = Arbitrary( + for + a <- Arbitrary.arbitrary[A] + f <- Arbitrary.arbitrary[Boolean].map(Function.const[Boolean, A]) + yield Bivariant[A](f, a) + ) + + case class Pred[A](run: A => Boolean) + + sealed trait IList[A] + final case class ICons[A](head: A, tail: IList[A]) extends IList[A] + final case class INil[A]() extends IList[A] + + object ICons: + + given [A: Arbitrary]: Arbitrary[ICons[A]] = + Arbitrary(for + head <- Arbitrary.arbitrary[A] + tail <- Arbitrary.arbitrary[IList[A]] + yield ICons(head, tail)) + + object IList: + + given [A: Arbitrary]: Arbitrary[IList[A]] = + Arbitrary(Arbitrary.arbitrary[Seq[A]].map(fromSeq)) + + given [A: Cogen]: Cogen[IList[A]] = + Cogen[Seq[A]].contramap(toList) + + def fromSeq[T](ts: Seq[T]): IList[T] = + ts.foldRight[IList[T]](INil())(ICons.apply) + + def toList[T](list: IList[T]): List[T] = + @tailrec def loop(list: IList[T], acc: List[T]): List[T] = list match + case INil() => acc.reverse + case ICons(head, tail) => loop(tail, head :: acc) + loop(list, Nil) + + sealed trait Snoc[A] + final case class SCons[A](init: Snoc[A], last: A) extends Snoc[A] + final case class SNil[A]() extends Snoc[A] + + object Snoc: + + given [A: Arbitrary]: Arbitrary[Snoc[A]] = + Arbitrary(Arbitrary.arbitrary[Seq[A]].map(fromSeq)) + + def fromSeq[T](ts: Seq[T]): Snoc[T] = + ts.foldLeft[Snoc[T]](SNil())(SCons.apply) + + def toList[T](snoc: Snoc[T]): List[T] = + @tailrec def loop(snoc: Snoc[T], acc: List[T]): List[T] = snoc match + case SNil() => acc + case SCons(init, last) => loop(init, last :: acc) + loop(snoc, Nil) + + object SCons: + + given [A: Arbitrary]: Arbitrary[SCons[A]] = + Arbitrary(for + init <- Arbitrary.arbitrary[Snoc[A]] + last <- Arbitrary.arbitrary[A] + yield SCons(init, last)) + + sealed trait Tree[A] + final case class Leaf[A](value: A) extends Tree[A] + final case class Node[A](left: Tree[A], right: Tree[A]) extends Tree[A] + + object Tree: + given [A: Arbitrary]: Arbitrary[Tree[A]] = + val leaf = Arbitrary.arbitrary[A].map(Leaf.apply) + + def tree(maxDepth: Int): Gen[Tree[A]] = + if (maxDepth <= 0) leaf + else Gen.oneOf(leaf, node(maxDepth)) + + def node(maxDepth: Int): Gen[Tree[A]] = for + depthL <- Gen.choose(0, maxDepth - 1) + depthR <- Gen.choose(0, maxDepth - 1) + left <- tree(depthL) + right <- tree(depthR) + yield Node(left, right) + + Arbitrary(Gen.sized(tree)) + + given [A: Cogen]: Cogen[Tree[A]] = + lazy val cogen: Cogen[Tree[A]] = Cogen { (seed, tree) => + tree match + case Leaf(value) => Cogen[A].perturb(seed, value) + case Node(left, right) => cogen.perturb(cogen.perturb(seed, left), right) + } + + cogen + + final case class Foo(i: Int, b: Option[String]) + object Foo: + + given Cogen[Foo] = + Cogen[Int].contramap(_.i) + + given Arbitrary[Foo] = + Arbitrary(for + i <- Arbitrary.arbitrary[Int] + b <- Arbitrary.arbitrary[Option[String]] + yield Foo(i, b)) + + final case class CommutativeFoo(i: Int, b: Option[Long]) + object CommutativeFoo: + + given Cogen[CommutativeFoo] = + Cogen[(Int, Option[Long])].contramap(x => (x.i, x.b)) + + given Arbitrary[CommutativeFoo] = + Arbitrary(for + i <- Arbitrary.arbitrary[Int] + b <- Arbitrary.arbitrary[Option[Long]] + yield CommutativeFoo(i, b)) + + case class Inner(i: Int) + case class Outer(in: Inner) + + object Inner: + + given Arbitrary[Inner] = + Arbitrary(Arbitrary.arbitrary[Int].map(apply)) + + given Cogen[Inner] = + Cogen[Int].contramap(_.i) + + object Outer: + + given Arbitrary[Outer] = + Arbitrary(Arbitrary.arbitrary[Inner].map(apply)) + + given Cogen[Outer] = + Cogen[Inner].contramap(_.in) + + sealed trait IntTree + final case class IntLeaf(t: Int) extends IntTree + final case class IntNode(l: IntTree, r: IntTree) extends IntTree + + object IntTree + + sealed trait GenericAdt[A] + final case class GenericAdtCase[A](value: Option[A]) extends GenericAdt[A] + + object GenericAdt: + + given [A: Arbitrary]: Arbitrary[GenericAdt[A]] = + Arbitrary(Arbitrary.arbitrary[Option[A]].map(GenericAdtCase.apply)) + + given [A: Cogen]: Cogen[GenericAdt[A]] = + Cogen[Option[A]].contramap { case GenericAdtCase(value) => value } + + final case class CaseClassWOption[A](value: Option[A]) + object CaseClassWOption: + given [A: Arbitrary]: Arbitrary[CaseClassWOption[A]] = + Arbitrary(Arbitrary.arbitrary[Option[A]].map(apply)) + + final case class First(value: String) + final case class Second(value: String) + final case class Middle(first: First, second: Option[Second]) + final case class Top(middle: Middle) + + final case class Address(street: String, city: String, state: String) + final case class ContactInfo(phoneNumber: String, address: Address) + final case class People(name: String, contactInfo: ContactInfo) + + final case class ListField(a: String, b: List[ListFieldChild]) + final case class ListFieldChild(c: Int) + + final case class Large( + bar1: String, + bar2: Int, + bar3: Boolean, + bar4: Large2, + bar5: List[String], + bar6: Set[Boolean], + bar7: Double, + bar8: Long, + bar9: Char, + bar10: Float, + bar11: String, + bar12: Map[String, Int], + bar13: Boolean, + bar14: Option[String], + bar15: List[String], + bar16: Set[Boolean], + bar17: Double, + bar18: Long, + bar19: Char, + bar20: Float + ) + + final case class Large2( + bar1: String, + bar2: Int, + bar3: Boolean, + bar4: Option[String], + bar5: List[String], + bar6: Set[Boolean], + bar7: Double, + bar8: Long, + bar9: Char, + bar10: Float, + bar11: String, + bar12: Map[String, Int], + bar13: Boolean, + bar14: Option[String], + bar15: List[String], + bar16: Set[Boolean], + bar17: Double, + bar18: Long, + bar19: Char, + bar20: Float, + bar21: String + ) + + final case class Large3( + bar1: String, + bar2: Int, + bar3: Boolean, + bar4: Option[String], + bar5: List[String], + bar6: Set[Boolean], + bar7: Double, + bar8: Long, + bar9: Char, + bar10: Float, + bar11: String, + bar12: Map[String, Int], + bar13: Boolean, + bar14: Option[String], + bar15: List[String], + bar16: Set[Boolean], + bar17: Double, + bar18: Long, + bar19: Char, + bar20: Float, + bar21: String + ) + + final case class Large4( + bar1: String, + bar2: Int, + bar3: Boolean, + bar4: Large5, + bar5: List[String], + bar6: List[Boolean], + bar7: Double, + bar8: Long, + bar9: Char, + bar10: Float, + bar11: String, + bar12: String, + bar13: Boolean, + bar14: Option[String], + bar15: List[String], + bar16: List[Boolean], + bar17: Double, + bar18: Long, + bar19: Char, + bar20: Float + ) + + final case class Large5( + bar1: String, + bar2: Int, + bar3: Boolean, + bar4: Option[String], + bar5: List[String], + bar6: List[Boolean], + bar7: Double, + bar8: Long, + bar9: Char, + bar10: Float, + bar11: String, + bar12: Int, + bar13: Boolean, + bar14: Option[String], + bar15: List[String], + bar16: List[Boolean], + bar17: Double, + bar18: Long, + bar19: Char, + bar20: Float, + bar21: String + ) + +end TestDefns + +trait TestEqInstances: + import TestDefns.* + + given [T: Eq]: Eq[ComplexProduct[T]] with + val eqSet = Eq[Set[T]] + val eqVec = Eq[Vector[T]] + val eqOpt = Eq[Eval[Option[T]]] + + def eqv(x: ComplexProduct[T], y: ComplexProduct[T]) = + x.lbl == y.lbl && + eqSet.eqv(x.set, y.set) && + eqVec.eqv(x.fns.map(_()), y.fns.map(_())) && + eqOpt.eqv(x.opt, y.opt) + + given [A: Eq]: Eq[Box[A]] = + Eq.by(_.content) + + given Eq[Recursive] = + Eq.fromUniversalEquals + + given [T: Eq]: Eq[Interleaved[T]] = + Eq.by(i => (i.i, i.t, i.l, i.tt, i.s)) + + given [A](using A: Eq[A]): Eq[IList[A]] = new Eq[IList[A]]: + @tailrec def eqv(x: IList[A], y: IList[A]): Boolean = (x, y) match + case (ICons(hx, tx), ICons(hy, ty)) => A.eqv(hx, hy) && eqv(tx, ty) + case (INil(), INil()) => true + case _ => false + + given [A](using A: Eq[A]): Eq[Snoc[A]] = new Eq[Snoc[A]]: + @tailrec def eqv(x: Snoc[A], y: Snoc[A]): Boolean = (x, y) match + case (SCons(ix, lx), SCons(iy, ly)) => A.eqv(lx, ly) && eqv(ix, iy) + case (SNil(), SNil()) => true + case _ => false + + given [A: Eq]: Eq[ICons[A]] = Eq.by(identity[IList[A]]) + given [A: Eq]: Eq[SCons[A]] = Eq.by(identity[Snoc[A]]) + + given [A](using A: Eq[A]): Eq[Tree[A]] with + def eqv(x: Tree[A], y: Tree[A]): Boolean = (x, y) match + case (Leaf(vx), Leaf(vy)) => A.eqv(vx, vy) + case (Node(lx, rx), Node(ly, ry)) => eqv(lx, ly) && eqv(rx, ry) + case _ => false + + given [A](using A: Eq[A]): Eq[Bivariant[A]] with + def eqv(x: Bivariant[A], y: Bivariant[A]): Boolean = (x, y) match + case (Bivariant(runX, storeX), Bivariant(runY, storeY)) => + A.eqv(storeX, storeY) && Eq[Boolean].eqv(runX(storeX), runY(storeY)) + + given Eq[Foo] = Eq.fromUniversalEquals + + given Eq[CommutativeFoo] = Eq.fromUniversalEquals + + given [A: Eq]: Eq[GenericAdt[A]] = + val eqvOpt = Eq[Option[A]] + Eq.instance { case (GenericAdtCase(vx), GenericAdtCase(vy)) => eqvOpt.eqv(vx, vy) } + + given [A: Eq]: Eq[CaseClassWOption[A]] = Eq.by(_.value) + + given Eq[Inner] = Eq.fromUniversalEquals + + given Eq[Outer] = Eq.fromUniversalEquals + + given Eq[EnumK0] = + import EnumK0.* + Eq.instance { + case (LeafS(s1), LeafS(s2)) => s1 === s2 + case (LeafI(i1), LeafI(i2)) => i1 === i2 + case _ => false + } + + given [A](using Eq[A]): Eq[EnumK1[A]] = + import EnumK1.* + Eq.instance { + case (Leaf(v1), Leaf(v2)) => v1 === v2 + case (Rec(l1, r1), Rec(l2, r2)) => l1 === l2 && r1 === r2 + case _ => false + } + + given [A](using Eq[A => Unit]): Eq[EnumK1Contra[A]] = + import EnumK1Contra.* + Eq.instance { + case (Leaf(v1), Leaf(v2)) => v1 === v2 + case (Rec(l1, r1), Rec(l2, r2)) => l1 === l2 && r1 === r2 + case _ => false + } + + given [A](using Eq[A], Eq[A => Unit]): Eq[EnumK1Inv[A]] = + import EnumK1Inv.* + Eq.instance { + case (Leaf(cov1, contra1), Leaf(cov2, contra2)) => cov1 === cov2 && contra1 === contra2 + case (Rec(l1, r1), Rec(l2, r2)) => l1 === l2 && r1 === r2 + case _ => false + } + +end TestEqInstances