Skip to content

Avoiding generator failures when size is zero #50

@travisbrown

Description

@travisbrown

Failing generators are really bad now that we have non-constant arbitrary functions in ScalaCheck 1.13. Here's an example of a place this comes up in this library (derived from an example by @nrinaudo):

import org.scalacheck._, Shapeless._

sealed trait Foo; case object Bar extends Foo

val prop = Prop.forAll { (f: Int => Foo) => f(0); true }

And:

scala> prop.check
! Exception raised on property evaluation.
> ARG_0: <function1>
> Exception: org.scalacheck.Gen$RetrievalError: couldn't generate value
org.scalacheck.Gen.loop$1(Gen.scala:57)
org.scalacheck.Gen.doPureApply(Gen.scala:58)
...

I've taken a shot at a diagnosis here, but in short it looks like something like this in MkCoproductArbitrary.ccons would work:

Arbitrary {
  Gen.sized {
    case size =>
      val sig = math.signum(size)

      try {
        Gen.frequency(
          1   -> Gen.resize(size - sig, Gen.lzy(headArbitrary.value.arbitrary)).map(Inl(_)),
          n() -> Gen.resize(size - sig, Gen.lzy(tailArbitrary.arbitrary.arbitrary)).map(Inr(_))
        )
      } catch {
        case _: StackOverflowError => Gen.fail
      }
  }
}

(I'd also just use math.min(0, size - 1) instead of the signum stuff, but that's not really relevant.)

Catching the stack overflow is an awful hack, but it avoids the Gen.fail on size == 0 for non-recursive ADTs while still not stack-overflowing for recursive ones.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions