forked from ocaml/ocaml
-
Notifications
You must be signed in to change notification settings - Fork 0
/
parmatch.ml
2363 lines (2106 loc) · 80.2 KB
/
parmatch.ml
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* en Automatique. *)
(* *)
(* All rights reserved. This file is distributed under the terms of *)
(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
(* Detection of partial matches and unused match cases. *)
open Misc
open Asttypes
open Types
open Typedtree
type 'pattern parmatch_case =
{ pattern : 'pattern;
has_guard : bool;
needs_refute : bool;
}
let typed_case { c_lhs; c_guard; c_rhs } =
{ pattern = c_lhs;
has_guard = Option.is_some c_guard;
needs_refute = (c_rhs.exp_desc = Texp_unreachable);
}
let untyped_case { Parsetree.pc_lhs; pc_guard; pc_rhs } =
{ pattern = pc_lhs;
has_guard = Option.is_some pc_guard;
needs_refute = (pc_rhs.pexp_desc = Parsetree.Pexp_unreachable);
}
(*************************************)
(* Utilities for building patterns *)
(*************************************)
let make_pat desc ty tenv =
{pat_desc = desc; pat_loc = Location.none; pat_extra = [];
pat_type = ty ; pat_env = tenv;
pat_attributes = [];
}
let omega = Patterns.omega
let omegas = Patterns.omegas
let omega_list = Patterns.omega_list
let extra_pat =
make_pat
(Tpat_var (Ident.create_local "+", mknoloc "+",
Uid.internal_not_actually_unique))
Ctype.none Env.empty
(*******************)
(* Coherence check *)
(*******************)
(* For some of the operations we do in this module, we would like (because it
simplifies matters) to assume that patterns appearing on a given column in a
pattern matrix are /coherent/ (think "of the same type").
Unfortunately that is not always true.
Consider the following (well-typed) example:
{[
type _ t = S : string t | U : unit t
let f (type a) (t1 : a t) (t2 : a t) (a : a) =
match t1, t2, a with
| U, _, () -> ()
| _, S, "" -> ()
]}
Clearly the 3rd column contains incoherent patterns.
On the example above, most of the algorithms will explore the pattern matrix
as illustrated by the following tree:
{v
S
-------> | "" |
U | S, "" | __/ | () |
--------> | _, () | \ not S
| U, _, () | __/ -------> | () |
| _, S, "" | \
---------> | S, "" | ----------> | "" |
not U S
v}
where following an edge labelled by a pattern P means "assuming the value I
am matching on is filtered by [P] on the column I am currently looking at,
then the following submatrix is still reachable".
Notice that at any point of that tree, if the first column of a matrix is
incoherent, then the branch leading to it can only be taken if the scrutinee
is ill-typed.
In the example above the only case where we have a matrix with an incoherent
first column is when we consider [t1, t2, a] to be [U, S, ...]. However such
a value would be ill-typed, so we can never actually get there.
Checking the first column at each step of the recursion and making the
conscious decision of "aborting" the algorithm whenever the first column
becomes incoherent, allows us to retain the initial assumption in later
stages of the algorithms.
---
N.B. two patterns can be considered coherent even though they might not be of
the same type.
That's in part because we only care about the "head" of patterns and leave
checking coherence of subpatterns for the next steps of the algorithm:
('a', 'b') and (1, ()) will be deemed coherent because they are both a tuples
of arity 2 (we'll notice at a later stage the incoherence of 'a' and 1).
But also because it can be hard/costly to determine exactly whether two
patterns are of the same type or not (eg. in the example above with _ and S,
but see also the module [Coherence_illustration] in
testsuite/tests/basic-more/robustmatch.ml).
For the moment our weak, loosely-syntactic, coherence check seems to be
enough and we leave it to each user to consider (and document!) what happens
when an "incoherence" is not detected by this check.
*)
(* Given the first column of a simplified matrix, this function first looks for
a "discriminating" pattern on that column (i.e. a non-omega one) and then
check that every other head pattern in the column is coherent with that one.
*)
let all_coherent column =
let open Patterns.Head in
let coherent_heads hp1 hp2 =
match hp1.pat_desc, hp2.pat_desc with
| Construct c, Construct c' ->
c.cstr_consts = c'.cstr_consts
&& c.cstr_nonconsts = c'.cstr_nonconsts
| Constant c1, Constant c2 -> begin
match c1, c2 with
| Const_char _, Const_char _
| Const_int _, Const_int _
| Const_int32 _, Const_int32 _
| Const_int64 _, Const_int64 _
| Const_nativeint _, Const_nativeint _
| Const_float _, Const_float _
| Const_string _, Const_string _ -> true
| ( Const_char _
| Const_int _
| Const_int32 _
| Const_int64 _
| Const_nativeint _
| Const_float _
| Const_string _), _ -> false
end
| Tuple l1, Tuple l2 -> l1 = l2
| Record (lbl1 :: _), Record (lbl2 :: _) ->
Array.length lbl1.lbl_all = Array.length lbl2.lbl_all
| Any, _
| _, Any
| Record [], Record []
| Variant _, Variant _
| Array _, Array _
| Lazy, Lazy -> true
| _, _ -> false
in
match
List.find
(function
| { pat_desc = Any } -> false
| _ -> true)
column
with
| exception Not_found ->
(* only omegas on the column: the column is coherent. *)
true
| discr_pat ->
List.for_all (coherent_heads discr_pat) column
let first_column simplified_matrix =
List.map (fun ((head, _args), _rest) -> head) simplified_matrix
(***********************)
(* Compatibility check *)
(***********************)
(* Patterns p and q compatible means:
there exists value V that matches both, However....
The case of extension types is dubious, as constructor rebind permits
that different constructors are the same (and are thus compatible).
Compilation must take this into account, consider:
type t = ..
type t += A|B
type t += C=A
let f x y = match x,y with
| true,A -> '1'
| _,C -> '2'
| false,A -> '3'
| _,_ -> '_'
As C is bound to A the value of f false A is '2' (and not '3' as it would
be in the absence of rebinding).
Not considering rebinding, patterns "false,A" and "_,C" are incompatible
and the compiler can swap the second and third clause, resulting in the
(more efficiently compiled) matching
match x,y with
| true,A -> '1'
| false,A -> '3'
| _,C -> '2'
| _,_ -> '_'
This is not correct: when C is bound to A, "f false A" returns '2' (not '3')
However, diagnostics do not take constructor rebinding into account.
Notice, that due to module abstraction constructor rebinding is hidden.
module X : sig type t = .. type t += A|B end = struct
type t = ..
type t += A
type t += B=A
end
open X
let f x = match x with
| A -> '1'
| B -> '2'
| _ -> '_'
The second clause above will NOT (and cannot) be flagged as useless.
Finally, there are two compatibility functions:
compat p q ---> 'syntactic compatibility, used for diagnostics.
may_compat p q ---> a safe approximation of possible compat,
for compilation
*)
let is_absent tag row = row_field_repr (get_row_field tag !row) = Rabsent
let is_absent_pat d =
match d.pat_desc with
| Patterns.Head.Variant { tag; cstr_row; _ } -> is_absent tag cstr_row
| _ -> false
let const_compare x y =
match x,y with
| Const_float f1, Const_float f2 ->
Stdlib.compare (float_of_string f1) (float_of_string f2)
| Const_string (s1, _, _), Const_string (s2, _, _) ->
String.compare s1 s2
| (Const_int _
|Const_char _
|Const_string (_, _, _)
|Const_float _
|Const_int32 _
|Const_int64 _
|Const_nativeint _
), _ -> Stdlib.compare x y
let records_args l1 l2 =
(* Invariant: fields are already sorted by Typecore.type_label_a_list *)
let rec combine r1 r2 l1 l2 = match l1,l2 with
| [],[] -> List.rev r1, List.rev r2
| [],(_,_,p2)::rem2 -> combine (omega::r1) (p2::r2) [] rem2
| (_,_,p1)::rem1,[] -> combine (p1::r1) (omega::r2) rem1 []
| (_,lbl1,p1)::rem1, ( _,lbl2,p2)::rem2 ->
if lbl1.lbl_pos < lbl2.lbl_pos then
combine (p1::r1) (omega::r2) rem1 l2
else if lbl1.lbl_pos > lbl2.lbl_pos then
combine (omega::r1) (p2::r2) l1 rem2
else (* same label on both sides *)
combine (p1::r1) (p2::r2) rem1 rem2 in
combine [] [] l1 l2
module Compat
(Constr:sig
val equal :
Types.constructor_description ->
Types.constructor_description ->
bool
end) = struct
let rec compat p q = match p.pat_desc,q.pat_desc with
(* Variables match any value *)
| ((Tpat_any|Tpat_var _),_)
| (_,(Tpat_any|Tpat_var _)) -> true
(* Structural induction *)
| Tpat_alias (p,_,_,_),_ -> compat p q
| _,Tpat_alias (q,_,_,_) -> compat p q
| Tpat_or (p1,p2,_),_ ->
(compat p1 q || compat p2 q)
| _,Tpat_or (q1,q2,_) ->
(compat p q1 || compat p q2)
(* Constructors, with special case for extension *)
| Tpat_construct (_, c1, ps1, _), Tpat_construct (_, c2, ps2, _) ->
Constr.equal c1 c2 && compats ps1 ps2
(* More standard stuff *)
| Tpat_variant(l1,op1, _), Tpat_variant(l2,op2,_) ->
l1=l2 && ocompat op1 op2
| Tpat_constant c1, Tpat_constant c2 ->
const_compare c1 c2 = 0
| Tpat_tuple ps, Tpat_tuple qs -> compats ps qs
| Tpat_lazy p, Tpat_lazy q -> compat p q
| Tpat_record (l1,_),Tpat_record (l2,_) ->
let ps,qs = records_args l1 l2 in
compats ps qs
| Tpat_array ps, Tpat_array qs ->
List.length ps = List.length qs &&
compats ps qs
| _,_ -> false
and ocompat op oq = match op,oq with
| None,None -> true
| Some p,Some q -> compat p q
| (None,Some _)|(Some _,None) -> false
and compats ps qs = match ps,qs with
| [], [] -> true
| p::ps, q::qs -> compat p q && compats ps qs
| _,_ -> false
end
module SyntacticCompat =
Compat
(struct
let equal c1 c2 = Types.equal_tag c1.cstr_tag c2.cstr_tag
end)
let compat = SyntacticCompat.compat
and compats = SyntacticCompat.compats
(* Due to (potential) rebinding, two extension constructors
of the same arity type may equal *)
exception Empty (* Empty pattern *)
(****************************************)
(* Utilities for retrieving type paths *)
(****************************************)
(* May need a clean copy, cf. PR#4745 *)
let clean_copy ty =
if get_level ty = Btype.generic_level then ty
else Subst.type_expr Subst.identity ty
let get_constructor_type_path ty tenv =
let ty = Ctype.expand_head tenv (clean_copy ty) in
match get_desc ty with
| Tconstr (path,_,_) -> path
| _ -> assert false
(****************************)
(* Utilities for matching *)
(****************************)
(* Check top matching *)
let simple_match d h =
let open Patterns.Head in
match d.pat_desc, h.pat_desc with
| Construct c1, Construct c2 ->
Types.equal_tag c1.cstr_tag c2.cstr_tag
| Variant { tag = t1; _ }, Variant { tag = t2 } ->
t1 = t2
| Constant c1, Constant c2 -> const_compare c1 c2 = 0
| Lazy, Lazy -> true
| Record _, Record _ -> true
| Tuple len1, Tuple len2
| Array len1, Array len2 -> len1 = len2
| _, Any -> true
| _, _ -> false
(* extract record fields as a whole *)
let record_arg ph =
let open Patterns.Head in
match ph.pat_desc with
| Any -> []
| Record args -> args
| _ -> fatal_error "Parmatch.as_record"
let extract_fields lbls arg =
let get_field pos arg =
match List.find (fun (lbl,_) -> pos = lbl.lbl_pos) arg with
| _, p -> p
| exception Not_found -> omega
in
List.map (fun lbl -> get_field lbl.lbl_pos arg) lbls
(* Build argument list when p2 >= p1, where p1 is a simple pattern *)
let simple_match_args discr head args =
let open Patterns.Head in
match head.pat_desc with
| Constant _ -> []
| Construct _
| Variant _
| Tuple _
| Array _
| Lazy -> args
| Record lbls -> extract_fields (record_arg discr) (List.combine lbls args)
| Any ->
begin match discr.pat_desc with
| Construct cstr -> Patterns.omegas cstr.cstr_arity
| Variant { has_arg = true }
| Lazy -> [Patterns.omega]
| Record lbls -> omega_list lbls
| Array len
| Tuple len -> Patterns.omegas len
| Variant { has_arg = false }
| Any
| Constant _ -> []
end
(* Consider a pattern matrix whose first column has been simplified to contain
only _ or a head constructor
| p1, r1...
| p2, r2...
| p3, r3...
| ...
We build a normalized /discriminating/ pattern from a pattern [q] by folding
over the first column of the matrix, "refining" [q] as we go:
- when we encounter a row starting with [Tuple] or [Lazy] then we
can stop and return that head, as we cannot refine any further. Indeed,
these constructors are alone in their signature, so they will subsume
whatever other head we might find, as well as the head we're threading
along.
- when we find a [Record] then it is a bit more involved: it is also alone
in its signature, however it might only be matching a subset of the
record fields. We use these fields to refine our accumulator and keep going
as another row might match on different fields.
- rows starting with a wildcard do not bring any information, so we ignore
them and keep going
- if we encounter anything else (i.e. any other constructor), then we just
stop and return our accumulator.
*)
let discr_pat q pss =
let open Patterns.Head in
let rec refine_pat acc = function
| [] -> acc
| ((head, _), _) :: rows ->
match head.pat_desc with
| Any -> refine_pat acc rows
| Tuple _ | Lazy -> head
| Record lbls ->
(* N.B. we could make this case "simpler" by refining the record case
using [all_record_args].
In which case we wouldn't need to fold over the first column for
records.
However it makes the witness we generate for the exhaustivity warning
less pretty. *)
let fields =
List.fold_right (fun lbl r ->
if List.exists (fun l -> l.lbl_pos = lbl.lbl_pos) r then
r
else
lbl :: r
) lbls (record_arg acc)
in
let d = { head with pat_desc = Record fields } in
refine_pat d rows
| _ -> acc
in
let q, _ = deconstruct q in
match q.pat_desc with
(* short-circuiting: clearly if we have anything other than [Record] or
[Any] to start with, we're not going to be able refine at all. So
there's no point going over the matrix. *)
| Any | Record _ -> refine_pat q pss
| _ -> q
(*
In case a matching value is found, set actual arguments
of the matching pattern.
*)
let rec read_args xs r = match xs,r with
| [],_ -> [],r
| _::xs, arg::rest ->
let args,rest = read_args xs rest in
arg::args,rest
| _,_ ->
fatal_error "Parmatch.read_args"
let set_args q r = match q with
| {pat_desc = Tpat_tuple omegas} ->
let args,rest = read_args omegas r in
make_pat (Tpat_tuple args) q.pat_type q.pat_env::rest
| {pat_desc = Tpat_record (omegas,closed)} ->
let args,rest = read_args omegas r in
let args =
List.map2 (fun (lid, lbl, _) arg -> (lid, lbl, arg)) omegas args in
make_pat (Tpat_record (args, closed)) q.pat_type q.pat_env :: rest
| {pat_desc = Tpat_construct (lid, c, omegas, _)} ->
let args,rest = read_args omegas r in
make_pat
(Tpat_construct (lid, c, args, None))
q.pat_type q.pat_env::
rest
| {pat_desc = Tpat_variant (l, omega, row)} ->
let arg, rest =
match omega, r with
Some _, a::r -> Some a, r
| None, r -> None, r
| _ -> assert false
in
make_pat
(Tpat_variant (l, arg, row)) q.pat_type q.pat_env::
rest
| {pat_desc = Tpat_lazy _omega} ->
begin match r with
arg::rest ->
make_pat (Tpat_lazy arg) q.pat_type q.pat_env::rest
| _ -> fatal_error "Parmatch.do_set_args (lazy)"
end
| {pat_desc = Tpat_array omegas} ->
let args,rest = read_args omegas r in
make_pat
(Tpat_array args) q.pat_type q.pat_env::
rest
| {pat_desc=Tpat_constant _|Tpat_any} ->
q::r (* case any is used in matching.ml *)
| {pat_desc = (Tpat_var _ | Tpat_alias _ | Tpat_or _); _} ->
fatal_error "Parmatch.set_args"
(* Given a matrix of non-empty rows
p1 :: r1...
p2 :: r2...
p3 :: r3...
Simplify the first column [p1 p2 p3] by splitting all or-patterns.
The result is a list of pairs
((pattern head, arguments), rest of row)
For example,
x :: r1
(Some _) as y :: r2
(None as x) as y :: r3
(Some x | (None as x)) :: r4
becomes
(( _ , [ ] ), r1)
(( Some, [_] ), r2)
(( None, [ ] ), r3)
(( Some, [x] ), r4)
(( None, [ ] ), r4)
*)
let simplify_head_pat ~add_column p ps k =
let rec simplify_head_pat p ps k =
match Patterns.General.(view p |> strip_vars).pat_desc with
| `Or (p1,p2,_) -> simplify_head_pat p1 ps (simplify_head_pat p2 ps k)
| #Patterns.Simple.view as view ->
add_column (Patterns.Head.deconstruct { p with pat_desc = view }) ps k
in simplify_head_pat p ps k
let rec simplify_first_col = function
| [] -> []
| [] :: _ -> assert false (* the rows are non-empty! *)
| (p::ps) :: rows ->
let add_column p ps k = (p, ps) :: k in
simplify_head_pat ~add_column p ps (simplify_first_col rows)
(* Builds the specialized matrix of [pss] according to the discriminating
pattern head [d].
See section 3.1 of http://moscova.inria.fr/~maranget/papers/warn/warn.pdf
NOTES:
- we are polymorphic on the type of matrices we work on, in particular a row
might not simply be a [pattern list]. That's why we have the [extend_row]
parameter.
*)
let build_specialized_submatrix ~extend_row discr pss =
let rec filter_rec = function
| ((head, args), ps) :: pss ->
if simple_match discr head
then extend_row (simple_match_args discr head args) ps :: filter_rec pss
else filter_rec pss
| _ -> [] in
filter_rec pss
(* The "default" and "specialized" matrices of a given matrix.
See section 3.1 of http://moscova.inria.fr/~maranget/papers/warn/warn.pdf .
*)
type 'matrix specialized_matrices = {
default : 'matrix;
constrs : (Patterns.Head.t * 'matrix) list;
}
(* Consider a pattern matrix whose first column has been simplified
to contain only _ or a head constructor
| p1, r1...
| p2, r2...
| p3, r3...
| ...
We split this matrix into a list of /specialized/ sub-matrices, one for
each head constructor appearing in the first column. For each row whose
first column starts with a head constructor, remove this head
column, prepend one column for each argument of the constructor,
and add the resulting row in the sub-matrix corresponding to this
head constructor.
Rows whose left column is omega (the Any pattern _) may match any
head constructor, so they are added to all sub-matrices.
In the case where all the rows in the matrix have an omega on their first
column, then there is only one /specialized/ sub-matrix, formed of all these
omega rows.
This matrix is also called the /default/ matrix.
See the documentation of [build_specialized_submatrix] for an explanation of
the [extend_row] parameter.
*)
let build_specialized_submatrices ~extend_row discr rows =
let extend_group discr p args r rs =
let r = extend_row (simple_match_args discr p args) r in
(discr, r :: rs)
in
(* insert a row of head [p] and rest [r] into the right group
Note: with this implementation, the order of the groups
is the order of their first row in the source order.
This is a nice property to get exhaustivity counter-examples
in source order.
*)
let rec insert_constr head args r = function
| [] ->
(* if no group matched this row, it has a head constructor that
was never seen before; add a new sub-matrix for this head *)
[extend_group head head args r []]
| (q0,rs) as bd::env ->
if simple_match q0 head
then extend_group q0 head args r rs :: env
else bd :: insert_constr head args r env
in
(* insert a row of head omega into all groups *)
let insert_omega r env =
List.map (fun (q0,rs) -> extend_group q0 Patterns.Head.omega [] r rs) env
in
let rec form_groups constr_groups omega_tails = function
| [] -> (constr_groups, omega_tails)
| ((head, args), tail) :: rest ->
match head.pat_desc with
| Patterns.Head.Any ->
(* note that calling insert_omega here would be wrong
as some groups may not have been formed yet, if the
first row with this head pattern comes after in the list *)
form_groups constr_groups (tail :: omega_tails) rest
| _ ->
form_groups
(insert_constr head args tail constr_groups) omega_tails rest
in
let constr_groups, omega_tails =
let initial_constr_group =
let open Patterns.Head in
match discr.pat_desc with
| Record _ | Tuple _ | Lazy ->
(* [discr] comes from [discr_pat], and in this case subsumes any of the
patterns we could find on the first column of [rows]. So it is better
to use it for our initial environment than any of the normalized
pattern we might obtain from the first column. *)
[discr,[]]
| _ -> []
in
form_groups initial_constr_group [] rows
in
(* groups are accumulated in reverse order;
we restore the order of rows in the source code *)
let default = List.rev omega_tails in
let constrs =
List.fold_right insert_omega omega_tails constr_groups
|> List.map (fun (discr, rs) -> (discr, List.rev rs))
in
{ default; constrs; }
(* Variant related functions *)
let set_last a =
let rec loop = function
| [] -> assert false
| [_] -> [Patterns.General.erase a]
| x::l -> x :: loop l
in
function
| (_, []) -> (Patterns.Head.deconstruct a, [])
| (first, row) -> (first, loop row)
(* mark constructor lines for failure when they are incomplete *)
let mark_partial =
let zero = make_pat (`Constant (Const_int 0)) Ctype.none Env.empty in
List.map (fun ((hp, _), _ as ps) ->
match hp.pat_desc with
| Patterns.Head.Any -> ps
| _ -> set_last zero ps
)
let close_variant env row =
let Row {fields; more; name=orig_name; closed; fixed} = row_repr row in
let name, static =
List.fold_left
(fun (nm, static) (_tag,f) ->
match row_field_repr f with
| Reither(_, _, false) ->
(* fixed=false means that this tag is not explicitly matched *)
link_row_field_ext ~inside:f rf_absent;
(None, static)
| Reither (_, _, true) -> (nm, false)
| Rabsent | Rpresent _ -> (nm, static))
(orig_name, true) fields in
if not closed || name != orig_name then begin
let more' = if static then Btype.newgenty Tnil else Btype.newgenvar () in
(* this unification cannot fail *)
Ctype.unify env more
(Btype.newgenty
(Tvariant
(create_row ~fields:[] ~more:more'
~closed:true ~name ~fixed)))
end
(*
Check whether the first column of env makes up a complete signature or
not. We work on the discriminating pattern heads of each sub-matrix: they
are not omega/Any.
*)
let full_match closing env = match env with
| [] -> false
| (discr, _) :: _ ->
let open Patterns.Head in
match discr.pat_desc with
| Any -> assert false
| Construct { cstr_tag = Cstr_extension _ ; _ } -> false
| Construct c -> List.length env = c.cstr_consts + c.cstr_nonconsts
| Variant { type_row; _ } ->
let fields =
List.map
(fun (d, _) ->
match d.pat_desc with
| Variant { tag } -> tag
| _ -> assert false)
env
in
let row = type_row () in
if closing && not (Btype.has_fixed_explanation row) then
(* closing=true, we are considering the variant as closed *)
List.for_all
(fun (tag,f) ->
match row_field_repr f with
Rabsent | Reither(_, _, false) -> true
| Reither (_, _, true)
(* m=true, do not discard matched tags, rather warn *)
| Rpresent _ -> List.mem tag fields)
(row_fields row)
else
row_closed row &&
List.for_all
(fun (tag,f) ->
row_field_repr f = Rabsent || List.mem tag fields)
(row_fields row)
| Constant Const_char _ ->
List.length env = 256
| Constant _
| Array _ -> false
| Tuple _
| Record _
| Lazy -> true
(* Written as a non-fragile matching, PR#7451 originated from a fragile matching
below. *)
let should_extend ext env = match ext with
| None -> false
| Some ext -> begin match env with
| [] -> assert false
| (p,_)::_ ->
let open Patterns.Head in
begin match p.pat_desc with
| Construct {cstr_tag=(Cstr_constant _|Cstr_block _|Cstr_unboxed)} ->
let path = get_constructor_type_path p.pat_type p.pat_env in
Path.same path ext
| Construct {cstr_tag=(Cstr_extension _)} -> false
| Constant _ | Tuple _ | Variant _ | Record _ | Array _ | Lazy -> false
| Any -> assert false
end
end
(* build a pattern from a constructor description *)
let pat_of_constr ex_pat cstr =
{ex_pat with pat_desc =
Tpat_construct (mknoloc (Longident.Lident cstr.cstr_name),
cstr, omegas cstr.cstr_arity, None)}
let orify x y = make_pat (Tpat_or (x, y, None)) x.pat_type x.pat_env
let rec orify_many = function
| [] -> assert false
| [x] -> x
| x :: xs -> orify x (orify_many xs)
(* build an or-pattern from a constructor list *)
let pat_of_constrs ex_pat cstrs =
let ex_pat = Patterns.Head.to_omega_pattern ex_pat in
if cstrs = [] then raise Empty else
orify_many (List.map (pat_of_constr ex_pat) cstrs)
let pats_of_type env ty =
match Ctype.extract_concrete_typedecl env ty with
| Typedecl (_, path, {type_kind = Type_variant _ | Type_record _}) ->
begin match Env.find_type_descrs path env with
| Type_variant (cstrs,_) when List.length cstrs <= 1 ||
(* Only explode when all constructors are GADTs *)
List.for_all (fun cd -> cd.cstr_generalized) cstrs ->
List.map (pat_of_constr (make_pat Tpat_any ty env)) cstrs
| Type_record (labels, _) ->
let fields =
List.map (fun ld ->
mknoloc (Longident.Lident ld.lbl_name), ld, omega)
labels
in
[make_pat (Tpat_record (fields, Closed)) ty env]
| _ -> [omega]
end
| Has_no_typedecl ->
begin match get_desc (Ctype.expand_head env ty) with
Ttuple tl ->
[make_pat (Tpat_tuple (omegas (List.length tl))) ty env]
| _ -> [omega]
end
| Typedecl (_, _, {type_kind = Type_abstract _ | Type_open})
| May_have_typedecl -> [omega]
let get_variant_constructors env ty =
match Ctype.extract_concrete_typedecl env ty with
| Typedecl (_, path, {type_kind = Type_variant _}) ->
begin match Env.find_type_descrs path env with
| Type_variant (cstrs,_) -> cstrs
| _ -> fatal_error "Parmatch.get_variant_constructors"
end
| _ -> fatal_error "Parmatch.get_variant_constructors"
module ConstructorSet = Set.Make(struct
type t = constructor_description
let compare c1 c2 = String.compare c1.cstr_name c2.cstr_name
end)
(* Sends back a pattern that complements the given constructors used_constrs *)
let complete_constrs constr used_constrs =
let c = constr.pat_desc in
let constrs = get_variant_constructors constr.pat_env c.cstr_res in
let used_constrs = ConstructorSet.of_list used_constrs in
let others =
List.filter
(fun cnstr -> not (ConstructorSet.mem cnstr used_constrs))
constrs in
(* Split constructors to put constant ones first *)
let const, nonconst =
List.partition (fun cnstr -> cnstr.cstr_arity = 0) others in
const @ nonconst
let build_other_constrs env p =
let open Patterns.Head in
match p.pat_desc with
| Construct ({ cstr_tag = Cstr_extension _ }) -> extra_pat
| Construct
({ cstr_tag = Cstr_constant _ | Cstr_block _ | Cstr_unboxed } as c) ->
let constr = { p with pat_desc = c } in
let get_constr q =
match q.pat_desc with
| Construct c -> c
| _ -> fatal_error "Parmatch.get_constr" in
let used_constrs = List.map (fun (p,_) -> get_constr p) env in
pat_of_constrs p (complete_constrs constr used_constrs)
| _ -> extra_pat
(* Auxiliary for build_other *)
let build_other_constant proj make first next p env =
let all = List.map (fun (p, _) -> proj p.pat_desc) env in
let rec try_const i =
if List.mem i all
then try_const (next i)
else make_pat (make i) p.pat_type p.pat_env
in try_const first
(*
Builds a pattern that is incompatible with all patterns in
the first column of env
*)
let some_private_tag = "<some private tag>"
let build_other ext env =
match env with
| [] -> omega
| (d, _) :: _ ->
let open Patterns.Head in
match d.pat_desc with
| Construct { cstr_tag = Cstr_extension _ } ->
(* let c = {c with cstr_name = "*extension*"} in *) (* PR#7330 *)
make_pat
(Tpat_var (Ident.create_local "*extension*",
{txt="*extension*"; loc = d.pat_loc},
Uid.internal_not_actually_unique))
Ctype.none Env.empty
| Construct _ ->
begin match ext with
| Some ext ->
if Path.same ext (get_constructor_type_path d.pat_type d.pat_env)
then
extra_pat
else
build_other_constrs env d
| _ ->
build_other_constrs env d
end
| Variant { cstr_row; type_row } ->
let tags =
List.map
(fun (d, _) ->
match d.pat_desc with
| Variant { tag } -> tag
| _ -> assert false)
env
in
let make_other_pat tag const =
let arg = if const then None else Some Patterns.omega in
make_pat (Tpat_variant(tag, arg, cstr_row)) d.pat_type d.pat_env
in
let row = type_row () in
begin match
List.fold_left
(fun others (tag,f) ->
if List.mem tag tags then others else
match row_field_repr f with
Rabsent (* | Reither _ *) -> others
(* This one is called after erasing pattern info *)
| Reither (c, _, _) -> make_other_pat tag c :: others
| Rpresent arg -> make_other_pat tag (arg = None) :: others)
[] (row_fields row)
with
[] ->
let tag =
if Btype.has_fixed_explanation row then some_private_tag else
let rec mktag tag =
if List.mem tag tags then mktag (tag ^ "'") else tag in
mktag "AnyOtherTag"
in make_other_pat tag true
| pat::other_pats ->
List.fold_left
(fun p_res pat ->
make_pat (Tpat_or (pat, p_res, None)) d.pat_type d.pat_env)
pat other_pats
end
| Constant Const_char _ ->
let all_chars =
List.map
(fun (p,_) -> match p.pat_desc with
| Constant (Const_char c) -> c
| _ -> assert false)
env
in
let rec find_other i imax =
if i > imax then raise Not_found
else
let ci = Char.chr i in
if List.mem ci all_chars then
find_other (i+1) imax
else
make_pat (Tpat_constant (Const_char ci)) d.pat_type d.pat_env
in
let rec try_chars = function
| [] -> Patterns.omega
| (c1,c2) :: rest ->
try
find_other (Char.code c1) (Char.code c2)