From ed9ece5548b1cda10d62dafe90961697106e38f5 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Sun, 26 Sep 2021 00:32:39 +0200 Subject: [PATCH] Port EmptySuite and also solve Empty for Coproducts --- .../src/main/scala-3/cats/derived/empty.scala | 8 +- .../main/scala-3/cats/derived/monoid.scala | 12 +-- .../main/scala-3/cats/derived/package.scala | 82 +++++++++-------- .../main/scala-3/cats/derived/semigroup.scala | 9 +- .../scala-3/cats/derived/util/Kinds.scala | 31 +++++++ .../scala-3/cats/derived/EmptySuite.scala | 89 +++++++++++++++++++ .../scala-3/cats/derived/EmptyTests.scala | 2 +- .../scala-3/cats/derived/KittensSuite.scala | 20 +++-- .../scala-3/cats/derived/MonoidTests.scala | 2 +- .../test/scala-3/cats/derived/adtdefns.scala | 7 +- 10 files changed, 193 insertions(+), 69 deletions(-) create mode 100644 core/src/main/scala-3/cats/derived/util/Kinds.scala create mode 100644 core/src/test/scala-3/cats/derived/EmptySuite.scala diff --git a/core/src/main/scala-3/cats/derived/empty.scala b/core/src/main/scala-3/cats/derived/empty.scala index 54e2134d..de8d389d 100644 --- a/core/src/main/scala-3/cats/derived/empty.scala +++ b/core/src/main/scala-3/cats/derived/empty.scala @@ -1,7 +1,9 @@ package cats.derived import alleycats.Empty +import cats.derived.util.Kinds import shapeless3.deriving.K0 + import scala.compiletime.* type DerivedEmpty[A] = Derived[Empty[A]] @@ -15,8 +17,4 @@ object DerivedEmpty: Empty(inst.unify.construct([A] => (A: Empty[A]) => A.empty)) inline given coproduct[A](using gen: K0.CoproductGeneric[A]): DerivedEmpty[A] = - K0.summonFirst[Or, gen.MirroredElemTypes, A].unify - -trait EmptyDerivation: - extension (E: Empty.type) - inline def derived[A]: Empty[A] = DerivedEmpty[A] + Kinds.summonOne0[Or, gen.MirroredElemTypes, A].unify diff --git a/core/src/main/scala-3/cats/derived/monoid.scala b/core/src/main/scala-3/cats/derived/monoid.scala index 2700f8de..12e0d37e 100644 --- a/core/src/main/scala-3/cats/derived/monoid.scala +++ b/core/src/main/scala-3/cats/derived/monoid.scala @@ -3,12 +3,8 @@ package cats.derived import cats.Monoid import shapeless3.deriving.K0 -trait ProductMonoid[F[x] <: Monoid[x], A]( - using inst: K0.ProductInstances[F, A] -) extends ProductSemigroup[F, A], Monoid[A]: +trait ProductMonoid[F[x] <: Monoid[x], A](using + inst: K0.ProductInstances[F, A] +) extends ProductSemigroup[F, A], + Monoid[A]: val empty: A = inst.construct([A] => (F: F[A]) => F.empty) - -trait MonoidDerivation: - extension (M: Monoid.type) - inline def derived[A](using gen: K0.ProductGeneric[A]): Monoid[A] = - new ProductMonoid[Monoid, A]{} diff --git a/core/src/main/scala-3/cats/derived/package.scala b/core/src/main/scala-3/cats/derived/package.scala index a871d8f1..23a34452 100644 --- a/core/src/main/scala-3/cats/derived/package.scala +++ b/core/src/main/scala-3/cats/derived/package.scala @@ -1,55 +1,59 @@ package cats.derived +import alleycats.* import cats.* - -extension (F: Foldable.type) - inline def derived[F[_]]: Foldable[F] = DerivedFoldable[F] - -extension (F: Functor.type) - inline def derived[F[_]]: Functor[F] = DerivedFunctor[F] - -extension (F: Reducible.type) - inline def derived[F[_]]: Reducible[F] = DerivedReducible[F] - -extension (F: Traverse.type) - inline def derived[F[_]]: Traverse[F] = DerivedTraverse[F] - -object semiauto extends - CommutativeMonoidDerivation, - CommutativeSemigroupDerivation, - ContravariantDerivation, - EmptyDerivation, - EmptyKDerivation, - EqDerivation, - HashDerivation, - InvariantDerivation, - MonoidDerivation, - MonoidKDerivation, - OrderDerivation, - PartialOrderDerivation, - SemigroupDerivation, - SemigroupKDerivation, - ShowDerivation, - Instances: - +import shapeless3.deriving.K0 + +import scala.util.NotGiven + +extension (E: Empty.type) inline def derived[A]: Empty[A] = DerivedEmpty[A] + +extension (S: Semigroup.type) + inline def derived[A](using gen: K0.ProductGeneric[A]): Semigroup[A] = + new ProductSemigroup[Semigroup, A] {} + +extension (M: Monoid.type) + inline def derived[A](using gen: K0.ProductGeneric[A]): Monoid[A] = + new ProductMonoid[Monoid, A] {} + +extension (F: Foldable.type) inline def derived[F[_]]: Foldable[F] = DerivedFoldable[F] +extension (F: Functor.type) inline def derived[F[_]]: Functor[F] = DerivedFunctor[F] +extension (F: Reducible.type) inline def derived[F[_]]: Reducible[F] = DerivedReducible[F] +extension (F: Traverse.type) inline def derived[F[_]]: Traverse[F] = DerivedTraverse[F] + +object semiauto + extends CommutativeMonoidDerivation, + CommutativeSemigroupDerivation, + ContravariantDerivation, + EmptyKDerivation, + EqDerivation, + HashDerivation, + InvariantDerivation, + MonoidKDerivation, + OrderDerivation, + PartialOrderDerivation, + SemigroupKDerivation, + ShowDerivation, + Instances: + + inline def empty[A]: Empty[A] = DerivedEmpty[A] 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] - object auto: + object empty: + inline given [A](using NotGiven[Empty[A]]): Empty[A] = DerivedEmpty[A] + object functor: - inline given [F[_]]: Functor[F] = DerivedFunctor[F] + inline given [F[_]](using NotGiven[Functor[F]]): Functor[F] = DerivedFunctor[F] object foldable: - inline given [F[_]]: Foldable[F] = DerivedFoldable[F] + inline given [F[_]](using NotGiven[Foldable[F]]): Foldable[F] = DerivedFoldable[F] object reducible: - inline given [F[_]]: Reducible[F] = DerivedReducible[F] + inline given [F[_]](using NotGiven[Reducible[F]]): Reducible[F] = DerivedReducible[F] object traverse: - inline given [F[_]]: Traverse[F] = DerivedTraverse[F] + inline given [F[_]](using NotGiven[Traverse[F]]): Traverse[F] = DerivedTraverse[F] diff --git a/core/src/main/scala-3/cats/derived/semigroup.scala b/core/src/main/scala-3/cats/derived/semigroup.scala index cc10cc8d..f9b005ee 100644 --- a/core/src/main/scala-3/cats/derived/semigroup.scala +++ b/core/src/main/scala-3/cats/derived/semigroup.scala @@ -3,13 +3,8 @@ package cats.derived import cats.Semigroup import shapeless3.deriving.K0 -trait ProductSemigroup[F[x] <: Semigroup[x], A]( - using inst: K0.ProductInstances[F, A] +trait ProductSemigroup[F[x] <: Semigroup[x], A](using + inst: K0.ProductInstances[F, A] ) extends Semigroup[A]: def combine(x: A, y: A): A = inst.map2(x, y)([A] => (F: F[A], x: A, y: A) => F.combine(x, y)) - -trait SemigroupDerivation: - extension (S: Semigroup.type) - inline def derived[A](using gen: K0.ProductGeneric[A]): Semigroup[A] = - new ProductSemigroup[Semigroup, 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..750357a8 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/util/Kinds.scala @@ -0,0 +1,31 @@ +package cats.derived.util + +import shapeless3.deriving.K0.LiftP + +import scala.compiletime.* +import scala.util.NotGiven + +object Kinds: + inline def summonOne0[F[_], T, U]: F[U] = + summonOneFrom[LiftP[F, T]].asInstanceOf[F[U]] + + inline def summonNone0[F[_], T]: Unit = + summonNoneFrom[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-3/cats/derived/EmptySuite.scala b/core/src/test/scala-3/cats/derived/EmptySuite.scala new file mode 100644 index 00000000..d4429a8c --- /dev/null +++ b/core/src/test/scala-3/cats/derived/EmptySuite.scala @@ -0,0 +1,89 @@ +/* + * 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.* +import scala.util.NotGiven + +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(0, "", 0, Nil, ""))) + 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]])) + + inline def testNoAuto[A]: Unit = + testNoInstance("Empty[" + nameOf[A] + "]", "Could not find an instance of Empty") + + inline def testNoSemi[A]: Unit = + testNoInstance("semiauto.empty[" + nameOf[A] + "]", "no implicit argument of type cats.derived.DerivedEmpty") + + locally { + import auto.empty.given + testEmpty("auto") + testNoAuto[IList[Int]] + testNoAuto[Snoc[Int]] + testNoAuto[Rgb] + } + + locally { + import semiInstances.given + testEmpty("semiauto") + testNoSemi[IList[Int]] + testNoSemi[Snoc[Int]] + testNoSemi[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 index b3012a7e..cc1e6c83 100644 --- a/core/src/test/scala-3/cats/derived/EmptyTests.scala +++ b/core/src/test/scala-3/cats/derived/EmptyTests.scala @@ -2,7 +2,7 @@ package cats.derived import alleycats.* import cats.* -import cats.derived.semiauto.* +import cats.derived.* object EmptyTests: case class Foo(i: Int, b: IntTree) derives Empty diff --git a/core/src/test/scala-3/cats/derived/KittensSuite.scala b/core/src/test/scala-3/cats/derived/KittensSuite.scala index f81dc759..4103bd4d 100644 --- a/core/src/test/scala-3/cats/derived/KittensSuite.scala +++ b/core/src/test/scala-3/cats/derived/KittensSuite.scala @@ -19,13 +19,13 @@ package cats.derived import cats.platform.Platform import cats.syntax.AllSyntax import munit.DisciplineSuite -import org.scalacheck.Test.Parameters 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. +/** An opinionated stack of traits to improve consistency and reduce boilerplate in Kittens tests. Note that unlike the + * corresponding CatsSuite in the Cat project, this trait does not mix in any instances. */ abstract class KittensSuite extends DisciplineSuite, AllSyntax, TestEqInstances: override val scalaCheckTestParameters: Parameters = super.scalaCheckTestParameters @@ -37,3 +37,13 @@ abstract class KittensSuite extends DisciplineSuite, AllSyntax, TestEqInstances: given [A: Arbitrary]: Arbitrary[List[A]] = Arbitrary.arbContainer + + inline def nameOf[A]: String = + ${ KittensSuite.nameOfMacro[A] } + + inline def testNoInstance(inline code: String, message: String): Unit = + test(s"No $code")(assert(compileErrors(code).contains(message))) + +object KittensSuite: + def nameOfMacro[A: Type](using Quotes) = + Expr(Type.show[A]) diff --git a/core/src/test/scala-3/cats/derived/MonoidTests.scala b/core/src/test/scala-3/cats/derived/MonoidTests.scala index a802ca67..412a36d1 100644 --- a/core/src/test/scala-3/cats/derived/MonoidTests.scala +++ b/core/src/test/scala-3/cats/derived/MonoidTests.scala @@ -2,7 +2,7 @@ package cats.derived import alleycats.* import cats.* -import cats.derived.semiauto.* +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/adtdefns.scala b/core/src/test/scala-3/cats/derived/adtdefns.scala index 0204e106..7f5d171f 100644 --- a/core/src/test/scala-3/cats/derived/adtdefns.scala +++ b/core/src/test/scala-3/cats/derived/adtdefns.scala @@ -25,9 +25,10 @@ import scala.annotation.tailrec object TestDefns { sealed trait Rgb - case object Red extends Rgb - case object Green extends Rgb - case object Blue extends 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 {