diff --git a/ppx_deriving_qcheck.opam b/ppx_deriving_qcheck.opam index 81e719ce..b4ca4220 100644 --- a/ppx_deriving_qcheck.opam +++ b/ppx_deriving_qcheck.opam @@ -12,6 +12,7 @@ depends: [ "ocaml" {>= "4.08.0"} "qcheck" {>= "0.17"} "ppxlib" {>= "0.22.0"} + "ppx_deriving" {>= "5.2.1"} "odoc" {with-doc} "alcotest" {with-test & >= "1.4.0" } ] diff --git a/src/ppx_deriving_qcheck/QCheck_generators.ml b/src/ppx_deriving_qcheck/QCheck_generators.ml new file mode 100644 index 00000000..a104ce04 --- /dev/null +++ b/src/ppx_deriving_qcheck/QCheck_generators.ml @@ -0,0 +1,96 @@ +open Ppxlib + +(** This module contains all generators from QCheck used to + derive a type declaration *) + +(** {2. Type} *) + +let ty = "QCheck.Gen.t" + +(** {2. Primitive generators} *) + +let unit loc = [%expr QCheck.Gen.unit] + +let int loc = [%expr QCheck.Gen.int] + +let string loc = [%expr QCheck.Gen.string] + +let char loc = [%expr QCheck.Gen.char] + +let bool loc = [%expr QCheck.Gen.bool] + +let float loc = [%expr QCheck.Gen.float] + +let int32 loc = [%expr QCheck.Gen.int32] + +let int64 loc = [%expr QCheck.Gen.int64] + +let option ~loc e = [%expr QCheck.Gen.option [%e e]] + +let list ~loc e = [%expr QCheck.Gen.list [%e e]] + +let array ~loc e = [%expr QCheck.Gen.array [%e e]] + +(** {2. Generator combinators} *) + +let pure ~loc x = [%expr QCheck.Gen.pure [%e x]] + +let frequency ~loc l = + match l with + | [%expr [([%e? _], [%e? x])]] -> x + | _ -> + [%expr QCheck.Gen.frequency [%e l]] + +let map ~loc pat expr gen = + [%expr QCheck.Gen.map (fun [%p pat] -> [%e expr]) [%e gen]] + +let pair ~loc a b = + [%expr QCheck.Gen.pair [%e a] [%e b]] + +let triple ~loc a b c = + [%expr QCheck.Gen.triple [%e a] [%e b] [%e c]] + +let quad ~loc a b c d= + [%expr QCheck.Gen.quad [%e a] [%e b] [%e c] [%e d]] + +let sized ~loc e = + [%expr QCheck.Gen.sized @@ [%e e]] + +let fix ~loc e = + [%expr QCheck.Gen.fix [%e e]] + +(** Observable generators *) +module Observable = struct + (** {2. Primitive generators} *) + let unit loc = [%expr QCheck.Observable.unit] + + let int loc = [%expr QCheck.Observable.int] + + let string loc = [%expr QCheck.Observable.string] + + let char loc = [%expr QCheck.Observable.char] + + let bool loc = [%expr QCheck.Observable.bool] + + let float loc = [%expr QCheck.Observable.float] + + let int32 loc = [%expr QCheck.Observable.int32] + + let int64 loc = [%expr QCheck.Observable.int64] + + let option ~loc e = [%expr QCheck.Observable.option [%e e]] + + let list ~loc e = [%expr QCheck.Observable.list [%e e]] + + let array ~loc e = [%expr QCheck.Observable.array [%e e]] + + (** {2. Observable combinators} *) + let pair ~loc a b = + [%expr QCheck.Observable.pair [%e a] [%e b]] + + let triple ~loc a b c = + [%expr QCheck.Observable.triple [%e a] [%e b] [%e c]] + + let quad ~loc a b c d= + [%expr QCheck.Observable.quad [%e a] [%e b] [%e c] [%e d]] +end diff --git a/src/ppx_deriving_qcheck/README.md b/src/ppx_deriving_qcheck/README.md index 1f0ef796..333c90fe 100644 --- a/src/ppx_deriving_qcheck/README.md +++ b/src/ppx_deriving_qcheck/README.md @@ -18,6 +18,16 @@ let test = (fun tree -> rev (rev tree) = tree) ``` +For `type tree` we derive two generators: +- `val gen_tree : tree Gen.t` and +- `val gen_tree_sized : int -> tree Gen.t` + +For non-recursive types the latter is however not derived. + +For types with the name `t` (i.e. `type t = ...`) which is a common idiom in OCaml code, +the deriver omits the name from the derived generators, +thus producing `val gen : t Gen.t` and optionally `val gen_sized : int -> t Gen.t`. + ### Overwrite generator If you wan't to specify your own `generator` for any type you can add an attribute to the type: @@ -238,20 +248,19 @@ let gen_color = type tree = Leaf of int | Node of tree * tree [@@deriving qcheck] -let gen_tree = - QCheck.Gen.sized @@ - (QCheck.Gen.fix - (fun self -> function - | 0 -> - QCheck.Gen.frequency - [(1, (QCheck.Gen.map (fun gen0 -> Leaf gen0) QCheck.Gen.int))] - | n -> - QCheck.Gen.frequency - [(1, - (QCheck.Gen.map (fun gen0 -> Leaf gen0) QCheck.Gen.int)); - (1, - (QCheck.Gen.map (fun (gen0, gen1) -> Node (gen0, gen1)) - (QCheck.Gen.pair (self (n / 2)) (self (n / 2)))))])) +(* ==> *) + +let rec gen_tree_sized n = + match n with + | 0 -> QCheck.Gen.map (fun gen0 -> Leaf gen0) QCheck.Gen.int + | n -> + QCheck.Gen.frequency + [(1, (QCheck.Gen.map (fun gen0 -> Leaf gen0) QCheck.Gen.int)); + (1, + (QCheck.Gen.map (fun (gen0, gen1) -> Node (gen0, gen1)) + (QCheck.Gen.pair (self (n / 2)) (self (n / 2)))))])) + +let gen_tree = QCheck.Gen.sized @@ gen_tree_sized ``` * Recursive polymorphic variants diff --git a/src/ppx_deriving_qcheck/args.ml b/src/ppx_deriving_qcheck/args.ml new file mode 100644 index 00000000..084a8a14 --- /dev/null +++ b/src/ppx_deriving_qcheck/args.ml @@ -0,0 +1,24 @@ +open Ppxlib + +(** [curry_args args body] adds parameter to [body] + + e.g.: + curry_args [gen_a; gen_b] () => fun gen_a -> fun gen_b -> () +*) +let rec curry_args ~loc args body = + match args with + | [] -> body + | x :: xs -> [%expr fun [%p x] -> [%e curry_args ~loc xs body]] + +(** [apply_args args body] applies parameters to [body] + + e.g.: + apply_args [gen_a; gen_b] f => f gen_a gen_b +*) +let apply_args ~loc args body = + let rec aux acc = function + | [] -> acc + | [arg] -> [%expr [%e acc] [%e arg]] + | arg :: args -> aux [%expr [%e acc] [%e arg]] args + in + aux body args diff --git a/src/ppx_deriving_qcheck/ppx_deriving_qcheck.ml b/src/ppx_deriving_qcheck/ppx_deriving_qcheck.ml index 5214dc87..a1d73b68 100644 --- a/src/ppx_deriving_qcheck/ppx_deriving_qcheck.ml +++ b/src/ppx_deriving_qcheck/ppx_deriving_qcheck.ml @@ -1,42 +1,75 @@ open Ppxlib +module G = QCheck_generators +module O = G.Observable -(** TypeGen can serve as a derivation environment. The map can be used - to remember how a type should be translated. +(** {1. ppx_deriving_qcheck} *) - For instance, a recursive type must be derivated to a self recursive - call reducing the size. +(** ppx_deriving_qcheck is a ppx deriver for QCheck generators. It does a + traversal map on type declarations annoted with [QCheck]. + Example: {[ - type tree = Leaf of int | Node of tree * tree + module Tree : sig + type t + + val gen : t QCheck.Gen.t + end = struct + type t = Leaf | Node of int * t * t + [@@deriving qcheck] + end ]} +*) + +(** {2. Misc. helpers} *) + +(** [name s] produces the generator name based on [s] *) +let name ?(sized = false) s = + let prefix = "gen" in + (match s with "t" -> prefix | s -> prefix ^ "_" ^ s) ^ + (if sized then "_sized" else "") - becomes +(** [pat ~loc s] creates a pattern for a generator based on {!name}. *) +let pat ~loc ?sized s = + let (module A) = Ast_builder.make loc in + let s = name ?sized s in + A.pvar s + +(** {2. Recursive generators} *) + +(** Recursive generators must be treated separatly: {[ - let gen_tree = - let open QCheck in - let open Gen in - sized - @@ fix (fun self -> function - | 0 -> frequency [ (1, pure Leaf) ] - | n -> - frequency - [ - (1, pure Leaf); - ( 1, - map - (fun (gen0, gen1, gen2) -> Node (gen0, gen1, gen2)) - (triple int (self (n / 1)) (self (n / 1))) );]) + type 'a list = Cons of 'a * 'a list | Nil ]} - The type [tree] is stored in a TypeGen.t with tree <- [%expr self (n/2)]. This - avoids the case where [tree] is derivated to [gen_tree] -*) -module TypeGen = Map.Make (struct - type t = string + becomes: + + {[ + let rec gen_list_sized gen_a n = + match n with + | 0 -> pure Nil + | n -> map2 (fun x xs -> Cons (x, xs) gen_a (gen_list_sized gen_a (n/2)) - let compare = compare -end) + let gen_list_sized gen_a = sized @@ (gen_list_sized gen_a) + ]} + + In the basic derivation {[ 'a list ]} would be translated to {[gen_list]}. + However, we want the generator to call itsef. + *) + +module Env = struct + (** [env] contains: + - the list of recursive types during the derivation + - the list of types to derive (i.e. mutual types) + - the current type to derive *) + type env = { + rec_types : string list; + curr_types : string list; + curr_type : string; + } + + let is_rec env x = List.mem x env.rec_types +end let rec longident_to_str = function | Lident s -> s @@ -44,200 +77,156 @@ let rec longident_to_str = function | Lapply (lg1, lg2) -> Printf.sprintf "%s %s" (longident_to_str lg1) (longident_to_str lg2) -let rec is_rec_typ typ_name = function +let rec is_rec_typ env = function | { ptyp_desc = Ptyp_constr ({ txt = x; _ }, _); _ } -> - longident_to_str x = typ_name - | { ptyp_desc = Ptyp_tuple xs; _ } -> List.exists (is_rec_typ typ_name) xs + List.exists (fun typ_name -> longident_to_str x = typ_name) env.Env.curr_types + | { ptyp_desc = Ptyp_tuple xs; _ } -> List.exists (is_rec_typ env) xs + | { ptyp_desc = Ptyp_variant (rws, _, _); _ } -> + List.exists (is_rec_row_field env) rws | _ -> false -let name s = - let prefix = "gen" in - match s with "t" -> prefix | s -> prefix ^ "_" ^ s +and is_rec_row_field env rw = + match rw.prf_desc with + | Rtag (lab, _, cts) -> + List.exists (fun typ_name -> lab.txt = typ_name) env.Env.curr_types || + List.exists (is_rec_typ env) cts + | Rinherit ct -> is_rec_typ env ct -let pat ~loc s = - let (module A) = Ast_builder.make loc in - let s = name s in - A.pvar s +let is_rec_constr_decl env cd = + match cd.pcd_args with + | Pcstr_tuple cts -> List.exists (is_rec_typ env) cts + | _ -> false + +(** [is_rec_type_decl env typ] looks for elements of [env.curr_types] + recursively in [typ]. *) +let is_rec_type_decl env typ = + let in_type_kind = + match typ.ptype_kind with + | Ptype_variant cstrs -> List.exists (is_rec_constr_decl env) cstrs + | _ -> false + in + let in_manifest = + match typ.ptype_manifest with + | Some x -> is_rec_typ env x + | None -> false + in + in_type_kind || in_manifest -let gen ~loc ?(env = TypeGen.empty) lg = +(** {2. Generator constructors} *) + +(** [gen_longident lg args] creates a generator using [lg]. + + The longident can either be a: + - Lident s: We transform to gen_s (or gen if s = "t") + - Ldot (lg, s): We transform to qualified generator (e.g. B.gen) +*) +let gen_longident ~loc ~env lg args = let (module A) = Ast_builder.make loc in match lg with | Lident s -> - Option.value ~default:(name s |> A.evar) @@ TypeGen.find_opt s env - | Ldot (lg, s) -> A.(pexp_ident (Located.mk @@ Ldot (lg, name s))) + if Env.is_rec env s then + name ~sized:true s |> A.evar |> + Args.apply_args ~loc args |> + Args.apply_args ~loc [ [%expr (n / 2)] ] + else + name s |> A.evar |> Args.apply_args ~loc args + | Ldot (lg, s) -> + A.(pexp_ident (Located.mk @@ Ldot (lg, name s))) |> + Args.apply_args ~loc args | Lapply (_, _) -> raise (Invalid_argument "gen received an Lapply") -let frequency ~loc l = [%expr QCheck.Gen.frequency [%e l]] +(** [gen_sized typ_name is_rec to_gen xs] uses [is_rec] to determine recursive + nodes in [xs]. -let pure ~loc x = [%expr QCheck.Gen.pure [%e x]] + If no recursive node is found, the type is _not_ recursive, we build a + generator using frequency. -let tree ~loc nodes leaves = - [%expr - QCheck.Gen.sized - @@ QCheck.Gen.fix (fun self -> function - | 0 -> [%e leaves] | n -> [%e nodes])] + However, if recursive nodes are found, we build a tree like generator using + {!gen_sized}. + + The function is generalized for variants and polymorphic variants: -let sized ~loc ~env typ_name (is_rec : 'a -> bool) - (to_gen : ?env:expression TypeGen.t -> 'a -> expression) (xs : 'a list) = + {[ + type t = Leaf | Node of int * t * t + + (* or *) + + type t = [`Leaf | `Node of int * t * t] + ]} + + Therefore, [is_rec] and [to_gen] are different for variants and polymorphic + variants. *) +let gen_sized ~loc (is_rec : 'a -> bool) (to_gen : 'a -> expression) (xs : 'a list) = let (module A) = Ast_builder.make loc in - let new_env = TypeGen.add typ_name [%expr self (n / 2)] env in let leaves = - List.filter (fun x -> not (is_rec x)) xs |> List.map (to_gen ~env) + List.filter (fun x -> not (is_rec x)) xs |> List.map to_gen in let nodes = List.filter is_rec xs in - if List.length nodes > 0 then - let nodes = List.map (to_gen ~env:new_env) nodes in - let leaves = A.elist leaves |> frequency ~loc - and nodes = A.elist (leaves @ nodes) |> frequency ~loc in - tree ~loc nodes leaves + if List.length nodes = 0 then + G.frequency ~loc (A.elist leaves) + else if List.length leaves = 0 then + let nodes = List.map to_gen nodes in + G.frequency ~loc (A.elist nodes) else - let gens = A.elist leaves in - frequency ~loc gens - -let mutually_recursive_gens ~loc gens = - let (module A) = Ast_builder.make loc in - let fake_gens = - List.map - (function - | [%stri let [%p? pat] = [%e? expr]] -> - let expr = [%expr fun () -> [%e expr]] in - A.value_binding ~pat ~expr - | _ -> assert false) - gens - in - let real_gens = - List.map - (function - | [%stri - let [%p? { ppat_desc = Ppat_var { txt = s; _ }; _ } as pat] = - [%e? _expr]] -> - let expr = A.evar s in - [%stri let [%p pat] = [%e expr] ()] - | _ -> assert false) - gens - in - - let mutual_gens = A.pstr_value Recursive fake_gens in - mutual_gens :: real_gens - -module Tuple = struct - type 'a t = - | Pair of 'a t * 'a t - | Triple of 'a * 'a * 'a - | Quad of 'a * 'a * 'a * 'a - | Elem of 'a - - let rec from_list = function - | [ a; b; c; d ] -> Quad (a, b, c, d) - | [ a; b; c ] -> Triple (a, b, c) - | [ a; b ] -> Pair (Elem a, Elem b) - | [ a ] -> Elem a - | l -> - let n = List.length l / 2 in - let i = ref 0 in - let l1 = - List.filter - (fun _ -> - let x = !i in - i := x + 1; - x < n) - l - in - i := 0; - let l2 = - List.filter - (fun _ -> - let x = !i in - i := x + 1; - x >= n) - l - in - Pair (from_list l1, from_list l2) - - let rec to_list = function - | Quad (a, b, c, d) -> [ a; b; c; d ] - | Triple (a, b, c) -> [ a; b; c ] - | Pair (a, b) -> to_list a @ to_list b - | Elem a -> [ a ] - - let to_expr ~loc t = - let l = to_list t in - let (module A) = Ast_builder.make loc in - List.mapi - (fun i _ -> - let s = Printf.sprintf "gen%d" i in - A.evar s) - l - |> A.pexp_tuple - - let rec nest ~loc ~pair ~triple ~quad = function - | Quad (a, b, c, d) -> [%expr [%e quad] [%e a] [%e b] [%e c] [%e d]] - | Triple (a, b, c) -> [%expr [%e triple] [%e a] [%e b] [%e c]] - | Pair (a, b) -> - [%expr - [%e pair] - [%e nest ~loc ~pair ~triple ~quad a] - [%e nest ~loc ~pair ~triple ~quad b]] - | Elem a -> a - - let to_gen ~loc t = - let pair = [%expr QCheck.Gen.pair] in - let triple = [%expr QCheck.Gen.triple] in - let quad = [%expr QCheck.Gen.quad] in - nest ~loc ~pair ~triple ~quad t - - let to_obs ~loc t = - let pair = [%expr QCheck.Observable.pair] in - let triple = [%expr QCheck.Observable.triple] in - let quad = [%expr QCheck.Observable.quad] in - nest ~loc ~pair ~triple ~quad t - - let to_pat ~loc t = - let fresh_id = - let id = ref 0 in - fun () -> - let x = !id in - let () = id := x + 1 in - Printf.sprintf "gen%d" x - in - let (module A) = Ast_builder.make loc in - let rec aux = function - | Quad (_, _, _, _) -> - let a = A.pvar @@ fresh_id () in - let b = A.pvar @@ fresh_id () in - let c = A.pvar @@ fresh_id () in - let d = A.pvar @@ fresh_id () in - [%pat? [%p a], [%p b], [%p c], [%p d]] - | Triple (_, _, _) -> - let a = A.pvar @@ fresh_id () in - let b = A.pvar @@ fresh_id () in - let c = A.pvar @@ fresh_id () in - [%pat? [%p a], [%p b], [%p c]] - | Pair (a, b) -> - let a = aux a in - let b = aux b in - [%pat? [%p a], [%p b]] - | Elem _ -> A.pvar @@ fresh_id () - in - aux t -end + let nodes = List.map to_gen nodes in + let leaves = A.elist leaves |> G.frequency ~loc + and nodes = A.elist (leaves @ nodes) |> G.frequency ~loc in + [%expr + match n with + | 0 -> [%e leaves] + | n -> [%e nodes] + ] + +(** [gen_tuple ~loc ?f tys] transforms list of type [tys] into a tuple generator. + + [f] can be used to transform tuples, for instance: + {[ + type t = Foo of int * int + ]} -let map ~loc pat expr gen = - [%expr QCheck.Gen.map (fun [%p pat] -> [%e expr]) [%e gen]] + Without [f]: + {[ + let gen = QCheck.Gen.(map (fun (x, y) -> (x, y)) (pair int int)) + ]} -let tuple ~loc ?(f = fun x -> x) tys = + With [f], building Foo: + {[ + let gen = QCheck.Gen.(map (fun (x, y) -> Foo (x, y)) (pair int int)) + ]} +*) +let gen_tuple ~loc ?(f = fun x -> x) tys = let tuple = Tuple.from_list tys in let gen = Tuple.to_gen ~loc tuple in let expr = Tuple.to_expr ~loc tuple |> f in let pat = Tuple.to_pat ~loc tuple in - map ~loc pat expr gen + G.map ~loc pat expr gen -let record ~loc ~gens ?(f = fun x -> x) xs = +(** [gen_record loc gens ?f label_decls] transforms [gens] and [label_decls] to + a record generator. + + Similarly to {!gen_tuple}, we can use [f] to transform records, for instance: + {[ + type t = Foo of { left : int; right : int } + ]} + + Without [f]: + {[ + let gen = QCheck.Gen.(map (fun (x, y) -> {left = x; right = y}) (pair int int)) + ]} + + With [f], building Foo: + {[ + let gen = QCheck.Gen.(map (fun (x, y) -> Foo {left = x; right = y}) (pair int int)) + ]} + +*) +let gen_record ~loc ~gens ?(f = fun x -> x) xs = let (module A) = Ast_builder.make loc in let tuple = Tuple.from_list gens in let gen = Tuple.to_gen ~loc tuple in let pat = Tuple.to_pat ~loc tuple in + (* TODO: this should be handled in {!Tuple} *) let gens = List.mapi (fun i _ -> @@ -253,43 +242,43 @@ let record ~loc ~gens ?(f = fun x -> x) xs = in let expr = A.pexp_record fields None |> f in - map ~loc pat expr gen + G.map ~loc pat expr gen -let rec gen_from_type ~loc ?(env = TypeGen.empty) ?(typ_name = "") typ = +(** {2. Core derivation} *) + +(** [gen_from_type typ] performs the AST traversal and derivation to qcheck generators *) +let rec gen_from_type ~loc ~env typ = Option.value (Attributes.gen typ) ~default: (match typ with - | [%type: unit] -> [%expr QCheck.Gen.unit] - | [%type: int] -> [%expr QCheck.Gen.int] - | [%type: string] | [%type: String.t] -> [%expr QCheck.Gen.string] - | [%type: char] -> [%expr QCheck.Gen.char] - | [%type: bool] -> [%expr QCheck.Gen.bool] - | [%type: float] -> [%expr QCheck.Gen.float] - | [%type: int32] | [%type: Int32.t] -> [%expr QCheck.Gen.int32] - | [%type: int64] | [%type: Int64.t] -> [%expr QCheck.Gen.int64] - | [%type: [%t? typ] option] -> - [%expr QCheck.Gen.option [%e gen_from_type ~loc ~env typ]] - | [%type: [%t? typ] list] -> - [%expr QCheck.Gen.list [%e gen_from_type ~loc ~env typ]] - | [%type: [%t? typ] array] -> - [%expr QCheck.Gen.array [%e gen_from_type ~loc ~env typ]] + | [%type: unit] -> G.unit loc + | [%type: int] -> G.int loc + | [%type: string] | [%type: String.t] -> G.string loc + | [%type: char] -> G.char loc + | [%type: bool] -> G.bool loc + | [%type: float] -> G.float loc + | [%type: int32] | [%type: Int32.t] -> G.int32 loc + | [%type: int64] | [%type: Int64.t] -> G.int64 loc + | [%type: [%t? typ] option] -> G.option ~loc (gen_from_type ~loc ~env typ) + | [%type: [%t? typ] list] -> G.list ~loc (gen_from_type ~loc ~env typ) + | [%type: [%t? typ] array] -> G.array ~loc (gen_from_type ~loc ~env typ) | { ptyp_desc = Ptyp_tuple typs; _ } -> let tys = List.map (gen_from_type ~loc ~env) typs in - tuple ~loc tys - | { ptyp_desc = Ptyp_constr ({ txt = ty; _ }, _); _ } -> - let x = TypeGen.find_opt (longident_to_str ty) env in - Option.value ~default:(gen ~loc ~env ty) x - | { ptyp_desc = Ptyp_var s; _ } -> gen ~loc (Lident s) + gen_tuple ~loc tys + | { ptyp_desc = Ptyp_constr ({ txt = ty; _ }, args); _ } -> + let args = List.map (gen_from_type ~loc ~env) args in + gen_longident ~loc ~env ty args + | { ptyp_desc = Ptyp_var s; _ } -> + gen_longident ~loc ~env (Lident s) [] | { ptyp_desc = Ptyp_variant (rws, _, _); _ } -> - gen_from_variant ~loc typ_name rws + gen_from_variant ~loc ~env rws | { ptyp_desc = Ptyp_arrow (_, left, right); _ } -> gen_from_arrow ~loc ~env left right | _ -> Ppxlib.Location.raise_errorf ~loc "This type is not supported in ppx_deriving_qcheck") -and gen_from_constr ~loc ?(env = TypeGen.empty) - { pcd_name; pcd_args; pcd_attributes; _ } = +and gen_from_constr ~loc ~env { pcd_name; pcd_args; pcd_attributes; _ } = let (module A) = Ast_builder.make loc in let constr_decl = A.constructor_declaration ~name:pcd_name ~args:pcd_args ~res:None @@ -299,73 +288,51 @@ and gen_from_constr ~loc ?(env = TypeGen.empty) let gen = match pcd_args with | Pcstr_tuple [] | Pcstr_record [] -> - pure ~loc @@ A.econstruct constr_decl None + G.pure ~loc @@ A.econstruct constr_decl None | Pcstr_tuple xs -> let tys = List.map (gen_from_type ~loc ~env) xs in - tuple ~loc ~f:mk_constr tys + gen_tuple ~loc ~f:mk_constr tys | Pcstr_record xs -> let tys = List.map (fun x -> gen_from_type ~loc ~env x.pld_type) xs in - record ~loc ~f:mk_constr ~gens:tys xs + gen_record ~loc ~f:mk_constr ~gens:tys xs in A.pexp_tuple [ Option.value ~default:[%expr 1] weight; gen ] -and gen_from_variant ~loc typ_name rws = +and gen_from_variant ~loc ~env rws = let (module A) = Ast_builder.make loc in - let is_rec (row : row_field) : bool = - match row.prf_desc with - | Rinherit _ -> false - | Rtag (_, _, typs) -> List.exists (is_rec_typ typ_name) typs - in - let to_gen ?env (row : row_field) : expression = + let is_rec = is_rec_row_field env in + let to_gen (row : row_field) : expression = let w = Attributes.weight row.prf_attributes |> Option.value ~default:[%expr 1] in let gen = match row.prf_desc with - | Rinherit typ -> gen_from_type ~loc typ - | Rtag (label, _, []) -> pure ~loc @@ A.pexp_variant label.txt None + | Rinherit typ -> gen_from_type ~loc ~env typ + | Rtag (label, _, []) -> G.pure ~loc @@ A.pexp_variant label.txt None | Rtag (label, _, typs) -> let f expr = A.pexp_variant label.txt (Some expr) in - tuple ~loc ~f (List.map (gen_from_type ~loc ?env) typs) + gen_tuple ~loc ~f (List.map (gen_from_type ~loc ~env) typs) in [%expr [%e w], [%e gen]] in - (* the environment is emptied, a variant can not be based on other mutuals types - containing variants - - For instance, the following type is accepted: - {[ - type x = [`X] - type xy = [`Y | foo] - ]} - However, this next is not: - {[ - type xy = [`X | y] - and y = [`Y] - ]} - *) - let gen = sized ~loc ~env:TypeGen.empty typ_name is_rec to_gen rws in - let typ_t = A.ptyp_constr (A.Located.mk @@ Lident typ_name) [] in - (* TODO: mutualize this ident for https://github.com/c-cube/qcheck/issues/190 *) - let typ_gen = A.Located.mk @@ Lident "QCheck.Gen.t" in + let gen = gen_sized ~loc is_rec to_gen rws in + let typ_t = A.ptyp_constr (A.Located.mk @@ Lident env.curr_type) [] in + let typ_gen = A.Located.mk @@ Lident G.ty in let typ = A.ptyp_constr typ_gen [ typ_t ] in [%expr ([%e gen] : [%t typ])] and gen_from_arrow ~loc ~env left right = let rec observable = function - | [%type: unit] -> [%expr QCheck.Observable.unit] - | [%type: bool] -> [%expr QCheck.Observable.bool] - | [%type: int] -> [%expr QCheck.Observable.int] - | [%type: float] -> [%expr QCheck.Observable.float] - | [%type: string] -> [%expr QCheck.Observable.string] - | [%type: char] -> [%expr QCheck.Observable.char] - | [%type: [%t? typ] option] -> - [%expr QCheck.Observable.option [%e observable typ]] - | [%type: [%t? typ] array] -> - [%expr QCheck.Observable.array [%e observable typ]] - | [%type: [%t? typ] list] -> - [%expr QCheck.Observable.list [%e observable typ]] + | [%type: unit] -> O.unit loc + | [%type: bool] -> O.bool loc + | [%type: int] -> O.int loc + | [%type: float] -> O.float loc + | [%type: string] -> O.string loc + | [%type: char] -> O.char loc + | [%type: [%t? typ] option] -> O.option ~loc (observable typ) + | [%type: [%t? typ] array] -> O.array ~loc (observable typ) + | [%type: [%t? typ] list] -> O.list ~loc (observable typ) | { ptyp_desc = Ptyp_tuple xs; _ } -> let obs = List.map observable xs in Tuple.from_list obs |> Tuple.to_obs ~loc @@ -381,66 +348,123 @@ and gen_from_arrow ~loc ~env left right = | x -> (gen_from_type ~loc ~env x, [%expr o_nil]) in let x, obs = aux right in + (* TODO: export this in qcheck_generators for https://github.com/c-cube/qcheck/issues/190 *) let arb = [%expr QCheck.make [%e x]] in [%expr QCheck.fun_nary QCheck.Tuple.([%e observable left] @-> [%e obs]) [%e arb] |> QCheck.gen] -let gen_from_kind_variant ~loc ~env typ_name xs = - let (module A) = Ast_builder.make loc in - let is_rec (constr : constructor_declaration) : bool = - match constr.pcd_args with - | Pcstr_tuple xs -> List.exists (is_rec_typ typ_name) xs - | _ -> false - in - sized ~loc ~env typ_name is_rec (gen_from_constr ~loc) xs +(** [gen_from_type_declaration loc td] creates a generator from the type declaration. + + It returns either `Recursive or `Normal. -let rec curry_args ~loc args body = - match args with - | [] -> body - | x :: xs -> [%expr fun [%p x] -> [%e curry_args ~loc xs body]] + - `Normal of expression: + The derived generator is not recursive, we return only the generator. -let gen_from_type_declaration ~loc ?(env = TypeGen.empty) td = - let name = td.ptype_name.txt in - let pat_gen = pat ~loc name in + - `Recursive of expression * expression + The derived generator was recursive (i.e. val gen : n -> t Gen.t), we return + the sized generator version, and a normal generator using this last with + [Gen.sized]. +*) +let gen_from_type_declaration ~loc ~env td = + let (module A) = Ast_builder.make loc in + let ty = env.Env.curr_type in + let is_rec = Env.is_rec env ty in let args = List.map (fun (typ, _) -> - match typ.ptyp_desc with Ptyp_var s -> pat ~loc s | _ -> assert false) + match typ.ptyp_desc with + | Ptyp_var s -> (pat ~loc s, name s |> A.evar) + | _ -> assert false) td.ptype_params in + let (args_pat, args_expr) = List.split args in let gen = match td.ptype_kind with - | Ptype_variant xs -> gen_from_kind_variant ~loc ~env name xs + | Ptype_variant xs -> + let is_rec cd = is_rec_constr_decl env cd in + gen_sized ~loc is_rec (gen_from_constr ~loc ~env) xs | Ptype_record xs -> let gens = List.map (fun x -> gen_from_type ~loc ~env x.pld_type) xs in - record ~loc ~gens xs + gen_record ~loc ~gens xs | _ -> let typ = Option.get td.ptype_manifest in - gen_from_type ~loc ~env ~typ_name:name typ + gen_from_type ~loc ~env typ in - let gen = curry_args ~loc args gen in - [%stri let [%p pat_gen] = [%e gen]] + let pat_gen = pat ~loc ty in + if not is_rec then + let gen = Args.curry_args ~loc args_pat gen in + `Normal [%stri let [%p pat_gen] = [%e gen]] + else + let gen = Args.curry_args ~loc (args_pat @ [A.pvar "n"]) gen in + let pat_gen_sized = pat ~loc ~sized:true ty in + let gen_sized = name ~sized:true ty |> A.evar in + let gen_normal = + Args.curry_args ~loc args_pat + (G.sized ~loc (Args.apply_args ~loc args_expr gen_sized)) + in + `Recursive ( + [%stri let rec [%p pat_gen_sized] = [%e gen]], + [%stri let [%p pat_gen] = [%e gen_normal]] + ) -let derive_gen ~loc xs : structure = +let mutually_recursive_gens ~loc gens = + let (module A) = Ast_builder.make loc in + let to_mutualize_gens = + List.map (function + | `Recursive (x, _) -> x + | `Normal x -> x) gens + in + let normal_gens = + List.filter_map (function + | `Recursive (_, x) -> Some x + | `Normal _ -> None) gens + in + let gens = + List.map (function + | [%stri let [%p? pat] = [%e? expr]] + | [%stri let rec [%p? pat] = [%e? expr]] -> + A.value_binding ~pat ~expr + | _ -> assert false) to_mutualize_gens + in + let mutual_gens = A.pstr_value Recursive gens in + mutual_gens :: normal_gens + +(** [derive_gen ~loc xs] creates generators for type declaration in [xs]. *) +let derive_gen ~loc (xs : rec_flag * type_declaration list) : structure = + let open Env in + let add_if_rec env typ x = + if is_rec_type_decl env typ then + { env with rec_types = x :: env.rec_types} + else env + in match xs with - | _, [ x ] -> [ gen_from_type_declaration ~loc x ] + | (_, [ x ]) -> + let typ_name = x.ptype_name.txt in + let env = { curr_type = typ_name; rec_types = []; curr_types = [typ_name] } in + let env = add_if_rec env x typ_name in + (match gen_from_type_declaration ~loc ~env x with + | `Recursive (gen_sized, gen) -> [gen_sized; gen] + | `Normal gen -> [gen]) | _, xs -> - let (module A) = Ast_builder.make loc in - let env = - List.fold_left - (fun env td -> - let x = td.ptype_name.txt in - let gen = name x |> A.evar in - let expr = [%expr [%e gen] ()] in - TypeGen.add x expr env) - TypeGen.empty xs - in - let gens = List.map (gen_from_type_declaration ~loc ~env) xs in - mutually_recursive_gens ~loc gens + let typ_names = List.map (fun x -> x.ptype_name.txt) xs in + let env = { curr_type = ""; rec_types = []; curr_types = typ_names } in + let env = + List.fold_left + (fun env x -> add_if_rec env x x.ptype_name.txt) + env xs + in + let gens = + List.map (fun x -> + let env = { env with curr_type = x.ptype_name.txt }in + gen_from_type_declaration ~loc ~env x) xs + in + mutually_recursive_gens ~loc gens + +(** {2. Ppxlib machinery} *) let create_gen ~ctxt (decls : rec_flag * type_declaration list) : structure = let loc = Expansion_context.Deriver.derived_item_loc ctxt in diff --git a/src/ppx_deriving_qcheck/tuple.ml b/src/ppx_deriving_qcheck/tuple.ml new file mode 100644 index 00000000..89194154 --- /dev/null +++ b/src/ppx_deriving_qcheck/tuple.ml @@ -0,0 +1,135 @@ +open Ppxlib +module G = QCheck_generators +module O = G.Observable + +(** {1. Tuple } *) + +(** This module implements nested tuples based on QCheck tuples generators (or observables): + - [Gen.pair] + - [Gen.triple] + - [Gen.quad] + + It can be used to nest large tuples in a generator. + - e.g. + {[ + type t = int * int * int + ]} + + Lets say QCheck does not have combinator to generate a triple. One has to write: + + {[ + let gen = QCheck.Gen.(map (fun ((x, y), z) -> (x, y, z) (pair (pair int int) int)) + ]} + + We copy this nesting mechanism with this module. + *) + +type 'a t = + | Pair of 'a t * 'a t + | Triple of 'a * 'a * 'a + | Quad of 'a * 'a * 'a * 'a + | Elem of 'a + +(** [from_list l] builds an {!'a t}, if len of [l] is greater than 4, the list + is split into a [Pair] of generators. *) +let rec from_list = function + | [ a; b; c; d ] -> Quad (a, b, c, d) + | [ a; b; c ] -> Triple (a, b, c) + | [ a; b ] -> Pair (Elem a, Elem b) + | [ a ] -> Elem a + | l -> + let n = List.length l / 2 in + let i = ref 0 in + let l1 = + List.filter + (fun _ -> + let x = !i in + i := x + 1; + x < n) + l + in + i := 0; + let l2 = + List.filter + (fun _ -> + let x = !i in + i := x + 1; + x >= n) + l + in + Pair (from_list l1, from_list l2) + +let rec to_list = function + | Quad (a, b, c, d) -> [ a; b; c; d ] + | Triple (a, b, c) -> [ a; b; c ] + | Pair (a, b) -> to_list a @ to_list b + | Elem a -> [ a ] + +(** [to_expr ~loc t] creates a tuple expression based on [t]. + [t] is transformed to a list, and each element from the list becomes + a variable referencing a generator. + + - e.g. + to_expr (Pair (_, _)) => (gen0, gen1) + *) +let to_expr ~loc t = + let l = to_list t in + let (module A) = Ast_builder.make loc in + List.mapi + (fun i _ -> + let s = Printf.sprintf "gen%d" i in + A.evar s) + l + |> A.pexp_tuple + +(** [nest pair triple quad t] creates a generator expression for [t] using + + - [pair] to combine Pair (_, _) + - [triple] to combine Triple (_, _, ) + - [quad] to combine Quad (_, _, _, _) +*) +let rec nest ~pair ~triple ~quad = function + | Quad (a, b, c, d) -> quad a b c d + | Triple (a, b, c) -> triple a b c + | Pair (a, b) -> + pair + (nest ~pair ~triple ~quad a) + (nest ~pair ~triple ~quad b) + | Elem a -> a + +(** [to_gen t] creates a Gen.t with generators' combinators *) +let to_gen ~loc t = + nest ~pair:(G.pair ~loc) ~triple:(G.triple ~loc) ~quad:(G.quad ~loc) t + +(** [to_obs t] creates a Obs.t with obsersvables' combinators *) +let to_obs ~loc t = + nest ~pair:(O.pair ~loc) ~triple:(O.triple ~loc) ~quad:(O.quad ~loc) t + +let to_pat ~loc t = + let fresh_id = + let id = ref 0 in + fun () -> + let x = !id in + let () = id := x + 1 in + Printf.sprintf "gen%d" x + in + let (module A) = Ast_builder.make loc in + let rec aux = function + | Quad (_, _, _, _) -> + let a = A.pvar @@ fresh_id () in + let b = A.pvar @@ fresh_id () in + let c = A.pvar @@ fresh_id () in + let d = A.pvar @@ fresh_id () in + [%pat? [%p a], [%p b], [%p c], [%p d]] + | Triple (_, _, _) -> + let a = A.pvar @@ fresh_id () in + let b = A.pvar @@ fresh_id () in + let c = A.pvar @@ fresh_id () in + [%pat? [%p a], [%p b], [%p c]] + | Pair (a, b) -> + let a = aux a in + let b = aux b in + [%pat? [%p a], [%p b]] + | Elem _ -> A.pvar @@ fresh_id () + in + aux t diff --git a/test/ppx_deriving_qcheck/deriver/test.ml b/test/ppx_deriving_qcheck/deriver/test.ml index 7d7d10b9..515d47c5 100644 --- a/test/ppx_deriving_qcheck/deriver/test.ml +++ b/test/ppx_deriving_qcheck/deriver/test.ml @@ -167,40 +167,20 @@ let test_alpha () = [ [%stri let gen gen_a = gen_a]; [%stri let gen gen_a = QCheck.Gen.list gen_a]; - [%stri - let gen gen_a = - QCheck.Gen.frequency - [ (1, QCheck.Gen.map (fun gen0 -> A gen0) gen_a) ]]; + [%stri let gen gen_a = QCheck.Gen.map (fun gen0 -> A gen0) gen_a]; [%stri let gen gen_a gen_b = - QCheck.Gen.frequency - [ - ( 1, - QCheck.Gen.map - (fun (gen0, gen1) -> A (gen0, gen1)) - (QCheck.Gen.pair gen_a gen_b) ); - ]]; + QCheck.Gen.map + (fun (gen0, gen1) -> A (gen0, gen1)) + (QCheck.Gen.pair gen_a gen_b)]; [%stri let gen gen_left gen_right = QCheck.Gen.map (fun (gen0, gen1) -> (gen0, gen1)) (QCheck.Gen.pair gen_left gen_right)]; [%stri - let gen_tree gen_a = - QCheck.Gen.sized - @@ QCheck.Gen.fix (fun self -> function - | 0 -> - QCheck.Gen.frequency - [ (1, QCheck.Gen.map (fun gen0 -> Leaf gen0) gen_a) ] - | n -> - QCheck.Gen.frequency - [ - (1, QCheck.Gen.map (fun gen0 -> Leaf gen0) gen_a); - ( 1, - QCheck.Gen.map - (fun (gen0, gen1) -> Node (gen0, gen1)) - (QCheck.Gen.pair (self (n / 2)) (self (n / 2))) ); - ])]; + let gen_int_tree = gen_tree QCheck.Gen.int + ] ] in let actual = @@ -212,7 +192,7 @@ let test_alpha () = [%stri type 'a t = A of 'a]; [%stri type ('a, 'b) t = A of 'a * 'b]; [%stri type ('left, 'right) t = 'left * 'right]; - [%stri type 'a tree = Leaf of 'a | Node of 'a tree * 'a tree]; + [%stri type int_tree = int tree] ] in check_eq ~expected ~actual "deriving alpha" @@ -278,10 +258,7 @@ let test_dependencies () = let test_konstr () = let expected = [ - [%stri - let gen = - QCheck.Gen.frequency - [ (1, QCheck.Gen.map (fun gen0 -> A gen0) QCheck.Gen.int) ]]; + [%stri let gen = QCheck.Gen.map (fun gen0 -> A gen0) QCheck.Gen.int]; [%stri let gen = QCheck.Gen.frequency @@ -385,30 +362,6 @@ let test_variant () = (1, QCheck.Gen.map (fun gen0 -> `C gen0) QCheck.Gen.string); ] : t QCheck.Gen.t)]; - [%stri - let gen = - (QCheck.Gen.sized - @@ QCheck.Gen.fix (fun self -> function - | 0 -> - QCheck.Gen.frequency - [ - (1, QCheck.Gen.pure `A); - (1, QCheck.Gen.map (fun gen0 -> `B gen0) QCheck.Gen.int); - ( 1, - QCheck.Gen.map (fun gen0 -> `C gen0) QCheck.Gen.string - ); - ] - | n -> - QCheck.Gen.frequency - [ - (1, QCheck.Gen.pure `A); - (1, QCheck.Gen.map (fun gen0 -> `B gen0) QCheck.Gen.int); - ( 1, - QCheck.Gen.map (fun gen0 -> `C gen0) QCheck.Gen.string - ); - (1, QCheck.Gen.map (fun gen0 -> `D gen0) (self (n / 2))); - ]) - : t QCheck.Gen.t)]; [%stri let gen_t' = (QCheck.Gen.frequency [ (1, QCheck.Gen.pure `B); (1, gen) ] @@ -420,7 +373,6 @@ let test_variant () = @@ extract' [ [%stri type t = [ `A | `B of int | `C of string ]]; - [%stri type t = [ `A | `B of int | `C of string | `D of t ]]; [%stri type t' = [ `B | t ]]; ] in @@ -430,175 +382,118 @@ let test_tree () = let expected = [ [%stri - let gen_tree = - QCheck.Gen.sized - @@ QCheck.Gen.fix (fun self -> function - | 0 -> QCheck.Gen.frequency [ (1, QCheck.Gen.pure Leaf) ] - | n -> - QCheck.Gen.frequency - [ - (1, QCheck.Gen.pure Leaf); - ( 1, - QCheck.Gen.map - (fun (gen0, gen1, gen2) -> Node (gen0, gen1, gen2)) - (QCheck.Gen.triple - QCheck.Gen.int - (self (n / 2)) - (self (n / 2))) ); - ])]; - [%stri - let gen_expr = - QCheck.Gen.sized - @@ QCheck.Gen.fix (fun self -> function - | 0 -> - QCheck.Gen.frequency - [ - ( 1, - QCheck.Gen.map (fun gen0 -> Value gen0) QCheck.Gen.int - ); - ] - | n -> - QCheck.Gen.frequency - [ - ( 1, - QCheck.Gen.map (fun gen0 -> Value gen0) QCheck.Gen.int - ); - ( 1, - QCheck.Gen.map - (fun (gen0, gen1, gen2) -> If (gen0, gen1, gen2)) - (QCheck.Gen.triple - (self (n / 2)) - (self (n / 2)) - (self (n / 2))) ); - ( 1, - QCheck.Gen.map - (fun (gen0, gen1) -> Eq (gen0, gen1)) - (QCheck.Gen.pair (self (n / 2)) (self (n / 2))) ); - ( 1, - QCheck.Gen.map - (fun (gen0, gen1) -> Lt (gen0, gen1)) - (QCheck.Gen.pair (self (n / 2)) (self (n / 2))) ); - ])]; + let rec gen_tree_sized gen_a n = + match n with + | 0 -> QCheck.Gen.pure Leaf + | n -> + QCheck.Gen.frequency + [ + (1, QCheck.Gen.pure Leaf); + ( 1, + QCheck.Gen.map + (fun (gen0, gen1, gen2) -> Node (gen0, gen1, gen2)) + (QCheck.Gen.triple + gen_a + ((gen_tree_sized gen_a) (n / 2)) + ((gen_tree_sized gen_a) (n / 2))) ); + ] + ]; + [%stri + let gen_tree gen_a = QCheck.Gen.sized @@ (gen_tree_sized gen_a) + ]; ] in let actual = - f' - @@ extract' - [ - [%stri type tree = Leaf | Node of int * tree * tree]; - [%stri - type expr = - | Value of int - | If of expr * expr * expr - | Eq of expr * expr - | Lt of expr * expr]; - ] + f + @@ extract [%stri type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree]; in check_eq ~expected ~actual "deriving tree" -let test_recursive () = +let test_expr () = let expected = [ [%stri - let rec gen_expr () = - QCheck.Gen.sized - @@ QCheck.Gen.fix (fun self -> function - | 0 -> - QCheck.Gen.frequency - [ - ( 1, - QCheck.Gen.map (fun gen0 -> Value gen0) (gen_value ()) - ); - ] - | n -> - QCheck.Gen.frequency - [ - ( 1, - QCheck.Gen.map (fun gen0 -> Value gen0) (gen_value ()) - ); - ( 1, - QCheck.Gen.map - (fun (gen0, gen1, gen2) -> If (gen0, gen1, gen2)) - (QCheck.Gen.triple - (self (n / 2)) - (self (n / 2)) - (self (n / 2))) ); - ( 1, - QCheck.Gen.map - (fun (gen0, gen1) -> Eq (gen0, gen1)) - (QCheck.Gen.pair (self (n / 2)) (self (n / 2))) ); - ( 1, - QCheck.Gen.map - (fun (gen0, gen1) -> Lt (gen0, gen1)) - (QCheck.Gen.pair (self (n / 2)) (self (n / 2))) ); - ]) - - and gen_value () = - QCheck.Gen.frequency - [ - (1, QCheck.Gen.map (fun gen0 -> Bool gen0) QCheck.Gen.bool); - (1, QCheck.Gen.map (fun gen0 -> Int gen0) QCheck.Gen.int); - ]]; - [%stri let gen_expr = gen_expr ()]; - [%stri let gen_value = gen_value ()]; + let rec gen_expr_sized n = + match n with + | 0 -> QCheck.Gen.map (fun gen0 -> Value gen0) QCheck.Gen.int + | n -> + QCheck.Gen.frequency + [ + ( 1, + QCheck.Gen.map (fun gen0 -> Value gen0) QCheck.Gen.int + ); + ( 1, + QCheck.Gen.map + (fun (gen0, gen1, gen2) -> If (gen0, gen1, gen2)) + (QCheck.Gen.triple + (gen_expr_sized (n / 2)) + (gen_expr_sized (n / 2)) + (gen_expr_sized (n / 2))) ); + ( 1, + QCheck.Gen.map + (fun (gen0, gen1) -> Eq (gen0, gen1)) + (QCheck.Gen.pair (gen_expr_sized (n / 2)) (gen_expr_sized (n / 2))) ); + ( 1, + QCheck.Gen.map + (fun (gen0, gen1) -> Lt (gen0, gen1)) + (QCheck.Gen.pair (gen_expr_sized (n / 2)) (gen_expr_sized (n / 2))) ); + ] + ]; + [%stri + let gen_expr = QCheck.Gen.sized @@ gen_expr_sized + ] ] in - let actual = - f - @@ extract - [%stri - type expr = - | Value of value - | If of expr * expr * expr - | Eq of expr * expr - | Lt of expr * expr - - and value = Bool of bool | Int of int] - in - check_eq ~expected ~actual "deriving recursive" - + f @@ extract + [%stri + type expr = + | Value of int + | If of expr * expr * expr + | Eq of expr * expr + | Lt of expr * expr] + in + check_eq ~expected ~actual "deriving expr" + let test_forest () = let expected = [ [%stri - let rec gen_tree () = - QCheck.Gen.frequency - [ - ( 1, - QCheck.Gen.map - (fun gen0 -> Node gen0) - (QCheck.Gen.map - (fun (gen0, gen1) -> (gen0, gen1)) - (QCheck.Gen.pair QCheck.Gen.int (gen_forest ()))) ); - ] - - and gen_forest () = - QCheck.Gen.sized - @@ QCheck.Gen.fix (fun self -> function - | 0 -> QCheck.Gen.frequency [ (1, QCheck.Gen.pure Nil) ] - | n -> - QCheck.Gen.frequency - [ - (1, QCheck.Gen.pure Nil); - ( 1, - QCheck.Gen.map - (fun gen0 -> Cons gen0) - (QCheck.Gen.map - (fun (gen0, gen1) -> (gen0, gen1)) - (QCheck.Gen.pair (gen_tree ()) (self (n / 2)))) ); - ])]; - [%stri let gen_tree = gen_tree ()]; - [%stri let gen_forest = gen_forest ()]; + let rec gen_tree_sized gen_a n = + QCheck.Gen.map + (fun gen0 -> Node gen0) + (QCheck.Gen.map + (fun (gen0, gen1) -> (gen0, gen1)) + (QCheck.Gen.pair gen_a ((gen_forest_sized gen_a) (n / 2)))) + + and gen_forest_sized gen_a n = + match n with + | 0 -> QCheck.Gen.pure Nil + | n -> + QCheck.Gen.frequency + [ + (1, QCheck.Gen.pure Nil); + ( 1, + QCheck.Gen.map + (fun gen0 -> Cons gen0) + (QCheck.Gen.map + (fun (gen0, gen1) -> (gen0, gen1)) + (QCheck.Gen.pair + ((gen_tree_sized gen_a) (n / 2)) + ((gen_forest_sized gen_a) (n / 2)))) ); + ] + ]; + [%stri let gen_tree gen_a = QCheck.Gen.sized @@ (gen_tree_sized gen_a)]; + [%stri let gen_forest gen_a = QCheck.Gen.sized @@ (gen_forest_sized gen_a)]; ] in let actual = f @@ extract [%stri - type tree = Node of (int * forest) + type 'a tree = Node of ('a * 'a forest) - and forest = Nil | Cons of (tree * forest)] + and 'a forest = Nil | Cons of ('a tree * 'a forest)] in check_eq ~expected ~actual "deriving forest" @@ -797,35 +692,33 @@ let test_recursive_poly_variant () = let expected = [ [%stri - let gen_tree = - (QCheck.Gen.sized - @@ QCheck.Gen.fix (fun self -> function - | 0 -> - QCheck.Gen.frequency - [ - ( 1, - QCheck.Gen.map (fun gen0 -> `Leaf gen0) QCheck.Gen.int - ); - ] - | n -> - QCheck.Gen.frequency - [ - ( 1, - QCheck.Gen.map (fun gen0 -> `Leaf gen0) QCheck.Gen.int - ); - ( 1, - QCheck.Gen.map - (fun gen0 -> `Node gen0) - (QCheck.Gen.map - (fun (gen0, gen1) -> (gen0, gen1)) - (QCheck.Gen.pair (self (n / 2)) (self (n / 2)))) - ); - ]) + let rec gen_tree_sized gen_a n = + (match n with + | 0 -> QCheck.Gen.map (fun gen0 -> `Leaf gen0) gen_a + | n -> + QCheck.Gen.frequency + [ + ( 1, + QCheck.Gen.map (fun gen0 -> `Leaf gen0) gen_a + ); + ( 1, + QCheck.Gen.map + (fun gen0 -> `Node gen0) + (QCheck.Gen.map + (fun (gen0, gen1) -> (gen0, gen1)) + (QCheck.Gen.pair + ((gen_tree_sized gen_a) (n / 2)) + ((gen_tree_sized gen_a) (n / 2)))) + ); + ] : tree QCheck.Gen.t)]; + [%stri + let gen_tree gen_a = QCheck.Gen.sized @@ (gen_tree_sized gen_a) + ] ] in let actual = - f @@ extract [%stri type tree = [ `Leaf of int | `Node of tree * tree ]] + f @@ extract [%stri type 'a tree = [ `Leaf of 'a | `Node of 'a tree * 'a tree ]] in check_eq ~expected ~actual "deriving recursive polymorphic variants" @@ -856,10 +749,10 @@ let () = test_case "deriving record" `Quick test_record; test_case "deriving equal" `Quick test_equal; test_case "deriving tree like" `Quick test_tree; + test_case "deriving expr like" `Quick test_expr; test_case "deriving alpha" `Quick test_alpha; test_case "deriving variant" `Quick test_variant; test_case "deriving weight constructors" `Quick test_weight_konstrs; - test_case "deriving recursive" `Quick test_recursive; test_case "deriving forest" `Quick test_forest; test_case "deriving fun primitives" `Quick test_fun_primitives; test_case "deriving fun option" `Quick test_fun_option;