Skip to content

Commit 115de55

Browse files
authored
Merge pull request #171 from vch9/master
add ppx_deriving_qcheck
2 parents b065a81 + e27eeb3 commit 115de55

File tree

11 files changed

+1699
-0
lines changed

11 files changed

+1699
-0
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ Simon Cruanes <[email protected]>
22
Rudi Grinberg <[email protected]>
33
Jacques-Pascal Deplaix <[email protected]>
44
Jan Midtgaard <[email protected]>
5+
Valentin Chaboche <[email protected]>

README.adoc

+12
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,18 @@ describe("qcheck-rely", ({test}) => {
402402
403403
----
404404

405+
=== Deriver
406+
407+
A ppx_deriver is provided to derive QCheck generators from a type declaration.
408+
409+
```ocaml
410+
type tree = Leaf of int | Node of tree * tree
411+
[@@deriving qcheck]
412+
```
413+
414+
See the according https://github.com/c-cube/qcheck/tree/master/src/ppx_deriving_qcheck/[README]
415+
for more information and examples.
416+
405417
=== Compatibility notes
406418

407419
Starting with 0.9, the library is split into several components:

ppx_deriving_qcheck.opam

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
opam-version: "2.0"
2+
name: "ppx_deriving_qcheck"
3+
version: "0.2.0"
4+
license: "BSD-2-Clause"
5+
synopsis: "PPX Deriver for QCheck"
6+
7+
maintainer: "[email protected]"
8+
author: [ "the qcheck contributors" ]
9+
10+
depends: [
11+
"dune" {>= "2.8.0"}
12+
"ocaml" {>= "4.08.0"}
13+
"qcheck" {>= "0.17"}
14+
"ppxlib" {>= "0.22.0"}
15+
"odoc" {with-doc}
16+
"alcotest" {with-test & >= "1.4.0" }
17+
]
18+
19+
build: [
20+
["dune" "build" "-p" name "-j" jobs]
21+
["dune" "build" "@doc" "-p" name "-j" jobs] {with-doc}
22+
["dune" "runtest" "-p" name "-j" jobs] {with-test}
23+
]
24+
25+
homepage: "https://github.com/c-cube/qcheck/"
26+
bug-reports: "https://github.com/c-cube/qcheck/-/issues"
27+
dev-repo: "git+https://github.com/vch9/ppx_deriving_qcheck.git"

src/ppx_deriving_qcheck/README.md

+307
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
# ppx_deriving_qcheck
2+
3+
## Generator
4+
Derive `QCheck.Gen.t` from a type declaration
5+
6+
```ocaml
7+
type tree = Leaf of int | Node of tree * tree
8+
[@@deriving qcheck]
9+
10+
let rec rev tree = match tree with
11+
| Leaf _ -> tree
12+
| Node (left, right) -> Node (rev left, rev right)
13+
14+
let test =
15+
QCheck.Test.make
16+
~name:"tree -> rev (rev tree) = tree"
17+
(QCheck.make gen_tree)
18+
(fun tree -> rev (rev tree) = tree)
19+
```
20+
21+
### Overwrite generator
22+
If you wan't to specify your own `generator` for any type you can
23+
add an attribute to the type:
24+
25+
```ocaml
26+
type t = (int : [@gen QCheck.Gen.(0 -- 10)])
27+
[@@deriving qcheck]
28+
29+
(* produces ==> *)
30+
31+
let gen : t QCheck.Gen.t = QCheck.Gen.(0 -- 10)
32+
```
33+
34+
This attribute has 2 advantages:
35+
* Use your own generator for a specific type (see above)
36+
* There is no generator available for the type
37+
```ocaml
38+
type my_foo =
39+
| Foo of my_other_type
40+
| Bar of bool
41+
[@@deriving qcheck]
42+
^^^^^^^^^^^^^^^^
43+
Error: Unbound value gen_my_other_type
44+
45+
(* Possible fix *)
46+
let gen_my_other_type = (* add your implementation here *)
47+
48+
type my_foo =
49+
| Foo of my_other_type [@gen gen_my_other_type]
50+
| Bar of bool
51+
[@@deriving qcheck]
52+
```
53+
54+
## How to use
55+
56+
Add to your OCaml libraries with dune
57+
```ocaml
58+
...
59+
(preprocess (pps ppx_deriving_qcheck)))
60+
...
61+
```
62+
63+
## Supported types
64+
65+
### Primitive types
66+
67+
* Unit
68+
```ocaml
69+
type t = unit [@@deriving qcheck]
70+
71+
(* ==> *)
72+
73+
let gen = QCheck.Gen.unit
74+
```
75+
76+
* Bool
77+
```ocaml
78+
type t = bool [@@deriving qcheck]
79+
80+
(* ==> *)
81+
82+
let gen = QCheck.Gen.bool
83+
```
84+
85+
* Integer
86+
```ocaml
87+
type t = int [@@deriving qcheck]
88+
89+
(* ==> *)
90+
91+
let gen = QCheck.Gen.int
92+
```
93+
94+
* Float
95+
```ocaml
96+
type t = float [@@deriving qcheck]
97+
98+
(* ==> *)
99+
100+
let gen = QCheck.Gen.float
101+
```
102+
103+
* String
104+
```ocaml
105+
type t = string [@@deriving qcheck]
106+
107+
(* ==> *)
108+
109+
let gen = QCheck.Gen.string
110+
```
111+
112+
* Char
113+
```ocaml
114+
type t = char [@@deriving qcheck]
115+
116+
(* ==> *)
117+
118+
let gen = QCheck.Gen.char
119+
```
120+
121+
* Option
122+
```ocaml
123+
type 'a t = 'a option [@@deriving qcheck]
124+
125+
(* ==> *)
126+
127+
let gen gen_a = QCheck.Gen.option gen_a
128+
```
129+
130+
* List
131+
```ocaml
132+
type 'a t = 'a list [@@deriving qcheck]
133+
134+
(* ==> *)
135+
136+
let gen gen_a = QCheck.Gen.list gen_a
137+
```
138+
139+
* Array
140+
```ocaml
141+
type 'a t = 'a array [@@deriving qcheck]
142+
143+
(* ==> *)
144+
145+
let gen gen_a = QCheck.Gen.array gen_a
146+
```
147+
148+
### Tuples of size `n`
149+
150+
* n = 2
151+
```ocaml
152+
type t = int * int [@@deriving qcheck]
153+
154+
(* ==> *)
155+
156+
let gen = QCheck.Gen.pair QCheck.Gen.int QCheck.Gen.int
157+
```
158+
159+
* n = 3
160+
```ocaml
161+
type t = int * int * int [@@deriving qcheck]
162+
163+
(* ==> *)
164+
165+
let gen = QCheck.Gen.triple QCheck.Gen.int QCheck.Gen.int QCheck.Gen.int
166+
```
167+
168+
* n = 4
169+
```ocaml
170+
type t = int * int * int * int [@@deriving qcheck]
171+
172+
(* ==> *)
173+
174+
let gen = QCheck.Gen.quad QCheck.Gen.int QCheck.Gen.int QCheck.Gen.int QCheck.Gen.int
175+
```
176+
177+
* n > 4, tuples are split between pairs, for instance n = 8
178+
```ocaml
179+
type t = int * int * int * int * int * int * int * int [@@deriving qcheck]
180+
181+
(* ==> *)
182+
183+
let gen =
184+
QCheck.Gen.pair
185+
(QCheck.Gen.quad QCheck.Gen.int QCheck.Gen.int QCheck.Gen.int QCheck.Gen.int)
186+
(QCheck.Gen.quad QCheck.Gen.int QCheck.Gen.int QCheck.Gen.int QCheck.Gen.int)
187+
```
188+
189+
## Records
190+
```ocaml
191+
type service = {
192+
service_name : string;
193+
port : int;
194+
protocol : string;
195+
} [@@deriving qcheck]
196+
197+
(* ==> *)
198+
199+
let gen_service =
200+
QCheck.Gen.map
201+
(fun (gen0, gen1, gen2) ->
202+
{ service_name = gen0; port = gen1; protocol = gen2 })
203+
(QCheck.Gen.triple QCheck.Gen.string QCheck.Gen.int QCheck.Gen.string)
204+
```
205+
206+
## Variants
207+
* Variants
208+
```ocaml
209+
type color = Red | Blue | Green
210+
[@@deriving qcheck]
211+
212+
(* ==> *)
213+
214+
let gen_color =
215+
QCheck.Gen.frequency
216+
[(1, (QCheck.Gen.pure Red));
217+
(1, (QCheck.Gen.pure Blue));
218+
(1, (QCheck.Gen.pure Green))]
219+
```
220+
221+
* Polymorphic variants
222+
```ocaml
223+
type color = [ `Red | `Blue | `Green ]
224+
[@@deriving qcheck]
225+
226+
(* ==> *)
227+
228+
let gen_color =
229+
(QCheck.Gen.frequency
230+
[(1, (QCheck.Gen.pure `Red));
231+
(1, (QCheck.Gen.pure `Blue));
232+
(1, (QCheck.Gen.pure `Green))] : color QCheck.Gen.t)
233+
```
234+
235+
## Recursive variants
236+
* Recursive variants
237+
```ocaml
238+
type tree = Leaf of int | Node of tree * tree
239+
[@@deriving qcheck]
240+
241+
let gen_tree =
242+
QCheck.Gen.sized @@
243+
(QCheck.Gen.fix
244+
(fun self -> function
245+
| 0 ->
246+
QCheck.Gen.frequency
247+
[(1, (QCheck.Gen.map (fun gen0 -> Leaf gen0) QCheck.Gen.int))]
248+
| n ->
249+
QCheck.Gen.frequency
250+
[(1,
251+
(QCheck.Gen.map (fun gen0 -> Leaf gen0) QCheck.Gen.int));
252+
(1,
253+
(QCheck.Gen.map (fun (gen0, gen1) -> Node (gen0, gen1))
254+
(QCheck.Gen.pair (self (n / 2)) (self (n / 2)))))]))
255+
```
256+
257+
* Recursive polymorphic variants
258+
```ocaml
259+
type tree = [ `Leaf of int | `Node of tree * tree ]
260+
[@@deriving qcheck]
261+
262+
(* ==> *)
263+
264+
/!\ FIXME: https://github.com/vch9/ppx_deriving_qcheck/issues/7 /!\
265+
```
266+
267+
## Mutual recursive types
268+
```ocaml
269+
type tree = Node of (int * forest)
270+
and forest = Nil | Cons of (tree * forest)
271+
[@@deriving qcheck]
272+
273+
(* ==> *)
274+
275+
let rec gen_tree () =
276+
QCheck.Gen.frequency
277+
[(1,
278+
(QCheck.Gen.map (fun gen0 -> Node gen0)
279+
(QCheck.Gen.map (fun (gen0, gen1) -> (gen0, gen1))
280+
(QCheck.Gen.pair QCheck.Gen.int (gen_forest ())))))]
281+
282+
and gen_forest () =
283+
QCheck.Gen.sized @@
284+
(QCheck.Gen.fix
285+
(fun self -> function
286+
| 0 -> QCheck.Gen.frequency [(1, (QCheck.Gen.pure Nil))]
287+
| n ->
288+
QCheck.Gen.frequency
289+
[(1, (QCheck.Gen.pure Nil));
290+
(1,
291+
(QCheck.Gen.map (fun gen0 -> Cons gen0)
292+
(QCheck.Gen.map (fun (gen0, gen1) -> (gen0, gen1))
293+
(QCheck.Gen.pair (gen_tree ()) (self (n / 2))))))]))
294+
295+
let gen_tree = gen_tree ()
296+
297+
let gen_forest = gen_forest ()
298+
```
299+
300+
## Unsupported types
301+
302+
### GADT
303+
Deriving a GADT currently produces an ill-typed generator.
304+
305+
### Let us know
306+
If you encounter a unsupported type (that should be), please let us know by creating
307+
an issue.

src/ppx_deriving_qcheck/attributes.ml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
open Ppxlib
2+
3+
(** [find_first_attribute xs name] returns the first attribute found in [xs]
4+
named [name] *)
5+
let find_attribute_opt xs name =
6+
List.find_opt (fun attribute -> attribute.attr_name.txt = name) xs
7+
8+
let get_expr_payload x =
9+
match x.attr_payload with
10+
| PStr [ { pstr_desc = Pstr_eval (e, _); _ } ] -> Some [%expr [%e e]]
11+
| _ -> None
12+
13+
let gen ct =
14+
Option.fold ~none:None ~some:get_expr_payload
15+
@@ find_attribute_opt ct.ptyp_attributes "gen"
16+
17+
let weight xs =
18+
Option.fold ~none:None ~some:get_expr_payload
19+
@@ find_attribute_opt xs "weight"

0 commit comments

Comments
 (0)