123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378(************************************************************************)(* * The Rocq Prover / The Rocq Development Team *)(* v * Copyright INRIA, CNRS and contributors *)(* <O___,, * (see version control and CREDITS file for authors & dates) *)(* \VV/ **************************************************************)(* // * This file is distributed under the terms of the *)(* * GNU Lesser General Public License Version 2.1 *)(* * (see LICENSE file for the text of the license) *)(************************************************************************)openSortsmoduleElimTable=structopenQualityletconst_eliminates_toqq'=matchq,q'with|QType,_->true|QProp,(QProp|QSProp)->true|QSProp,QSProp->true|(QProp|QSProp),_->falseleteliminates_toqq'=matchq,q'with|QConstantQType,_->true|QConstantq,QConstantq'->const_eliminates_toqq'|QVarq,QVarq'->QVar.equalqq'|(QConstant_|QVar_),_->falseendmoduleG=AcyclicGraph.Make(structtypet=Quality.tmoduleSet=Quality.SetmoduleMap=Quality.Mapletequal=Quality.equalletcompare=Quality.compareletraw_pr=Quality.raw_prletanomaly_errq=Pp.(str"Quality "++Quality.raw_prq++str" undefined.")end)(** We only use G to track connected components of the eliminability relation.
In particular, G |= q1 = q2 does not mean that they are equal, but just
equi-eliminable. As a result, one should not use the check_eq / enforce_eq
functions in the remainder of this module.
Note that we also abuse the Lt relation to get some rigidity for free for
the built-in sorts, but this is a hack that should go away at some point. *)moduleRigidPaths=structtypet=(Quality.tlist)Quality.Map.tletadd_elim_toq1q2g=letupdls=Some(matchlswith|Somels->q2::ls|None->[q2])inQuality.Map.updateq1updgletempty:t=Quality.Map.emptyletdfsqg=letrecauxseenq=ifQuality.Set.memqseenthenseenelseletseen=Quality.Set.addqseeninmatchQuality.Map.find_optqgwith|None->seen|Somels->List.fold_leftauxseenlsinauxQuality.Set.emptyqletcheck_forbidden_path(map:Quality.Set.tQuality.Map.t)g=letfoldqsls=letallowed=dfsqginletdiff=Quality.Set.diffsallowedinifQuality.Set.is_emptydiffthenlselseList.append(List.map(funq'->(q,q'))(List.of_seq@@Quality.Set.to_seqdiff))lsinletforbidden=Quality.Map.foldfoldmap[]inmatchforbiddenwith|[]->None|p::_->SomependmoduleQMap=QVar.MapmoduleQSet=QVar.Settypet={graph:G.t;(** Connected components of the eliminability graph *)rigid_paths:RigidPaths.t;ground_and_global_sorts:Quality.Set.t;dominant:Quality.tQMap.t;delayed_check:QSet.tQMap.t;}typepath_explanation=G.explanationLazy.ttypeexplanation=|Pathofpath_explanation|OtherofPp.ttypequality_inconsistency=((QVar.t->Pp.t)option)*(ElimConstraint.kind*Quality.t*Quality.t*explanationoption)(* If s can eliminate to s', we want an edge between s and s'.
In the acyclic graph, it means setting s to be lower or equal than s'.
This function ensures a uniform behaviour between [check] and [enforce]. *)letto_graph_cstrk=letopenElimConstraintinmatchkwith|ElimTo->AcyclicGraph.Letypeelimination_error=|IllegalConstraintFromSPropofQuality.t|IllegalConstantConstraintofQuality.constant*Quality.constant|CreatesForbiddenPathofQuality.t*Quality.t|MultipleDominanceofQuality.t*Quality.t*Quality.t|QualityInconsistencyofquality_inconsistencyexceptionEliminationErrorofelimination_errorletnon_refl_pairsl=letfoldx=List.fold_right(funyacc->ifx<>ythen(x,y)::accelseacc)linList.fold_rightfoldl[]letget_new_rigid_pathsgpdom=letadd(q1,_,q2)accu=Quality.Map.updateq1(funls->Some(matchlswithSomels->Quality.Set.addq2ls|None->Quality.Set.singletonq2))accuinletall_paths=G.constraints_for~kept:domgaddQuality.Map.emptyinRigidPaths.check_forbidden_pathall_pathspletset_dominantgqvq={gwithdominant=QMap.addqvqg.dominant}(* Set the dominant sort of qv to the minimum between q1 and q2 if they are related.
[q1] is the dominant of qv in [g]. *)letupdate_dominant_if_relatedgqvq1q2=ifG.check_leqg.graphq1q2thenSome(set_dominantgqvq2)elseifG.check_leqg.graphq2q1thenSomegelseNone(* If [qv] is not dominated, set dominance to [q].
Otherwise, checks whether the dominant of [qv] and [q] are related.
If so, puts the smallest of the two as the dominant of [qv]. *)letrecupdate_dominancegqqv=letg'=matchQMap.find_optqvg.dominantwith|None->Some(set_dominantgqvq)|Someq'->update_dominant_if_relatedgqvq'qinmatchQMap.find_optqvg.delayed_checkwith|None->g'|Someqs->letg'=QSet.fold(funvg->Option.bindg(fung->update_dominancegqv))qsg'inmatchg'with|Somegraph->Some{graphwithdelayed_check=QMap.setqvQSet.emptyg.delayed_check}|None->Noneletupdate_dominance_if_validg(q1,k,q2)=matchkwith|ElimConstraint.ElimTo->(* if the constraint is s ~> g, dominants are not modified. *)ifQuality.is_qconstq2thenSomegelsematchq1,q2with|(Quality.QConstant_|Quality.QVar_),Quality.QConstant_->assertfalse|Quality.QVarqv1,Quality.QVarqv2->(* 3 cases:
- if [qv1] is a global, treat as constants.
- if [qv1] is not dominated, delay the check to when [qv1] gets dominated.
- if [qv1] is dominated, try to update the dominance of [qv2]. *)ifQuality.is_qglobalq1thenupdate_dominancegq1qv2else(matchQMap.find_optqv1g.dominantwith|None->letadd_delayedqs=Some{gwithdelayed_check=QMap.setqv1(QSet.addqv2qs)g.delayed_check}in(matchQMap.find_optqv1g.delayed_checkwith|None->add_delayedQSet.empty|Someqs->add_delayedqs)|Someq'->update_dominancegq'qv2)|Quality.QConstant_,Quality.QVarqv->update_dominancegq1qvletdominance_checkg(q1,_,q2ascstr)=letdom_q1()=matchq1with|Quality.QConstant_->q1|Quality.QVarqv->ifQuality.is_qglobalq1thenq1elseQMap.findqvg.dominantinletdom_q2()=matchq2with|Quality.QConstant_->assertfalse|Quality.QVarqv->QMap.findqvg.dominantinmatchupdate_dominance_if_validgcstrwith|None->raise(EliminationError(MultipleDominance(dom_q2(),q2,dom_q1())))|Someg->gletcheck_rigid_pathsg=letpaths=get_new_rigid_pathsg.graphg.rigid_pathsg.ground_and_global_sortsinmatchpathswith|None->()|Some(q1,q2)->raise(EliminationError(CreatesForbiddenPath(q1,q2)))letadd_rigid_pathq1q2g=letopenQualityinmatchq1,q2with(* Adding a constraint from SProp -> * is not allowed *)|QConstantQSProp,_->raise(EliminationError(IllegalConstraintFromSPropq2))(* Adding constraints between constants is not allowed *)|QConstantqc1,QConstantqc2->raise(EliminationError(IllegalConstantConstraint(qc1,qc2)))|_,_->{gwithrigid_paths=RigidPaths.add_elim_toq1q2g.rigid_paths}letenforce_funckq1q2g=matchkwith|ElimConstraint.ElimTo->beginmatchG.enforce_leqq1q2g.graphwith|None->None|Somegraph->Some{gwithgraph}endletenforce_constraint(q1,k,q2)g=matchenforce_funckq1q2gwith|None->lete=lazy(G.get_explanation(q1,to_graph_cstrk,q2)g.graph)inraise@@EliminationError(QualityInconsistency(None,(k,q1,q2,Some(Pathe))))|Someg->dominance_checkg(q1,k,q2)letmerge_constraintscstsg=ElimConstraints.foldenforce_constraintcstsgletcheck_constraintg(q1,k,q2)=matchkwith|ElimConstraint.ElimTo->G.check_leqg.graphq1q2letcheck_constraintscstsg=ElimConstraints.for_all(check_constraintg)cstsexceptionAlreadyDeclared=G.AlreadyDeclaredletadd_qualityqg=letgraph=G.addqg.graphinletg=enforce_constraint(Quality.qtype,ElimConstraint.ElimTo,q){gwithgraph}inlet(paths,ground_and_global_sorts)=ifQuality.is_qglobalqthen(RigidPaths.add_elim_toQuality.qtypeqg.rigid_paths,Quality.Set.addqg.ground_and_global_sorts)else(g.rigid_paths,g.ground_and_global_sorts)in(* As Type ~> s, set Type to be the dominant sort of q if q is a variable. *)letdominant=matchqwith|Quality.QVarqv->QMap.addqvQuality.qtypeg.dominant|Quality.QConstant_->g.dominantin{gwithrigid_paths=paths;ground_and_global_sorts;dominant}letenforce_eliminates_tos1s2g=enforce_constraint(s1,ElimConstraint.ElimTo,s2)gletinitial_graph=letg=G.emptyinletg=List.fold_left(fungq->G.addqg)gQuality.all_constantsin(* Enforces the constant constraints defined in the table of
[Constants.eliminates_to] without reflexivity (should be consistent,
otherwise the [Option.get] will fail). *)letfold(g,p)(q,q')=ifElimTable.eliminates_toqq'then(Option.get@@G.enforce_ltqq'g,RigidPaths.add_elim_toqq'p)else(g,p)inlet(g,p)=List.fold_leftfold(g,RigidPaths.empty)@@non_refl_pairsQuality.all_constantsin{graph=g;rigid_paths=p;ground_and_global_sorts=Quality.Set.of_listQuality.all_constants;dominant=QMap.empty;delayed_check=QMap.empty;}leteliminates_togqq'=G.check_leqg.graphqq'letupdate_rigidsgg'={g'withrigid_paths=g.rigid_paths}letsort_eliminates_togs1s2=eliminates_tog(qualitys1)(qualitys2)leteliminates_to_propgq=eliminates_togqQuality.qpropletdomaing=G.domaing.graphletqvar_domaing=Quality.Set.fold(funqacc->matchqwithQuality.QVarq->QVar.Set.addqacc|_->acc)(domaing)QVar.Set.empty(* XXX the function below does not handle equivalence classes, but it's only used for printing *)letmergegg'=letqs=domaing'inletg=Quality.Set.fold(funqacc->tryadd_qualityqaccwith_->acc)qsginQuality.Set.fold(funq->Quality.Set.fold(funq'acc->ifeliminates_tog'qq'thenenforce_eliminates_toqq'accelseacc)qs)qsgletis_emptyg=QVar.Set.is_empty(qvar_domaing)(* Pretty printing *)letpr_pmapsepprmap=letcmp(q1,_)(q2,_)=Quality.compareq1q2inPp.prlist_with_sepseppr(List.sortcmp(Quality.Map.bindingsmap))letpr_arcprq=letopenPpinfunction|q1,G.Nodeltle->ifQuality.Map.is_emptyltlethenmt()elseprqq1++spc()++v0(pr_pmapspc(fun(q2,_)->str"-> "++prqq2)ltle)++fnl()|q1,G.Aliasq2->prqq1++str" <-> "++prqq2++fnl()letreprg=G.reprg.graphletis_declaredqg=matchG.check_declaredg.graph(Quality.Set.singletonq)with|Result.Ok_->true|Result.Error_->falseletpr_qualitiesprqg=pr_pmapPp.mt(pr_arcprq)(reprg)letexplain_quality_inconsistencyprvr=letopenPpinletpr_cst=function|AcyclicGraph.Eq->str"="|AcyclicGraph.Le->str"->"|AcyclicGraph.Lt->str"->"(* Yes, it's the same as above. *)inmatchrwith|None->mt()|Some(Otherp)->p|Some(Pathp)->letpstart,p=Lazy.forcepinifp=[]thenmt()elseletqualities=pstart::List.mapsndpinletconstants=List.filterQuality.is_qconstqualitiesinstr"because it would identify "++pr_enum(Quality.prprv)constants++spc()++str"which is inconsistent."++spc()++str"This is introduced by the constraints "++prlist(fun(r,v)->Quality.prprvv++spc()++pr_cstr++spc())p++Quality.prprvpstartletexplain_elimination_errordefprverr=letopenPpinmatcherrwith|IllegalConstraintFromSPropq->str"Enforcing elimination constraints from SProp to any other sort is not allowed. "++brk(1,0)++str"This expression would enforce that SProp eliminates to "++Quality.prdefprvq++str"."|IllegalConstantConstraint(q1,q2)->str"Enforcing elimination constraints between constant sorts (Type, Prop or SProp) is not allowed."++brk(1,0)++str"Here: "++Quality.Constants.prq1++str" and "++Quality.Constants.prq2++str"."|CreatesForbiddenPath(q1,q2)->str"This expression would enforce a non-declared elimination constraint between"++spc()++Quality.prdefprvq1++spc()++str"and"++spc()++Quality.prdefprvq2|MultipleDominance(q1,qv,q2)->letpr_elimq=Quality.prdefprvq++spc()++str"->"++spc()++Quality.prdefprvqvinstr"This expression enforces"++spc()++pr_elimq1++spc()++str"and"++spc()++pr_elimq2++spc()++str"which might make type-checking undecidable"|QualityInconsistency(prv,(k,q1,q2,r))->letprv=matchprvwithSomeprv->prv|None->defprvinstr"The quality constraints are inconsistent: "++str"cannot enforce"++spc()++Quality.prprvq1++spc()++ElimConstraint.pr_kindk++spc()++Quality.prprvq2++spc()++explain_quality_inconsistencyprvr