diff --git a/project/MimaSettings.scala b/project/MimaSettings.scala index d8fa47d62..bf581aae8 100644 --- a/project/MimaSettings.scala +++ b/project/MimaSettings.scala @@ -25,6 +25,14 @@ object MimaSettings { ) private def otherProblems = Seq( - ) + "org.scalacheck.Shrink.shrinkTuple2", + "org.scalacheck.Shrink.shrinkTuple3", + "org.scalacheck.Shrink.shrinkTuple4", + "org.scalacheck.Shrink.shrinkTuple5", + "org.scalacheck.Shrink.shrinkTuple6", + "org.scalacheck.Shrink.shrinkTuple7", + "org.scalacheck.Shrink.shrinkTuple8", + "org.scalacheck.Shrink.shrinkTuple9" + ).map(m => ProblemFilters.exclude[IncompatibleSignatureProblem](m)) } diff --git a/project/codegen.scala b/project/codegen.scala index 27312a0fe..5a8526a21 100644 --- a/project/codegen.scala +++ b/project/codegen.scala @@ -50,6 +50,27 @@ object codegen { def fntype(i: Int) = s"(${types(i)}) => Z" + def shrinkTuple(i: Int): String = + s"""| /** Shrink instance of ${i}-tuple */ + | implicit def shrinkTuple${i}[ + | ${shrinkTypes(i)} + | ]: Shrink[(${types(i)})] = + | Shrink { case (${vals(i)}) => + | ${shrinkEachComponentOneByOne(i)} + | }""".stripMargin + + def shrinkTypes(n: Int): String = + (1 to n).map(i => s"T${i}:Shrink").mkString(", ") + + def shrinkEachComponentOneByOne(n: Int): String = + (1 to n).map(shrinkSingleComponent(n, _)).mkString(" append\n ") + + def shrinkSingleComponent(n: Int, i: Int): String = + s"""Shrink.shrink(t${i}).map((${partialShrinkTuple(n, i)}))""" + + def partialShrinkTuple(n: Int, i: Int): String = + (1 to n).map(k => if (k == i) "_" else s"t${k}").mkString(", ") + def arbfn(i: Int) = s""" | /** Arbitrary instance of Function${i} */ | implicit def arbFunction${i}[${types(i)},Z](implicit g: Arbitrary[Z], ${coImplicits(i)}): Arbitrary[${fntype(i)}] = @@ -158,6 +179,20 @@ object codegen { } val genAll: Seq[GeneratedFile] = Seq( + GeneratedFile( + "ShrinkArities.scala", + s"""/** + |Defines implicit [[org.scalacheck.Shrink]] instances for tuples + | + |Auto-generated using project/codegen.scala + |*/ + |package org.scalacheck + | + |private[scalacheck] trait ShrinkArities{ + | + |${2 to 22 map shrinkTuple mkString("\n\n")} + |} + |""".stripMargin), GeneratedFile( "ArbitraryArities.scala", s"""/** diff --git a/src/main/scala/org/scalacheck/Shrink.scala b/src/main/scala/org/scalacheck/Shrink.scala index 86ee1316f..2b09f46a7 100644 --- a/src/main/scala/org/scalacheck/Shrink.scala +++ b/src/main/scala/org/scalacheck/Shrink.scala @@ -29,7 +29,7 @@ trait ShrinkLowPriority { implicit def shrinkAny[T]: Shrink[T] = Shrink(_ => Stream.empty) } -object Shrink extends ShrinkLowPriority with ShrinkVersionSpecific with time.JavaTimeShrink { +object Shrink extends ShrinkLowPriority with ShrinkVersionSpecific with time.JavaTimeShrink with ShrinkArities { import Stream.{cons, empty} import scala.collection._ @@ -114,108 +114,6 @@ object Shrink extends ShrinkLowPriority with ShrinkVersionSpecific with time.Jav case Some(x) => cons(None, for(y <- shrink(x)) yield Some(y)) } - /** Shrink instance of 2-tuple */ - implicit def shrinkTuple2[ - T1:Shrink, T2:Shrink - ]: Shrink[(T1,T2)] = - Shrink { case (t1,t2) => - shrink(t1).map((_,t2)) append - shrink(t2).map((t1,_)) - } - - /** Shrink instance of 3-tuple */ - implicit def shrinkTuple3[ - T1:Shrink, T2:Shrink, T3:Shrink - ]: Shrink[(T1,T2,T3)] = - Shrink { case (t1,t2,t3) => - shrink(t1).map((_, t2, t3)) append - shrink(t2).map((t1, _, t3)) append - shrink(t3).map((t1, t2, _)) - } - - /** Shrink instance of 4-tuple */ - implicit def shrinkTuple4[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink - ]: Shrink[(T1,T2,T3,T4)] = - Shrink { case (t1,t2,t3,t4) => - shrink(t1).map((_, t2, t3, t4)) append - shrink(t2).map((t1, _, t3, t4)) append - shrink(t3).map((t1, t2, _, t4)) append - shrink(t4).map((t1, t2, t3, _)) - } - - /** Shrink instance of 5-tuple */ - implicit def shrinkTuple5[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink, T5:Shrink - ]: Shrink[(T1,T2,T3,T4,T5)] = - Shrink { case (t1,t2,t3,t4,t5) => - shrink(t1).map((_, t2, t3, t4, t5)) append - shrink(t2).map((t1, _, t3, t4, t5)) append - shrink(t3).map((t1, t2, _, t4, t5)) append - shrink(t4).map((t1, t2, t3, _, t5)) append - shrink(t5).map((t1, t2, t3, t4, _)) - } - - /** Shrink instance of 6-tuple */ - implicit def shrinkTuple6[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink, T5:Shrink, T6:Shrink - ]: Shrink[(T1,T2,T3,T4,T5,T6)] = - Shrink { case (t1,t2,t3,t4,t5,t6) => - shrink(t1).map((_, t2, t3, t4, t5, t6)) append - shrink(t2).map((t1, _, t3, t4, t5, t6)) append - shrink(t3).map((t1, t2, _, t4, t5, t6)) append - shrink(t4).map((t1, t2, t3, _, t5, t6)) append - shrink(t5).map((t1, t2, t3, t4, _, t6)) append - shrink(t6).map((t1, t2, t3, t4, t5, _)) - } - - /** Shrink instance of 7-tuple */ - implicit def shrinkTuple7[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink, T5:Shrink, T6:Shrink, T7:Shrink - ]: Shrink[(T1,T2,T3,T4,T5,T6,T7)] = - Shrink { case (t1,t2,t3,t4,t5,t6,t7) => - shrink(t1).map((_, t2, t3, t4, t5, t6, t7)) append - shrink(t2).map((t1, _, t3, t4, t5, t6, t7)) append - shrink(t3).map((t1, t2, _, t4, t5, t6, t7)) append - shrink(t4).map((t1, t2, t3, _, t5, t6, t7)) append - shrink(t5).map((t1, t2, t3, t4, _, t6, t7)) append - shrink(t6).map((t1, t2, t3, t4, t5, _, t7)) append - shrink(t7).map((t1, t2, t3, t4, t5, t6, _)) - } - - /** Shrink instance of 8-tuple */ - implicit def shrinkTuple8[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink, T5:Shrink, T6:Shrink, - T7:Shrink, T8:Shrink - ]: Shrink[(T1,T2,T3,T4,T5,T6,T7,T8)] = - Shrink { case (t1,t2,t3,t4,t5,t6,t7,t8) => - shrink(t1).map((_, t2, t3, t4, t5, t6, t7, t8)) append - shrink(t2).map((t1, _, t3, t4, t5, t6, t7, t8)) append - shrink(t3).map((t1, t2, _, t4, t5, t6, t7, t8)) append - shrink(t4).map((t1, t2, t3, _, t5, t6, t7, t8)) append - shrink(t5).map((t1, t2, t3, t4, _, t6, t7, t8)) append - shrink(t6).map((t1, t2, t3, t4, t5, _, t7, t8)) append - shrink(t7).map((t1, t2, t3, t4, t5, t6, _, t8)) append - shrink(t8).map((t1, t2, t3, t4, t5, t6, t7, _)) - } - - /** Shrink instance of 9-tuple */ - implicit def shrinkTuple9[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink, T5:Shrink, T6:Shrink, - T7:Shrink, T8:Shrink, T9:Shrink - ]: Shrink[(T1,T2,T3,T4,T5,T6,T7,T8,T9)] = - Shrink { case (t1,t2,t3,t4,t5,t6,t7,t8,t9) => - shrink(t1).map((_, t2, t3, t4, t5, t6, t7, t8, t9)) append - shrink(t2).map((t1, _, t3, t4, t5, t6, t7, t8, t9)) append - shrink(t3).map((t1, t2, _, t4, t5, t6, t7, t8, t9)) append - shrink(t4).map((t1, t2, t3, _, t5, t6, t7, t8, t9)) append - shrink(t5).map((t1, t2, t3, t4, _, t6, t7, t8, t9)) append - shrink(t6).map((t1, t2, t3, t4, t5, _, t7, t8, t9)) append - shrink(t7).map((t1, t2, t3, t4, t5, t6, _, t8, t9)) append - shrink(t8).map((t1, t2, t3, t4, t5, t6, t7, _, t9)) append - shrink(t9).map((t1, t2, t3, t4, t5, t6, t7, t8, _)) - } - implicit def shrinkEither[T1:Shrink, T2:Shrink]: Shrink[Either[T1, T2]] = Shrink { x => x.fold(shrink(_).map(Left(_)), shrink(_).map(Right(_))) diff --git a/src/test/scala/org/scalacheck/ShrinkSpecification.scala b/src/test/scala/org/scalacheck/ShrinkSpecification.scala index d75612d49..1fa20cb29 100644 --- a/src/test/scala/org/scalacheck/ShrinkSpecification.scala +++ b/src/test/scala/org/scalacheck/ShrinkSpecification.scala @@ -102,6 +102,40 @@ object ShrinkSpecification extends Properties("Shrink") { shrink(e).forall(_.isRight) } + property("shrink[Unit].isEmpty") = Prop(shrink(()).isEmpty) + + // Tuples shrink even when one component doesn't + property("shrink[(T, Unit)] eqv shrink[T]") = + forAllNoShrink { (u: Unit, i: Int) => + shrink(((), i)) == shrink(i).map(((), _)) + } + + property("shrink[(Unit, T)] eqv shrink[T]") = + forAllNoShrink { (i: Int, u: Unit) => + shrink((i, ())) == shrink(i).map((_, ())) + } + + // Tuple shrinking is associative* for all arities, and can be inductively + // defined for n in terms of 2 and n-1. (* modulo ordering) + def eqvTupleShrinks[T: Ordering](xs: Stream[T], ys: Stream[T]): Boolean = + xs.toList.sorted == ys.toList.sorted + + property("shrink[(T, U, V)] eqv shrink[(T, (U, V))]") = + forAllNoShrink { (b: Byte, c: Char, s: Short) => + eqvTupleShrinks( + shrink((b, c, s)), + shrink((b, (c, s))).map { case (b, (c, s)) => (b, c, s) } + ) + } + + property("shrink[(T, U, V, W)] eqv shrink[((T, U, V), W)]") = + forAllNoShrink { (b: Byte, c: Char, s: Short, i: Int) => + eqvTupleShrinks( + shrink((b, c, s, i)), + shrink(((b, c, s), i)).map { case ((b, c, s), i) => (b, c, s, i) } + ) + } + property("suchThat") = { implicit def shrinkEvenLength[A]: Shrink[List[A]] = Shrink.shrinkContainer[List,A].suchThat(evenLength(_)) @@ -116,7 +150,7 @@ object ShrinkSpecification extends Properties("Shrink") { def shrinkEvenLength[A]: Shrink[List[A]] = Shrink.shrinkContainer[List,A].suchThat(evenLength(_)) - property("shrink[List[Int].suchThat") = { + property("shrink[List[Int]].suchThat") = { forAll { (l: List[Int]) => shrink(l)(shrinkEvenLength).forall(evenLength(_)) }