From 2ea0ae3020c0c500b347dbeba83746168c76830f Mon Sep 17 00:00:00 2001
From: Robbert Krebbers <mail@robbertkrebbers.nl>
Date: Wed, 4 Dec 2019 23:48:19 +0100
Subject: [PATCH] Better detection when to use
 `tac_specialize_intuitionistic_helper`.

---
 theories/proofmode/ltac_tactics.v | 102 +++++++++++++++++++++---------
 1 file changed, 73 insertions(+), 29 deletions(-)

diff --git a/theories/proofmode/ltac_tactics.v b/theories/proofmode/ltac_tactics.v
index d1d785a89..5fdeb14cc 100644
--- a/theories/proofmode/ltac_tactics.v
+++ b/theories/proofmode/ltac_tactics.v
@@ -951,15 +951,66 @@ Ltac iSpecializePat_go H1 pats :=
 Local Tactic Notation "iSpecializePat" open_constr(H) constr(pat) :=
   let pats := spec_pat.parse pat in iSpecializePat_go H pats.
 
-(** The argument [p] denotes whether the conclusion of the specialized term is
-intuitionistic. If so, one can use all spatial hypotheses for both proving the
-premises and the remaning goal. The argument [p] can either be a Boolean or an
-introduction pattern, which will be coerced into [true] when it solely contains
-`#` or `%` patterns at the top-level.
-
-In case the specialization pattern in [t] states that the modality of the goal
-should be kept for one of the premises (i.e. [>[H1 .. Hn]] is used) then [p]
-defaults to [false] (i.e. spatial hypotheses are not preserved). *)
+(** The tactics [iSpecialize trm as #] and [iSpecializeCore trm as true] allow
+one to use the entire spatial context /twice/: the first time for proving the
+premises [Q1 .. Qn] of [H : Q1 -* .. -∗ Qn -∗ R], and the second time for
+proving the remaining goal. This is possible if all of the following properties
+hold:
+1. The conclusion [R] of the hypothesis [H] is persistent.
+2. The specialization pattern [[> ..]] for wrapping a modality is not used for
+   any of the premises [Q1 .. Qn].
+3. The BI is either affine, or the hypothesis [H] resides in the intuitionistic
+   context.
+
+The copying of the context for proving the premises of [H] and the remaining
+goal is implemented using the lemma [tac_specialize_intuitionistic_helper].
+
+Since the tactic [iSpecialize .. as #] is used a helper to implement
+[iDestruct .. as "#.."], [iPoseProof .. as "#.."], [iSpecialize .. as "#.."],
+and friends, the behavior on violations of these conditions is as follows:
+
+- If condition 1 is violated (i.e. the conclusion [R] of [H] is not persistent),
+  the tactic will fail.
+- If condition 2 or 3 is violated, the tactic will fall back to consuming the
+  hypotheses for proving the premises [Q1 .. Qn]. That is, it will fall back to
+  not using [tac_specialize_intuitionistic_helper].
+
+The function [use_tac_specialize_intuitionistic_helper Δ pat] below returns
+[true] iff the specialization pattern [pat] consumes any spatial hypotheses,
+and does not contain the pattern [[> ..]] (cf. condition 2). If the function
+returns [false], then the conclusion can be moved in the intuitionistic context
+even if conditions 1 and 3 do not hold. Therefore, in that case, we prefer
+putting the conclusion to the intuitionistic context directly and not using
+[tac_specialize_intuitionistic_helper], which requires conditions 1 and 3. *)
+Fixpoint use_tac_specialize_intuitionistic_helper {M}
+    (Δ : envs M) (pats : list spec_pat) : bool :=
+  match pats with
+  | [] => false
+  | (SForall | SPureGoal _) :: pats =>
+     use_tac_specialize_intuitionistic_helper Δ pats
+  | SAutoFrame _ :: _ => true
+  | SIdent H _ :: pats =>
+     match envs_lookup_delete false H Δ with
+     | Some (false, _, Δ) => true
+     | Some (true, _, Δ) => use_tac_specialize_intuitionistic_helper Δ pats
+     | None => false (* dummy case (invalid pattern, will fail in the tactic anyway) *)
+     end
+  | SGoal (SpecGoal GModal _ _ _ _) :: _ => false
+  | SGoal (SpecGoal GIntuitionistic _ _ _ _) :: pats =>
+     use_tac_specialize_intuitionistic_helper Δ pats
+  | SGoal (SpecGoal GSpatial neg Hs_frame Hs _) :: pats =>
+     match envs_split (if neg is true then Right else Left)
+                      (if neg then Hs else pm_app Hs_frame Hs) Δ with
+     | Some (Δ1,Δ2) => if env_spatial_is_nil Δ1
+                       then use_tac_specialize_intuitionistic_helper Δ2 pats
+                       else true
+     | None => false (* dummy case (invalid pattern, will fail in the tactic anyway) *)
+     end
+  end.
+
+(** The argument [p] of [iSpecializeCore] can either be a Boolean, or an
+introduction pattern that is coerced into [true] when it solely contains [#] or
+[%] patterns at the top-level. *)
 Tactic Notation "iSpecializeCore" open_constr(H)
     "with" open_constr(xs) open_constr(pat) "as" constr(p) :=
   let p := intro_pat_intuitionistic p in
@@ -972,25 +1023,17 @@ Tactic Notation "iSpecializeCore" open_constr(H)
   iSpecializeArgs H xs; [..|
     lazymatch type of H with
     | ident =>
-       (* The lemma [tac_specialize_intuitionistic_helper] allows one to use the
-       whole spatial context for:
-       - proving the premises of the lemma we specialize, and,
-       - the remaining goal.
-
-       We can only use if all of the following properties hold:
-       - The result of the specialization is persistent.
-       - No modality is eliminated.
-       - If the BI is not affine, the hypothesis should be in the intuitionistic
-         context.
-
-       As an optimization, we do only use [tac_specialize_intuitionistic_helper]
-       if no implications nor wands are eliminated, i.e. [pat ≠ []]. *)
        let pat := spec_pat.parse pat in
-       lazymatch eval compute in
-         (p && bool_decide (pat ≠ []) && negb (existsb spec_pat_modal pat)) with
+       let Δ := iGetCtx in
+       (* Check if we should use [tac_specialize_intuitionistic_helper]. Notice
+       that [pm_eval] does not unfold [use_tac_specialize_intuitionistic_helper],
+       so we should do that first. *)
+       let b := eval cbv [use_tac_specialize_intuitionistic_helper] in
+         (if p then use_tac_specialize_intuitionistic_helper Δ pat else false) in
+       lazymatch eval pm_eval in b with
        | true =>
-          (* Check that if the BI is not affine, the hypothesis is in the
-          intuitionistic context. *)
+          (* Check that the BI is either affine, or the hypothesis [H] resides
+          in the intuitionistic context. *)
           lazymatch iTypeOf H with
           | Some (?q, _) =>
              let PROP := iBiOfGoal in
@@ -999,10 +1042,11 @@ Tactic Notation "iSpecializeCore" open_constr(H)
                 notypeclasses refine (tac_specialize_intuitionistic_helper _ H _ _ _ _ _ _ _ _ _ _);
                   [pm_reflexivity
                    (* This premise, [envs_lookup j Δ = Some (q,P)],
-                   holds because [iTypeOf] succeeded *)
+                   holds because the [iTypeOf] above succeeded *)
                   |pm_reduce; iSolveTC
-                   (* This premise, [if q then TCTrue else BiAffine PROP],
-                   holds because [q || TC_to_bool (BiAffine PROP)] is true *)
+                   (* This premise, [if q then TCTrue else BiAffine PROP], holds
+                   because we established that [q || TC_to_bool (BiAffine PROP)]
+                   is true *)
                   |iSpecializePat H pat;
                     [..
                     |notypeclasses refine (tac_specialize_intuitionistic_helper_done _ H _ _ _);
-- 
GitLab