forked from thanhnguyen-aws/plausible
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathMutuallyRecursiveTypeTest.lean
More file actions
173 lines (148 loc) · 6.2 KB
/
Copy pathMutuallyRecursiveTypeTest.lean
File metadata and controls
173 lines (148 loc) · 6.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import Plausible.Arbitrary
import Plausible.DeriveArbitrary
import Plausible.Attr
import Plausible.Testable
open Plausible
/-
# Testing the deriving `Arbitrary` handler on mutually recursive inductive types
To test that our derived generators can handle mutually recursive types,
we define two mutually recursive types (one `inductive` and one `structure`)
to represent a binary tree.
(Example adapted from Cornell CS 3110 lecture notes
https://www.cs.cornell.edu/courses/cs3110/2008fa/lectures/lec04.html)
```lean
mutual
inductive NatTree
| Empty : NatTree
| Node : Node → NatTree
deriving Nonempty
structure Node where
value : Nat
left : NatTree
right : NatTree
deriving Nonempty
end
```
Note that the user needs to add the `deriving Nonempty` annotation
to each type in the mutually recursive definition -- this is needed
in order to convince Lean that the type `Nat → Plausible.Gen NatTree`
is empty during the derivation process.
-/
mutual
/-- A (possibly empty) binary tree -/
inductive NatTree
| Empty : NatTree
| Node : Node → NatTree
deriving Nonempty, Repr
/-- A child node in a tree, containing a value and two subtrees -/
structure Node where
value : Nat
left : NatTree
right : NatTree
deriving Nonempty
end
set_option trace.plausible.deriving.arbitrary true in
/--
trace: [plausible.deriving.arbitrary] ⏎
[mutual
partial def arbitraryNatTree✝ : Nat → Plausible.Gen (@NatTree✝) :=
let localinst✝ : Plausible.ArbitraryFueled✝ (@NatTree✝) := ⟨arbitraryNatTree✝⟩;
let localinst✝¹ : Plausible.ArbitraryFueled✝ (@Node✝) := ⟨arbitraryNode✝⟩;
let rec aux_arb (fuel✝ : Nat) : Plausible.Gen (@NatTree✝) :=
match fuel✝ with
| Nat.zero =>
Plausible.Gen.oneOfWithDefault (pure NatTree.Empty)
[(pure NatTree.Empty),
(do
let a✝ ← Plausible.Arbitrary.arbitrary
return NatTree.Node a✝)]
| fuel'✝ + 1 =>
Plausible.Gen.frequency (pure NatTree.Empty)
[(1, (pure NatTree.Empty)),
(1,
(do
let a✝ ← Plausible.Arbitrary.arbitrary
return NatTree.Node a✝)),
]
fun fuel✝ => aux_arb fuel✝
partial def arbitraryNode✝ : Nat → Plausible.Gen (@Node✝) :=
let localinst✝² : Plausible.ArbitraryFueled✝ (@NatTree✝) := ⟨arbitraryNatTree✝⟩;
let localinst✝³ : Plausible.ArbitraryFueled✝ (@Node✝) := ⟨arbitraryNode✝⟩;
let rec aux_arb (fuel✝¹ : Nat) : Plausible.Gen (@Node✝) :=
match fuel✝¹ with
| Nat.zero =>
Plausible.Gen.oneOfWithDefault
(do
let a✝¹ ← Plausible.Arbitrary.arbitrary
let a✝² ← Plausible.Arbitrary.arbitrary
let a✝³ ← Plausible.Arbitrary.arbitrary
return Node.mk a✝¹ a✝² a✝³)
[(do
let a✝¹ ← Plausible.Arbitrary.arbitrary
let a✝² ← Plausible.Arbitrary.arbitrary
let a✝³ ← Plausible.Arbitrary.arbitrary
return Node.mk a✝¹ a✝² a✝³)]
| fuel'✝¹ + 1 =>
Plausible.Gen.frequency
(do
let a✝¹ ← Plausible.Arbitrary.arbitrary
let a✝² ← Plausible.Arbitrary.arbitrary
let a✝³ ← Plausible.Arbitrary.arbitrary
return Node.mk a✝¹ a✝² a✝³)
[(1,
(do
let a✝¹ ← Plausible.Arbitrary.arbitrary
let a✝² ← Plausible.Arbitrary.arbitrary
let a✝³ ← Plausible.Arbitrary.arbitrary
return Node.mk a✝¹ a✝² a✝³)),
]
fun fuel✝¹ => aux_arb fuel✝¹
end,
instance : Plausible.ArbitraryFueled✝ (@NatTree✝) :=
⟨arbitraryNatTree✝⟩]
-/
#guard_msgs in
deriving instance Arbitrary for NatTree
-- Test that we can successfully synthesize instances of `Arbitrary` & `ArbitraryFueled`
/-- info: instArbitraryFueledNatTree -/
#guard_msgs in
#synth ArbitraryFueled NatTree
/-- info: instArbitraryOfArbitraryFueled -/
#guard_msgs in
#synth Arbitrary NatTree
/-- `search tree x` recursively searches for a value `x` in `tree`,
returning a `Bool` indicating `x`'s membership in `tree`
(Note that `tree` may not obey the binary search tree
invariant, so this algorithm is not the most efficient.) -/
def search (tree : NatTree) (x : Nat) : Bool :=
match tree with
| .Empty => false
| .Node { value, left, right } =>
value == x || search left x || search right x
/-- A shrinker for `NatTree`, adapted from Penn CIS 5520 lecture notes
https://www.seas.upenn.edu/~cis5520/current/lectures/stub/05-quickcheck/QuickCheck.html -/
def shrinkNatTree (tree : NatTree) : List NatTree :=
match tree with
| .Empty => []
| .Node {value := x, left := l, right := r} =>
[.Empty, l, r] -- left and right trees are smaller
++ (fun l' => NatTree.Node $ Node.mk x l' r) <$> shrinkNatTree l -- shrink left subtree
++ (fun r' => NatTree.Node $ Node.mk x l r') <$> shrinkNatTree r -- shrink right tree
++ (fun x' => NatTree.Node $ Node.mk x' l r) <$> Shrinkable.shrink x -- shrink the value
/-- `Shrinkable` instance for `NatTree` -/
instance : Shrinkable NatTree where
shrink := shrinkNatTree
/-- `SampleableExt` instance for `NatTree` -/
instance : SampleableExt NatTree :=
SampleableExt.mkSelfContained Arbitrary.arbitrary
/-!
To test whether the derived generator can generate counterexamples,
we create an erroneous property `∀ tree : NatTree, search tree 5`,
and ask Plausible to falsify it.
(This property is false, since there exist trees which don't contain the value 5,
e.g. the `Empty` tree.)
-/
/-- error: Found a counter-example! -/
#guard_msgs in
#eval Testable.check (∀ tree : NatTree, search tree 5)
(cfg := {numInst := 10, maxSize := 2, quiet := true})