Commit 86f93e35 authored by Robbert Krebbers's avatar Robbert Krebbers
Browse files

Retry "Let iLöb automatically revert and introduce spatial hypotheses."

parent 29ee3a0f
Pipeline #451 passed with stage
...@@ -796,6 +796,12 @@ Proof. ...@@ -796,6 +796,12 @@ Proof.
Qed. Qed.
Lemma entails_wand P Q : (P Q) True (P - Q). Lemma entails_wand P Q : (P Q) True (P - Q).
Proof. auto using wand_intro_l. Qed. Proof. auto using wand_intro_l. Qed.
Lemma wand_curry P Q R : (P - Q - R) (P Q - R).
Proof.
apply (anti_symm _).
- apply wand_intro_l. by rewrite (comm _ P) -assoc !wand_elim_r.
- do 2 apply wand_intro_l. by rewrite assoc (comm _ Q) wand_elim_r.
Qed.
Lemma sep_and P Q : (P Q) (P Q). Lemma sep_and P Q : (P Q) (P Q).
Proof. auto. Qed. Proof. auto. Qed.
......
...@@ -140,7 +140,7 @@ Lemma wait_spec l P (Φ : val → iProp) : ...@@ -140,7 +140,7 @@ Lemma wait_spec l P (Φ : val → iProp) :
Proof. Proof.
rename P into R; rewrite /recv /barrier_ctx. rename P into R; rewrite /recv /barrier_ctx.
iIntros "[Hr HΦ]"; iDestruct "Hr" as {γ P Q i} "(#(%&Hh&Hsts)&Hγ&#HQ&HQR)". iIntros "[Hr HΦ]"; iDestruct "Hr" as {γ P Q i} "(#(%&Hh&Hsts)&Hγ&#HQ&HQR)".
iLöb "Hγ HQR HΦ" as "IH". wp_rec. wp_focus (! _)%E. iLöb as "IH". wp_rec. wp_focus (! _)%E.
iSts γ as [p I]; iDestruct "Hγ" as "[Hl Hr]". iSts γ as [p I]; iDestruct "Hγ" as "[Hl Hr]".
wp_load. destruct p. wp_load. destruct p.
- (* a Low state. The comparison fails, and we recurse. *) - (* a Low state. The comparison fails, and we recurse. *)
......
...@@ -75,7 +75,7 @@ Lemma join_spec (Ψ : val → iProp) l (Φ : val → iProp) : ...@@ -75,7 +75,7 @@ Lemma join_spec (Ψ : val → iProp) l (Φ : val → iProp) :
(join_handle l Ψ v, Ψ v - Φ v) WP join (%l) {{ Φ }}. (join_handle l Ψ v, Ψ v - Φ v) WP join (%l) {{ Φ }}.
Proof. Proof.
rewrite /join_handle; iIntros "[[% H] Hv]"; iDestruct "H" as {γ} "(#?&Hγ&#?)". rewrite /join_handle; iIntros "[[% H] Hv]"; iDestruct "H" as {γ} "(#?&Hγ&#?)".
iLöb "Hγ Hv" as "IH". wp_rec. wp_focus (! _)%E. iLöb as "IH". wp_rec. wp_focus (! _)%E.
iInv N as "Hinv"; iDestruct "Hinv" as {v} "[Hl Hinv]". iInv N as "Hinv"; iDestruct "Hinv" as {v} "[Hl Hinv]".
wp_load. iDestruct "Hinv" as "[%|Hinv]"; subst. wp_load. iDestruct "Hinv" as "[%|Hinv]"; subst.
- iSplitL "Hl"; [iNext; iExists _; iFrame "Hl"; by iLeft|]. - iSplitL "Hl"; [iNext; iExists _; iFrame "Hl"; by iLeft|].
......
...@@ -85,6 +85,9 @@ Definition envs_split {M} ...@@ -85,6 +85,9 @@ Definition envs_split {M}
Definition envs_persistent {M} (Δ : envs M) := Definition envs_persistent {M} (Δ : envs M) :=
if env_spatial Δ is Enil then true else false. if env_spatial Δ is Enil then true else false.
Definition envs_clear_spatial {M} (Δ : envs M) : envs M :=
Envs (env_persistent Δ) Enil.
(* Coq versions of the tactics *) (* Coq versions of the tactics *)
Section tactics. Section tactics.
Context {M : cmraT}. Context {M : cmraT}.
...@@ -92,6 +95,10 @@ Implicit Types Γ : env (uPred M). ...@@ -92,6 +95,10 @@ Implicit Types Γ : env (uPred M).
Implicit Types Δ : envs M. Implicit Types Δ : envs M.
Implicit Types P Q : uPred M. Implicit Types P Q : uPred M.
Lemma of_envs_def Δ :
of_envs Δ = ( envs_wf Δ Π env_persistent Δ Π★ env_spatial Δ)%I.
Proof. done. Qed.
Lemma envs_lookup_delete_Some Δ Δ' i p P : Lemma envs_lookup_delete_Some Δ Δ' i p P :
envs_lookup_delete i Δ = Some (p,P,Δ') envs_lookup_delete i Δ = Some (p,P,Δ')
envs_lookup i Δ = Some (p,P) Δ' = envs_delete i p Δ. envs_lookup i Δ = Some (p,P) Δ' = envs_delete i p Δ.
...@@ -227,9 +234,23 @@ Proof. ...@@ -227,9 +234,23 @@ Proof.
env_split_fresh_1, env_split_fresh_2. env_split_fresh_1, env_split_fresh_2.
Qed. Qed.
Lemma env_special_nil_persistent Δ : envs_persistent Δ = true PersistentP Δ. Lemma envs_clear_spatial_sound Δ :
Δ (envs_clear_spatial Δ Π★ env_spatial Δ)%I.
Proof.
rewrite /of_envs /envs_clear_spatial /=; apply const_elim_sep_l=> Hwf.
rewrite right_id -assoc; apply sep_intro_True_l; [apply const_intro|done].
destruct Hwf; constructor; simpl; auto using Enil_wf.
Qed.
Lemma env_fold_wand Γ Q : env_fold uPred_wand Q Γ (Π★ Γ - Q).
Proof.
revert Q; induction Γ as [|Γ IH i P]=> Q /=; [by rewrite wand_True|].
by rewrite IH wand_curry (comm uPred_sep).
Qed.
Lemma envs_persistent_persistent Δ : envs_persistent Δ = true PersistentP Δ.
Proof. intros; destruct Δ as [? []]; simplify_eq/=; apply _. Qed. Proof. intros; destruct Δ as [? []]; simplify_eq/=; apply _. Qed.
Hint Immediate env_special_nil_persistent : typeclass_instances. Hint Immediate envs_persistent_persistent : typeclass_instances.
(** * Adequacy *) (** * Adequacy *)
Lemma tac_adequate P : Envs Enil Enil P True P. Lemma tac_adequate P : Envs Enil Enil P True P.
...@@ -253,9 +274,9 @@ Qed. ...@@ -253,9 +274,9 @@ Qed.
Lemma tac_clear Δ Δ' i p P Q : Lemma tac_clear Δ Δ' i p P Q :
envs_lookup_delete i Δ = Some (p,P,Δ') Δ' Q Δ Q. envs_lookup_delete i Δ = Some (p,P,Δ') Δ' Q Δ Q.
Proof. intros. by rewrite envs_lookup_delete_sound // sep_elim_r. Qed. Proof. intros. by rewrite envs_lookup_delete_sound // sep_elim_r. Qed.
Lemma tac_clear_spatial Δ Δ1 Δ2 Q : Lemma tac_clear_spatial Δ Δ' Q :
envs_split true [] Δ = Some (Δ1,Δ2) Δ1 Q Δ Q. envs_clear_spatial Δ = Δ' Δ' Q Δ Q.
Proof. intros. by rewrite envs_split_sound // sep_elim_l. Qed. Proof. intros <- ?. by rewrite envs_clear_spatial_sound // sep_elim_l. Qed.
Lemma tac_duplicate Δ Δ' i p j P Q : Lemma tac_duplicate Δ Δ' i p j P Q :
envs_lookup i Δ = Some (p, P) envs_lookup i Δ = Some (p, P)
...@@ -333,10 +354,10 @@ Lemma tac_löb Δ Δ' i Q : ...@@ -333,10 +354,10 @@ Lemma tac_löb Δ Δ' i Q :
envs_app true (Esnoc Enil i ( Q)%I) Δ = Some Δ' envs_app true (Esnoc Enil i ( Q)%I) Δ = Some Δ'
Δ' Q Δ Q. Δ' Q Δ Q.
Proof. Proof.
intros ?? HQ. rewrite (persistentP Δ) envs_app_sound //; simpl. intros ?? HQ. rewrite -(always_elim Q) -(löb ( Q)) -always_later.
rewrite right_id -{2}(always_elim Q) -(löb ( Q)); apply impl_intro_l. apply impl_intro_l, (always_intro _ _).
rewrite -always_later -{1}(always_always ( ( Q))) always_and_sep_l'. rewrite envs_app_sound //; simpl.
by rewrite -always_sep wand_elim_r HQ. by rewrite right_id always_and_sep_l' wand_elim_r HQ.
Qed. Qed.
(** * Always *) (** * Always *)
...@@ -502,6 +523,12 @@ Proof. ...@@ -502,6 +523,12 @@ Proof.
- by rewrite HQ wand_elim_r. - by rewrite HQ wand_elim_r.
Qed. Qed.
Lemma tac_revert_spatial Δ Q :
envs_clear_spatial Δ (env_fold uPred_wand Q (env_spatial Δ)) Δ Q.
Proof.
intros HΔ. by rewrite envs_clear_spatial_sound HΔ env_fold_wand wand_elim_l.
Qed.
Lemma tac_assert Δ Δ1 Δ2 Δ2' lr js j P Q : Lemma tac_assert Δ Δ1 Δ2 Δ2' lr js j P Q :
envs_split lr js Δ = Some (Δ1,Δ2) envs_split lr js Δ = Some (Δ1,Δ2)
envs_app (envs_persistent Δ1) (Esnoc Enil j P) Δ2 = Some Δ2' envs_app (envs_persistent Δ1) (Esnoc Enil j P) Δ2 = Some Δ2'
......
...@@ -28,9 +28,18 @@ Inductive env_wf {A} : env A → Prop := ...@@ -28,9 +28,18 @@ Inductive env_wf {A} : env A → Prop :=
Fixpoint env_to_list {A} (E : env A) : list A := Fixpoint env_to_list {A} (E : env A) : list A :=
match E with Enil => [] | Esnoc Γ _ x => x :: env_to_list Γ end. match E with Enil => [] | Esnoc Γ _ x => x :: env_to_list Γ end.
Coercion env_to_list : env >-> list. Coercion env_to_list : env >-> list.
Instance env_dom {A} : Dom (env A) stringset := Instance env_dom {A} : Dom (env A) stringset :=
fix go Γ := let _ : Dom _ _ := @go in fix go Γ := let _ : Dom _ _ := @go in
match Γ with Enil => | Esnoc Γ i _ => {[ i ]} dom stringset Γ end. match Γ with Enil => | Esnoc Γ i _ => {[ i ]} dom stringset Γ end.
Fixpoint env_dom_list {A} (Γ : env A) : list string :=
match Γ with Enil => [] | Esnoc Γ i _ => i :: env_dom_list Γ end.
Fixpoint env_fold {A B} (f : B A A) (x : A) (Γ : env B) : A :=
match Γ with
| Enil => x
| Esnoc Γ _ y => env_fold f (f y x) Γ
end.
Fixpoint env_app {A} (Γapp : env A) (Γ : env A) : option (env A) := Fixpoint env_app {A} (Γapp : env A) (Γ : env A) : option (env A) :=
match Γapp with match Γapp with
......
...@@ -118,15 +118,21 @@ Definition parse (s : string) : option (list intro_pat) := ...@@ -118,15 +118,21 @@ Definition parse (s : string) : option (list intro_pat) :=
Ltac parse s := Ltac parse s :=
lazymatch type of s with lazymatch type of s with
| list intro_pat => s | list intro_pat => s
| string => lazymatch eval vm_compute in (parse s) with | list string =>
| Some ?pats => pats | _ => fail "invalid list intro_pat" s lazymatch eval vm_compute in (mjoin <$> mapM parse s) with
end | Some ?pats => pats | _ => fail "invalid list intro_pat" s
end
| string =>
lazymatch eval vm_compute in (parse s) with
| Some ?pats => pats | _ => fail "invalid list intro_pat" s
end
end. end.
Ltac parse_one s := Ltac parse_one s :=
lazymatch type of s with lazymatch type of s with
| intro_pat => s | intro_pat => s
| string => lazymatch eval vm_compute in (parse s) with | string =>
| Some [?pat] => pat | _ => fail "invalid intro_pat" s lazymatch eval vm_compute in (parse s) with
end | Some [?pat] => pat | _ => fail "invalid intro_pat" s
end
end. end.
End intro_pat. End intro_pat.
...@@ -81,7 +81,6 @@ Proof. ...@@ -81,7 +81,6 @@ Proof.
eauto using tac_pvs_timeless. eauto using tac_pvs_timeless.
Qed. Qed.
Hint Immediate env_special_nil_persistent : typeclass_instances.
Lemma tac_pvs_assert {A} (fsa : FSA Λ Σ A) fsaV Δ Δ1 Δ2 Δ2' E lr js j P Q Φ : Lemma tac_pvs_assert {A} (fsa : FSA Λ Σ A) fsaV Δ Δ1 Δ2 Δ2' E lr js j P Q Φ :
FSASplit Q E fsa fsaV Φ FSASplit Q E fsa fsaV Φ
envs_split lr js Δ = Some (Δ1,Δ2) envs_split lr js Δ = Some (Δ1,Δ2)
......
...@@ -4,7 +4,7 @@ From iris.proofmode Require Export notation. ...@@ -4,7 +4,7 @@ From iris.proofmode Require Export notation.
From iris.prelude Require Import stringmap. From iris.prelude Require Import stringmap.
Declare Reduction env_cbv := cbv [ Declare Reduction env_cbv := cbv [
env_lookup env_lookup_delete env_delete env_app env_lookup env_fold env_lookup_delete env_delete env_app
env_replace env_split_go env_split env_replace env_split_go env_split
decide (* operational classes *) decide (* operational classes *)
sumbool_rec sumbool_rect (* sumbool *) sumbool_rec sumbool_rect (* sumbool *)
...@@ -13,7 +13,7 @@ Declare Reduction env_cbv := cbv [ ...@@ -13,7 +13,7 @@ Declare Reduction env_cbv := cbv [
string_eq_dec string_rec string_rect (* strings *) string_eq_dec string_rec string_rect (* strings *)
env_persistent env_spatial envs_persistent env_persistent env_spatial envs_persistent
envs_lookup envs_lookup_delete envs_delete envs_app envs_lookup envs_lookup_delete envs_delete envs_app
envs_simple_replace envs_replace envs_split]. envs_simple_replace envs_replace envs_split envs_clear_spatial].
Ltac env_cbv := Ltac env_cbv :=
match goal with |- ?u => let v := eval env_cbv in u in change v end. match goal with |- ?u => let v := eval env_cbv in u in change v end.
...@@ -365,6 +365,8 @@ Tactic Notation "iApply" open_constr (H) "{" open_constr(x1) open_constr(x2) ...@@ -365,6 +365,8 @@ Tactic Notation "iApply" open_constr (H) "{" open_constr(x1) open_constr(x2)
iSpecialize H { x1 x2 x3 x4 x5 x6 x7 x8 }; last iApply H Hs. iSpecialize H { x1 x2 x3 x4 x5 x6 x7 x8 }; last iApply H Hs.
(** * Revert *) (** * Revert *)
Tactic Notation "iRevert" "★" := eapply tac_revert_spatial; env_cbv.
Tactic Notation "iForallRevert" ident(x) := Tactic Notation "iForallRevert" ident(x) :=
match type of x with match type of x with
| _ : Prop => revert x; apply tac_pure_revert | _ : Prop => revert x; apply tac_pure_revert
...@@ -753,60 +755,45 @@ Tactic Notation "iNext":= ...@@ -753,60 +755,45 @@ Tactic Notation "iNext":=
|let P := match goal with |- upred_tactics.StripLaterL ?P _ => P end in |let P := match goal with |- upred_tactics.StripLaterL ?P _ => P end in
apply _ || fail "iNext:" P "does not contain laters"|]. apply _ || fail "iNext:" P "does not contain laters"|].
Tactic Notation "iLöb" "as" constr (H) := (* This is pretty ugly, but without Ltac support for manipulating lists of
eapply tac_löb with _ H; idents I do not know how to do this better. *)
[reflexivity || fail "iLöb: non-empty spatial context" Ltac iLöbCore IH tac_before tac_after :=
|env_cbv; reflexivity || fail "iLöb:" H "not fresh"|]. match goal with
| |- of_envs ?Δ _ =>
Tactic Notation "iLöb" "{" ident(x1) "}" "as" constr (H) := let Hs := constr:(rev (env_dom_list (env_spatial Δ))) in
iRevert { x1 }; iLöb as H; iIntros { x1 }. iRevert ; tac_before;
Tactic Notation "iLöb" "{" ident(x1) ident(x2) "}" "as" constr (H) := eapply tac_löb with _ IH;
iRevert { x1 x2 }; iLöb as H; iIntros { x1 x2 }. [reflexivity
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) "}" "as" constr (H) := |env_cbv; reflexivity || fail "iLöb:" IH "not fresh"|];
iRevert { x1 x2 x3 }; iLöb as H; iIntros { x1 x2 x3 }. tac_after; iIntros Hs
end.
Tactic Notation "iLöb" "as" constr (IH) := iLöbCore IH idtac idtac.
Tactic Notation "iLöb" "{" ident(x1) "}" "as" constr (IH) :=
iLöbCore IH ltac:(iRevert { x1 }) ltac:(iIntros { x1 }).
Tactic Notation "iLöb" "{" ident(x1) ident(x2) "}" "as" constr (IH) :=
iLöbCore IH ltac:(iRevert { x1 x2 }) ltac:(iIntros { x1 x2 }).
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) "}" "as" constr (IH) :=
iLöbCore IH ltac:(iRevert { x1 x2 x3 }) ltac:(iIntros { x1 x2 x3 }).
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4) "}" "as" Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4) "}" "as"
constr (H):= constr (IH):=
iRevert { x1 x2 x3 x4 }; iLöb as H; iIntros { x1 x2 x3 x4 }. iLöbCore IH ltac:(iRevert { x1 x2 x3 x4 }) ltac:(iIntros { x1 x2 x3 x4 }).
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4)
ident(x5) "}" "as" constr (H) :=
iRevert { x1 x2 x3 x4 x5 }; iLöb as H; iIntros { x1 x2 x3 x4 x5 }.
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4)
ident(x5) ident(x6) "}" "as" constr (H) :=
iRevert { x1 x2 x3 x4 x5 x6 }; iLöb as H; iIntros { x1 x2 x3 x4 x5 x6 }.
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4)
ident(x5) ident(x6) ident(x7) "}" "as" constr (H) :=
iRevert { x1 x2 x3 x4 x5 x6 x7 }; iLöb as H; iIntros { x1 x2 x3 x4 x5 x6 x7 }.
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4)
ident(x5) ident(x6) ident(x7) ident(x8) "}" "as" constr (H) :=
iRevert { x1 x2 x3 x4 x5 x6 x7 x8 };
iLöb as H; iIntros { x1 x2 x3 x4 x5 x6 x7 x8 }.
Tactic Notation "iLöb" constr(Hs) "as" constr (H) :=
iRevert Hs; iLöb as H; iIntros Hs.
Tactic Notation "iLöb" "{" ident(x1) "}" constr(Hs) "as" constr (H) :=
iRevert { x1 } Hs; iLöb as H; iIntros { x1 } Hs.
Tactic Notation "iLöb" "{" ident(x1) ident(x2) "}" constr(Hs) "as" constr (H) :=
iRevert { x1 x2 } Hs; iLöb as H; iIntros { x1 x2 } Hs.
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) "}" constr(Hs) "as"
constr (H) :=
iRevert { x1 x2 x3 } Hs; iLöb as H; iIntros { x1 x2 x3 } Hs.
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4) "}"
constr(Hs) "as" constr (H) :=
iRevert { x1 x2 x3 x4 } Hs; iLöb as H; iIntros { x1 x2 x3 x4 } Hs.
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4) Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4)
ident(x5) "}" constr(Hs) "as" constr (H) := ident(x5) "}" "as" constr (IH) :=
iRevert { x1 x2 x3 x4 x5 } Hs; iLöb as H; iIntros { x1 x2 x3 x4 x5 } Hs. iLöbCore IH ltac:(iRevert { x1 x2 x3 x4 x5 })
ltac:(iIntros { x1 x2 x3 x4 x5 }).
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4) Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4)
ident(x5) ident(x6) "}" constr(Hs) "as" constr (H) := ident(x5) ident(x6) "}" "as" constr (IH) :=
iRevert { x1 x2 x3 x4 x5 x6 } Hs; iLöb as H; iIntros { x1 x2 x3 x4 x5 x6 } Hs. iLöbCore IH ltac:(iRevert { x1 x2 x3 x4 x5 x6 })
ltac:(iIntros { x1 x2 x3 x4 x5 x6 }).
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4) Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4)
ident(x5) ident(x6) ident(x7) "}" constr(Hs) "as" constr (H) := ident(x5) ident(x6) ident(x7) "}" "as" constr (IH) :=
iRevert { x1 x2 x3 x4 x5 x6 x7 } Hs; iLöbCore IH ltac:(iRevert { x1 x2 x3 x4 x5 x6 x7 })
iLöb as H; iIntros { x1 x2 x3 x4 x5 x6 x7 } Hs. ltac:(iIntros { x1 x2 x3 x4 x5 x6 x7 }).
Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4) Tactic Notation "iLöb" "{" ident(x1) ident(x2) ident(x3) ident(x4)
ident(x5) ident(x6) ident(x7) ident(x8) "}" constr(Hs) "as" constr (H) := ident(x5) ident(x6) ident(x7) ident(x8) "}" "as" constr (IH) :=
iRevert { x1 x2 x3 x4 x5 x6 x7 x8 } Hs; iLöbCore IH ltac:(iRevert { x1 x2 x3 x4 x5 x6 x7 x8 })
iLöb as H; iIntros { x1 x2 x3 x4 x5 x6 x7 x8 } Hs. ltac:(iIntros { x1 x2 x3 x4 x5 x6 x7 x8 }).
(** * Assert *) (** * Assert *)
Tactic Notation "iAssert" constr(Q) "as" constr(pat) "with" constr(Hs) := Tactic Notation "iAssert" constr(Q) "as" constr(pat) "with" constr(Hs) :=
......
...@@ -42,7 +42,7 @@ Section LiftingTests. ...@@ -42,7 +42,7 @@ Section LiftingTests.
n1 < n2 n1 < n2
Φ #(n2 - 1) WP FindPred #n2 #n1 @ E {{ Φ }}. Φ #(n2 - 1) WP FindPred #n2 #n1 @ E {{ Φ }}.
Proof. Proof.
iIntros {Hn} "HΦ". iLöb {n1 Hn} "HΦ" as "IH". iIntros {Hn} "HΦ". iLöb {n1 Hn} as "IH".
wp_rec. wp_let. wp_op. wp_let. wp_op=> ?; wp_if. wp_rec. wp_let. wp_op. wp_let. wp_op=> ?; wp_if.
- iApply "IH" "% HΦ". omega. - iApply "IH" "% HΦ". omega.
- iPvsIntro. by assert (n1 = n2 - 1) as -> by omega. - iPvsIntro. by assert (n1 = n2 - 1) as -> by omega.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment