123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578(************************************************************************)(* * 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) *)(************************************************************************)moduleCVars=VarsopenPpopenCErrorsopenUtilopenNamesopenNameopsopenConstropenContextopenTermopsopenEnvironopenEConstropenVarsopenNamegenopenDeclarationsopenReductionopsopenTacredopenGenredexpropenTacmachopenLogicopenClenvopenTacticalsopenRocqlibopenEvarutilopenIndrecopenUnificationopenLocusopenLocusopsopenTactypesopenProofview.NotationsopenTacticsmoduleNamedDecl=Context.Named.DeclarationlettclEVARS=Proofview.Unsafe.tclEVARSletinj_with_occurrencese=(AllOccurrences,e)lettyp_ofenvsigmac=letopenRetypingintryget_type_of~lax:trueenvsigmacwithRetypeErrore->user_err(print_retype_errore)(*********************************************)(* Errors *)(*********************************************)exceptionAlreadyUsedofId.texceptionSchemeDontApplyexceptionNeedFullyAppliedArgumentexceptionNotAnInductionSchemeofstringexceptionNotAnInductionSchemeLetInexceptionCannotFindInductiveArgumentexceptionMentionConclusionDependentOnofId.texceptionDontKnowWhatToDoWithofintro_pattern_naming_exprexceptionUnsupportedWithClauseexceptionUnsupportedEqnClauseexceptionUnsupportedInClauseofboolexceptionDontKnowWhereToFindArgumentexceptionMultipleAsAndUsingClauseOnlyListleterror?loce=Loc.raise?loceexceptionUnhandledletwrap_unhandledfe=trySome(fe)withUnhandled->Nonelettactic_interp_error_handler=function|AlreadyUsedid->Id.printid++str" is already used."|SchemeDontApply->str"Scheme cannot be applied."|NeedFullyAppliedArgument->str"Need a fully applied argument."|NotAnInductionSchemes->lets=ifnot(String.is_emptys)thens^" "elsesinstr"Cannot recognize "++strs++str"an induction scheme."|NotAnInductionSchemeLetIn->str"Strange letin, cannot recognize an induction scheme."|CannotFindInductiveArgument->str"Cannot find inductive argument of elimination scheme."|MentionConclusionDependentOnid->str"Conclusion must be mentioned: it depends on "++Id.printid++str"."|DontKnowWhatToDoWithid->str"Do not know what to do with "++Miscprint.pr_intro_pattern_namingid|UnsupportedEqnClause->str"'eqn' clause not supported here."|UnsupportedWithClause->str"'with' clause not supported here."|UnsupportedInClauseb->str(ifbthen"'in' clause not supported here."else"'eqn' clause not supported here.")|DontKnowWhereToFindArgument->str"Don't know where to find some argument."|MultipleAsAndUsingClauseOnlyList->str"'as' clause with multiple arguments and 'using' clause can only occur last."|_->raiseUnhandledlet_=CErrors.register_handler(wrap_unhandledtactic_interp_error_handler)letfresh_id_in_envavoididenv=letavoid'=ids_of_named_context_val(named_context_valenv)inletavoid=ifId.Set.is_emptyavoidthenavoid'elseId.Set.unionavoid'avoidinnext_ident_away_in_goal(Global.env())idavoidletnew_fresh_idavoididgl=fresh_id_in_envavoidid(Proofview.Goal.envgl)letintros_until_nn=Tactics.try_intros_until(fun_->tclIDTAC)(AnonHypn)lettry_intros_until_id_checkid=Tactics.try_intros_until(fun_->tclIDTAC)(NamedHyp(CAst.makeid))(* Apply a tactic on a quantified hypothesis, an hypothesis in context
or a term with bindings *)lettactic_infer_flagswith_evar=Pretyping.{use_coercions=true;use_typeclasses=UseTC;solve_unification_constraints=true;fail_evar=notwith_evar;expand_evars=true;program_mode=false;polymorphic=false;undeclared_evars_patvars=false;patvars_abstract=false;unconstrained_sorts=false;}letonOpenInductionArgenvsigmatac=function|clear_flag,ElimOnConstrf->let(sigma',cbl)=fenvsigmainTacticals.tclTHEN(Proofview.Unsafe.tclEVARSsigma')(tacclear_flag(Somesigma,cbl))|clear_flag,ElimOnAnonHypn->Tacticals.tclTHEN(intros_until_nn)(Tacticals.onLastHyp(func->Proofview.Goal.enterbeginfungl->letsigma=Tacmach.projectglintacclear_flag(Somesigma,(c,NoBindings))end))|clear_flag,ElimOnIdent{CAst.v=id}->(* A quantified hypothesis *)Tacticals.tclTHEN(try_intros_until_id_checkid)(Proofview.Goal.enterbeginfungl->letsigma=Tacmach.projectglintacclear_flag(Somesigma,(mkVarid,NoBindings))end)letwith_no_bindings(c,lbind)=iflbind!=NoBindingsthenerrorUnsupportedWithClause;c(********************************************)(* Elimination tactics *)(********************************************)letindex_of_ind_argsigmat=letrecauxijt=matchEConstr.kindsigmatwith|LetIn(_,_,_,t)->auxijt|Prod(_,t,u)->(* heuristic *)ifisIndsigma(fst(decompose_appsigmat))thenaux(Somej)(j+1)uelseauxi(j+1)u|_->matchiwith|Somei->i|None->errorCannotFindInductiveArgumentinauxNone0t(*
* Elimination tactic with bindings and using an arbitrary
* elimination constant called elimc. This constant should end
* with a clause (x:I)(P .. ), where P is a bound variable.
* The term c is of type t, which is a product ending with a type
* matching I, lbindc are the expected terms for c arguments
*)typeeliminator=|ElimConstantof(Constant.t*EInstance.t)(* Constant generated by a scheme function *)|ElimClauseofEConstr.constrwith_bindings(* Arbitrary expression provided by the user *)letis_nonrecenvmind=(Environ.lookup_mind(fstmind)env).mind_finite==Declarations.BiFiniteletfind_ind_eliminatorenvsigmainds=letc=lookup_eliminatorenvindsinletsigma,c=EConstr.fresh_globalenvsigmacinsigma,destConstsigmacletclear_wildcards=Tactics.Internal.clear_wildcards(*****************************)(* Decomposing introductions *)(*****************************)letinsert_beforedeclslasthypenv=matchlasthypwith|None->push_named_contextdeclsenv|Someid->Environ.fold_named_context(fun_denv->letd=EConstr.of_named_decldinletenv=ifId.equalid(NamedDecl.get_idd)thenpush_named_contextdeclsenvelseenvinpush_nameddenv)~init:(reset_contextenv)envletmk_eq_nameenvid{CAst.loc;v=ido}=matchidowith|IntroAnonymous->fresh_id_in_env(Id.Set.singletonid)(add_prefix"Heq"id)env|IntroFreshheq_base->fresh_id_in_env(Id.Set.singletonid)heq_baseenv|IntroIdentifierid->ifList.memid(ids_of_named_context(named_contextenv))thenerror(AlreadyUsedid);id(* unsafe *)letmkletin_goalenvsigmawith_eqdep(id,lastlhyp,ccl,c)ty=letopenContext.Named.Declarationinlett=matchtywithSomet->t|_->typ_ofenvsigmacinletr=Retyping.relevance_of_typeenvsigmatinletdecl=ifdepthenLocalDef(make_annotidr,c,t)elseLocalAssum(make_annotidr,t)inmatchwith_eqwith|Some(lr,heq)->leteqdata=build_rocq_eq_data()inletargs=iflrthen[mkVarid;c]else[c;mkVarid]inlet(sigma,eq)=Evd.fresh_globalenvsigmaeqdata.eqinletrefl=mkRef(eqdata.refl,snd(destRefsigmaeq))in(* NB we are not in the right env for [id] so we only check the partial application.
This is enough to produce the desired univ constraint between univ of eq and univ of t *)letsigma,eq=Typing.checked_applistenvsigmaeq[t]inleteq=applist(eq,args)inletrefl=applist(refl,[t;mkVarid])inletnewenv=insert_before[LocalAssum(make_annotheqERelevance.relevant,eq);decl]lastlhypenvinlet(sigma,x)=new_evarnewenvsigmacclin(sigma,mkNamedLetInsigma(make_annotidr)ct(mkNamedLetInsigma(make_annotheqERelevance.relevant)refleqx),Some(fst@@destEvarsigmax))|None->letnewenv=insert_before[decl]lastlhypenvinlet(sigma,x)=new_evarnewenvsigmacclin(sigma,mkNamedLetInsigma(make_annotidr)ctx,Some(fst@@destEvarsigmax))letwarn_cannot_remove_as_expected=CWarnings.create~name:"cannot-remove-as-expected"~category:CWarnings.CoreCategories.tactics(fun(id,inglobal)->letpp=matchinglobalwith|None->mt()|Someref->str", it is used implicitly in "++Printer.pr_globalrefinstr"Cannot remove "++Id.printid++pp++str".")letclear_for_destructids=Proofview.tclORELSE(Tactics.Internal.clear_gen(funenvsigmaiderringlobal->raise(ClearDependencyError(id,err,inglobal)))ids)(function|ClearDependencyError(id,err,inglobal),_->warn_cannot_remove_as_expected(id,inglobal);Proofview.tclUNIT()|e->Exninfo.iraisee)(* Either unfold and clear if defined or simply clear if not a definition *)letexpand_hypid=Tacticals.tclTRY(Tactics.unfold_bodyid)<*>clear_for_destruct[id](*****************************)(* High-level induction *)(*****************************)(*
* A "natural" induction tactic
*
- [H0:T0, ..., Hi:Ti, hyp0:P->I(args), Hi+1:Ti+1, ..., Hn:Tn |-G] is the goal
- [hyp0] is the induction hypothesis
- we extract from [args] the variables which are not rigid parameters
of the inductive type, this is [indvars] (other terms are forgotten);
- we look for all hyps depending of [hyp0] or one of [indvars]:
this is [dephyps] of types [deptyps] respectively
- [statuslist] tells for each hyps in [dephyps] after which other hyp
fixed in the context they must be moved (when induction is done)
- [hyp0succ] is the name of the hyp fixed in the context after which to
move the subterms of [hyp0succ] in the i-th branch where it is supposed
to be the i-th constructor of the inductive type.
Strategy: (cf in [induction_with_atomization_of_ind_arg])
- requantify and clear all [dephyps]
- apply induction on [hyp0]
- clear those of [indvars] that are variables and [hyp0]
- in the i-th subgoal, intro the arguments of the i-th constructor
of the inductive type after [hyp0succ] (done in
[induct_discharge]) let the induction hypotheses on top of the
hyps because they may depend on variables between [hyp0] and the
top. A counterpart is that the dep hyps programmed to be intro-ed
on top must now be intro-ed after the induction hypotheses
- move each of [dephyps] at the right place following the
[statuslist]
*)letwarn_unused_intro_pattern=CWarnings.create~name:"unused-intro-pattern"~category:CWarnings.CoreCategories.tactics(fun(env,sigma,names)->strbrk"Unused introduction "++str(String.plural(List.lengthnames)"pattern")++str": "++prlist_with_sepspc(Miscprint.pr_intro_pattern(func->Printer.pr_econstr_envenvsigma(snd(cenvsigma))))names)letcheck_unused_namesenvsigmanames=ifnot(List.is_emptynames)thenwarn_unused_intro_pattern(env,sigma,names)letintropattern_of_nameglavoid=function|Anonymous->IntroNamingIntroAnonymous|Nameid->IntroNaming(IntroIdentifier(new_fresh_idavoididgl))letrecconsume_patternavoidnaisdepgl=letopenCAstinfunction|[]->((CAst.make@@intropattern_of_nameglavoidna),[])|{loc;v=IntroForthcomingtrue}::nameswhennotisdep->consume_patternavoidnaisdepglnames|{loc;v=IntroForthcoming_}::namesasfullpat->(CAst.make?loc@@intropattern_of_nameglavoidna,fullpat)|{loc;v=IntroNamingIntroAnonymous}::names->(CAst.make?loc@@intropattern_of_nameglavoidna,names)|{loc;v=IntroNaming(IntroFreshid')}::names->(CAst.make?loc@@IntroNaming(IntroIdentifier(new_fresh_idavoidid'gl)),names)|pat::names->(pat,names)letre_intro_dependent_hypotheses(lstatus,rstatus)(_,tophyp)=lettophyp=matchtophypwithNone->MoveLast|Somehyp->MoveAfterhypinletnewlstatus=(* if some IH has taken place at the top of hyps *)List.map(function(hyp,MoveLast)->(hyp,tophyp)|x->x)lstatusinTacticals.tclTHEN(Tactics.intros_moverstatus)(Tactics.intros_movenewlstatus)letdest_intro_patterns=Tactics.Internal.dest_intro_patternsletsafe_dest_intro_patternswith_evarsavoidthindestpattac=Proofview.tclORELSE(dest_intro_patternswith_evarsavoidthindestpattac)beginfunction(e,info)->matchewith|CannotMoveHyp_->(* May happen e.g. with "destruct x using s" with an hypothesis
which is morally an induction hypothesis to be "MoveLast" if
known as such but which is considered instead as a subterm of
a constructor to be move at the place of x. *)dest_intro_patternswith_evarsavoidthinMoveLastpattac|e->Proofview.tclZERO~infoeendtypeelim_arg_kind=RecArg|IndArg|OtherArgtypebranch_argument={ba_kind:elim_arg_kind;ba_assum:bool;ba_dep:bool;ba_name:Id.t;}typerecarg_position=|AfterFixedPositionofId.toption(* None = top of context *)letupdate_dest(recargdests,tophypasdests)=function|[]->dests|hyp::_->(matchrecargdestswith|AfterFixedPositionNone->AfterFixedPosition(Somehyp)|x->x),(matchtophypwithNone->Somehyp|x->x)letget_recarg_dest(recargdests,tophyp)=matchrecargdestswith|AfterFixedPositionNone->MoveLast|AfterFixedPosition(Someid)->MoveAfterid(* Current policy re-introduces recursive arguments of destructed
variable at the place of the original variable while induction
hypothesese are introduced at the top of the context. Since in the
general case of an inductive scheme, the induction hypotheses can
arrive just after the recursive arguments (e.g. as in "forall
t1:tree, P t1 -> forall t2:tree, P t2 -> P (node t1 t2)", we need
to update the position for t2 after "P t1" is introduced if ever t2
had to be introduced at the top of the context).
*)letinduct_dischargewith_evarsdestsavoid'tac(avoid,ra)names=letavoid=Id.Set.unionavoid'(Id.Set.unionavoid(Tactics.Internal.explicit_intro_namesnames))inletrecpeel_tacradestsnamesthin=matchrawith|({ba_kind=RecArg}asrarg)::({ba_kind=IndArg}asiarg)::ra'->Proofview.Goal.enterbeginfungl->let(recpat,names)=matchnameswith|[{CAst.loc;v=IntroNaming(IntroIdentifierid)}aspat]->letid'=new_fresh_idavoid(add_prefix"IH"id)glin(pat,[CAst.make@@IntroNaming(IntroIdentifierid')])|_->consume_patternavoid(Namerarg.ba_name)rarg.ba_depglnamesinletdest=get_recarg_destdestsindest_intro_patternswith_evarsavoidthindest[recpat](funidsthin->Proofview.Goal.enterbeginfungl->let(hyprec,names)=consume_patternavoid(Nameiarg.ba_name)iarg.ba_depglnamesindest_intro_patternswith_evarsavoidthinMoveLast[hyprec](funids'thin->peel_tacra'(update_destdestsids')namesthin)end)end|({ba_kind=IndArg}asiarg)::ra'->Proofview.Goal.enterbeginfungl->(* Rem: does not happen in Rocq schemes, only in user-defined schemes *)letpat,names=consume_patternavoid(Nameiarg.ba_name)iarg.ba_depglnamesindest_intro_patternswith_evarsavoidthinMoveLast[pat](funidsthin->peel_tacra'(update_destdestsids)namesthin)end|({ba_kind=RecArg}asrarg)::ra'->Proofview.Goal.enterbeginfungl->let(pat,names)=consume_patternavoid(Namerarg.ba_name)rarg.ba_depglnamesinletdest=get_recarg_destdestsindest_intro_patternswith_evarsavoidthindest[pat](funidsthin->peel_tacra'destsnamesthin)end|({ba_kind=OtherArg}asoarg)::ra'->Proofview.Goal.enterbeginfungl->let(pat,names)=consume_patternavoidAnonymousoarg.ba_depglnamesinletdest=get_recarg_destdestsinsafe_dest_intro_patternswith_evarsavoidthindest[pat](funidsthin->peel_tacra'destsnamesthin)end|[]->Proofview.Goal.enterbeginfungl->letenv=Proofview.Goal.envglinletsigma=Proofview.Goal.sigmaglincheck_unused_namesenvsigmanames;Tacticals.tclTHEN(clear_wildcardsthin)(tacdests)endinpeel_tacra(dests,None)names[](* - le recalcul de indtyp à chaque itération de atomize_one est pour ne pas
s'embêter à regarder si un letin_tac ne fait pas des
substitutions aussi sur l'argument voisin *)letexpand_projectionsenvsigmac=letrecauxenvc=matchEConstr.kindsigmacwith|Proj(p,_,c)->Retyping.expand_projectionenvsigmap(auxenvc)[]|_->map_constr_with_full_bindersenvsigmapush_relauxenvcinauxenvc(* Marche pas... faut prendre en compte l'occurrence précise... *)letatomize_param_of_indenvsigma(hd,params,indices)=letparams'=List.map(expand_projectionsenvsigma)paramsin(* le gl est important pour ne pas préévaluer *)letrecatomize_oneaccuargsargs'avoid=function|[]->lett=applist(hd,params@args)in(List.revaccu,avoid,t)|c::rem->matchEConstr.kindsigmacwith|Varidwhennot(List.exists(func->occur_varenvsigmaidc)args')&¬(List.exists(func->occur_varenvsigmaidc)params')->(* Based on the knowledge given by the user, all
constraints on the variable are generalizable in the
current environment so that it is clearable after destruction *)atomize_oneaccu(c::args)(c::args')(Id.Set.addidavoid)rem|_->letc'=expand_projectionsenvsigmacinletdependentt=dependentsigmactinifList.existsdependentparams'||List.existsdependentargs'then(* This is a case where the argument is constrained in a
way which would require some kind of inversion; we
follow the (old) discipline of not generalizing over
this term, since we don't try to invert the
constraint anyway. *)atomize_oneaccu(c::args)(c'::args')avoidremelse(* We reason blindly on the term and do as if it were
generalizable, ignoring the constraints coming from
its structure *)letid=matchEConstr.kindsigmacwith|Varid->id|_->letty=Retyping.get_type_ofenvsigmacinid_of_name_using_hdcharenvsigmatyAnonymousinletx=fresh_id_in_envavoididenvinletaccu=(x,c)::accuinatomize_oneaccu(mkVarx::args)(mkVarx::args')(Id.Set.addxavoid)reminatomize_one[][][]Id.Set.empty(List.revindices)(* [cook_sign] builds the lists [beforetoclear] (preceding the
ind. var.) and [aftertoclear] (coming after the ind. var.) of hyps
that must be erased, the lists of hyps to be generalize [decldeps] on the
goal together with the places [(lstatus,rstatus)] where to re-intro
them after induction. To know where to re-intro the dep hyp, we
remember the name of the hypothesis [lhyp] after which (if the dep
hyp is more recent than [hyp0]) or [rhyp] before which (if older
than [hyp0]) its equivalent must be moved when the induction has
been applied. Since computation of dependencies and [rhyp] is from
more ancient (on the right) to more recent hyp (on the left) but
the computation of [lhyp] progresses from the other way, [cook_hyp]
is in two passes (an alternative would have been to write an
higher-order algorithm). We use references to reduce
the accumulation of arguments.
To summarize, the situation looks like this
Goal(n,x) -| H6:(Q n); x:A; H5:True; H4:(le O n); H3:(P n); H2:True; n:nat
Left Right
Induction hypothesis is H4 ([hyp0])
Variable parameters of (le O n) is the singleton list with "n" ([indvars])
The dependent hyps are H3 and H6 ([dephyps])
For H3 the memorized places are H5 ([lhyp]) and H2 ([rhyp])
because these names are among the hyp which are fixed through the induction
For H6 the neighbours are None ([lhyp]) and H5 ([rhyp])
For H3, because on the right of H4, we remember rhyp (here H2)
For H6, because on the left of H4, we remember lhyp (here None)
For H4, we remember lhyp (here H5)
The right neighbour is then translated into the left neighbour
because move_hyp tactic needs the name of the hyp _after_ which we
move the hyp to move.
But, say in the 2nd subgoal of the hypotheses, the goal will be
(m:nat)((P m)->(Q m)->(Goal m)) -> (P Sm)-> (Q Sm)-> (Goal Sm)
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^
both go where H4 was goes where goes where
H3 was H6 was
We have to intro and move m and the recursive hyp first, but then
where to move H3 ??? Only the hyp on its right is relevant, but we
have to translate it into the name of the hyp on the left
Note: this case where some hyp(s) in [dephyps] has(have) the same
left neighbour as [hyp0] is the only problematic case with right
neighbours. For the other cases (e.g. an hyp H1:(R n) between n and H2
would have posed no problem. But for uniformity, we decided to use
the right hyp for all hyps on the right of H4.
Other solutions are welcome
PC 9 fev 06: Adapted to accept multi argument principle with no
main arg hyp. hyp0 is now optional, meaning that it is possible
that there is no main induction hypotheses. In this case, we
consider the last "parameter" (in [indvars]) as the limit between
"left" and "right", BUT it must be included in indhyps.
Other solutions are still welcome
*)exceptionShuntofId.tmove_locationletcook_signhyp0_optinhypsindvarsenvsigma=(* First phase from L to R: get [toclear], [decldep] and [statuslist]
for the hypotheses before (= more ancient than) hyp0 (see above) *)lettoclear=ref[]inletavoid=refId.Set.emptyinletdecldeps=ref[]inletldeps=ref[]inletrstatus=ref[]inletlstatus=ref[]inletbefore=reftrueinletmaindep=reffalseinletseek_depsenvdeclrhyp=letdecl=EConstr.of_named_decldeclinlethyp=NamedDecl.get_iddeclinif(matchhyp0_optwithSomehyp0->Id.equalhyphyp0|_->false)thenbeginbefore:=false;(* Note that if there was no main induction hypotheses, then hyp
is one of indvars too *)toclear:=hyp::!toclear;MoveFirst(* fake value *)endelseifId.Set.memhypindvarsthenbegin(* The variables in indvars are such that they don't occur any
more after generalization, so declare them to clear. *)toclear:=hyp::!toclear;rhypendelseletdephyp0=not!before&&List.is_emptyinhyps&&(Option.cata(funid->occur_var_in_declenvsigmaiddecl)falsehyp0_opt)inletdepother=List.is_emptyinhyps&&(Id.Set.exists(funid->occur_var_in_declenvsigmaiddecl)indvars||List.exists(fundecl'->occur_var_in_declenvsigma(NamedDecl.get_iddecl')decl)!decldeps)inifnot(List.is_emptyinhyps)&&Id.List.memhypinhyps||dephyp0||depotherthenbegindecldeps:=decl::!decldeps;avoid:=Id.Set.addhyp!avoid;maindep:=dephyp0||!maindep;if!beforethenbegintoclear:=hyp::!toclear;rstatus:=(hyp,rhyp)::!rstatusendelsebegintoclear:=hyp::!toclear;ldeps:=hyp::!ldeps(* status computed in 2nd phase *)end;MoveBeforehypendelseMoveBeforehypinlet_=fold_named_contextseek_depsenv~init:MoveFirstin(* 2nd phase from R to L: get left hyp of [hyp0] and [lhyps] *)letcompute_lstatuslhypdecl=lethyp=NamedDecl.get_iddeclinif(matchhyp0_optwithSomehyp0->Id.equalhyphyp0|_->false)thenraise(Shuntlhyp);ifId.List.memhyp!ldepsthenbeginlstatus:=(hyp,lhyp)::!lstatus;lhypendelseifId.List.memhyp!toclearthenlhypelseMoveAfterhypintrylet_=fold_named_context_reversecompute_lstatus~init:MoveLastenvinraise(ShuntMoveLast)(* ?? FIXME *)withShuntlhyp0->letlhyp0=matchlhyp0with|MoveLast->None|MoveAfterhyp->Somehyp|_->assertfalseinletstatuslists=(!lstatus,List.rev!rstatus)inletrecargdests=AfterFixedPosition(ifOption.is_emptyhyp0_optthenNoneelselhyp0)in(statuslists,recargdests,!toclear,!decldeps,!avoid,!maindep)(*
The general form of an induction principle is the following:
forall prm1 prm2 ... prmp, (induction parameters)
forall Q1...,(Qi:Ti_1 -> Ti_2 ->...-> Ti_ni),...Qq, (predicates)
branch1, branch2, ... , branchr, (branches of the principle)
forall (x1:Ti_1) (x2:Ti_2) ... (xni:Ti_ni), (induction arguments)
(HI: I prm1..prmp x1...xni) (optional main induction arg)
-> (Qi x1...xni HI (f prm1...prmp x1...xni)).(conclusion)
^^ ^^^^^^^^^^^^^^^^^^^^^^^^
optional optional argument added if
even if HI principle generated by functional
present above induction, only if HI does not exist
[indarg] [farg]
HI is not present when the induction principle does not come directly from an
inductive type (like when it is generated by functional induction for
example). HI is present otherwise BUT may not appear in the conclusion
(dependent principle). HI and (f...) cannot be both present.
Principles taken from functional induction have the final (f...).*)(* [rel_contexts] and [rel_declaration] actually contain triples, and
lists are actually in reverse order to fit [compose_prod]. *)typeelim_scheme={elimt:types;indref:GlobRef.toption;params:rel_context;(* (prm1,tprm1);(prm2,tprm2)...(prmp,tprmp) *)nparams:int;(* number of parameters *)predicates:rel_context;(* (Qq, (Tq_1 -> Tq_2 ->...-> Tq_nq)), (Q1,...) *)npredicates:int;(* Number of predicates *)branches:rel_context;(* branchr,...,branch1 *)nbranches:int;(* Number of branches *)args:rel_context;(* (xni, Ti_ni) ... (x1, Ti_1) *)nargs:int;(* number of arguments *)indarg:rel_declarationoption;(* Some (H,I prm1..prmp x1...xni)
if HI is in premisses, None otherwise *)concl:types;(* Qi x1...xni HI (f...), HI and (f...)
are optional and mutually exclusive *)indarg_in_concl:bool;(* true if HI appears at the end of conclusion *)farg_in_concl:bool;(* true if (f...) appears at the end of conclusion *)}letempty_scheme={elimt=mkProp;indref=None;params=[];nparams=0;predicates=[];npredicates=0;branches=[];nbranches=0;args=[];nargs=0;indarg=None;concl=mkProp;indarg_in_concl=false;farg_in_concl=false;}letmake_basenid=ifInt.equaln0||Int.equaln1thenidelse(* This extends the name to accept new digits if it already ends with *)(* digits *)Id.of_string(atompart_of_id(make_ident(Id.to_stringid)(Some0)))(* Builds two different names from an optional inductive type and a
number, also deals with a list of names to avoid. If the inductive
type is None, then hyprecname is IHi where i is a number. *)letmake_up_namesnind_optcname=letis_hyp=String.equal(atompart_of_idcname)"H"inletbase=Id.to_string(make_basencname)inletind_prefix="IH"inletbase_ind=ifis_hypthenmatchind_optwith|None->Id.of_stringind_prefix|Someind_id->add_prefixind_prefix(Nametab.basename_of_globalind_id)elseadd_prefixind_prefixcnameinlethyprecname=make_basenbase_indinletavoid=ifInt.equaln1(* Only one recursive argument *)||Int.equaln0thenId.Set.emptyelse(* Forbid to use cname, cname0, hyprecname and hyprecname0 *)(* in order to get names such as f1, f2, ... *)letavoid=Id.Set.add(make_ident(Id.to_stringhyprecname)None)(Id.Set.singleton(make_ident(Id.to_stringhyprecname)(Some0)))inifnot(String.equal(atompart_of_idcname)"H")thenId.Set.add(make_identbase(Some0))(Id.Set.add(make_identbaseNone)avoid)elseavoidinId.of_stringbase,hyprecname,avoidleterror_ind_schemes=error(NotAnInductionSchemes)letoccur_relsigmanc=letres=not(noccurnsigmanc)inres(* This function splits the products of the induction scheme [elimt] into four
parts:
- branches, easily detectable (they are not referred by rels in the subterm)
- what was found before branches (acc1) that is: parameters and predicates
- what was found after branches (acc3) that is: args and indarg if any
if there is no branch, we try to fill in acc3 with args/indargs.
We also return the conclusion.
*)letdecompose_paramspred_branch_argssigmaelimt=letopenContext.Rel.Declarationinletreccut_noccurelimtacc2=matchEConstr.kindsigmaelimtwith|Prod(nme,tpe,elimt')->lethd_tpe,_=decompose_appsigma(snd(decompose_prod_declssigmatpe))inifnot(occur_relsigma1elimt')&&isRelsigmahd_tpethencut_noccurelimt'(LocalAssum(nme,tpe)::acc2)elseletacc3,ccl=decompose_prod_declssigmaelimtinacc2,acc3,ccl|App(_,_)|Rel_->acc2,[],elimt|_->error_ind_scheme""inletreccut_occurelimtacc1=matchEConstr.kindsigmaelimtwith|Prod(nme,tpe,c)whenoccur_relsigma1c->cut_occurc(LocalAssum(nme,tpe)::acc1)|Prod(nme,tpe,c)->letacc2,acc3,ccl=cut_noccurelimt[]inacc1,acc2,acc3,ccl|App(_,_)|Rel_->acc1,[],[],elimt|_->error_ind_scheme""inletacc1,acc2,acc3,ccl=cut_occurelimt[]in(* Particular treatment when dealing with a dependent empty type elim scheme:
if there is no branch, then acc1 contains all hyps which is wrong (acc1
should contain parameters and predicate only). This happens for an empty
type (See for example Empty_set_ind, as False would actually be ok). Then
we must find the predicate of the conclusion to separate params_pred from
args. We suppose there is only one predicate here. *)matchacc2with|[]->lethyps,ccl=decompose_prod_declssigmaelimtinlethd_ccl_pred,_=decompose_appsigmacclinbeginmatchEConstr.kindsigmahd_ccl_predwith|Reli->letacc3,acc1=List.chop(i-1)hypsinacc1,[],acc3,ccl|_->error_ind_scheme""end|_->acc1,acc2,acc3,cclletexchange_hd_appsigmasubst_hdt=lethd,args=decompose_appsigmatinmkApp(subst_hd,args)(* Builds an elim_scheme from its type and calling form (const+binding). We
first separate branches. We obtain branches, hyps before (params + preds),
hyps after (args <+ indarg if present>) and conclusion. Then we proceed as
follows:
- separate parameters and predicates in params_preds. For that we build:
forall (x1:Ti_1)(xni:Ti_ni) (HI:I prm1..prmp x1...xni), DUMMY x1...xni HI/farg
^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^
optional opt
Free rels appearing in this term are parameters (branches should not
appear, and the only predicate would have been Qi but we replaced it by
DUMMY). We guess this heuristic catches all params. TODO: generalize to
the case where args are merged with branches (?) and/or where several
predicates are cited in the conclusion.
- finish to fill in the elim_scheme: indarg/farg/args and finally indref. *)letcompute_elim_sigsigmaelimt=letopenContext.Rel.Declarationinletparams_preds,branches,args_indargs,conclusion=decompose_paramspred_branch_argssigmaelimtinletccl=exchange_hd_appsigma(mkVar(Id.of_string"__QI_DUMMY__"))conclusioninletconcl_with_args=it_mkProd_or_LetIncclargs_indargsinletnparams=Int.Set.cardinal(free_relssigmaconcl_with_args)inletpreds,params=List.chop(List.lengthparams_preds-nparams)params_predsin(* A first approximation, further analysis will tweak it *)letres=ref{empty_schemewith(* This fields are ok: *)elimt=elimt;concl=conclusion;predicates=preds;npredicates=List.lengthpreds;branches=branches;nbranches=List.lengthbranches;farg_in_concl=isAppsigmaccl&&isAppsigma(last_argsigmaccl);params=params;nparams=nparams;(* all other fields are unsure at this point. Including these:*)args=args_indargs;nargs=List.lengthargs_indargs;}intry(* Order of tests below is important. Each of them exits if successful. *)(* 1- First see if (f x...) is in the conclusion. *)if!res.farg_in_conclthenbeginres:={!reswithindarg=None;indarg_in_concl=false;farg_in_concl=true};raise_notraceExitend;(* 2- If no args_indargs (=!res.nargs at this point) then no indarg *)ifInt.equal!res.nargs0thenraise_notraceExit;(* 3- Look at last arg: is it the indarg? *)ignore(matchList.hdargs_indargswith|LocalDef(hiname,_,hi)->error_ind_scheme""|LocalAssum(hiname,hi)->lethi_ind,hi_args=decompose_appsigmahiinlethi_is_ind=(* hi est d'un type globalisable *)matchEConstr.kindsigmahi_indwith|Ind(mind,_)->true|Var_->true|Const_->true|Construct_->true|_->falseinlethi_args_enough=(* hi a le bon nbre d'arguments *)Int.equal(Array.lengthhi_args)(List.lengthparams+!res.nargs-1)in(* FIXME: Ces deux tests ne sont pas suffisants. *)ifnot(hi_is_ind&&hi_args_enough)thenraise_notraceExit(* No indarg *)else(* Last arg is the indarg *)res:={!reswithindarg=Some(List.hd!res.args);indarg_in_concl=occur_relsigma1ccl;args=List.tl!res.args;nargs=!res.nargs-1;};raise_notraceExit);raise_notraceExit(* exit anyway *)withExit->(* Ending by computing indref: *)match!res.indargwith|None->!res(* No indref *)|Some(LocalDef_)->error_ind_scheme""|Some(LocalAssum(_,ind))->letindhd,indargs=decompose_appsigmaindintry{!reswithindref=Some(fst(destRefsigmaindhd))}withDestKO->errorCannotFindInductiveArgumentletcompute_scheme_signatureevdschemenames_infoind_type_guess=letopenContext.Rel.Declarationinletf,l=decompose_appevdscheme.conclin(* Vérifier que les arguments de Qi sont bien les xi. *)letcond,check_concl=matchscheme.indargwith|Some(LocalDef_)->errorNotAnInductionSchemeLetIn|None->(* Non standard scheme *)letcondhd=EConstr.eq_constrevdhdind_type_guess&¬scheme.farg_in_conclin(cond,fun__->())|Some(LocalAssum(_,ind))->(* Standard scheme from an inductive type *)letindhd,indargs=decompose_app_listevdindinletcondhd=EConstr.eq_constrevdhdindhdinletcheck_conclis_predp=(* Check again conclusion *)letccl_arg_ok=is_pred(p+scheme.nargs+1)f==IndArginletind_is_ok=List.equal(func1c2->EConstr.eq_constrevdc1c2)(List.lastnscheme.nargsindargs)(Context.Rel.instance_listmkRel0scheme.args)inifnot(ccl_arg_ok&&ind_is_ok)thenerror_ind_scheme"the conclusion of"in(cond,check_concl)inletis_prednc=lethd=fst(decompose_appevdc)inmatchEConstr.kindevdhdwith|Relqwhenn<q&&q<=n+scheme.npredicates->IndArg|_whencondhd->RecArg|_->OtherArginletreccheck_branchpc=matchEConstr.kindevdcwith|Prod(_,t,c)->(is_predpt,true,not(Vars.noccurnevd1c))::check_branch(p+1)c|LetIn(_,_,_,c)->(OtherArg,false,not(Vars.noccurnevd1c))::check_branch(p+1)c|_whenis_predpc==IndArg->[]|_->raise_notraceExitinletrecfind_branchesplbrch=matchlbrchwith|LocalAssum(_,t)::brs->beginmatchcheck_branchptwith|lchck_brch->letn=List.count(fun(b,_,_)->b==RecArg)lchck_brchinletrecvarname,hyprecname,avoid=make_up_namesnscheme.indrefnames_infoinletmap(b,is_assum,dep)={ba_kind=b;ba_assum=is_assum;ba_dep=dep;ba_name=ifb==IndArgthenhyprecnameelserecvarname;}inletnamesign=List.mapmaplchck_brchin(avoid,namesign)::find_branches(p+1)brs|exceptionExit->error_ind_scheme"the branches of"end|LocalDef_::_->error_ind_scheme"the branches of"|[]->check_conclis_predp;[]inArray.of_list(find_branches0(List.revscheme.branches))letcompute_case_signatureenvminddepnames_info=letindref=GlobRef.IndRefmindinletreccheck_branchc=matchConstr.kindcwith|Prod(_,t,c)->lethd,_=Constr.decompose_apptin(* no recursive call in case analysis *)letarg=ifConstr.isRefXindrefhdthenRecArgelseOtherArgin(arg,true,not(CVars.noccurn1c))::check_branchc|LetIn(_,_,_,c)->(OtherArg,false,not(CVars.noccurn1c))::check_branchc|_->[]inlet(mib,mip)=Inductive.lookup_mind_specifenvmindinletfind_branchesk=let(ctx,typ)=mip.mind_nf_lc.(k)inletargctx=List.firstnmip.mind_consnrealdecls.(k)ctxinlet_,args=Constr.decompose_apptypinlet_,indices=Array.chopmib.mind_nparamsargsinletbase=ifdepthenArray.appendindices(Context.Rel.instanceConstr.mkRel0argctx)elseindicesinletbase=Constr.mkApp(Constr.mkProp,base)in(* only used for noccurn *)letlchck_brch=check_branch(Term.it_mkProd_or_LetInbaseargctx)inletn=List.count(fun(b,_,_)->b==RecArg)lchck_brchinletrecvarname,hyprecname,avoid=make_up_namesn(Someindref)names_infoinletmap(b,is_assum,dep)={ba_kind=b;ba_assum=is_assum;ba_dep=dep;ba_name=recvarname;}inletnamesign=List.mapmaplchck_brchin(avoid,namesign)inArray.init(Array.lengthmip.mind_consnames)find_branchesleterror_cannot_recognizeind=user_errPp.(str"Cannot recognize a statement based on "++Nametab.pr_global_envId.Set.empty(IndRefind)++str".")letguess_elim_shapeenvsigmaisrecshyp0=lettmptyp0=Typing.type_of_variableenvhyp0inlet(mind,u),typ=Tacred.reduce_to_atomic_indenvsigmatmptyp0inletis_elim=isrec&¬(is_nonrecenvmind)inletnparams=ifis_elimthenletgr=lookup_eliminatorenvmindsinletsigma,ind=Evd.fresh_globalenvsigmagrinletelimt=Retyping.get_type_ofenvsigmaindinletscheme=compute_elim_sigsigmaelimtinlet()=matchscheme.indrefwith|None->error_cannot_recognizemind|Someref->ifQGlobRef.equalenvref(IndRefmind)then()elseerror_cannot_recognizemindinscheme.nparamselseletmib=Environ.lookup_mind(fstmind)envinmib.mind_nparamsinlethd,args=decompose_app_listsigmatypinlet(params,indices)=List.chopnparamsargsinis_elim,(mind,u),(hd,params,indices)letgiven_elimenvsigmahyp0(elimc,lbindase)=lettmptyp0=Typing.type_of_variableenvhyp0inletind_type_guess,_=decompose_appsigma(snd(decompose_prodsigmatmptyp0))inletsigma,elimt=Typing.type_ofenvsigmaelimcinsigma,(e,elimt),ind_type_guesstypescheme_signature=(Id.Set.t*branch_argumentlist)arraytypeeliminator_source=|CaseOverofId.t*(inductive*EInstance.t)|ElimOverofId.t*(inductive*EInstance.t)|ElimUsingofId.t*(Evd.econstrwith_bindings*EConstr.types*scheme_signature)|ElimUsingListof(Evd.econstrwith_bindings*EConstr.types*scheme_signature)*Id.tlist*Id.tlist*EConstr.tlistletfind_induction_typeenvsigmaisrecelimhyp0sort=matchelimwith|None->letis_elim,ind,typ=guess_elim_shapeenvsigmaisrecsorthyp0in(* We drop the scheme and elimc/elimt waiting to know if it is dependent, this
needs no update to sigma at this point. *)letelim=ifis_elimthenElimOver(hyp0,ind)elseCaseOver(hyp0,ind)insigma,typ,elim|Some(elimc,lbindase)->letsigma,elimt=Typing.type_ofenvsigmaelimcinletscheme=compute_elim_sigsigmaelimtinlet()=ifOption.is_emptyscheme.indargthenerrorCannotFindInductiveArgumentinletref=matchscheme.indrefwith|None->error_ind_scheme""|Someref->refinlettmptyp0=Typing.type_of_variableenvhyp0inletindtyp=reduce_to_atomic_refenvsigmareftmptyp0inlethd,args=decompose_app_listsigmaindtypinletindsign=compute_scheme_signaturesigmaschemehyp0hdinlet(params,indices)=List.chopscheme.nparamsargsinsigma,(hd,params,indices),ElimUsing(hyp0,(e,elimt,indsign))letis_functional_inductionelimcgl=letsigma=Tacmach.projectglinletscheme=compute_elim_sigsigma(Tacmach.pf_get_type_ofgl(fstelimc))in(* The test is not safe: with non-functional induction on non-standard
induction scheme, this may fail *)Option.is_emptyscheme.indarg(* Instantiate all meta variables of elimclause using lid, some elts
of lid are parameters (first ones), the other are
arguments. Returns the clause obtained. *)letrecolle_clenviparamsargselimclausegl=letlindmv=Array.of_list(clenv_argumentselimclause)inletk=matchiwithNone->Array.lengthlindmv-List.lengthargs|Somei->iin(* parameters correspond to first elts of lid. *)letclauses_params=List.mapi(funiid->id,lindmv.(i))paramsinletclauses_args=List.mapi(funiid->id,lindmv.(k+i))argsinletclauses=clauses_params@clauses_argsin(* iteration of clenv_instantiate with all infos we have. *)List.fold_right(funeacc->letx,i=einlety=pf_get_hyp_typxglinletelimclause'=clenv_instantiateiacc(mkVarx,y)inelimclause')(List.revclauses)elimclause(* Unification of the goal and the principle applied to meta variables:
(elimc ?i ?j ?k...?l). This solves partly meta variables (and may
produce new ones). Then refine with the resulting term with holes.
*)letinduction_tacwith_evarsparamsindvars(elim,elimt)=Proofview.Goal.enterbeginfungl->letenv=Proofview.Goal.envglinletsigma=Tacmach.projectglinletclause,bindings,index=matchelimwith|ElimConstantc->leti=index_of_ind_argsigmaelimtin(mkConstUc,elimt),NoBindings,Somei|ElimClause(elimc,lbindelimc)->(elimc,elimt),lbindelimc,Nonein(* elimclause contains this: (elimc ?i ?j ?k...?l) *)letelimclause=make_clenv_bindingenvsigmaclausebindingsin(* elimclause' is built from elimclause by instantiating all args and params. *)letelimclause=recolle_clenvindexparamsindvarselimclauseglinClenv.res_pf~with_evars~flags:(elim_flags())elimclauseendletdestruct_tacwith_evarsindvardep=Proofview.Goal.enterbeginfungl->letenv=Proofview.Goal.envglinletty=Typing.type_of_variableenvindvarinClenv.case_pf~with_evars~dep(mkVarindvar,ty)end(* Apply induction "in place" taking into account dependent
hypotheses from the context, replacing the main hypothesis on which
induction applies with the induction hypotheses *)letapply_induction_in_contextwith_evarsinhypselimindvarsnames=Proofview.Goal.enterbeginfungl->letsigma=Proofview.Goal.sigmaglinletenv=Proofview.Goal.envglinletconcl=Tacmach.pf_conclglinlethyp0=matchelimwith|ElimUsing(hyp0,_)|ElimOver(hyp0,_)|CaseOver(hyp0,_)->Somehyp0|ElimUsingList_->Noneinletstatuslists,lhyp0,toclear,deps,avoid,dep_in_hyps=cook_signhyp0inhypsindvarsenvsigmainlettmpcl=it_mkNamedProd_or_LetInsigmaconcldepsinlets=Retyping.get_sort_family_ofenvsigmatmpclinletdeps_cstr=List.fold_left(funadecl->ifNamedDecl.is_local_assumdeclthen(mkVar(NamedDecl.get_iddecl))::aelsea)[]depsin(* Wait the last moment to guess the eliminator so as to know if we
need a dependent one or not *)let(sigma,isrec,induct_tac,indsign)=matchelimwith|CaseOver(id,(mind,u))->letdep_in_concl=occur_varenvsigmaidconclinletdep=dep_in_hyps||dep_in_conclinletdep=dep||default_case_analysis_dependenceenvmindinletindsign=compute_case_signatureenvminddepidinlettac=destruct_tacwith_evarsiddepinsigma,false,tac,indsign|ElimOver(id,(mind,u))->letsigma,ind=find_ind_eliminatorenvsigmamindsin(* FIXME: we should store this instead of recomputing it *)letelimt=Retyping.get_type_ofenvsigma(mkConstUind)inletscheme=compute_elim_sigsigmaelimtinletindsign=compute_scheme_signaturesigmaschemeid(mkIndU(mind,u))inlettac=induction_tacwith_evars[][id](ElimConstantind,elimt)insigma,true,tac,indsign|ElimUsing(hyp0,(elim,elimt,indsign))->lettac=induction_tacwith_evars[][hyp0](ElimClauseelim,elimt)insigma,(* bugged, should be computed *)true,tac,indsign|ElimUsingList((elim,elimt,indsign),params,realindvars,patts)->lettac=Tacticals.tclTHENLIST[(* pattern to make the predicate appear. *)Tactics.reduce(Pattern(List.mapinj_with_occurrencespatts))onConcl;(* Induction by "refine (indscheme ?i ?j ?k...)" + resolution of all
possible holes using arguments given by the user (but the
functional one). *)(* FIXME: Tester ca avec un principe dependant et non-dependant *)induction_tacwith_evarsparamsrealindvars(ElimClauseelim,elimt);]insigma,(* bugged, should be computed *)true,tac,indsigninletbranchletsigns=letfba=ba.ba_assuminArray.map(fun(_,l)->List.mapfl)indsigninletnames=compute_induction_namestruebranchletsignsnamesinlet()=Array.iter(Tactics.Internal.check_name_unicityenvtoclear[])namesinlettac=(ifisrecthenTacticals.tclTHENFIRSTnelseTacticals.tclTHENLASTn)(Tacticals.tclTHENLIST[(* Generalize dependent hyps (but not args) *)ifdeps=[]thenProofview.tclUNIT()elseTactics.apply_type~typecheck:falsetmpcldeps_cstr;(* side-conditions in elim (resp case) schemes come last (resp first) *)induct_tac;Tacticals.tclMAPexpand_hyptoclear;])(Array.map2(induct_dischargewith_evarslhyp0avoid(re_intro_dependent_hypothesesstatuslists))indsignnames)inProofview.tclTHEN(Proofview.Unsafe.tclEVARSsigma)tacendletinduction_with_atomization_of_ind_argisrecwith_evarselimnameshyp0inhyps=Proofview.Goal.enterbeginfungl->letenv=Proofview.Goal.envglinletsigma=Proofview.Goal.sigmaglinletsort=Tacticals.elimination_sort_of_goalglinletsigma,ty,elim_info=find_induction_typeenvsigmaisrecelimhyp0sortinletletins,avoid,t=atomize_param_of_indenvsigmatyinletletins=tclMAP(fun(na,c)->Tactics.letin_tacNone(Namena)cNoneallHypsAndConcl)letinsinTacticals.tclTHENLIST[Proofview.Unsafe.tclEVARSsigma;letins;Tactics.change_in_hyp~check:falseNone(Tactics.make_change_argt)(hyp0,InHypTypeOnly);apply_induction_in_contextwith_evarsinhypselim_infoavoidnames]endletmsg_not_right_number_induction_argumentsscheme=str"Not the right number of induction arguments (expected "++pr_enum(funx->x)[ifscheme.farg_in_conclthenstr"the function name"elsemt();ifscheme.nparams!=0thenintscheme.nparams++str(String.pluralscheme.nparams" parameter")elsemt();ifscheme.nargs!=0thenintscheme.nargs++str(String.pluralscheme.nargs" argument")elsemt()]++str")."(* Induction on a list of induction arguments. Analyze the elim
scheme (which is mandatory for multiple ind args), check that all
parameters and arguments are given (mandatory too).
Main differences with induction_from_context is that there is no
main induction argument. On the other hand, all args and params
must be given, so we help a bit the unifier by making the "pattern"
by hand before calling induction_tac *)letinduction_without_atomizationisrecwith_evarselimnameslid=Proofview.Goal.enterbeginfungl->letenv=Proofview.Goal.envglinletsigma=Proofview.Goal.sigmaglinlethyp0=List.hdlidin(* Check that the elimination scheme has a form similar to the
elimination schemes built by Rocq. Schemes may have the standard
form computed from an inductive type OR (feb. 2006) a non standard
form. That is: with no main induction argument and with an optional
extra final argument of the form (f x y ...) in the conclusion. In
the non standard case, naming of generated hypos is slightly
different. *)let(sigma,(elimc,elimt),ind_type_guess)=given_elimenvsigmahyp0eliminletscheme=compute_elim_sigsigmaelimtinletindsign=compute_scheme_signaturesigmaschemehyp0ind_type_guessinletnargs_indarg_farg=scheme.nargs+(ifscheme.farg_in_conclthen1else0)inifnot(Int.equal(List.lengthlid)(scheme.nparams+nargs_indarg_farg))thenletinfo=Exninfo.reify()inTacticals.tclZEROMSG~info(msg_not_right_number_induction_argumentsscheme)elseletindvars,lid_params=List.chopnargs_indarg_farglidin(* terms to patternify we must patternify indarg or farg if present in concl *)letrealindvars=List.rev(ifscheme.farg_in_conclthenList.tlindvarselseindvars)inletlidcstr=List.mapmkVar(List.revindvars)inletparams=List.revlid_paramsinletindvars=(* Temporary hack for compatibility, while waiting for better
analysis of the form of induction schemes: a scheme like
gt_wf_rec was taken as a functional scheme with no parameters,
but by chance, because of the addition of at least hyp0 for
cook_sign, it behaved as if there was a real induction arg. *)ifList.is_emptyindvarsthenId.Set.singleton(List.hdlid_params)elseId.Set.of_listindvarsinletelim=ElimUsingList((elimc,scheme.elimt,indsign),params,realindvars,lidcstr)inapply_induction_in_contextwith_evars[]elimindvarsnamesend(* assume that no occurrences are selected *)letclear_unselected_contextidinhypscls=Proofview.Goal.enterbeginfungl->ifoccur_var(Tacmach.pf_envgl)(Tacmach.projectgl)id(Tacmach.pf_conclgl)&&cls.concl_occs==NoOccurrencesthenerror(MentionConclusionDependentOnid);matchcls.onhypswith|Somehyps->letto_erased=letid'=NamedDecl.get_iddinifId.List.memid'inhypsthen(* if selected, do not erase *)Noneelse(* erase if not selected and dependent on id or selected hyps *)lettestid=occur_var_in_decl(Tacmach.pf_envgl)(Tacmach.projectgl)iddinifList.existstest(id::inhyps)thenSomeid'elseNoneinletids=List.map_filterto_erase(Proofview.Goal.hypsgl)inclearids|None->Proofview.tclUNIT()endletuse_bindingsenvsigmaelimmust_be_closed(c,lbind)typ=lettyp=(* Normalize [typ] until the induction reference becomes plainly visible *)matchelimwith|None->(* w/o an scheme, the term has to be applied at least until
obtaining an inductive type (even though the arity might be
known only by pattern-matching, as in the case of a term of
the form "nat_rect ?A ?o ?s n", with ?A to be inferred by
matching. *)letsign,t=whd_decompose_prodenvsigmatypinit_mkProdtsign|Some(elimc,_)->(* Otherwise, we compute the induction reference of the scheme
and go looking for that. *)letsigma,elimt=Typing.type_ofenvsigmaelimcinletscheme=compute_elim_sigsigmaelimtinmatchscheme.indrefwith|None->errorCannotFindInductiveArgument|Someindref->Tacred.reduce_to_quantified_ref~allow_failure:trueenvsigmaindreftypinletrecfind_clausetyp=tryletindclause=make_clenv_bindingenvsigma(c,typ)lbindinifmust_be_closed&&occur_meta(clenv_evdindclause)(clenv_valueindclause)thenerrorNeedFullyAppliedArgument;(* We lose the possibility of coercions in with-bindings *)letmetas=Clenv.clenv_meta_listindclauseinletsigma,metas,term=pose_all_metas_as_evars~metasenv(clenv_evdindclause)(clenv_valueindclause)inletsigma,metas,typ=pose_all_metas_as_evars~metasenvsigma(clenv_typeindclause)insigma,term,typwithewhennoncriticale->matchred_productenvsigmatypwith|None->raisee|Sometyp->find_clausetypinfind_clausetypletcheck_expected_typeenvsigma(elimc,bl)elimt=(* Compute the expected template type of the term in case a using
clause is given *)letsign,_=whd_decompose_prodenvsigmaelimtinletn=List.lengthsigninifn==0thenerrorSchemeDontApply;letsigma,cl=EClause.make_evar_clauseenvsigma~len:(n-1)elimtinletsigma=EClause.solve_evar_clauseenvsigmatrueclblinlet(_,u,_)=destProdsigma(whd_allenvsigmacl.cl_concl)infunt->matchEvarconv.unify_leq_delayenvsigmatuwith|_sigma->true|exceptionEvarconv.UnableToUnify_->falseletcheck_enough_appliedenvsigmaelim=(* A heuristic to decide whether the induction arg is enough applied *)matchelimwith|None->(* No eliminator given *)funu->lett,_=decompose_appsigma(whd_allenvsigmau)inisIndsigmat|Someelimc->letelimt=Retyping.get_type_ofenvsigma(fstelimc)inletscheme=compute_elim_sigsigmaelimtinmatchscheme.indrefwith|None->(* in the absence of information, do not assume it may be
partially applied *)fun_->true|Some_->(* Last argument is supposed to be the induction argument *)check_expected_typeenvsigmaelimcelimtletguard_no_unifiable=Proofview.guard_no_unifiable>>=function|None->Proofview.tclUNIT()|Somel->Proofview.tclENV>>=functionenv->Proofview.tclEVARMAP>>=functionsigma->letinfo=Exninfo.reify()inProofview.tclZERO~info(RefinerError(env,sigma,UnresolvedBindingsl))letpose_induction_arg_thenisrecwith_evars(is_arg_pure_hyp,from_prefix)elimid((pending,(c0,lbind)),(eqname,names))t0inhypsclstac=Proofview.Goal.enterbeginfungl->letsigma=Proofview.Goal.sigmaglinletenv=Proofview.Goal.envglinletccl=Proofview.Goal.conclglinletcheck=check_enough_appliedenvsigmaeliminletsigma',c,_=use_bindingsenvsigmaelimfalse(c0,lbind)t0inletabs=AbstractPattern(from_prefix,check,Nameid,(pending,c),cls)inlet(id,sign,_,lastlhyp,ccl,res)=make_abstractionenvsigma'cclabsinmatchreswith|None->(* pattern not found *)letwith_eq=Option.map(funeq->(false,mk_eq_nameenvideq))eqnameinletinhyps=ifList.is_emptyinhypstheninhypselseOption.fold_left(funinhyps(_,heq)->heq::inhyps)inhypswith_eqin(* we restart using bindings after having tried type-class
resolution etc. on the term given by the user *)letflags=tactic_infer_flags(with_evars&&(* do not give a success semantics to edestruct on an open term yet *)false)inlet(sigma,c0)=finish_evar_resolution~flagsenvsigma(pending,c0)inlettac=(ifisrecthen(* Historically, induction has side conditions last *)Tacticals.tclTHENFIRSTelse(* and destruct has side conditions first *)Tacticals.tclTHENLAST)(Tacticals.tclTHENLIST[Refine.refine_with_principal~typecheck:falsebeginfunsigma->letb=notwith_evars&&with_eq!=Noneinletsigma,c,t=use_bindingsenvsigmaelimb(c0,lbind)t0inmkletin_goalenvsigmawith_eqfalse(id,lastlhyp,ccl,c)(Somet)end;ifwith_evarsthenProofview.shelve_unifiableelseguard_no_unifiable;ifis_arg_pure_hypthenProofview.tclEVARMAP>>=funsigma->Tacticals.tclTRY(clear[destVarsigmac0])elseProofview.tclUNIT();ifisrecthenProofview.cycle(-1)elseProofview.tclUNIT()])(tacinhyps)inProofview.tclTHEN(Proofview.Unsafe.tclEVARSsigma)tac|Some(sigma',c)->(* pattern found *)(* TODO: if ind has predicate parameters, use JMeq instead of eq *)letenv=reset_with_named_contextsignenvinletwith_eq=Option.map(funeq->(false,mk_eq_nameenvideq))eqnameinletinhyps=ifList.is_emptyinhypstheninhypselseOption.fold_left(funinhyps(_,heq)->heq::inhyps)inhypswith_eqinlettac=Tacticals.tclTHENLIST[Refine.refine_with_principal~typecheck:falsebeginfunsigma->mkletin_goalenvsigmawith_eqtrue(id,lastlhyp,ccl,c)Noneend;(tacinhyps)]inProofview.tclTHEN(Proofview.Unsafe.tclEVARSsigma')tacendlethas_generic_occurrences_but_goalclsidenvsigmaccl=clause_with_generic_context_selectioncls&&(* TODO: whd_evar of goal *)(cls.concl_occs!=NoOccurrences||not(occur_varenvsigmaidccl))letinduction_genclear_flagisrecwith_evarselim((_pending,(c,lbind)),(eqname,names)asarg)cls=letinhyps=matchclswith|Some{onhyps=Somehyps}->List.map(fun((_,id),_)->id)hyps|_->[]inProofview.Goal.enterbeginfungl->letenv=Proofview.Goal.envglinletevd=Proofview.Goal.sigmaglinletccl=Proofview.Goal.conclglinletcls=Option.defaultallHypsAndConclclsinlett=typ_ofenvevdcinletis_arg_pure_hyp=isVarevdc&¬(mem_named_context_val(destVarevdc)(Global.named_context_val()))&&lbind==NoBindings&¬with_evars&&Option.is_emptyeqname&&clear_flag==None&&has_generic_occurrences_but_goalcls(destVarevdc)envevdcclinletenough_applied=check_enough_appliedenvevdelimtinifis_arg_pure_hyp&&enough_appliedthen(* First case: induction on a variable already in an inductive type and
with maximal abstraction over the variable.
This is a situation where the induction argument is a
clearable variable of the goal w/o occurrence selection
and w/o equality kept: no need to generalize *)letid=destVarevdcinTacticals.tclTHEN(clear_unselected_contextidinhypscls)(induction_with_atomization_of_ind_argisrecwith_evarselimnamesidinhyps)else(* Otherwise, we look for the pattern, possibly adding missing arguments and
declaring the induction argument as a new local variable *)letid=(* Type not the right one if partially applied but anyway for internal use*)letavoid=matcheqnamewith|Some{CAst.v=IntroIdentifierid}->Id.Set.singletonid|_->Id.Set.emptyinletx=id_of_name_using_hdcharenvevdtAnonymousinnew_fresh_idavoidxglinletinfo_arg=(is_arg_pure_hyp,notenough_applied)inpose_induction_arg_thenisrecwith_evarsinfo_argelimidargtinhypscls(induction_with_atomization_of_ind_argisrecwith_evarselimnamesid)end(* Induction on a list of arguments. First make induction arguments
atomic (using letins), then do induction. The specificity here is
that all arguments and parameters of the scheme are given
(mandatory for the moment), so we don't need to deal with
parameters of the inductive type as in induction_gen. *)letinduction_gen_lisrecwith_evarselimnameslc=letnewlc=ref[]inletlc=List.map(function|(c,None)->c|(c,Some{CAst.loc;v=eqname})->error?loc(DontKnowWhatToDoWitheqname))lcinletrecatomize_listl=matchlwith|[]->Proofview.tclUNIT()|c::l'->Proofview.tclEVARMAP>>=funsigma->matchEConstr.kindsigmacwith|Varidwhennot(mem_named_context_valid(Global.named_context_val()))&¬with_evars->let()=newlc:=id::!newlcinatomize_listl'|_->Proofview.Goal.enterbeginfungl->letsigma,t=pf_applyTyping.type_ofglcinletx=id_of_name_using_hdchar(Proofview.Goal.envgl)sigmatAnonymousinletid=new_fresh_idId.Set.emptyxglinletnewl'=List.map(funr->replace_termsigmac(mkVarid)r)l'inlet()=newlc:=id::!newlcinTacticals.tclTHENLIST[tclEVARSsigma;Tactics.letin_tacNone(Nameid)cNoneallHypsAndConcl;atomize_listnewl';]endinTacticals.tclTHENLIST[(atomize_listlc);(Proofview.tclUNIT()>>=fun()->(* ensure newlc has been computed *)induction_without_atomizationisrecwith_evarselimnames!newlc)](* Induction either over a term, over a quantified premisse, or over
several quantified premisses (like with functional induction
principles).
TODO: really unify induction with one and induction with several
args *)letinduction_destructisrecwith_evars(lc,elim)=matchlcwith|[]->assertfalse(* ensured by syntax, but if called inside caml? *)|[c,(eqname,namesasallnames),cls]->Proofview.Goal.enterbeginfungl->letenv=Proofview.Goal.envglinletsigma=Tacmach.projectglinmatchelimwith|Someelimwhenis_functional_inductionelimgl->(* Standard induction on non-standard induction schemes *)(* will be removable when is_functional_induction will be more clever *)ifnot(Option.is_emptycls)thenerror(UnsupportedInClausetrue);let_,c=force_destruction_argfalseenvsigmacinonInductionArg(fun_clear_flagc->induction_gen_lisrecwith_evarselimnames[with_no_bindingsc,eqname])c|_->(* standard induction *)onOpenInductionArgenvsigma(funclear_flagc->induction_genclear_flagisrecwith_evarselim(c,allnames)cls)cend|_->Proofview.Goal.enterbeginfungl->letenv=Proofview.Goal.envglinletsigma=Tacmach.projectglinmatchelimwith|None->(* Several arguments, without "using" clause *)(* TODO: Do as if the arguments after the first one were called with *)(* "destruct", but selecting occurrences on the initial copy of *)(* the goal *)let(a,b,cl)=List.hdlcinletl=List.tllcin(* TODO *)Tacticals.tclTHEN(onOpenInductionArgenvsigma(funclear_flaga->induction_genclear_flagisrecwith_evarsNone(a,b)cl)a)(Tacticals.tclMAP(fun(a,b,cl)->Proofview.Goal.enterbeginfungl->letenv=Proofview.Goal.envglinletsigma=Tacmach.projectglinonOpenInductionArgenvsigma(funclear_flaga->induction_genclear_flagfalsewith_evarsNone(a,b)cl)aend)l)|Someelim->(* Several induction hyps with induction scheme *)letlc=List.map(on_pi1(func->snd(force_destruction_argfalseenvsigmac)))lcinletnewlc=List.map(fun(x,(eqn,names),cls)->ifcls!=NonethenerrorUnsupportedEqnClause;matchxwith(* FIXME: should we deal with ElimOnIdent? *)|_clear_flag,ElimOnConstrx->ifeqn<>Nonethenerror(UnsupportedInClausefalse);(with_no_bindingsx,names)|_->errorDontKnowWhereToFindArgument)lcin(* Check that "as", if any, is given only on the last argument *)letnames,rest=List.sep_last(List.mapsndnewlc)inifList.exists(funn->not(Option.is_emptyn))restthenerrorMultipleAsAndUsingClauseOnlyList;letnewlc=List.map(fun(x,_)->(x,None))newlcininduction_gen_lisrecwith_evarselimnamesnewlcendletinductionevclrcle=induction_genclrtrueeve((None,(c,NoBindings)),(None,l))Noneletdestructevclrcle=induction_genclrfalseeve((None,(c,NoBindings)),(None,l))None