haskell_match 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +98 -0
- data/LICENSE-APACHE +202 -0
- data/LICENSE-MIT +21 -0
- data/README.md +1484 -0
- data/ext/haskell_match/Cargo.lock +33 -0
- data/ext/haskell_match/Cargo.toml +22 -0
- data/ext/haskell_match/extconf.rb +41 -0
- data/ext/haskell_match/src/core/ast.rs +190 -0
- data/ext/haskell_match/src/core/error.rs +52 -0
- data/ext/haskell_match/src/core/exhaust.rs +699 -0
- data/ext/haskell_match/src/core/hs/ast.rs +256 -0
- data/ext/haskell_match/src/core/hs/json.rs +225 -0
- data/ext/haskell_match/src/core/hs/layout.rs +346 -0
- data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
- data/ext/haskell_match/src/core/hs/mod.rs +14 -0
- data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
- data/ext/haskell_match/src/core/lexer.rs +590 -0
- data/ext/haskell_match/src/core/mod.rs +19 -0
- data/ext/haskell_match/src/core/parser.rs +1116 -0
- data/ext/haskell_match/src/core/pretty.rs +373 -0
- data/ext/haskell_match/src/core/resolve.rs +336 -0
- data/ext/haskell_match/src/core/tree.rs +921 -0
- data/ext/haskell_match/src/core/typecheck.rs +226 -0
- data/ext/haskell_match/src/core/types.rs +404 -0
- data/ext/haskell_match/src/lib.rs +19 -0
- data/ext/haskell_match/src/ruby/mod.rs +1195 -0
- data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
- data/lib/haskell_match/binding_plan.rb +84 -0
- data/lib/haskell_match/case_of.rb +71 -0
- data/lib/haskell_match/clauses.rb +354 -0
- data/lib/haskell_match/data.rb +417 -0
- data/lib/haskell_match/deep_call.rb +98 -0
- data/lib/haskell_match/deriving.rb +130 -0
- data/lib/haskell_match/dsl.rb +71 -0
- data/lib/haskell_match/errors.rb +85 -0
- data/lib/haskell_match/field_types.rb +140 -0
- data/lib/haskell_match/function.rb +240 -0
- data/lib/haskell_match/haskell/compiler.rb +961 -0
- data/lib/haskell_match/haskell.rb +326 -0
- data/lib/haskell_match/inspect.rb +45 -0
- data/lib/haskell_match/lazy_list.rb +210 -0
- data/lib/haskell_match/native_loader.rb +64 -0
- data/lib/haskell_match/pattern.rb +75 -0
- data/lib/haskell_match/pattern_ast.rb +394 -0
- data/lib/haskell_match/prelude.rb +448 -0
- data/lib/haskell_match/scope.rb +44 -0
- data/lib/haskell_match/version.rb +5 -0
- data/lib/haskell_match.rb +41 -0
- metadata +124 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
//! Exhaustiveness and redundancy analysis (Maranget, "Warnings for pattern
|
|
2
|
+
//! matching", JFP 2007).
|
|
3
|
+
//!
|
|
4
|
+
//! * `useful(P, q)` decides whether row `q` can match a value no row of `P`
|
|
5
|
+
//! matches. A clause whose pattern is not useful with respect to the
|
|
6
|
+
//! clauses before it is redundant (GHC: `-Woverlapping-patterns`).
|
|
7
|
+
//! * `missing(P, n)` enumerates witnesses for values no row matches
|
|
8
|
+
//! (GHC: `-Wincomplete-patterns`, "Patterns not matched: ...").
|
|
9
|
+
//!
|
|
10
|
+
//! Guarded clauses may fall through, so they are excluded from the matrix
|
|
11
|
+
//! when computing what later clauses or the whole function cover, exactly
|
|
12
|
+
//! as GHC treats guards other than `otherwise`.
|
|
13
|
+
|
|
14
|
+
use super::ast::{HKey, Lit, LitKey, Pat};
|
|
15
|
+
use super::pretty::WPat;
|
|
16
|
+
use super::types::{ConId, TypeEnv, TypeId};
|
|
17
|
+
|
|
18
|
+
type Row = Vec<Pat>;
|
|
19
|
+
type Matrix = Vec<Row>;
|
|
20
|
+
|
|
21
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
22
|
+
pub struct Analysis {
|
|
23
|
+
/// Witness vectors (one pattern per argument) for unmatched values.
|
|
24
|
+
pub missing: Vec<Vec<WPat>>,
|
|
25
|
+
/// Whether `missing` was cut off at the requested limit.
|
|
26
|
+
pub truncated: bool,
|
|
27
|
+
/// Indices of clauses that can never be selected.
|
|
28
|
+
pub redundant: Vec<usize>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
enum Heads {
|
|
32
|
+
None,
|
|
33
|
+
Cons(TypeId, Vec<ConId>),
|
|
34
|
+
Lits(Vec<Lit>),
|
|
35
|
+
/// The distinct key sets of the Hash patterns in the column.
|
|
36
|
+
Hashes(Vec<Vec<HKey>>),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn key_set(fields: &[(HKey, Pat)]) -> Vec<HKey> {
|
|
40
|
+
fields.iter().map(|(k, _)| k.clone()).collect()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn same_keys(a: &[HKey], b: &[HKey]) -> bool {
|
|
44
|
+
a.len() == b.len() && a.iter().all(|k| b.contains(k))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn subset(a: &[HKey], b: &[HKey]) -> bool {
|
|
48
|
+
a.iter().all(|k| b.contains(k))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn heads(env: &TypeEnv, m: &Matrix) -> Heads {
|
|
52
|
+
let mut cons: Vec<ConId> = Vec::new();
|
|
53
|
+
let mut lits: Vec<Lit> = Vec::new();
|
|
54
|
+
let mut keys: Vec<LitKey> = Vec::new();
|
|
55
|
+
let mut hashes: Vec<Vec<HKey>> = Vec::new();
|
|
56
|
+
let mut ty = None;
|
|
57
|
+
let mut kind = None;
|
|
58
|
+
for row in m {
|
|
59
|
+
match &row[0] {
|
|
60
|
+
Pat::Con(c, _) => {
|
|
61
|
+
ty = Some(env.type_of_con(*c));
|
|
62
|
+
if !cons.contains(c) {
|
|
63
|
+
cons.push(*c);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
Pat::Lit(l) => {
|
|
67
|
+
kind = Some(l.kind());
|
|
68
|
+
let k = l.key();
|
|
69
|
+
if !keys.contains(&k) {
|
|
70
|
+
keys.push(k);
|
|
71
|
+
lits.push(l.clone());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
Pat::Hash(fields) => {
|
|
75
|
+
let ks = key_set(fields);
|
|
76
|
+
if !hashes.iter().any(|h| same_keys(h, &ks)) {
|
|
77
|
+
hashes.push(ks);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
_ => {}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
match (ty, kind) {
|
|
84
|
+
(Some(t), _) => Heads::Cons(t, cons),
|
|
85
|
+
(None, Some(_)) => Heads::Lits(lits),
|
|
86
|
+
(None, None) if !hashes.is_empty() => Heads::Hashes(hashes),
|
|
87
|
+
(None, None) => Heads::None,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// S({K}, P): rows that can match a Hash having exactly the keys `keys`,
|
|
92
|
+
/// with the value at each key expanded into its own column. A Hash row
|
|
93
|
+
/// whose keys are a subset of `keys` matches (its missing keys become `_`);
|
|
94
|
+
/// one needing a key outside `keys` cannot.
|
|
95
|
+
fn specialize_hash(m: &Matrix, keys: &[HKey]) -> Matrix {
|
|
96
|
+
let mut out = Vec::new();
|
|
97
|
+
for row in m {
|
|
98
|
+
match &row[0] {
|
|
99
|
+
Pat::Hash(fields) if subset(&key_set(fields), keys) => {
|
|
100
|
+
let mut r: Row = keys
|
|
101
|
+
.iter()
|
|
102
|
+
.map(|k| {
|
|
103
|
+
fields
|
|
104
|
+
.iter()
|
|
105
|
+
.find(|(kk, _)| kk == k)
|
|
106
|
+
.map(|(_, p)| p.clone())
|
|
107
|
+
.unwrap_or(Pat::Wild)
|
|
108
|
+
})
|
|
109
|
+
.collect();
|
|
110
|
+
r.extend_from_slice(&row[1..]);
|
|
111
|
+
out.push(r);
|
|
112
|
+
}
|
|
113
|
+
Pat::Wild => {
|
|
114
|
+
let mut r: Row = wilds(keys.len()).collect();
|
|
115
|
+
r.extend_from_slice(&row[1..]);
|
|
116
|
+
out.push(r);
|
|
117
|
+
}
|
|
118
|
+
_ => {}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
out
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// D(P) for a Hash column: wildcard rows plus `{}` rows, which match every
|
|
125
|
+
/// Hash.
|
|
126
|
+
fn default_hash(m: &Matrix) -> Matrix {
|
|
127
|
+
m.iter()
|
|
128
|
+
.filter(|row| {
|
|
129
|
+
matches!(&row[0], Pat::Wild) || matches!(&row[0], Pat::Hash(f) if f.is_empty())
|
|
130
|
+
})
|
|
131
|
+
.map(|row| row[1..].to_vec())
|
|
132
|
+
.collect()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn wilds(n: usize) -> impl Iterator<Item = Pat> {
|
|
136
|
+
std::iter::repeat_n(Pat::Wild, n)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// S(c, P): rows that can match constructor `c`, with its fields expanded.
|
|
140
|
+
fn specialize_con(env: &TypeEnv, m: &Matrix, c: ConId) -> Matrix {
|
|
141
|
+
let arity = env.con(c).arity;
|
|
142
|
+
let mut out = Vec::new();
|
|
143
|
+
for row in m {
|
|
144
|
+
match &row[0] {
|
|
145
|
+
Pat::Con(cc, args) if *cc == c => {
|
|
146
|
+
let mut r: Row = args.clone();
|
|
147
|
+
r.extend_from_slice(&row[1..]);
|
|
148
|
+
out.push(r);
|
|
149
|
+
}
|
|
150
|
+
Pat::Wild => {
|
|
151
|
+
let mut r: Row = wilds(arity).collect();
|
|
152
|
+
r.extend_from_slice(&row[1..]);
|
|
153
|
+
out.push(r);
|
|
154
|
+
}
|
|
155
|
+
_ => {}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
out
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn specialize_lit(m: &Matrix, l: &Lit) -> Matrix {
|
|
162
|
+
let mut out = Vec::new();
|
|
163
|
+
for row in m {
|
|
164
|
+
match &row[0] {
|
|
165
|
+
Pat::Lit(ll) if ll.same(l) => out.push(row[1..].to_vec()),
|
|
166
|
+
Pat::Wild => out.push(row[1..].to_vec()),
|
|
167
|
+
_ => {}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
out
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// D(P): rows with a wildcard in the first column, first column dropped.
|
|
174
|
+
fn default(m: &Matrix) -> Matrix {
|
|
175
|
+
m.iter()
|
|
176
|
+
.filter(|row| matches!(row[0], Pat::Wild))
|
|
177
|
+
.map(|row| row[1..].to_vec())
|
|
178
|
+
.collect()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Is `q` useful with respect to `m`? All patterns must be skeletons.
|
|
182
|
+
pub fn useful(env: &TypeEnv, m: &Matrix, q: &[Pat]) -> bool {
|
|
183
|
+
if q.is_empty() {
|
|
184
|
+
return m.is_empty();
|
|
185
|
+
}
|
|
186
|
+
if m.is_empty() {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
match &q[0] {
|
|
190
|
+
Pat::Con(c, args) => {
|
|
191
|
+
let mut nq: Vec<Pat> = args.clone();
|
|
192
|
+
nq.extend_from_slice(&q[1..]);
|
|
193
|
+
useful(env, &specialize_con(env, m, *c), &nq)
|
|
194
|
+
}
|
|
195
|
+
Pat::Lit(l) => useful(env, &specialize_lit(m, l), &q[1..]),
|
|
196
|
+
Pat::Hash(fields) => {
|
|
197
|
+
let keys = key_set(fields);
|
|
198
|
+
let mut nq: Vec<Pat> = fields.iter().map(|(_, p)| p.clone()).collect();
|
|
199
|
+
nq.extend_from_slice(&q[1..]);
|
|
200
|
+
useful(env, &specialize_hash(m, &keys), &nq)
|
|
201
|
+
}
|
|
202
|
+
_ => match heads(env, m) {
|
|
203
|
+
Heads::Cons(ty, cs) if env.is_complete(ty, &cs) => cs.iter().any(|c| {
|
|
204
|
+
let mut nq: Vec<Pat> = wilds(env.con(*c).arity).collect();
|
|
205
|
+
nq.extend_from_slice(&q[1..]);
|
|
206
|
+
useful(env, &specialize_con(env, m, *c), &nq)
|
|
207
|
+
}),
|
|
208
|
+
// a Hash lacking a key of every Hash pattern escapes them all
|
|
209
|
+
Heads::Hashes(_) => useful(env, &default_hash(m), &q[1..]),
|
|
210
|
+
_ => useful(env, &default(m), &q[1..]),
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
struct Collector<'a> {
|
|
216
|
+
env: &'a TypeEnv,
|
|
217
|
+
limit: usize,
|
|
218
|
+
truncated: bool,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
impl<'a> Collector<'a> {
|
|
222
|
+
fn missing(&mut self, m: &Matrix, n: usize) -> Vec<Vec<WPat>> {
|
|
223
|
+
if n == 0 {
|
|
224
|
+
return if m.is_empty() { vec![vec![]] } else { vec![] };
|
|
225
|
+
}
|
|
226
|
+
let mut out: Vec<Vec<WPat>> = Vec::new();
|
|
227
|
+
match heads(self.env, m) {
|
|
228
|
+
Heads::Cons(ty, cs) => {
|
|
229
|
+
// Constructors absent from the column are unmatched outright
|
|
230
|
+
// (combined with whatever the wildcard rows leave unmatched);
|
|
231
|
+
// constructors present in the column may still be incomplete
|
|
232
|
+
// in their fields or in the remaining columns.
|
|
233
|
+
let dflt = if self.env.is_complete(ty, &cs) {
|
|
234
|
+
None
|
|
235
|
+
} else {
|
|
236
|
+
Some(self.missing(&default(m), n - 1))
|
|
237
|
+
};
|
|
238
|
+
let all: Vec<ConId> = self.env.ty(ty).cons.clone();
|
|
239
|
+
'cons: for c in all {
|
|
240
|
+
let arity = self.env.con(c).arity;
|
|
241
|
+
if cs.contains(&c) {
|
|
242
|
+
let sub = self.missing(&specialize_con(self.env, m, c), arity + n - 1);
|
|
243
|
+
for w in sub {
|
|
244
|
+
if out.len() >= self.limit {
|
|
245
|
+
self.truncated = true;
|
|
246
|
+
break 'cons;
|
|
247
|
+
}
|
|
248
|
+
let (args, rest) = w.split_at(arity);
|
|
249
|
+
let mut row = vec![WPat::Con(c, args.to_vec())];
|
|
250
|
+
row.extend_from_slice(rest);
|
|
251
|
+
out.push(row);
|
|
252
|
+
}
|
|
253
|
+
} else if let Some(dflt) = &dflt {
|
|
254
|
+
for w in dflt {
|
|
255
|
+
if out.len() >= self.limit {
|
|
256
|
+
self.truncated = true;
|
|
257
|
+
break 'cons;
|
|
258
|
+
}
|
|
259
|
+
let mut row = vec![WPat::Con(
|
|
260
|
+
c,
|
|
261
|
+
std::iter::repeat_n(WPat::Wild, arity).collect(),
|
|
262
|
+
)];
|
|
263
|
+
row.extend_from_slice(w);
|
|
264
|
+
out.push(row);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
Heads::Lits(lits) => {
|
|
270
|
+
'lits: for l in &lits {
|
|
271
|
+
let sub = self.missing(&specialize_lit(m, l), n - 1);
|
|
272
|
+
for w in sub {
|
|
273
|
+
if out.len() >= self.limit {
|
|
274
|
+
self.truncated = true;
|
|
275
|
+
break 'lits;
|
|
276
|
+
}
|
|
277
|
+
let mut row = vec![WPat::Lit(l.clone())];
|
|
278
|
+
row.extend_from_slice(&w);
|
|
279
|
+
out.push(row);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
let sub = self.missing(&default(m), n - 1);
|
|
283
|
+
for w in sub {
|
|
284
|
+
if out.len() >= self.limit {
|
|
285
|
+
self.truncated = true;
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
let mut row = vec![WPat::NotLit(lits.clone())];
|
|
289
|
+
row.extend_from_slice(&w);
|
|
290
|
+
out.push(row);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
Heads::Hashes(sets) => {
|
|
294
|
+
'sets: for keys in &sets {
|
|
295
|
+
let sub = self.missing(&specialize_hash(m, keys), keys.len() + n - 1);
|
|
296
|
+
for w in sub {
|
|
297
|
+
if out.len() >= self.limit {
|
|
298
|
+
self.truncated = true;
|
|
299
|
+
break 'sets;
|
|
300
|
+
}
|
|
301
|
+
let (vals, rest) = w.split_at(keys.len());
|
|
302
|
+
let fields = keys.iter().cloned().zip(vals.iter().cloned()).collect();
|
|
303
|
+
let mut row = vec![WPat::Hash(fields)];
|
|
304
|
+
row.extend_from_slice(rest);
|
|
305
|
+
out.push(row);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// `{}` matches every Hash, so with it present the default
|
|
309
|
+
// case was already covered by the empty key set above
|
|
310
|
+
if !sets.iter().any(|k| k.is_empty()) {
|
|
311
|
+
let sub = self.missing(&default_hash(m), n - 1);
|
|
312
|
+
for w in sub {
|
|
313
|
+
if out.len() >= self.limit {
|
|
314
|
+
self.truncated = true;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
let mut row = vec![WPat::NoKeys(sets.clone())];
|
|
318
|
+
row.extend_from_slice(&w);
|
|
319
|
+
out.push(row);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
Heads::None => {
|
|
324
|
+
let sub = self.missing(&default(m), n - 1);
|
|
325
|
+
for w in sub {
|
|
326
|
+
if out.len() >= self.limit {
|
|
327
|
+
self.truncated = true;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
let mut row = vec![WPat::Wild];
|
|
331
|
+
row.extend_from_slice(&w);
|
|
332
|
+
out.push(row);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
out
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// Analyse a function's clauses. `clauses` holds the (already resolved)
|
|
341
|
+
/// argument patterns of each clause and whether it carries a guard.
|
|
342
|
+
pub fn analyze(env: &TypeEnv, clauses: &[(Vec<Pat>, bool)], limit: usize) -> Analysis {
|
|
343
|
+
let arity = clauses.first().map(|c| c.0.len()).unwrap_or(0);
|
|
344
|
+
let skeletons: Vec<Row> = clauses
|
|
345
|
+
.iter()
|
|
346
|
+
.map(|(pats, _)| pats.iter().map(Pat::skeleton).collect())
|
|
347
|
+
.collect();
|
|
348
|
+
|
|
349
|
+
// redundancy: each clause against the unguarded clauses before it
|
|
350
|
+
let mut redundant = Vec::new();
|
|
351
|
+
let mut covering: Matrix = Vec::new();
|
|
352
|
+
for (i, (_, guarded)) in clauses.iter().enumerate() {
|
|
353
|
+
if !useful(env, &covering, &skeletons[i]) {
|
|
354
|
+
redundant.push(i);
|
|
355
|
+
}
|
|
356
|
+
if !guarded {
|
|
357
|
+
covering.push(skeletons[i].clone());
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Ask for one witness more than the caller wants so an exact hit on the
|
|
362
|
+
// limit is distinguishable from a cut-off list.
|
|
363
|
+
let limit = limit.max(1);
|
|
364
|
+
let mut col = Collector {
|
|
365
|
+
env,
|
|
366
|
+
limit: limit + 1,
|
|
367
|
+
truncated: false,
|
|
368
|
+
};
|
|
369
|
+
let mut missing = col.missing(&covering, arity);
|
|
370
|
+
let truncated = col.truncated || missing.len() > limit;
|
|
371
|
+
missing.truncate(limit);
|
|
372
|
+
Analysis {
|
|
373
|
+
missing,
|
|
374
|
+
truncated,
|
|
375
|
+
redundant,
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
#[cfg(test)]
|
|
380
|
+
mod tests {
|
|
381
|
+
use super::*;
|
|
382
|
+
use crate::core::parser::parse_pattern;
|
|
383
|
+
use crate::core::pretty::render_witness;
|
|
384
|
+
use crate::core::resolve::resolve_clause;
|
|
385
|
+
use crate::core::types::ConSpec;
|
|
386
|
+
|
|
387
|
+
fn env() -> TypeEnv {
|
|
388
|
+
let mut env = TypeEnv::new();
|
|
389
|
+
env.register(
|
|
390
|
+
"Maybe",
|
|
391
|
+
&[
|
|
392
|
+
ConSpec {
|
|
393
|
+
name: "Nothing".into(),
|
|
394
|
+
arity: 0,
|
|
395
|
+
fields: None,
|
|
396
|
+
handle: 0,
|
|
397
|
+
},
|
|
398
|
+
ConSpec {
|
|
399
|
+
name: "Just".into(),
|
|
400
|
+
arity: 1,
|
|
401
|
+
fields: None,
|
|
402
|
+
handle: 0,
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
)
|
|
406
|
+
.unwrap();
|
|
407
|
+
env.register(
|
|
408
|
+
"Shape",
|
|
409
|
+
&[
|
|
410
|
+
ConSpec {
|
|
411
|
+
name: "Circle".into(),
|
|
412
|
+
arity: 1,
|
|
413
|
+
fields: None,
|
|
414
|
+
handle: 0,
|
|
415
|
+
},
|
|
416
|
+
ConSpec {
|
|
417
|
+
name: "Rect".into(),
|
|
418
|
+
arity: 2,
|
|
419
|
+
fields: None,
|
|
420
|
+
handle: 0,
|
|
421
|
+
},
|
|
422
|
+
ConSpec {
|
|
423
|
+
name: "Tri".into(),
|
|
424
|
+
arity: 3,
|
|
425
|
+
fields: None,
|
|
426
|
+
handle: 0,
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
)
|
|
430
|
+
.unwrap();
|
|
431
|
+
env
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/// Clause spec: list of pattern strings; a trailing "|" marks a guard.
|
|
435
|
+
fn run(env: &mut TypeEnv, clauses: &[&[&str]]) -> (Vec<String>, Vec<usize>, bool) {
|
|
436
|
+
let mut resolved = Vec::new();
|
|
437
|
+
for c in clauses {
|
|
438
|
+
let guarded = c.last() == Some(&"|");
|
|
439
|
+
let pats: Vec<&str> = c.iter().copied().filter(|s| *s != "|").collect();
|
|
440
|
+
let raws: Vec<_> = pats.iter().map(|s| parse_pattern(s).unwrap()).collect();
|
|
441
|
+
let (ps, _) = resolve_clause(env, &raws).unwrap();
|
|
442
|
+
resolved.push((ps, guarded));
|
|
443
|
+
}
|
|
444
|
+
let a = analyze(env, &resolved, 50);
|
|
445
|
+
let missing = a.missing.iter().map(|w| render_witness(env, w)).collect();
|
|
446
|
+
(missing, a.redundant, a.truncated)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
#[test]
|
|
450
|
+
fn exhaustive_functions() {
|
|
451
|
+
let mut env = env();
|
|
452
|
+
let empty: Vec<String> = vec![];
|
|
453
|
+
assert_eq!(
|
|
454
|
+
run(&mut env, &[&["Nothing"], &["Just _"]]),
|
|
455
|
+
(empty.clone(), vec![], false)
|
|
456
|
+
);
|
|
457
|
+
assert_eq!(
|
|
458
|
+
run(&mut env, &[&["[]"], &["(x:xs)"]]),
|
|
459
|
+
(empty.clone(), vec![], false)
|
|
460
|
+
);
|
|
461
|
+
assert_eq!(
|
|
462
|
+
run(&mut env, &[&["[]"], &["[x]"], &["(x:y:rest)"]]),
|
|
463
|
+
(empty.clone(), vec![], false)
|
|
464
|
+
);
|
|
465
|
+
assert_eq!(
|
|
466
|
+
run(&mut env, &[&["True"], &["False"]]),
|
|
467
|
+
(empty.clone(), vec![], false)
|
|
468
|
+
);
|
|
469
|
+
assert_eq!(
|
|
470
|
+
run(&mut env, &[&["0"], &["n"]]),
|
|
471
|
+
(empty.clone(), vec![], false)
|
|
472
|
+
);
|
|
473
|
+
assert_eq!(
|
|
474
|
+
run(&mut env, &[&["(a, b)"]]),
|
|
475
|
+
(empty.clone(), vec![], false)
|
|
476
|
+
);
|
|
477
|
+
assert_eq!(run(&mut env, &[&["()"]]), (empty.clone(), vec![], false));
|
|
478
|
+
assert_eq!(
|
|
479
|
+
run(&mut env, &[&["_", "_"]]),
|
|
480
|
+
(empty.clone(), vec![], false)
|
|
481
|
+
);
|
|
482
|
+
// multi-argument zip
|
|
483
|
+
assert_eq!(
|
|
484
|
+
run(&mut env, &[&["(x:xs)", "(y:ys)"], &["_", "_"]]),
|
|
485
|
+
(empty.clone(), vec![], false)
|
|
486
|
+
);
|
|
487
|
+
// guard followed by catch-all
|
|
488
|
+
assert_eq!(
|
|
489
|
+
run(&mut env, &[&["Just x", "|"], &["Just _"], &["Nothing"]]),
|
|
490
|
+
(empty.clone(), vec![], false)
|
|
491
|
+
);
|
|
492
|
+
// lazy and as patterns are irrefutable / transparent
|
|
493
|
+
assert_eq!(
|
|
494
|
+
run(&mut env, &[&["~(Just x)"]]),
|
|
495
|
+
(empty.clone(), vec![], false)
|
|
496
|
+
);
|
|
497
|
+
assert_eq!(
|
|
498
|
+
run(&mut env, &[&["a@(Just x)"], &["Nothing"]]),
|
|
499
|
+
(empty, vec![], false)
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
#[test]
|
|
504
|
+
fn missing_witnesses() {
|
|
505
|
+
let mut env = env();
|
|
506
|
+
assert_eq!(run(&mut env, &[&["Just _"]]).0, vec!["Nothing"]);
|
|
507
|
+
assert_eq!(
|
|
508
|
+
run(&mut env, &[&["Nothing"], &["Just Nothing"]]).0,
|
|
509
|
+
vec!["Just (Just _)"]
|
|
510
|
+
);
|
|
511
|
+
assert_eq!(run(&mut env, &[&["(x:xs)"]]).0, vec!["[]"]);
|
|
512
|
+
assert_eq!(run(&mut env, &[&["[]"], &["[x]"]]).0, vec!["(_:_:_)"]);
|
|
513
|
+
assert_eq!(run(&mut env, &[&["[]"], &["(x:y:rest)"]]).0, vec!["[_]"]);
|
|
514
|
+
assert_eq!(run(&mut env, &[&["True"]]).0, vec!["False"]);
|
|
515
|
+
assert_eq!(
|
|
516
|
+
run(&mut env, &[&["0"], &["1"]]).0,
|
|
517
|
+
vec!["p1 where p1 is not one of {0, 1}"]
|
|
518
|
+
);
|
|
519
|
+
assert_eq!(
|
|
520
|
+
run(&mut env, &[&["\"a\""]]).0,
|
|
521
|
+
vec!["[]", "('a':_:_)", "(p1:_) where p1 is not one of {'a'}"]
|
|
522
|
+
);
|
|
523
|
+
assert_eq!(
|
|
524
|
+
run(&mut env, &[&["\"\""], &["(c:cs)"]]).0,
|
|
525
|
+
Vec::<String>::new()
|
|
526
|
+
);
|
|
527
|
+
assert_eq!(
|
|
528
|
+
run(&mut env, &[&["'a'"]]).0,
|
|
529
|
+
vec!["p1 where p1 is not one of {'a'}"]
|
|
530
|
+
);
|
|
531
|
+
assert_eq!(
|
|
532
|
+
run(&mut env, &[&["Circle _"]]).0,
|
|
533
|
+
vec!["Rect _ _", "Tri _ _ _"]
|
|
534
|
+
);
|
|
535
|
+
assert_eq!(
|
|
536
|
+
run(&mut env, &[&["(True, Nothing)"], &["(False, _)"]]).0,
|
|
537
|
+
vec!["(True, Just _)"]
|
|
538
|
+
);
|
|
539
|
+
assert_eq!(
|
|
540
|
+
run(&mut env, &[&["(x:xs)", "(y:ys)"], &["[]", "[]"]]).0,
|
|
541
|
+
vec!["[] (_:_)", "(_:_) []"]
|
|
542
|
+
);
|
|
543
|
+
assert_eq!(
|
|
544
|
+
run(&mut env, &[&["Circle _", "Circle _"]]).0,
|
|
545
|
+
vec![
|
|
546
|
+
"(Circle _) (Rect _ _)",
|
|
547
|
+
"(Circle _) (Tri _ _ _)",
|
|
548
|
+
"(Rect _ _) _",
|
|
549
|
+
"(Tri _ _ _) _"
|
|
550
|
+
]
|
|
551
|
+
);
|
|
552
|
+
assert_eq!(
|
|
553
|
+
run(&mut env, &[&["0", "[]"], &["n", "(x:xs)"]]).0,
|
|
554
|
+
vec!["p1 [] where p1 is not one of {0}"]
|
|
555
|
+
);
|
|
556
|
+
assert_eq!(
|
|
557
|
+
run(&mut env, &[&["0", "[]"], &["n", "[]"]]).0,
|
|
558
|
+
vec!["0 (_:_)", "p1 (_:_) where p1 is not one of {0}"]
|
|
559
|
+
);
|
|
560
|
+
assert_eq!(
|
|
561
|
+
run(&mut env, &[&["0", "[]"], &["1", "_"]]).0,
|
|
562
|
+
vec!["0 (_:_)", "p1 _ where p1 is not one of {0, 1}"]
|
|
563
|
+
);
|
|
564
|
+
assert_eq!(
|
|
565
|
+
run(&mut env, &[&["Just 0"], &["Just n"]]).0,
|
|
566
|
+
vec!["Nothing"]
|
|
567
|
+
);
|
|
568
|
+
// a guard alone does not cover
|
|
569
|
+
assert_eq!(
|
|
570
|
+
run(&mut env, &[&["Just x", "|"], &["Nothing"]]).0,
|
|
571
|
+
vec!["Just _"]
|
|
572
|
+
);
|
|
573
|
+
// nested literal inside constructor
|
|
574
|
+
assert_eq!(
|
|
575
|
+
run(&mut env, &[&["Just 0"], &["Nothing"]]).0,
|
|
576
|
+
vec!["Just p1 where p1 is not one of {0}"]
|
|
577
|
+
);
|
|
578
|
+
// nothing at all
|
|
579
|
+
let r = run(&mut env, &[]);
|
|
580
|
+
assert_eq!(r.0, vec![""]);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
#[test]
|
|
584
|
+
fn redundant_clauses() {
|
|
585
|
+
let mut env = env();
|
|
586
|
+
assert_eq!(run(&mut env, &[&["_"], &["Just x"]]).1, vec![1]);
|
|
587
|
+
assert_eq!(
|
|
588
|
+
run(&mut env, &[&["Nothing"], &["Just _"], &["Just 0"]]).1,
|
|
589
|
+
vec![2]
|
|
590
|
+
);
|
|
591
|
+
assert_eq!(
|
|
592
|
+
run(&mut env, &[&["[]"], &["(x:xs)"], &["[a, b]"], &["_"]]).1,
|
|
593
|
+
vec![2, 3]
|
|
594
|
+
);
|
|
595
|
+
assert_eq!(run(&mut env, &[&["0"], &["0"]]).1, vec![1]);
|
|
596
|
+
assert_eq!(run(&mut env, &[&["x"], &["0"]]).1, vec![1]);
|
|
597
|
+
// guarded clause does not make a later identical clause redundant...
|
|
598
|
+
assert_eq!(run(&mut env, &[&["Just x", "|"], &["Just x"]]).1, vec![]);
|
|
599
|
+
// ...but is itself redundant after an unguarded cover
|
|
600
|
+
assert_eq!(
|
|
601
|
+
run(&mut env, &[&["Just _"], &["Just x", "|"], &["Nothing"]]).1,
|
|
602
|
+
vec![1]
|
|
603
|
+
);
|
|
604
|
+
assert_eq!(
|
|
605
|
+
run(
|
|
606
|
+
&mut env,
|
|
607
|
+
&[
|
|
608
|
+
&["(True, _)"],
|
|
609
|
+
&["(_, True)"],
|
|
610
|
+
&["(False, False)"],
|
|
611
|
+
&["(True, True)"]
|
|
612
|
+
]
|
|
613
|
+
)
|
|
614
|
+
.1,
|
|
615
|
+
vec![3]
|
|
616
|
+
);
|
|
617
|
+
assert_eq!(
|
|
618
|
+
run(
|
|
619
|
+
&mut env,
|
|
620
|
+
&[
|
|
621
|
+
&["(x:xs)", "_"],
|
|
622
|
+
&["_", "(y:ys)"],
|
|
623
|
+
&["[]", "[]"],
|
|
624
|
+
&["[]", "_"]
|
|
625
|
+
]
|
|
626
|
+
)
|
|
627
|
+
.1,
|
|
628
|
+
vec![3]
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
#[test]
|
|
633
|
+
fn hash_patterns() {
|
|
634
|
+
let mut env = env();
|
|
635
|
+
let empty: Vec<String> = vec![];
|
|
636
|
+
// a wildcard (or `{}`) is needed to cover every Hash
|
|
637
|
+
assert_eq!(
|
|
638
|
+
run(&mut env, &[&["{name = n}"], &["_"]]),
|
|
639
|
+
(empty.clone(), vec![], false)
|
|
640
|
+
);
|
|
641
|
+
assert_eq!(
|
|
642
|
+
run(&mut env, &[&["{name = n}"], &["{}"]]),
|
|
643
|
+
(empty.clone(), vec![], false)
|
|
644
|
+
);
|
|
645
|
+
assert_eq!(
|
|
646
|
+
run(&mut env, &[&["{name = n}"]]).0,
|
|
647
|
+
vec!["p1 where p1 is a Hash without the key name"]
|
|
648
|
+
);
|
|
649
|
+
assert_eq!(
|
|
650
|
+
run(&mut env, &[&["{a = 1}"], &["{b = _}"]]).0,
|
|
651
|
+
vec![
|
|
652
|
+
"{a = p1} where p1 is not one of {1}",
|
|
653
|
+
"p1 where p1 is a Hash without the key a or without the key b"
|
|
654
|
+
]
|
|
655
|
+
);
|
|
656
|
+
// nested exhaustiveness inside a key
|
|
657
|
+
assert_eq!(
|
|
658
|
+
run(&mut env, &[&["{m = Just x}"], &["_"]]),
|
|
659
|
+
(empty.clone(), vec![], false)
|
|
660
|
+
);
|
|
661
|
+
assert_eq!(
|
|
662
|
+
run(&mut env, &[&["{m = Just x}"], &["{m = Nothing}"], &["{}"]]),
|
|
663
|
+
(empty, vec![], false)
|
|
664
|
+
);
|
|
665
|
+
// redundancy: a superset of keys after a subset is unreachable
|
|
666
|
+
assert_eq!(
|
|
667
|
+
run(&mut env, &[&["{a = _}"], &["{a = _, b = _}"]]).1,
|
|
668
|
+
vec![1]
|
|
669
|
+
);
|
|
670
|
+
assert_eq!(run(&mut env, &[&["{}"], &["{a = _}"]]).1, vec![1]);
|
|
671
|
+
// but a different key set is not
|
|
672
|
+
assert_eq!(
|
|
673
|
+
run(&mut env, &[&["{a = _}"], &["{b = _}"]]).1,
|
|
674
|
+
Vec::<usize>::new()
|
|
675
|
+
);
|
|
676
|
+
assert_eq!(
|
|
677
|
+
run(&mut env, &[&["{a = 0}"], &["{a = _}"]]).1,
|
|
678
|
+
Vec::<usize>::new()
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
#[test]
|
|
683
|
+
fn witness_limit() {
|
|
684
|
+
let mut env = env();
|
|
685
|
+
// 4 witnesses (see missing_witnesses); limit 3 truncates
|
|
686
|
+
let mut resolved = Vec::new();
|
|
687
|
+
let raws = vec![
|
|
688
|
+
parse_pattern("Circle _").unwrap(),
|
|
689
|
+
parse_pattern("Circle _").unwrap(),
|
|
690
|
+
];
|
|
691
|
+
resolved.push((resolve_clause(&mut env, &raws).unwrap().0, false));
|
|
692
|
+
let a = analyze(&env, &resolved, 3);
|
|
693
|
+
assert_eq!(a.missing.len(), 3);
|
|
694
|
+
assert!(a.truncated);
|
|
695
|
+
let a = analyze(&env, &resolved, 100);
|
|
696
|
+
assert_eq!(a.missing.len(), 4);
|
|
697
|
+
assert!(!a.truncated);
|
|
698
|
+
}
|
|
699
|
+
}
|