1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401(**************************************************************************)(* *)(* 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. *)openMiscopenAsttypesopenTypesopenTypedtreetype'patternparmatch_case={pattern:'pattern;has_guard:bool;needs_refute:bool;}lettyped_case{c_lhs;c_guard;c_rhs}={pattern=c_lhs;has_guard=Option.is_somec_guard;needs_refute=(c_rhs.exp_desc=Texp_unreachable);}letuntyped_case{Parsetree.pc_lhs;pc_guard;pc_rhs}={pattern=pc_lhs;has_guard=Option.is_somepc_guard;needs_refute=(pc_rhs.pexp_desc=Parsetree.Pexp_unreachable);}(*************************************)(* Utilities for building patterns *)(*************************************)letmake_patdesctytenv={pat_desc=desc;pat_loc=Location.none;pat_extra=[];pat_type=ty;pat_env=tenv;pat_attributes=[];}letomega=Patterns.omegaletomegas=Patterns.omegasletomega_list=Patterns.omega_listletextra_pat=make_pat(Tpat_var(Ident.create_local"+",mknoloc"+",Uid.internal_not_actually_unique))Ctype.noneEnv.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.
*)letall_coherentcolumn=letopenPatterns.Headinletcoherent_headshp1hp2=matchhp1.pat_desc,hp2.pat_descwith|Constructc,Constructc'->c.cstr_consts=c'.cstr_consts&&c.cstr_nonconsts=c'.cstr_nonconsts|Constantc1,Constantc2->beginmatchc1,c2with|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_),_->falseend|Tuplel1,Tuplel2->l1=l2|Record(lbl1::_),Record(lbl2::_)->Array.lengthlbl1.lbl_all=Array.lengthlbl2.lbl_all|Any,_|_,Any|Record[],Record[]|Variant_,Variant_|Array_,Array_|Lazy,Lazy->true|_,_->falseinmatchList.find(function|{pat_desc=Any}->false|_->true)columnwith|exceptionNot_found->(* only omegas on the column: the column is coherent. *)true|discr_pat->List.for_all(coherent_headsdiscr_pat)columnletfirst_columnsimplified_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
*)letis_absenttagrow=row_field_repr(get_row_fieldtag!row)=Rabsentletis_absent_patd=matchd.pat_descwith|Patterns.Head.Variant{tag;cstr_row;_}->is_absenttagcstr_row|_->falseletconst_comparexy=matchx,ywith|Const_floatf1,Const_floatf2->Stdlib.compare(float_of_stringf1)(float_of_stringf2)|Const_string(s1,_,_),Const_string(s2,_,_)->String.compares1s2|(Const_int_|Const_char_|Const_string(_,_,_)|Const_float_|Const_int32_|Const_int64_|Const_nativeint_),_->Stdlib.comparexyletrecords_argsl1l2=(* Invariant: fields are already sorted by Typecore.type_label_a_list *)letreccombiner1r2l1l2=matchl1,l2with|[],[]->List.revr1,List.revr2|[],(_,_,p2)::rem2->combine(omega::r1)(p2::r2)[]rem2|(_,_,p1)::rem1,[]->combine(p1::r1)(omega::r2)rem1[]|(_,lbl1,p1)::rem1,(_,lbl2,p2)::rem2->iflbl1.lbl_pos<lbl2.lbl_posthencombine(p1::r1)(omega::r2)rem1l2elseiflbl1.lbl_pos>lbl2.lbl_posthencombine(omega::r1)(p2::r2)l1rem2else(* same label on both sides *)combine(p1::r1)(p2::r2)rem1rem2incombine[][]l1l2moduleCompat(Constr:sigvalequal:Types.constructor_description->Types.constructor_description->boolend)=structletreccompatpq=matchp.pat_desc,q.pat_descwith(* Variables match any value *)|((Tpat_any|Tpat_var_),_)|(_,(Tpat_any|Tpat_var_))->true(* Structural induction *)|Tpat_alias(p,_,_,_),_->compatpq|_,Tpat_alias(q,_,_,_)->compatpq|Tpat_or(p1,p2,_),_->(compatp1q||compatp2q)|_,Tpat_or(q1,q2,_)->(compatpq1||compatpq2)(* Constructors, with special case for extension *)|Tpat_construct(_,c1,ps1,_),Tpat_construct(_,c2,ps2,_)->Constr.equalc1c2&&compatsps1ps2(* More standard stuff *)|Tpat_variant(l1,op1,_),Tpat_variant(l2,op2,_)->l1=l2&&ocompatop1op2|Tpat_constantc1,Tpat_constantc2->const_comparec1c2=0|Tpat_tupleps,Tpat_tupleqs->compatspsqs|Tpat_lazyp,Tpat_lazyq->compatpq|Tpat_record(l1,_),Tpat_record(l2,_)->letps,qs=records_argsl1l2incompatspsqs|Tpat_arrayps,Tpat_arrayqs->List.lengthps=List.lengthqs&&compatspsqs|_,_->falseandocompatopoq=matchop,oqwith|None,None->true|Somep,Someq->compatpq|(None,Some_)|(Some_,None)->falseandcompatspsqs=matchps,qswith|[],[]->true|p::ps,q::qs->compatpq&&compatspsqs|_,_->falseendmoduleSyntacticCompat=Compat(structletequalc1c2=Types.equal_tagc1.cstr_tagc2.cstr_tagend)letcompat=SyntacticCompat.compatandcompats=SyntacticCompat.compats(* Due to (potential) rebinding, two extension constructors
of the same arity type may equal *)exceptionEmpty(* Empty pattern *)(****************************************)(* Utilities for retrieving type paths *)(****************************************)(* May need a clean copy, cf. PR#4745 *)letclean_copyty=ifget_levelty=Btype.generic_levelthentyelseSubst.type_exprSubst.identitytyletget_constructor_type_pathtytenv=letty=Ctype.expand_headtenv(clean_copyty)inmatchget_desctywith|Tconstr(path,_,_)->path|_->assertfalse(****************************)(* Utilities for matching *)(****************************)(* Check top matching *)letsimple_matchdh=letopenPatterns.Headinmatchd.pat_desc,h.pat_descwith|Constructc1,Constructc2->Types.equal_tagc1.cstr_tagc2.cstr_tag|Variant{tag=t1;_},Variant{tag=t2}->t1=t2|Constantc1,Constantc2->const_comparec1c2=0|Lazy,Lazy->true|Record_,Record_->true|Tuplelen1,Tuplelen2|Arraylen1,Arraylen2->len1=len2|_,Any->true|_,_->false(* extract record fields as a whole *)letrecord_argph=letopenPatterns.Headinmatchph.pat_descwith|Any->[]|Recordargs->args|_->fatal_error"Parmatch.as_record"letextract_fieldslblsarg=letget_fieldposarg=matchList.find(fun(lbl,_)->pos=lbl.lbl_pos)argwith|_,p->p|exceptionNot_found->omegainList.map(funlbl->get_fieldlbl.lbl_posarg)lbls(* Build argument list when p2 >= p1, where p1 is a simple pattern *)letsimple_match_argsdiscrheadargs=letopenPatterns.Headinmatchhead.pat_descwith|Constant_->[]|Construct_|Variant_|Tuple_|Array_|Lazy->args|Recordlbls->extract_fields(record_argdiscr)(List.combinelblsargs)|Any->beginmatchdiscr.pat_descwith|Constructcstr->Patterns.omegascstr.cstr_arity|Variant{has_arg=true}|Lazy->[Patterns.omega]|Recordlbls->omega_listlbls|Arraylen|Tuplelen->Patterns.omegaslen|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.
*)letdiscr_patqpss=letopenPatterns.Headinletrecrefine_patacc=function|[]->acc|((head,_),_)::rows->matchhead.pat_descwith|Any->refine_pataccrows|Tuple_|Lazy->head|Recordlbls->(* 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. *)letfields=List.fold_right(funlblr->ifList.exists(funl->l.lbl_pos=lbl.lbl_pos)rthenrelselbl::r)lbls(record_argacc)inletd={headwithpat_desc=Recordfields}inrefine_patdrows|_->accinletq,_=deconstructqinmatchq.pat_descwith(* 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_patqpss|_->q(*
In case a matching value is found, set actual arguments
of the matching pattern.
*)letrecread_argsxsr=matchxs,rwith|[],_->[],r|_::xs,arg::rest->letargs,rest=read_argsxsrestinarg::args,rest|_,_->fatal_error"Parmatch.read_args"letset_argsqr=matchqwith|{pat_desc=Tpat_tupleomegas}->letargs,rest=read_argsomegasrinmake_pat(Tpat_tupleargs)q.pat_typeq.pat_env::rest|{pat_desc=Tpat_record(omegas,closed)}->letargs,rest=read_argsomegasrinletargs=List.map2(fun(lid,lbl,_)arg->(lid,lbl,arg))omegasargsinmake_pat(Tpat_record(args,closed))q.pat_typeq.pat_env::rest|{pat_desc=Tpat_construct(lid,c,omegas,_)}->letargs,rest=read_argsomegasrinmake_pat(Tpat_construct(lid,c,args,None))q.pat_typeq.pat_env::rest|{pat_desc=Tpat_variant(l,omega,row)}->letarg,rest=matchomega,rwithSome_,a::r->Somea,r|None,r->None,r|_->assertfalseinmake_pat(Tpat_variant(l,arg,row))q.pat_typeq.pat_env::rest|{pat_desc=Tpat_lazy_omega}->beginmatchrwitharg::rest->make_pat(Tpat_lazyarg)q.pat_typeq.pat_env::rest|_->fatal_error"Parmatch.do_set_args (lazy)"end|{pat_desc=Tpat_arrayomegas}->letargs,rest=read_argsomegasrinmake_pat(Tpat_arrayargs)q.pat_typeq.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)
*)letsimplify_head_pat~add_columnppsk=letrecsimplify_head_patppsk=matchPatterns.General.(viewp|>strip_vars).pat_descwith|`Or(p1,p2,_)->simplify_head_patp1ps(simplify_head_patp2psk)|#Patterns.Simple.viewasview->add_column(Patterns.Head.deconstruct{pwithpat_desc=view})pskinsimplify_head_patppskletrecsimplify_first_col=function|[]->[]|[]::_->assertfalse(* the rows are non-empty! *)|(p::ps)::rows->letadd_columnppsk=(p,ps)::kinsimplify_head_pat~add_columnpps(simplify_first_colrows)(* 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.
*)letbuild_specialized_submatrix~extend_rowdiscrpss=letrecfilter_rec=function|((head,args),ps)::pss->ifsimple_matchdiscrheadthenextend_row(simple_match_argsdiscrheadargs)ps::filter_recpsselsefilter_recpss|_->[]infilter_recpss(* The "default" and "specialized" matrices of a given matrix.
See section 3.1 of http://moscova.inria.fr/~maranget/papers/warn/warn.pdf .
*)type'matrixspecialized_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.
*)letbuild_specialized_submatrices~extend_rowdiscrrows=letextend_groupdiscrpargsrrs=letr=extend_row(simple_match_argsdiscrpargs)rin(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.
*)letrecinsert_constrheadargsr=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_groupheadheadargsr[]]|(q0,rs)asbd::env->ifsimple_matchq0headthenextend_groupq0headargsrrs::envelsebd::insert_constrheadargsrenvin(* insert a row of head omega into all groups *)letinsert_omegarenv=List.map(fun(q0,rs)->extend_groupq0Patterns.Head.omega[]rrs)envinletrecform_groupsconstr_groupsomega_tails=function|[]->(constr_groups,omega_tails)|((head,args),tail)::rest->matchhead.pat_descwith|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_groupsconstr_groups(tail::omega_tails)rest|_->form_groups(insert_constrheadargstailconstr_groups)omega_tailsrestinletconstr_groups,omega_tails=letinitial_constr_group=letopenPatterns.Headinmatchdiscr.pat_descwith|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,[]]|_->[]inform_groupsinitial_constr_group[]rowsin(* groups are accumulated in reverse order;
we restore the order of rows in the source code *)letdefault=List.revomega_tailsinletconstrs=List.fold_rightinsert_omegaomega_tailsconstr_groups|>List.map(fun(discr,rs)->(discr,List.revrs))in{default;constrs;}(* Variant related functions *)letset_lasta=letrecloop=function|[]->assertfalse|[_]->[Patterns.General.erasea]|x::l->x::looplinfunction|(_,[])->(Patterns.Head.deconstructa,[])|(first,row)->(first,looprow)(* mark constructor lines for failure when they are incomplete *)letmark_partial=letzero=make_pat(`Constant(Const_int0))Ctype.noneEnv.emptyinList.map(fun((hp,_),_asps)->matchhp.pat_descwith|Patterns.Head.Any->ps|_->set_lastzerops)letclose_variantenvrow=letRow{fields;more;name=orig_name;closed;fixed}=row_reprrowinletname,static=List.fold_left(fun(nm,static)(_tag,f)->matchrow_field_reprfwith|Reither(_,_,false)->(* fixed=false means that this tag is not explicitly matched *)link_row_field_ext~inside:frf_absent;(None,static)|Reither(_,_,true)->(nm,false)|Rabsent|Rpresent_->(nm,static))(orig_name,true)fieldsinifnotclosed||name!=orig_namethenbeginletmore'=ifstaticthenBtype.newgentyTnilelseBtype.newgenvar()in(* this unification cannot fail *)Ctype.unifyenvmore(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.
*)letfull_matchclosingenv=matchenvwith|[]->false|(discr,_)::_->letopenPatterns.Headinmatchdiscr.pat_descwith|Any->assertfalse|Construct{cstr_tag=Cstr_extension_;_}->false|Constructc->List.lengthenv=c.cstr_consts+c.cstr_nonconsts|Variant{type_row;_}->letfields=List.map(fun(d,_)->matchd.pat_descwith|Variant{tag}->tag|_->assertfalse)envinletrow=type_row()inifclosing&¬(Btype.has_fixed_explanationrow)then(* closing=true, we are considering the variant as closed *)List.for_all(fun(tag,f)->matchrow_field_reprfwithRabsent|Reither(_,_,false)->true|Reither(_,_,true)(* m=true, do not discard matched tags, rather warn *)|Rpresent_->List.memtagfields)(row_fieldsrow)elserow_closedrow&&List.for_all(fun(tag,f)->row_field_reprf=Rabsent||List.memtagfields)(row_fieldsrow)|ConstantConst_char_->List.lengthenv=256|Constant_|Array_->false|Tuple_|Record_|Lazy->true(* Written as a non-fragile matching, PR#7451 originated from a fragile matching
below. *)letshould_extendextenv=matchextwith|None->false|Someext->beginmatchenvwith|[]->assertfalse|(p,_)::_->letopenPatterns.Headinbeginmatchp.pat_descwith|Construct{cstr_tag=(Cstr_constant_|Cstr_block_|Cstr_unboxed)}->letpath=get_constructor_type_pathp.pat_typep.pat_envinPath.samepathext|Construct{cstr_tag=(Cstr_extension_)}->false|Constant_|Tuple_|Variant_|Record_|Array_|Lazy->false|Any->assertfalseendend(* build a pattern from a constructor description *)letpat_of_constrex_patcstr={ex_patwithpat_desc=Tpat_construct(mknoloc(Longident.Lidentcstr.cstr_name),cstr,omegascstr.cstr_arity,None)}letorifyxy=make_pat(Tpat_or(x,y,None))x.pat_typex.pat_envletrecorify_many=function|[]->assertfalse|[x]->x|x::xs->orifyx(orify_manyxs)(* build an or-pattern from a constructor list *)letpat_of_constrsex_patcstrs=letex_pat=Patterns.Head.to_omega_patternex_patinifcstrs=[]thenraiseEmptyelseorify_many(List.map(pat_of_constrex_pat)cstrs)letpats_of_typeenvty=matchCtype.extract_concrete_typedeclenvtywith|Typedecl(_,path,{type_kind=Type_variant_|Type_record_})->beginmatchEnv.find_type_descrspathenvwith|Type_variant(cstrs,_)whenList.lengthcstrs<=1||(* Only explode when all constructors are GADTs *)List.for_all(funcd->cd.cstr_generalized)cstrs->List.map(pat_of_constr(make_patTpat_anytyenv))cstrs|Type_record(labels,_)->letfields=List.map(funld->mknoloc(Longident.Lidentld.lbl_name),ld,omega)labelsin[make_pat(Tpat_record(fields,Closed))tyenv]|_->[omega]end|Has_no_typedecl->beginmatchget_desc(Ctype.expand_headenvty)withTtupletl->[make_pat(Tpat_tuple(omegas(List.lengthtl)))tyenv]|_->[omega]end|Typedecl(_,_,{type_kind=Type_abstract_|Type_open})|May_have_typedecl->[omega]letget_variant_constructorsenvty=matchCtype.extract_concrete_typedeclenvtywith|Typedecl(_,path,{type_kind=Type_variant_})->beginmatchEnv.find_type_descrspathenvwith|Type_variant(cstrs,_)->cstrs|_->fatal_error"Parmatch.get_variant_constructors"end|_->fatal_error"Parmatch.get_variant_constructors"moduleConstructorSet=Set.Make(structtypet=constructor_descriptionletcomparec1c2=String.comparec1.cstr_namec2.cstr_nameend)(* Sends back a pattern that complements the given constructors used_constrs *)letcomplete_constrsconstrused_constrs=letc=constr.pat_descinletconstrs=get_variant_constructorsconstr.pat_envc.cstr_resinletused_constrs=ConstructorSet.of_listused_constrsinletothers=List.filter(funcnstr->not(ConstructorSet.memcnstrused_constrs))constrsin(* Split constructors to put constant ones first *)letconst,nonconst=List.partition(funcnstr->cnstr.cstr_arity=0)othersinconst@nonconstletbuild_other_constrsenvp=letopenPatterns.Headinmatchp.pat_descwith|Construct({cstr_tag=Cstr_extension_})->extra_pat|Construct({cstr_tag=Cstr_constant_|Cstr_block_|Cstr_unboxed}asc)->letconstr={pwithpat_desc=c}inletget_constrq=matchq.pat_descwith|Constructc->c|_->fatal_error"Parmatch.get_constr"inletused_constrs=List.map(fun(p,_)->get_constrp)envinpat_of_constrsp(complete_constrsconstrused_constrs)|_->extra_pat(* Auxiliary for build_other *)letbuild_other_constantprojmakefirstnextpenv=letall=List.map(fun(p,_)->projp.pat_desc)envinletrectry_consti=ifList.memiallthentry_const(nexti)elsemake_pat(makei)p.pat_typep.pat_envintry_constfirst(*
Builds a pattern that is incompatible with all patterns in
the first column of env
*)letsome_private_tag="<some private tag>"letbuild_otherextenv=matchenvwith|[]->omega|(d,_)::_->letopenPatterns.Headinmatchd.pat_descwith|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.noneEnv.empty|Construct_->beginmatchextwith|Someext->ifPath.sameext(get_constructor_type_pathd.pat_typed.pat_env)thenextra_patelsebuild_other_constrsenvd|_->build_other_constrsenvdend|Variant{cstr_row;type_row}->lettags=List.map(fun(d,_)->matchd.pat_descwith|Variant{tag}->tag|_->assertfalse)envinletmake_other_pattagconst=letarg=ifconstthenNoneelseSomePatterns.omegainmake_pat(Tpat_variant(tag,arg,cstr_row))d.pat_typed.pat_envinletrow=type_row()inbeginmatchList.fold_left(funothers(tag,f)->ifList.memtagtagsthenotherselsematchrow_field_reprfwithRabsent(* | Reither _ *)->others(* This one is called after erasing pattern info *)|Reither(c,_,_)->make_other_pattagc::others|Rpresentarg->make_other_pattag(arg=None)::others)[](row_fieldsrow)with[]->lettag=ifBtype.has_fixed_explanationrowthensome_private_tagelseletrecmktagtag=ifList.memtagtagsthenmktag(tag^"'")elsetaginmktag"AnyOtherTag"inmake_other_pattagtrue|pat::other_pats->List.fold_left(funp_respat->make_pat(Tpat_or(pat,p_res,None))d.pat_typed.pat_env)patother_patsend|ConstantConst_char_->letall_chars=List.map(fun(p,_)->matchp.pat_descwith|Constant(Const_charc)->c|_->assertfalse)envinletrecfind_otheriimax=ifi>imaxthenraiseNot_foundelseletci=Char.chriinifList.memciall_charsthenfind_other(i+1)imaxelsemake_pat(Tpat_constant(Const_charci))d.pat_typed.pat_envinletrectry_chars=function|[]->Patterns.omega|(c1,c2)::rest->tryfind_other(Char.codec1)(Char.codec2)with|Not_found->try_charsrestintry_chars['a','z';'A','Z';'0','9';' ','~';Char.chr0,Char.chr255]|ConstantConst_int_->build_other_constant(functionConstant(Const_inti)->i|_->assertfalse)(functioni->Tpat_constant(Const_inti))0succdenv|ConstantConst_int32_->build_other_constant(functionConstant(Const_int32i)->i|_->assertfalse)(functioni->Tpat_constant(Const_int32i))0lInt32.succdenv|ConstantConst_int64_->build_other_constant(functionConstant(Const_int64i)->i|_->assertfalse)(functioni->Tpat_constant(Const_int64i))0LInt64.succdenv|ConstantConst_nativeint_->build_other_constant(functionConstant(Const_nativeinti)->i|_->assertfalse)(functioni->Tpat_constant(Const_nativeinti))0nNativeint.succdenv|ConstantConst_string_->build_other_constant(functionConstant(Const_string(s,_,_))->String.lengths|_->assertfalse)(functioni->Tpat_constant(Const_string(String.makei'*',Location.none,None)))0succdenv|ConstantConst_float_->build_other_constant(functionConstant(Const_floatf)->float_of_stringf|_->assertfalse)(functionf->Tpat_constant(Const_float(string_of_floatf)))0.0(funf->f+.1.0)denv|Array_->letall_lengths=List.map(fun(p,_)->matchp.pat_descwith|Arraylen->len|_->assertfalse)envinletrectry_arraysl=ifList.memlall_lengthsthentry_arrays(l+1)elsemake_pat(Tpat_array(omegasl))d.pat_typed.pat_envintry_arrays0|_->Patterns.omegaletrechas_instancep=matchp.pat_descwith|Tpat_variant(l,_,r)whenis_absentlr->false|Tpat_any|Tpat_var_|Tpat_constant_|Tpat_variant(_,None,_)->true|Tpat_alias(p,_,_,_)|Tpat_variant(_,Somep,_)->has_instancep|Tpat_or(p1,p2,_)->has_instancep1||has_instancep2|Tpat_construct(_,_,ps,_)|Tpat_tupleps|Tpat_arrayps->has_instancesps|Tpat_record(lps,_)->has_instances(List.map(fun(_,_,x)->x)lps)|Tpat_lazyp->has_instancepandhas_instances=function|[]->true|q::rem->has_instanceq&&has_instancesrem(*
Core function :
Is the last row of pattern matrix pss + qs satisfiable ?
That is :
Does there exists at least one value vector, es such that :
1- for all ps in pss ps # es (ps and es are not compatible)
2- qs <= es (es matches qs)
---
In two places in the following function, we check the coherence of the first
column of (pss + qs).
If it is incoherent, then we exit early saying that (pss + qs) is not
satisfiable (which is equivalent to saying "oh, we shouldn't have considered
that branch, no good result came come from here").
But what happens if we have a coherent but ill-typed column?
- we might end up returning [false], which is equivalent to noticing the
incompatibility: clearly this is fine.
- if we end up returning [true] then we're saying that [qs] is useful while
it is not. This is sad but not the end of the world, we're just allowing dead
code to survive.
*)letrecsatisfiablepssqs=matchpsswith|[]->has_instancesqs|_->matchqswith|[]->false|q::qs->matchPatterns.General.(viewq|>strip_vars).pat_descwith|`Or(q1,q2,_)->satisfiablepss(q1::qs)||satisfiablepss(q2::qs)|`Any->letpss=simplify_first_colpssinifnot(all_coherent(first_columnpss))thenfalseelsebeginlet{default;constrs}=letq0=discr_patPatterns.Simple.omegapssinbuild_specialized_submatrices~extend_row:(@)q0pssinifnot(full_matchfalseconstrs)thensatisfiabledefaultqselseList.exists(fun(p,pss)->not(is_absent_patp)&&satisfiablepss(simple_match_argspPatterns.Head.omega[]@qs))constrsend|`Variant(l,_,r)whenis_absentlr->false|#Patterns.Simple.viewasview->letq={qwithpat_desc=view}inletpss=simplify_first_colpssinlethq,qargs=Patterns.Head.deconstructqinifnot(all_coherent(hq::first_columnpss))thenfalseelsebeginletq0=discr_patqpssinsatisfiable(build_specialized_submatrix~extend_row:(@)q0pss)(simple_match_argsq0hqqargs@qs)end(* While [satisfiable] only checks whether the last row of [pss + qs] is
satisfiable, this function returns the (possibly empty) list of vectors [es]
which verify:
1- for all ps in pss, ps # es (ps and es are not compatible)
2- qs <= es (es matches qs)
This is done to enable GADT handling
For considerations regarding the coherence check, see the comment on
[satisfiable] above. *)letreclist_satisfying_vectorspssqs=matchpsswith|[]->ifhas_instancesqsthen[qs]else[]|_->matchqswith|[]->[]|q::qs->matchPatterns.General.(viewq|>strip_vars).pat_descwith|`Or(q1,q2,_)->list_satisfying_vectorspss(q1::qs)@list_satisfying_vectorspss(q2::qs)|`Any->letpss=simplify_first_colpssinifnot(all_coherent(first_columnpss))then[]elsebeginletq0=discr_patPatterns.Simple.omegapssinletwilddefault_matrixp=List.map(funqs->p::qs)(list_satisfying_vectorsdefault_matrixqs)inmatchbuild_specialized_submatrices~extend_row:(@)q0psswith|{default;constrs=[]}->(* first column of pss is made of variables only *)wilddefaultomega|{default;constrs=((p,_)::_asconstrs)}->letfor_constrs()=List.flatten(List.map(fun(p,pss)->ifis_absent_patpthen[]elseletwitnesses=list_satisfying_vectorspss(simple_match_argspPatterns.Head.omega[]@qs)inletp=Patterns.Head.to_omega_patternpinList.map(set_argsp)witnesses)constrs)iniffull_matchfalseconstrsthenfor_constrs()elsebeginmatchp.pat_descwith|Construct_->(* activate this code
for checking non-gadt constructors *)wilddefault(build_other_constrsconstrsp)@for_constrs()|_->wilddefaultPatterns.omegaendend|`Variant(l,_,r)whenis_absentlr->[]|#Patterns.Simple.viewasview->letq={qwithpat_desc=view}inlethq,qargs=Patterns.Head.deconstructqinletpss=simplify_first_colpssinifnot(all_coherent(hq::first_columnpss))then[]elsebeginletq0=discr_patqpssinList.map(set_args(Patterns.Head.to_omega_patternq0))(list_satisfying_vectors(build_specialized_submatrix~extend_row:(@)q0pss)(simple_match_argsq0hqqargs@qs))end(******************************************)(* Look for a row that matches some value *)(******************************************)(*
Useful for seeing if the example of
non-matched value can indeed be matched
(by a guarded clause)
*)letrecdo_matchpssqs=matchqswith|[]->beginmatchpsswith|[]::_->true|_->falseend|q::qs->matchPatterns.General.(viewq|>strip_vars).pat_descwith|`Or(q1,q2,_)->do_matchpss(q1::qs)||do_matchpss(q2::qs)|`Any->letrecremove_first_column=function|(_::ps)::rem->ps::remove_first_columnrem|_->[]indo_match(remove_first_columnpss)qs|#Patterns.Simple.viewasview->letq={qwithpat_desc=view}inletq0,qargs=Patterns.Head.deconstructqinletpss=simplify_first_colpssin(* [pss] will (or won't) match [q0 :: qs] regardless of the coherence of
its first column. *)do_match(build_specialized_submatrix~extend_row:(@)q0pss)(qargs@qs)(*
let print_pat pat =
let rec string_of_pat pat =
match pat.pat_desc with
Tpat_var _ -> "v"
| Tpat_any -> "_"
| Tpat_alias (p, x) -> Printf.sprintf "(%s) as ?" (string_of_pat p)
| Tpat_constant n -> "0"
| Tpat_construct (_, lid, _) ->
Printf.sprintf "%s" (String.concat "." (Longident.flatten lid.txt))
| Tpat_lazy p ->
Printf.sprintf "(lazy %s)" (string_of_pat p)
| Tpat_or (p1,p2,_) ->
Printf.sprintf "(%s | %s)" (string_of_pat p1) (string_of_pat p2)
| Tpat_tuple list ->
Printf.sprintf "(%s)" (String.concat "," (List.map string_of_pat list))
| Tpat_variant (_, _, _) -> "variant"
| Tpat_record (_, _) -> "record"
| Tpat_array _ -> "array"
in
Printf.fprintf stderr "PAT[%s]\n%!" (string_of_pat pat)
*)(*
Now another satisfiable function that additionally
supplies an example of a matching value.
This function should be called for exhaustiveness check only.
*)letrecexhaust(ext:Path.toption)pssn=matchpsswith|[]->Seq.return(omegasn)|[]::_->Seq.empty|[(p::ps)]->exhaust_single_rowextppsn|pss->specialize_and_exhaustextpssnandexhaust_single_rowextppsn=(* Shortcut: in the single-row case p :: ps we know that all
counter-examples are either of the form
counter-example(p) :: omegas
or
p :: counter-examples(ps)
This is very interesting in the case where p contains
or-patterns, as the non-shortcut path below would do a separate
search for each constructor of the or-pattern, which can lead to
an exponential blowup on examples such as
| (A|B), (A|B), (A|B), (A|B) -> foo
Note that this shortcut also applies to examples such as
| A, A, A, A -> foo | (A|B), (A|B), (A|B), (A|B) -> bar
thanks to the [get_mins] preprocessing step which will drop the
first row (subsumed by the second). Code with this shape does
occur naturally when people want to avoid fragile pattern
matches: if A and B are the only two constructors, this is the
best way to make a non-fragile distinction between "all As" and
"at least one B".
*)List.to_seq[Somep;None]|>Seq.flat_map(function|Somep->letsub_witnesses=exhaustext[ps](n-1)inSeq.map(funrow->p::row)sub_witnesses|None->(* note: calling [exhaust] recursively of p would
result in an infinite loop in the case n=1 *)letp_witnesses=specialize_and_exhaustext[[p]]1inSeq.map(funp_row->p_row@omegas(n-1))p_witnesses)andspecialize_and_exhaustextpssn=letpss=simplify_first_colpssinifnot(all_coherent(first_columnpss))then(* We're considering an ill-typed branch, we won't actually be able to
produce a well typed value taking that branch. *)Seq.emptyelsebegin(* Assuming the first column is ill-typed but considered coherent, we
might end up producing an ill-typed witness of non-exhaustivity
corresponding to the current branch.
If [exhaust] has been called by [do_check_partial], then the witnesses
produced get typechecked and the ill-typed ones are discarded.
If [exhaust] has been called by [do_check_fragile], then it is possible
we might fail to warn the user that the matching is fragile. See for
example testsuite/tests/warnings/w04_failure.ml. *)letq0=discr_patPatterns.Simple.omegapssinmatchbuild_specialized_submatrices~extend_row:(@)q0psswith|{default;constrs=[]}->(* first column of pss is made of variables only *)letsub_witnesses=exhaustextdefault(n-1)inletq0=Patterns.Head.to_omega_patternq0inSeq.map(funrow->q0::row)sub_witnesses|{default;constrs}->lettry_non_omega(p,pss)=ifis_absent_patpthenSeq.emptyelseletsub_witnesses=exhaustextpss(List.length(simple_match_argspPatterns.Head.omega[])+n-1)inletp=Patterns.Head.to_omega_patternpinSeq.map(set_argsp)sub_witnessesinlettry_omega()=iffull_matchfalseconstrs&¬(should_extendextconstrs)thenSeq.emptyelseletsub_witnesses=exhaustextdefault(n-1)inmatchbuild_otherextconstrswith|exceptionEmpty->(* cannot occur, since constructors don't make
a full signature *)fatal_error"Parmatch.exhaust"|p->Seq.map(funtail->p::tail)sub_witnessesin(* Lazily compute witnesses for all constructor submatrices
(Some constr_mat) then the wildcard/default submatrix (None).
Note that the call to [try_omega ()] is delayed to after
all constructor matrices have been traversed. *)List.map(funconstr_mat->Someconstr_mat)constrs@[None]|>List.to_seq|>Seq.flat_map(function|Someconstr_mat->try_non_omegaconstr_mat|None->try_omega())endletexhaustextpssn=exhaustextpssn|>Seq.map(function|[x]->x|_->assertfalse)(*
Another exhaustiveness check, enforcing variant typing.
Note that it does not check exact exhaustiveness, but whether a
matching could be made exhaustive by closing all variant types.
When this is true of all other columns, the current column is left
open (even if it means that the whole matching is not exhaustive as
a result).
When this is false for the matrix minus the current column, and the
current column is composed of variant tags, we close the variant
(even if it doesn't help in making the matching exhaustive).
*)letrecpressure_variantstdefs=function|[]->false|[]::_->true|pss->letpss=simplify_first_colpssinifnot(all_coherent(first_columnpss))thentrueelsebeginletq0=discr_patPatterns.Simple.omegapssinmatchbuild_specialized_submatrices~extend_row:(@)q0psswith|{default;constrs=[]}->pressure_variantstdefsdefault|{default;constrs}->letrectry_non_omega=function|(_p,pss)::rem->letok=pressure_variantstdefspssin(* The order below matters : we want [pressure_variants] to be
called on all the specialized submatrices because we might
close some variant in any of them regardless of whether [ok]
is true for [pss] or not *)try_non_omegarem&&ok|[]->trueiniffull_match(tdefs=None)constrsthentry_non_omegaconstrselseiftdefs=Nonethenpressure_variantsNonedefaultelseletfull=full_matchtrueconstrsinletok=iffullthentry_non_omegaconstrselsebeginlet{constrs=partial_constrs;_}=build_specialized_submatrices~extend_row:(@)q0(mark_partialpss)intry_non_omegapartial_constrsendinbeginmatchconstrs,tdefswith|[],_|_,None->()|(d,_)::_,Someenv->matchd.pat_descwith|Variant{type_row;_}->letrow=type_row()inifBtype.has_fixed_explanationrow||pressure_variantsNonedefaultthen()elseclose_variantenvrow|_->()end;okend(* Yet another satisfiable function *)(*
This time every_satisfiable pss qs checks the
utility of every expansion of qs.
Expansion means expansion of or-patterns inside qs
*)typeanswer=|Used(* Useful pattern *)|Unused(* Useless pattern *)|UpartialofTypedtree.patternlist(* Mixed, with list of useless ones *)(* this row type enable column processing inside the matrix
- left -> elements not to be processed,
- right -> elements to be processed
*)typeusefulness_row={no_ors:patternlist;ors:patternlist;active:patternlist}(*
let pretty_row {ors=ors ; no_ors=no_ors; active=active} =
pretty_line ors ; prerr_string " *" ;
pretty_line no_ors ; prerr_string " *" ;
pretty_line active
let pretty_rows rs =
prerr_endline "begin matrix" ;
List.iter
(fun r ->
pretty_row r ;
prerr_endline "")
rs ;
prerr_endline "end matrix"
*)(* Initial build *)letmake_rowps={ors=[];no_ors=[];active=ps}letmake_rowspss=List.mapmake_rowpss(* Useful to detect and expand or pats inside as pats *)letis_varp=matchPatterns.General.(viewp|>strip_vars).pat_descwith|`Any->true|_->falseletis_var_columnrs=List.for_all(funr->matchr.activewith|p::_->is_varp|[]->assertfalse)rs(* Standard or-args for left-to-right matching *)letrecor_argsp=matchp.pat_descwith|Tpat_or(p1,p2,_)->p1,p2|Tpat_alias(p,_,_,_)->or_argsp|_->assertfalse(* Just remove current column *)letremover=matchr.activewith|_::rem->{rwithactive=rem}|[]->assertfalseletremove_columnrs=List.mapremovers(* Current column has been processed *)letpush_no_orr=matchr.activewith|p::rem->{rwithno_ors=p::r.no_ors;active=rem}|[]->assertfalseletpush_orr=matchr.activewith|p::rem->{rwithors=p::r.ors;active=rem}|[]->assertfalseletpush_or_columnrs=List.mappush_orrsandpush_no_or_columnrs=List.mappush_no_orrsletrecsimplify_first_usefulness_col=function|[]->[]|row::rows->matchrow.activewith|[]->assertfalse(* the rows are non-empty! *)|p::ps->letadd_columnppsk=(p,{rowwithactive=ps})::kinsimplify_head_pat~add_columnpps(simplify_first_usefulness_colrows)(* Back to normal matrices *)letmake_vectorr=List.revr.no_orsletmake_matrixrs=List.mapmake_vectorrs(* Standard union on answers *)letunion_resr1r2=matchr1,r2with|(Unused,_)|(_,Unused)->Unused|Used,_->r2|_,Used->r1|Upartialu1,Upartialu2->Upartial(u1@u2)(* propose or pats for expansion *)letextract_elementsqs=letrecdo_recseen=function|[]->[]|q::rem->{no_ors=List.rev_appendseenrem@qs.no_ors;ors=[];active=[q]}::do_rec(q::seen)remindo_rec[]qs.ors(* idem for matrices *)lettransposers=matchrswith|[]->assertfalse|r::rem->leti=List.map(funx->[x])rinList.fold_left(List.map2(funrx->x::r))iremletextract_columnspssqs=matchpsswith|[]->List.map(fun_->[])qs.ors|_->letrows=List.mapextract_elementspssintransposerows(* Core function
The idea is to first look for or patterns (recursive case), then
check or-patterns argument usefulness (terminal case)
*)letrecevery_satisfiablespssqs=matchqs.activewith|[]->(* qs is now partitioned, check usefulness *)beginmatchqs.orswith|[]->(* no or-patterns *)ifsatisfiable(make_matrixpss)(make_vectorqs)thenUsedelseUnused|_->(* n or-patterns -> 2n expansions *)List.fold_right2(funpssqsr->matchrwith|Unused->Unused|_->matchqs.activewith|[q]->letq1,q2=or_argsqinletr_loc=every_bothpssqsq1q2inunion_resrr_loc|_->assertfalse)(extract_columnspssqs)(extract_elementsqs)Usedend|q::rem->beginmatchPatterns.General.(viewq|>strip_vars).pat_descwith|`Any->ifis_var_columnpssthen(* forget about ``all-variable'' columns now *)every_satisfiables(remove_columnpss)(removeqs)else(* otherwise this is direct food for satisfiable *)every_satisfiables(push_no_or_columnpss)(push_no_orqs)|`Or(q1,q2,_)->ifq1.pat_loc.Location.loc_ghost&&q2.pat_loc.Location.loc_ghostthen(* syntactically generated or-pats should not be expanded *)every_satisfiables(push_no_or_columnpss)(push_no_orqs)else(* this is a real or-pattern *)every_satisfiables(push_or_columnpss)(push_orqs)|`Variant(l,_,r)whenis_absentlr->(* Ah Jacques... *)Unused|#Patterns.Simple.viewasview->letq={qwithpat_desc=view}in(* standard case, filter matrix *)letpss=simplify_first_usefulness_colpssinlethq,args=Patterns.Head.deconstructqin(* The handling of incoherent matrices is kept in line with
[satisfiable] *)ifnot(all_coherent(hq::first_columnpss))thenUnusedelsebeginletq0=discr_patqpssinevery_satisfiables(build_specialized_submatrixq0pss~extend_row:(funpsr->{rwithactive=ps@r.active})){qswithactive=simple_match_argsq0hqargs@rem}endend(*
This function ``every_both'' performs the usefulness check
of or-pat q1|q2.
The trick is to call every_satisfied twice with
current active columns restricted to q1 and q2,
That way,
- others orpats in qs.ors will not get expanded.
- all matching work performed on qs.no_ors is not performed again.
*)andevery_bothpssqsq1q2=letqs1={qswithactive=[q1]}andqs2={qswithactive=[q2]}inletr1=every_satisfiablespssqs1andr2=every_satisfiables(ifcompatq1q2thenqs1::psselsepss)qs2inmatchr1with|Unused->beginmatchr2with|Unused->Unused|Used->Upartial[q1]|Upartialu2->Upartial(q1::u2)end|Used->beginmatchr2with|Unused->Upartial[q2]|_->r2end|Upartialu1->beginmatchr2with|Unused->Upartial(u1@[q2])|Used->r1|Upartialu2->Upartial(u1@u2)end(* le_pat p q means, forall V, V matches q implies V matches p *)letrecle_patpq=match(p.pat_desc,q.pat_desc)with|(Tpat_var_|Tpat_any),_->true|Tpat_alias(p,_,_,_),_->le_patpq|_,Tpat_alias(q,_,_,_)->le_patpq|Tpat_constant(c1),Tpat_constant(c2)->const_comparec1c2=0|Tpat_construct(_,c1,ps,_),Tpat_construct(_,c2,qs,_)->Types.equal_tagc1.cstr_tagc2.cstr_tag&&le_patspsqs|Tpat_variant(l1,Somep1,_),Tpat_variant(l2,Somep2,_)->(l1=l2&&le_patp1p2)|Tpat_variant(l1,None,_r1),Tpat_variant(l2,None,_)->l1=l2|Tpat_variant(_,_,_),Tpat_variant(_,_,_)->false|Tpat_tuple(ps),Tpat_tuple(qs)->le_patspsqs|Tpat_lazyp,Tpat_lazyq->le_patpq|Tpat_record(l1,_),Tpat_record(l2,_)->letps,qs=records_argsl1l2inle_patspsqs|Tpat_array(ps),Tpat_array(qs)->List.lengthps=List.lengthqs&&le_patspsqs(* In all other cases, enumeration is performed *)|_,_->not(satisfiable[[p]][q])andle_patspsqs=matchps,qswithp::ps,q::qs->le_patpq&&le_patspsqs|_,_->trueletget_minsleps=letrecselect_recr=function[]->r|p::ps->ifList.exists(funp0->lep0p)psthenselect_recrpselseselect_rec(p::r)psin(* [select_rec] removes the elements that are followed by a smaller element.
An element that is preceded by a smaller element may stay in the list.
We thus do two passes on the list, which is returned reversed
the first time. *)select_rec[](select_rec[]ps)(*
lub p q is a pattern that matches all values matched by p and q
may raise Empty, when p and q are not compatible
*)letreclubpq=matchp.pat_desc,q.pat_descwith|Tpat_alias(p,_,_,_),_->lubpq|_,Tpat_alias(q,_,_,_)->lubpq|(Tpat_any|Tpat_var_),_->q|_,(Tpat_any|Tpat_var_)->p|Tpat_or(p1,p2,_),_->orlubp1p2q|_,Tpat_or(q1,q2,_)->orlubq1q2p(* Thanks god, lub is commutative *)|Tpat_constantc1,Tpat_constantc2whenconst_comparec1c2=0->p|Tpat_tupleps,Tpat_tupleqs->letrs=lubspsqsinmake_pat(Tpat_tuplers)p.pat_typep.pat_env|Tpat_lazyp,Tpat_lazyq->letr=lubpqinmake_pat(Tpat_lazyr)p.pat_typep.pat_env|Tpat_construct(lid,c1,ps1,_),Tpat_construct(_,c2,ps2,_)whenTypes.equal_tagc1.cstr_tagc2.cstr_tag->letrs=lubsps1ps2inmake_pat(Tpat_construct(lid,c1,rs,None))p.pat_typep.pat_env|Tpat_variant(l1,Somep1,row),Tpat_variant(l2,Somep2,_)whenl1=l2->letr=lubp1p2inmake_pat(Tpat_variant(l1,Somer,row))p.pat_typep.pat_env|Tpat_variant(l1,None,_row),Tpat_variant(l2,None,_)whenl1=l2->p|Tpat_record(l1,closed),Tpat_record(l2,_)->letrs=record_lubsl1l2inmake_pat(Tpat_record(rs,closed))p.pat_typep.pat_env|Tpat_arrayps,Tpat_arrayqswhenList.lengthps=List.lengthqs->letrs=lubspsqsinmake_pat(Tpat_arrayrs)p.pat_typep.pat_env|_,_->raiseEmptyandorlubp1p2q=tryletr1=lubp1qintry{qwithpat_desc=(Tpat_or(r1,lubp2q,None))}with|Empty->r1with|Empty->lubp2qandrecord_lubsl1l2=letreclub_recl1l2=matchl1,l2with|[],_->l2|_,[]->l1|(lid1,lbl1,p1)::rem1,(lid2,lbl2,p2)::rem2->iflbl1.lbl_pos<lbl2.lbl_posthen(lid1,lbl1,p1)::lub_recrem1l2elseiflbl2.lbl_pos<lbl1.lbl_posthen(lid2,lbl2,p2)::lub_recl1rem2else(lid1,lbl1,lubp1p2)::lub_recrem1rem2inlub_recl1l2andlubspsqs=matchps,qswith|p::ps,q::qs->lubpq::lubspsqs|_,_->[](******************************)(* Exported variant closing *)(******************************)(* Apply pressure to variants *)letpressure_variantstdefspatl=ignore(pressure_variants(Sometdefs)(List.map(funp->[p;omega])patl))letpressure_variants_in_computation_patterntdefspatl=letadd_rowpssp_opt=matchp_optwith|None->pss|Somep->p::pssinletval_pss,exn_pss=List.fold_right(funpat(vpss,epss)->let(vp,ep)=split_patternpatinadd_rowvpssvp,add_rowepssep)patl([],[])inpressure_variantstdefsval_pss;pressure_variantstdefsexn_pss(*****************************)(* Utilities for diagnostics *)(*****************************)(*
Build up a working pattern matrix by forgetting
about guarded patterns
*)letrecinitial_matrix=function[]->[]|{has_guard=true}::rem->initial_matrixrem|{has_guard=false;pattern=p}::rem->[p]::initial_matrixrem(*
Build up a working pattern matrix by keeping
only the patterns which are guarded
*)letrecinitial_only_guarded=function|[]->[]|{has_guard=false;_}::rem->initial_only_guardedrem|{pattern=pat;_}::rem->[pat]::initial_only_guardedrem(************************)(* Exhaustiveness check *)(************************)(* Whether the counter-example contains an extension pattern *)letcontains_extensionpat=exists_pattern(function|{pat_desc=Tpat_var(_,{txt="*extension*"},_)}->true|_->false)patletdo_check_partial~predloccaselpss=matchpsswith|[]->(*
This can occur
- For empty matches generated by ocamlp4 (no warning)
- when all patterns have guards (then, casel <> [])
(specific warning)
Then match MUST be considered non-exhaustive,
otherwise compilation of PM is broken.
*)beginmatchcaselwith|[]->()|_->ifWarnings.is_activeWarnings.All_clauses_guardedthenLocation.prerr_warninglocWarnings.All_clauses_guardedend;Partial|ps::_->letcounter_examples=exhaustNonepss(List.lengthps)|>Seq.filter_mappredinmatchcounter_examples()with|Seq.Nil->Total|Seq.Cons(v,_rest)->ifWarnings.is_active(Warnings.Partial_match"")thenbeginleterrmsg=letdoc=refFormat_doc.Doc.emptyinletfmt=Format_doc.formatterdocinFormat_doc.fprintffmt"@[<v>%a"Printpat.top_prettyv;ifdo_match(initial_only_guardedcasel)[v]thenFormat_doc.fprintffmt"@,(However, some guarded clause may match this value.)";ifcontains_extensionvthenFormat_doc.fprintffmt"@,@[Matching over values of extensible variant types \
(the *extension* above)@,\
must include a wild card pattern@ in order to be exhaustive.@]";Format_doc.fprintffmt"@]";Format_doc.(asprintf"%a"pp_doc)!docinLocation.prerr_warningloc(Warnings.Partial_matcherrmsg)end;Partial(*****************)(* Fragile check *)(*****************)(* Collect all data types in a pattern *)letrecadd_pathpath=function|[]->[path]|x::remaspaths->ifPath.samepathxthenpathselsex::add_pathpathremletextendable_pathpath=not(Path.samepathPredef.path_bool||Path.samepathPredef.path_list||Path.samepathPredef.path_unit||Path.samepathPredef.path_option)letreccollect_paths_from_patrp=matchp.pat_descwith|Tpat_construct(_,{cstr_tag=(Cstr_constant_|Cstr_block_|Cstr_unboxed)},ps,_)->letpath=get_constructor_type_pathp.pat_typep.pat_envinList.fold_leftcollect_paths_from_pat(ifextendable_pathpaththenadd_pathpathrelser)ps|Tpat_any|Tpat_var_|Tpat_constant_|Tpat_variant(_,None,_)->r|Tpat_tupleps|Tpat_arrayps|Tpat_construct(_,{cstr_tag=Cstr_extension_},ps,_)->List.fold_leftcollect_paths_from_patrps|Tpat_record(lps,_)->List.fold_left(funr(_,_,p)->collect_paths_from_patrp)rlps|Tpat_variant(_,Somep,_)|Tpat_alias(p,_,_,_)->collect_paths_from_patrp|Tpat_or(p1,p2,_)->collect_paths_from_pat(collect_paths_from_patrp1)p2|Tpat_lazyp->collect_paths_from_patrp(*
Actual fragile check
1. Collect data types in the patterns of the match.
2. One exhaustivity check per datatype, considering that
the type is extended.
*)letdo_check_fragileloccaselpss=letexts=List.fold_left(funrc->collect_paths_from_patrc.pattern)[]caselinmatchextswith|[]->()|_->matchpsswith|[]->()|ps::_->List.iter(funext->letwitnesses=exhaust(Someext)pss(List.lengthps)inmatchwitnesses()with|Seq.Nil->Location.prerr_warningloc(Warnings.Fragile_match(Path.nameext))|Seq.Cons_->())exts(********************************)(* Exported unused clause check *)(********************************)letcheck_unusedpredcasel=ifWarnings.is_activeWarnings.Redundant_case||List.exists(funvc->vc.needs_refute)caselthenletrecdo_recpref=function|[]->()|{pattern=q;has_guard;needs_refute=refute}::rem->letqs=[q]inbegintryletpss=(* prev was accumulated in reverse order;
restore source order to get ordered counter-examples *)List.revpref|>List.filter(compatsqs)|>get_minsle_patsin(* First look for redundant or partially redundant patterns *)letr=every_satisfiables(make_rowspss)(make_rowqs)in(* Do not warn for unused [pat -> .] *)ifr=Unused&&refutethen()elseletr=(* Do not refine if either:
- we already know the clause is unused
- the clause under consideration is not a refutation clause
and either:
+ there are no other lines
+ we do not care whether the types prevent this clause to
be reached.
If the clause under consideration *is* a refutation clause
then we do need to check more carefully whether it can be
refuted or not. *)letskip=r=Unused||(notrefute&&pref=[])||not(refute||Warnings.is_activeWarnings.Unreachable_case)inifskipthenrelse(* Then look for empty patterns *)letsfs=list_satisfying_vectorspssqsinifsfs=[]thenUnusedelseletsfs=List.map(function[u]->u|_->assertfalse)sfsinletu=orify_manysfsin(*Format.eprintf "%a@." pretty_val u;*)letpattern={uwithpat_loc=q.pat_loc}inmatchpredrefutepatternwithNonewhennotrefute->Location.prerr_warningq.pat_locWarnings.Unreachable_case;Used|_->rinmatchrwith|Unused->Location.prerr_warningq.pat_locWarnings.Redundant_case|Upartialps->List.iter(funp->Location.prerr_warningp.pat_locWarnings.Redundant_subpat)ps|Used->()withEmpty|Not_found->assertfalseend;ifhas_guardthendo_recprefremelsedo_rec([q]::pref)remindo_rec[]casel(*********************************)(* Exported irrefutability tests *)(*********************************)letirrefutablepat=le_patpatomegaletinactive~partialpat=matchpartialwith|Partial->false|Total->beginletreclooppat=matchpat.pat_descwith|Tpat_lazy_|Tpat_array_->false|Tpat_any|Tpat_var_|Tpat_variant(_,None,_)->true|Tpat_constantc->beginmatchcwith|Const_string_|Const_int_|Const_char_|Const_float_|Const_int32_|Const_int64_|Const_nativeint_->trueend|Tpat_tupleps|Tpat_construct(_,_,ps,_)->List.for_all(funp->loopp)ps|Tpat_alias(p,_,_,_)|Tpat_variant(_,Somep,_)->loopp|Tpat_record(ldps,_)->List.for_all(fun(_,lbl,p)->lbl.lbl_mut=Immutable&&loopp)ldps|Tpat_or(p,q,_)->loopp&&loopqinlooppatend(*********************************)(* Exported exhaustiveness check *)(*********************************)(*
Fragile check is performed when required and
on exhaustive matches only.
*)letcheck_partialpredloccasel=letpss=initial_matrixcaselinletpss=get_minsle_patspssinlettotal=do_check_partial~predloccaselpssiniftotal=Total&&Warnings.is_active(Warnings.Fragile_match"")thenbegindo_check_fragileloccaselpssend;total(*************************************)(* Ambiguous variable in or-patterns *)(*************************************)(* Specification: ambiguous variables in or-patterns.
The semantics of or-patterns in OCaml is specified with
a left-to-right bias: a value [v] matches the pattern [p | q] if it
matches [p] or [q], but if it matches both, the environment
captured by the match is the environment captured by [p], never the
one captured by [q].
While this property is generally well-understood, one specific case
where users expect a different semantics is when a pattern is
followed by a when-guard: [| p when g -> e]. Consider for example:
| ((Const x, _) | (_, Const x)) when is_neutral x -> branch
The semantics is clear: match the scrutinee against the pattern, if
it matches, test the guard, and if the guard passes, take the
branch.
However, consider the input [(Const a, Const b)], where [a] fails
the test [is_neutral f], while [b] passes the test [is_neutral
b]. With the left-to-right semantics, the clause above is *not*
taken by its input: matching [(Const a, Const b)] against the
or-pattern succeeds in the left branch, it returns the environment
[x -> a], and then the guard [is_neutral a] is tested and fails,
the branch is not taken. Most users, however, intuitively expect
that any pair that has one side passing the test will take the
branch. They assume it is equivalent to the following:
| (Const x, _) when is_neutral x -> branch
| (_, Const x) when is_neutral x -> branch
while it is not.
The code below is dedicated to finding these confusing cases: the
cases where a guard uses "ambiguous" variables, that are bound to
different parts of the scrutinees by different sides of
a or-pattern. In other words, it finds the cases where the
specified left-to-right semantics is not equivalent to
a non-deterministic semantics (any branch can be taken) relatively
to a specific guard.
*)letpattern_varsp=Ident.Set.of_list(Typedtree.pat_bound_identsp)(* Row for ambiguous variable search,
row is the traditional pattern row,
varsets contain a list of head variable sets (varsets)
A given varset contains all the variables that appeared at the head
of a pattern in the row at some point during traversal: they would
all be bound to the same value at matching time. On the contrary,
two variables of different varsets appeared at different places in
the pattern and may be bound to distinct sub-parts of the matched
value.
All rows of a (sub)matrix have rows of the same length,
but also varsets of the same length.
Varsets are populated when simplifying the first column
-- the variables of the head pattern are collected in a new varset.
For example,
{ row = x :: r1; varsets = s1 }
{ row = (Some _) as y :: r2; varsets = s2 }
{ row = (None as x) as y :: r3; varsets = s3 }
{ row = (Some x | (None as x)) :: r4 with varsets = s4 }
becomes
(_, { row = r1; varsets = {x} :: s1 })
(Some _, { row = r2; varsets = {y} :: s2 })
(None, { row = r3; varsets = {x, y} :: s3 })
(Some x, { row = r4; varsets = {} :: s4 })
(None, { row = r4; varsets = {x} :: s4 })
*)typeamb_row={row:patternlist;varsets:Ident.Set.tlist;}letsimplify_head_amb_pathead_bound_variablesvarsets~add_columnppsk=letrecsimplhead_bound_variablesvarsetsppsk=match(Patterns.General.viewp).pat_descwith|`Alias(p,x,_,_)->simpl(Ident.Set.addxhead_bound_variables)varsetsppsk|`Var(x,_,_)->simpl(Ident.Set.addxhead_bound_variables)varsetsPatterns.omegapsk|`Or(p1,p2,_)->simplhead_bound_variablesvarsetsp1ps(simplhead_bound_variablesvarsetsp2psk)|#Patterns.Simple.viewasview->add_column(Patterns.Head.deconstruct{pwithpat_desc=view}){row=ps;varsets=head_bound_variables::varsets;}kinsimplhead_bound_variablesvarsetsppsk(*
To accurately report ambiguous variables, one must consider
that previous clauses have already matched some values.
Consider for example:
| (Foo x, Foo y) -> ...
| ((Foo x, _) | (_, Foo x)) when bar x -> ...
The second line taken in isolation uses an unstable variable,
but the discriminating values, of the shape [(Foo v1, Foo v2)],
would all be filtered by the line above.
To track this information, the matrices we analyze contain both
*positive* rows, that describe the rows currently being analyzed
(of type Varsets.row, so that their varsets are tracked) and
*negative rows*, that describe the cases already matched against.
The values matched by a signed matrix are the values matched by
some of the positive rows but none of the negative rows. In
particular, a variable is stable if, for any value not matched by
any of the negative rows, the environment captured by any of the
matching positive rows is identical.
*)type('a,'b)signed=Positiveof'a|Negativeof'bletrecsimplify_first_amb_col=function|[]->[]|(Negative[]|Positive{row=[];_})::_->assertfalse|Negative(n::ns)::rem->letadd_columnnnsk=(n,Negativens)::kinsimplify_head_pat~add_columnnns(simplify_first_amb_colrem)|Positive{row=p::ps;varsets;}::rem->letadd_columnppsk=(p,Positiveps)::kinsimplify_head_amb_patIdent.Set.emptyvarsets~add_columnpps(simplify_first_amb_colrem)(* Compute stable bindings *)typestable_vars=|All|VarsofIdent.Set.tletstable_intersv1sv2=matchsv1,sv2with|All,sv|sv,All->sv|Varss1,Varss2->Vars(Ident.Set.inters1s2)letreducef=function|[]->invalid_arg"reduce"|x::xs->List.fold_leftfxxsletrecmatrix_stable_varsm=matchmwith|[]->All|((Positive{row=[];_}|Negative[])::_)asempty_rows->letexceptionNegative_empty_rowin(* if at least one empty row is negative, the matrix matches no value *)letget_varsets=function|Negativen->(* All rows have the same number of columns;
if the first row is empty, they all are. *)assert(n=[]);raiseNegative_empty_row|Positivep->assert(p.row=[]);p.varsetsinbeginmatchList.mapget_varsetsempty_rowswith|exceptionNegative_empty_row->All|rows_varsets->letstables_in_varsets=reduce(List.map2Ident.Set.inter)rows_varsetsin(* The stable variables are those stable at any position *)Vars(List.fold_leftIdent.Set.unionIdent.Set.emptystables_in_varsets)end|m->letis_negative=function|Negative_->true|Positive_->falseinifList.for_allis_negativemthen(* optimization: quit early if there are no positive rows.
This may happen often when the initial matrix has many
negative cases and few positive cases (a small guarded
clause after a long list of clauses) *)Allelsebeginletm=simplify_first_amb_colminifnot(all_coherent(first_columnm))thenAllelsebegin(* If the column is ill-typed but deemed coherent, we might
spuriously warn about some variables being unstable.
As sad as that might be, the warning can be silenced by
splitting the or-pattern... *)letsubmatrices=letextend_rowcolumns=function|Negativer->Negative(columns@r)|Positiver->Positive{rwithrow=columns@r.row}inletq0=discr_patPatterns.Simple.omegaminlet{default;constrs}=build_specialized_submatrices~extend_rowq0minletnon_default=List.mapsndconstrsiniffull_matchfalseconstrsthennon_defaultelsedefault::non_defaultin(* A stable variable must be stable in each submatrix. *)letsubmat_stable=List.mapmatrix_stable_varssubmatricesinList.fold_leftstable_interAllsubmat_stableendendletpattern_stable_varsnsp=matrix_stable_vars(List.fold_left(funmn->Negativen::m)[Positive{varsets=[];row=[p]}]ns)(* All identifier paths that appear in an expression that occurs
as a clause right hand side or guard.
*)letall_rhs_identsexp=letids=refIdent.Set.emptyinletopenTast_iteratorinletexpr_iteriterexp=matchexp.exp_descwith|Texp_ident(path,_lid,_descr)->List.iter(funid->ids:=Ident.Set.addid!ids)(Path.headspath)(* Use default iterator methods for rest of match.*)|_->Tast_iterator.default_iterator.expriterexpinletiterator={Tast_iterator.default_iteratorwithexpr=expr_iter}initerator.expriteratorexp;!idsletcheck_ambiguous_bindings=letopenWarningsinletwarn0=Ambiguous_var_in_pattern_guard[]infuncases->ifis_activewarn0thenletcheck_casenscase=matchcasewith|{c_lhs=p;c_guard=None;_}->[p]::ns|{c_lhs=p;c_guard=Someg;_}->letall=Ident.Set.inter(pattern_varsp)(all_rhs_identsg)inifnot(Ident.Set.is_emptyall)thenbeginmatchpattern_stable_varsnspwith|All->()|Varsstable->letambiguous=Ident.Set.diffallstableinifnot(Ident.Set.is_emptyambiguous)thenbeginletpps=Ident.Set.elementsambiguous|>List.mapIdent.nameinletwarn=Ambiguous_var_in_pattern_guardppsinLocation.prerr_warningp.pat_locwarnendend;nsinignore(List.fold_leftcheck_case[]cases)letdo_complete_partial~(pred:pattern->patternoption)pss=(* c/p of [do_check_partial] without the parts concerning the generation of
the error message or the warning emiting. *)matchpsswith|[]->[]|ps::_->lettypecheckp=predpinexhaustNonepss(List.lengthps)|>Seq.filter_maptypecheck|>List.of_seqletcomplete_partial~(pred:pattern->patternoption)pss=letpss=get_minsle_patspssindo_complete_partial~predpssletreturn_unusedcasel=letrecdo_recaccpref=function|[]->acc|q::rem->letqs=[q]inletacc=tryletpss=get_minsle_pats(List.filter(compatsqs)pref)inletr=every_satisfiables(make_rowspss)(make_rowqs)inmatchrwith|Unused->`Unusedq::acc|Upartialps->`Unused_subs(q,ps)::acc|Used->accwithEmpty|Not_found->assertfalsein(* FIXME: we need to know whether there is a guard here, because if there
is, we dont want to add [[q]] to [pref]. *)do_recacc([q]::pref)remindo_rec[][]casel