c-cube / qcheck

QuickCheck inspired property-based testing for OCaml.
https://c-cube.github.io/qcheck/
BSD 2-Clause "Simplified" License
345 stars 37 forks source link

ppx_deriving_qcheck cannot derive a generator from a recursive type declaration with containers #269

Open Invizory opened 1 year ago

Invizory commented 1 year ago

Minimal reproducible example:

type t = Box of t list [@@deriving qcheck]

ppx_deriving_qcheck produces the following generator with unbound gen:

let gen = QCheck.Gen.map (fun gen0 -> Box gen0) (QCheck.Gen.list gen)

This trick (given -rectypes is enabled) make it derive the following generator:

type 'a open_t = Box of 'a list
and t = t open_t
[@@deriving qcheck]
let rec gen_open_t gen_a =
  QCheck.Gen.map (fun gen0 -> Box gen0) (QCheck.Gen.list gen_a)
and gen_sized n = gen_open_t (gen_sized (n / 2))
let gen = QCheck.Gen.sized gen_sized

However, this generatior is non-terminating.

jmid commented 1 year ago

Thanks for the report!

Ping @vch9 - I think we are not handling type constructors correctly in is_rec_typ

vch9 commented 1 year ago

Thanks for the report again.

Definitely a problem in is_rec_type, t should not be considered recursive (in the latter). I will try to have a look

vch9 commented 1 year ago

I think the deriver fails on a very much simpler example: type t = t list. I did not consider the rectypes flag as I just discovered it :sweat_smile:.

It creates:

let gen = QCheck.Gen.list gen
let arb = QCheck.make @@ gen

Should we be smarter than:

let rec gen = QCheck.Gen.list gen
let arb = QCheck.make @@ gen

?

jmid commented 1 year ago

IMO using rectypes is a cornercase that we don't necessarily have to support (I read the report as trying to use it as a workaround).

I suggest prioritising the reported case type t = Box of t list of "vanilla OCaml usage" where it seems we don't identify an occurrence of t as recursive when it is a type parameter to a type constructor such as list (or option or result ...) :thinking:

In trying out a potential workaround I just experienced the following error variant:

utop # #require "ppx_deriving";;
utop # #require "ppx_deriving_qcheck";;
utop # type t = Box of foo
       and foo = t list [@@deriving qcheck];;
Error: Unbound value n
7h3kk1d commented 2 weeks ago

I'm not sure but I think this is related. I'm having an issue where I can't derive a generator in a mutually recursive datatype with equivalent constructors:

[@deriving qcheck]
type conflicting_constructor =
  | A
  | B(conflicting_constructor2)
and conflicting_constructor2 =
  | B(conflicting_constructor)
  | C;
  Error: This expression has type
         conflicting_constructor QCheck.Gen.t =
           Random.State.t -> conflicting_constructor
       but an expression was expected of type
         conflicting_constructor2 QCheck.Gen.t =
           Random.State.t -> conflicting_constructor2
       Type conflicting_constructor is not compatible with type
         conflicting_constructor2