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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE-APACHE +202 -0
  4. data/LICENSE-MIT +21 -0
  5. data/README.md +1484 -0
  6. data/ext/haskell_match/Cargo.lock +33 -0
  7. data/ext/haskell_match/Cargo.toml +22 -0
  8. data/ext/haskell_match/extconf.rb +41 -0
  9. data/ext/haskell_match/src/core/ast.rs +190 -0
  10. data/ext/haskell_match/src/core/error.rs +52 -0
  11. data/ext/haskell_match/src/core/exhaust.rs +699 -0
  12. data/ext/haskell_match/src/core/hs/ast.rs +256 -0
  13. data/ext/haskell_match/src/core/hs/json.rs +225 -0
  14. data/ext/haskell_match/src/core/hs/layout.rs +346 -0
  15. data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
  16. data/ext/haskell_match/src/core/hs/mod.rs +14 -0
  17. data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
  18. data/ext/haskell_match/src/core/lexer.rs +590 -0
  19. data/ext/haskell_match/src/core/mod.rs +19 -0
  20. data/ext/haskell_match/src/core/parser.rs +1116 -0
  21. data/ext/haskell_match/src/core/pretty.rs +373 -0
  22. data/ext/haskell_match/src/core/resolve.rs +336 -0
  23. data/ext/haskell_match/src/core/tree.rs +921 -0
  24. data/ext/haskell_match/src/core/typecheck.rs +226 -0
  25. data/ext/haskell_match/src/core/types.rs +404 -0
  26. data/ext/haskell_match/src/lib.rs +19 -0
  27. data/ext/haskell_match/src/ruby/mod.rs +1195 -0
  28. data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
  29. data/lib/haskell_match/binding_plan.rb +84 -0
  30. data/lib/haskell_match/case_of.rb +71 -0
  31. data/lib/haskell_match/clauses.rb +354 -0
  32. data/lib/haskell_match/data.rb +417 -0
  33. data/lib/haskell_match/deep_call.rb +98 -0
  34. data/lib/haskell_match/deriving.rb +130 -0
  35. data/lib/haskell_match/dsl.rb +71 -0
  36. data/lib/haskell_match/errors.rb +85 -0
  37. data/lib/haskell_match/field_types.rb +140 -0
  38. data/lib/haskell_match/function.rb +240 -0
  39. data/lib/haskell_match/haskell/compiler.rb +961 -0
  40. data/lib/haskell_match/haskell.rb +326 -0
  41. data/lib/haskell_match/inspect.rb +45 -0
  42. data/lib/haskell_match/lazy_list.rb +210 -0
  43. data/lib/haskell_match/native_loader.rb +64 -0
  44. data/lib/haskell_match/pattern.rb +75 -0
  45. data/lib/haskell_match/pattern_ast.rb +394 -0
  46. data/lib/haskell_match/prelude.rb +448 -0
  47. data/lib/haskell_match/scope.rb +44 -0
  48. data/lib/haskell_match/version.rb +5 -0
  49. data/lib/haskell_match.rb +41 -0
  50. 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
+ }