123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590(* This file is free software, part of Zipperposition. See file "license" for more details. *)(** {1 Inference and simplification rules for Algebraic types} *)openLogtkopenLibzipperpositionmoduleT=TermmoduleS=SubstmoduleLit=LiteralmoduleLits=LiteralsmoduleStmt=Statementtypeterm=T.tletprof_detect=ZProf.make"enum_types.detect"letprof_instantiate=ZProf.make"enum_types.instantiate_vars"letstat_declare=Util.mk_stat"enum_types.declare"letstat_simplify=Util.mk_stat"enum_types.simplify"letstat_instantiate=Util.mk_stat"enum_types.instantiate_axiom"letsection=Util.Section.make~parent:Const.section"enum_ty"(* flag for clauses that are declarations of enumerated types *)letflag_enumeration_clause=SClause.new_flag()exceptionErrorofstringlet()=Printexc.register_printer(functionErrors->Some("error in enum_types: "^s)|_->None)leterror_s=raise(Errors)leterrorf_msg=CCFormat.ksprintfmsg~f:error_typeid_or_ty_builtin=|IofID.t|BofType.builtinletpp_id_or_builtinout=function|Iid->ID.ppoutid|Bb->Type.pp_builtinoutb(** {2 Inference rules} *)moduletypeS=sigmoduleEnv:Env.SmoduleC:moduletypeofEnv.Ctypedeclvalpp_decl:declCCFormat.printertypedeclare_result=|Newofdecl|AlreadyDeclaredofdeclvaldeclare_ty:proof:Proof.t->ty_id:ID.t->ty_vars:Type.tHVar.tlist->var:Type.tHVar.t->termlist->declare_result(** Declare that the domain of the type [ty_id] is restricted to
given list of [cases], in the form [forall var. Or_{c in cases} var = c].
The type of [var] must be [ty_id ty_vars].
Will be ignored if the type already has a enum declaration, and the old
declaration will be returned instead.
@return either the new declaration, or the already existing one for
this type if any
@raise Error if some of the preconditions is not filled *)valinstantiate_vars:Env.multi_simpl_rule(** Instantiate variables whose type is a known enumerated type,
with all cases of this type. *)(** {5 Registration} *)valsetup:unit->unit(** Register rules in the environment *)endlet_enable=reffalselet_instantiate_shielded=reffalselet_accept_unary_types=reftruelet_instantiate_projector_axiom=reffalseletis_projector_id~of_=matchInd_ty.as_projectoridwith|Somep->ID.equal(Ind_ty.projector_idp)of_|None->falsemoduleMake(E:Env.S):SwithmoduleEnv=E=structmoduleEnv=EmoduleC=Env.CmodulePS=Env.ProofStatemoduleCtx=Env.Ctx(* one particular enum type. The return type has the shape [id(vars)],
such as [list(a)] or [map(a,b)] *)typedecl={decl_ty_id:id_or_ty_builtin;decl_ty_vars:Type.tHVar.tlist;decl_ty:Type.t;(* id applied to ty_vars (shortcut) *)decl_var:Type.tHVar.t;(* x = ... *)decl_cases:termlist;(* ... t1 | t2 | ... | tn *)decl_proof:[`DataofProof.t*Type.tStatement.data|`ClauseofProof.t];(* justification for the enumeration axiom *)mutabledecl_symbols:ID.Set.t;(* set of declared symbols for t1,...,tn *)}letpp_decloutd=Format.fprintfout"@[<1>{enum_ty=@[%a@],@ cases=@[%a@]}@]"Type.ppd.decl_ty(Util.pp_listT.pp)d.decl_cases(* set of enumerated types (indexed by [decl_ty_id]) *)letdecls_by_id=ID.Tbl.create16letdecls_builtin=ref[]letfind_decl_=function|Ii->ID.Tbl.finddecls_by_idi|Bb->List.assocb!decls_builtinletadd_decl_iddecl=matchidwith|Iid->ID.Tbl.adddecls_by_ididdecl|Bb->decls_builtin:=(b,decl)::!decls_builtin(* triggered whenever a new EnumType is added *)leton_new_decl=Signal.create()(* check that [var] is the only free (term) variable in all cases *)letcheck_uniq_var_is_~varcases=cases|>Iter.of_list|>Iter.flat_mapT.Seq.vars|>Iter.filter(funv->not(Type.equalType.tType(HVar.tyv)))|>Iter.for_all(HVar.equalType.equalvar)(* check that all vars in [l] are pairwise distinct *)letreccheck_all_distinct_accl=matchlwith|[]->true|v::l'->not(CCList.mem~eq:(HVar.equalType.equal)vacc)&&check_all_distinct_(v::acc)l'typedeclare_result=|Newofdecl|AlreadyDeclaredofdeclletdeclare_~proof~ty_id:id~ty_vars~varcases=ifnot(check_all_distinct_[]ty_vars)thenerrorf_"invalid declaration %a: duplicate type variable"pp_id_or_builtinid;ifnot(check_uniq_var_is_~varcases)thenerrorf_"invalid declaration %a: %a is not the only variable in @[%a@]"pp_id_or_builtinid(Util.pp_listT.pp)casesHVar.ppvar;tryletdecl=find_decl_idinUtil.debugf~section3"@[an enum is already declared for type %a@]"(funk->kpp_id_or_builtinid);AlreadyDeclareddeclwithNot_found->letty=matchidwith|Iid->Type.appid(List.mapType.varty_vars)|Bb->assert(ty_vars=[]);Type.builtinbinUtil.debugf~section1"@[<2>declare new enum type `@[%a@]`@ (@[cases %a ∈ {@[<hv>%a@]}@])@]"(funk->kType.pptyHVar.ppvar(Util.pp_list~sep:", "T.pp)cases);Util.incr_statstat_declare;(* set of already declared symbols *)letdecl_symbols=List.fold_left(funsett->matchT.headtwith|None->errorf_"non-symbolic case @[%a@]"T.ppt|Somes->ID.Set.addsset)ID.Set.emptycasesinletdecl={decl_ty_id=id;decl_ty_vars=ty_vars;decl_ty=ty;decl_var=var;decl_cases=cases;decl_symbols;decl_proof=proof;}inadd_decl_iddecl;Signal.sendon_new_decldecl;Newdecl(* declare an enumerated type *)letdeclare_ty~proof~ty_id~ty_vars~varcases=declare_~proof:(`Clauseproof)~var~ty_id:(Ity_id)~ty_varscasesletas_simple_tyty=matchType.viewtywith|Type.App(id,[])->Some(Iid)|Type.Builtinb->Some(Bb)|_->Noneletis_simple_tyty=as_simple_tyty<>None(* detect whether the clause [c] is a declaration of a simply-typed EnumType
with only constants as cases (in other words, a monomorphic finite type) *)letdetect_decl_c=leteq_var_~vart=matchT.viewtwith|T.Varv'->HVar.equalType.equalvarv'|_->falseandget_var_t=matchT.viewtwith|T.Varv->v|_->assertfalseandis_constt=matchT.viewtwith|T.Const_->true|_->falsein(* loop over literals checking whether they are all of the form
[var = t] for some constant [t] *)letrec_check_all_vars~ty~varacclits=matchlitswith|[]->(* now also check that no case has free variables other than [var],
and that there are at least 2 cases *)ifcheck_uniq_var_is_~varacc&&(!_accept_unary_types||List.lengthacc>=2)thenSome(var,acc)elseNone|Lit.Equation(l,r,true)::lits'wheneq_var_~varl&&is_constr->_check_all_vars~ty~var(r::acc)lits'|Lit.Equation(l,r,true)::lits'wheneq_var_~varr&&is_constl->_check_all_vars~ty~var(l::acc)lits'|_->Noneinletlits=C.litscinifCCArray.exists(funl->not(Lit.is_eql))litsthenNoneelsematchArray.to_listlitswith|Lit.Equation(l,r,true)::litswhenT.is_varl&&is_simple_ty(T.tyl)&&is_constr->letvar=get_var_lin_check_all_vars~ty:(T.tyl)~var[r]lits|Lit.Equation(l,r,true)::litswhenT.is_varr&&is_simple_ty(T.tyr)&&is_constl->letvar=get_var_rin_check_all_vars~ty:(T.tyr)~var[l]lits|_->Noneletdetect_declarationc=ZProf.with_profprof_detectdetect_decl_c(* retrieve variables that are directly under a positive equation *)letvars_under_eq_lits=Iter.of_arraylits|>Iter.filterLit.is_eq|>Iter.flat_mapLit.Seq.terms|>Iter.filterT.is_var(* variables occurring under some function symbol (at non-0 depth) *)let_shielded_varslits=Iter.of_arraylits|>Iter.flat_mapLit.Seq.terms|>Iter.flat_mapT.Seq.subterms_depth|>Iter.filter_map(fun(v,depth)->ifdepth>0&&T.is_varvthenSomevelseNone)|>T.Seq.add_setT.Set.emptyletnaked_vars_lits=letv=vars_under_eq_lits|>T.Seq.add_setT.Set.emptyinT.Set.diffv(_shielded_varslits)|>T.Set.elements(* assuming [length decl.decl_ty_vars = length args], bind them pairwise
in a substitution *)letbind_vars_(d,sc_decl)(args,sc_args)=List.fold_left2(funsubstvarg->letv=(v:Type.tHVar.t:>InnerTerm.tHVar.t)inSubst.Ty.bindsubst(v,sc_decl)(arg,sc_args))Subst.emptyd.decl_ty_varsargs(* given a type [ty], find whether it's an enum type, and if it is the
case return [Some (decl, subst)] *)letfind_ty_sc_decltysc_ty=letfind_auxil=tryletd=find_decl_iinifList.lengthl=List.lengthd.decl_ty_varsthenletsubst=bind_vars_(d,sc_decl)(l,sc_ty)inSome(d,subst)elseNonewithNot_found->NoneinmatchType.viewtywith|Type.Builtinb->find_aux(Bb)[]|Type.App(id,l)->find_aux(Iid)l|_->None(* TODO: maybe relax the restriction that is must not be naked, but only
up to a given depth (if CLI arg?) *)(* TODO: only instantiate naked variables that are in positive equations;
those in negative equations may come from purification and must be
found be E-unification *)(* instantiate variables that belong to an enum case *)letinstantiate_vars_c=(* which variables are candidate? depends on a CLI flag *)letvars=if!_instantiate_shieldedthenvars_under_eq_(C.litsc)|>Iter.to_rev_listelsenaked_vars_(C.litsc)inlets_c=0ands_decl=1inCCList.find_map(funv->matchfind_ty_s_decl(T.tyv)s_cwith|None->None|Some(decl,subst)->(* we found an enum type declaration for [v], replace it
with each case for the enum type *)Util.incr_statstat_simplify;letsubst=Unif_subst.of_substsubstinletl=List.map(funcase->(* replace [v] with [case] now *)letsubst=Unif.FO.unify_full~subst(v,s_c)(case,s_decl)inletrenaming=Subst.Renaming.create()inletc_guard=Literals.of_unif_substrenamingsubstandsubst=Unif_subst.substsubstinletlits'=Lits.apply_substrenamingsubst(C.litsc,s_c)inletproof=Proof.Step.inference[Proof.Parent.from@@C.proofc]~rule:(Proof.Rule.mk"enum_type_case_switch")inlettrail=C.trailcandpenalty=C.penaltycinletc'=C.create_a~trail~penalty(CCArray.appendc_guardlits')proofinUtil.debugf~section3"@[<2>deduce @[%a@]@ from @[%a@]@ @[(enum_type switch on %a)@]@]"(funk->kC.ppc'C.ppcType.ppdecl.decl_ty);c')decl.decl_casesinSomel)varsletinstantiate_varsc=ZProf.with_profprof_instantiateinstantiate_vars_cletinstantiate_axiom_~ty_sspoly_argsdecl=ifID.Set.memsdecl.decl_symbolsthenNone(* already declared *)else(letty_args,_=Type.open_funty_sin(* need to add an axiom instance for this symbol and declaration *)decl.decl_symbols<-ID.Set.addsdecl.decl_symbols;(* create the axiom.
- build [subst = decl.x->s(u1,...,u_m)]
where the the [u_i] are variables of the types required by [ty_s]
- evaluate [decl.x = decl.t1 | decl.t2 .... | decl.t_m] in subst
*)letvars=List.mapi(funity->HVar.make~tyi|>T.var)ty_argsinlett=T.app(T.const~ty:ty_ss)varsinUtil.debugf~section5"@[<2>instantiate enum type `%a`@ on `@[%a@]`@]"(funk->kpp_id_or_builtindecl.decl_ty_idT.ppt);letus=bind_vars_(decl,0)(poly_args,1)|>Unif_subst.of_substinletus=Unif.FO.unify_full~subst:us(T.vardecl.decl_var,0)(t,1)inletrenaming=Subst.Renaming.create()inletsubst=Unif_subst.substusandc_guard=Literal.of_unif_substrenamingusinletlits=List.map(funcase->Lit.mk_eq(S.FO.applyrenamingsubst(t,1))(S.FO.applyrenamingsubst(case,0)))decl.decl_casesinletproof=letparent=matchdecl.decl_proofwith|`Data(src,_)->src|`Clausesrc->srcinProof.Step.inference~rule:(Proof.Rule.mk"axiom_enum_types")[Proof.Parent.fromparent]inlettrail=Trail.emptyin(* start with initial penalty *)letpenalty=4inletc'=C.create~trail~penalty(c_guard@lits)proofinUtil.debugf~section3"@[<2>instantiate axiom of enum type `%a` \
on @[%a@]:@ clause @[%a@]@]"(funk->kpp_id_or_builtindecl.decl_ty_idID.ppsC.ppc');Util.incr_statstat_instantiate;Somec')(* assume [s : ty_s] where [ty_s = _ -> ... -> decl.decl_ty_id poly_args]
This builds the axiom
[forall x1...xn.
let t = s x1...xn in
exists y11...y1m. t = c1 y11...y1mn
or exists ..... t = c2 ....
or ....]
where [c1, ..., ck] are the constructors of [decl].
It uses the projectors instead of just skolemizing the "exists" *)letinstantiate_axiom~ty_sspoly_argsdecl=if!_instantiate_projector_axiomtheninstantiate_axiom_~ty_sspoly_argsdeclelseNone(* [check_decl_ id ~ty decl] checks whether [ty] is compatible
with [decl.decl_ty]. If it is the case, let [c a1...an = ty], we
add the axiom
[forall x1:a1...xn:an, id(x1...xn) = t_1[x := id(x1..xn)] or ... or t_m[...]]
where the [t_i] are the cases of [decl] *)letcheck_decl_~tysdecl=let_,ty_ret=Type.open_funtyinmatchType.viewty_ret,decl.decl_ty_idwith|Type.Builtinb,Bb'whenb=b'->instantiate_axiom~ty_s:tys[]decl|Type.App(c,args),IiwhenID.equalci&¬(is_projector_s~of_:i)&&List.lengthargs=List.lengthdecl.decl_ty_vars->instantiate_axiom~ty_s:tysargsdecl|_->None(* add axioms for new symbol [s] with type [ty], if needed *)let_on_new_symbols~ty=letauxi=tryletdecl=find_decl_iincheck_decl_~tysdecl|>CCOpt.to_listwithNot_found->[]inletclauses=let_,ty_ret=Type.open_funtyinmatchType.viewty_retwith|Type.Builtinb->aux(Bb)|Type.App(id,_)->aux(Iid)|_->[]in(* set of support *)PS.ActiveSet.add(Iter.of_listclauses)let_on_new_decldecl=letclauses=Signature.fold(Ctx.signature())[](funaccs(ty,_)->matchcheck_decl_s~tydeclwith|None->acc|Somec->c::acc)inPS.PassiveSet.add(Iter.of_listclauses)letis_trivialc=C.get_flagflag_enumeration_clausec(* detect whether the clause is a declaration of enum type, and if it
is, declare the type! *)let_detect_and_declarec=Util.debugf~section5"@[<2>examine clause@ `@[%a@]`@]"(funk->kC.ppc);matchdetect_declarationcwith|None->()|Some(var,cases)->letty_id=CCOpt.get_exn(as_simple_ty(HVar.tyvar))inletis_new=declare_~ty_id~ty_vars:[]~var~proof:(`Clause(C.proofc))casesin(* clause becomes redundant if it's a new declaration *)matchis_newwith|New_->C.set_flagflag_enumeration_clausectrue|AlreadyDeclared_->()(* introduce projectors for each constructor's argument, in order to
declare the inductive type as an EnumType. *)let_declare_inductive~proofd=Util.debugf~section5"@[<2>examine data `%a`@]"(funk->kID.ppd.Stmt.data_id);(* make HVars *)letty_vars=List.mapi(funi_->HVar.make~ty:Type.tTypei)d.Stmt.data_argsinletty_vars_t=List.mapT.varty_varsinletty_of_var=Type.appd.Stmt.data_id(List.mapType.varty_vars)inletv=HVar.make~ty:ty_of_var(List.lengthd.Stmt.data_args+1)inletv_t=T.varvinletcases=List.map(fun(c_id,c_ty,c_args)->(* declare projector functions for this constructor *)letnum_ty_vars,_,_ty_ret=Type.open_poly_func_tyinassert(num_ty_vars=List.lengthty_vars);letprojs_of_v=List.map(fun(_ty_arg,(proj,ty_proj))->T.app(T.const~ty:ty_projproj)(ty_vars_t@[v_t]))c_argsin(* [c (proj1 v) (proj2 v) ... (proj_n v)] *)T.app(T.const~ty:c_tyc_id)(ty_vars_t@projs_of_v))d.Stmt.data_cstorsinlet_=declare_~proof:(`Data(proof,d))~var:v~ty_id:(Id.Stmt.data_id)~ty_varscasesin()(* detect whether the input statement contains some EnumType declaration *)let_detect_stmtstmt=matchStmt.viewstmtwith|Stmt.Assertc->letproof=Stmt.proof_stepstmtinletc=C.of_forms~trail:Trail.emptycproofin_detect_and_declarec|Stmt.Datal->letproof=Stmt.as_proof_cstmtinList.iter(_declare_inductive~proof)l|Stmt.TyDecl_|Stmt.Def_|Stmt.Rewrite_|Stmt.Lemma_|Stmt.NegatedGoal_|Stmt.Goal_->()letsetup()=if!_enablethen(Util.debug~section1"register handling of enumerated types";Env.add_multi_simpl_rule~priority:5instantiate_vars;Env.add_is_trivialis_trivial;(* look in input statements for inductive types *)Signal.on_everyEnv.on_input_statement_detect_stmt;(* signals: instantiate axioms upon new symbols, or when new
declarations are added *)Signal.on_everyCtx.on_new_symbol(fun(s,ty)->_on_new_symbols~ty);Signal.on_everyon_new_decl(fundecl->_on_new_decldecl;(* need to simplify (instantiate) active clauses that have naked
variables of the given type *)Env.simplify_active_withinstantiate_vars);Signature.iter(Ctx.signature())(funs(ty,_)->_on_new_symbols~ty);)end(** {2 As Extension} *)letextension=letregisterenv=letmoduleE=(valenv:Env.S)inletmoduleET=Make(E)inET.setup()in{Extensions.defaultwithExtensions.name="enum_types";env_actions=[register];}let()=Extensions.registerextension;Params.add_opts["--enum-types",Options.switch_settrue_enable," enable inferences for enumerated/inductive types";"--no-enum-types",Options.switch_setfalse_enable," disable inferences for enumerated/inductive types";"--projector-axioms",Options.switch_settrue_instantiate_projector_axiom," enable exhaustiveness axioms for inductive types (with projectors)";"--no-projector-axioms",Options.switch_setfalse_instantiate_projector_axiom," disable exhaustiveness axioms for inductive types (with projectors)";"--enum-shielded",Options.switch_settrue_instantiate_shielded," enable/disable instantiation of shielded variables of enum type";"--no-enum-shielded",Options.switch_setfalse_instantiate_shielded," enable/disable instantiation of shielded variables of enum type";"--enum-unary",Options.switch_settrue_accept_unary_types," enable support for unary enum types (one case)";"--no-enum-unary",Options.switch_setfalse_accept_unary_types," disable support for unary enum types (one case)"]