diff --git a/test/shared/src/main/scala/zio/test/laws/GenF.scala b/test/shared/src/main/scala/zio/test/laws/GenF.scala index b26233663add..989f935ab41d 100644 --- a/test/shared/src/main/scala/zio/test/laws/GenF.scala +++ b/test/shared/src/main/scala/zio/test/laws/GenF.scala @@ -26,7 +26,6 @@ import zio.test.{ Gen, Sized } * knows how to generate lists with elements given a generator of elements of * that type. You can think of `GenF` as a "recipe" for building generators * for parameterized types. - * ` */ trait GenF[-R, F[_]] { diff --git a/test/shared/src/main/scala/zio/test/laws/GenF2.scala b/test/shared/src/main/scala/zio/test/laws/GenF2.scala new file mode 100644 index 000000000000..c98376429237 --- /dev/null +++ b/test/shared/src/main/scala/zio/test/laws/GenF2.scala @@ -0,0 +1,31 @@ +package zio.test.laws + +import zio.random.Random +import zio.test.{ FunctionVariants, Gen } + +/** + * A `GenF` knows how to construct a generator of `F[A,B]` values given a + * generator of `A` and generator of `B` values. For example, a `GenF2` of `Function1` values + * knows how to generate functions A => B with elements given a generator of elements of + * that type `B`. + */ +trait GenF2[-R, F[_, _]] { + + /** + * Construct a generator of `F[A,B]` values given a generator of `B` values. + */ + def apply[R1 <: R, A, B](gen: Gen[R1, B]): Gen[R1, F[A, B]] +} + +object GenF2 extends FunctionVariants { + + /** + * A generator of `Function1` A => B values. + */ + val function1: GenF2[Random, Function1] = + new GenF2[Random, Function1] { + + override def apply[R1 <: Random, A, B](gen: Gen[R1, B]): Gen[R1, Function1[A, B]] = + function[R1, A, B](gen) + } +} diff --git a/test/shared/src/main/scala/zio/test/laws/ZLawful.scala b/test/shared/src/main/scala/zio/test/laws/ZLawful.scala index 1af55fa63fb8..19c4794ab02a 100644 --- a/test/shared/src/main/scala/zio/test/laws/ZLawful.scala +++ b/test/shared/src/main/scala/zio/test/laws/ZLawful.scala @@ -34,6 +34,7 @@ package zio.test.laws */ trait ZLawful[-Caps[_], -R] { self => def laws: ZLaws[Caps, R] + def +[Caps1[x] <: Caps[x], R1 <: R](that: ZLawful[Caps1, R1]): ZLawful[Caps1, R1] = new ZLawful[Caps1, R1] { val laws = self.laws + that.laws diff --git a/test/shared/src/main/scala/zio/test/laws/ZLawfulF.scala b/test/shared/src/main/scala/zio/test/laws/ZLawfulF.scala index a9268c85468b..8a868cd64d57 100644 --- a/test/shared/src/main/scala/zio/test/laws/ZLawfulF.scala +++ b/test/shared/src/main/scala/zio/test/laws/ZLawfulF.scala @@ -17,7 +17,7 @@ package zio.test.laws /** - * `ZLawful[CapsF, Caps, R]` describes a set of laws that a parameterized type + * `ZLawfulF[CapsF, Caps, R]` describes a set of laws that a parameterized type * `F[A]` with capabilities `CapsF` is expected to satisfy with respect to all * types `A` that have capabilities `Caps`. Lawful instances can be combined * using `+` to describe a set of capabilities and all of the laws that those diff --git a/test/shared/src/main/scala/zio/test/laws/ZLawfulF2.scala b/test/shared/src/main/scala/zio/test/laws/ZLawfulF2.scala new file mode 100644 index 000000000000..ebc00d9f7153 --- /dev/null +++ b/test/shared/src/main/scala/zio/test/laws/ZLawfulF2.scala @@ -0,0 +1,15 @@ +package zio.test.laws + +object ZLawfulF2 { + + trait Divariant[-CapsBoth[_[-_, +_]], -CapsLeft[_], -CapsRight[_], -R] { self => + def laws: ZLawsF2.Divariant[CapsBoth, CapsLeft, CapsRight, R] + + def +[CapsBoth1[x[-_, +_]] <: CapsBoth[x], CapsLeft1[x] <: CapsLeft[x], CapsRight1[x] <: CapsRight[x], R1 <: R]( + that: Divariant[CapsBoth1, CapsLeft1, CapsRight1, R1] + ): Divariant[CapsBoth1, CapsLeft1, CapsRight1, R1] = + new Divariant[CapsBoth1, CapsLeft1, CapsRight1, R1] { + val laws = self.laws + that.laws + } + } +} diff --git a/test/shared/src/main/scala/zio/test/laws/ZLawsF2.scala b/test/shared/src/main/scala/zio/test/laws/ZLawsF2.scala new file mode 100644 index 000000000000..62c6d54ca61a --- /dev/null +++ b/test/shared/src/main/scala/zio/test/laws/ZLawsF2.scala @@ -0,0 +1,90 @@ +package zio.test.laws + +import zio.{ URIO, ZIO } +import zio.test.{ check, Gen, TestConfig, TestResult } + +object ZLawsF2 { + + /** + * `ZLawsF2` for Divariant type constructors. + */ + abstract class Divariant[-CapsF[_[-_, +_]], -CapsLeft[_], -CapsRight[_], -R] { self => + + /** + * Test that values of type `F[+_,-_]` satisfy the laws using the specified + * function to construct a generator of `F[A,B]` values given a generator of + * `B` values. + */ + def run[R1 <: R with TestConfig, F[-_, +_]: CapsF, A: CapsLeft, B: CapsRight]( + genF: GenF2[R1, F], + gen: Gen[R1, B] + ): ZIO[R1, Nothing, TestResult] + + /** + * Combine these laws with the specified laws to produce a set of laws that + * require both sets of laws to be satisfied. + */ + def +[CapsF1[x[-_, +_]] <: CapsF[x], CapsLeft1[x] <: CapsLeft[x], CapsRight1[x] <: CapsRight[x], R1 <: R]( + that: Divariant[CapsF1, CapsLeft1, CapsRight1, R1] + ): Divariant[CapsF1, CapsLeft1, CapsRight1, R1] = + Divariant.Both(self, that) + } + + object Divariant { + + private final case class Both[-CapsBothF[_[-_, +_]], -CapsLeft[_], -CapsRight[_], -R]( + left: Divariant[CapsBothF, CapsLeft, CapsRight, R], + right: Divariant[CapsBothF, CapsLeft, CapsRight, R] + ) extends Divariant[CapsBothF, CapsLeft, CapsRight, R] { + + override final def run[R1 <: R with TestConfig, F[-_, +_]: CapsBothF, A: CapsLeft, B: CapsRight]( + genF: GenF2[R1, F], + gen: Gen[R1, B] + ): ZIO[R1, Nothing, TestResult] = { + val lhs: ZIO[R1, Nothing, TestResult] = left.run(genF, gen) + val rhs: ZIO[R1, Nothing, TestResult] = right.run(genF, gen) + lhs.zipWith(rhs)(_ && _) + } + } + + /** + * Constructs a law from a pure function taking one parameterized value and + * two functions that can be composed. + */ + abstract class ComposeLaw[-CapsBothF[_[-_, +_]], -Caps[_]](label: String) + extends Divariant[CapsBothF, Caps, Caps, Any] { + self => + def apply[F[-_, +_]: CapsBothF, A: Caps, B: Caps, A1: Caps, A2: Caps]( + fa: F[A, B], + f: A => A1, + g: A1 => A2 + ): TestResult + + final def run[R <: TestConfig, F[-_, +_]: CapsBothF, A: Caps, B: Caps, A1: Caps, A2: Caps]( + genF: GenF2[R, F], + genB: Gen[R, B], + genA1: Gen[R, A1], + genA2: Gen[R, A2] + ): URIO[R, TestResult] = + check( + genF[R, A, B](genB), + Gen.function[R, A, A1](genA1), + Gen.function[R, A1, A2](genA2) + )(apply(_, _, _).map(_.label(label))) + } + + /** + * Constructs a law from a pure function taking a single parameter. + */ + abstract class Law1[-CapsBothF[_[-_, +_]], -CapsLeft[_], -CapsRight[_]](label: String) + extends Divariant[CapsBothF, CapsLeft, CapsRight, Any] { self => + def apply[F[-_, +_]: CapsBothF, A: CapsLeft, B: CapsRight](fa: F[A, B]): TestResult + + final def run[R <: TestConfig, F[-_, +_]: CapsBothF, A: CapsLeft, B: CapsRight]( + genF: GenF2[R, F], + gen: Gen[R, B] + ): URIO[R, TestResult] = + check(genF[R, A, B](gen))(apply(_).map(_.label(label))) + } + } +} diff --git a/test/shared/src/main/scala/zio/test/laws/package.scala b/test/shared/src/main/scala/zio/test/laws/package.scala index 60ffb3a97ce6..2b14a869a27f 100644 --- a/test/shared/src/main/scala/zio/test/laws/package.scala +++ b/test/shared/src/main/scala/zio/test/laws/package.scala @@ -78,6 +78,11 @@ package object laws { type Invariant[-CapsF[_[_]], -Caps[_]] = ZLawfulF.Invariant[CapsF, Caps, Any] } + object LawfulF2 { + type Divariant[-CapsBoth[_[-_, +_]], -CapsLeft[_], -CapsRight[_]] = + ZLawfulF2.Divariant[CapsBoth, CapsLeft, CapsRight, Any] + } + object Laws { type Law1[-Caps[_]] = ZLaws.Law1[Caps] type Law1M[-Caps[_]] = ZLaws.Law1M[Caps, Any] @@ -129,6 +134,17 @@ package object laws { } } + object LawsF2 { + type Divariant[-CapsBoth[_[-_, +_]], -CapsLeft[_], -CapsRight[_]] = + ZLawsF2.Divariant[CapsBoth, CapsLeft, CapsRight, Any] + + object Divariant { + type ComposeLaw[-CapsBothF[_[-_, +_]], -Caps[_]] = ZLawsF2.Divariant.ComposeLaw[CapsBothF, Caps] + type Law1[CapsBothF[_[-_, +_]], -CapsLeft[_], -CapsRight[_]] = + ZLawsF2.Divariant.Law1[CapsBothF, CapsLeft, CapsRight] + } + } + /** * Checks that all values generated by a the specified generator satisfy the * expected behavior of the lawful instance.