From f175420516ad6eb32833a206b0b211535f78cb48 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sat, 4 Jul 2026 16:51:42 +0200 Subject: [PATCH 01/86] Use `colGt` and `ppSpace` for specialisation pattern syntax --- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index 35612ca96..baaea6a8c 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -23,11 +23,11 @@ declare_syntax_cat specPat syntax ident : specPat syntax "%" term:max : specPat -syntax "[" frameIdent* optional("//") "]" optional(" as " ident) : specPat -syntax "[" "-" frameIdent* optional("//") "]" optional(" as " ident) : specPat -syntax "[>" frameIdent* optional("//") "]" optional(" as " ident) : specPat -syntax "[>" "-" frameIdent* optional("//") "]" optional(" as " ident) : specPat -syntax "[#" frameIdent* optional("//") "]" optional(" as " ident) : specPat +syntax "[" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +syntax "[" "-" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +syntax "[>" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +syntax "[>" "-" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +syntax "[#" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat syntax "[" "$" "]" : specPat syntax "[>" "$" "]" : specPat syntax "[#" "$" "]" : specPat From 55224afbbf53b44cfa2e69bf7c8abb8d656c41ce Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sat, 4 Jul 2026 17:12:10 +0200 Subject: [PATCH 02/86] Add docstrings to `specPat` syntax declarations --- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index baaea6a8c..563723097 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -1,12 +1,12 @@ /- Copyright (c) 2025 Oliver Soeser. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Oliver Soeser, Zongyuan Liu, Yunsong Yang, Michael Sammler +Authors: Oliver Soeser, Zongyuan Liu, Yunsong Yang, Michael Sammler, Alvin Tang -/ module public import Lean.Syntax -meta import Iris.Std.RocqPorting +public meta import Iris.Std.RocqPorting @[expose] public section @@ -22,14 +22,47 @@ syntax ident : frameIdent declare_syntax_cat specPat syntax ident : specPat +/-- Use a proof or a tactic sequence to match a pure hypothesis in the premise. -/ syntax "%" term:max : specPat +/-- + `[ H₁ … Hₙ ]` generates a subgoal for the premise with `H₁ … Hₙ` as the + specified hypotheses. + `[ H₁ … Hₙ // ]` attempts to solve the subgoal using `itrivial`. +-/ syntax "[" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +/-- + `[- H₁ … Hₙ ]` generates a subgoal for the premise with all but `H₁ … Hₙ` + as the specified hypotheses. + `[- H₁ … Hₙ // ]` attempts to solve the subgoal using `itrivial`. +-/ syntax "[" "-" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +/-- + `[> H₁ … Hₙ ]` generates a subgoal for the premise with `H₁ … Hₙ` as the + specified hypotheses with the subgoal wrapped in a modality. + `[> H₁ … Hₙ // ]` attempts to solve the subgoal using `itrivial`. +-/ syntax "[>" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +/-- + `[> H₁ … Hₙ ]` generates a subgoal for the premise with all but `H₁ … Hₙ` as the + specified hypotheses with the subgoal wrapped in a modality. + `[> H₁ … Hₙ // ]` attempts to solve the subgoal using `itrivial`. +-/ syntax "[>" "-" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +/-- + `[# H₁ … Hₙ ]` generates a subgoal for the persistent premise + with all hypotheses in the context available for the subgoal. + `[# H₁ … Hₙ // ]` attempts to solve the subgoal using `itrivial`. +-/ syntax "[#" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +/-- + `[$]` solves the subgoal by framing, first with spatial hypotheses, + then with intuitionistic hypotheses. Spatial hypotheses that are not framed + are carried over to the subsequent goal. +-/ syntax "[" "$" "]" : specPat +/-- `[>$]` solves the subgoal by wrapping the premise with the modality and then by framing. -/ syntax "[>" "$" "]" : specPat +/-- `[#$]` solves the subgoal for the persistent premise by framing. -/ syntax "[#" "$" "]" : specPat @[rocq_alias goal_kind] From 6b4bdbd38a016108461e86573fcf5d0188d831b3 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sat, 4 Jul 2026 17:21:38 +0200 Subject: [PATCH 03/86] Improve docstrings and condense `specPat` syntax --- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 43 ++++++------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index 563723097..6bab9334e 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -26,28 +26,19 @@ syntax ident : specPat syntax "%" term:max : specPat /-- `[ H₁ … Hₙ ]` generates a subgoal for the premise with `H₁ … Hₙ` as the - specified hypotheses. - `[ H₁ … Hₙ // ]` attempts to solve the subgoal using `itrivial`. + hypotheses chosen for the context of the subgoal. `[- H₁ … Hₙ ]` is analogous + with all but `H₁ … Hₙ` as the chosen hypotheses. `[ H₁ … Hₙ ]` and + `[- H₁ … Hₙ // ]` attempt to solve the subgoal using `itrivial`. -/ -syntax "[" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat -/-- - `[- H₁ … Hₙ ]` generates a subgoal for the premise with all but `H₁ … Hₙ` - as the specified hypotheses. - `[- H₁ … Hₙ // ]` attempts to solve the subgoal using `itrivial`. --/ -syntax "[" "-" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +syntax "[" ("-")? (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat /-- `[> H₁ … Hₙ ]` generates a subgoal for the premise with `H₁ … Hₙ` as the - specified hypotheses with the subgoal wrapped in a modality. - `[> H₁ … Hₙ // ]` attempts to solve the subgoal using `itrivial`. --/ -syntax "[>" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat -/-- - `[> H₁ … Hₙ ]` generates a subgoal for the premise with all but `H₁ … Hₙ` as the - specified hypotheses with the subgoal wrapped in a modality. - `[> H₁ … Hₙ // ]` attempts to solve the subgoal using `itrivial`. + hypotheses chosen for the context of the subgoal wrapped in a modality. + `[> H₁ … Hₙ ]` is analogous with all but `H₁ … Hₙ` as the chosen hypotheses. + `[ H₁ … Hₙ // ]` and `[> H₁ … Hₙ // ]` also attempt to solve the subgoal + using `itrivial`. -/ -syntax "[>" "-" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +syntax "[>" ("-")? (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat /-- `[# H₁ … Hₙ ]` generates a subgoal for the persistent premise with all hypotheses in the context available for the subgoal. @@ -55,7 +46,7 @@ syntax "[>" "-" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt id -/ syntax "[#" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat /-- - `[$]` solves the subgoal by framing, first with spatial hypotheses, + `[$]` solves the subgoal by framing, first with spatial hypotheses, and then with intuitionistic hypotheses. Spatial hypotheses that are not framed are carried over to the subsequent goal. -/ @@ -125,18 +116,12 @@ where go : TSyntax `specPat → Option SpecPat | `(specPat| $name:ident) => some <| .ident name | `(specPat| % $term:term) => some <| .pure term - | `(specPat| [$[$names:frameIdent]* $[//%$trivTk]?] $[as $goal:ident]?) => - let (hyps, frame) := names.toList.partitionMap FrameIdent.parse; - some <| .goal {kind := .spatial, negate := false, trivial := trivTk.isSome, frame, hyps } <| (TSyntax.getId <*> goal).getD .anonymous - | `(specPat| [- $[$names:frameIdent]* $[//%$trivTk]?] $[as $goal:ident]?) => - let (hyps, frame) := names.toList.partitionMap FrameIdent.parse; - some <| .goal {kind := .spatial, negate := true, trivial := trivTk.isSome, frame, hyps } <| (TSyntax.getId <*> goal).getD .anonymous - | `(specPat| [> $[$names:frameIdent]* $[//%$trivTk]?] $[as $goal:ident]?) => + | `(specPat| [$[-%$negTk]? $[$names:frameIdent]* $[//%$trivTk]?] $[as $goal:ident]?) => let (hyps, frame) := names.toList.partitionMap FrameIdent.parse; - some <| .goal {kind := .modal, negate := false, trivial := trivTk.isSome, frame, hyps } <| (TSyntax.getId <*> goal).getD .anonymous - | `(specPat| [> - $[$names:frameIdent]* $[//%$trivTk]?] $[as $goal:ident]?) => + some <| .goal {kind := .spatial, negate := negTk.isSome, trivial := trivTk.isSome, frame, hyps } <| (TSyntax.getId <*> goal).getD .anonymous + | `(specPat| [> $[-%$negTk]? $[$names:frameIdent]* $[//%$trivTk]?] $[as $goal:ident]?) => let (hyps, frame) := names.toList.partitionMap FrameIdent.parse; - some <| .goal {kind := .modal, negate := true, trivial := trivTk.isSome, frame, hyps } <| (TSyntax.getId <*> goal).getD .anonymous + some <| .goal {kind := .modal, negate := negTk.isSome, trivial := trivTk.isSome, frame, hyps } <| (TSyntax.getId <*> goal).getD .anonymous | `(specPat| [# $[$names:frameIdent]* $[//%$trivTk]?] $[as $goal:ident]?) => let (hyps, frame) := names.toList.partitionMap FrameIdent.parse; some <| .goal {kind := .intuitionistic, negate := false, trivial := trivTk.isSome, frame, hyps } <| (TSyntax.getId <*> goal).getD .anonymous From 6465eff534953624d3e4036b0c68375c96728007 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sat, 4 Jul 2026 17:28:18 +0200 Subject: [PATCH 04/86] Add a test for `ispecialize` for the syntax `[- ...]` --- Iris/Iris/Tests/Tactics.lean | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index fb8509a5f..cd0989b25 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1292,6 +1292,15 @@ example [BI PROP] (P Q : PROP) : · rfl iexact H +/-- Tests `ispecialize` with subgoals excluding specified hypotheses -/ +example [BI PROP] (P1 P2 P3 Q : PROP) : P1 -∗ P2 -∗ P3 -∗ (P1 -∗ P2 -∗ P3 -∗ Q) -∗ Q := by + iintro HP1 HP2 HP3 HPQ + ispecialize HPQ $$ [- HP2 HP3] [- HP3] [-] + · iexact HP1 + · iexact HP2 + · iexact HP3 + iexact HPQ + end specialize -- split From e62679bf33f519ef1557673f5414f8b4cdf56322 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sat, 4 Jul 2026 17:44:46 +0200 Subject: [PATCH 05/86] Docstring for `pmTerm` --- Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean b/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean index 73eec6df9..65787a185 100644 --- a/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean +++ b/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean @@ -14,9 +14,12 @@ namespace Iris.ProofMode open Lean declare_syntax_cat pmTerm - -syntax term : pmTerm -syntax term colGt " $$ " (colGt specPat)+ : pmTerm +/-- + The proof mode term `H $$ spat₁ … spatₙ` refers to `H`, a hypothesis or a + Lean term whose conclusion is an entailment, with the specialisation patterns + `spat₁ … spatₙ` applied to its premises. +-/ +syntax term (colGt " $$ " (colGt ppSpace specPat)+)? : pmTerm @[rocq_alias iTrm] structure PMTerm where From 69674fdc97f34495e5d2c85565921633af592bc3 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 13:23:17 +0200 Subject: [PATCH 06/86] Start extending `processWand` for the remaining specialisation patterns --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 88c7c68da..06877d9c3 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -118,11 +118,7 @@ private def processWand : addBIGoal hyps goal g let pf := q(specialize_wand_subgoal $out₂ $pf $pf' $pf'') return { e := el', hyps := hypsl', p := q(false), out := out₂, pf } - - | { hyps, p, out, pf, .. }, .autoframe kind => do - if kind != .spatial then - -- TODO - throwError "ispecialize: only spatial kind is supported at the moment" + | { hyps, p, out, pf, .. }, .autoframe .spatial => do let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) @@ -130,6 +126,16 @@ private def processWand : let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose return { e := _, hyps := hyps', p := q(false), out := out₂, pf := q(specialize_wand_autoframe $out₂ $pf $pf') } + | { hyps, p, out, pf, .. }, .autoframe .intuitionistic => do + let out₁ ← mkFreshExprMVarQ prop + let out₂ ← mkFreshExprMVarQ prop + let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) + | throwError m!"ispecialize: {out} is not a wand" + let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) + let ⟨_, hyps', pf'⟩ ← res.finishClose + return { e := _, hyps := hyps', p := q(false), out := out₂, pf := q(specialize_wand_autoframe $out₂ $pf $pf') } + | { hyps, p, out, pf, .. }, .autoframe .modal => do + throwError m!"ispecialize: autoframe with the modal kind is not supported at the moment" /-- `iCasesPat.should_try_dup_context` determines when iSpecializeCore should try to duplicate the separation context. The duplication only works if the conclusion of the specialization is persistent. From 95b72fad2526d93b39710185a2f6ffc729242234 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 14:04:32 +0200 Subject: [PATCH 07/86] Add a test for `.autoframe` for the intuitionistic kind --- Iris/Iris/Tests/Tactics.lean | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index cd0989b25..ffa5d69b2 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1301,6 +1301,13 @@ example [BI PROP] (P1 P2 P3 Q : PROP) : P1 -∗ P2 -∗ P3 -∗ (P1 -∗ P2 -∗ · iexact HP3 iexact HPQ +/-- Tests `ispecialize` with autoframing for the intuitionistic kind -/ +example [BI PROP] (P1 P2 P3 Q : PROP) : + □ P1 -∗ P2 -∗ □ P3 -∗ (□ P1 -∗ P2 -∗ P3 -∗ Q) -∗ Q := by + iintro #HP1 HP2 #HP3 HPQ + ispecialize HPQ $$ [#$] [$] [#$] + iexact HPQ + end specialize -- split From f6f317ad9fce6aa9c87724ce0ce2fd2ed2496bd3 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 14:29:33 +0200 Subject: [PATCH 08/86] Add `ipm_backtrack` for `intoAbsorbingly_absorbing` --- Iris/Iris/ProofMode/Instances.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Iris/Iris/ProofMode/Instances.lean b/Iris/Iris/ProofMode/Instances.lean index 8ebc6aa25..ba0f30590 100644 --- a/Iris/Iris/ProofMode/Instances.lean +++ b/Iris/Iris/ProofMode/Instances.lean @@ -573,7 +573,7 @@ instance (priority := default + 30) intoAbsorbingly_true [BI PROP] : IntoAbsorbingly (PROP := PROP) iprop(True) emp where into_absorbingly := absorbingly_emp.2 -@[rocq_alias into_absorbingly_absorbing] +@[ipm_backtrack, rocq_alias into_absorbingly_absorbing] instance (priority := default + 20) intoAbsorbingly_absorbing [BI PROP] (P : PROP) [Absorbing P] : IntoAbsorbingly P P where into_absorbingly := absorbing_absorbingly.2 From 62db85714fb8a9c45050fa46767f9451d74a2c42 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 14:49:56 +0200 Subject: [PATCH 09/86] Towards implementing `.autoframe` for the persistent kind --- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 6 +-- Iris/Iris/ProofMode/Tactics/Specialize.lean | 42 +++++++++++++------ Iris/Iris/Tests/Tactics.lean | 11 +++++ 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index 6bab9334e..0eaaaba9f 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -62,7 +62,7 @@ inductive SpecGoalKind -- TODO: implement | modal -- TODO: implement - | intuitionistic + | persistent deriving Repr, Inhabited, BEq @[rocq_alias spec_goal] @@ -124,8 +124,8 @@ where some <| .goal {kind := .modal, negate := negTk.isSome, trivial := trivTk.isSome, frame, hyps } <| (TSyntax.getId <*> goal).getD .anonymous | `(specPat| [# $[$names:frameIdent]* $[//%$trivTk]?] $[as $goal:ident]?) => let (hyps, frame) := names.toList.partitionMap FrameIdent.parse; - some <| .goal {kind := .intuitionistic, negate := false, trivial := trivTk.isSome, frame, hyps } <| (TSyntax.getId <*> goal).getD .anonymous + some <| .goal {kind := .persistent, negate := false, trivial := trivTk.isSome, frame, hyps } <| (TSyntax.getId <*> goal).getD .anonymous | `(specPat| [$]) => some <| .autoframe .spatial - | `(specPat| [# $]) => some <| .autoframe .intuitionistic + | `(specPat| [# $]) => some <| .autoframe .persistent | `(specPat| [> $]) => some <| .autoframe .modal | _ => none diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 06877d9c3..fd1c0fa8d 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -35,12 +35,20 @@ theorem specialize_wand_subgoal [BI PROP] {q : Bool} {A1 A2 A3 A4 Q P1 : PROP} P refine h1.trans <| (sep_mono_left h2.1).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_left h3).trans ?_)) exact (sep_mono_right inst.1).trans wand_elim_right -theorem specialize_wand_autoframe [BI PROP] {q : Bool} {A1 A2 A3 Q P1 : PROP} P2 +theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} {A1 A2 A3 Q P1 : PROP} P2 (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ P1) [inst : IntoWand q false Q .out P1 .out P2] : A1 ⊢ A3 ∗ P2 := h1.trans <| (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) +theorem specialize_wand_autoframe_persistent [BI PROP] {q : Bool} {A1 A2 Q P1' : PROP} P1 P2 + (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ P1') + [inst1 : IntoAbsorbingly P1' P1] [inst2 : Persistent P1] + [inst3 : IntoWand q true Q .out P1 .out P2] : + A1 ⊢ A2 ∗ □?q P2 := calc + _ ⊢ A2 ∗ □?q Q := h1 + _ ⊢ A2 ∗ □?q P2 := sorry + theorem specialize_forall [BI PROP] {p : Bool} {A1 A2 P : PROP} {α : Sort _} {Φ : α → PROP} [inst : IntoForall P Φ] (h : A1 ⊢ A2 ∗ □?p P) (a : α) : A1 ⊢ A2 ∗ □?p (Φ a) := by refine h.trans <| sep_mono_right <| intuitionisticallyIf_mono <| inst.1.trans (forall_elim a) @@ -59,7 +67,7 @@ public meta section open Lean Elab Tactic Meta Qq Std structure SpecializeState {prop : Q(Type u)} (bi : Q(BI $prop)) (orig : Q($prop)) where - (e : Q($prop)) (hyps : Hyps bi e) (p : Q(Bool)) (out : Q($prop)) + {e : Q($prop)} (hyps : Hyps bi e) (p : Q(Bool)) (out : Q($prop)) pf : Q($orig ⊢ $e ∗ □?$p $out) private def processWand : @@ -75,7 +83,7 @@ private def processWand : let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p $p1 $out .in $out₁' .out $out₂) | throwError m!"ispecialize: cannot instantiate {out} with {out₁'}" let pf := q(specialize_wand $pf $pf') - return { e := e', hyps := hyps', p := p2, out := out₂, pf } + return { hyps := hyps', p := p2, out := out₂, pf } | { e, hyps, p, out, pf, .. }, .pure t => do let v ← mkFreshLevelMVar let α : Q(Sort v) ← mkFreshExprMVarQ q(Sort v) @@ -117,7 +125,7 @@ private def processWand : else addBIGoal hyps goal g let pf := q(specialize_wand_subgoal $out₂ $pf $pf' $pf'') - return { e := el', hyps := hypsl', p := q(false), out := out₂, pf } + return { hyps := hypsl', p := q(false), out := out₂, pf } | { hyps, p, out, pf, .. }, .autoframe .spatial => do let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop @@ -125,15 +133,25 @@ private def processWand : | throwError m!"ispecialize: {out} is not a wand" let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose - return { e := _, hyps := hyps', p := q(false), out := out₂, pf := q(specialize_wand_autoframe $out₂ $pf $pf') } - | { hyps, p, out, pf, .. }, .autoframe .intuitionistic => do + return { hyps := hyps', p := q(false), out := out₂, + pf := q(specialize_wand_autoframe_spatial $out₂ $pf $pf') } + | { hyps, p, out, pf, .. }, .autoframe .persistent => do let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" - let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) - let ⟨_, hyps', pf'⟩ ← res.finishClose - return { e := _, hyps := hyps', p := q(false), out := out₂, pf := q(specialize_wand_autoframe $out₂ $pf $pf') } + let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) + | throwError m!"ispecialize: {out} is not a wand" + let some _ ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) + | throwError m!"ispecialize: {out₁} is not persistent" + let out₁' ← mkFreshExprMVarQ prop + let some _ ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) + | throwError m!"ispecialize: type class synthessis failed for {out₁} with IntoAbsorbingly" + let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) + let pf' ← res.finish <| fun hyps goal => do + let some pf ← iTrivial hyps goal + | throwError "ispecialize: unable to solve premise by framing" + return pf + return { hyps, p, out := out₂, + pf := q(specialize_wand_autoframe_persistent $out₁ $out₂ $pf $pf') } | { hyps, p, out, pf, .. }, .autoframe .modal => do throwError m!"ispecialize: autoframe with the modal kind is not supported at the moment" @@ -170,7 +188,7 @@ A tuple containing: def iSpecializeCore {e} (hyps : @Hyps u prop bi e) (pa : Q(Bool)) (A : Q($prop)) (spats : List SpecPat) (try_dup_context : Bool := false) : ProofModeM ((e' : _) × Hyps bi e' × (pb : Q(Bool)) × (B : Q($prop)) × Q($e ∗ □?$pa $A ⊢ $e' ∗ □?$pb $B)) := do let state := { hyps, out := A, p := pa, pf := q(.rfl), .. } - let ⟨_, hyps', pb, B, pf⟩ ← spats.foldlM processWand state + let ⟨hyps', pb, B, pf⟩ ← spats.foldlM processWand state if try_dup_context then -- context duplication succeeds if `B` is persistent, and `A` is persistent or affine let B' : Q($prop) ← mkFreshExprMVarQ q($prop) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index ffa5d69b2..748831b42 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1308,6 +1308,17 @@ example [BI PROP] (P1 P2 P3 Q : PROP) : ispecialize HPQ $$ [#$] [$] [#$] iexact HPQ +/-- + Tests `ispecialize` with autoframing with a persistent hypothesis in the + spatial context used twice. +-/ +example [BI PROP] (φ : Prop) (Q : PROP) : + ⌜φ⌝ -∗ (⌜φ⌝ -∗ Q) -∗ (⌜φ⌝ -∗ Q) -∗ ⌜φ⌝ ∗ Q ∗ Q := by + iintro HP1 HPQ1 HPQ2 + ispecialize HPQ1 $$ [#$] + ispecialize HPQ2 $$ [#$] + iframe + end specialize -- split From a9785bee234295e62b65d80e8a0267a5c13cdc80 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 15:13:51 +0200 Subject: [PATCH 10/86] Finish proof for `specialize_wand_autoframe_persistent` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 27 ++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index fd1c0fa8d..239cd1b5e 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -45,9 +45,24 @@ theorem specialize_wand_autoframe_persistent [BI PROP] {q : Bool} {A1 A2 Q P1' : (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ P1') [inst1 : IntoAbsorbingly P1' P1] [inst2 : Persistent P1] [inst3 : IntoWand q true Q .out P1 .out P2] : - A1 ⊢ A2 ∗ □?q P2 := calc - _ ⊢ A2 ∗ □?q Q := h1 - _ ⊢ A2 ∗ □?q P2 := sorry + A1 ⊢ A2 ∗ □?q P2 := + have h3 : □ P1 ∗ □?q Q ⊢ □?q P2 := by cases q with + | false => exact (sep_mono_right inst3.into_wand).trans wand_elim_right + | true => calc + _ ⊢ □ □ P1 ∗ □ □ Q := sep_mono intuitionistically_idem.mpr intuitionistically_idem.mpr + _ ⊢ □ (□ P1 ∗ □ Q) := intuitionistically_sep_mpr + _ ⊢ □ (□ P1 ∗ (□ P1 -∗ P2)) := intuitionistically_mono <| sep_mono_right <| inst3.into_wand + _ ⊢ □?true P2 := intuitionistically_mono wand_elim_right + calc + _ ⊢ A2 ∗ □?q Q := h1 + _ ⊢ (A2 ∧ A2) ∗ □?q Q := sep_mono_left <| and_intro .rfl .rfl + _ ⊢ (A2 ∧ P1') ∗ □?q Q := sep_mono_left <| and_mono_right h2 + _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right into_absorbingly + _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right <| absorbingly_mono Persistent.persistent + _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right <| absorbingly_persistently.mp + _ ⊢ (A2 ∗ □ P1) ∗ □?q Q := sep_mono_left persistently_and_intuitionistically_sep_right.mp + _ ⊢ A2 ∗ □ P1 ∗ □?q Q := sep_assoc.mp + _ ⊢ A2 ∗ □?q P2 := sep_mono_right h3 theorem specialize_forall [BI PROP] {p : Bool} {A1 A2 P : PROP} {α : Sort _} {Φ : α → PROP} [inst : IntoForall P Φ] (h : A1 ⊢ A2 ∗ □?p P) (a : α) : A1 ⊢ A2 ∗ □?p (Φ a) := by @@ -74,7 +89,7 @@ private def processWand : @SpecializeState u prop bi orig → SpecPat → ProofModeM (SpecializeState bi orig) | { hyps, p, out, pf, .. }, .ident i => do let ivar ← hyps.findWithInfo i - let ⟨e', hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar + let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar let p2 := if p1.constName! == ``true then p else q(false) have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ have : $p2 =Q ($p1 && $p) := ⟨⟩ @@ -110,7 +125,7 @@ private def processWand : if ivars.contains ivar then throwError "ispecialize: {name} used twice" frameIVars := frameIVars.reverse - let ⟨el', _, hypsl', hypsr', pf'⟩ := Hyps.split bi + let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop @@ -152,7 +167,7 @@ private def processWand : return pf return { hyps, p, out := out₂, pf := q(specialize_wand_autoframe_persistent $out₁ $out₂ $pf $pf') } - | { hyps, p, out, pf, .. }, .autoframe .modal => do + | { .. }, .autoframe .modal => do throwError m!"ispecialize: autoframe with the modal kind is not supported at the moment" /-- `iCasesPat.should_try_dup_context` determines when iSpecializeCore should try to duplicate the separation context. From ba99bdb6628be3f081942db75c0dd2f93563f68a Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 15:47:54 +0200 Subject: [PATCH 11/86] Refactor `processWand` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 60 +++++++++++---------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 239cd1b5e..152ba3394 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -1,7 +1,7 @@ /- Copyright (c) 2022 Lars König. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Lars König, Mario Carneiro, Michael Sammler +Authors: Lars König, Mario Carneiro, Michael Sammler, Alvin Tang -/ module @@ -68,7 +68,7 @@ theorem specialize_forall [BI PROP] {p : Bool} {A1 A2 P : PROP} {α : Sort _} { [inst : IntoForall P Φ] (h : A1 ⊢ A2 ∗ □?p P) (a : α) : A1 ⊢ A2 ∗ □?p (Φ a) := by refine h.trans <| sep_mono_right <| intuitionisticallyIf_mono <| inst.1.trans (forall_elim a) -theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B} +theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} (h : P ∗ □?pa A ⊢ P' ∗ □?pb B) (h2 : pa = true ∨ Affine A) [IntoPersistently pb B B'] @@ -81,25 +81,27 @@ theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B} public meta section open Lean Elab Tactic Meta Qq Std -structure SpecializeState {prop : Q(Type u)} (bi : Q(BI $prop)) (orig : Q($prop)) where +private structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig : Q($prop)) where {e : Q($prop)} (hyps : Hyps bi e) (p : Q(Bool)) (out : Q($prop)) pf : Q($orig ⊢ $e ∗ □?$p $out) -private def processWand : - @SpecializeState u prop bi orig → SpecPat → ProofModeM (SpecializeState bi orig) - | { hyps, p, out, pf, .. }, .ident i => do +private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($prop)} + (specState : @SpecializeState u prop bi orig) (spat : SpecPat) : + ProofModeM (@SpecializeState u prop bi orig) := do + let { e, hyps, p, out, pf } := specState + match spat with + | .ident i => do let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar let p2 := if p1.constName! == ``true then p else q(false) have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ have : $p2 =Q ($p1 && $p) := ⟨⟩ - let out₂ ← mkFreshExprMVarQ prop let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p $p1 $out .in $out₁' .out $out₂) | throwError m!"ispecialize: cannot instantiate {out} with {out₁'}" let pf := q(specialize_wand $pf $pf') return { hyps := hyps', p := p2, out := out₂, pf } - | { e, hyps, p, out, pf, .. }, .pure t => do + | .pure t => do let v ← mkFreshLevelMVar let α : Q(Sort v) ← mkFreshExprMVarQ q(Sort v) let Φ : Q($α → $prop) ← mkFreshExprMVarQ q($α → $prop) @@ -111,7 +113,7 @@ private def processWand : let newMVarIds ← getMVarsNoDelayed x for mvar in newMVarIds do addMVarGoal mvar return { e, hyps, p, out := out', pf := q(specialize_forall $pf $x) } - | { hyps, p, out, pf, .. }, .goal {kind, negate, trivial, frame := f, hyps := hs} g => do + | .goal { kind, negate, trivial, frame := f, hyps := hs } g => do if kind != .spatial then -- TODO throwError "ispecialize: only spatial kind is supported at the moment" @@ -141,7 +143,7 @@ private def processWand : addBIGoal hyps goal g let pf := q(specialize_wand_subgoal $out₂ $pf $pf' $pf'') return { hyps := hypsl', p := q(false), out := out₂, pf } - | { hyps, p, out, pf, .. }, .autoframe .spatial => do + | .autoframe .spatial => do let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) @@ -150,25 +152,25 @@ private def processWand : let ⟨_, hyps', pf'⟩ ← res.finishClose return { hyps := hyps', p := q(false), out := out₂, pf := q(specialize_wand_autoframe_spatial $out₂ $pf $pf') } - | { hyps, p, out, pf, .. }, .autoframe .persistent => do - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" - let some _ ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) - | throwError m!"ispecialize: {out₁} is not persistent" - let out₁' ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) - | throwError m!"ispecialize: type class synthessis failed for {out₁} with IntoAbsorbingly" - let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) - let pf' ← res.finish <| fun hyps goal => do - let some pf ← iTrivial hyps goal - | throwError "ispecialize: unable to solve premise by framing" - return pf - return { hyps, p, out := out₂, - pf := q(specialize_wand_autoframe_persistent $out₁ $out₂ $pf $pf') } - | { .. }, .autoframe .modal => do - throwError m!"ispecialize: autoframe with the modal kind is not supported at the moment" + | .autoframe .persistent => + let out₁ ← mkFreshExprMVarQ prop + let out₂ ← mkFreshExprMVarQ prop + let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) + | throwError m!"ispecialize: {out} is not a wand" + let some _ ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) + | throwError m!"ispecialize: {out₁} is not persistent" + let out₁' ← mkFreshExprMVarQ prop + let some _ ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) + | throwError m!"ispecialize: type class synthessis failed for {out₁} with IntoAbsorbingly" + let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) + let pf' ← res.finish <| fun hyps goal => do + let some pf ← iTrivial hyps goal + | throwError "ispecialize: unable to solve premise by framing" + return pf + return { hyps, p, out := out₂, + pf := q(specialize_wand_autoframe_persistent $out₁ $out₂ $pf $pf') } + | .autoframe .modal => + throwError m!"ispecialize: autoframe with the modal kind is not supported at the moment" /-- `iCasesPat.should_try_dup_context` determines when iSpecializeCore should try to duplicate the separation context. The duplication only works if the conclusion of the specialization is persistent. From d0cf88551564143a11e04ea9fde0fefcfe071f91 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 16:20:18 +0200 Subject: [PATCH 12/86] Implement `[# ...]` specialisation pattern, more tests --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 49 ++++++++++++++++----- Iris/Iris/Tests/Tactics.lean | 41 ++++++++++++++++- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 152ba3394..fd14e9df4 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -41,7 +41,7 @@ theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} {A1 A2 A3 Q P1 : h1.trans <| (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) -theorem specialize_wand_autoframe_persistent [BI PROP] {q : Bool} {A1 A2 Q P1' : PROP} P1 P2 +theorem specialize_wand_persistent [BI PROP] {q : Bool} {A1 A2 Q P1' : PROP} P1 P2 (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ P1') [inst1 : IntoAbsorbingly P1' P1] [inst2 : Persistent P1] [inst3 : IntoWand q true Q .out P1 .out P2] : @@ -106,17 +106,14 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro let α : Q(Sort v) ← mkFreshExprMVarQ q(Sort v) let Φ : Q($α → $prop) ← mkFreshExprMVarQ q($α → $prop) let some _ ← ProofModeM.trySynthInstanceQ q(IntoForall $out $Φ) - | throwError "ispecialize: {out} is not a lean premise" + | throwError "ispecialize: {out} is not a Lean premise" let x ← elabTermEnsuringTypeQ (u := .succ .zero) t α have out' : Q($prop) := Expr.headBeta q($Φ $x) have : $out' =Q $Φ $x := ⟨⟩ let newMVarIds ← getMVarsNoDelayed x for mvar in newMVarIds do addMVarGoal mvar return { e, hyps, p, out := out', pf := q(specialize_forall $pf $x) } - | .goal { kind, negate, trivial, frame := f, hyps := hs } g => do - if kind != .spatial then - -- TODO - throwError "ispecialize: only spatial kind is supported at the moment" + | .goal { kind := .spatial, negate, trivial, frame := f, hyps := hs } g => do let mut ivars : IVarIdSet := {} for name in hs do ivars := ivars.insert (← hyps.findWithInfo name) @@ -132,17 +129,45 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" + | throwError m!"ispecialize: {out} is not a wand" let res ← iFrame bi _ hypsr' out₁ (frameIVars.map (⟨.ipm ·, true⟩)) let pf'' ← res.finish λ hyps goal => do if trivial then let some r ← iTrivial hyps goal - | throwError "ispecialize: itrivial could not solve {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" + | throwError "ispecialize: itrivial could not solve {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" return r else addBIGoal hyps goal g let pf := q(specialize_wand_subgoal $out₂ $pf $pf' $pf'') return { hyps := hypsl', p := q(false), out := out₂, pf } + | .goal { kind := .persistent, trivial, frame := f, hyps := hs, .. } g => do + if !hs.isEmpty then + throwError "ispecialize: the subgoal for the persistent premise should not consume hypotheses" + let out₁ ← mkFreshExprMVarQ prop + let out₂ ← mkFreshExprMVarQ prop + let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) + | throwError m!"ispecialize: {out} is not a wand" + let some _ ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) + | throwError m!"ispecialize: {out₁} is not persistent" + let out₁' ← mkFreshExprMVarQ prop + let some _ ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) + | throwError m!"ispecialize: type class synthesis failed for {out₁} with IntoAbsorbingly" + let mut frameIVars : List IVarId := [] + for name in f do + frameIVars := (← hyps.findWithInfo name) :: frameIVars + frameIVars := frameIVars.reverse + let res ← iFrame bi _ hyps out₁' (frameIVars.map (⟨.ipm ·, true⟩)) + let pf' ← res.finish λ hyps goal => do + if trivial then + let some r ← iTrivial hyps goal + | throwError "ispecialize: itrivial could not solve {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" + return r + else + addBIGoal hyps goal g + return { hyps, p, out := out₂, + pf := q(specialize_wand_persistent $out₁ $out₂ $pf $pf') } + | .goal { kind := .modal, .. } _ => + throwError "ispecialize: framing with the modal kind is not supported at the moment" | .autoframe .spatial => do let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop @@ -168,7 +193,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro | throwError "ispecialize: unable to solve premise by framing" return pf return { hyps, p, out := out₂, - pf := q(specialize_wand_autoframe_persistent $out₁ $out₂ $pf $pf') } + pf := q(specialize_wand_persistent $out₁ $out₂ $pf $pf') } | .autoframe .modal => throwError m!"ispecialize: autoframe with the modal kind is not supported at the moment" @@ -202,8 +227,10 @@ A tuple containing: - `B`: Resulting proposition after applying all patterns - `pf`: Proof of `hyps ∗ □?pa A ⊢ hyps' ∗ □?pb B`, =`hyps ∗ □?pa A ⊢ hyps ∗ □ B` if context duplication succeeds -/ -def iSpecializeCore {e} (hyps : @Hyps u prop bi e) (pa : Q(Bool)) (A : Q($prop)) (spats : List SpecPat) (try_dup_context : Bool := false) : - ProofModeM ((e' : _) × Hyps bi e' × (pb : Q(Bool)) × (B : Q($prop)) × Q($e ∗ □?$pa $A ⊢ $e' ∗ □?$pb $B)) := do +def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} + (hyps : Hyps bi e) (pa : Q(Bool)) (A : Q($prop)) + (spats : List SpecPat) (try_dup_context : Bool := false) : + ProofModeM ((e' : _) × Hyps bi e' × (pb : Q(Bool)) × (B : Q($prop)) × Q($e ∗ □?$pa $A ⊢ $e' ∗ □?$pb $B)) := do let state := { hyps, out := A, p := pa, pf := q(.rfl), .. } let ⟨hyps', pb, B, pf⟩ ← spats.foldlM processWand state if try_dup_context then diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 748831b42..c2b8091c2 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -661,7 +661,7 @@ example [BI PROP] (Q : α → PROP) (a b : α) : (∀ x, ∀ y, ⌜x = a⌝ -∗ iintro H iapply H $$ %_ %b %rfl -/-- error: ispecialize: iprop(P a -∗ Q b) is not a lean premise -/ +/-- error: ispecialize: iprop(P a -∗ Q b) is not a Lean premise -/ #guard_msgs in example [BI PROP] (P Q : α → PROP) (a b : α) : (∀ x, ∀ y, P x -∗ Q y) ⊢ P a -∗ Q b := by iintro H HP @@ -1319,6 +1319,45 @@ example [BI PROP] (φ : Prop) (Q : PROP) : ispecialize HPQ2 $$ [#$] iframe +/- Tests `ispecialize` with autoframing, but the premise is not persistent. -/ +/-- error: ispecialize: P is not persistent -/ +#guard_msgs in +example [BI PROP] (φ : Prop) (P Q : PROP) : + P -∗ (P -∗ Q) -∗ True := by + iintro HP HPQ + ispecialize HPQ $$ [#$] + +/-- Tests `ispecialize` for a persistent premise with chosen hypotheses for the subgoal. -/ +example [BI PROP] (P1 P2 P3 Q : PROP) : + P1 -∗ P2 -∗ P3 -∗ + (( P1 ∗ P2) -∗ Q) -∗ + (( P1 ∗ P3) -∗ Q) -∗ + P1 ∗ P2 ∗ P3 ∗ Q ∗ Q := by + iintro HP1 HP2 HP3 HPQ12 HPQ13 + ispecialize HPQ12 $$ [# $HP1] + · iexact HP2 + ispecialize HPQ13 $$ [# $HP1 $HP3] + iframe + +/- + Tests `ispecialize` for handling a persistent premise, except that the + premise is not persistent. +-/ +/-- error: ispecialize: P is not persistent -/ +#guard_msgs in +example [BI PROP] (φ : Prop) (P Q : PROP) : + P -∗ (P -∗ Q) -∗ True := by + iintro HP HPQ + ispecialize HPQ $$ [# $HP] + +/- Tests `ispecialize` with hypotheses chosen to be consumed for a persistent premise. -/ +/-- error: ispecialize: the subgoal for the persistent premise should not consume hypotheses -/ +#guard_msgs in +example [BI PROP] (φ : Prop) (P Q : PROP) : + P -∗ ( P -∗ Q) -∗ True := by + iintro HP HPQ + ispecialize HPQ $$ [# HP] + end specialize -- split From 1053db637cc88abed082652bbfecc6007e8de66d Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 16:38:55 +0200 Subject: [PATCH 13/86] Use `withRef` in `processWand` for more accurate error highlighting --- Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean | 4 ++-- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 6 +++--- Iris/Iris/ProofMode/Tactics/Apply.lean | 6 ++++-- Iris/Iris/ProofMode/Tactics/Specialize.lean | 6 ++++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean b/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean index 65787a185..40257b1a5 100644 --- a/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean +++ b/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean @@ -24,7 +24,7 @@ syntax term (colGt " $$ " (colGt ppSpace specPat)+)? : pmTerm @[rocq_alias iTrm] structure PMTerm where term : Term - spats : List SpecPat + spats : List (Syntax × SpecPat) deriving Repr, Inhabited partial def PMTerm.parse (term : Syntax) : MacroM PMTerm := do @@ -33,7 +33,7 @@ partial def PMTerm.parse (term : Syntax) : MacroM PMTerm := do | `(pmTerm| $trm:term $$ $[$spats:specPat]*) => return ⟨trm, ← parseSpats spats⟩ | _ => Macro.throwUnsupported where - parseSpats (spats : TSyntaxArray `specPat) : MacroM (List SpecPat) := + parseSpats (spats : TSyntaxArray `specPat) : MacroM (List (Syntax × SpecPat)) := return (← spats.toList.mapM fun pat => SpecPat.parse pat.raw) def PMTerm.is_nontrivial (pmt : PMTerm) : Bool := !pmt.spats.isEmpty diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index 0eaaaba9f..eb54c7cfa 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -108,10 +108,10 @@ def FrameIdent.parse : TSyntax `frameIdent → (Ident ⊕ Ident) else .inl default -- should not happen @[rocq_alias spec_pat.parse] -def SpecPat.parse (pat : Syntax) : MacroM SpecPat := do - match go ⟨← expandMacros pat⟩ with +def SpecPat.parse (term : Syntax) : MacroM (Syntax × SpecPat) := do + match go ⟨← expandMacros term⟩ with | none => Macro.throwUnsupported - | some pat => return pat + | some pat => return ⟨term, pat⟩ where go : TSyntax `specPat → Option SpecPat | `(specPat| $name:ident) => some <| .ident name diff --git a/Iris/Iris/ProofMode/Tactics/Apply.lean b/Iris/Iris/ProofMode/Tactics/Apply.lean index dfaa859c8..bb26ee625 100644 --- a/Iris/Iris/ProofMode/Tactics/Apply.lean +++ b/Iris/Iris/ProofMode/Tactics/Apply.lean @@ -40,7 +40,9 @@ Apply a hypothesis `A` to the `goal` by eliminating the wands recursively ## Returns The proof of `hyps ∗ □?p A ⊢ goal` -/ -private partial def iApplyCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : Hyps bi e) (p : Q(Bool)) (A : Q($prop)) (goal : Q($prop)) : ProofModeM Q($e ∗ □?$p $A ⊢ $goal) := do +private partial def iApplyCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} + (hyps : Hyps bi e) (p : Q(Bool)) (A : Q($prop)) (goal : Q($prop)) : + ProofModeM Q($e ∗ □?$p $A ⊢ $goal) := do let B ← mkFreshExprMVarQ q($prop) -- if `A := ?B -∗ goal`, add `B` as a new subgoal and conclude `goal` if let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $A .out $B .in $goal) then @@ -49,7 +51,7 @@ private partial def iApplyCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : -- otherwise, if `A` has the form `?P -∗ ?B`, create a subgoal for `P` and continue with ?B let some ⟨_, hyps', pb, B, pf⟩ ← try? <| iSpecializeCore hyps p A - [.goal {kind := .spatial, negate := false, trivial := false, frame := [], hyps := []} .anonymous] + [⟨← getRef, .goal {kind := .spatial, negate := false, trivial := false, frame := [], hyps := []} .anonymous⟩] | throwError m!"iapply: cannot apply {A} to {goal}" let pf' ← iApplyCore hyps' pb B goal return q($(pf).trans $pf') diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index fd14e9df4..bfb4844db 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -86,9 +86,11 @@ private structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig : pf : Q($orig ⊢ $e ∗ □?$p $out) private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($prop)} - (specState : @SpecializeState u prop bi orig) (spat : SpecPat) : + (specState : @SpecializeState u prop bi orig) (spat : Syntax × SpecPat) : ProofModeM (@SpecializeState u prop bi orig) := do let { e, hyps, p, out, pf } := specState + let ⟨ref, spat⟩ := spat + withRef ref do match spat with | .ident i => do let ivar ← hyps.findWithInfo i @@ -229,7 +231,7 @@ A tuple containing: -/ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : Hyps bi e) (pa : Q(Bool)) (A : Q($prop)) - (spats : List SpecPat) (try_dup_context : Bool := false) : + (spats : List (Syntax × SpecPat)) (try_dup_context : Bool := false) : ProofModeM ((e' : _) × Hyps bi e' × (pb : Q(Bool)) × (B : Q($prop)) × Q($e ∗ □?$pa $A ⊢ $e' ∗ □?$pb $B)) := do let state := { hyps, out := A, p := pa, pf := q(.rfl), .. } let ⟨hyps', pb, B, pf⟩ ← spats.foldlM processWand state From 0dde59c56fc3c65c0babba3fde229c26d94dce79 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 17:03:15 +0200 Subject: [PATCH 14/86] Update `Porting.lean` and `tactics.md` --- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 4 +--- Iris/Iris/ProofMode/Porting.lean | 4 ++-- Iris/tactics.md | 2 ++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index eb54c7cfa..43b048e47 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -59,9 +59,7 @@ syntax "[#" "$" "]" : specPat @[rocq_alias goal_kind] inductive SpecGoalKind | spatial - -- TODO: implement - | modal - -- TODO: implement + | modal -- TODO: implement `.autoframe` and `.goal` for the modal kind in `processWand` | persistent deriving Repr, Inhabited, BEq diff --git a/Iris/Iris/ProofMode/Porting.lean b/Iris/Iris/ProofMode/Porting.lean index dad0da8f9..d406db9a9 100644 --- a/Iris/Iris/ProofMode/Porting.lean +++ b/Iris/Iris/ProofMode/Porting.lean @@ -85,12 +85,12 @@ import Iris.Std.RocqPorting #rocq_concept proofmode "Specialization Patterns" "Nested (pattern: (H spat ...))" missing "" #rocq_concept proofmode "Specialization Patterns" "SPureGoal (pattern: [%])" ported "pattern: %" #rocq_concept proofmode "Specialization Patterns" "SGoal (Kind: Spatial) (pattern: [H1 ...])" ported "pattern: [H1 ...]" -#rocq_concept proofmode "Specialization Patterns" "SGoal (Kind: Intuitionistic) (pattern: [#H1 ...])" missing "pattern: [#H1 ...]" +#rocq_concept proofmode "Specialization Patterns" "SGoal (Kind: Intuitionistic) (pattern: [#H1 ...])" ported "pattern: [#H1 ...]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Kind: Modal) (pattern: [>H1 ...])" missing "pattern: [>H1 ...]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Negate) (pattern: [-H1 ...])" ported "pattern: [-H1 ...]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Frame) (pattern: [$H1])" ported "pattern: [$H1]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Hyps) (pattern: [H1 ...])" ported "pattern: [H1 ...]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Done) (pattern: [... //])" ported "pattern: [... //]" #rocq_concept proofmode "Specialization Patterns" "SAutoFrame (Kind: Spatial) (pattern: [$])" ported "pattern: [$]" -#rocq_concept proofmode "Specialization Patterns" "SAutoFrame (Kind: Intuitionistic) (pattern: [#$])" missing "pattern: [#$]" +#rocq_concept proofmode "Specialization Patterns" "SAutoFrame (Kind: Intuitionistic) (pattern: [#$])" ported "pattern: [#$]" #rocq_concept proofmode "Specialization Patterns" "SAutoFrame (Kind: Modal) (pattern: [>$])" missing "pattern: [>$]" diff --git a/Iris/tactics.md b/Iris/tactics.md index f9e465ec0..50164a687 100644 --- a/Iris/tactics.md +++ b/Iris/tactics.md @@ -110,9 +110,11 @@ Used in [proof mode terms](#proof-mode-terms) after `$$` to eliminate the premis - `%`*t* — Instantiate a universal quantifier with the pure term *t*. - `[`*H₁* ... *Hₙ*`]` — Generate a subgoal for the premise with exactly the spatial hypotheses *Hᵢ*. Hypotheses written as `$H` are framed instead of forming the context. - `[-` *H₁* ... *Hₙ*`]` — Like above, but use all spatial hypotheses *except* the listed ones. +- `[-` `$`*H₁* ... `$`*Hₙ*`]` — Generate a subgoal for the *persistent* premise with the hypotheses *H₁*, ..., *Hₙ* being framed. The spatial hypotheses used for framing are not consumed. - `[`... `//]` — Additionally try to solve the subgoal with `itrivial` and fail if unsuccessful. - `[`...`] as` *name* — Name the generated subgoal *name*. - `[$]` — Solve the premise entirely by framing spatial and intuitionistic hypotheses. +- `[#$]` — Solve the *persistent* premise by framing spatial and intutionistic hypotheses. The spatial hypotheses used for framing are not consumed. If the conclusion of a specialization is persistent, the context can be shared between the subgoal and the main goal (e.g. `ihave #HQ : □ Q $$ [HP]` keeps `HP` usable in the main goal). From 976511e1150a1a6d16ae475558c693a917275e85 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 17:06:37 +0200 Subject: [PATCH 15/86] Typo fix --- Iris/tactics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Iris/tactics.md b/Iris/tactics.md index 50164a687..ec0648978 100644 --- a/Iris/tactics.md +++ b/Iris/tactics.md @@ -110,7 +110,7 @@ Used in [proof mode terms](#proof-mode-terms) after `$$` to eliminate the premis - `%`*t* — Instantiate a universal quantifier with the pure term *t*. - `[`*H₁* ... *Hₙ*`]` — Generate a subgoal for the premise with exactly the spatial hypotheses *Hᵢ*. Hypotheses written as `$H` are framed instead of forming the context. - `[-` *H₁* ... *Hₙ*`]` — Like above, but use all spatial hypotheses *except* the listed ones. -- `[-` `$`*H₁* ... `$`*Hₙ*`]` — Generate a subgoal for the *persistent* premise with the hypotheses *H₁*, ..., *Hₙ* being framed. The spatial hypotheses used for framing are not consumed. +- `[#` `$`*H₁* ... `$`*Hₙ*`]` — Generate a subgoal for the *persistent* premise with the hypotheses *H₁*, ..., *Hₙ* being framed. The spatial hypotheses used for framing are not consumed. - `[`... `//]` — Additionally try to solve the subgoal with `itrivial` and fail if unsuccessful. - `[`...`] as` *name* — Name the generated subgoal *name*. - `[$]` — Solve the premise entirely by framing spatial and intuitionistic hypotheses. From b5921a6a64a8deb2de3fcd1e54b6b07f1f3b68ef Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 17:22:34 +0200 Subject: [PATCH 16/86] Extend `specPat` syntax for nested specialisation patterns --- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 25 ++++++++++++++----- Iris/Iris/ProofMode/Tactics/Specialize.lean | 3 ++- Iris/Iris/Tests/Tactics.lean | 8 ++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index 43b048e47..ff4910b5a 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -55,6 +55,12 @@ syntax "[" "$" "]" : specPat syntax "[>" "$" "]" : specPat /-- `[#$]` solves the subgoal for the persistent premise by framing. -/ syntax "[#" "$" "]" : specPat +/-- + `(H spat₁ … spatₙ)` specialises the hypothesis `H` with the specialisation + patterns `spat₁ … spatₙ` before the resultant hypothesis is itself used for + matching a premise. +-/ +syntax "(" colGt ident "$$" (colGt ppSpace specPat)+ ppSpace ")" : specPat @[rocq_alias goal_kind] inductive SpecGoalKind @@ -75,7 +81,7 @@ deriving Repr, Inhabited -- see https://gitlab.mpi-sws.org/iris/iris/-/blob/master/iris/proofmode/spec_patterns.v?ref_type=heads#L15 @[rocq_alias spec_pat] inductive SpecPat - | ident (name : Ident) + | ident (name : Ident) (recursiveSpecPats : List <| Syntax × SpecPat) | pure (t : Term) | goal (goal : SpecGoal) (goalName : Name) | autoframe (goal : SpecGoalKind) @@ -106,13 +112,20 @@ def FrameIdent.parse : TSyntax `frameIdent → (Ident ⊕ Ident) else .inl default -- should not happen @[rocq_alias spec_pat.parse] -def SpecPat.parse (term : Syntax) : MacroM (Syntax × SpecPat) := do - match go ⟨← expandMacros term⟩ with - | none => Macro.throwUnsupported - | some pat => return ⟨term, pat⟩ +partial def SpecPat.parse (term : Syntax) : MacroM (Syntax × SpecPat) := do + let stx ← expandMacros term + match stx with + -- Recursive call for nested specialisation patterns + | `(specPat| ($name:ident $$ $[$spats:specPat]*)) => + let nested ← spats.toList.mapM (SpecPat.parse ·.raw) + return ⟨term, .ident name nested⟩ + | _ => + match go ⟨stx⟩ with + | none => Macro.throwUnsupported + | some pat => return ⟨term, pat⟩ where go : TSyntax `specPat → Option SpecPat - | `(specPat| $name:ident) => some <| .ident name + | `(specPat| $name:ident) => some <| .ident name [] | `(specPat| % $term:term) => some <| .pure term | `(specPat| [$[-%$negTk]? $[$names:frameIdent]* $[//%$trivTk]?] $[as $goal:ident]?) => let (hyps, frame) := names.toList.partitionMap FrameIdent.parse; diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index bfb4844db..27821f103 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -92,7 +92,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro let ⟨ref, spat⟩ := spat withRef ref do match spat with - | .ident i => do + | .ident i [] => do let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar let p2 := if p1.constName! == ``true then p else q(false) @@ -103,6 +103,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro throwError m!"ispecialize: cannot instantiate {out} with {out₁'}" let pf := q(specialize_wand $pf $pf') return { hyps := hyps', p := p2, out := out₂, pf } + | .ident _ _ => throwError "ispecialize: nested specialisation patterns are not yet supported" | .pure t => do let v ← mkFreshLevelMVar let α : Q(Sort v) ← mkFreshExprMVarQ q(Sort v) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index c2b8091c2..113f2c409 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1358,6 +1358,14 @@ example [BI PROP] (φ : Prop) (P Q : PROP) : iintro HP HPQ ispecialize HPQ $$ [# HP] +/- TODO: tests `ispecialize` with nested specialisation patterns. -/ +/-- error: ispecialize: nested specialisation patterns are not yet supported -/ +#guard_msgs in +example [BI PROP] (P Q R : PROP) : ⊢ (P -∗ Q) -∗ (Q -∗ R) -∗ P -∗ R := by + iintro HPQ HQR HP + ispecialize HQR $$ (HPQ $$ HP) + iexact HQR + end specialize -- split From 6f57286efeea7aae0411e6a2b3fdd450739524cc Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 17:37:37 +0200 Subject: [PATCH 17/86] Implement nested specialisation patterns --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 46 ++++++++++++++------- Iris/Iris/Tests/Tactics.lean | 13 +++--- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 27821f103..6c8f0a147 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -81,10 +81,12 @@ theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} public meta section open Lean Elab Tactic Meta Qq Std -private structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig : Q($prop)) where +structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig : Q($prop)) where {e : Q($prop)} (hyps : Hyps bi e) (p : Q(Bool)) (out : Q($prop)) pf : Q($orig ⊢ $e ∗ □?$p $out) +mutual + private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($prop)} (specState : @SpecializeState u prop bi orig) (spat : Syntax × SpecPat) : ProofModeM (@SpecializeState u prop bi orig) := do @@ -103,7 +105,17 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro throwError m!"ispecialize: cannot instantiate {out} with {out₁'}" let pf := q(specialize_wand $pf $pf') return { hyps := hyps', p := p2, out := out₂, pf } - | .ident _ _ => throwError "ispecialize: nested specialisation patterns are not yet supported" + | .ident i spats => + let ivar ← hyps.findWithInfo i + let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar + let ⟨_, hyps'', pB, B, pfNest⟩ ← iSpecializeCore hyps' p1 out₁' spats + let p2 := if pB.constName! == ``true then p else q(false) + have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ + have : $p2 =Q ($pB && $p) := ⟨⟩ + let out₂ ← mkFreshExprMVarQ prop + let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) | + throwError m!"ispecialize: cannot instantiate {out} with {B}" + return { hyps := hyps'', p := p2, out := out₂, pf := q(sorry) } | .pure t => do let v ← mkFreshLevelMVar let α : Q(Sort v) ← mkFreshExprMVarQ q(Sort v) @@ -200,20 +212,6 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro | .autoframe .modal => throwError m!"ispecialize: autoframe with the modal kind is not supported at the moment" -/-- `iCasesPat.should_try_dup_context` determines when iSpecializeCore should try to duplicate the separation context. -The duplication only works if the conclusion of the specialization is persistent. - -TODO: Should this also return true for lists of intuitionistic patterns? (check in Rocq) - -TODO: This also needs to check that there are no modality addition patterns in `pat` once they are implemented. --/ -@[rocq_alias intro_pat_intuitionistic, rocq_alias use_tac_specialize_intuitionistic_helper] -def iCasesPat.should_try_dup_context (pat : iCasesPat) : Bool := - match pat with - | .intuitionistic _ => true - | .pure _ => true - | _ => false - /-- Specialize a proposition `A` by applying a sequence of specialization patterns. ## Parameters @@ -251,6 +249,22 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} return ⟨_, hyps, q(true), B', q(specialize_dup_context $pf $af)⟩ return ⟨_, hyps', pb, B, pf⟩ +end + +/-- `iCasesPat.should_try_dup_context` determines when iSpecializeCore should try to duplicate the separation context. +The duplication only works if the conclusion of the specialization is persistent. + +TODO: Should this also return true for lists of intuitionistic patterns? (check in Rocq) + +TODO: This also needs to check that there are no modality addition patterns in `pat` once they are implemented. +-/ +@[rocq_alias intro_pat_intuitionistic, rocq_alias use_tac_specialize_intuitionistic_helper] +def iCasesPat.should_try_dup_context (pat : iCasesPat) : Bool := + match pat with + | .intuitionistic _ => true + | .pure _ => true + | _ => false + /-- `ispecialize pmt` specialises a hypothesis according to `pmt : pmTerm`. -/ diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 113f2c409..afd41233e 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1358,13 +1358,12 @@ example [BI PROP] (φ : Prop) (P Q : PROP) : iintro HP HPQ ispecialize HPQ $$ [# HP] -/- TODO: tests `ispecialize` with nested specialisation patterns. -/ -/-- error: ispecialize: nested specialisation patterns are not yet supported -/ -#guard_msgs in -example [BI PROP] (P Q R : PROP) : ⊢ (P -∗ Q) -∗ (Q -∗ R) -∗ P -∗ R := by - iintro HPQ HQR HP - ispecialize HQR $$ (HPQ $$ HP) - iexact HQR +/- Tests `ispecialize` with nested specialisation patterns. -/ +example [BI PROP] (P Q R S T : PROP) : + ⊢ (P -∗ T -∗ Q) -∗ (Q -∗ T -∗ R) -∗ (R -∗ S) -∗ P -∗ T -∗ S := by + iintro HPTQ HQTR HRS HP HT + ispecialize HRS $$ (HQTR $$ (HPTQ $$ HP [# $HT]) [HT //]) + iassumption end specialize From a0f06d5e9e90efc3bd52c390fda52a8e02000e29 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 17:46:30 +0200 Subject: [PATCH 18/86] Finish the proof for nested specialisation patterns in `processWand` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 6c8f0a147..a56e1c315 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -17,10 +17,10 @@ public section open BI theorem specialize_wand [BI PROP] {q p : Bool} {A1 A2 A3 Q P1 P2 : PROP} - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊣⊢ A3 ∗ □?p P1) + (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ □?p P1) [h3 : IntoWand q p Q .in P1 .out P2] : A1 ⊢ A3 ∗ □?(p && q) P2 := by - refine h1.trans <| (sep_mono_left h2.1).trans <| sep_assoc.1.trans (sep_mono_right ?_) + refine h1.trans <| (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ?_) cases p with | false => exact (sep_mono_right h3.1).trans <| wand_elim_right | true => exact @@ -103,7 +103,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro let out₂ ← mkFreshExprMVarQ prop let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p $p1 $out .in $out₁' .out $out₂) | throwError m!"ispecialize: cannot instantiate {out} with {out₁'}" - let pf := q(specialize_wand $pf $pf') + let pf := q(specialize_wand $pf $(pf').mp) return { hyps := hyps', p := p2, out := out₂, pf } | .ident i spats => let ivar ← hyps.findWithInfo i @@ -115,7 +115,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro let out₂ ← mkFreshExprMVarQ prop let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) | throwError m!"ispecialize: cannot instantiate {out} with {B}" - return { hyps := hyps'', p := p2, out := out₂, pf := q(sorry) } + return { hyps := hyps'', p := p2, out := out₂, pf := q(specialize_wand $pf ($(pf').mp.trans $pfNest)) } | .pure t => do let v ← mkFreshLevelMVar let α : Q(Sort v) ← mkFreshExprMVarQ q(Sort v) From 1a71cc50e699524fa99f8eba1e01a45e72076ba0 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 17:52:12 +0200 Subject: [PATCH 19/86] Update documentation --- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 2 +- Iris/Iris/ProofMode/Porting.lean | 2 +- Iris/tactics.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index ff4910b5a..5675e329c 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -56,7 +56,7 @@ syntax "[>" "$" "]" : specPat /-- `[#$]` solves the subgoal for the persistent premise by framing. -/ syntax "[#" "$" "]" : specPat /-- - `(H spat₁ … spatₙ)` specialises the hypothesis `H` with the specialisation + `(H $$ spat₁ … spatₙ)` specialises the hypothesis `H` with the specialisation patterns `spat₁ … spatₙ` before the resultant hypothesis is itself used for matching a premise. -/ diff --git a/Iris/Iris/ProofMode/Porting.lean b/Iris/Iris/ProofMode/Porting.lean index d406db9a9..773296881 100644 --- a/Iris/Iris/ProofMode/Porting.lean +++ b/Iris/Iris/ProofMode/Porting.lean @@ -82,7 +82,7 @@ import Iris.Std.RocqPorting #rocq_concept proofmode "Specialization Patterns" missing "" #rocq_concept proofmode "Specialization Patterns" "SIdent (pattern: H)" ported "pattern: H" -#rocq_concept proofmode "Specialization Patterns" "Nested (pattern: (H spat ...))" missing "" +#rocq_concept proofmode "Specialization Patterns" "Nested (pattern: (H spat ...))" ported "pattern: (H $$ spat₁ ... spatₙ)" #rocq_concept proofmode "Specialization Patterns" "SPureGoal (pattern: [%])" ported "pattern: %" #rocq_concept proofmode "Specialization Patterns" "SGoal (Kind: Spatial) (pattern: [H1 ...])" ported "pattern: [H1 ...]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Kind: Intuitionistic) (pattern: [#H1 ...])" ported "pattern: [#H1 ...]" diff --git a/Iris/tactics.md b/Iris/tactics.md index ec0648978..39a5460df 100644 --- a/Iris/tactics.md +++ b/Iris/tactics.md @@ -115,6 +115,7 @@ Used in [proof mode terms](#proof-mode-terms) after `$$` to eliminate the premis - `[`...`] as` *name* — Name the generated subgoal *name*. - `[$]` — Solve the premise entirely by framing spatial and intuitionistic hypotheses. - `[#$]` — Solve the *persistent* premise by framing spatial and intutionistic hypotheses. The spatial hypotheses used for framing are not consumed. +- `(` *H* `$$` *spat₁* ... *spatₙ* `)` — specialises the hypothesis *H* with the specialisation patterns *spat₁*, ..., *spatₙ* before the resultant hypothesis is itself used for matching a premise. If the conclusion of a specialization is persistent, the context can be shared between the subgoal and the main goal (e.g. `ihave #HQ : □ Q $$ [HP]` keeps `HP` usable in the main goal). From ca7332f74dc182517ea907c2a44950c0be3572ce Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 5 Jul 2026 18:12:31 +0200 Subject: [PATCH 20/86] Minor docstring improvement --- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index 5675e329c..0722a014d 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -40,9 +40,10 @@ syntax "[" ("-")? (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt -/ syntax "[>" ("-")? (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat /-- - `[# H₁ … Hₙ ]` generates a subgoal for the persistent premise - with all hypotheses in the context available for the subgoal. - `[# H₁ … Hₙ // ]` attempts to solve the subgoal using `itrivial`. + `[# $H₁ … $Hₙ ]` generates a subgoal for the persistent premise + with all hypotheses in the context available for the subgoal. The hypotheses + `$H₁ … $Hₙ` are framed. + `[# $H₁ … $Hₙ // ]` further attempts to solve the subgoal using `itrivial`. -/ syntax "[#" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat /-- From 3c14a21c560ac903f3af39c269ef97f3698ef299 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 10:15:45 +0200 Subject: [PATCH 21/86] Use `ProofModeM` type class synthesiser instaed of the built-in one --- Iris/Iris/ProofMode/Tactics/Revert.lean | 3 ++- Iris/Iris/ProofMode/Tactics/Specialize.lean | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Revert.lean b/Iris/Iris/ProofMode/Tactics/Revert.lean index 7245dc91f..1feee2188 100644 --- a/Iris/Iris/ProofMode/Tactics/Revert.lean +++ b/Iris/Iris/ProofMode/Tactics/Revert.lean @@ -57,7 +57,8 @@ private def RevertState.revertLeanPropHyp ProofModeM (@RevertState u prop bi origE origGoal) := do let { e, hyps, goal, reverted, pf } := st let P ← mkFreshExprMVarQ prop - let _hA : Q(MakeAffinely iprop(⌜$φ⌝) $P) ← synthInstanceQ q(MakeAffinely iprop(⌜$φ⌝) $P) + let some _ ← ProofModeM.trySynthInstanceQ q(MakeAffinely iprop(⌜$φ⌝) $P) + | throwError m!"irevert: MakeAffinely type class synthesis failed with {φ}" let hp : Q($φ) := mkFVar f let goal' : Q($prop) := q(iprop($P -∗ $goal)) let pf' : Q(($e ⊢ $goal') → ($origE ⊢ $origGoal)) := q(fun h => $pf (pure_revert h $hp)) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index a56e1c315..1b014e40e 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -237,7 +237,7 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} if try_dup_context then -- context duplication succeeds if `B` is persistent, and `A` is persistent or affine let B' : Q($prop) ← mkFreshExprMVarQ q($prop) - let .some _ ← trySynthInstanceQ q(IntoPersistently $pb $B $B') + let .some _ ← ProofModeM.trySynthInstanceQ q(IntoPersistently $pb $B $B') | return ⟨_, hyps', pb, B, pf⟩ have af : MetaM (Option Q($pa = true ∨ Affine $A)) := match matchBool pa with From 33c9d72d1aa985a4bb0714ecfb2545d5849fbf0d Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 10:53:27 +0200 Subject: [PATCH 22/86] Port the type class `AddModal` and the theorem `addModal_id` --- Iris/Iris/ProofMode/Classes.lean | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Iris/Iris/ProofMode/Classes.lean b/Iris/Iris/ProofMode/Classes.lean index ac2ca161b..33ac1c060 100644 --- a/Iris/Iris/ProofMode/Classes.lean +++ b/Iris/Iris/ProofMode/Classes.lean @@ -185,6 +185,19 @@ class ElimModal {PROP} [BI PROP] (φ : outParam $ Prop) (p : Bool) (p' : outPara elim_modal : φ → □?p P ∗ (□?p' P' -∗ Q') ⊢ Q export ElimModal (elim_modal) +/-- +`AddModal` is used by `ispecialize` and `iassert` to add a modality to the +goal corresponding to the premise/asserted proposition. +-/ +@[ipm_class, rocq_alias AddModal] +class AddModal {PROP} [BI PROP] (P : outParam $ PROP) (P' Q : PROP) where + add_modal : P ∗ (P' -∗ Q) ⊢ Q +export AddModal (add_modal) + +@[rocq_alias add_modal_id] +theorem addModal_id {PROP} [BI PROP] (P Q : PROP) : AddModal P P Q where + add_modal := wand_elim_right + @[ipm_class, rocq_alias Frame] class Frame {PROP} [BI PROP] (p : Bool) (R P : PROP) (Q : outParam $ PROP) where frame : □?p R ∗ Q ⊢ P From 32df6d0275af82d6384a88c6b90608ad92b7bc82 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 11:04:32 +0200 Subject: [PATCH 23/86] Port `add_modal_bupd` --- Iris/Iris/ProofMode/InstancesUpdates.lean | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Iris/Iris/ProofMode/InstancesUpdates.lean b/Iris/Iris/ProofMode/InstancesUpdates.lean index 30edff808..5d67712b0 100644 --- a/Iris/Iris/ProofMode/InstancesUpdates.lean +++ b/Iris/Iris/ProofMode/InstancesUpdates.lean @@ -88,6 +88,11 @@ instance elimModal_bupd p (P Q : PROP) : elim_modal _ := (sep_mono_left intuitionisticallyIf_elim).trans <| bupd_frame_right.trans <| (BIUpdate.mono wand_elim_right).trans BIUpdate.trans +@[rocq_alias add_modal_bupd] +instance addModal_bupd (P Q : PROP) : + AddModal iprop(|==> P) P iprop(|==> Q) where + add_modal := bupd_wand_right.trans bupd_trans + end BIBasicUpdate section SBIBasicUpdate From 9d5f5f0af9c2aa5e03521b775a876e9827776dff Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 11:23:01 +0200 Subject: [PATCH 24/86] Start implementing `.autoframe .modal` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 1b014e40e..823ae8bf6 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -187,7 +187,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" + | throwError m!"ispecialize: {out} is not a wand" let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose return { hyps := hyps', p := q(false), out := out₂, @@ -210,7 +210,16 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro return { hyps, p, out := out₂, pf := q(specialize_wand_persistent $out₁ $out₂ $pf $pf') } | .autoframe .modal => - throwError m!"ispecialize: autoframe with the modal kind is not supported at the moment" + let out₁ ← mkFreshExprMVarQ prop + let out₂ ← mkFreshExprMVarQ prop + let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) + | throwError m!"ispecialize: {out} is not a wand" + let out₁' ← mkFreshExprMVarQ prop + let some _ ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $out₂) + | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {out₂}" + let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) + let ⟨_, hyps', pf'⟩ ← res.finishClose + return { hyps := hyps', p := q(false), out := out₂, pf := q(sorry) } /-- Specialize a proposition `A` by applying a sequence of specialization patterns. From a83284ec87de534297b086cb33b8a17c39c9c53c Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 11:44:52 +0200 Subject: [PATCH 25/86] Pass the proof goal to `processWand` and `iSpecializeCore` for `.autoframe .spatial` Proofs for `.autoframe .spatial` not yet complete --- Iris/Iris/ProofMode/Tactics/Apply.lean | 4 ++-- Iris/Iris/ProofMode/Tactics/Cases.lean | 2 +- Iris/Iris/ProofMode/Tactics/Have.lean | 2 +- Iris/Iris/ProofMode/Tactics/HaveCore.lean | 4 ++-- Iris/Iris/ProofMode/Tactics/Rewrite.lean | 8 ++++---- Iris/Iris/ProofMode/Tactics/Specialize.lean | 20 ++++++++++---------- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Apply.lean b/Iris/Iris/ProofMode/Tactics/Apply.lean index bb26ee625..29ae8a315 100644 --- a/Iris/Iris/ProofMode/Tactics/Apply.lean +++ b/Iris/Iris/ProofMode/Tactics/Apply.lean @@ -50,7 +50,7 @@ private partial def iApplyCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} return q(apply $pf) -- otherwise, if `A` has the form `?P -∗ ?B`, create a subgoal for `P` and continue with ?B - let some ⟨_, hyps', pb, B, pf⟩ ← try? <| iSpecializeCore hyps p A + let some ⟨_, hyps', pb, B, pf⟩ ← try? <| iSpecializeCore hyps p A goal [⟨← getRef, .goal {kind := .spatial, negate := false, trivial := false, frame := [], hyps := []} .anonymous⟩] | throwError m!"iapply: cannot apply {A} to {goal}" let pf' ← iApplyCore hyps' pb B goal @@ -72,7 +72,7 @@ elab "iapply " colGt pmt:pmTerm : tactic => do let pmt ← liftMacroM <| PMTerm.parse pmt ProofModeM.runTactic λ mvar { hyps, goal, .. } => do -- elaborate the proof mode term `pmt` to the hypothesis `out` - let ⟨e, hyps', p, out, pf⟩ ← iHave hyps pmt true + let ⟨e, hyps', p, out, pf⟩ ← iHave hyps goal pmt true -- if `□?p out` directly matches goal, behave like `iexact` if let some _ ← ProofModeM.trySynthInstanceQ q(FromAssumption $p .in $out $goal) then -- ensure the context can be discarded diff --git a/Iris/Iris/ProofMode/Tactics/Cases.lean b/Iris/Iris/ProofMode/Tactics/Cases.lean index 9f5ec3854..b3154c2d4 100644 --- a/Iris/Iris/ProofMode/Tactics/Cases.lean +++ b/Iris/Iris/ProofMode/Tactics/Cases.lean @@ -266,7 +266,7 @@ elab "icases" keep:("+keep ")? colGt pmt:pmTerm " with " colGt pat:icasesPat : t -- We keep the persistent hypothesis if it is required by the user (+keep is set by ihave) -- or if we perform specialization - let ⟨_, hyps, p, A, pf⟩ ← iHave hyps pmt (keep.isSome || pmt.is_nontrivial) + let ⟨_, hyps, p, A, pf⟩ ← iHave hyps goal pmt (keep.isSome || pmt.is_nontrivial) (try_dup_context := pat.should_try_dup_context) -- process pattern diff --git a/Iris/Iris/ProofMode/Tactics/Have.lean b/Iris/Iris/ProofMode/Tactics/Have.lean index 3c97cd888..5e2b80b01 100644 --- a/Iris/Iris/ProofMode/Tactics/Have.lean +++ b/Iris/Iris/ProofMode/Tactics/Have.lean @@ -39,6 +39,6 @@ elab "ihave " colGt pat:icasesPat " : " P:term " $$ " spat:specPat : tactic => d ProofModeM.runTactic λ mvar { prop, bi, hyps, goal, .. } => do let P ← elabTermEnsuringTypeQ (← `(iprop($P))) prop -- establish `P` with `spat` - let ⟨_, hyps', p, A, pf⟩ ← iSpecializeCore hyps q(true) q(iprop($P -∗ $P)) [spat] (try_dup_context := pat.should_try_dup_context) + let ⟨_, hyps', p, A, pf⟩ ← iSpecializeCore hyps q(true) q(iprop($P -∗ $P)) goal [spat] (try_dup_context := pat.should_try_dup_context) let pf2 ← iCasesCore bi hyps' goal pat p A mvar.assign q(ihave_assert (($pf).trans $pf2)) diff --git a/Iris/Iris/ProofMode/Tactics/HaveCore.lean b/Iris/Iris/ProofMode/Tactics/HaveCore.lean index 83e5f6aed..267515cb5 100644 --- a/Iris/Iris/ProofMode/Tactics/HaveCore.lean +++ b/Iris/Iris/ProofMode/Tactics/HaveCore.lean @@ -88,11 +88,11 @@ private def iHaveCore {e} (hyps : @Hyps u prop bi e) return ⟨_, hyps, q(true), hyp, q(have_asEmpValid $val)⟩ -def iHave {e} (hyps : @Hyps u prop bi e) +def iHave {e} (hyps : @Hyps u prop bi e) (goal : Q($prop)) (pmt : PMTerm) (keep : Bool) (try_dup_context : Bool := false) : ProofModeM ((e' : _) × Hyps bi e' × (p : Q(Bool)) × (out : Q($prop)) × Q($e ⊢ $e' ∗ □?$p $out)) := do -- assert `term` as hypothesis `A` let ⟨_, hyps', p, A, pf⟩ ← iHaveCore hyps pmt.term keep -- specialize `A` with `spats` - let ⟨_, hyps'', pb, B, pf'⟩ ← iSpecializeCore hyps' p A pmt.spats (try_dup_context := try_dup_context) + let ⟨_, hyps'', pb, B, pf'⟩ ← iSpecializeCore hyps' p A goal pmt.spats (try_dup_context := try_dup_context) return ⟨_, hyps'', pb, B, q($(pf).trans $pf')⟩ diff --git a/Iris/Iris/ProofMode/Tactics/Rewrite.lean b/Iris/Iris/ProofMode/Tactics/Rewrite.lean index 8e8e2d4f1..3faaa5798 100644 --- a/Iris/Iris/ProofMode/Tactics/Rewrite.lean +++ b/Iris/Iris/ProofMode/Tactics/Rewrite.lean @@ -112,11 +112,11 @@ end rule end IRewrite private def iRewriteCore {prop : Q(Type u)} {bi : Q(BI $prop)} - {e} (hyps : Hyps bi e) (rule : IRewrite.Rule) + {e} (hyps : Hyps bi e) (goal : Q($prop)) (rule : IRewrite.Rule) (target : Q($prop)) (occs : Occurrences := Occurrences.all) : ProofModeM ((target' : Q($prop)) × Q($e ⊢ ($target ∗-∗ $target'))) := do - let ⟨_, _, _, eq, pf⟩ ← iHave hyps rule.term true + let ⟨_, _, _, eq, pf⟩ ← iHave hyps goal rule.term true let .some sbi ← trySynthInstanceQ q(Sbi $prop) | throwError "irewrite: could not synthesize Sbi instance" @@ -164,7 +164,7 @@ def iRewriteGoal {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : Hyps bi e) (rule : IRewrite.Rule) (goal : Q($prop)) (occs : Occurrences := Occurrences.all) : ProofModeM Q($e ⊢ $goal) := do - let ⟨goal', pf⟩ ← iRewriteCore hyps rule goal (occs := occs) + let ⟨goal', pf⟩ ← iRewriteCore hyps goal rule goal (occs := occs) let pf' ← addBIGoal hyps q($goal') return q(rewrite_tac_goal $pf $pf') @@ -174,7 +174,7 @@ def iRewriteHyp {prop : Q(Type u)} {bi : Q(BI $prop)} (occs : Occurrences := Occurrences.all) : ProofModeM ((e' : _) × Hyps bi e' × Q($e ⊢ $e')) := do let some r ← hyps.replace ivar λ _ _ ty => do - let ⟨ty', pf⟩ ← iRewriteCore hyps rule ty (occs := occs) + let ⟨ty', pf⟩ ← iRewriteCore hyps ty rule ty (occs := occs) return ⟨ty', q(rewrite_tac_hyp $pf)⟩ | throwError "irewrite: cannot find hyp" -- should never happen return r diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 823ae8bf6..dde440673 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -81,15 +81,15 @@ theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} public meta section open Lean Elab Tactic Meta Qq Std -structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig : Q($prop)) where +structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig goal : Q($prop)) where {e : Q($prop)} (hyps : Hyps bi e) (p : Q(Bool)) (out : Q($prop)) pf : Q($orig ⊢ $e ∗ □?$p $out) mutual -private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($prop)} - (specState : @SpecializeState u prop bi orig) (spat : Syntax × SpecPat) : - ProofModeM (@SpecializeState u prop bi orig) := do +private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q($prop)} + (specState : @SpecializeState u prop bi orig goal) (spat : Syntax × SpecPat) : + ProofModeM (@SpecializeState u prop bi orig goal) := do let { e, hyps, p, out, pf } := specState let ⟨ref, spat⟩ := spat withRef ref do @@ -108,7 +108,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro | .ident i spats => let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar - let ⟨_, hyps'', pB, B, pfNest⟩ ← iSpecializeCore hyps' p1 out₁' spats + let ⟨_, hyps'', pB, B, pfNest⟩ ← iSpecializeCore hyps' p1 out₁' goal spats let p2 := if pB.constName! == ``true then p else q(false) have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ have : $p2 =Q ($pB && $p) := ⟨⟩ @@ -215,8 +215,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig : Q($pro let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) | throwError m!"ispecialize: {out} is not a wand" let out₁' ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $out₂) - | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {out₂}" + let some _ ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) + | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose return { hyps := hyps', p := q(false), out := out₂, pf := q(sorry) } @@ -238,10 +238,10 @@ A tuple containing: - `pf`: Proof of `hyps ∗ □?pa A ⊢ hyps' ∗ □?pb B`, =`hyps ∗ □?pa A ⊢ hyps ∗ □ B` if context duplication succeeds -/ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} - (hyps : Hyps bi e) (pa : Q(Bool)) (A : Q($prop)) + (hyps : Hyps bi e) (pa : Q(Bool)) (A : Q($prop)) (goal : Q($prop)) (spats : List (Syntax × SpecPat)) (try_dup_context : Bool := false) : ProofModeM ((e' : _) × Hyps bi e' × (pb : Q(Bool)) × (B : Q($prop)) × Q($e ∗ □?$pa $A ⊢ $e' ∗ □?$pb $B)) := do - let state := { hyps, out := A, p := pa, pf := q(.rfl), .. } + let state : SpecializeState _ goal := { hyps, out := A, p := pa, pf := q(.rfl), .. } let ⟨hyps', pb, B, pf⟩ ← spats.foldlM processWand state if try_dup_context then -- context duplication succeeds if `B` is persistent, and `A` is persistent or affine @@ -288,7 +288,7 @@ elab "ispecialize " colGt pmt:pmTerm : tactic => do hyps.removeG true λ name ivar' _ _ => if ivar == ivar' then some name else none | throwError "ispecialize: cannot find argument" - let ⟨_, hyps'', pb, B, pf'⟩ ← iSpecializeCore hyps' p out pmt.spats + let ⟨_, hyps'', pb, B, pf'⟩ ← iSpecializeCore hyps' p out goal pmt.spats let hyps''' := Hyps.add bi name ivar pb B hyps'' let pf'' ← addBIGoal hyps''' goal mvar.assign q(($pf).1.trans <| $(pf').trans <| $pf'') From baeeed906a3ef0e23abc38db22f27c7d57d86eb6 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 11:45:44 +0200 Subject: [PATCH 26/86] Add a test for `ispecialize` with `[>$]` using type class instance `addModal_bupd` --- Iris/Iris/Tests/Tactics.lean | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index afd41233e..1211c1c76 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1358,13 +1358,21 @@ example [BI PROP] (φ : Prop) (P Q : PROP) : iintro HP HPQ ispecialize HPQ $$ [# HP] -/- Tests `ispecialize` with nested specialisation patterns. -/ +/-- Tests `ispecialize` with nested specialisation patterns. -/ example [BI PROP] (P Q R S T : PROP) : ⊢ (P -∗ T -∗ Q) -∗ (Q -∗ T -∗ R) -∗ (R -∗ S) -∗ P -∗ T -∗ S := by iintro HPTQ HQTR HRS HP HT ispecialize HRS $$ (HQTR $$ (HPTQ $$ HP [# $HT]) [HT //]) iassumption +/-- Tests `ispecialize` with `.autoframe .modal` using the type class instance `addModal_bupd`. -/ +example [BI PROP] [BIUpdate PROP] (P Q : PROP) : + ⊢ (P -∗ Q) -∗ (|==> P) -∗ (|==> Q) := by + iintro HPQ HP + ispecialize HPQ $$ [>$] + imodintro + iassumption + end specialize -- split From 45f0e96ce031a724cce8fd00edbbcf156bd9a4be Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 15:23:32 +0200 Subject: [PATCH 27/86] Partially refactoring `SpecializeState` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 155 ++++++++++++++------ 1 file changed, 113 insertions(+), 42 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index dde440673..86146a4c9 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -17,34 +17,65 @@ public section open BI theorem specialize_wand [BI PROP] {q p : Bool} {A1 A2 A3 Q P1 P2 : PROP} - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ □?p P1) - [h3 : IntoWand q p Q .in P1 .out P2] : + (inst : IntoWand q p Q .in P1 .out P2) + (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ □?p P1) : A1 ⊢ A3 ∗ □?(p && q) P2 := by - refine h1.trans <| (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ?_) + refine h1.trans <| (sep_mono_left h2).trans <| sep_assoc.mp.trans (sep_mono_right ?_) cases p with - | false => exact (sep_mono_right h3.1).trans <| wand_elim_right + | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right | true => exact - (sep_mono intuitionisticallyIf_intutitionistically.2 intuitionisticallyIf_idem.2).trans <| - intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap h3.1) + (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| + intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) + +theorem specialize_wand_cont [BI PROP] {q p : Bool} {A1 A2 A3 Q P1 P2 : PROP} + (inst : IntoWand q p Q .in P1 .out P2) + (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ A3 ∗ □?p P1) + (h3 : A3 ∗ □?(p && q) P2 ⊢ goal) : + A1 ⊢ goal := by + refine h1 (Entails.trans ?_ h3) + refine (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ?_) + cases p with + | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right + | true => exact + (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| + intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) -- TODO: if q is true and A1 is persistent, this proof can guarantee □ P2 instead of P2 -- see https://gitlab.mpi-sws.org/iris/iris/-/blob/846ed45bed6951035c6204fef365d9a344022ae6/iris/proofmode/coq_tactics.v#L336 theorem specialize_wand_subgoal [BI PROP] {q : Bool} {A1 A2 A3 A4 Q P1 : PROP} P2 - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊣⊢ A3 ∗ A4) (h3 : A4 ⊢ P1) - [inst : IntoWand q false Q .out P1 .out P2] : A1 ⊢ A3 ∗ P2 := by + (inst : IntoWand q false Q .out P1 .out P2) + (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊣⊢ A3 ∗ A4) (h3 : A4 ⊢ P1) : A1 ⊢ A3 ∗ P2 := by refine h1.trans <| (sep_mono_left h2.1).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_left h3).trans ?_)) exact (sep_mono_right inst.1).trans wand_elim_right -theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} {A1 A2 A3 Q P1 : PROP} P2 - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ P1) - [inst : IntoWand q false Q .out P1 .out P2] : A1 ⊢ A3 ∗ P2 := +theorem specialize_wand_subgoal_cont [BI PROP] {q : Bool} {A1 A2 A3 A4 Q P1 : PROP} P2 + (inst : IntoWand q false Q .out P1 .out P2) + (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊣⊢ A3 ∗ A4) (h3 : A4 ⊢ P1) + (h4 : A3 ∗ P2 ⊢ goal) : A1 ⊢ goal := by + refine h1 (Entails.trans ?_ h4) + refine (sep_mono_left h2.1).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_left h3).trans ?_)) + exact (sep_mono_right inst.1).trans wand_elim_right + +theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} + {A1 A2 A3 Q P1 : PROP} P2 + (inst : IntoWand q false Q .out P1 .out P2) + (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ P1) : A1 ⊢ A3 ∗ P2 := h1.trans <| (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) +theorem specialize_wand_autoframe_spatial_cont [BI PROP] {q : Bool} + {A1 A2 A3 Q P1 : PROP} P2 + (inst : IntoWand q false Q .out P1 .out P2) + (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ A3 ∗ P1) (h3 : A3 ∗ P2 ⊢ goal) : + A1 ⊢ goal := by + refine h1 (Entails.trans ?_ h3) + exact (sep_mono_left h2).trans <| sep_assoc.1.trans + (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) + theorem specialize_wand_persistent [BI PROP] {q : Bool} {A1 A2 Q P1' : PROP} P1 P2 - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ P1') - [inst1 : IntoAbsorbingly P1' P1] [inst2 : Persistent P1] - [inst3 : IntoWand q true Q .out P1 .out P2] : + (inst3 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) + (inst1 : IntoAbsorbingly P1' P1) + (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ P1') : A1 ⊢ A2 ∗ □?q P2 := have h3 : □ P1 ∗ □?q Q ⊢ □?q P2 := by cases q with | false => exact (sep_mono_right inst3.into_wand).trans wand_elim_right @@ -64,9 +95,38 @@ theorem specialize_wand_persistent [BI PROP] {q : Bool} {A1 A2 Q P1' : PROP} P1 _ ⊢ A2 ∗ □ P1 ∗ □?q Q := sep_assoc.mp _ ⊢ A2 ∗ □?q P2 := sep_mono_right h3 +theorem specialize_wand_persistent_cont [BI PROP] {q : Bool} {A1 A2 Q P1' : PROP} P1 P2 + (inst3 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) + (inst1 : IntoAbsorbingly P1' P1) + (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ P1') (h3 : A2 ∗ □?q P2 ⊢ goal) : + A1 ⊢ goal := by + refine h1 (Entails.trans ?_ h3) + have h4 : □ P1 ∗ □?q Q ⊢ □?q P2 := by cases q with + | false => exact (sep_mono_right inst3.into_wand).trans wand_elim_right + | true => calc + _ ⊢ □ □ P1 ∗ □ □ Q := sep_mono intuitionistically_idem.mpr intuitionistically_idem.mpr + _ ⊢ □ (□ P1 ∗ □ Q) := intuitionistically_sep_mpr + _ ⊢ □ (□ P1 ∗ (□ P1 -∗ P2)) := intuitionistically_mono <| sep_mono_right <| inst3.into_wand + _ ⊢ □?true P2 := intuitionistically_mono wand_elim_right + calc + _ ⊢ (A2 ∧ A2) ∗ □?q Q := sep_mono_left <| and_intro .rfl .rfl + _ ⊢ (A2 ∧ P1') ∗ □?q Q := sep_mono_left <| and_mono_right h2 + _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right into_absorbingly + _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right <| absorbingly_mono Persistent.persistent + _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right <| absorbingly_persistently.mp + _ ⊢ (A2 ∗ □ P1) ∗ □?q Q := sep_mono_left persistently_and_intuitionistically_sep_right.mp + _ ⊢ A2 ∗ □ P1 ∗ □?q Q := sep_assoc.mp + _ ⊢ A2 ∗ □?q P2 := sep_mono_right h4 + theorem specialize_forall [BI PROP] {p : Bool} {A1 A2 P : PROP} {α : Sort _} {Φ : α → PROP} - [inst : IntoForall P Φ] (h : A1 ⊢ A2 ∗ □?p P) (a : α) : A1 ⊢ A2 ∗ □?p (Φ a) := by - refine h.trans <| sep_mono_right <| intuitionisticallyIf_mono <| inst.1.trans (forall_elim a) + (inst : IntoForall P Φ) (h : A1 ⊢ A2 ∗ □?p P) (a : α) : A1 ⊢ A2 ∗ □?p (Φ a) := by + refine h.trans <| sep_mono_right <| intuitionisticallyIf_mono <| inst.into_forall.trans (forall_elim a) + +theorem specialize_forall_cont [BI PROP] {p : Bool} {A1 A2 P : PROP} {α : Sort _} {Φ : α → PROP} + (inst : IntoForall P Φ) (h : (A2 ∗ □?p P ⊢ goal) → A1 ⊢ goal) (a : α) + (h2 : A2 ∗ □?p (Φ a) ⊢ goal) : A1 ⊢ goal := by + refine h (Entails.trans ?_ h2) + refine sep_mono_right <| intuitionisticallyIf_mono <| inst.into_forall.trans (forall_elim a) theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} (h : P ∗ □?pa A ⊢ P' ∗ □?pb B) @@ -83,14 +143,15 @@ open Lean Elab Tactic Meta Qq Std structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig goal : Q($prop)) where {e : Q($prop)} (hyps : Hyps bi e) (p : Q(Bool)) (out : Q($prop)) - pf : Q($orig ⊢ $e ∗ □?$p $out) + (pfCont : Q(($e ∗ □?$p $out ⊢ $goal) → $orig ⊢ $goal)) + pf : Option Q($orig ⊢ $e ∗ □?$p $out) mutual private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q($prop)} (specState : @SpecializeState u prop bi orig goal) (spat : Syntax × SpecPat) : ProofModeM (@SpecializeState u prop bi orig goal) := do - let { e, hyps, p, out, pf } := specState + let { e, hyps, p, out, pfCont, pf } := specState let ⟨ref, spat⟩ := spat withRef ref do match spat with @@ -101,10 +162,11 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ have : $p2 =Q ($p1 && $p) := ⟨⟩ let out₂ ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p $p1 $out .in $out₁' .out $out₂) | + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $p1 $out .in $out₁' .out $out₂) | throwError m!"ispecialize: cannot instantiate {out} with {out₁'}" - let pf := q(specialize_wand $pf $(pf').mp) - return { hyps := hyps', p := p2, out := out₂, pf } + let pfCont := q(specialize_wand_cont $inst $pfCont $(pf').mp) + let pf := pf.bind (fun pf => some q(specialize_wand $inst $pf $(pf').mp)) + return { hyps := hyps', p := p2, out := out₂, pfCont, pf } | .ident i spats => let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar @@ -113,21 +175,25 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ have : $p2 =Q ($pB && $p) := ⟨⟩ let out₂ ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) | + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) | throwError m!"ispecialize: cannot instantiate {out} with {B}" - return { hyps := hyps'', p := p2, out := out₂, pf := q(specialize_wand $pf ($(pf').mp.trans $pfNest)) } + let pfCont := q(specialize_wand_cont $inst $pfCont ($(pf').mp.trans $pfNest)) + let pf := pf.bind (fun pf => some q(specialize_wand $inst $pf ($(pf').mp.trans $pfNest))) + return { hyps := hyps'', p := p2, out := out₂, pfCont, pf } | .pure t => do let v ← mkFreshLevelMVar let α : Q(Sort v) ← mkFreshExprMVarQ q(Sort v) let Φ : Q($α → $prop) ← mkFreshExprMVarQ q($α → $prop) - let some _ ← ProofModeM.trySynthInstanceQ q(IntoForall $out $Φ) + let some inst ← ProofModeM.trySynthInstanceQ q(IntoForall $out $Φ) | throwError "ispecialize: {out} is not a Lean premise" let x ← elabTermEnsuringTypeQ (u := .succ .zero) t α have out' : Q($prop) := Expr.headBeta q($Φ $x) have : $out' =Q $Φ $x := ⟨⟩ let newMVarIds ← getMVarsNoDelayed x for mvar in newMVarIds do addMVarGoal mvar - return { e, hyps, p, out := out', pf := q(specialize_forall $pf $x) } + let pfCont := q(specialize_forall_cont $inst $pfCont $x) + let pf := pf.bind (fun pf => some q(specialize_forall $inst $pf $x)) + return { e, hyps, p, out := out', pfCont, pf } | .goal { kind := .spatial, negate, trivial, frame := f, hyps := hs } g => do let mut ivars : IVarIdSet := {} for name in hs do @@ -143,7 +209,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) | throwError m!"ispecialize: {out} is not a wand" let res ← iFrame bi _ hypsr' out₁ (frameIVars.map (⟨.ipm ·, true⟩)) let pf'' ← res.finish λ hyps goal => do @@ -153,19 +219,20 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q return r else addBIGoal hyps goal g - let pf := q(specialize_wand_subgoal $out₂ $pf $pf' $pf'') - return { hyps := hypsl', p := q(false), out := out₂, pf } + let pfCont := q(specialize_wand_subgoal_cont $out₂ $inst $pfCont $pf' $pf'') + let pf := pf.bind (fun pf => some q(specialize_wand_subgoal $out₂ $inst $pf $pf' $pf'')) + return { hyps := hypsl', p := q(false), out := out₂, pfCont, pf } | .goal { kind := .persistent, trivial, frame := f, hyps := hs, .. } g => do if !hs.isEmpty then throwError "ispecialize: the subgoal for the persistent premise should not consume hypotheses" let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) + let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) | throwError m!"ispecialize: {out} is not a wand" - let some _ ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) + let some inst2 ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) | throwError m!"ispecialize: {out₁} is not persistent" let out₁' ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) + let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) | throwError m!"ispecialize: type class synthesis failed for {out₁} with IntoAbsorbingly" let mut frameIVars : List IVarId := [] for name in f do @@ -179,36 +246,40 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q return r else addBIGoal hyps goal g - return { hyps, p, out := out₂, - pf := q(specialize_wand_persistent $out₁ $out₂ $pf $pf') } + let pfCont := q(specialize_wand_persistent_cont $out₁ $out₂ $inst1 $inst2 $inst3 $pfCont $pf') + let pf := pf.bind (fun pf => some q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf $pf')) + return { hyps, p, out := out₂, pfCont, pf } + | .goal { kind := .modal, .. } _ => throwError "ispecialize: framing with the modal kind is not supported at the moment" | .autoframe .spatial => do let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) | throwError m!"ispecialize: {out} is not a wand" let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose - return { hyps := hyps', p := q(false), out := out₂, - pf := q(specialize_wand_autoframe_spatial $out₂ $pf $pf') } + let pfCont := q(specialize_wand_autoframe_spatial_cont $out₂ $inst $pfCont $pf') + let pf := pf.bind (fun pf => some q(specialize_wand_autoframe_spatial $out₂ $inst $pf $pf')) + return { hyps := hyps', p := q(false), out := out₂, pfCont, pf } | .autoframe .persistent => let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) + let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) | throwError m!"ispecialize: {out} is not a wand" - let some _ ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) + let some inst2 ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) | throwError m!"ispecialize: {out₁} is not persistent" let out₁' ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) + let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) | throwError m!"ispecialize: type class synthessis failed for {out₁} with IntoAbsorbingly" let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let pf' ← res.finish <| fun hyps goal => do let some pf ← iTrivial hyps goal | throwError "ispecialize: unable to solve premise by framing" return pf - return { hyps, p, out := out₂, - pf := q(specialize_wand_persistent $out₁ $out₂ $pf $pf') } + let pfCont := q(specialize_wand_persistent_cont $out₁ $out₂ $inst1 $inst2 $inst3 $pfCont $pf') + let pf := pf.bind (fun pf => some q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf $pf')) + return { hyps, p, out := out₂, pfCont, pf } | .autoframe .modal => let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop @@ -219,7 +290,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose - return { hyps := hyps', p := q(false), out := out₂, pf := q(sorry) } + return { hyps := hyps', p := q(false), out := out₂, pfCont := q(sorry), pf := none } /-- Specialize a proposition `A` by applying a sequence of specialization patterns. From c9a97ec1268509e29e9406c760806277f08938ba Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 15:47:59 +0200 Subject: [PATCH 28/86] Refactor code for `.autoframe .modal` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 44 +++++++++++++-------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 86146a4c9..ee48c8cf5 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -27,7 +27,8 @@ theorem specialize_wand [BI PROP] {q p : Bool} {A1 A2 A3 Q P1 P2 : PROP} (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) -theorem specialize_wand_cont [BI PROP] {q p : Bool} {A1 A2 A3 Q P1 P2 : PROP} +theorem specialize_wand_cont [BI PROP] {q p : Bool} + {A1 A2 A3 Q P1 P2 goal : PROP} (inst : IntoWand q p Q .in P1 .out P2) (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ A3 ∗ □?p P1) (h3 : A3 ∗ □?(p && q) P2 ⊢ goal) : @@ -48,7 +49,8 @@ theorem specialize_wand_subgoal [BI PROP] {q : Bool} {A1 A2 A3 A4 Q P1 : PROP} P refine h1.trans <| (sep_mono_left h2.1).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_left h3).trans ?_)) exact (sep_mono_right inst.1).trans wand_elim_right -theorem specialize_wand_subgoal_cont [BI PROP] {q : Bool} {A1 A2 A3 A4 Q P1 : PROP} P2 +theorem specialize_wand_subgoal_cont [BI PROP] {q : Bool} + {A1 A2 A3 A4 Q P1 goal : PROP} P2 (inst : IntoWand q false Q .out P1 .out P2) (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊣⊢ A3 ∗ A4) (h3 : A4 ⊢ P1) (h4 : A3 ∗ P2 ⊢ goal) : A1 ⊢ goal := by @@ -64,7 +66,7 @@ theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) theorem specialize_wand_autoframe_spatial_cont [BI PROP] {q : Bool} - {A1 A2 A3 Q P1 : PROP} P2 + {A1 A2 A3 Q P1 goal : PROP} P2 (inst : IntoWand q false Q .out P1 .out P2) (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ A3 ∗ P1) (h3 : A3 ∗ P2 ⊢ goal) : A1 ⊢ goal := by @@ -72,7 +74,8 @@ theorem specialize_wand_autoframe_spatial_cont [BI PROP] {q : Bool} exact (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) -theorem specialize_wand_persistent [BI PROP] {q : Bool} {A1 A2 Q P1' : PROP} P1 P2 +theorem specialize_wand_persistent [BI PROP] {q : Bool} + {A1 A2 Q P1' : PROP} P1 P2 (inst3 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) (inst1 : IntoAbsorbingly P1' P1) (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ P1') : @@ -95,7 +98,8 @@ theorem specialize_wand_persistent [BI PROP] {q : Bool} {A1 A2 Q P1' : PROP} P1 _ ⊢ A2 ∗ □ P1 ∗ □?q Q := sep_assoc.mp _ ⊢ A2 ∗ □?q P2 := sep_mono_right h3 -theorem specialize_wand_persistent_cont [BI PROP] {q : Bool} {A1 A2 Q P1' : PROP} P1 P2 +theorem specialize_wand_persistent_cont [BI PROP] {q : Bool} + {A1 A2 Q P1' goal : PROP} P1 P2 (inst3 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) (inst1 : IntoAbsorbingly P1' P1) (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ P1') (h3 : A2 ∗ □?q P2 ⊢ goal) : @@ -122,7 +126,7 @@ theorem specialize_forall [BI PROP] {p : Bool} {A1 A2 P : PROP} {α : Sort _} { (inst : IntoForall P Φ) (h : A1 ⊢ A2 ∗ □?p P) (a : α) : A1 ⊢ A2 ∗ □?p (Φ a) := by refine h.trans <| sep_mono_right <| intuitionisticallyIf_mono <| inst.into_forall.trans (forall_elim a) -theorem specialize_forall_cont [BI PROP] {p : Bool} {A1 A2 P : PROP} {α : Sort _} {Φ : α → PROP} +theorem specialize_forall_cont [BI PROP] {p : Bool} {A1 A2 P goal : PROP} {α : Sort _} {Φ : α → PROP} (inst : IntoForall P Φ) (h : (A2 ∗ □?p P ⊢ goal) → A1 ⊢ goal) (a : α) (h2 : A2 ∗ □?p (Φ a) ⊢ goal) : A1 ⊢ goal := by refine h (Entails.trans ?_ h2) @@ -146,6 +150,7 @@ structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig goal : Q($ (pfCont : Q(($e ∗ □?$p $out ⊢ $goal) → $orig ⊢ $goal)) pf : Option Q($orig ⊢ $e ∗ □?$p $out) +set_option maxHeartbeats 300000 in mutual private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q($prop)} @@ -170,7 +175,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | .ident i spats => let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar - let ⟨_, hyps'', pB, B, pfNest⟩ ← iSpecializeCore hyps' p1 out₁' goal spats + let ⟨_, hyps'', pB, B, _, some pfNest⟩ ← iSpecializeCore hyps' p1 out₁' goal spats + | throwError "ispecialize: nested specialisation pattern is not supported with modality handling" let p2 := if pB.constName! == ``true then p else q(false) have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ have : $p2 =Q ($pB && $p) := ⟨⟩ @@ -311,23 +317,29 @@ A tuple containing: def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : Hyps bi e) (pa : Q(Bool)) (A : Q($prop)) (goal : Q($prop)) (spats : List (Syntax × SpecPat)) (try_dup_context : Bool := false) : - ProofModeM ((e' : _) × Hyps bi e' × (pb : Q(Bool)) × (B : Q($prop)) × Q($e ∗ □?$pa $A ⊢ $e' ∗ □?$pb $B)) := do - let state : SpecializeState _ goal := { hyps, out := A, p := pa, pf := q(.rfl), .. } - let ⟨hyps', pb, B, pf⟩ ← spats.foldlM processWand state + ProofModeM ((e' : _) × Hyps bi e' × (pb : Q(Bool)) × (B : Q($prop)) × + Q(($e' ∗ □?$pb $B ⊢ $goal) → $e ∗ □?$pa $A ⊢ $goal) × + Option Q($e ∗ □?$pa $A ⊢ $e' ∗ □?$pb $B)) := do + let state : SpecializeState _ goal := { hyps, out := A, p := pa, pfCont := q(id), pf := some q(.rfl) } + let ⟨hyps', pb, B, pfCont, pf⟩ ← spats.foldlM processWand state if try_dup_context then + let pf ← do match pf with + | some pf => pure pf + | none => throwError "ispecialize: unable to duplicate context" -- context duplication succeeds if `B` is persistent, and `A` is persistent or affine let B' : Q($prop) ← mkFreshExprMVarQ q($prop) let .some _ ← ProofModeM.trySynthInstanceQ q(IntoPersistently $pb $B $B') - | return ⟨_, hyps', pb, B, pf⟩ + | return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ have af : MetaM (Option Q($pa = true ∨ Affine $A)) := match matchBool pa with | .inl _ => return some q(.inl (.refl _)) | .inr _ => do let .some h ← trySynthInstanceQ q(Affine $A) | return none return some q(.inr $h) - let some af ← af | return ⟨_, hyps', pb, B, pf⟩ - return ⟨_, hyps, q(true), B', q(specialize_dup_context $pf $af)⟩ - return ⟨_, hyps', pb, B, pf⟩ + let some af ← af | return ⟨_, hyps', pb, B, q($(pf).trans), pf⟩ + return ⟨_, hyps, q(true), B', q((specialize_dup_context $pf $af).trans), some q(specialize_dup_context $pf $af)⟩ + else + return ⟨_, hyps', pb, B, pfCont, pf⟩ end @@ -359,7 +371,7 @@ elab "ispecialize " colGt pmt:pmTerm : tactic => do hyps.removeG true λ name ivar' _ _ => if ivar == ivar' then some name else none | throwError "ispecialize: cannot find argument" - let ⟨_, hyps'', pb, B, pf'⟩ ← iSpecializeCore hyps' p out goal pmt.spats + let ⟨_, hyps'', pb, B, pf', _⟩ ← iSpecializeCore hyps' p out goal pmt.spats let hyps''' := Hyps.add bi name ivar pb B hyps'' let pf'' ← addBIGoal hyps''' goal - mvar.assign q(($pf).1.trans <| $(pf').trans <| $pf'') + mvar.assign q(($pf).1.trans <| $pf' $pf'') From 9c4be103888b5fe83497dc230e37c11a07d1ef2c Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 16:37:33 +0200 Subject: [PATCH 29/86] Update `ihave`, `irewrite`, `icases` and `iapply` because of the updated `iSpecializeCore` and `iHave` signature --- Iris/Iris/ProofMode/Tactics/Apply.lean | 18 +++++++++--------- Iris/Iris/ProofMode/Tactics/Cases.lean | 2 +- Iris/Iris/ProofMode/Tactics/Have.lean | 4 ++-- Iris/Iris/ProofMode/Tactics/HaveCore.lean | 9 +++++---- Iris/Iris/ProofMode/Tactics/Rewrite.lean | 13 +++++++++---- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Apply.lean b/Iris/Iris/ProofMode/Tactics/Apply.lean index 29ae8a315..4aa42b57c 100644 --- a/Iris/Iris/ProofMode/Tactics/Apply.lean +++ b/Iris/Iris/ProofMode/Tactics/Apply.lean @@ -19,16 +19,16 @@ open BI theorem apply [BI PROP] {p} {P Q Q1 R : PROP} (h1 : P ⊢ Q1) [h2 : IntoWand p false Q .out Q1 .in R] : P ∗ □?p Q ⊢ R := - (Entails.trans (sep_mono_left h1) (wand_elim_swap h2.1)) + (Entails.trans (sep_mono_left h1) (wand_elim_swap h2.into_wand)) public meta section open Lean Elab Tactic Meta Qq Std -- Like `ProofMode.assumption`, but specialized for the `iapply` case -theorem apply_assumption [BI PROP] {p : Bool} {P A Q : PROP} [inst : FromAssumption p .in A Q] - [TCOr (Affine P) (Absorbing Q)] - (h : e ⊢ P ∗ □?p A) : e ⊢ Q := - h.trans <| (sep_mono_right inst.1).trans sep_elim_right +theorem apply_assumption [BI PROP] {p : Bool} {P A Q : PROP} + [inst : FromAssumption p .in A Q] [TCOr (Affine P) (Absorbing Q)] : + P ∗ □?p A ⊢ Q := + (sep_mono_right inst.from_assumption).trans sep_elim_right /-- Apply a hypothesis `A` to the `goal` by eliminating the wands recursively @@ -50,11 +50,11 @@ private partial def iApplyCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} return q(apply $pf) -- otherwise, if `A` has the form `?P -∗ ?B`, create a subgoal for `P` and continue with ?B - let some ⟨_, hyps', pb, B, pf⟩ ← try? <| iSpecializeCore hyps p A goal + let some ⟨_, hyps', pb, B, pf, _⟩ ← try? <| iSpecializeCore hyps p A goal [⟨← getRef, .goal {kind := .spatial, negate := false, trivial := false, frame := [], hyps := []} .anonymous⟩] | throwError m!"iapply: cannot apply {A} to {goal}" let pf' ← iApplyCore hyps' pb B goal - return q($(pf).trans $pf') + return q($pf $pf') /-- `iapply pmt` matches the conclusion of `pmt : pmTerm` against the goal and @@ -78,8 +78,8 @@ elab "iapply " colGt pmt:pmTerm : tactic => do -- ensure the context can be discarded let LOption.some _ ← trySynthInstanceQ q(TCOr (Affine $e) (Absorbing $goal)) | throwError "iapply: the context {e} is not affine and goal not absorbing" - mvar.assign q(apply_assumption (Q := $goal) $pf) + mvar.assign q($pf apply_assumption) return -- otherwise, `out` should be a wand, handled by `iApplyCore` let pf' ← iApplyCore hyps' p out goal - mvar.assign q($(pf).trans $pf') + mvar.assign q($pf $pf') diff --git a/Iris/Iris/ProofMode/Tactics/Cases.lean b/Iris/Iris/ProofMode/Tactics/Cases.lean index b3154c2d4..c00dd6c98 100644 --- a/Iris/Iris/ProofMode/Tactics/Cases.lean +++ b/Iris/Iris/ProofMode/Tactics/Cases.lean @@ -272,7 +272,7 @@ elab "icases" keep:("+keep ")? colGt pmt:pmTerm " with " colGt pat:icasesPat : t -- process pattern let pf2 ← iCasesCore bi hyps goal pat p A - mvar.assign q(($pf).trans $pf2) + mvar.assign q($pf $pf2) /-- `imod pmt with pat` eliminates the modality at the top of `pmt : pmTerm` into diff --git a/Iris/Iris/ProofMode/Tactics/Have.lean b/Iris/Iris/ProofMode/Tactics/Have.lean index 5e2b80b01..bd5fd8f0d 100644 --- a/Iris/Iris/ProofMode/Tactics/Have.lean +++ b/Iris/Iris/ProofMode/Tactics/Have.lean @@ -39,6 +39,6 @@ elab "ihave " colGt pat:icasesPat " : " P:term " $$ " spat:specPat : tactic => d ProofModeM.runTactic λ mvar { prop, bi, hyps, goal, .. } => do let P ← elabTermEnsuringTypeQ (← `(iprop($P))) prop -- establish `P` with `spat` - let ⟨_, hyps', p, A, pf⟩ ← iSpecializeCore hyps q(true) q(iprop($P -∗ $P)) goal [spat] (try_dup_context := pat.should_try_dup_context) + let ⟨_, hyps', p, A, pf, _⟩ ← iSpecializeCore hyps q(true) q(iprop($P -∗ $P)) goal [spat] (try_dup_context := pat.should_try_dup_context) let pf2 ← iCasesCore bi hyps' goal pat p A - mvar.assign q(ihave_assert (($pf).trans $pf2)) + mvar.assign q(ihave_assert ($pf $pf2)) diff --git a/Iris/Iris/ProofMode/Tactics/HaveCore.lean b/Iris/Iris/ProofMode/Tactics/HaveCore.lean index 267515cb5..4f1b3d5aa 100644 --- a/Iris/Iris/ProofMode/Tactics/HaveCore.lean +++ b/Iris/Iris/ProofMode/Tactics/HaveCore.lean @@ -89,10 +89,11 @@ private def iHaveCore {e} (hyps : @Hyps u prop bi e) return ⟨_, hyps, q(true), hyp, q(have_asEmpValid $val)⟩ def iHave {e} (hyps : @Hyps u prop bi e) (goal : Q($prop)) - (pmt : PMTerm) (keep : Bool) (try_dup_context : Bool := false) : - ProofModeM ((e' : _) × Hyps bi e' × (p : Q(Bool)) × (out : Q($prop)) × Q($e ⊢ $e' ∗ □?$p $out)) := do + (pmt : PMTerm) (keep : Bool) (try_dup_context : Bool := false) : + ProofModeM ((e' : _) × Hyps bi e' × (p : Q(Bool)) × (out : Q($prop)) × + Q(($e' ∗ □?$p $out ⊢ $goal) → $e ⊢ $goal)) := do -- assert `term` as hypothesis `A` let ⟨_, hyps', p, A, pf⟩ ← iHaveCore hyps pmt.term keep -- specialize `A` with `spats` - let ⟨_, hyps'', pb, B, pf'⟩ ← iSpecializeCore hyps' p A goal pmt.spats (try_dup_context := try_dup_context) - return ⟨_, hyps'', pb, B, q($(pf).trans $pf')⟩ + let ⟨_, hyps'', pb, B, pf', _⟩ ← iSpecializeCore hyps' p A goal pmt.spats (try_dup_context := try_dup_context) + return ⟨_, hyps'', pb, B, q(fun x => $(pf).trans ($pf' x))⟩ diff --git a/Iris/Iris/ProofMode/Tactics/Rewrite.lean b/Iris/Iris/ProofMode/Tactics/Rewrite.lean index 3faaa5798..77511c444 100644 --- a/Iris/Iris/ProofMode/Tactics/Rewrite.lean +++ b/Iris/Iris/ProofMode/Tactics/Rewrite.lean @@ -112,11 +112,16 @@ end rule end IRewrite private def iRewriteCore {prop : Q(Type u)} {bi : Q(BI $prop)} - {e} (hyps : Hyps bi e) (goal : Q($prop)) (rule : IRewrite.Rule) + {e} (hyps : Hyps bi e) (_goal : Q($prop)) (rule : IRewrite.Rule) (target : Q($prop)) (occs : Occurrences := Occurrences.all) : ProofModeM ((target' : Q($prop)) × Q($e ⊢ ($target ∗-∗ $target'))) := do - let ⟨_, _, _, eq, pf⟩ ← iHave hyps goal rule.term true + let g : Q($prop) ← mkFreshExprMVarQ q($prop) + let ⟨e', _, p, eq, pf⟩ ← iHave hyps g rule.term true + unless ← isDefEq g q(iprop($e' ∗ □?$p $eq)) do + throwError "irewrite: could not pin the equality goal" + have : $g =Q iprop($e' ∗ □?$p $eq) := ⟨⟩ + let pf' : Q($e ⊢ $e' ∗ □?$p $eq) := q($pf .rfl) let .some sbi ← trySynthInstanceQ q(Sbi $prop) | throwError "irewrite: could not synthesize Sbi instance" @@ -155,10 +160,10 @@ private def iRewriteCore {prop : Q(Type u)} {bi : Q(BI $prop)} match rule.direction with | .forward => have : $target =Q $Ψ $a := ⟨⟩ - return ⟨_, q(rewrite_tac $Ψ $pf)⟩ + return ⟨_, q(rewrite_tac $Ψ $pf')⟩ | .backward => do have : $target =Q $Ψ $b := ⟨⟩ - return ⟨_, q(rewrite_tac_symm $Ψ $pf)⟩ + return ⟨_, q(rewrite_tac_symm $Ψ $pf')⟩ def iRewriteGoal {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : Hyps bi e) (rule : IRewrite.Rule) (goal : Q($prop)) From ef042c81263332ff68f7e21d5dc51fc72ac2a1da Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 16:51:23 +0200 Subject: [PATCH 30/86] Finish proof construction for `.autoframe .modal` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index ee48c8cf5..a5d21241c 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -142,6 +142,11 @@ theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} · cases h2 <;> subst_eqs <;> apply sep_elim_left · apply h.trans $ (sep_mono_right (persistentlyIf_of_intuitionisticallyIf.trans into_persistently)).trans sep_elim_right +theorem specialize_modal [BI PROP] {e e' goal R P1 P1' P2 : PROP} {p : Bool} + (h1 : e ⊢ e' ∗ P1') (h2 : e' ∗ P2 ⊢ goal) + (inst1 : AddModal P1' P1 goal) (inst2 : IntoWand p false R .out P1 .out P2) : + e ∗ □?p R ⊢ goal := sorry + public meta section open Lean Elab Tactic Meta Qq Std @@ -255,7 +260,6 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pfCont := q(specialize_wand_persistent_cont $out₁ $out₂ $inst1 $inst2 $inst3 $pfCont $pf') let pf := pf.bind (fun pf => some q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf $pf')) return { hyps, p, out := out₂, pfCont, pf } - | .goal { kind := .modal, .. } _ => throwError "ispecialize: framing with the modal kind is not supported at the moment" | .autoframe .spatial => do @@ -289,14 +293,15 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | .autoframe .modal => let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) + let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) | throwError m!"ispecialize: {out} is not a wand" let out₁' ← mkFreshExprMVarQ prop - let some _ ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) + let some inst2 ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose - return { hyps := hyps', p := q(false), out := out₂, pfCont := q(sorry), pf := none } + let pfCont := q(fun pf => $pfCont (specialize_modal $pf' pf $inst2 $inst1)) + return { hyps := hyps', p := q(false), out := out₂, pfCont := q($pfCont), pf := none } /-- Specialize a proposition `A` by applying a sequence of specialization patterns. From 3501e6479186418f82c4c963a5f7e5059db5be1a Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 16:53:43 +0200 Subject: [PATCH 31/86] Finish proof for `specialize_modal` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index a5d21241c..268a66cb5 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -145,7 +145,13 @@ theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} theorem specialize_modal [BI PROP] {e e' goal R P1 P1' P2 : PROP} {p : Bool} (h1 : e ⊢ e' ∗ P1') (h2 : e' ∗ P2 ⊢ goal) (inst1 : AddModal P1' P1 goal) (inst2 : IntoWand p false R .out P1 .out P2) : - e ∗ □?p R ⊢ goal := sorry + e ∗ □?p R ⊢ goal := calc + _ ⊢ (e' ∗ P1') ∗ □?p R := sep_mono_left h1 + _ ⊢ P1' ∗ (e' ∗ □?p R) := sep_assoc.mp.trans sep_left_comm.mp + _ ⊢ P1' ∗ (e' ∗ (P1 -∗ P2)) := sep_mono_right (sep_mono_right inst2.into_wand) + _ ⊢ P1' ∗ ((P2 -∗ goal) ∗ (P1 -∗ P2)) := sep_mono_right (sep_mono_left (wand_intro h2)) + _ ⊢ P1' ∗ (P1 -∗ goal) := sep_mono_right (sep_comm.mp.trans wand_trans) + _ ⊢ goal := inst1.add_modal public meta section open Lean Elab Tactic Meta Qq Std From 0fc3a8d48507ba20dde5dcf247b0c76e2693cdba Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 17:19:05 +0200 Subject: [PATCH 32/86] Implement `.goal` with `kind := .modal` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 33 +++++++++++++++++++-- Iris/Iris/Tests/Tactics.lean | 11 +++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 268a66cb5..53a278d6d 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -266,8 +266,37 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pfCont := q(specialize_wand_persistent_cont $out₁ $out₂ $inst1 $inst2 $inst3 $pfCont $pf') let pf := pf.bind (fun pf => some q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf $pf')) return { hyps, p, out := out₂, pfCont, pf } - | .goal { kind := .modal, .. } _ => - throwError "ispecialize: framing with the modal kind is not supported at the moment" + | .goal { kind := .modal, negate, trivial, frame := f, hyps := hs, .. } g => + let mut ivars : IVarIdSet := {} + for name in hs do + ivars := ivars.insert (← hyps.findWithInfo name) + let mut frameIVars : List IVarId := [] + for name in f do + let ivar ← hyps.findWithInfo name + frameIVars := ivar :: frameIVars + if ivars.contains ivar then + throwError "ispecialize: {name} used twice" + frameIVars := frameIVars.reverse + let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi + (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps + let out₁ ← mkFreshExprMVarQ prop + let out₂ ← mkFreshExprMVarQ prop + let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) + | throwError m!"ispecialize: {out} is not a wand" + let out₁' ← mkFreshExprMVarQ prop + let some inst2 ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) + | throwError "ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" + let res ← iFrame bi _ hypsr' out₁' (frameIVars.map (⟨.ipm ·, true⟩)) + let pf'' ← res.finish λ hyps goal => do + if trivial then + let some r ← iTrivial hyps goal + | throwError "ispecialize: itrivial could not solve {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" + return r + else + addBIGoal hyps goal g + let h := q($(pf').mp.trans (sep_mono_right $pf'')) + let pfCont := q(fun pf => $pfCont (specialize_modal $h pf $inst2 $inst1)) + return { hyps := hypsl', p := q(false), out := out₂, pfCont, pf := none } | .autoframe .spatial => do let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 1211c1c76..e217f2442 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1373,6 +1373,17 @@ example [BI PROP] [BIUpdate PROP] (P Q : PROP) : imodintro iassumption +/-- + Tests `ispecialize` with the handling of the modality using the type class + instance `addModal_bupd`. The subgoal is manually solved. -/ +example [BI PROP] [BIUpdate PROP] (P Q : PROP) : + ⊢ (P -∗ Q) -∗ (|==> P) -∗ (|==> Q) := by + iintro HPQ HP + ispecialize HPQ $$ [> HP] + · iassumption + · imodintro + iassumption + end specialize -- split From a0ade889cdd902331e164358d5d4c49e0c6cde5f Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 17:41:18 +0200 Subject: [PATCH 33/86] Tweak the handling of `try_dup_context` in `iSpecializeCore` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 53a278d6d..b52faeb91 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -362,10 +362,8 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} Option Q($e ∗ □?$pa $A ⊢ $e' ∗ □?$pb $B)) := do let state : SpecializeState _ goal := { hyps, out := A, p := pa, pfCont := q(id), pf := some q(.rfl) } let ⟨hyps', pb, B, pfCont, pf⟩ ← spats.foldlM processWand state - if try_dup_context then - let pf ← do match pf with - | some pf => pure pf - | none => throwError "ispecialize: unable to duplicate context" + match try_dup_context, pf with + | true, some pf => -- context duplication succeeds if `B` is persistent, and `A` is persistent or affine let B' : Q($prop) ← mkFreshExprMVarQ q($prop) let .some _ ← ProofModeM.trySynthInstanceQ q(IntoPersistently $pb $B $B') @@ -378,8 +376,7 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} return some q(.inr $h) let some af ← af | return ⟨_, hyps', pb, B, q($(pf).trans), pf⟩ return ⟨_, hyps, q(true), B', q((specialize_dup_context $pf $af).trans), some q(specialize_dup_context $pf $af)⟩ - else - return ⟨_, hyps', pb, B, pfCont, pf⟩ + | _, _ => return ⟨_, hyps', pb, B, pfCont, pf⟩ end From 60fd20944936f6ec403f4e4c98ddc38ec2bd47a6 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Mon, 6 Jul 2026 17:51:31 +0200 Subject: [PATCH 34/86] Minor formatting --- Iris/Iris/Tests/Tactics.lean | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index e217f2442..bd092a9f8 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1375,7 +1375,8 @@ example [BI PROP] [BIUpdate PROP] (P Q : PROP) : /-- Tests `ispecialize` with the handling of the modality using the type class - instance `addModal_bupd`. The subgoal is manually solved. -/ + instance `addModal_bupd`. The subgoal is manually solved. +-/ example [BI PROP] [BIUpdate PROP] (P Q : PROP) : ⊢ (P -∗ Q) -∗ (|==> P) -∗ (|==> Q) := by iintro HPQ HP From 257f6e89c0b9453ed3c4005691391ff779b4a8cf Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 09:55:12 +0200 Subject: [PATCH 35/86] Add instance `addModal_wand` --- Iris/Iris/ProofMode/Instances.lean | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Iris/Iris/ProofMode/Instances.lean b/Iris/Iris/ProofMode/Instances.lean index ba0f30590..e0813229d 100644 --- a/Iris/Iris/ProofMode/Instances.lean +++ b/Iris/Iris/ProofMode/Instances.lean @@ -856,6 +856,17 @@ instance elimModal_absorbingly_here [BI PROP] p (P Q : PROP) [Absorbing Q] : ElimModal True p false iprop( P) P Q Q where elim_modal _ := (sep_mono_left intuitionisticallyIf_elim).trans $ absorbingly_sep_left.1.trans $ absorbing_absorbingly.1.trans wand_elim_right +-- AddModal +@[rocq_alias add_modal_wand] +instance addModal_wand [BI PROP] (P P' Q R : PROP) [h : AddModal P P' Q] : + AddModal P P' iprop(R -∗ Q) where + add_modal := by + apply wand_intro + calc + (P ∗ (P' -∗ R -∗ Q)) ∗ R ⊢ P ∗ (P' -∗ R -∗ Q) ∗ R := sep_assoc.mp + _ ⊢ P ∗ (P' -∗ Q) := sorry + _ ⊢ Q := h.add_modal + -- CombineSepAs @[rocq_alias maybe_combine_sep_as_default] instance (priority := default - 20) combineSepAs_default [BI PROP] (P Q : PROP) : From 755ff1c671f6927626c4fde2a7662da994f7b19c Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 10:25:17 +0200 Subject: [PATCH 36/86] Complete proof for `addModal_wand` --- Iris/Iris/ProofMode/Instances.lean | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Iris/Iris/ProofMode/Instances.lean b/Iris/Iris/ProofMode/Instances.lean index e0813229d..20178fa90 100644 --- a/Iris/Iris/ProofMode/Instances.lean +++ b/Iris/Iris/ProofMode/Instances.lean @@ -861,11 +861,19 @@ instance elimModal_absorbingly_here [BI PROP] p (P Q : PROP) [Absorbing Q] : instance addModal_wand [BI PROP] (P P' Q R : PROP) [h : AddModal P P' Q] : AddModal P P' iprop(R -∗ Q) where add_modal := by + have h1 : (P' -∗ R -∗ Q) ∗ R ⊢ P' -∗ Q := by + apply wand_intro + calc + _ ⊢ (P' -∗ R -∗ Q) ∗ R ∗ P' := sep_assoc.mp + _ ⊢ (P' -∗ R -∗ Q) ∗ P' ∗ R := sep_mono_right sep_comm.mp + _ ⊢ ((P' -∗ R -∗ Q) ∗ P') ∗ R := sep_assoc.mpr + _ ⊢ (R -∗ Q) ∗ R := sep_mono_left wand_elim_left + _ ⊢ Q := wand_elim_left apply wand_intro calc - (P ∗ (P' -∗ R -∗ Q)) ∗ R ⊢ P ∗ (P' -∗ R -∗ Q) ∗ R := sep_assoc.mp - _ ⊢ P ∗ (P' -∗ Q) := sorry - _ ⊢ Q := h.add_modal + _ ⊢ P ∗ (P' -∗ R -∗ Q) ∗ R := sep_assoc.mp + _ ⊢ P ∗ (P' -∗ Q) := sep_mono_right h1 + _ ⊢ Q := h.add_modal -- CombineSepAs @[rocq_alias maybe_combine_sep_as_default] From 2d0195bd7b0919e9f5d380237d487aeef0514dc7 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 10:38:08 +0200 Subject: [PATCH 37/86] Implement support for nested patterns along with modality handling --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 30 +++++++++++++++------ Iris/Iris/Tests/Tactics.lean | 22 +++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index b52faeb91..11fff5393 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -41,6 +41,17 @@ theorem specialize_wand_cont [BI PROP] {q p : Bool} (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) +theorem specialize_wand_nested_cont [BI PROP] {q p : Bool} {C B out out₂ goal : PROP} + (inst : IntoWand q p out .in B .out out₂) + (k : C ∗ □?(p && q) out₂ ⊢ goal) : + C ∗ □?p B ⊢ ((□?q out) -∗ goal) := by + refine wand_intro (sep_assoc.mp.trans <| (sep_mono_right ?_).trans k) + cases p with + | false => exact (sep_mono_right inst.into_wand).trans wand_elim_right + | true => exact + (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| + intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) + -- TODO: if q is true and A1 is persistent, this proof can guarantee □ P2 instead of P2 -- see https://gitlab.mpi-sws.org/iris/iris/-/blob/846ed45bed6951035c6204fef365d9a344022ae6/iris/proofmode/coq_tactics.v#L336 theorem specialize_wand_subgoal [BI PROP] {q : Bool} {A1 A2 A3 A4 Q P1 : PROP} P2 @@ -178,24 +189,27 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ have : $p2 =Q ($p1 && $p) := ⟨⟩ let out₂ ← mkFreshExprMVarQ prop - let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $p1 $out .in $out₁' .out $out₂) | - throwError m!"ispecialize: cannot instantiate {out} with {out₁'}" + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $p1 $out .in $out₁' .out $out₂) + | throwError m!"ispecialize: cannot instantiate {out} with {out₁'}" let pfCont := q(specialize_wand_cont $inst $pfCont $(pf').mp) let pf := pf.bind (fun pf => some q(specialize_wand $inst $pf $(pf').mp)) return { hyps := hyps', p := p2, out := out₂, pfCont, pf } | .ident i spats => let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar - let ⟨_, hyps'', pB, B, _, some pfNest⟩ ← iSpecializeCore hyps' p1 out₁' goal spats - | throwError "ispecialize: nested specialisation pattern is not supported with modality handling" + let ⟨_, hyps'', pB, B, pfContNest, pfNest⟩ ← iSpecializeCore hyps' p1 out₁' q(iprop(□?$p $out -∗ $goal)) spats let p2 := if pB.constName! == ``true then p else q(false) have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ have : $p2 =Q ($pB && $p) := ⟨⟩ let out₂ ← mkFreshExprMVarQ prop - let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) | - throwError m!"ispecialize: cannot instantiate {out} with {B}" - let pfCont := q(specialize_wand_cont $inst $pfCont ($(pf').mp.trans $pfNest)) - let pf := pf.bind (fun pf => some q(specialize_wand $inst $pf ($(pf').mp.trans $pfNest))) + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) + | throwError m!"ispecialize: cannot instantiate {out} with {B}" + let pfCont ← do match pfNest with + | some pfNest => + pure q(specialize_wand_cont $inst $pfCont ($(pf').mp.trans $pfNest)) + | none => pure q(fun pf => $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (specialize_wand_nested_cont $inst pf))))) + let pf := pfNest.bind (fun pfNest => + pf.bind (fun pf => some q(specialize_wand $inst $pf ($(pf').mp.trans $pfNest)))) return { hyps := hyps'', p := p2, out := out₂, pfCont, pf } | .pure t => do let v ← mkFreshLevelMVar diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index bd092a9f8..fa29f42ec 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1385,6 +1385,28 @@ example [BI PROP] [BIUpdate PROP] (P Q : PROP) : · imodintro iassumption +/-- + Tests `ispecialize` with the handling of the modality, nested patterns and + the use of the type class instance `addModal_wand`. +-/ +example [BI PROP] [BIUpdate PROP] (P Q R : PROP) : + ⊢ (P -∗ R) -∗ (Q -∗ P) -∗ (|==> Q) -∗ (|==> R) := by + iintro HPR HQP HQ + ispecialize HPR $$ (HQP $$ [> HQ //]) + imodintro + iassumption + +/-- + Tests `ispecialize` with the auto-framing with modality, nested patterns and + the use of the type class instance `addModal_wand`. +-/ +example [BI PROP] [BIUpdate PROP] (P Q R : PROP) : + ⊢ (P -∗ R) -∗ (Q -∗ P) -∗ (|==> Q) -∗ (|==> R) := by + iintro HPR HQP HQ + ispecialize HPR $$ (HQP $$ [>$]) + imodintro + iassumption + end specialize -- split From 978191b258156f77752d1f0f3c6a53d626aceaa8 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 10:44:39 +0200 Subject: [PATCH 38/86] Update documentation and tests --- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 2 +- Iris/Iris/ProofMode/Porting.lean | 6 +++--- Iris/Iris/Tests/Tactics.lean | 12 ++++++------ Iris/tactics.md | 5 ++++- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index 0722a014d..993ba8188 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -66,7 +66,7 @@ syntax "(" colGt ident "$$" (colGt ppSpace specPat)+ ppSpace ")" : specPat @[rocq_alias goal_kind] inductive SpecGoalKind | spatial - | modal -- TODO: implement `.autoframe` and `.goal` for the modal kind in `processWand` + | modal | persistent deriving Repr, Inhabited, BEq diff --git a/Iris/Iris/ProofMode/Porting.lean b/Iris/Iris/ProofMode/Porting.lean index 773296881..37e87c1e7 100644 --- a/Iris/Iris/ProofMode/Porting.lean +++ b/Iris/Iris/ProofMode/Porting.lean @@ -80,17 +80,17 @@ import Iris.Std.RocqPorting #rocq_concept proofmode "Intro Patterns" "IClear (pattern: {selpat})" missing "" #rocq_concept proofmode "Intro Patterns" "IClearFrame (pattern: {$selpat})" missing "" -#rocq_concept proofmode "Specialization Patterns" missing "" +#rocq_concept proofmode "Specialization Patterns" ported "" #rocq_concept proofmode "Specialization Patterns" "SIdent (pattern: H)" ported "pattern: H" #rocq_concept proofmode "Specialization Patterns" "Nested (pattern: (H spat ...))" ported "pattern: (H $$ spat₁ ... spatₙ)" #rocq_concept proofmode "Specialization Patterns" "SPureGoal (pattern: [%])" ported "pattern: %" #rocq_concept proofmode "Specialization Patterns" "SGoal (Kind: Spatial) (pattern: [H1 ...])" ported "pattern: [H1 ...]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Kind: Intuitionistic) (pattern: [#H1 ...])" ported "pattern: [#H1 ...]" -#rocq_concept proofmode "Specialization Patterns" "SGoal (Kind: Modal) (pattern: [>H1 ...])" missing "pattern: [>H1 ...]" +#rocq_concept proofmode "Specialization Patterns" "SGoal (Kind: Modal) (pattern: [>H1 ...])" ported "pattern: [>H1 ...]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Negate) (pattern: [-H1 ...])" ported "pattern: [-H1 ...]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Frame) (pattern: [$H1])" ported "pattern: [$H1]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Hyps) (pattern: [H1 ...])" ported "pattern: [H1 ...]" #rocq_concept proofmode "Specialization Patterns" "SGoal (Done) (pattern: [... //])" ported "pattern: [... //]" #rocq_concept proofmode "Specialization Patterns" "SAutoFrame (Kind: Spatial) (pattern: [$])" ported "pattern: [$]" #rocq_concept proofmode "Specialization Patterns" "SAutoFrame (Kind: Intuitionistic) (pattern: [#$])" ported "pattern: [#$]" -#rocq_concept proofmode "Specialization Patterns" "SAutoFrame (Kind: Modal) (pattern: [>$])" missing "pattern: [>$]" +#rocq_concept proofmode "Specialization Patterns" "SAutoFrame (Kind: Modal) (pattern: [>$])" ported "pattern: [>$]" diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index fa29f42ec..4948d823c 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1305,7 +1305,7 @@ example [BI PROP] (P1 P2 P3 Q : PROP) : P1 -∗ P2 -∗ P3 -∗ (P1 -∗ P2 -∗ example [BI PROP] (P1 P2 P3 Q : PROP) : □ P1 -∗ P2 -∗ □ P3 -∗ (□ P1 -∗ P2 -∗ P3 -∗ Q) -∗ Q := by iintro #HP1 HP2 #HP3 HPQ - ispecialize HPQ $$ [#$] [$] [#$] + ispecialize HPQ $$ [# $] [$] [# $] iexact HPQ /-- @@ -1315,8 +1315,8 @@ example [BI PROP] (P1 P2 P3 Q : PROP) : example [BI PROP] (φ : Prop) (Q : PROP) : ⌜φ⌝ -∗ (⌜φ⌝ -∗ Q) -∗ (⌜φ⌝ -∗ Q) -∗ ⌜φ⌝ ∗ Q ∗ Q := by iintro HP1 HPQ1 HPQ2 - ispecialize HPQ1 $$ [#$] - ispecialize HPQ2 $$ [#$] + ispecialize HPQ1 $$ [# $] + ispecialize HPQ2 $$ [# $] iframe /- Tests `ispecialize` with autoframing, but the premise is not persistent. -/ @@ -1325,7 +1325,7 @@ example [BI PROP] (φ : Prop) (Q : PROP) : example [BI PROP] (φ : Prop) (P Q : PROP) : P -∗ (P -∗ Q) -∗ True := by iintro HP HPQ - ispecialize HPQ $$ [#$] + ispecialize HPQ $$ [# $] /-- Tests `ispecialize` for a persistent premise with chosen hypotheses for the subgoal. -/ example [BI PROP] (P1 P2 P3 Q : PROP) : @@ -1369,7 +1369,7 @@ example [BI PROP] (P Q R S T : PROP) : example [BI PROP] [BIUpdate PROP] (P Q : PROP) : ⊢ (P -∗ Q) -∗ (|==> P) -∗ (|==> Q) := by iintro HPQ HP - ispecialize HPQ $$ [>$] + ispecialize HPQ $$ [> $] imodintro iassumption @@ -1403,7 +1403,7 @@ example [BI PROP] [BIUpdate PROP] (P Q R : PROP) : example [BI PROP] [BIUpdate PROP] (P Q R : PROP) : ⊢ (P -∗ R) -∗ (Q -∗ P) -∗ (|==> Q) -∗ (|==> R) := by iintro HPR HQP HQ - ispecialize HPR $$ (HQP $$ [>$]) + ispecialize HPR $$ (HQP $$ [> $]) imodintro iassumption diff --git a/Iris/tactics.md b/Iris/tactics.md index 39a5460df..85371a7f5 100644 --- a/Iris/tactics.md +++ b/Iris/tactics.md @@ -110,11 +110,14 @@ Used in [proof mode terms](#proof-mode-terms) after `$$` to eliminate the premis - `%`*t* — Instantiate a universal quantifier with the pure term *t*. - `[`*H₁* ... *Hₙ*`]` — Generate a subgoal for the premise with exactly the spatial hypotheses *Hᵢ*. Hypotheses written as `$H` are framed instead of forming the context. - `[-` *H₁* ... *Hₙ*`]` — Like above, but use all spatial hypotheses *except* the listed ones. +- `[>` *H₁* ... *Hₙ*`]` — Given the proof goal involves a modality, solve the premise by wrapping it with the modality and then by generating a subgoal with the spatial hypotheses *Hᵢ*. Hypotheses written as `$H` are framed instead of forming the context. +- `[>-` *H₁* ... *Hₙ*`]` — Like above, but use all spatial hypotheses *except* the listed ones. - `[#` `$`*H₁* ... `$`*Hₙ*`]` — Generate a subgoal for the *persistent* premise with the hypotheses *H₁*, ..., *Hₙ* being framed. The spatial hypotheses used for framing are not consumed. - `[`... `//]` — Additionally try to solve the subgoal with `itrivial` and fail if unsuccessful. - `[`...`] as` *name* — Name the generated subgoal *name*. - `[$]` — Solve the premise entirely by framing spatial and intuitionistic hypotheses. -- `[#$]` — Solve the *persistent* premise by framing spatial and intutionistic hypotheses. The spatial hypotheses used for framing are not consumed. +- `[# $]` — Solve the *persistent* premise by framing spatial and intutionistic hypotheses. The spatial hypotheses used for framing are not consumed. +- `[> $]` — Given the proof goal involves a modality, solve the premise by wrapping it with the modality and then by framing. - `(` *H* `$$` *spat₁* ... *spatₙ* `)` — specialises the hypothesis *H* with the specialisation patterns *spat₁*, ..., *spatₙ* before the resultant hypothesis is itself used for matching a premise. If the conclusion of a specialization is persistent, the context can be shared between the subgoal and the main goal (e.g. `ihave #HQ : □ Q $$ [HP]` keeps `HP` usable in the main goal). From dad020140c3aa33dff1faa0d19ee9e8a3abcbb4a Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 10:57:14 +0200 Subject: [PATCH 39/86] Remove TODO: `should_try_dup_context` should not check modality patterns --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 2 -- 1 file changed, 2 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 11fff5393..1280504e6 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -398,8 +398,6 @@ end The duplication only works if the conclusion of the specialization is persistent. TODO: Should this also return true for lists of intuitionistic patterns? (check in Rocq) - -TODO: This also needs to check that there are no modality addition patterns in `pat` once they are implemented. -/ @[rocq_alias intro_pat_intuitionistic, rocq_alias use_tac_specialize_intuitionistic_helper] def iCasesPat.should_try_dup_context (pat : iCasesPat) : Bool := From ee97f6da1337b250f952c2dad734c3a6677e09bc Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 11:04:09 +0200 Subject: [PATCH 40/86] Add a test for `try_dup_context` --- Iris/Iris/Tests/Tactics.lean | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 4948d823c..3868663b0 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -855,6 +855,17 @@ example [BI PROP] (P Q : PROP) (h : P ⊢ □ Q) : ⊢ P -∗ P ∗ Q := by · iexact HP · iexact HQ +/-- + Tests `ihave` with the specialisation pattern involving modalities. + Despite `try_dup_context` being `true`, the context is not duplicated. +-/ +example [BI PROP] [BIAffine PROP] [BIUpdate PROP] (P : PROP) [Persistent P] : + |==> P ⊢ |==> P := by + iintro HP + ihave #HP : P $$ [> HP //] + imodintro + iexact HP + end ihave -- ex falso From 58897964d4f42ce4c0dfb434d6053c7a2642eded Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 11:13:39 +0200 Subject: [PATCH 41/86] Minor code formatting --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 1280504e6..135b79dea 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -388,7 +388,7 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} | .inr _ => do let .some h ← trySynthInstanceQ q(Affine $A) | return none return some q(.inr $h) - let some af ← af | return ⟨_, hyps', pb, B, q($(pf).trans), pf⟩ + let some af ← af | return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ return ⟨_, hyps, q(true), B', q((specialize_dup_context $pf $af).trans), some q(specialize_dup_context $pf $af)⟩ | _, _ => return ⟨_, hyps', pb, B, pfCont, pf⟩ From fbde70f52bf570ffb9ac92b49897a9ada9a833a4 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 11:37:06 +0200 Subject: [PATCH 42/86] Add another test for `ihave` --- Iris/Iris/Tests/Tactics.lean | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 3868663b0..8d56c8bc1 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -866,6 +866,14 @@ example [BI PROP] [BIAffine PROP] [BIUpdate PROP] (P : PROP) [Persistent P] : imodintro iexact HP +/-- Tests `ihave` with the specialisation pattern involving auto-framing with modalities. -/ +example [BI PROP] [BIAffine PROP] [BIUpdate PROP] (P : PROP) [Persistent P] : + |==> P ⊢ |==> P := by + iintro HP + ihave #HP : P $$ [>$] + imodintro + iexact HP + end ihave -- ex falso From decc7dccd84d930137959fddcae95228a5587455 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 13:21:17 +0200 Subject: [PATCH 43/86] Bug fix for `iCasesPat.should_try_dup_context` and add missing `ipm_backtrack` --- Iris/Iris/ProofMode/Instances.lean | 4 ++-- Iris/Iris/ProofMode/Tactics/Specialize.lean | 9 +++++---- Iris/Iris/Tests/Tactics.lean | 11 +++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Iris/Iris/ProofMode/Instances.lean b/Iris/Iris/ProofMode/Instances.lean index 20178fa90..c2ccbf9df 100644 --- a/Iris/Iris/ProofMode/Instances.lean +++ b/Iris/Iris/ProofMode/Instances.lean @@ -321,12 +321,12 @@ instance intoAnd_and_affine_r [BI PROP] (P P' Q : PROP) [Affine Q] into_and := (and_mono_right (affine_affinely _).2).trans <| affinely_and_right.1.trans <| affinely_and.1.trans <| and_mono h.1 (affine_affinely _).1 -@[rocq_alias into_and_sep] +@[ipm_backtrack, rocq_alias into_and_sep] instance intoAnd_sep [BI PROP] [BIPositive PROP] (P Q : PROP) : IntoAnd true iprop(P ∗ Q) P Q where into_and := intuitionistically_sep.1.trans <| and_sep_intuitionistically.2.trans <| intuitionistically_and.2 -@[rocq_alias into_and_sep_affine] +@[ipm_backtrack, rocq_alias into_and_sep_affine] instance intoAnd_sep_affine (p : Bool) [BI PROP] (P Q : PROP) [TCOr (Affine P) (Absorbing Q)] [TCOr (Affine Q) (Absorbing P)] : IntoAnd p iprop(P ∗ Q) P Q where diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 135b79dea..936bcea68 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -394,14 +394,15 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} end -/-- `iCasesPat.should_try_dup_context` determines when iSpecializeCore should try to duplicate the separation context. +/-- +`iCasesPat.should_try_dup_context` determines when iSpecializeCore should try to +duplicate the separation context. The duplication only works if the conclusion of the specialization is persistent. - -TODO: Should this also return true for lists of intuitionistic patterns? (check in Rocq) -/ @[rocq_alias intro_pat_intuitionistic, rocq_alias use_tac_specialize_intuitionistic_helper] -def iCasesPat.should_try_dup_context (pat : iCasesPat) : Bool := +partial def iCasesPat.should_try_dup_context (pat : iCasesPat) : Bool := match pat with + | .conjunction args | .disjunction args => args.all (·.should_try_dup_context) | .intuitionistic _ => true | .pure _ => true | _ => false diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 8d56c8bc1..6944573a8 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -874,6 +874,17 @@ example [BI PROP] [BIAffine PROP] [BIUpdate PROP] (P : PROP) [Persistent P] : imodintro iexact HP +/-- + Tests `ihave` with a destruction pattern involving a conjunction of + intuitionistic hypotheses. +-/ +example [BI PROP] (P Q1 Q2 : PROP) [Persistent Q1] [Persistent Q2] : + ⊢ P -∗ (P -∗ □ Q1 ∗ □ Q2) -∗ P ∗ (P -∗ □ Q1 ∗ □ Q2) := by + iintro HP HPQ + ihave ⟨#HQ1, #HQ2⟩ : □ Q1 ∗ □ Q2 $$ [HP HPQ] + · iapply HPQ $$ HP + · isplitl [HP] <;> iassumption + end ihave -- ex falso From 8786002604ad04b51ee88fc5404b3499cfaa10f4 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 15:23:03 +0200 Subject: [PATCH 44/86] Improve `specPat` syntax with `colGt` and `ppSpace` --- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index 0722a014d..9bed4ff26 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -30,7 +30,7 @@ syntax "%" term:max : specPat with all but `H₁ … Hₙ` as the chosen hypotheses. `[ H₁ … Hₙ ]` and `[- H₁ … Hₙ // ]` attempt to solve the subgoal using `itrivial`. -/ -syntax "[" ("-")? (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +syntax "[" ("-")? (colGt ppSpace frameIdent)* (" //")? " ]" (" as " colGt ident)? : specPat /-- `[> H₁ … Hₙ ]` generates a subgoal for the premise with `H₁ … Hₙ` as the hypotheses chosen for the context of the subgoal wrapped in a modality. @@ -38,14 +38,14 @@ syntax "[" ("-")? (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt `[ H₁ … Hₙ // ]` and `[> H₁ … Hₙ // ]` also attempt to solve the subgoal using `itrivial`. -/ -syntax "[>" ("-")? (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +syntax "[>" ("-")? (colGt ppSpace frameIdent)* (" //")? " ]" (" as " colGt ident)? : specPat /-- `[# $H₁ … $Hₙ ]` generates a subgoal for the persistent premise with all hypotheses in the context available for the subgoal. The hypotheses `$H₁ … $Hₙ` are framed. `[# $H₁ … $Hₙ // ]` further attempts to solve the subgoal using `itrivial`. -/ -syntax "[#" (colGt ppSpace frameIdent)* ("//")? ppSpace "]" (" as " colGt ident)? : specPat +syntax "[#" (colGt ppSpace frameIdent)* (" //")? " ]" (" as " colGt ident)? : specPat /-- `[$]` solves the subgoal by framing, first with spatial hypotheses, and then with intuitionistic hypotheses. Spatial hypotheses that are not framed @@ -61,12 +61,12 @@ syntax "[#" "$" "]" : specPat patterns `spat₁ … spatₙ` before the resultant hypothesis is itself used for matching a premise. -/ -syntax "(" colGt ident "$$" (colGt ppSpace specPat)+ ppSpace ")" : specPat +syntax "(" colGt ident colGt " $$ " (colGt ppSpace specPat)+ ")" : specPat @[rocq_alias goal_kind] inductive SpecGoalKind | spatial - | modal -- TODO: implement `.autoframe` and `.goal` for the modal kind in `processWand` + | modal | persistent deriving Repr, Inhabited, BEq From 11e1a18de6d77da8948d8042704b8bc77eafee2c Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 17:06:01 +0200 Subject: [PATCH 45/86] Code refactoring --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 179 ++++++++------------ 1 file changed, 71 insertions(+), 108 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 936bcea68..beee015e9 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -16,24 +16,10 @@ namespace Iris.ProofMode public section open BI -theorem specialize_wand [BI PROP] {q p : Bool} {A1 A2 A3 Q P1 P2 : PROP} +theorem specialize_wand_step [BI PROP] {q p : Bool} {A2 A3 Q P1 P2 : PROP} (inst : IntoWand q p Q .in P1 .out P2) - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ □?p P1) : - A1 ⊢ A3 ∗ □?(p && q) P2 := by - refine h1.trans <| (sep_mono_left h2).trans <| sep_assoc.mp.trans (sep_mono_right ?_) - cases p with - | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right - | true => exact - (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| - intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) - -theorem specialize_wand_cont [BI PROP] {q p : Bool} - {A1 A2 A3 Q P1 P2 goal : PROP} - (inst : IntoWand q p Q .in P1 .out P2) - (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ A3 ∗ □?p P1) - (h3 : A3 ∗ □?(p && q) P2 ⊢ goal) : - A1 ⊢ goal := by - refine h1 (Entails.trans ?_ h3) + (h2 : A2 ⊢ A3 ∗ □?p P1) : + A2 ∗ □?q Q ⊢ A3 ∗ □?(p && q) P2 := by refine (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ?_) cases p with | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right @@ -41,81 +27,25 @@ theorem specialize_wand_cont [BI PROP] {q p : Bool} (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) -theorem specialize_wand_nested_cont [BI PROP] {q p : Bool} {C B out out₂ goal : PROP} - (inst : IntoWand q p out .in B .out out₂) - (k : C ∗ □?(p && q) out₂ ⊢ goal) : - C ∗ □?p B ⊢ ((□?q out) -∗ goal) := by - refine wand_intro (sep_assoc.mp.trans <| (sep_mono_right ?_).trans k) - cases p with - | false => exact (sep_mono_right inst.into_wand).trans wand_elim_right - | true => exact - (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| - intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) - -- TODO: if q is true and A1 is persistent, this proof can guarantee □ P2 instead of P2 -- see https://gitlab.mpi-sws.org/iris/iris/-/blob/846ed45bed6951035c6204fef365d9a344022ae6/iris/proofmode/coq_tactics.v#L336 -theorem specialize_wand_subgoal [BI PROP] {q : Bool} {A1 A2 A3 A4 Q P1 : PROP} P2 - (inst : IntoWand q false Q .out P1 .out P2) - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊣⊢ A3 ∗ A4) (h3 : A4 ⊢ P1) : A1 ⊢ A3 ∗ P2 := by - refine h1.trans <| (sep_mono_left h2.1).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_left h3).trans ?_)) - exact (sep_mono_right inst.1).trans wand_elim_right - -theorem specialize_wand_subgoal_cont [BI PROP] {q : Bool} - {A1 A2 A3 A4 Q P1 goal : PROP} P2 +theorem specialize_wand_subgoal_step [BI PROP] {q : Bool} {A2 A3 A4 Q P1 : PROP} P2 (inst : IntoWand q false Q .out P1 .out P2) - (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊣⊢ A3 ∗ A4) (h3 : A4 ⊢ P1) - (h4 : A3 ∗ P2 ⊢ goal) : A1 ⊢ goal := by - refine h1 (Entails.trans ?_ h4) - refine (sep_mono_left h2.1).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_left h3).trans ?_)) + (h2 : A2 ⊣⊢ A3 ∗ A4) (h3 : A4 ⊢ P1) : A2 ∗ □?q Q ⊢ A3 ∗ P2 := by + refine (sep_mono_left h2.1).trans <| sep_assoc.1.trans + (sep_mono_right ((sep_mono_left h3).trans ?_)) exact (sep_mono_right inst.1).trans wand_elim_right -theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} - {A1 A2 A3 Q P1 : PROP} P2 +theorem specialize_wand_autoframe_spatial_step [BI PROP] {q : Bool} {A2 A3 Q P1 : PROP} P2 (inst : IntoWand q false Q .out P1 .out P2) - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ P1) : A1 ⊢ A3 ∗ P2 := - h1.trans <| (sep_mono_left h2).trans <| sep_assoc.1.trans + (h2 : A2 ⊢ A3 ∗ P1) : A2 ∗ □?q Q ⊢ A3 ∗ P2 := + (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) -theorem specialize_wand_autoframe_spatial_cont [BI PROP] {q : Bool} - {A1 A2 A3 Q P1 goal : PROP} P2 - (inst : IntoWand q false Q .out P1 .out P2) - (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ A3 ∗ P1) (h3 : A3 ∗ P2 ⊢ goal) : - A1 ⊢ goal := by - refine h1 (Entails.trans ?_ h3) - exact (sep_mono_left h2).trans <| sep_assoc.1.trans - (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) - -theorem specialize_wand_persistent [BI PROP] {q : Bool} - {A1 A2 Q P1' : PROP} P1 P2 +theorem specialize_wand_persistent_step [BI PROP] {q : Bool} {A2 Q P1' : PROP} P1 P2 (inst3 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) (inst1 : IntoAbsorbingly P1' P1) - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ P1') : - A1 ⊢ A2 ∗ □?q P2 := - have h3 : □ P1 ∗ □?q Q ⊢ □?q P2 := by cases q with - | false => exact (sep_mono_right inst3.into_wand).trans wand_elim_right - | true => calc - _ ⊢ □ □ P1 ∗ □ □ Q := sep_mono intuitionistically_idem.mpr intuitionistically_idem.mpr - _ ⊢ □ (□ P1 ∗ □ Q) := intuitionistically_sep_mpr - _ ⊢ □ (□ P1 ∗ (□ P1 -∗ P2)) := intuitionistically_mono <| sep_mono_right <| inst3.into_wand - _ ⊢ □?true P2 := intuitionistically_mono wand_elim_right - calc - _ ⊢ A2 ∗ □?q Q := h1 - _ ⊢ (A2 ∧ A2) ∗ □?q Q := sep_mono_left <| and_intro .rfl .rfl - _ ⊢ (A2 ∧ P1') ∗ □?q Q := sep_mono_left <| and_mono_right h2 - _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right into_absorbingly - _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right <| absorbingly_mono Persistent.persistent - _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right <| absorbingly_persistently.mp - _ ⊢ (A2 ∗ □ P1) ∗ □?q Q := sep_mono_left persistently_and_intuitionistically_sep_right.mp - _ ⊢ A2 ∗ □ P1 ∗ □?q Q := sep_assoc.mp - _ ⊢ A2 ∗ □?q P2 := sep_mono_right h3 - -theorem specialize_wand_persistent_cont [BI PROP] {q : Bool} - {A1 A2 Q P1' goal : PROP} P1 P2 - (inst3 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) - (inst1 : IntoAbsorbingly P1' P1) - (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ P1') (h3 : A2 ∗ □?q P2 ⊢ goal) : - A1 ⊢ goal := by - refine h1 (Entails.trans ?_ h3) + (h2 : A2 ⊢ P1') : A2 ∗ □?q Q ⊢ A2 ∗ □?q P2 := by have h4 : □ P1 ∗ □?q Q ⊢ □?q P2 := by cases q with | false => exact (sep_mono_right inst3.into_wand).trans wand_elim_right | true => calc @@ -133,15 +63,45 @@ theorem specialize_wand_persistent_cont [BI PROP] {q : Bool} _ ⊢ A2 ∗ □ P1 ∗ □?q Q := sep_assoc.mp _ ⊢ A2 ∗ □?q P2 := sep_mono_right h4 -theorem specialize_forall [BI PROP] {p : Bool} {A1 A2 P : PROP} {α : Sort _} {Φ : α → PROP} - (inst : IntoForall P Φ) (h : A1 ⊢ A2 ∗ □?p P) (a : α) : A1 ⊢ A2 ∗ □?p (Φ a) := by - refine h.trans <| sep_mono_right <| intuitionisticallyIf_mono <| inst.into_forall.trans (forall_elim a) +theorem specialize_forall_step [BI PROP] {p : Bool} {A2 P : PROP} {α : Sort _} {Φ : α → PROP} + (inst : IntoForall P Φ) (a : α) : A2 ∗ □?p P ⊢ A2 ∗ □?p (Φ a) := + sep_mono_right <| intuitionisticallyIf_mono <| inst.into_forall.trans (forall_elim a) -theorem specialize_forall_cont [BI PROP] {p : Bool} {A1 A2 P goal : PROP} {α : Sort _} {Φ : α → PROP} - (inst : IntoForall P Φ) (h : (A2 ∗ □?p P ⊢ goal) → A1 ⊢ goal) (a : α) - (h2 : A2 ∗ □?p (Φ a) ⊢ goal) : A1 ⊢ goal := by - refine h (Entails.trans ?_ h2) - refine sep_mono_right <| intuitionisticallyIf_mono <| inst.into_forall.trans (forall_elim a) +theorem specialize_wand [BI PROP] {q p : Bool} {A1 A2 A3 Q P1 P2 : PROP} + (inst : IntoWand q p Q .in P1 .out P2) + (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ □?p P1) : + A1 ⊢ A3 ∗ □?(p && q) P2 := by + refine h1.trans <| (sep_mono_left h2).trans <| sep_assoc.mp.trans (sep_mono_right ?_) + cases p with + | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right + | true => exact + (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| + intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) + +theorem specialize_wand_cont [BI PROP] {q p : Bool} + {A1 A2 A3 Q P1 P2 goal : PROP} + (inst : IntoWand q p Q .in P1 .out P2) + (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ A3 ∗ □?p P1) + (h3 : A3 ∗ □?(p && q) P2 ⊢ goal) : + A1 ⊢ goal := by + refine h1 (Entails.trans ?_ h3) + refine (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ?_) + cases p with + | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right + | true => exact + (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| + intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) + +theorem specialize_wand_nested_cont [BI PROP] {q p : Bool} {C B out out₂ goal : PROP} + (inst : IntoWand q p out .in B .out out₂) + (k : C ∗ □?(p && q) out₂ ⊢ goal) : + C ∗ □?p B ⊢ ((□?q out) -∗ goal) := by + refine wand_intro (sep_assoc.mp.trans <| (sep_mono_right ?_).trans k) + cases p with + | false => exact (sep_mono_right inst.into_wand).trans wand_elim_right + | true => exact + (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| + intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} (h : P ∗ □?pa A ⊢ P' ∗ □?pb B) @@ -172,6 +132,15 @@ structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig goal : Q($ (pfCont : Q(($e ∗ □?$p $out ⊢ $goal) → $orig ⊢ $goal)) pf : Option Q($orig ⊢ $e ∗ □?$p $out) +private def SpecializeState.update {u} {prop : Q(Type u)} {bi : Q(BI $prop)} + {orig goal : Q($prop)} (st : @SpecializeState u prop bi orig goal) + {e' : Q($prop)} (hyps' : Hyps bi e') (p' : Q(Bool)) (out' : Q($prop)) + (pfCont : Q($(st.e) ∗ □?$(st.p) $(st.out) ⊢ $e' ∗ □?$p' $out')) : + @SpecializeState u prop bi orig goal := + { hyps := hyps', p := p', out := out', + pfCont := q(fun pf => $(st.pfCont) ($(pfCont).trans pf)), + pf := st.pf.map (fun pf => q($(pf).trans $pfCont)) } + set_option maxHeartbeats 300000 in mutual @@ -191,9 +160,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let out₂ ← mkFreshExprMVarQ prop let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $p1 $out .in $out₁' .out $out₂) | throwError m!"ispecialize: cannot instantiate {out} with {out₁'}" - let pfCont := q(specialize_wand_cont $inst $pfCont $(pf').mp) - let pf := pf.bind (fun pf => some q(specialize_wand $inst $pf $(pf').mp)) - return { hyps := hyps', p := p2, out := out₂, pfCont, pf } + let pfStep := q(specialize_wand_step $inst $(pf').mp) + return specState.update hyps' p2 out₂ pfStep | .ident i spats => let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar @@ -222,9 +190,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q have : $out' =Q $Φ $x := ⟨⟩ let newMVarIds ← getMVarsNoDelayed x for mvar in newMVarIds do addMVarGoal mvar - let pfCont := q(specialize_forall_cont $inst $pfCont $x) - let pf := pf.bind (fun pf => some q(specialize_forall $inst $pf $x)) - return { e, hyps, p, out := out', pfCont, pf } + let pfStep : Q($e ∗ □?$p $out ⊢ $e ∗ □?$p $Φ $x) := q(specialize_forall_step (A2 := $e) (p := $p) $inst $x) + return specState.update hyps p out' pfStep | .goal { kind := .spatial, negate, trivial, frame := f, hyps := hs } g => do let mut ivars : IVarIdSet := {} for name in hs do @@ -250,9 +217,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q return r else addBIGoal hyps goal g - let pfCont := q(specialize_wand_subgoal_cont $out₂ $inst $pfCont $pf' $pf'') - let pf := pf.bind (fun pf => some q(specialize_wand_subgoal $out₂ $inst $pf $pf' $pf'')) - return { hyps := hypsl', p := q(false), out := out₂, pfCont, pf } + let pfStep := q(specialize_wand_subgoal_step $out₂ $inst $pf' $pf'') + return specState.update hypsl' q(false) out₂ pfStep | .goal { kind := .persistent, trivial, frame := f, hyps := hs, .. } g => do if !hs.isEmpty then throwError "ispecialize: the subgoal for the persistent premise should not consume hypotheses" @@ -277,9 +243,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q return r else addBIGoal hyps goal g - let pfCont := q(specialize_wand_persistent_cont $out₁ $out₂ $inst1 $inst2 $inst3 $pfCont $pf') - let pf := pf.bind (fun pf => some q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf $pf')) - return { hyps, p, out := out₂, pfCont, pf } + let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') + return specState.update hyps p out₂ pfStep | .goal { kind := .modal, negate, trivial, frame := f, hyps := hs, .. } g => let mut ivars : IVarIdSet := {} for name in hs do @@ -318,9 +283,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | throwError m!"ispecialize: {out} is not a wand" let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose - let pfCont := q(specialize_wand_autoframe_spatial_cont $out₂ $inst $pfCont $pf') - let pf := pf.bind (fun pf => some q(specialize_wand_autoframe_spatial $out₂ $inst $pf $pf')) - return { hyps := hyps', p := q(false), out := out₂, pfCont, pf } + let pfStep := q(specialize_wand_autoframe_spatial_step $out₂ $inst $pf') + return specState.update hyps' q(false) out₂ pfStep | .autoframe .persistent => let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop @@ -336,9 +300,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let some pf ← iTrivial hyps goal | throwError "ispecialize: unable to solve premise by framing" return pf - let pfCont := q(specialize_wand_persistent_cont $out₁ $out₂ $inst1 $inst2 $inst3 $pfCont $pf') - let pf := pf.bind (fun pf => some q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf $pf')) - return { hyps, p, out := out₂, pfCont, pf } + let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') + return specState.update hyps p out₂ pfStep | .autoframe .modal => let out₁ ← mkFreshExprMVarQ prop let out₂ ← mkFreshExprMVarQ prop From b5acfe23fe9bdfc9fe019814c2027961abd739e7 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 17:16:04 +0200 Subject: [PATCH 46/86] `processWand`: Combine `.ident i []` and `.ident i spats` cases into one --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 22 --------------------- 1 file changed, 22 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index beee015e9..68b7cc032 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -16,17 +16,6 @@ namespace Iris.ProofMode public section open BI -theorem specialize_wand_step [BI PROP] {q p : Bool} {A2 A3 Q P1 P2 : PROP} - (inst : IntoWand q p Q .in P1 .out P2) - (h2 : A2 ⊢ A3 ∗ □?p P1) : - A2 ∗ □?q Q ⊢ A3 ∗ □?(p && q) P2 := by - refine (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ?_) - cases p with - | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right - | true => exact - (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| - intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) - -- TODO: if q is true and A1 is persistent, this proof can guarantee □ P2 instead of P2 -- see https://gitlab.mpi-sws.org/iris/iris/-/blob/846ed45bed6951035c6204fef365d9a344022ae6/iris/proofmode/coq_tactics.v#L336 theorem specialize_wand_subgoal_step [BI PROP] {q : Bool} {A2 A3 A4 Q P1 : PROP} P2 @@ -151,17 +140,6 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let ⟨ref, spat⟩ := spat withRef ref do match spat with - | .ident i [] => do - let ivar ← hyps.findWithInfo i - let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar - let p2 := if p1.constName! == ``true then p else q(false) - have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ - have : $p2 =Q ($p1 && $p) := ⟨⟩ - let out₂ ← mkFreshExprMVarQ prop - let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $p1 $out .in $out₁' .out $out₂) - | throwError m!"ispecialize: cannot instantiate {out} with {out₁'}" - let pfStep := q(specialize_wand_step $inst $(pf').mp) - return specState.update hyps' p2 out₂ pfStep | .ident i spats => let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar From 9c9aee5d454d4909278bfec188028f2e4f314566 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 17:32:25 +0200 Subject: [PATCH 47/86] Major refactoring to reduce repetitve code --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 40 +++++++++------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 68b7cc032..dc6f2c77a 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -130,6 +130,16 @@ private def SpecializeState.update {u} {prop : Q(Type u)} {bi : Q(BI $prop)} pfCont := q(fun pf => $(st.pfCont) ($(pfCont).trans pf)), pf := st.pf.map (fun pf => q($(pf).trans $pfCont)) } +private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} + (p : Q(Bool)) (out : Q($prop)) (persistent : Bool) : + ProofModeM <| (out₁ : Q($prop)) × (out₂ : Q($prop)) × + Q(IntoWand $p $persistent $out .out $out₁ .out $out₂) := do + let out₁ ← mkFreshExprMVarQ prop + let out₂ ← mkFreshExprMVarQ prop + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $persistent $out .out $out₁ .out $out₂) + | throwError m!"ispecialize: {out} is not a wand" + return ⟨out₁, out₂, inst⟩ + set_option maxHeartbeats 300000 in mutual @@ -183,10 +193,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q frameIVars := frameIVars.reverse let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" + let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false let res ← iFrame bi _ hypsr' out₁ (frameIVars.map (⟨.ipm ·, true⟩)) let pf'' ← res.finish λ hyps goal => do if trivial then @@ -200,10 +207,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | .goal { kind := .persistent, trivial, frame := f, hyps := hs, .. } g => do if !hs.isEmpty then throwError "ispecialize: the subgoal for the persistent premise should not consume hypotheses" - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" + let ⟨out₁, out₂, inst1⟩ ← synthIntoWand p out true let some inst2 ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) | throwError m!"ispecialize: {out₁} is not persistent" let out₁' ← mkFreshExprMVarQ prop @@ -236,10 +240,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q frameIVars := frameIVars.reverse let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" + let ⟨out₁, out₂, inst1⟩ ← synthIntoWand p out false let out₁' ← mkFreshExprMVarQ prop let some inst2 ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) | throwError "ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" @@ -255,19 +256,13 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pfCont := q(fun pf => $pfCont (specialize_modal $h pf $inst2 $inst1)) return { hyps := hypsl', p := q(false), out := out₂, pfCont, pf := none } | .autoframe .spatial => do - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" + let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose let pfStep := q(specialize_wand_autoframe_spatial_step $out₂ $inst $pf') return specState.update hyps' q(false) out₂ pfStep | .autoframe .persistent => - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" + let ⟨out₁, out₂, inst1⟩ ← synthIntoWand p out true let some inst2 ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) | throwError m!"ispecialize: {out₁} is not persistent" let out₁' ← mkFreshExprMVarQ prop @@ -281,10 +276,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep | .autoframe .modal => - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" + let ⟨out₁, out₂, inst1⟩ ← synthIntoWand p out false let out₁' ← mkFreshExprMVarQ prop let some inst2 ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" From 4d66aed09883fec9b2afc369addacfe131ebf2ad Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 17:45:00 +0200 Subject: [PATCH 48/86] Minor formatting --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 32 +++++++++++---------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index dc6f2c77a..1488a9646 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -93,25 +93,28 @@ theorem specialize_wand_nested_cont [BI PROP] {q p : Bool} {C B out out₂ goal intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} - (h : P ∗ □?pa A ⊢ P' ∗ □?pb B) - (h2 : pa = true ∨ Affine A) - [IntoPersistently pb B B'] - : P ∗ □?pa A ⊢ P ∗ □ B' := by - apply Entails.trans _ persistently_and_intuitionistically_sep_right.1 - apply and_intro - · cases h2 <;> subst_eqs <;> apply sep_elim_left - · apply h.trans $ (sep_mono_right (persistentlyIf_of_intuitionisticallyIf.trans into_persistently)).trans sep_elim_right + (h : P ∗ □?pa A ⊢ P' ∗ □?pb B) + (h2 : pa = true ∨ Affine A) + [IntoPersistently pb B B'] : + P ∗ □?pa A ⊢ P ∗ □ B' := by + apply Entails.trans _ persistently_and_intuitionistically_sep_right.1 + apply and_intro + · cases h2 <;> subst_eqs <;> apply sep_elim_left + · apply h.trans <| + (sep_mono_right (persistentlyIf_of_intuitionisticallyIf.trans into_persistently)).trans <| + sep_elim_right theorem specialize_modal [BI PROP] {e e' goal R P1 P1' P2 : PROP} {p : Bool} (h1 : e ⊢ e' ∗ P1') (h2 : e' ∗ P2 ⊢ goal) - (inst1 : AddModal P1' P1 goal) (inst2 : IntoWand p false R .out P1 .out P2) : + (inst1 : IntoWand p false R .out P1 .out P2) + (inst2 : AddModal P1' P1 goal) : e ∗ □?p R ⊢ goal := calc _ ⊢ (e' ∗ P1') ∗ □?p R := sep_mono_left h1 _ ⊢ P1' ∗ (e' ∗ □?p R) := sep_assoc.mp.trans sep_left_comm.mp - _ ⊢ P1' ∗ (e' ∗ (P1 -∗ P2)) := sep_mono_right (sep_mono_right inst2.into_wand) + _ ⊢ P1' ∗ (e' ∗ (P1 -∗ P2)) := sep_mono_right (sep_mono_right inst1.into_wand) _ ⊢ P1' ∗ ((P2 -∗ goal) ∗ (P1 -∗ P2)) := sep_mono_right (sep_mono_left (wand_intro h2)) _ ⊢ P1' ∗ (P1 -∗ goal) := sep_mono_right (sep_comm.mp.trans wand_trans) - _ ⊢ goal := inst1.add_modal + _ ⊢ goal := inst2.add_modal public meta section open Lean Elab Tactic Meta Qq Std @@ -140,7 +143,6 @@ private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} | throwError m!"ispecialize: {out} is not a wand" return ⟨out₁, out₂, inst⟩ -set_option maxHeartbeats 300000 in mutual private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q($prop)} @@ -212,7 +214,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | throwError m!"ispecialize: {out₁} is not persistent" let out₁' ← mkFreshExprMVarQ prop let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) - | throwError m!"ispecialize: type class synthesis failed for {out₁} with IntoAbsorbingly" + | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" let mut frameIVars : List IVarId := [] for name in f do frameIVars := (← hyps.findWithInfo name) :: frameIVars @@ -253,7 +255,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q else addBIGoal hyps goal g let h := q($(pf').mp.trans (sep_mono_right $pf'')) - let pfCont := q(fun pf => $pfCont (specialize_modal $h pf $inst2 $inst1)) + let pfCont := q(fun pf => $pfCont (specialize_modal $h pf $inst1 $inst2)) return { hyps := hypsl', p := q(false), out := out₂, pfCont, pf := none } | .autoframe .spatial => do let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false @@ -282,7 +284,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose - let pfCont := q(fun pf => $pfCont (specialize_modal $pf' pf $inst2 $inst1)) + let pfCont := q(fun pf => $pfCont (specialize_modal $pf' pf $inst1 $inst2)) return { hyps := hyps', p := q(false), out := out₂, pfCont := q($pfCont), pf := none } /-- Specialize a proposition `A` by applying a sequence of specialization patterns. From d852a8756ae04f74e4e753c2d017ca8c0fedf45e Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 18:05:24 +0200 Subject: [PATCH 49/86] Code refactoring: `findFrameIVars` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 36 +++++++++------------ 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 1488a9646..88a326ade 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -143,6 +143,20 @@ private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} | throwError m!"ispecialize: {out} is not a wand" return ⟨out₁, out₂, inst⟩ +private def findFrameIVars {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} + (hyps : Hyps bi e) (hs : List Ident) (f : List Ident) : + ProofModeM <| IVarIdSet × List IVarId := do + let mut ivars : IVarIdSet := {} + for name in hs do + ivars := ivars.insert (← hyps.findWithInfo name) + let mut frameIVars : List IVarId := [] + for name in f do + let ivar ← hyps.findWithInfo name + frameIVars := ivar :: frameIVars + if ivars.contains ivar then + throwError "ispecialize: {name} used twice" + return ⟨ivars, frameIVars.reverse⟩ + mutual private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q($prop)} @@ -183,16 +197,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pfStep : Q($e ∗ □?$p $out ⊢ $e ∗ □?$p $Φ $x) := q(specialize_forall_step (A2 := $e) (p := $p) $inst $x) return specState.update hyps p out' pfStep | .goal { kind := .spatial, negate, trivial, frame := f, hyps := hs } g => do - let mut ivars : IVarIdSet := {} - for name in hs do - ivars := ivars.insert (← hyps.findWithInfo name) - let mut frameIVars : List IVarId := [] - for name in f do - let ivar ← hyps.findWithInfo name - frameIVars := ivar :: frameIVars - if ivars.contains ivar then - throwError "ispecialize: {name} used twice" - frameIVars := frameIVars.reverse + let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false @@ -230,16 +235,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep | .goal { kind := .modal, negate, trivial, frame := f, hyps := hs, .. } g => - let mut ivars : IVarIdSet := {} - for name in hs do - ivars := ivars.insert (← hyps.findWithInfo name) - let mut frameIVars : List IVarId := [] - for name in f do - let ivar ← hyps.findWithInfo name - frameIVars := ivar :: frameIVars - if ivars.contains ivar then - throwError "ispecialize: {name} used twice" - frameIVars := frameIVars.reverse + let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps let ⟨out₁, out₂, inst1⟩ ← synthIntoWand p out false From bf5027c27976368b7577be72ca914255dc985d8a Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 18:06:58 +0200 Subject: [PATCH 50/86] Update error message for consistency --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 88a326ade..1ca4ab497 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -269,7 +269,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let pf' ← res.finish <| fun hyps goal => do let some pf ← iTrivial hyps goal - | throwError "ispecialize: unable to solve premise by framing" + | throwError "ispecialize: itrivial could not solve {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" return pf let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep From c87a0a178deca3c12ebbe68ac810e77de393dc28 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 18:14:26 +0200 Subject: [PATCH 51/86] Code refactoring: helper function `finishFrameSubgoal` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 42 +++++++++------------ 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 1ca4ab497..ec828f170 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -157,6 +157,19 @@ private def findFrameIVars {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} throwError "ispecialize: {name} used twice" return ⟨ivars, frameIVars.reverse⟩ +private def finishFrameSubgoal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} + {e goal : Q($prop)} (trivial : Bool) (g : Option Name) + (res : FrameResult bi e goal) : ProofModeM Q($e ⊢ $goal) := do + res.finish λ hyps goal => do + if trivial then + let some r ← iTrivial hyps goal + | throwError "ispecialize: itrivial could not solve\ + {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" + return r + else match g with + | none => addBIGoal hyps goal + | some g => addBIGoal hyps goal g + mutual private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q($prop)} @@ -202,13 +215,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false let res ← iFrame bi _ hypsr' out₁ (frameIVars.map (⟨.ipm ·, true⟩)) - let pf'' ← res.finish λ hyps goal => do - if trivial then - let some r ← iTrivial hyps goal - | throwError "ispecialize: itrivial could not solve {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" - return r - else - addBIGoal hyps goal g + let pf'' ← finishFrameSubgoal trivial g res let pfStep := q(specialize_wand_subgoal_step $out₂ $inst $pf' $pf'') return specState.update hypsl' q(false) out₂ pfStep | .goal { kind := .persistent, trivial, frame := f, hyps := hs, .. } g => do @@ -225,13 +232,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q frameIVars := (← hyps.findWithInfo name) :: frameIVars frameIVars := frameIVars.reverse let res ← iFrame bi _ hyps out₁' (frameIVars.map (⟨.ipm ·, true⟩)) - let pf' ← res.finish λ hyps goal => do - if trivial then - let some r ← iTrivial hyps goal - | throwError "ispecialize: itrivial could not solve {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" - return r - else - addBIGoal hyps goal g + let pf' ← finishFrameSubgoal trivial g res let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep | .goal { kind := .modal, negate, trivial, frame := f, hyps := hs, .. } g => @@ -243,13 +244,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let some inst2 ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) | throwError "ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" let res ← iFrame bi _ hypsr' out₁' (frameIVars.map (⟨.ipm ·, true⟩)) - let pf'' ← res.finish λ hyps goal => do - if trivial then - let some r ← iTrivial hyps goal - | throwError "ispecialize: itrivial could not solve {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" - return r - else - addBIGoal hyps goal g + let pf'' ← finishFrameSubgoal trivial g res let h := q($(pf').mp.trans (sep_mono_right $pf'')) let pfCont := q(fun pf => $pfCont (specialize_modal $h pf $inst1 $inst2)) return { hyps := hypsl', p := q(false), out := out₂, pfCont, pf := none } @@ -267,10 +262,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) | throwError m!"ispecialize: type class synthessis failed for {out₁} with IntoAbsorbingly" let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) - let pf' ← res.finish <| fun hyps goal => do - let some pf ← iTrivial hyps goal - | throwError "ispecialize: itrivial could not solve {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" - return pf + let pf' ← finishFrameSubgoal true none res let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep | .autoframe .modal => From a36db2a1e3696a73380c54e4738b930891434bd3 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 18:17:39 +0200 Subject: [PATCH 52/86] Minor formatting --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index ec828f170..6909e8cc3 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -192,7 +192,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pfCont ← do match pfNest with | some pfNest => pure q(specialize_wand_cont $inst $pfCont ($(pf').mp.trans $pfNest)) - | none => pure q(fun pf => $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (specialize_wand_nested_cont $inst pf))))) + | none => pure q(fun pf => + $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (specialize_wand_nested_cont $inst pf))))) let pf := pfNest.bind (fun pfNest => pf.bind (fun pf => some q(specialize_wand $inst $pf ($(pf').mp.trans $pfNest)))) return { hyps := hyps'', p := p2, out := out₂, pfCont, pf } @@ -227,10 +228,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let out₁' ← mkFreshExprMVarQ prop let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" - let mut frameIVars : List IVarId := [] - for name in f do - frameIVars := (← hyps.findWithInfo name) :: frameIVars - frameIVars := frameIVars.reverse + let ⟨_, frameIVars⟩ ← findFrameIVars hyps [] f let res ← iFrame bi _ hyps out₁' (frameIVars.map (⟨.ipm ·, true⟩)) let pf' ← finishFrameSubgoal trivial g res let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') @@ -260,7 +258,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | throwError m!"ispecialize: {out₁} is not persistent" let out₁' ← mkFreshExprMVarQ prop let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) - | throwError m!"ispecialize: type class synthessis failed for {out₁} with IntoAbsorbingly" + | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let pf' ← finishFrameSubgoal true none res let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') From c99add9cfc9f1560b6c8d80a3e99c4c2d336690d Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 18:37:21 +0200 Subject: [PATCH 53/86] Code refactoring: `synthIntoWandPersistent` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 55 ++++++++++++--------- Iris/Iris/Tests/Tactics.lean | 3 +- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 6909e8cc3..270f790e0 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -133,16 +133,6 @@ private def SpecializeState.update {u} {prop : Q(Type u)} {bi : Q(BI $prop)} pfCont := q(fun pf => $(st.pfCont) ($(pfCont).trans pf)), pf := st.pf.map (fun pf => q($(pf).trans $pfCont)) } -private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} - (p : Q(Bool)) (out : Q($prop)) (persistent : Bool) : - ProofModeM <| (out₁ : Q($prop)) × (out₂ : Q($prop)) × - Q(IntoWand $p $persistent $out .out $out₁ .out $out₂) := do - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $persistent $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" - return ⟨out₁, out₂, inst⟩ - private def findFrameIVars {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : Hyps bi e) (hs : List Ident) (f : List Ident) : ProofModeM <| IVarIdSet × List IVarId := do @@ -170,6 +160,33 @@ private def finishFrameSubgoal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} | none => addBIGoal hyps goal | some g => addBIGoal hyps goal g +private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} + (p : Q(Bool)) (out : Q($prop)) (persistent : Bool) : + ProofModeM <| (out₁ : Q($prop)) × (out₂ : Q($prop)) × + Q(IntoWand $p $persistent $out .out $out₁ .out $out₂) := do + let out₁ ← mkFreshExprMVarQ prop + let out₂ ← mkFreshExprMVarQ prop + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $persistent $out .out $out₁ .out $out₂) + | throwError m!"ispecialize: {out} is not a wand" + return ⟨out₁, out₂, inst⟩ + +private def synthIntoWandPersistent {u} {prop : Q(Type u)} {bi : Q(BI $prop)} + (p : Q(Bool)) (out : Q($prop)) : + ProofModeM ((out₁ : Q($prop)) × (out₂ : Q($prop)) × (out₁' : Q($prop)) × + Q(IntoWand «$p» true «$out» InOut.out «$out₁» InOut.out «$out₂») × + Q(Persistent $out₁) × + Q(IntoAbsorbingly $out₁' $out₁)) := do + let out₁ ← mkFreshExprMVarQ prop + let out₂ ← mkFreshExprMVarQ prop + let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) + | throwError m!"ispecialize: {out} is not a wand" + let some inst2 ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) + | throwError m!"ispecialize: {out₁} is not persistent" + let out₁' ← mkFreshExprMVarQ prop + let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) + | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" + pure ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ + mutual private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q($prop)} @@ -222,12 +239,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | .goal { kind := .persistent, trivial, frame := f, hyps := hs, .. } g => do if !hs.isEmpty then throwError "ispecialize: the subgoal for the persistent premise should not consume hypotheses" - let ⟨out₁, out₂, inst1⟩ ← synthIntoWand p out true - let some inst2 ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) - | throwError m!"ispecialize: {out₁} is not persistent" - let out₁' ← mkFreshExprMVarQ prop - let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) - | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" + let ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ ← synthIntoWandPersistent p out let ⟨_, frameIVars⟩ ← findFrameIVars hyps [] f let res ← iFrame bi _ hyps out₁' (frameIVars.map (⟨.ipm ·, true⟩)) let pf' ← finishFrameSubgoal trivial g res @@ -253,12 +265,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pfStep := q(specialize_wand_autoframe_spatial_step $out₂ $inst $pf') return specState.update hyps' q(false) out₂ pfStep | .autoframe .persistent => - let ⟨out₁, out₂, inst1⟩ ← synthIntoWand p out true - let some inst2 ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) - | throwError m!"ispecialize: {out₁} is not persistent" - let out₁' ← mkFreshExprMVarQ prop - let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) - | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" + let ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ ← synthIntoWandPersistent p out let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let pf' ← finishFrameSubgoal true none res let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') @@ -310,7 +317,9 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} let .some h ← trySynthInstanceQ q(Affine $A) | return none return some q(.inr $h) let some af ← af | return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ - return ⟨_, hyps, q(true), B', q((specialize_dup_context $pf $af).trans), some q(specialize_dup_context $pf $af)⟩ + return ⟨_, hyps, q(true), B', + q((specialize_dup_context $pf $af).trans), + some q(specialize_dup_context $pf $af)⟩ | _, _ => return ⟨_, hyps', pb, B, pfCont, pf⟩ end diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 6944573a8..82faaa029 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1108,7 +1108,7 @@ example [BI PROP] (Q : PROP) : P ⊢ (P -∗ Q) -∗ Q := by -- Test `ispecialize` with failing `//` /-- -error: ispecialize: itrivial could not solve ⏎ +error: ispecialize: itrivial could not solve ⊢ False -/ #guard_msgs in @@ -1116,7 +1116,6 @@ example [BI PROP] (Q : PROP) : ⊢ (False -∗ Q) -∗ Q := by iintro HQ ispecialize HQ $$ [//] - /-- Tests `ispecialize` with named subgoal -/ example [BI PROP] (Q : PROP) : P ⊢ (⌜True⌝ -∗ P -∗ ⌜True⌝ -∗ Q) -∗ Q := by iintro HP HPQ From e637fae6a4e0380c7bf2b3a6b350afc66d0359e7 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 18:48:57 +0200 Subject: [PATCH 54/86] Code refactoring: `synthIntoWandModal` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 29 ++++++++++++--------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 270f790e0..7bfd52247 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -173,7 +173,7 @@ private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} private def synthIntoWandPersistent {u} {prop : Q(Type u)} {bi : Q(BI $prop)} (p : Q(Bool)) (out : Q($prop)) : ProofModeM ((out₁ : Q($prop)) × (out₂ : Q($prop)) × (out₁' : Q($prop)) × - Q(IntoWand «$p» true «$out» InOut.out «$out₁» InOut.out «$out₂») × + Q(IntoWand $p true $out .out $out₁ .out $out₂) × Q(Persistent $out₁) × Q(IntoAbsorbingly $out₁' $out₁)) := do let out₁ ← mkFreshExprMVarQ prop @@ -187,6 +187,20 @@ private def synthIntoWandPersistent {u} {prop : Q(Type u)} {bi : Q(BI $prop)} | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" pure ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ +private def synthIntoWandModal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} + (p : Q(Bool)) (out goal : Q($prop)) : + ProofModeM ((out₁ : Q($prop)) × (out₂ : Q($prop)) × (out₁' : Q($prop)) × + Q(IntoWand $p false $out .out $out₁ .out $out₂) × + Q(AddModal $out₁' $out₁ $goal)) := do + let out₁ ← mkFreshExprMVarQ prop + let out₂ ← mkFreshExprMVarQ prop + let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) + | throwError m!"ispecialize: {out} is not a wand" + let out₁' ← mkFreshExprMVarQ prop + let some inst2 ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) + | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" + pure ⟨out₁, out₂, out₁', inst1, inst2⟩ + mutual private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q($prop)} @@ -201,8 +215,6 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar let ⟨_, hyps'', pB, B, pfContNest, pfNest⟩ ← iSpecializeCore hyps' p1 out₁' q(iprop(□?$p $out -∗ $goal)) spats let p2 := if pB.constName! == ``true then p else q(false) - have : $out₁ =Q iprop(□?$p1 $out₁') := ⟨⟩ - have : $p2 =Q ($pB && $p) := ⟨⟩ let out₂ ← mkFreshExprMVarQ prop let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) | throwError m!"ispecialize: cannot instantiate {out} with {B}" @@ -222,7 +234,6 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | throwError "ispecialize: {out} is not a Lean premise" let x ← elabTermEnsuringTypeQ (u := .succ .zero) t α have out' : Q($prop) := Expr.headBeta q($Φ $x) - have : $out' =Q $Φ $x := ⟨⟩ let newMVarIds ← getMVarsNoDelayed x for mvar in newMVarIds do addMVarGoal mvar let pfStep : Q($e ∗ □?$p $out ⊢ $e ∗ □?$p $Φ $x) := q(specialize_forall_step (A2 := $e) (p := $p) $inst $x) @@ -249,10 +260,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps - let ⟨out₁, out₂, inst1⟩ ← synthIntoWand p out false - let out₁' ← mkFreshExprMVarQ prop - let some inst2 ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) - | throwError "ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" + let ⟨out₁, out₂, out₁', inst1, inst2⟩ ← synthIntoWandModal p out goal let res ← iFrame bi _ hypsr' out₁' (frameIVars.map (⟨.ipm ·, true⟩)) let pf'' ← finishFrameSubgoal trivial g res let h := q($(pf').mp.trans (sep_mono_right $pf'')) @@ -271,10 +279,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep | .autoframe .modal => - let ⟨out₁, out₂, inst1⟩ ← synthIntoWand p out false - let out₁' ← mkFreshExprMVarQ prop - let some inst2 ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) - | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" + let ⟨out₁, out₂, out₁', inst1, inst2⟩ ← synthIntoWandModal p out goal let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose let pfCont := q(fun pf => $pfCont (specialize_modal $pf' pf $inst1 $inst2)) From 6cd6e9386b600588d82a4ed3cdafc04f953ec7c3 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 19:00:40 +0200 Subject: [PATCH 55/86] More refactoring --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 32 ++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 7bfd52247..39de7e3b8 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -147,18 +147,20 @@ private def findFrameIVars {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} throwError "ispecialize: {name} used twice" return ⟨ivars, frameIVars.reverse⟩ -private def finishFrameSubgoal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} - {e goal : Q($prop)} (trivial : Bool) (g : Option Name) - (res : FrameResult bi e goal) : ProofModeM Q($e ⊢ $goal) := do +private def finishFrameSubgoal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} + (hyps : Hyps bi e) (goal : Q($prop)) (trivial : Bool) (g : Option Name) + (frameIVars : Option <| List IVarId) : ProofModeM Q($e ⊢ $goal) := do + let targets ← do match frameIVars with + | none => SelPat.resolve hyps [.spatial, .intuitionistic] + | some frameIVars => pure (frameIVars.map (⟨.ipm ·, true⟩)) + let res ← iFrame bi _ hyps goal targets res.finish λ hyps goal => do if trivial then let some r ← iTrivial hyps goal | throwError "ispecialize: itrivial could not solve\ {← ppExpr <| IrisGoal.toExpr {hyps, goal ..}}" return r - else match g with - | none => addBIGoal hyps goal - | some g => addBIGoal hyps goal g + else addBIGoal hyps goal <| g.getD .anonymous private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} (p : Q(Bool)) (out : Q($prop)) (persistent : Bool) : @@ -213,7 +215,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | .ident i spats => let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar - let ⟨_, hyps'', pB, B, pfContNest, pfNest⟩ ← iSpecializeCore hyps' p1 out₁' q(iprop(□?$p $out -∗ $goal)) spats + let ⟨_, hyps'', pB, B, pfContNest, pfNest⟩ ← + iSpecializeCore hyps' p1 out₁' q(iprop(□?$p $out -∗ $goal)) spats let p2 := if pB.constName! == ``true then p else q(false) let out₂ ← mkFreshExprMVarQ prop let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) @@ -236,15 +239,15 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q have out' : Q($prop) := Expr.headBeta q($Φ $x) let newMVarIds ← getMVarsNoDelayed x for mvar in newMVarIds do addMVarGoal mvar - let pfStep : Q($e ∗ □?$p $out ⊢ $e ∗ □?$p $Φ $x) := q(specialize_forall_step (A2 := $e) (p := $p) $inst $x) + let pfStep : Q($e ∗ □?$p $out ⊢ $e ∗ □?$p $Φ $x) := + q(specialize_forall_step (A2 := $e) (p := $p) $inst $x) return specState.update hyps p out' pfStep | .goal { kind := .spatial, negate, trivial, frame := f, hyps := hs } g => do let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false - let res ← iFrame bi _ hypsr' out₁ (frameIVars.map (⟨.ipm ·, true⟩)) - let pf'' ← finishFrameSubgoal trivial g res + let pf'' ← finishFrameSubgoal hypsr' out₁ trivial g frameIVars let pfStep := q(specialize_wand_subgoal_step $out₂ $inst $pf' $pf'') return specState.update hypsl' q(false) out₂ pfStep | .goal { kind := .persistent, trivial, frame := f, hyps := hs, .. } g => do @@ -252,8 +255,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q throwError "ispecialize: the subgoal for the persistent premise should not consume hypotheses" let ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ ← synthIntoWandPersistent p out let ⟨_, frameIVars⟩ ← findFrameIVars hyps [] f - let res ← iFrame bi _ hyps out₁' (frameIVars.map (⟨.ipm ·, true⟩)) - let pf' ← finishFrameSubgoal trivial g res + let pf' ← finishFrameSubgoal hyps out₁' trivial g frameIVars let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep | .goal { kind := .modal, negate, trivial, frame := f, hyps := hs, .. } g => @@ -261,8 +263,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps let ⟨out₁, out₂, out₁', inst1, inst2⟩ ← synthIntoWandModal p out goal - let res ← iFrame bi _ hypsr' out₁' (frameIVars.map (⟨.ipm ·, true⟩)) - let pf'' ← finishFrameSubgoal trivial g res + let pf'' ← finishFrameSubgoal hypsr' out₁' trivial g frameIVars let h := q($(pf').mp.trans (sep_mono_right $pf'')) let pfCont := q(fun pf => $pfCont (specialize_modal $h pf $inst1 $inst2)) return { hyps := hypsl', p := q(false), out := out₂, pfCont, pf := none } @@ -274,8 +275,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q return specState.update hyps' q(false) out₂ pfStep | .autoframe .persistent => let ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ ← synthIntoWandPersistent p out - let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) - let pf' ← finishFrameSubgoal true none res + let pf' ← finishFrameSubgoal hyps out₁' true none none let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep | .autoframe .modal => From dc32001ce0b1b2ffa49f3d30b5c4c4b67f9a7c0e Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 19:49:15 +0200 Subject: [PATCH 56/86] Minor formatting --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 74 ++++++++++----------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 39de7e3b8..06ec4f279 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -16,6 +16,42 @@ namespace Iris.ProofMode public section open BI +theorem specialize_wand [BI PROP] {q p : Bool} {A1 A2 A3 Q P1 P2 : PROP} + (inst : IntoWand q p Q .in P1 .out P2) + (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ □?p P1) : + A1 ⊢ A3 ∗ □?(p && q) P2 := by + refine h1.trans <| (sep_mono_left h2).trans <| sep_assoc.mp.trans (sep_mono_right ?_) + cases p with + | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right + | true => exact + (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| + intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) + +theorem specialize_wand_cont [BI PROP] {q p : Bool} + {A1 A2 A3 Q P1 P2 goal : PROP} + (inst : IntoWand q p Q .in P1 .out P2) + (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ A3 ∗ □?p P1) + (h3 : A3 ∗ □?(p && q) P2 ⊢ goal) : + A1 ⊢ goal := by + refine h1 (Entails.trans ?_ h3) + refine (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ?_) + cases p with + | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right + | true => exact + (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| + intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) + +theorem specialize_wand_nested [BI PROP] {q p : Bool} {C B out out₂ goal : PROP} + (inst : IntoWand q p out .in B .out out₂) + (k : C ∗ □?(p && q) out₂ ⊢ goal) : + C ∗ □?p B ⊢ ((□?q out) -∗ goal) := by + refine wand_intro (sep_assoc.mp.trans <| (sep_mono_right ?_).trans k) + cases p with + | false => exact (sep_mono_right inst.into_wand).trans wand_elim_right + | true => exact + (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| + intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) + -- TODO: if q is true and A1 is persistent, this proof can guarantee □ P2 instead of P2 -- see https://gitlab.mpi-sws.org/iris/iris/-/blob/846ed45bed6951035c6204fef365d9a344022ae6/iris/proofmode/coq_tactics.v#L336 theorem specialize_wand_subgoal_step [BI PROP] {q : Bool} {A2 A3 A4 Q P1 : PROP} P2 @@ -56,42 +92,6 @@ theorem specialize_forall_step [BI PROP] {p : Bool} {A2 P : PROP} {α : Sort _} (inst : IntoForall P Φ) (a : α) : A2 ∗ □?p P ⊢ A2 ∗ □?p (Φ a) := sep_mono_right <| intuitionisticallyIf_mono <| inst.into_forall.trans (forall_elim a) -theorem specialize_wand [BI PROP] {q p : Bool} {A1 A2 A3 Q P1 P2 : PROP} - (inst : IntoWand q p Q .in P1 .out P2) - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ □?p P1) : - A1 ⊢ A3 ∗ □?(p && q) P2 := by - refine h1.trans <| (sep_mono_left h2).trans <| sep_assoc.mp.trans (sep_mono_right ?_) - cases p with - | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right - | true => exact - (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| - intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) - -theorem specialize_wand_cont [BI PROP] {q p : Bool} - {A1 A2 A3 Q P1 P2 goal : PROP} - (inst : IntoWand q p Q .in P1 .out P2) - (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ A3 ∗ □?p P1) - (h3 : A3 ∗ □?(p && q) P2 ⊢ goal) : - A1 ⊢ goal := by - refine h1 (Entails.trans ?_ h3) - refine (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ?_) - cases p with - | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right - | true => exact - (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| - intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) - -theorem specialize_wand_nested_cont [BI PROP] {q p : Bool} {C B out out₂ goal : PROP} - (inst : IntoWand q p out .in B .out out₂) - (k : C ∗ □?(p && q) out₂ ⊢ goal) : - C ∗ □?p B ⊢ ((□?q out) -∗ goal) := by - refine wand_intro (sep_assoc.mp.trans <| (sep_mono_right ?_).trans k) - cases p with - | false => exact (sep_mono_right inst.into_wand).trans wand_elim_right - | true => exact - (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| - intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) - theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} (h : P ∗ □?pa A ⊢ P' ∗ □?pb B) (h2 : pa = true ∨ Affine A) @@ -225,7 +225,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | some pfNest => pure q(specialize_wand_cont $inst $pfCont ($(pf').mp.trans $pfNest)) | none => pure q(fun pf => - $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (specialize_wand_nested_cont $inst pf))))) + $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (specialize_wand_nested $inst pf))))) let pf := pfNest.bind (fun pfNest => pf.bind (fun pf => some q(specialize_wand $inst $pf ($(pf').mp.trans $pfNest)))) return { hyps := hyps'', p := p2, out := out₂, pfCont, pf } From 2349a047af6bb48860811956b583b1d9c5d4fdde Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 19:52:39 +0200 Subject: [PATCH 57/86] More minor formatting --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 06ec4f279..2e3a6c36b 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -321,10 +321,11 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} | .inr _ => do let .some h ← trySynthInstanceQ q(Affine $A) | return none return some q(.inr $h) - let some af ← af | return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ - return ⟨_, hyps, q(true), B', - q((specialize_dup_context $pf $af).trans), - some q(specialize_dup_context $pf $af)⟩ + match ← af with + | none => return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ + | some af => + return ⟨_, hyps, q(true), B', q((specialize_dup_context $pf $af).trans), + some q(specialize_dup_context $pf $af)⟩ | _, _ => return ⟨_, hyps', pb, B, pfCont, pf⟩ end From f07cd6238e152801579a1e9424486dab1df28d1e Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 20:02:36 +0200 Subject: [PATCH 58/86] Refactor `iSpecializeCore` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 2e3a6c36b..129e9ba73 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -313,17 +313,15 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} | true, some pf => -- context duplication succeeds if `B` is persistent, and `A` is persistent or affine let B' : Q($prop) ← mkFreshExprMVarQ q($prop) - let .some _ ← ProofModeM.trySynthInstanceQ q(IntoPersistently $pb $B $B') - | return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ - have af : MetaM (Option Q($pa = true ∨ Affine $A)) := - match matchBool pa with - | .inl _ => return some q(.inl (.refl _)) - | .inr _ => do - let .some h ← trySynthInstanceQ q(Affine $A) | return none - return some q(.inr $h) - match ← af with - | none => return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ - | some af => + let af ← do match matchBool pa with + | .inl _ => pure <| some q(Or.inl (.refl $pa)) + | .inr _ => do + let .some h ← trySynthInstanceQ q(Affine $A) | pure none + pure <| some q(Or.inr (a := $pa = true) $h) + let inst ← ProofModeM.trySynthInstanceQ q(IntoPersistently $pb $B $B') + match inst, af with + | none, _ | _, none => return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ + | some _, some af => return ⟨_, hyps, q(true), B', q((specialize_dup_context $pf $af).trans), some q(specialize_dup_context $pf $af)⟩ | _, _ => return ⟨_, hyps', pb, B, pfCont, pf⟩ From 561b11c8d91ff626f665107e14d8efb1e521c0f8 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 21:31:24 +0200 Subject: [PATCH 59/86] Consolidate three theorems into one --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 47 +++++---------------- 1 file changed, 10 insertions(+), 37 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 129e9ba73..5f1e2acb0 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -16,36 +16,10 @@ namespace Iris.ProofMode public section open BI -theorem specialize_wand [BI PROP] {q p : Bool} {A1 A2 A3 Q P1 P2 : PROP} - (inst : IntoWand q p Q .in P1 .out P2) - (h1 : A1 ⊢ A2 ∗ □?q Q) (h2 : A2 ⊢ A3 ∗ □?p P1) : - A1 ⊢ A3 ∗ □?(p && q) P2 := by - refine h1.trans <| (sep_mono_left h2).trans <| sep_assoc.mp.trans (sep_mono_right ?_) - cases p with - | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right - | true => exact - (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| - intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) - -theorem specialize_wand_cont [BI PROP] {q p : Bool} - {A1 A2 A3 Q P1 P2 goal : PROP} - (inst : IntoWand q p Q .in P1 .out P2) - (h1 : (A2 ∗ □?q Q ⊢ goal) → A1 ⊢ goal) (h2 : A2 ⊢ A3 ∗ □?p P1) - (h3 : A3 ∗ □?(p && q) P2 ⊢ goal) : - A1 ⊢ goal := by - refine h1 (Entails.trans ?_ h3) - refine (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ?_) - cases p with - | false => exact (sep_mono_right inst.into_wand).trans <| wand_elim_right - | true => exact - (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| - intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) - -theorem specialize_wand_nested [BI PROP] {q p : Bool} {C B out out₂ goal : PROP} - (inst : IntoWand q p out .in B .out out₂) - (k : C ∗ □?(p && q) out₂ ⊢ goal) : - C ∗ □?p B ⊢ ((□?q out) -∗ goal) := by - refine wand_intro (sep_assoc.mp.trans <| (sep_mono_right ?_).trans k) +theorem specialize_wand_step [BI PROP] {q p : Bool} {A Q P1 P2 : PROP} + (inst : IntoWand q p Q .in P1 .out P2) : + (A ∗ □?p P1) ∗ □?q Q ⊢ A ∗ □?(p && q) P2 := by + refine sep_assoc.mp.trans (sep_mono_right ?_) cases p with | false => exact (sep_mono_right inst.into_wand).trans wand_elim_right | true => exact @@ -221,14 +195,13 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let out₂ ← mkFreshExprMVarQ prop let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) | throwError m!"ispecialize: cannot instantiate {out} with {B}" - let pfCont ← do match pfNest with + match pfNest with + | none => + let pfCont := q(fun pf => $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (wand_intro ((specialize_wand_step $inst).trans pf)))))) + return { hyps := hyps'', p := p2, out := out₂, pfCont, pf := none } | some pfNest => - pure q(specialize_wand_cont $inst $pfCont ($(pf').mp.trans $pfNest)) - | none => pure q(fun pf => - $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (specialize_wand_nested $inst pf))))) - let pf := pfNest.bind (fun pfNest => - pf.bind (fun pf => some q(specialize_wand $inst $pf ($(pf').mp.trans $pfNest)))) - return { hyps := hyps'', p := p2, out := out₂, pfCont, pf } + let pfStep := q((sep_mono_left ($(pf').mp.trans $pfNest)).trans (specialize_wand_step $inst)) + return specState.update hyps'' p2 out₂ pfStep | .pure t => do let v ← mkFreshLevelMVar let α : Q(Sort v) ← mkFreshExprMVarQ q(Sort v) From 81ef1830aff205a5970ac4b1a6baacada00b2fbc Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 21:34:21 +0200 Subject: [PATCH 60/86] Rename theorems --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 5f1e2acb0..ae94d16c3 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -16,7 +16,7 @@ namespace Iris.ProofMode public section open BI -theorem specialize_wand_step [BI PROP] {q p : Bool} {A Q P1 P2 : PROP} +theorem specialize_wand [BI PROP] {q p : Bool} {A Q P1 P2 : PROP} (inst : IntoWand q p Q .in P1 .out P2) : (A ∗ □?p P1) ∗ □?q Q ⊢ A ∗ □?(p && q) P2 := by refine sep_assoc.mp.trans (sep_mono_right ?_) @@ -28,20 +28,20 @@ theorem specialize_wand_step [BI PROP] {q p : Bool} {A Q P1 P2 : PROP} -- TODO: if q is true and A1 is persistent, this proof can guarantee □ P2 instead of P2 -- see https://gitlab.mpi-sws.org/iris/iris/-/blob/846ed45bed6951035c6204fef365d9a344022ae6/iris/proofmode/coq_tactics.v#L336 -theorem specialize_wand_subgoal_step [BI PROP] {q : Bool} {A2 A3 A4 Q P1 : PROP} P2 +theorem specialize_wand_subgoal [BI PROP] {q : Bool} {A2 A3 A4 Q P1 : PROP} P2 (inst : IntoWand q false Q .out P1 .out P2) (h2 : A2 ⊣⊢ A3 ∗ A4) (h3 : A4 ⊢ P1) : A2 ∗ □?q Q ⊢ A3 ∗ P2 := by refine (sep_mono_left h2.1).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_left h3).trans ?_)) exact (sep_mono_right inst.1).trans wand_elim_right -theorem specialize_wand_autoframe_spatial_step [BI PROP] {q : Bool} {A2 A3 Q P1 : PROP} P2 +theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} {A2 A3 Q P1 : PROP} P2 (inst : IntoWand q false Q .out P1 .out P2) (h2 : A2 ⊢ A3 ∗ P1) : A2 ∗ □?q Q ⊢ A3 ∗ P2 := (sep_mono_left h2).trans <| sep_assoc.1.trans (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) -theorem specialize_wand_persistent_step [BI PROP] {q : Bool} {A2 Q P1' : PROP} P1 P2 +theorem specialize_wand_persistent [BI PROP] {q : Bool} {A2 Q P1' : PROP} P1 P2 (inst3 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) (inst1 : IntoAbsorbingly P1' P1) (h2 : A2 ⊢ P1') : A2 ∗ □?q Q ⊢ A2 ∗ □?q P2 := by @@ -62,7 +62,7 @@ theorem specialize_wand_persistent_step [BI PROP] {q : Bool} {A2 Q P1' : PROP} P _ ⊢ A2 ∗ □ P1 ∗ □?q Q := sep_assoc.mp _ ⊢ A2 ∗ □?q P2 := sep_mono_right h4 -theorem specialize_forall_step [BI PROP] {p : Bool} {A2 P : PROP} {α : Sort _} {Φ : α → PROP} +theorem specialize_forall [BI PROP] {p : Bool} {A2 P : PROP} {α : Sort _} {Φ : α → PROP} (inst : IntoForall P Φ) (a : α) : A2 ∗ □?p P ⊢ A2 ∗ □?p (Φ a) := sep_mono_right <| intuitionisticallyIf_mono <| inst.into_forall.trans (forall_elim a) @@ -197,10 +197,10 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | throwError m!"ispecialize: cannot instantiate {out} with {B}" match pfNest with | none => - let pfCont := q(fun pf => $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (wand_intro ((specialize_wand_step $inst).trans pf)))))) + let pfCont := q(fun pf => $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (wand_intro ((specialize_wand $inst).trans pf)))))) return { hyps := hyps'', p := p2, out := out₂, pfCont, pf := none } | some pfNest => - let pfStep := q((sep_mono_left ($(pf').mp.trans $pfNest)).trans (specialize_wand_step $inst)) + let pfStep := q((sep_mono_left ($(pf').mp.trans $pfNest)).trans (specialize_wand $inst)) return specState.update hyps'' p2 out₂ pfStep | .pure t => do let v ← mkFreshLevelMVar @@ -213,7 +213,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let newMVarIds ← getMVarsNoDelayed x for mvar in newMVarIds do addMVarGoal mvar let pfStep : Q($e ∗ □?$p $out ⊢ $e ∗ □?$p $Φ $x) := - q(specialize_forall_step (A2 := $e) (p := $p) $inst $x) + q(specialize_forall (A2 := $e) (p := $p) $inst $x) return specState.update hyps p out' pfStep | .goal { kind := .spatial, negate, trivial, frame := f, hyps := hs } g => do let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f @@ -221,7 +221,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false let pf'' ← finishFrameSubgoal hypsr' out₁ trivial g frameIVars - let pfStep := q(specialize_wand_subgoal_step $out₂ $inst $pf' $pf'') + let pfStep := q(specialize_wand_subgoal $out₂ $inst $pf' $pf'') return specState.update hypsl' q(false) out₂ pfStep | .goal { kind := .persistent, trivial, frame := f, hyps := hs, .. } g => do if !hs.isEmpty then @@ -229,7 +229,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ ← synthIntoWandPersistent p out let ⟨_, frameIVars⟩ ← findFrameIVars hyps [] f let pf' ← finishFrameSubgoal hyps out₁' trivial g frameIVars - let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') + let pfStep := q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep | .goal { kind := .modal, negate, trivial, frame := f, hyps := hs, .. } g => let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f @@ -244,12 +244,12 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose - let pfStep := q(specialize_wand_autoframe_spatial_step $out₂ $inst $pf') + let pfStep := q(specialize_wand_autoframe_spatial $out₂ $inst $pf') return specState.update hyps' q(false) out₂ pfStep | .autoframe .persistent => let ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ ← synthIntoWandPersistent p out let pf' ← finishFrameSubgoal hyps out₁' true none none - let pfStep := q(specialize_wand_persistent_step $out₁ $out₂ $inst1 $inst2 $inst3 $pf') + let pfStep := q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep | .autoframe .modal => let ⟨out₁, out₂, out₁', inst1, inst2⟩ ← synthIntoWandModal p out goal From e841bb122b1477ba0b9fcff86fa68b0b38a886ad Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 21:36:56 +0200 Subject: [PATCH 61/86] Slight optimisations in `processWand` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index ae94d16c3..02f9ea0d3 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -190,7 +190,12 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar let ⟨_, hyps'', pB, B, pfContNest, pfNest⟩ ← - iSpecializeCore hyps' p1 out₁' q(iprop(□?$p $out -∗ $goal)) spats + if spats.isEmpty then + -- No nested specialisation patterns + pure ⟨_, hyps', p1, out₁', q(id), some q(.rfl)⟩ + else + -- There are nested specialisation patterns, requires recursive calls + iSpecializeCore hyps' p1 out₁' q(iprop(□?$p $out -∗ $goal)) spats let p2 := if pB.constName! == ``true then p else q(false) let out₂ ← mkFreshExprMVarQ prop let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) From 364e5eacf59421c1c863b40ed827d1094e27f56d Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 21:40:08 +0200 Subject: [PATCH 62/86] Add some comments --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 02f9ea0d3..afa4e2f71 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -98,6 +98,7 @@ structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig goal : Q($ (pfCont : Q(($e ∗ □?$p $out ⊢ $goal) → $orig ⊢ $goal)) pf : Option Q($orig ⊢ $e ∗ □?$p $out) +/-- Used in all cases in `processWand` except those involving the `.modal` kind -/ private def SpecializeState.update {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q($prop)} (st : @SpecializeState u prop bi orig goal) {e' : Q($prop)} (hyps' : Hyps bi e') (p' : Q(Bool)) (out' : Q($prop)) @@ -107,6 +108,7 @@ private def SpecializeState.update {u} {prop : Q(Type u)} {bi : Q(BI $prop)} pfCont := q(fun pf => $(st.pfCont) ($(pfCont).trans pf)), pf := st.pf.map (fun pf => q($(pf).trans $pfCont)) } +/-- Used by all `.goal` cases in `processWand`. -/ private def findFrameIVars {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : Hyps bi e) (hs : List Ident) (f : List Ident) : ProofModeM <| IVarIdSet × List IVarId := do @@ -121,6 +123,7 @@ private def findFrameIVars {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} throwError "ispecialize: {name} used twice" return ⟨ivars, frameIVars.reverse⟩ +/-- Used by all `.goal` cases and the `.autoframe persistent` case in `processWand`. -/ private def finishFrameSubgoal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : Hyps bi e) (goal : Q($prop)) (trivial : Bool) (g : Option Name) (frameIVars : Option <| List IVarId) : ProofModeM Q($e ⊢ $goal) := do @@ -146,6 +149,7 @@ private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} | throwError m!"ispecialize: {out} is not a wand" return ⟨out₁, out₂, inst⟩ +/-- Used by the cases `.autoframe` and `.goal` in `processWand` with the `.persistent` kind. -/ private def synthIntoWandPersistent {u} {prop : Q(Type u)} {bi : Q(BI $prop)} (p : Q(Bool)) (out : Q($prop)) : ProofModeM ((out₁ : Q($prop)) × (out₂ : Q($prop)) × (out₁' : Q($prop)) × @@ -163,6 +167,7 @@ private def synthIntoWandPersistent {u} {prop : Q(Type u)} {bi : Q(BI $prop)} | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" pure ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ +/-- Used by the cases `.autoframe` and `.goal` in `processWand` with the `.modal` kind. -/ private def synthIntoWandModal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} (p : Q(Bool)) (out goal : Q($prop)) : ProofModeM ((out₁ : Q($prop)) × (out₂ : Q($prop)) × (out₁' : Q($prop)) × From 84267229cb4908ec1ff64ccaa672d6006f2925ca Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 21:58:37 +0200 Subject: [PATCH 63/86] Add comments and reuse `synthIntoWand` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 29 ++++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index afa4e2f71..9d220327e 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -147,6 +147,8 @@ private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} let out₂ ← mkFreshExprMVarQ prop let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $persistent $out .out $out₁ .out $out₂) | throwError m!"ispecialize: {out} is not a wand" + let out₁ : Q($prop) ← instantiateMVars out₁ + let out₂ : Q($prop) ← instantiateMVars out₂ return ⟨out₁, out₂, inst⟩ /-- Used by the cases `.autoframe` and `.goal` in `processWand` with the `.persistent` kind. -/ @@ -156,16 +158,14 @@ private def synthIntoWandPersistent {u} {prop : Q(Type u)} {bi : Q(BI $prop)} Q(IntoWand $p true $out .out $out₁ .out $out₂) × Q(Persistent $out₁) × Q(IntoAbsorbingly $out₁' $out₁)) := do - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p true $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" + let ⟨out₁, out₂, inst1⟩ : (out₁ : Q($prop)) × (out₂ : Q($prop)) × + Q(IntoWand $p true $out .out $out₁ .out $out₂) ← @synthIntoWand u prop bi p out true let some inst2 ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) | throwError m!"ispecialize: {out₁} is not persistent" let out₁' ← mkFreshExprMVarQ prop let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" - pure ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ + return ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ /-- Used by the cases `.autoframe` and `.goal` in `processWand` with the `.modal` kind. -/ private def synthIntoWandModal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} @@ -173,10 +173,8 @@ private def synthIntoWandModal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} ProofModeM ((out₁ : Q($prop)) × (out₂ : Q($prop)) × (out₁' : Q($prop)) × Q(IntoWand $p false $out .out $out₁ .out $out₂) × Q(AddModal $out₁' $out₁ $goal)) := do - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some inst1 ← ProofModeM.trySynthInstanceQ q(IntoWand $p false $out .out $out₁ .out $out₂) - | throwError m!"ispecialize: {out} is not a wand" + let ⟨out₁, out₂, inst1⟩ : (out₁ : Q($prop)) × (out₂ : Q($prop)) × + Q(IntoWand $p false $out .out $out₁ .out $out₂) ← @synthIntoWand u prop bi p out false let out₁' ← mkFreshExprMVarQ prop let some inst2 ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" @@ -191,6 +189,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let ⟨ref, spat⟩ := spat withRef ref do match spat with + -- A hypothesis name, possibly with nested specialisation patterns | .ident i spats => let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar @@ -212,6 +211,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | some pfNest => let pfStep := q((sep_mono_left ($(pf').mp.trans $pfNest)).trans (specialize_wand $inst)) return specState.update hyps'' p2 out₂ pfStep + -- A pure Lean hypothesis | .pure t => do let v ← mkFreshLevelMVar let α : Q(Sort v) ← mkFreshExprMVarQ q(Sort v) @@ -225,6 +225,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pfStep : Q($e ∗ □?$p $out ⊢ $e ∗ □?$p $Φ $x) := q(specialize_forall (A2 := $e) (p := $p) $inst $x) return specState.update hyps p out' pfStep + -- Subgoal with `[ H₁ … Hₙ ]` or `[- H₁ … Hₙ ]` | .goal { kind := .spatial, negate, trivial, frame := f, hyps := hs } g => do let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi @@ -233,6 +234,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pf'' ← finishFrameSubgoal hypsr' out₁ trivial g frameIVars let pfStep := q(specialize_wand_subgoal $out₂ $inst $pf' $pf'') return specState.update hypsl' q(false) out₂ pfStep + -- Subgoal with `[# H₁ … Hₙ ]` or `[#- H₁ … Hₙ ]` | .goal { kind := .persistent, trivial, frame := f, hyps := hs, .. } g => do if !hs.isEmpty then throwError "ispecialize: the subgoal for the persistent premise should not consume hypotheses" @@ -241,6 +243,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let pf' ← finishFrameSubgoal hyps out₁' trivial g frameIVars let pfStep := q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep + -- Subgoal with `[> H₁ … Hₙ ]` or `[>- H₁ … Hₙ ]` | .goal { kind := .modal, negate, trivial, frame := f, hyps := hs, .. } g => let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi @@ -250,17 +253,20 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let h := q($(pf').mp.trans (sep_mono_right $pf'')) let pfCont := q(fun pf => $pfCont (specialize_modal $h pf $inst1 $inst2)) return { hyps := hypsl', p := q(false), out := out₂, pfCont, pf := none } + -- Auto-framing with `[$]` | .autoframe .spatial => do let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose let pfStep := q(specialize_wand_autoframe_spatial $out₂ $inst $pf') return specState.update hyps' q(false) out₂ pfStep + -- Auto-framing with `[#$]` | .autoframe .persistent => let ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ ← synthIntoWandPersistent p out let pf' ← finishFrameSubgoal hyps out₁' true none none let pfStep := q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf') return specState.update hyps p out₂ pfStep + -- Auto-framing with `[>$]` | .autoframe .modal => let ⟨out₁, out₂, out₁', inst1, inst2⟩ ← synthIntoWandModal p out goal let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) @@ -294,7 +300,7 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} let ⟨hyps', pb, B, pfCont, pf⟩ ← spats.foldlM processWand state match try_dup_context, pf with | true, some pf => - -- context duplication succeeds if `B` is persistent, and `A` is persistent or affine + -- Duplicate context if `B` is persistent and `A` is persistent/affine let B' : Q($prop) ← mkFreshExprMVarQ q($prop) let af ← do match matchBool pa with | .inl _ => pure <| some q(Or.inl (.refl $pa)) @@ -303,10 +309,13 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} pure <| some q(Or.inr (a := $pa = true) $h) let inst ← ProofModeM.trySynthInstanceQ q(IntoPersistently $pb $B $B') match inst, af with + -- Context duplication does not succeed | none, _ | _, none => return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ + -- Context duplication succeeds | some _, some af => return ⟨_, hyps, q(true), B', q((specialize_dup_context $pf $af).trans), some q(specialize_dup_context $pf $af)⟩ + -- No request to duplicate the context, or the `.modal` kind is involved | _, _ => return ⟨_, hyps', pb, B, pfCont, pf⟩ end From 10e8a59512c78d513b2d67a32a035d7783c41c25 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 22:00:56 +0200 Subject: [PATCH 64/86] Code refactoring: `splitFrameHyps` --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 9d220327e..7e9e2a095 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -123,6 +123,16 @@ private def findFrameIVars {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} throwError "ispecialize: {name} used twice" return ⟨ivars, frameIVars.reverse⟩ +/-- Used by the `.goal` cases with the `.spatial` or `.modal` kind. -/ +private def splitFrameHyps {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} + (hyps : Hyps bi e) (hs f : List Ident) (negate : Bool) : + ProofModeM <| (el : Q($prop)) × (er : Q($prop)) × + Hyps bi el × Hyps bi er × Q($e ⊣⊢ $el ∗ $er) × List IVarId := do + let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f + let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi + (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps + return ⟨_, _, hypsl', hypsr', pf', frameIVars⟩ + /-- Used by all `.goal` cases and the `.autoframe persistent` case in `processWand`. -/ private def finishFrameSubgoal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : Hyps bi e) (goal : Q($prop)) (trivial : Bool) (g : Option Name) @@ -227,9 +237,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q return specState.update hyps p out' pfStep -- Subgoal with `[ H₁ … Hₙ ]` or `[- H₁ … Hₙ ]` | .goal { kind := .spatial, negate, trivial, frame := f, hyps := hs } g => do - let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f - let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi - (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps + let ⟨_, _, hypsl', hypsr', pf', frameIVars⟩ ← splitFrameHyps hyps hs f negate let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false let pf'' ← finishFrameSubgoal hypsr' out₁ trivial g frameIVars let pfStep := q(specialize_wand_subgoal $out₂ $inst $pf' $pf'') @@ -245,9 +253,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q return specState.update hyps p out₂ pfStep -- Subgoal with `[> H₁ … Hₙ ]` or `[>- H₁ … Hₙ ]` | .goal { kind := .modal, negate, trivial, frame := f, hyps := hs, .. } g => - let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f - let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi - (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps + let ⟨_, _, hypsl', hypsr', pf', frameIVars⟩ ← splitFrameHyps hyps hs f negate let ⟨out₁, out₂, out₁', inst1, inst2⟩ ← synthIntoWandModal p out goal let pf'' ← finishFrameSubgoal hypsr' out₁' trivial g frameIVars let h := q($(pf').mp.trans (sep_mono_right $pf'')) From 8bfda62833ac2da48e3dcd72a664fee308095e8c Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Tue, 7 Jul 2026 22:12:52 +0200 Subject: [PATCH 65/86] Condense code, more comments --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 7e9e2a095..49cb199ca 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -95,7 +95,9 @@ open Lean Elab Tactic Meta Qq Std structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig goal : Q($prop)) where {e : Q($prop)} (hyps : Hyps bi e) (p : Q(Bool)) (out : Q($prop)) + -- A weaker proposition than `pf`, applicable even when the `.modal` kind is involved (pfCont : Q(($e ∗ □?$p $out ⊢ $goal) → $orig ⊢ $goal)) + -- Holds when the `.modal` kind is not involved, cannot duplicate context if `pf = none` pf : Option Q($orig ⊢ $e ∗ □?$p $out) /-- Used in all cases in `processWand` except those involving the `.modal` kind -/ @@ -110,17 +112,16 @@ private def SpecializeState.update {u} {prop : Q(Type u)} {bi : Q(BI $prop)} /-- Used by all `.goal` cases in `processWand`. -/ private def findFrameIVars {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} - (hyps : Hyps bi e) (hs : List Ident) (f : List Ident) : - ProofModeM <| IVarIdSet × List IVarId := do - let mut ivars : IVarIdSet := {} - for name in hs do - ivars := ivars.insert (← hyps.findWithInfo name) + (hyps : Hyps bi e) (hs f : List Ident) : ProofModeM <| IVarIdSet × List IVarId := do + -- Hypotheses to be included in the subgoal + let ivars ← hs.foldlM (return ·.insert <| ← hyps.findWithInfo ·) {} + -- Hypotheses to be framed let mut frameIVars : List IVarId := [] for name in f do let ivar ← hyps.findWithInfo name - frameIVars := ivar :: frameIVars if ivars.contains ivar then throwError "ispecialize: {name} used twice" + frameIVars := ivar :: frameIVars return ⟨ivars, frameIVars.reverse⟩ /-- Used by the `.goal` cases with the `.spatial` or `.modal` kind. -/ @@ -216,7 +217,8 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | throwError m!"ispecialize: cannot instantiate {out} with {B}" match pfNest with | none => - let pfCont := q(fun pf => $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (wand_intro ((specialize_wand $inst).trans pf)))))) + let pfCont := q(fun pf => $pfCont (wand_elim_left_trans + ($(pf').mp.trans ($pfContNest (wand_intro ((specialize_wand $inst).trans pf)))))) return { hyps := hyps'', p := p2, out := out₂, pfCont, pf := none } | some pfNest => let pfStep := q((sep_mono_left ($(pf').mp.trans $pfNest)).trans (specialize_wand $inst)) @@ -228,7 +230,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let Φ : Q($α → $prop) ← mkFreshExprMVarQ q($α → $prop) let some inst ← ProofModeM.trySynthInstanceQ q(IntoForall $out $Φ) | throwError "ispecialize: {out} is not a Lean premise" - let x ← elabTermEnsuringTypeQ (u := .succ .zero) t α + let x ← elabTermEnsuringTypeQ t α have out' : Q($prop) := Expr.headBeta q($Φ $x) let newMVarIds ← getMVarsNoDelayed x for mvar in newMVarIds do addMVarGoal mvar @@ -302,7 +304,8 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} ProofModeM ((e' : _) × Hyps bi e' × (pb : Q(Bool)) × (B : Q($prop)) × Q(($e' ∗ □?$pb $B ⊢ $goal) → $e ∗ □?$pa $A ⊢ $goal) × Option Q($e ∗ □?$pa $A ⊢ $e' ∗ □?$pb $B)) := do - let state : SpecializeState _ goal := { hyps, out := A, p := pa, pfCont := q(id), pf := some q(.rfl) } + let state : SpecializeState _ goal := + { hyps, out := A, p := pa, pfCont := q(id), pf := some q(.rfl) } let ⟨hyps', pb, B, pfCont, pf⟩ ← spats.foldlM processWand state match try_dup_context, pf with | true, some pf => From e54e73d2dc7ac841be8fef49c4c739a48af1e786 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 10:21:29 +0200 Subject: [PATCH 66/86] Move `pmTerm` and its parser into `SpecPattern.lean` for mutual induction --- Iris/Iris/ProofMode/Patterns.lean | 1 - .../ProofMode/Patterns/ProofModeTerm.lean | 39 ---------------- Iris/Iris/ProofMode/Patterns/SpecPattern.lean | 44 +++++++++++++++---- Iris/Iris/ProofMode/Tactics/Apply.lean | 2 +- Iris/Iris/ProofMode/Tactics/Cases.lean | 2 +- Iris/Iris/ProofMode/Tactics/HaveCore.lean | 2 +- Iris/Iris/ProofMode/Tactics/Rewrite.lean | 2 +- Iris/Iris/ProofMode/Tactics/Specialize.lean | 2 +- 8 files changed, 41 insertions(+), 53 deletions(-) delete mode 100644 Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean diff --git a/Iris/Iris/ProofMode/Patterns.lean b/Iris/Iris/ProofMode/Patterns.lean index a204abd27..f49a02676 100644 --- a/Iris/Iris/ProofMode/Patterns.lean +++ b/Iris/Iris/ProofMode/Patterns.lean @@ -2,6 +2,5 @@ module public import Iris.ProofMode.Patterns.CasesPattern public import Iris.ProofMode.Patterns.IntroPattern -public import Iris.ProofMode.Patterns.ProofModeTerm public import Iris.ProofMode.Patterns.SelPattern public import Iris.ProofMode.Patterns.SpecPattern diff --git a/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean b/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean deleted file mode 100644 index 40257b1a5..000000000 --- a/Iris/Iris/ProofMode/Patterns/ProofModeTerm.lean +++ /dev/null @@ -1,39 +0,0 @@ -/- -Copyright (c) 2025 Oliver Soeser. All rights reserved. -Released under Apache 2.0 license as described in the file LICENSE. -Authors: Oliver Soeser, Zongyuan Liu --/ -module - -public import Iris.ProofMode.Patterns.SpecPattern -meta import Iris.Std.RocqPorting - -@[expose] public section - -namespace Iris.ProofMode -open Lean - -declare_syntax_cat pmTerm -/-- - The proof mode term `H $$ spat₁ … spatₙ` refers to `H`, a hypothesis or a - Lean term whose conclusion is an entailment, with the specialisation patterns - `spat₁ … spatₙ` applied to its premises. --/ -syntax term (colGt " $$ " (colGt ppSpace specPat)+)? : pmTerm - -@[rocq_alias iTrm] -structure PMTerm where - term : Term - spats : List (Syntax × SpecPat) - deriving Repr, Inhabited - -partial def PMTerm.parse (term : Syntax) : MacroM PMTerm := do - match ← expandMacros term with - | `(pmTerm| $trm:term) => return ⟨trm, []⟩ - | `(pmTerm| $trm:term $$ $[$spats:specPat]*) => return ⟨trm, ← parseSpats spats⟩ - | _ => Macro.throwUnsupported -where - parseSpats (spats : TSyntaxArray `specPat) : MacroM (List (Syntax × SpecPat)) := - return (← spats.toList.mapM fun pat => SpecPat.parse pat.raw) - -def PMTerm.is_nontrivial (pmt : PMTerm) : Bool := !pmt.spats.isEmpty diff --git a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean index 9bed4ff26..2b5a65ad0 100644 --- a/Iris/Iris/ProofMode/Patterns/SpecPattern.lean +++ b/Iris/Iris/ProofMode/Patterns/SpecPattern.lean @@ -20,6 +20,7 @@ syntax ident : frameIdent -- syntax "$" ident : frameIdent declare_syntax_cat specPat +declare_syntax_cat pmTerm syntax ident : specPat /-- Use a proof or a tactic sequence to match a pure hypothesis in the premise. -/ @@ -61,7 +62,13 @@ syntax "[#" "$" "]" : specPat patterns `spat₁ … spatₙ` before the resultant hypothesis is itself used for matching a premise. -/ -syntax "(" colGt ident colGt " $$ " (colGt ppSpace specPat)+ ")" : specPat +syntax "(" pmTerm ")" : specPat +/-- + The proof mode term `H $$ spat₁ … spatₙ` refers to `H`, a hypothesis or a + Lean term whose conclusion is an entailment, with the specialisation patterns + `spat₁ … spatₙ` applied to its premises. +-/ +syntax term (colGt " $$ " (colGt ppSpace specPat)+)? : pmTerm @[rocq_alias goal_kind] inductive SpecGoalKind @@ -79,7 +86,6 @@ structure SpecGoal where hyps : List Ident deriving Repr, Inhabited --- see https://gitlab.mpi-sws.org/iris/iris/-/blob/master/iris/proofmode/spec_patterns.v?ref_type=heads#L15 @[rocq_alias spec_pat] inductive SpecPat | ident (name : Ident) (recursiveSpecPats : List <| Syntax × SpecPat) @@ -107,23 +113,34 @@ def SpecPat.isModal : SpecPat → Bool def FrameIdent.parse : TSyntax `frameIdent → (Ident ⊕ Ident) | `(frameIdent| $name:ident) => .inl name | e => - -- Antiquotations start with $, so if we find one, it is a framing assumption + -- Antiquotations start with `$`, so if we find one, it is a framing assumption if e.raw.isAntiquot then .inr ⟨e.raw.getAntiquotTerm⟩ else .inl default -- should not happen +@[rocq_alias iTrm] +structure PMTerm where + term : Term + spats : List (Syntax × SpecPat) + deriving Repr, Inhabited + +def PMTerm.is_nontrivial (pmt : PMTerm) : Bool := !pmt.spats.isEmpty + +mutual + @[rocq_alias spec_pat.parse] partial def SpecPat.parse (term : Syntax) : MacroM (Syntax × SpecPat) := do let stx ← expandMacros term match stx with - -- Recursive call for nested specialisation patterns - | `(specPat| ($name:ident $$ $[$spats:specPat]*)) => - let nested ← spats.toList.mapM (SpecPat.parse ·.raw) - return ⟨term, .ident name nested⟩ + -- Recursive parsing for nested specialisation patterns + | `(specPat| ( $pmt:pmTerm )) => + let pmt ← PMTerm.parse pmt + return ⟨term, .ident ⟨pmt.term⟩ pmt.spats⟩ + -- No nested specialisation pattern involved | _ => match go ⟨stx⟩ with - | none => Macro.throwUnsupported | some pat => return ⟨term, pat⟩ + | none => Macro.throwUnsupported where go : TSyntax `specPat → Option SpecPat | `(specPat| $name:ident) => some <| .ident name [] @@ -141,3 +158,14 @@ where | `(specPat| [# $]) => some <| .autoframe .persistent | `(specPat| [> $]) => some <| .autoframe .modal | _ => none + +partial def PMTerm.parse (term : Syntax) : MacroM PMTerm := do + match ← expandMacros term with + | `(pmTerm| $trm:term) => return ⟨trm, []⟩ + | `(pmTerm| $trm:term $$ $[$spats:specPat]*) => return ⟨trm, ← parseSpats spats⟩ + | _ => Macro.throwUnsupported +where + parseSpats (spats : TSyntaxArray `specPat) : MacroM (List (Syntax × SpecPat)) := + return (← spats.toList.mapM fun pat => SpecPat.parse pat.raw) + +end diff --git a/Iris/Iris/ProofMode/Tactics/Apply.lean b/Iris/Iris/ProofMode/Tactics/Apply.lean index 4aa42b57c..24d1d79ea 100644 --- a/Iris/Iris/ProofMode/Tactics/Apply.lean +++ b/Iris/Iris/ProofMode/Tactics/Apply.lean @@ -7,7 +7,7 @@ module import Iris.BI import Iris.ProofMode.Classes -meta import Iris.ProofMode.Patterns.ProofModeTerm +meta import Iris.ProofMode.Patterns.SpecPattern meta import Iris.ProofMode.Tactics.Assumption public meta import Iris.ProofMode.Tactics.HaveCore diff --git a/Iris/Iris/ProofMode/Tactics/Cases.lean b/Iris/Iris/ProofMode/Tactics/Cases.lean index c00dd6c98..f5c65c6da 100644 --- a/Iris/Iris/ProofMode/Tactics/Cases.lean +++ b/Iris/Iris/ProofMode/Tactics/Cases.lean @@ -5,7 +5,7 @@ Authors: Lars König, Mario Carneiro, Michael Sammler, Yunsong Yang -/ module -meta import Iris.ProofMode.Patterns.ProofModeTerm +meta import Iris.ProofMode.Patterns.SpecPattern meta import Iris.ProofMode.Patterns.CasesPattern public meta import Iris.ProofMode.Tactics.Mod public meta import Iris.ProofMode.Tactics.Pure diff --git a/Iris/Iris/ProofMode/Tactics/HaveCore.lean b/Iris/Iris/ProofMode/Tactics/HaveCore.lean index 4f1b3d5aa..325c91e1d 100644 --- a/Iris/Iris/ProofMode/Tactics/HaveCore.lean +++ b/Iris/Iris/ProofMode/Tactics/HaveCore.lean @@ -7,7 +7,7 @@ module import Iris.BI import Iris.ProofMode.Classes -public meta import Iris.ProofMode.Patterns.ProofModeTerm +public meta import Iris.ProofMode.Patterns.SpecPattern public meta import Iris.ProofMode.Tactics.Basic public meta import Iris.ProofMode.Tactics.Specialize diff --git a/Iris/Iris/ProofMode/Tactics/Rewrite.lean b/Iris/Iris/ProofMode/Tactics/Rewrite.lean index 77511c444..d5b61c6b1 100644 --- a/Iris/Iris/ProofMode/Tactics/Rewrite.lean +++ b/Iris/Iris/ProofMode/Tactics/Rewrite.lean @@ -9,7 +9,7 @@ public import Iris.BI.InternalEq public import Iris.ProofMode.Classes public import Iris.Std.TC public import Iris.ProofMode.ProofModeM -public meta import Iris.ProofMode.Patterns.ProofModeTerm +public meta import Iris.ProofMode.Patterns.SpecPattern public meta import Iris.ProofMode.Tactics.HaveCore meta import Lean.Parser.Tactic diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 49cb199ca..2cc615691 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -5,7 +5,7 @@ Authors: Lars König, Mario Carneiro, Michael Sammler, Alvin Tang -/ module -public meta import Iris.ProofMode.Patterns.ProofModeTerm +public meta import Iris.ProofMode.Patterns.SpecPattern public meta import Iris.ProofMode.Patterns.CasesPattern public meta import Iris.ProofMode.Tactics.Basic public import Iris.ProofMode.Tactics.Trivial From e27f1eb6b952827f4200248e2b6d29f26803275a Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 10:54:19 +0200 Subject: [PATCH 67/86] Add comments --- Iris/Iris/ProofMode/Tactics/Cases.lean | 2 +- Iris/Iris/ProofMode/Tactics/Frame.lean | 6 +- Iris/Iris/ProofMode/Tactics/Specialize.lean | 101 +++++++++++--------- 3 files changed, 61 insertions(+), 48 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Cases.lean b/Iris/Iris/ProofMode/Tactics/Cases.lean index f5c65c6da..81029e18a 100644 --- a/Iris/Iris/ProofMode/Tactics/Cases.lean +++ b/Iris/Iris/ProofMode/Tactics/Cases.lean @@ -215,7 +215,7 @@ partial def iCasesCore {P} (hyps : Hyps bi P) (goal : Q($prop)) (pat : iCasesPat | .frame => do let ⟨ivar, hyps'⟩ ← Hyps.addWithInfo bi (← `(binderIdent | _)) p A hyps - let res ← iFrame bi _ hyps' goal [⟨.ipm ivar, true⟩] + let res ← iFrame bi hyps' goal [⟨.ipm ivar, true⟩] res.finish @k | .conjunction [arg] | .disjunction [arg] => iCasesCore hyps goal arg p A @k diff --git a/Iris/Iris/ProofMode/Tactics/Frame.lean b/Iris/Iris/ProofMode/Tactics/Frame.lean index af5be77c3..ddd9e2b99 100644 --- a/Iris/Iris/ProofMode/Tactics/Frame.lean +++ b/Iris/Iris/ProofMode/Tactics/Frame.lean @@ -86,7 +86,7 @@ private def FrameResult.step {u prop bi origE origGoal} : else return st -def iFrame {prop : Q(Type u)} (bi : Q(BI $prop)) (e : Q($prop)) +def iFrame {prop : Q(Type u)} (bi : Q(BI $prop)) {e : Q($prop)} (hyps : Hyps bi e) (goal : Q($prop)) (sels : List SelTarget) : ProofModeM (FrameResult bi e goal) := do let mut st : FrameResult bi e goal := { progress := false, e, hyps, goal, pf := q(frame_init) } @@ -136,10 +136,10 @@ def FrameResult.finishClose {u prop bi origE origGoal} (res : @FrameResult u pro elab "iframe " pats:(colGt ppSpace selPat)+ : tactic => do let pats ← liftMacroM <| SelPat.parse pats - ProofModeM.runTactic λ mvar { bi, e, hyps, goal, .. } => do + ProofModeM.runTactic λ mvar { bi, hyps, goal, .. } => do let pats ← SelPat.resolve hyps pats - let res ← iFrame bi e hyps goal pats + let res ← iFrame bi hyps goal pats mvar.assign (← res.finish (addBIGoal · ·)) /-- diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 2cc615691..8c44d365e 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -31,50 +31,49 @@ theorem specialize_wand [BI PROP] {q p : Bool} {A Q P1 P2 : PROP} theorem specialize_wand_subgoal [BI PROP] {q : Bool} {A2 A3 A4 Q P1 : PROP} P2 (inst : IntoWand q false Q .out P1 .out P2) (h2 : A2 ⊣⊢ A3 ∗ A4) (h3 : A4 ⊢ P1) : A2 ∗ □?q Q ⊢ A3 ∗ P2 := by - refine (sep_mono_left h2.1).trans <| sep_assoc.1.trans + refine (sep_mono_left h2.mp).trans <| sep_assoc.mp.trans (sep_mono_right ((sep_mono_left h3).trans ?_)) - exact (sep_mono_right inst.1).trans wand_elim_right + exact (sep_mono_right inst.into_wand).trans wand_elim_right theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} {A2 A3 Q P1 : PROP} P2 (inst : IntoWand q false Q .out P1 .out P2) (h2 : A2 ⊢ A3 ∗ P1) : A2 ∗ □?q Q ⊢ A3 ∗ P2 := - (sep_mono_left h2).trans <| sep_assoc.1.trans + (sep_mono_left h2).trans <| sep_assoc.mp.trans (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) theorem specialize_wand_persistent [BI PROP] {q : Bool} {A2 Q P1' : PROP} P1 P2 - (inst3 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) - (inst1 : IntoAbsorbingly P1' P1) - (h2 : A2 ⊢ P1') : A2 ∗ □?q Q ⊢ A2 ∗ □?q P2 := by - have h4 : □ P1 ∗ □?q Q ⊢ □?q P2 := by cases q with - | false => exact (sep_mono_right inst3.into_wand).trans wand_elim_right + (inst1 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) + (inst3 : IntoAbsorbingly P1' P1) (h1 : A2 ⊢ P1') : A2 ∗ □?q Q ⊢ A2 ∗ □?q P2 := by + have h2 : □ P1 ∗ □?q Q ⊢ □?q P2 := by cases q with + | false => exact (sep_mono_right inst1.into_wand).trans wand_elim_right | true => calc _ ⊢ □ □ P1 ∗ □ □ Q := sep_mono intuitionistically_idem.mpr intuitionistically_idem.mpr _ ⊢ □ (□ P1 ∗ □ Q) := intuitionistically_sep_mpr - _ ⊢ □ (□ P1 ∗ (□ P1 -∗ P2)) := intuitionistically_mono <| sep_mono_right <| inst3.into_wand + _ ⊢ □ (□ P1 ∗ (□ P1 -∗ P2)) := intuitionistically_mono <| sep_mono_right <| inst1.into_wand _ ⊢ □?true P2 := intuitionistically_mono wand_elim_right calc _ ⊢ (A2 ∧ A2) ∗ □?q Q := sep_mono_left <| and_intro .rfl .rfl - _ ⊢ (A2 ∧ P1') ∗ □?q Q := sep_mono_left <| and_mono_right h2 + _ ⊢ (A2 ∧ P1') ∗ □?q Q := sep_mono_left <| and_mono_right h1 _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right into_absorbingly _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right <| absorbingly_mono Persistent.persistent _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right <| absorbingly_persistently.mp _ ⊢ (A2 ∗ □ P1) ∗ □?q Q := sep_mono_left persistently_and_intuitionistically_sep_right.mp _ ⊢ A2 ∗ □ P1 ∗ □?q Q := sep_assoc.mp - _ ⊢ A2 ∗ □?q P2 := sep_mono_right h4 + _ ⊢ A2 ∗ □?q P2 := sep_mono_right h2 theorem specialize_forall [BI PROP] {p : Bool} {A2 P : PROP} {α : Sort _} {Φ : α → PROP} (inst : IntoForall P Φ) (a : α) : A2 ∗ □?p P ⊢ A2 ∗ □?p (Φ a) := sep_mono_right <| intuitionisticallyIf_mono <| inst.into_forall.trans (forall_elim a) theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} - (h : P ∗ □?pa A ⊢ P' ∗ □?pb B) + (h1 : P ∗ □?pa A ⊢ P' ∗ □?pb B) (h2 : pa = true ∨ Affine A) [IntoPersistently pb B B'] : P ∗ □?pa A ⊢ P ∗ □ B' := by apply Entails.trans _ persistently_and_intuitionistically_sep_right.1 apply and_intro · cases h2 <;> subst_eqs <;> apply sep_elim_left - · apply h.trans <| + · apply h1.trans <| (sep_mono_right (persistentlyIf_of_intuitionisticallyIf.trans into_persistently)).trans <| sep_elim_right @@ -100,48 +99,64 @@ structure SpecializeState {prop : Q(Type u)} {bi : Q(BI $prop)} (orig goal : Q($ -- Holds when the `.modal` kind is not involved, cannot duplicate context if `pf = none` pf : Option Q($orig ⊢ $e ∗ □?$p $out) -/-- Used in all cases in `processWand` except those involving the `.modal` kind -/ +/-- + Updates a `SpecializeState` instance by one iteration. + Used in all cases in `processWand` except those involving the `.modal` kind. +-/ private def SpecializeState.update {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q($prop)} (st : @SpecializeState u prop bi orig goal) {e' : Q($prop)} (hyps' : Hyps bi e') (p' : Q(Bool)) (out' : Q($prop)) - (pfCont : Q($(st.e) ∗ □?$(st.p) $(st.out) ⊢ $e' ∗ □?$p' $out')) : + (pfStep : Q($(st.e) ∗ □?$(st.p) $(st.out) ⊢ $e' ∗ □?$p' $out')) : @SpecializeState u prop bi orig goal := { hyps := hyps', p := p', out := out', - pfCont := q(fun pf => $(st.pfCont) ($(pfCont).trans pf)), - pf := st.pf.map (fun pf => q($(pf).trans $pfCont)) } + pfCont := q(fun pf => $(st.pfCont) ($(pfStep).trans pf)), + pf := st.pf.map (fun pf => q($(pf).trans $pfStep)) } -/-- Used by all `.goal` cases in `processWand`. -/ +/-- + Returns `IVarId` values of hypotheses to be included in a subgoal and those to be framed. + Used by all `.goal` cases in `processWand`. +-/ private def findFrameIVars {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} - (hyps : Hyps bi e) (hs f : List Ident) : ProofModeM <| IVarIdSet × List IVarId := do + (hyps : Hyps bi e) (subgoalIdents frameIdents : List Ident) : + ProofModeM <| IVarIdSet × List IVarId := do -- Hypotheses to be included in the subgoal - let ivars ← hs.foldlM (return ·.insert <| ← hyps.findWithInfo ·) {} + let subgoalIVars ← subgoalIdents.foldlM (return ·.insert <| ← hyps.findWithInfo ·) {} -- Hypotheses to be framed let mut frameIVars : List IVarId := [] - for name in f do - let ivar ← hyps.findWithInfo name - if ivars.contains ivar then - throwError "ispecialize: {name} used twice" + for i in frameIdents do + let ivar ← hyps.findWithInfo i + if subgoalIVars.contains ivar then + throwError "ispecialize: {i} used twice" frameIVars := ivar :: frameIVars - return ⟨ivars, frameIVars.reverse⟩ + return ⟨subgoalIVars, frameIVars.reverse⟩ -/-- Used by the `.goal` cases with the `.spatial` or `.modal` kind. -/ +/-- + Split hypotheses into those to be included in a subgoal and those to be framed. + Used by the `.goal` cases with the `.spatial` or `.modal` kind. +-/ private def splitFrameHyps {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} - (hyps : Hyps bi e) (hs f : List Ident) (negate : Bool) : + (hyps : Hyps bi e) (subgoalIdents frameIdents : List Ident) (negate : Bool) : ProofModeM <| (el : Q($prop)) × (er : Q($prop)) × Hyps bi el × Hyps bi er × Q($e ⊣⊢ $el ∗ $er) × List IVarId := do - let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps hs f - let ⟨_, _, hypsl', hypsr', pf'⟩ := Hyps.split bi + let ⟨ivars, frameIVars⟩ ← findFrameIVars hyps subgoalIdents frameIdents + let ⟨el, er, hypsl, hypsr, pf⟩ := Hyps.split bi (λ _ ivar => (negate ^^ ivars.contains ivar) || frameIVars.contains ivar) hyps - return ⟨_, _, hypsl', hypsr', pf', frameIVars⟩ + return ⟨el, er, hypsl, hypsr, pf, frameIVars⟩ -/-- Used by all `.goal` cases and the `.autoframe persistent` case in `processWand`. -/ +/-- + Applying framing and then solve the goal using `itrivial` (when `trivial` is + `true`) or add the goal into the proof state (when `trivial` is `false`). + Used by all `.goal` cases and the `.autoframe persistent` case in `processWand`. +-/ private def finishFrameSubgoal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} (hyps : Hyps bi e) (goal : Q($prop)) (trivial : Bool) (g : Option Name) (frameIVars : Option <| List IVarId) : ProofModeM Q($e ⊢ $goal) := do let targets ← do match frameIVars with + -- For auto-framing | none => SelPat.resolve hyps [.spatial, .intuitionistic] + -- For framing with the specified hypotheses | some frameIVars => pure (frameIVars.map (⟨.ipm ·, true⟩)) - let res ← iFrame bi _ hyps goal targets + let res ← iFrame bi hyps goal targets res.finish λ hyps goal => do if trivial then let some r ← iTrivial hyps goal @@ -158,8 +173,6 @@ private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} let out₂ ← mkFreshExprMVarQ prop let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $persistent $out .out $out₁ .out $out₂) | throwError m!"ispecialize: {out} is not a wand" - let out₁ : Q($prop) ← instantiateMVars out₁ - let out₂ : Q($prop) ← instantiateMVars out₂ return ⟨out₁, out₂, inst⟩ /-- Used by the cases `.autoframe` and `.goal` in `processWand` with the `.persistent` kind. -/ @@ -169,14 +182,14 @@ private def synthIntoWandPersistent {u} {prop : Q(Type u)} {bi : Q(BI $prop)} Q(IntoWand $p true $out .out $out₁ .out $out₂) × Q(Persistent $out₁) × Q(IntoAbsorbingly $out₁' $out₁)) := do - let ⟨out₁, out₂, inst1⟩ : (out₁ : Q($prop)) × (out₂ : Q($prop)) × + let ⟨out₁, out₂, instWand⟩ : (out₁ : Q($prop)) × (out₂ : Q($prop)) × Q(IntoWand $p true $out .out $out₁ .out $out₂) ← @synthIntoWand u prop bi p out true - let some inst2 ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) + let some intoPers ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) | throwError m!"ispecialize: {out₁} is not persistent" let out₁' ← mkFreshExprMVarQ prop - let some inst3 ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) + let some instAbsorb ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" - return ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ + return ⟨out₁, out₂, out₁', instWand, intoPers, instAbsorb⟩ /-- Used by the cases `.autoframe` and `.goal` in `processWand` with the `.modal` kind. -/ private def synthIntoWandModal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} @@ -184,12 +197,12 @@ private def synthIntoWandModal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} ProofModeM ((out₁ : Q($prop)) × (out₂ : Q($prop)) × (out₁' : Q($prop)) × Q(IntoWand $p false $out .out $out₁ .out $out₂) × Q(AddModal $out₁' $out₁ $goal)) := do - let ⟨out₁, out₂, inst1⟩ : (out₁ : Q($prop)) × (out₂ : Q($prop)) × - Q(IntoWand $p false $out .out $out₁ .out $out₂) ← @synthIntoWand u prop bi p out false + let ⟨out₁, out₂, instWand⟩ : (out₁ : Q($prop)) × (out₂ : Q($prop)) × + Q(IntoWand $p false $out .out $out₁ .out $out₂) ← @synthIntoWand u prop bi p out false let out₁' ← mkFreshExprMVarQ prop - let some inst2 ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) + let some instModal ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" - pure ⟨out₁, out₂, out₁', inst1, inst2⟩ + pure ⟨out₁, out₂, out₁', instWand, instModal⟩ mutual @@ -264,7 +277,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q -- Auto-framing with `[$]` | .autoframe .spatial => do let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false - let res ← iFrame bi _ hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) + let res ← iFrame bi hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose let pfStep := q(specialize_wand_autoframe_spatial $out₂ $inst $pf') return specState.update hyps' q(false) out₂ pfStep @@ -277,7 +290,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q -- Auto-framing with `[>$]` | .autoframe .modal => let ⟨out₁, out₂, out₁', inst1, inst2⟩ ← synthIntoWandModal p out goal - let res ← iFrame bi _ hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) + let res ← iFrame bi hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose let pfCont := q(fun pf => $pfCont (specialize_modal $pf' pf $inst1 $inst2)) return { hyps := hyps', p := q(false), out := out₂, pfCont := q($pfCont), pf := none } From 16c6a710fbf82248fbe37c5908bce0a32475ecbd Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 11:25:30 +0200 Subject: [PATCH 68/86] Code refactoring --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 32 ++++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 8c44d365e..b85563e1d 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -22,9 +22,11 @@ theorem specialize_wand [BI PROP] {q p : Bool} {A Q P1 P2 : PROP} refine sep_assoc.mp.trans (sep_mono_right ?_) cases p with | false => exact (sep_mono_right inst.into_wand).trans wand_elim_right - | true => exact - (sep_mono intuitionisticallyIf_intutitionistically.mpr intuitionisticallyIf_idem.mpr).trans <| - intuitionisticallyIf_sep_mpr.trans <| intuitionisticallyIf_mono <| (wand_elim_swap inst.into_wand) + | true => calc + _ ⊢ □?q □ P1 ∗ □?q Q := sep_mono_left intuitionisticallyIf_intutitionistically.mpr + _ ⊢ □?q □ P1 ∗ □?q □?q Q := sep_mono_right intuitionisticallyIf_idem.mpr + _ ⊢ □?q (□ P1 ∗ □?q Q) := intuitionisticallyIf_sep_mpr + _ ⊢ □?q P2 := intuitionisticallyIf_mono <| wand_elim_swap inst.into_wand -- TODO: if q is true and A1 is persistent, this proof can guarantee □ P2 instead of P2 -- see https://gitlab.mpi-sws.org/iris/iris/-/blob/846ed45bed6951035c6204fef365d9a344022ae6/iris/proofmode/coq_tactics.v#L336 @@ -217,22 +219,24 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q | .ident i spats => let ivar ← hyps.findWithInfo i let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar - let ⟨_, hyps'', pB, B, pfContNest, pfNest⟩ ← + let ⟨_, hyps'', pNest, outNest, pfContNest, pfNest⟩ ← if spats.isEmpty then -- No nested specialisation patterns pure ⟨_, hyps', p1, out₁', q(id), some q(.rfl)⟩ else -- There are nested specialisation patterns, requires recursive calls iSpecializeCore hyps' p1 out₁' q(iprop(□?$p $out -∗ $goal)) spats - let p2 := if pB.constName! == ``true then p else q(false) + let p2 := if pNest.constName! == ``true then p else q(false) let out₂ ← mkFreshExprMVarQ prop - let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pB $out .in $B .out $out₂) - | throwError m!"ispecialize: cannot instantiate {out} with {B}" + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pNest $out .in $outNest .out $out₂) + | throwError m!"ispecialize: IntoWand type class synthesis failed with {out} with {outNest}" match pfNest with + -- Nested specialisation pattern involves the `.modal` kind | none => let pfCont := q(fun pf => $pfCont (wand_elim_left_trans ($(pf').mp.trans ($pfContNest (wand_intro ((specialize_wand $inst).trans pf)))))) return { hyps := hyps'', p := p2, out := out₂, pfCont, pf := none } + -- Nested specialisation pattern does not involve the `.modal` kind, or no nested pattern | some pfNest => let pfStep := q((sep_mono_left ($(pf').mp.trans $pfNest)).trans (specialize_wand $inst)) return specState.update hyps'' p2 out₂ pfStep @@ -293,7 +297,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let res ← iFrame bi hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose let pfCont := q(fun pf => $pfCont (specialize_modal $pf' pf $inst1 $inst2)) - return { hyps := hyps', p := q(false), out := out₂, pfCont := q($pfCont), pf := none } + return { hyps := hyps', p := q(false), out := out₂, pfCont, pf := none } /-- Specialize a proposition `A` by applying a sequence of specialization patterns. @@ -335,8 +339,8 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} | none, _ | _, none => return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ -- Context duplication succeeds | some _, some af => - return ⟨_, hyps, q(true), B', q((specialize_dup_context $pf $af).trans), - some q(specialize_dup_context $pf $af)⟩ + let pf := q(specialize_dup_context $pf $af) + return ⟨_, hyps, q(true), B', q($(pf).trans), some q($pf)⟩ -- No request to duplicate the context, or the `.modal` kind is involved | _, _ => return ⟨_, hyps', pb, B, pfCont, pf⟩ @@ -361,15 +365,15 @@ partial def iCasesPat.should_try_dup_context (pat : iCasesPat) : Bool := elab "ispecialize " colGt pmt:pmTerm : tactic => do let pmt ← liftMacroM <| PMTerm.parse pmt ProofModeM.runTactic λ mvar { bi, hyps, goal, .. } => do - -- hypothesis must be in the context, otherwise use ihave + -- Hypothesis must be in the context, otherwise use `ihave` let name := ⟨pmt.term⟩ let some ivar ← try? <| hyps.findWithInfo name - | throwError "{name} should be a hypothesis, use ihave instead" + | throwError "ispecialize: {name} should be a hypothesis, use ihave instead" let some ⟨name, _, hyps', _, out, p, _, pf⟩ := Id.run <| hyps.removeG true λ name ivar' _ _ => if ivar == ivar' then some name else none - | throwError "ispecialize: cannot find argument" + | throwError "ispecialize: cannot find argument {name}" let ⟨_, hyps'', pb, B, pf', _⟩ ← iSpecializeCore hyps' p out goal pmt.spats let hyps''' := Hyps.add bi name ivar pb B hyps'' let pf'' ← addBIGoal hyps''' goal - mvar.assign q(($pf).1.trans <| $pf' $pf'') + mvar.assign q(($pf).mp.trans <| $pf' $pf'') From 135b4d9a7816d7a586c2e0e208b361cec123ff5c Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 11:30:36 +0200 Subject: [PATCH 69/86] Refactor theorem proofs --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index b85563e1d..54a5d3b68 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -39,9 +39,10 @@ theorem specialize_wand_subgoal [BI PROP] {q : Bool} {A2 A3 A4 Q P1 : PROP} P2 theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} {A2 A3 Q P1 : PROP} P2 (inst : IntoWand q false Q .out P1 .out P2) - (h2 : A2 ⊢ A3 ∗ P1) : A2 ∗ □?q Q ⊢ A3 ∗ P2 := - (sep_mono_left h2).trans <| sep_assoc.mp.trans - (sep_mono_right ((sep_mono_right inst.into_wand).trans wand_elim_right)) + (h2 : A2 ⊢ A3 ∗ P1) : A2 ∗ □?q Q ⊢ A3 ∗ P2 := calc + _ ⊢ (A3 ∗ P1) ∗ □?q Q := sep_mono_left h2 + _ ⊢ A3 ∗ P1 ∗ □?q Q := sep_assoc.mp + _ ⊢ A3 ∗ P2 := sep_mono_right <| (sep_mono_right inst.into_wand).trans wand_elim_right theorem specialize_wand_persistent [BI PROP] {q : Bool} {A2 Q P1' : PROP} P1 P2 (inst1 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) @@ -72,12 +73,13 @@ theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} (h2 : pa = true ∨ Affine A) [IntoPersistently pb B B'] : P ∗ □?pa A ⊢ P ∗ □ B' := by - apply Entails.trans _ persistently_and_intuitionistically_sep_right.1 + apply Entails.trans _ persistently_and_intuitionistically_sep_right.mp apply and_intro · cases h2 <;> subst_eqs <;> apply sep_elim_left - · apply h1.trans <| - (sep_mono_right (persistentlyIf_of_intuitionisticallyIf.trans into_persistently)).trans <| - sep_elim_right + · calc + _ ⊢ P' ∗ □?pb B := h1 + _ ⊢ P' ∗ B' := sep_mono_right <| persistentlyIf_of_intuitionisticallyIf.trans into_persistently + _ ⊢ B' := sep_elim_right theorem specialize_modal [BI PROP] {e e' goal R P1 P1' P2 : PROP} {p : Bool} (h1 : e ⊢ e' ∗ P1') (h2 : e' ∗ P2 ⊢ goal) From 2af916ddde92609228c0e282c305219d3e519aed Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 11:50:01 +0200 Subject: [PATCH 70/86] Theorem and proof refactoring --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 184 +++++++++++--------- 1 file changed, 100 insertions(+), 84 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 54a5d3b68..2893706af 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -37,6 +37,25 @@ theorem specialize_wand_subgoal [BI PROP] {q : Bool} {A2 A3 A4 Q P1 : PROP} P2 (sep_mono_right ((sep_mono_left h3).trans ?_)) exact (sep_mono_right inst.into_wand).trans wand_elim_right +theorem specialize_wand_nest [BI PROP] {e e' e'' out out1' Q out2 : PROP} + {p p1 q : Bool} (inst : IntoWand p q out .in Q .out out2) + (h1 : e ⊣⊢ e' ∗ □?p1 out1') (h2 : e' ∗ □?p1 out1' ⊢ e'' ∗ □?q Q) : + e ∗ □?p out ⊢ e'' ∗ □?(q && p) out2 := calc + _ ⊢ (e'' ∗ □?q Q) ∗ □?p out := sep_mono_left <| h1.mp.trans h2 + _ ⊢ e'' ∗ □?(q && p) out2 := specialize_wand inst + +theorem specialize_wand_nest_modal [BI PROP] + {orig e e' e'' goal out out1' Q out2 : PROP} {p p1 q : Bool} + (inst : IntoWand p q out .in Q .out out2) + (h1 : (e ∗ □?p out ⊢ goal) → orig ⊢ goal) (h2 : e ⊣⊢ e' ∗ □?p1 out1') + (h3 : (e'' ∗ □?q Q ⊢ □?p out -∗ goal) → e' ∗ □?p1 out1' ⊢ □?p out -∗ goal) + (h4 : e'' ∗ □?(q && p) out2 ⊢ goal) : orig ⊢ goal := by + apply h1 + apply wand_elim_left_trans + calc + _ ⊢ e' ∗ □?p1 out1' := h2.mp + _ ⊢ □?p out -∗ goal := h3 <| wand_intro <| (specialize_wand inst).trans h4 + theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} {A2 A3 Q P1 : PROP} P2 (inst : IntoWand q false Q .out P1 .out P2) (h2 : A2 ⊢ A3 ∗ P1) : A2 ∗ □?q Q ⊢ A3 ∗ P2 := calc @@ -45,34 +64,34 @@ theorem specialize_wand_autoframe_spatial [BI PROP] {q : Bool} {A2 A3 Q P1 : PRO _ ⊢ A3 ∗ P2 := sep_mono_right <| (sep_mono_right inst.into_wand).trans wand_elim_right theorem specialize_wand_persistent [BI PROP] {q : Bool} {A2 Q P1' : PROP} P1 P2 - (inst1 : IntoWand q true Q .out P1 .out P2) (inst2 : Persistent P1) - (inst3 : IntoAbsorbingly P1' P1) (h1 : A2 ⊢ P1') : A2 ∗ □?q Q ⊢ A2 ∗ □?q P2 := by + (instWand : IntoWand q true Q .out P1 .out P2) (instPers : Persistent P1) + (instAbsorb : IntoAbsorbingly P1' P1) (h1 : A2 ⊢ P1') : A2 ∗ □?q Q ⊢ A2 ∗ □?q P2 := by have h2 : □ P1 ∗ □?q Q ⊢ □?q P2 := by cases q with - | false => exact (sep_mono_right inst1.into_wand).trans wand_elim_right + | false => exact (sep_mono_right instWand.into_wand).trans wand_elim_right | true => calc _ ⊢ □ □ P1 ∗ □ □ Q := sep_mono intuitionistically_idem.mpr intuitionistically_idem.mpr _ ⊢ □ (□ P1 ∗ □ Q) := intuitionistically_sep_mpr - _ ⊢ □ (□ P1 ∗ (□ P1 -∗ P2)) := intuitionistically_mono <| sep_mono_right <| inst1.into_wand + _ ⊢ □ (□ P1 ∗ (□ P1 -∗ P2)) := intuitionistically_mono <| sep_mono_right instWand.into_wand _ ⊢ □?true P2 := intuitionistically_mono wand_elim_right + have h3 : A2 ⊢ A2 ∗ □ P1 := calc + _ ⊢ (A2 ∧ A2) := and_intro .rfl .rfl + _ ⊢ (A2 ∧ P1') := and_mono_right h1 + _ ⊢ (A2 ∧ P1) := and_mono_right into_absorbingly + _ ⊢ (A2 ∧ P1) := and_mono_right <| absorbingly_mono Persistent.persistent + _ ⊢ (A2 ∧ P1) := and_mono_right absorbingly_persistently.mp + _ ⊢ (A2 ∗ □ P1) := persistently_and_intuitionistically_sep_right.mp calc - _ ⊢ (A2 ∧ A2) ∗ □?q Q := sep_mono_left <| and_intro .rfl .rfl - _ ⊢ (A2 ∧ P1') ∗ □?q Q := sep_mono_left <| and_mono_right h1 - _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right into_absorbingly - _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right <| absorbingly_mono Persistent.persistent - _ ⊢ (A2 ∧ P1) ∗ □?q Q := sep_mono_left <| and_mono_right <| absorbingly_persistently.mp - _ ⊢ (A2 ∗ □ P1) ∗ □?q Q := sep_mono_left persistently_and_intuitionistically_sep_right.mp - _ ⊢ A2 ∗ □ P1 ∗ □?q Q := sep_assoc.mp - _ ⊢ A2 ∗ □?q P2 := sep_mono_right h2 + _ ⊢ (A2 ∗ □ P1) ∗ □?q Q := sep_mono_left h3 + _ ⊢ A2 ∗ □ P1 ∗ □?q Q := sep_assoc.mp + _ ⊢ A2 ∗ □?q P2 := sep_mono_right h2 theorem specialize_forall [BI PROP] {p : Bool} {A2 P : PROP} {α : Sort _} {Φ : α → PROP} (inst : IntoForall P Φ) (a : α) : A2 ∗ □?p P ⊢ A2 ∗ □?p (Φ a) := sep_mono_right <| intuitionisticallyIf_mono <| inst.into_forall.trans (forall_elim a) theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} - (h1 : P ∗ □?pa A ⊢ P' ∗ □?pb B) - (h2 : pa = true ∨ Affine A) - [IntoPersistently pb B B'] : - P ∗ □?pa A ⊢ P ∗ □ B' := by + (inst : IntoPersistently pb B B') (h1 : P ∗ □?pa A ⊢ P' ∗ □?pb B) + (h2 : pa = true ∨ Affine A) : P ∗ □?pa A ⊢ P ∗ □ B' := by apply Entails.trans _ persistently_and_intuitionistically_sep_right.mp apply and_intro · cases h2 <;> subst_eqs <;> apply sep_elim_left @@ -83,15 +102,15 @@ theorem specialize_dup_context [BI PROP] {P : PROP} {pa A P' pb B B'} theorem specialize_modal [BI PROP] {e e' goal R P1 P1' P2 : PROP} {p : Bool} (h1 : e ⊢ e' ∗ P1') (h2 : e' ∗ P2 ⊢ goal) - (inst1 : IntoWand p false R .out P1 .out P2) - (inst2 : AddModal P1' P1 goal) : + (instWand : IntoWand p false R .out P1 .out P2) + (instModal : AddModal P1' P1 goal) : e ∗ □?p R ⊢ goal := calc _ ⊢ (e' ∗ P1') ∗ □?p R := sep_mono_left h1 _ ⊢ P1' ∗ (e' ∗ □?p R) := sep_assoc.mp.trans sep_left_comm.mp - _ ⊢ P1' ∗ (e' ∗ (P1 -∗ P2)) := sep_mono_right (sep_mono_right inst1.into_wand) + _ ⊢ P1' ∗ (e' ∗ (P1 -∗ P2)) := sep_mono_right (sep_mono_right instWand.into_wand) _ ⊢ P1' ∗ ((P2 -∗ goal) ∗ (P1 -∗ P2)) := sep_mono_right (sep_mono_left (wand_intro h2)) _ ⊢ P1' ∗ (P1 -∗ goal) := sep_mono_right (sep_comm.mp.trans wand_trans) - _ ⊢ goal := inst2.add_modal + _ ⊢ goal := instModal.add_modal public meta section open Lean Elab Tactic Meta Qq Std @@ -171,42 +190,40 @@ private def finishFrameSubgoal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} private def synthIntoWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} (p : Q(Bool)) (out : Q($prop)) (persistent : Bool) : - ProofModeM <| (out₁ : Q($prop)) × (out₂ : Q($prop)) × - Q(IntoWand $p $persistent $out .out $out₁ .out $out₂) := do - let out₁ ← mkFreshExprMVarQ prop - let out₂ ← mkFreshExprMVarQ prop - let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $persistent $out .out $out₁ .out $out₂) + ProofModeM <| (out1 : Q($prop)) × (out2 : Q($prop)) × + Q(IntoWand $p $persistent $out .out $out1 .out $out2) := do + let out1 ← mkFreshExprMVarQ prop + let out2 ← mkFreshExprMVarQ prop + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $persistent $out .out $out1 .out $out2) | throwError m!"ispecialize: {out} is not a wand" - return ⟨out₁, out₂, inst⟩ + return ⟨out1, out2, inst⟩ /-- Used by the cases `.autoframe` and `.goal` in `processWand` with the `.persistent` kind. -/ private def synthIntoWandPersistent {u} {prop : Q(Type u)} {bi : Q(BI $prop)} (p : Q(Bool)) (out : Q($prop)) : - ProofModeM ((out₁ : Q($prop)) × (out₂ : Q($prop)) × (out₁' : Q($prop)) × - Q(IntoWand $p true $out .out $out₁ .out $out₂) × - Q(Persistent $out₁) × - Q(IntoAbsorbingly $out₁' $out₁)) := do - let ⟨out₁, out₂, instWand⟩ : (out₁ : Q($prop)) × (out₂ : Q($prop)) × - Q(IntoWand $p true $out .out $out₁ .out $out₂) ← @synthIntoWand u prop bi p out true - let some intoPers ← ProofModeM.trySynthInstanceQ q(Persistent $out₁) - | throwError m!"ispecialize: {out₁} is not persistent" - let out₁' ← mkFreshExprMVarQ prop - let some instAbsorb ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out₁' $out₁) - | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out₁}" - return ⟨out₁, out₂, out₁', instWand, intoPers, instAbsorb⟩ + ProofModeM ((out1 : Q($prop)) × (out2 : Q($prop)) × (out1' : Q($prop)) × + Q(IntoWand $p true $out .out $out1 .out $out2) × + Q(Persistent $out1) × Q(IntoAbsorbingly $out1' $out1)) := do + let ⟨out1, out2, instWand⟩ : (out1 : Q($prop)) × (out2 : Q($prop)) × + Q(IntoWand $p true $out .out $out1 .out $out2) ← @synthIntoWand u prop bi p out true + let some intoPers ← ProofModeM.trySynthInstanceQ q(Persistent $out1) + | throwError m!"ispecialize: {out1} is not persistent" + let out1' ← mkFreshExprMVarQ prop + let some instAbsorb ← ProofModeM.trySynthInstanceQ q(IntoAbsorbingly $out1' $out1) + | throwError m!"ispecialize: IntoAbsorbingly type class synthesis failed with {out1}" + return ⟨out1, out2, out1', instWand, intoPers, instAbsorb⟩ /-- Used by the cases `.autoframe` and `.goal` in `processWand` with the `.modal` kind. -/ private def synthIntoWandModal {u} {prop : Q(Type u)} {bi : Q(BI $prop)} (p : Q(Bool)) (out goal : Q($prop)) : - ProofModeM ((out₁ : Q($prop)) × (out₂ : Q($prop)) × (out₁' : Q($prop)) × - Q(IntoWand $p false $out .out $out₁ .out $out₂) × - Q(AddModal $out₁' $out₁ $goal)) := do - let ⟨out₁, out₂, instWand⟩ : (out₁ : Q($prop)) × (out₂ : Q($prop)) × - Q(IntoWand $p false $out .out $out₁ .out $out₂) ← @synthIntoWand u prop bi p out false - let out₁' ← mkFreshExprMVarQ prop - let some instModal ← ProofModeM.trySynthInstanceQ q(AddModal $out₁' $out₁ $goal) - | throwError m!"ispecialize: AddModal type class synthesis failed with {out₁} and {goal}" - pure ⟨out₁, out₂, out₁', instWand, instModal⟩ + ProofModeM ((out1 : Q($prop)) × (out2 : Q($prop)) × (out1' : Q($prop)) × + Q(IntoWand $p false $out .out $out1 .out $out2) × Q(AddModal $out1' $out1 $goal)) := do + let ⟨out1, out2, instWand⟩ : (out1 : Q($prop)) × (out2 : Q($prop)) × + Q(IntoWand $p false $out .out $out1 .out $out2) ← @synthIntoWand u prop bi p out false + let out1' ← mkFreshExprMVarQ prop + let some instModal ← ProofModeM.trySynthInstanceQ q(AddModal $out1' $out1 $goal) + | throwError m!"ispecialize: AddModal type class synthesis failed with {out1} and {goal}" + pure ⟨out1, out2, out1', instWand, instModal⟩ mutual @@ -220,28 +237,27 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q -- A hypothesis name, possibly with nested specialisation patterns | .ident i spats => let ivar ← hyps.findWithInfo i - let ⟨_, hyps', out₁, out₁', p1, _, pf'⟩ := hyps.remove false ivar + let ⟨_, hyps', _, out1', p1, _, pf'⟩ := hyps.remove false ivar let ⟨_, hyps'', pNest, outNest, pfContNest, pfNest⟩ ← if spats.isEmpty then -- No nested specialisation patterns - pure ⟨_, hyps', p1, out₁', q(id), some q(.rfl)⟩ + pure ⟨_, hyps', p1, out1', q(id), some q(.rfl)⟩ else -- There are nested specialisation patterns, requires recursive calls - iSpecializeCore hyps' p1 out₁' q(iprop(□?$p $out -∗ $goal)) spats + iSpecializeCore hyps' p1 out1' q(iprop(□?$p $out -∗ $goal)) spats let p2 := if pNest.constName! == ``true then p else q(false) - let out₂ ← mkFreshExprMVarQ prop - let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pNest $out .in $outNest .out $out₂) + let out2 ← mkFreshExprMVarQ prop + let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pNest $out .in $outNest .out $out2) | throwError m!"ispecialize: IntoWand type class synthesis failed with {out} with {outNest}" match pfNest with -- Nested specialisation pattern involves the `.modal` kind | none => - let pfCont := q(fun pf => $pfCont (wand_elim_left_trans - ($(pf').mp.trans ($pfContNest (wand_intro ((specialize_wand $inst).trans pf)))))) - return { hyps := hyps'', p := p2, out := out₂, pfCont, pf := none } + let pfCont := q(specialize_wand_nest_modal $inst $pfCont $pf' $pfContNest) + return { hyps := hyps'', p := p2, out := out2, pfCont, pf := none } -- Nested specialisation pattern does not involve the `.modal` kind, or no nested pattern | some pfNest => - let pfStep := q((sep_mono_left ($(pf').mp.trans $pfNest)).trans (specialize_wand $inst)) - return specState.update hyps'' p2 out₂ pfStep + let pfStep := q(specialize_wand_nest $inst $pf' $pfNest) + return specState.update hyps'' p2 out2 pfStep -- A pure Lean hypothesis | .pure t => do let v ← mkFreshLevelMVar @@ -258,48 +274,48 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q return specState.update hyps p out' pfStep -- Subgoal with `[ H₁ … Hₙ ]` or `[- H₁ … Hₙ ]` | .goal { kind := .spatial, negate, trivial, frame := f, hyps := hs } g => do - let ⟨_, _, hypsl', hypsr', pf', frameIVars⟩ ← splitFrameHyps hyps hs f negate - let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false - let pf'' ← finishFrameSubgoal hypsr' out₁ trivial g frameIVars - let pfStep := q(specialize_wand_subgoal $out₂ $inst $pf' $pf'') - return specState.update hypsl' q(false) out₂ pfStep + let ⟨_, _, hypsl, hypsr, pf', frameIVars⟩ ← splitFrameHyps hyps hs f negate + let ⟨out1, out2, inst⟩ ← synthIntoWand p out false + let pf'' ← finishFrameSubgoal hypsr out1 trivial g frameIVars + let pfStep := q(specialize_wand_subgoal $out2 $inst $pf' $pf'') + return specState.update hypsl q(false) out2 pfStep -- Subgoal with `[# H₁ … Hₙ ]` or `[#- H₁ … Hₙ ]` | .goal { kind := .persistent, trivial, frame := f, hyps := hs, .. } g => do if !hs.isEmpty then throwError "ispecialize: the subgoal for the persistent premise should not consume hypotheses" - let ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ ← synthIntoWandPersistent p out + let ⟨out1, out2, out1', instWand, instPers, instAbsorb⟩ ← synthIntoWandPersistent p out let ⟨_, frameIVars⟩ ← findFrameIVars hyps [] f - let pf' ← finishFrameSubgoal hyps out₁' trivial g frameIVars - let pfStep := q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf') - return specState.update hyps p out₂ pfStep + let pf' ← finishFrameSubgoal hyps out1' trivial g frameIVars + let pfStep := q(specialize_wand_persistent $out1 $out2 $instWand $instPers $instAbsorb $pf') + return specState.update hyps p out2 pfStep -- Subgoal with `[> H₁ … Hₙ ]` or `[>- H₁ … Hₙ ]` | .goal { kind := .modal, negate, trivial, frame := f, hyps := hs, .. } g => let ⟨_, _, hypsl', hypsr', pf', frameIVars⟩ ← splitFrameHyps hyps hs f negate - let ⟨out₁, out₂, out₁', inst1, inst2⟩ ← synthIntoWandModal p out goal - let pf'' ← finishFrameSubgoal hypsr' out₁' trivial g frameIVars + let ⟨out1, out2, out1', instWand, instModal⟩ ← synthIntoWandModal p out goal + let pf'' ← finishFrameSubgoal hypsr' out1' trivial g frameIVars let h := q($(pf').mp.trans (sep_mono_right $pf'')) - let pfCont := q(fun pf => $pfCont (specialize_modal $h pf $inst1 $inst2)) - return { hyps := hypsl', p := q(false), out := out₂, pfCont, pf := none } + let pfCont := q(fun pf => $pfCont (specialize_modal $h pf $instWand $instModal)) + return { hyps := hypsl', p := q(false), out := out2, pfCont, pf := none } -- Auto-framing with `[$]` | .autoframe .spatial => do - let ⟨out₁, out₂, inst⟩ ← synthIntoWand p out false - let res ← iFrame bi hyps out₁ (← SelPat.resolve hyps [.spatial, .intuitionistic]) + let ⟨out1, out2, inst⟩ ← synthIntoWand p out false + let res ← iFrame bi hyps out1 (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose - let pfStep := q(specialize_wand_autoframe_spatial $out₂ $inst $pf') - return specState.update hyps' q(false) out₂ pfStep + let pfStep := q(specialize_wand_autoframe_spatial $out2 $inst $pf') + return specState.update hyps' q(false) out2 pfStep -- Auto-framing with `[#$]` | .autoframe .persistent => - let ⟨out₁, out₂, out₁', inst1, inst2, inst3⟩ ← synthIntoWandPersistent p out - let pf' ← finishFrameSubgoal hyps out₁' true none none - let pfStep := q(specialize_wand_persistent $out₁ $out₂ $inst1 $inst2 $inst3 $pf') - return specState.update hyps p out₂ pfStep + let ⟨out1, out2, out1', instWand, instPers, instAbsorb⟩ ← synthIntoWandPersistent p out + let pf' ← finishFrameSubgoal hyps out1' true none none + let pfStep := q(specialize_wand_persistent $out1 $out2 $instWand $instPers $instAbsorb $pf') + return specState.update hyps p out2 pfStep -- Auto-framing with `[>$]` | .autoframe .modal => - let ⟨out₁, out₂, out₁', inst1, inst2⟩ ← synthIntoWandModal p out goal - let res ← iFrame bi hyps out₁' (← SelPat.resolve hyps [.spatial, .intuitionistic]) + let ⟨out1, out2, out1', instWand, instModal⟩ ← synthIntoWandModal p out goal + let res ← iFrame bi hyps out1' (← SelPat.resolve hyps [.spatial, .intuitionistic]) let ⟨_, hyps', pf'⟩ ← res.finishClose - let pfCont := q(fun pf => $pfCont (specialize_modal $pf' pf $inst1 $inst2)) - return { hyps := hyps', p := q(false), out := out₂, pfCont, pf := none } + let pfCont := q(fun pf => $pfCont (specialize_modal $pf' pf $instWand $instModal)) + return { hyps := hyps', p := q(false), out := out2, pfCont, pf := none } /-- Specialize a proposition `A` by applying a sequence of specialization patterns. @@ -340,8 +356,8 @@ def iSpecializeCore {prop : Q(Type u)} {bi : Q(BI $prop)} {e} -- Context duplication does not succeed | none, _ | _, none => return ⟨_, hyps', pb, B, q($(pf).trans), some q($pf)⟩ -- Context duplication succeeds - | some _, some af => - let pf := q(specialize_dup_context $pf $af) + | some inst, some af => + let pf := q(specialize_dup_context $inst $pf $af) return ⟨_, hyps, q(true), B', q($(pf).trans), some q($pf)⟩ -- No request to duplicate the context, or the `.modal` kind is involved | _, _ => return ⟨_, hyps', pb, B, pfCont, pf⟩ From 1147f469a14d5b867f93f031d3a3abaea182f676 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 13:02:38 +0200 Subject: [PATCH 71/86] Port `add_modal_forall` --- Iris/Iris/ProofMode/Instances.lean | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Iris/Iris/ProofMode/Instances.lean b/Iris/Iris/ProofMode/Instances.lean index c2ccbf9df..3a01919cb 100644 --- a/Iris/Iris/ProofMode/Instances.lean +++ b/Iris/Iris/ProofMode/Instances.lean @@ -875,6 +875,14 @@ instance addModal_wand [BI PROP] (P P' Q R : PROP) [h : AddModal P P' Q] : _ ⊢ P ∗ (P' -∗ Q) := sep_mono_right h1 _ ⊢ Q := h.add_modal +@[rocq_alias add_modal_forall] +instance addModal_forall {A : Type} [BI PROP] (P P' : PROP) (Φ : A → PROP) + [h : ∀ x, AddModal P P' (Φ x)] : AddModal P P' iprop(∀ x, Φ x) where + add_modal := by + apply forall_intro + intro a + exact (sep_mono_right (wand_mono .rfl (forall_elim a))).trans (h a).add_modal + -- CombineSepAs @[rocq_alias maybe_combine_sep_as_default] instance (priority := default - 20) combineSepAs_default [BI PROP] (P Q : PROP) : From db59b6dd2b76cf193fcea9d34a3200a775258116 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 13:07:44 +0200 Subject: [PATCH 72/86] Port `wandM` --- Iris/Iris/BI/BIBase.lean | 14 ++++++++++++++ Iris/Iris/BI/DerivedLaws.lean | 6 ++++++ Iris/Iris/ProofMode/Instances.lean | 13 +++++++++++++ 3 files changed, 33 insertions(+) diff --git a/Iris/Iris/BI/BIBase.lean b/Iris/Iris/BI/BIBase.lean index ae7c216dd..91a9753cd 100644 --- a/Iris/Iris/BI/BIBase.lean +++ b/Iris/Iris/BI/BIBase.lean @@ -54,6 +54,8 @@ syntax "⌜" term "⌝" : term syntax:35 term:36 " ∗ " term:35 : term /-- Separating implication. -/ syntax:25 term:26 " -∗ " term:25 : term +/-- Separating implication with an optional wand premise. -/ +syntax:25 term:26 " -∗? " term:25 : term /-- Persistency modality. `persistently` is a primitive of BI. -/ syntax:max " " term:40 : term /-- Later modality. `later` is a primitive of BI. -/ @@ -163,6 +165,18 @@ delab_rule iff delab_rule wandIff | `($_ $P $Q) => do ``(iprop($(← unpackIprop P) ∗-∗ $(← unpackIprop Q))) +@[simp, rocq_alias bi_wandM] +def wandM [BIBase PROP] (mP : Option PROP) (Q : PROP) : PROP := + match mP with + | none => Q + | some P => iprop(P -∗ Q) + +macro_rules + | `(iprop($mP -∗? $Q)) => ``(wandM $mP iprop($Q)) + +delab_rule wandM + | `($_ $mP $Q) => do ``(iprop($mP -∗? $(← unpackIprop Q))) + /-- Affine modality. ``` def affinely (P) := emp ∧ P diff --git a/Iris/Iris/BI/DerivedLaws.lean b/Iris/Iris/BI/DerivedLaws.lean index 2e71ef58c..e4efb8eea 100644 --- a/Iris/Iris/BI/DerivedLaws.lean +++ b/Iris/Iris/BI/DerivedLaws.lean @@ -1041,6 +1041,12 @@ theorem emp_or [BI PROP] {P : PROP} [Affine P] : emp ∨ P ⊣⊢ emp := ⟨or_e theorem emp_wand [BI PROP] {P : PROP} : (emp -∗ P) ⊣⊢ P := ⟨emp_sep.mpr.trans wand_elim_right, wand_intro_left emp_sep.mp⟩ +@[rocq_alias bi.wandM_sound] +theorem wandM_sound [BI PROP] {mP : Option PROP} {Q : PROP} : + (mP -∗? Q) ⊣⊢ (mP.getD emp -∗ Q) := by + cases mP <;> simp [BIBase.wandM] + exact emp_wand.symm + @[rocq_alias bi.or_emp] theorem or_emp [BI PROP] {P : PROP} [Affine P] : P ∨ emp ⊣⊢ emp := or_comm.trans emp_or diff --git a/Iris/Iris/ProofMode/Instances.lean b/Iris/Iris/ProofMode/Instances.lean index 3a01919cb..82e0061aa 100644 --- a/Iris/Iris/ProofMode/Instances.lean +++ b/Iris/Iris/ProofMode/Instances.lean @@ -51,6 +51,12 @@ instance fromImp_imp [BI PROP] (P1 P2 : PROP) : FromImp iprop(P1 → P2) P1 P2 : @[rocq_alias from_wand_wand] instance fromWand_wand [BI PROP] (P1 P2 : PROP) : FromWand iprop(P1 -∗ P2) io P1 P2 := ⟨.rfl⟩ +-- FromWandM +@[rocq_alias from_wand_wandM] +instance fromWand_wandM [BI PROP] (mP1 : Option PROP) (P2 : PROP) : + FromWand iprop(mP1 -∗? P2) io (mP1.getD emp) P2 where + from_wand := wandM_sound.mpr + -- IntoWand #rocq_ignore into_wand_wand' "IntoWand' is not used in Lean" #rocq_ignore into_wand_impl' "IntoWand' is not used in Lean" @@ -61,6 +67,13 @@ instance intoWand_wand (p q : Bool) [BI PROP] (P Q P' : PROP) [h : FromAssumptio IntoWand p q iprop(P' -∗ Q) ioP P ioQ Q where into_wand := (intuitionisticallyIf_mono <| wand_mono_left h.1).trans intuitionisticallyIf_elim +@[rocq_alias into_wand_wandM] +instance intoWand_wandM (p q : Bool) [BI PROP] (mP' : Option PROP) (P Q : PROP) + [h : FromAssumption q ioP P (mP'.getD emp)] : + IntoWand p q iprop(mP' -∗? Q) ioP P ioQ Q where + into_wand := (intuitionisticallyIf_mono wandM_sound.mp).trans <| + (intuitionisticallyIf_mono <| wand_mono_left h.1).trans intuitionisticallyIf_elim + -- TODO: compare this with into_wand_impl_false_false, into_wand_impl_false_true, ... in Rocq instance intoWand_imp_false [BI PROP] (P Q P' : PROP) [Absorbing P'] [Absorbing iprop(P' → Q)] [h : FromAssumption b ioP P P'] : IntoWand false b iprop(P' → Q) ioP P ioQ Q where From a5e0a7e501db8dc36777ac4c5fcc12afd060076d Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 13:13:52 +0200 Subject: [PATCH 73/86] Port `add_modal_wandM` --- Iris/Iris/ProofMode/Instances.lean | 37 +++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Iris/Iris/ProofMode/Instances.lean b/Iris/Iris/ProofMode/Instances.lean index 82e0061aa..1a32b6e03 100644 --- a/Iris/Iris/ProofMode/Instances.lean +++ b/Iris/Iris/ProofMode/Instances.lean @@ -869,24 +869,35 @@ instance elimModal_absorbingly_here [BI PROP] p (P Q : PROP) [Absorbing Q] : ElimModal True p false iprop( P) P Q Q where elim_modal _ := (sep_mono_left intuitionisticallyIf_elim).trans $ absorbingly_sep_left.1.trans $ absorbing_absorbingly.1.trans wand_elim_right +theorem addModal_wand_mp [BI PROP] {P P' Q R : PROP} [h : AddModal P P' Q] : + P ∗ (P' -∗ R -∗ Q) ⊢ R -∗ Q := by + have h1 : (P' -∗ R -∗ Q) ∗ R ⊢ P' -∗ Q := by + apply wand_intro + calc + _ ⊢ (P' -∗ R -∗ Q) ∗ R ∗ P' := sep_assoc.mp + _ ⊢ (P' -∗ R -∗ Q) ∗ P' ∗ R := sep_mono_right sep_comm.mp + _ ⊢ ((P' -∗ R -∗ Q) ∗ P') ∗ R := sep_assoc.mpr + _ ⊢ (R -∗ Q) ∗ R := sep_mono_left wand_elim_left + _ ⊢ Q := wand_elim_left + apply wand_intro + calc + _ ⊢ P ∗ (P' -∗ R -∗ Q) ∗ R := sep_assoc.mp + _ ⊢ P ∗ (P' -∗ Q) := sep_mono_right h1 + _ ⊢ Q := h.add_modal + -- AddModal @[rocq_alias add_modal_wand] instance addModal_wand [BI PROP] (P P' Q R : PROP) [h : AddModal P P' Q] : AddModal P P' iprop(R -∗ Q) where + add_modal := addModal_wand_mp + +@[rocq_alias add_modal_wandM] +instance addModal_wandM [BI PROP] (P P' Q : PROP) (mR : Option PROP) + [h : AddModal P P' Q] : AddModal P P' iprop(mR -∗? Q) where add_modal := by - have h1 : (P' -∗ R -∗ Q) ∗ R ⊢ P' -∗ Q := by - apply wand_intro - calc - _ ⊢ (P' -∗ R -∗ Q) ∗ R ∗ P' := sep_assoc.mp - _ ⊢ (P' -∗ R -∗ Q) ∗ P' ∗ R := sep_mono_right sep_comm.mp - _ ⊢ ((P' -∗ R -∗ Q) ∗ P') ∗ R := sep_assoc.mpr - _ ⊢ (R -∗ Q) ∗ R := sep_mono_left wand_elim_left - _ ⊢ Q := wand_elim_left - apply wand_intro - calc - _ ⊢ P ∗ (P' -∗ R -∗ Q) ∗ R := sep_assoc.mp - _ ⊢ P ∗ (P' -∗ Q) := sep_mono_right h1 - _ ⊢ Q := h.add_modal + cases mR with + | none => exact h.add_modal + | some R => exact addModal_wand_mp @[rocq_alias add_modal_forall] instance addModal_forall {A : Type} [BI PROP] (P P' : PROP) (Φ : A → PROP) From 066ad2053379c49f384392ea857317abf604f84f Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 13:16:44 +0200 Subject: [PATCH 74/86] `HaveCore.lean`: typo fix --- Iris/Iris/ProofMode/Tactics/HaveCore.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/HaveCore.lean b/Iris/Iris/ProofMode/Tactics/HaveCore.lean index 325c91e1d..395c77822 100644 --- a/Iris/Iris/ProofMode/Tactics/HaveCore.lean +++ b/Iris/Iris/ProofMode/Tactics/HaveCore.lean @@ -56,13 +56,13 @@ private def iHaveCore {e} (hyps : @Hyps u prop bi e) let ⟨_, hyps, _, out', p, _, pf⟩ := hyps.remove (!keep) ivar return ⟨_, hyps, p, out', q($pf.1)⟩ else - -- lean hypothesis + -- Lean hypothesis let val ← instantiateMVars <| ← elabTerm tm none (mayPostpone := true) let ty ← instantiateMVars <| ← inferType val let ⟨newMVars, _, _⟩ ← forallMetaTelescope ty let val := mkAppN val newMVars - -- TOOD: should we call postprocessAppMVars? + -- TODO: should we call postprocessAppMVars? let newMVarIds ← newMVars.map Expr.mvarId! |>.filterM fun mvarId => not <$> mvarId.isAssigned let otherMVarIds ← getMVarsNoDelayed val let otherMVarIds := otherMVarIds.filter (!newMVarIds.contains ·) From 77a46858502ff647d286d2297e0c179070bac898 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 13:45:58 +0200 Subject: [PATCH 75/86] Port `add_modal_fupd` --- Iris/Iris/ProofMode/InstancesUpdates.lean | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Iris/Iris/ProofMode/InstancesUpdates.lean b/Iris/Iris/ProofMode/InstancesUpdates.lean index 5d67712b0..618ae96ac 100644 --- a/Iris/Iris/ProofMode/InstancesUpdates.lean +++ b/Iris/Iris/ProofMode/InstancesUpdates.lean @@ -205,6 +205,16 @@ instance (priority := low) elimModal_fupd_fupd_wrongMask p E0 E1 E2 E3 (P Q : PR p false iprop(|={E1,E2}=> P) iprop(False) iprop(|={E0,E3}=> Q) iprop(False) where elim_modal h := by cases h +@[rocq_alias add_modal_fupd] +instance addModal_fupd E1 E2 (P Q : PROP) : + AddModal iprop(|={E1}=> P) P iprop(|={E1,E2}=> Q) where + add_modal := by + calc + _ ⊢ (P ={E1, E2}=∗ Q) ∗ (|={E1}=> P) := sep_comm.mp + _ ⊢ |={E1}=> ((P -∗ |={E1,E2}=> Q) ∗ P) := fupd_frame_left + _ ⊢ |={E1}=> (|={E1,E2}=> Q) := BIFUpdate.mono wand_elim_left + _ ⊢ |={E1, E2}=> Q := BIFUpdate.trans + end BIFancyUpdate section SBIFancyUpdate From 591a63853318f34b4f51b348e36c83955a400695 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 13:56:53 +0200 Subject: [PATCH 76/86] Port `AddModal` instances in `InstancesLater.lean` --- Iris/Iris/ProofMode/InstancesLater.lean | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Iris/Iris/ProofMode/InstancesLater.lean b/Iris/Iris/ProofMode/InstancesLater.lean index e1089e7e4..d68c85ec9 100644 --- a/Iris/Iris/ProofMode/InstancesLater.lean +++ b/Iris/Iris/ProofMode/InstancesLater.lean @@ -318,6 +318,42 @@ instance (priority := default - 10) elimModal_timeless [BI PROP] p (P P' Q : PRO ElimModal True p p P P' Q Q where elim_modal _ := ((sep_mono ((intuitionisticallyIf_mono into_except0).trans except0_intuitionisticallyIf) except0_intro).trans $ except0_sep.2.trans (except0_mono wand_elim_right)).trans is_except0 +/-- AddModal -/ +@[rocq_alias add_modal_later_except_0] +instance addModal_later_except_0 [BI PROP] (P Q : PROP) [h : Timeless P] : + AddModal iprop(▷ P) P iprop(◇ Q) where + add_modal := calc + _ ⊢ ◇ P ∗ (P -∗ ◇ Q) := sep_mono_left h.timeless + _ ⊢ ◇ (P ∗ (P -∗ ◇ Q)) := except0_frame_right + _ ⊢ ◇ (◇ Q) := except0_mono wand_elim_right + _ ⊢ ◇ Q := except0_idem.mp + +@[rocq_alias add_modal_later] +instance addModal_later [BI PROP] (P Q : PROP) [h : Timeless P] : + AddModal iprop(▷ P) P iprop(▷ Q) where + add_modal := calc + _ ⊢ ◇ P ∗ (P -∗ ▷ Q) := sep_mono_left h.timeless + _ ⊢ ◇ (P ∗ (P -∗ ▷ Q)) := except0_frame_right + _ ⊢ ◇ (▷ Q) := except0_mono wand_elim_right + _ ⊢ ▷ Q := except0_later + +@[rocq_alias add_modal_except_0] +instance addModal_except_0 [BI PROP] (P Q : PROP) : + AddModal iprop(◇ P) P iprop(◇ Q) where + add_modal := + calc + _ ⊢ ◇ (P ∗ (P -∗ ◇ Q)) := except0_frame_right + _ ⊢ ◇ (◇ Q) := except0_mono wand_elim_right + _ ⊢ ◇ Q := except0_idem.mp + +@[rocq_alias add_modal_except_0_later] +instance addModal_except_0_later [BI PROP] (P Q : PROP) : + AddModal iprop(◇ P) P iprop(▷ Q) where + add_modal := calc + _ ⊢ ◇ (P ∗ (P -∗ ▷ Q)) := except0_frame_right + _ ⊢ ◇ (▷ Q) := except0_mono wand_elim_right + _ ⊢ ▷ Q := except0_later + /-- IntoLaterN -/ @[rocq_alias maybe_into_laterN_default] instance (priority := low) intoLaterN_default [BI PROP] only_head n (P : PROP) : From 220ebefe3a08fa63f4f5891cf42be6593212e57e Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 14:17:52 +0200 Subject: [PATCH 77/86] Port `add_modal_fupd_wp` --- Iris/Iris/ProgramLogic/WeakestPre.lean | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Iris/Iris/ProgramLogic/WeakestPre.lean b/Iris/Iris/ProgramLogic/WeakestPre.lean index 896ca484a..9149fc3ba 100644 --- a/Iris/Iris/ProgramLogic/WeakestPre.lean +++ b/Iris/Iris/ProgramLogic/WeakestPre.lean @@ -681,4 +681,11 @@ instance elimModalFupdWpAtomic_wrongMask : p false iprop(|={E₁,E₂}=> P) iprop(False) (WP e @ s ; E₁ {{ Φ }}) iprop(False) where elim_modal := nofun +@[rocq_alias add_modal_fupd_wp] +instance addModalFupdWp : AddModal iprop(|={E}=> P) P (WP e @ s ; E {{ Φ }}) where + add_modal := by + iintro ⟨H1, H2⟩ + imod H1 + iapply H2 $$ H1 + end ProofModeClasses From 893e0cb7baddf6a609ff7894ec1b00a23419e044 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 15:10:09 +0200 Subject: [PATCH 78/86] Improve error messages and more tests --- Iris/Iris/ProofMode/Tactics/Specialize.lean | 6 ++-- Iris/Iris/Tests/Tactics.lean | 35 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Iris/Iris/ProofMode/Tactics/Specialize.lean b/Iris/Iris/ProofMode/Tactics/Specialize.lean index 2893706af..06e40a779 100644 --- a/Iris/Iris/ProofMode/Tactics/Specialize.lean +++ b/Iris/Iris/ProofMode/Tactics/Specialize.lean @@ -148,8 +148,10 @@ private def findFrameIVars {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {e} let mut frameIVars : List IVarId := [] for i in frameIdents do let ivar ← hyps.findWithInfo i + if frameIVars.contains ivar then + throwError "ispecialize: {i} used twice for framing" if subgoalIVars.contains ivar then - throwError "ispecialize: {i} used twice" + throwError "ispecialize: {i} cannot be used for both the subgoal and framing" frameIVars := ivar :: frameIVars return ⟨subgoalIVars, frameIVars.reverse⟩ @@ -248,7 +250,7 @@ private def processWand {u} {prop : Q(Type u)} {bi : Q(BI $prop)} {orig goal : Q let p2 := if pNest.constName! == ``true then p else q(false) let out2 ← mkFreshExprMVarQ prop let some inst ← ProofModeM.trySynthInstanceQ q(IntoWand $p $pNest $out .in $outNest .out $out2) - | throwError m!"ispecialize: IntoWand type class synthesis failed with {out} with {outNest}" + | throwError m!"ispecialize: IntoWand type class synthesis failed with {out} and {outNest}" match pfNest with -- Nested specialisation pattern involves the `.modal` kind | none => diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 82faaa029..26b84b8ea 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1436,6 +1436,41 @@ example [BI PROP] [BIUpdate PROP] (P Q R : PROP) : imodintro iassumption +/- Tests `ispecialize` with an invalid specialisation pattern (duplicated hypotheses). -/ +/-- error: ispecialize: HP used twice for framing -/ +#guard_msgs in +example [BI PROP] (P Q : PROP) : P ⊢ (P -∗ Q) -∗ Q := by + iintro HP HPQ + ispecialize HPQ $$ [$HP $HP] + +/- Tests `ispecialize` with an invalid specialisation pattern (duplicated hypotheses). -/ +/-- error: ispecialize: HP cannot be used for both the subgoal and framing -/ +#guard_msgs in +example [BI PROP] (P Q : PROP) : P ⊢ (P -∗ Q) -∗ Q := by + iintro HP HPQ + ispecialize HPQ $$ [HP $HP] + +/- Tests `ispecialize` with an invalid hypothesis choice. -/ +/-- error: ispecialize: P is not a wand -/ +#guard_msgs in +example [BI PROP] (P Q : PROP) : P ⊢ Q := by + iintro HP + ispecialize HP $$ [$] + +/- Tests `ispecialize` with an invalid specialisation pattern. -/ +/-- error: ispecialize: IntoWand type class synthesis failed with P and Q -/ +#guard_msgs in +example [BI PROP] (P Q : PROP) : P ⊢ Q -∗ Q := by + iintro HP HQ + ispecialize HP $$ HQ + +/- Tests `ispecialize` with an invalid specialisation pattern using pure hypotheses. -/ +/-- error: ispecialize: P is not a Lean premise -/ +#guard_msgs in +example [BI PROP] (P Q : PROP) : P ⊢ Q := by + iintro HP + ispecialize HP $$ %(0 : Nat) + end specialize -- split From 0ca25ef1d0803bfa7b9414e4064b2edcf64952cc Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 15:14:04 +0200 Subject: [PATCH 79/86] Test `addModal_fupd` --- Iris/Iris/Tests/Tactics.lean | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 26b84b8ea..c4fa77a5c 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1394,13 +1394,20 @@ example [BI PROP] (P Q R S T : PROP) : ispecialize HRS $$ (HQTR $$ (HPTQ $$ HP [# $HT]) [HT //]) iassumption -/-- Tests `ispecialize` with `.autoframe .modal` using the type class instance `addModal_bupd`. -/ -example [BI PROP] [BIUpdate PROP] (P Q : PROP) : - ⊢ (P -∗ Q) -∗ (|==> P) -∗ (|==> Q) := by - iintro HPQ HP - ispecialize HPQ $$ [> $] - imodintro - iassumption +/-- + Tests `ispecialize` with `.autoframe .modal` using the type class instance + `addModal_bupd` and `addModal_fupd`. +-/ +example [BI PROP] [BIUpdate PROP] [BIFUpdate PROP] (P Q R S : PROP) : + ⊢ (P -∗ Q) -∗ (R -∗ S) -∗ (|==> P) -∗ (|={E}=> R) -∗ (|==> Q) ∗ (|={E}=> S) := by + iintro HPQ HRS HP HR + isplitl [HPQ HP] + · ispecialize HPQ $$ [>$] + imodintro + iassumption + · ispecialize HRS $$ [>$] + imodintro + iassumption /-- Tests `ispecialize` with the handling of the modality using the type class @@ -1473,6 +1480,8 @@ example [BI PROP] (P Q : PROP) : P ⊢ Q := by end specialize +/- + -- split namespace split @@ -2955,3 +2964,5 @@ example (P Q : PROP) : iloeb as IH end iloeb + +-/ From 926dc0c5f33838f2ea58cc12f337d613e3044caa Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 15:22:28 +0200 Subject: [PATCH 80/86] Add more tests for the use of `AddModal` instances by `ispecialize` --- Iris/Iris/Tests/Tactics.lean | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index c4fa77a5c..e20f52dc9 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -10,12 +10,14 @@ public import Iris.ProofMode public import Iris.Instances.IProp public import Iris.Instances.Lib.LaterCredits public import Iris.Instances.Lib.Token +public import Iris.ProgramLogic.Language +public import Iris.ProgramLogic.WeakestPre public import Iris.Algebra.CMRA @[expose] public section namespace Iris.Tests -open BI CMRA DFrac +open BI CMRA DFrac ProgramLogic /- This file contains tests with various scenarios for all available tactics. -/ @@ -1409,6 +1411,34 @@ example [BI PROP] [BIUpdate PROP] [BIFUpdate PROP] (P Q R S : PROP) : imodintro iassumption +/-- Tests `ispecialize` for its use of the type class instance `add_modal_forall`, + `add_modal_bupd` and `add_modal_later`. -/ +example [BI PROP] [BIUpdate PROP] + (P : PROP) (Q : Nat → PROP) (R S : PROP) [Timeless R] : + ⊢ (P -∗ (∀ x, Q x)) -∗ (|==> P) -∗ (R -∗ S) -∗ (▷ R) -∗ + (∀ x, |==> Q x) ∗ (▷ S) := by + iintro HPQ HP HRS HR + isplitl [HPQ HP] + · ispecialize HPQ $$ [>$] + iintro %x + ispecialize HPQ $$ %x + imodintro + iassumption + · ispecialize HRS $$ [> HR] + · imod HR + iassumption + · inext + iassumption + +/-- Tests `ispecialize` for its use of the type class instance `add_modal_fupd_wp`. -/ +example {hlc : HasLC} {Expr State Obs Val : Type _} [Language Expr State Obs Val] + {GF : BundledGFunctors} [IrisGS_gen hlc Expr GF] + (s : Stuckness) (E : CoPset) (e : Expr) (P : IProp GF) (Φ : Val → IProp GF) : + ⊢ (P -∗ WP e @ s ; E {{ Φ }}) -∗ (|={E}=> P) -∗ WP e @ s ; E {{ Φ }} := by + iintro HPQ HP + ispecialize HPQ $$ [>$] + iassumption + /-- Tests `ispecialize` with the handling of the modality using the type class instance `addModal_bupd`. The subgoal is manually solved. From 58a8c923cfdb1bf919e8367265e071ca9ace6761 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 15:26:45 +0200 Subject: [PATCH 81/86] Minor formatting --- Iris/Iris/Tests/Tactics.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index e20f52dc9..699863437 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1400,7 +1400,7 @@ example [BI PROP] (P Q R S T : PROP) : Tests `ispecialize` with `.autoframe .modal` using the type class instance `addModal_bupd` and `addModal_fupd`. -/ -example [BI PROP] [BIUpdate PROP] [BIFUpdate PROP] (P Q R S : PROP) : +example [BI PROP] [BIUpdate PROP] [BIFUpdate PROP] (P Q R S : PROP) (E : CoPset) : ⊢ (P -∗ Q) -∗ (R -∗ S) -∗ (|==> P) -∗ (|={E}=> R) -∗ (|==> Q) ∗ (|={E}=> S) := by iintro HPQ HRS HP HR isplitl [HPQ HP] @@ -1416,7 +1416,7 @@ example [BI PROP] [BIUpdate PROP] [BIFUpdate PROP] (P Q R S : PROP) : example [BI PROP] [BIUpdate PROP] (P : PROP) (Q : Nat → PROP) (R S : PROP) [Timeless R] : ⊢ (P -∗ (∀ x, Q x)) -∗ (|==> P) -∗ (R -∗ S) -∗ (▷ R) -∗ - (∀ x, |==> Q x) ∗ (▷ S) := by + (∀ x, |==> Q x) ∗ (▷ S) := by iintro HPQ HP HRS HR isplitl [HPQ HP] · ispecialize HPQ $$ [>$] From cbf8435c84fa1cbe93dcdf6a3a03a02934c02e19 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 15:30:50 +0200 Subject: [PATCH 82/86] Add `ispecialize` test with `specPat` naming the subgoal --- Iris/Iris/Tests/Tactics.lean | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 699863437..1d5f2dac9 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1508,6 +1508,14 @@ example [BI PROP] (P Q : PROP) : P ⊢ Q := by iintro HP ispecialize HP $$ %(0 : Nat) +/-- Tests `ispecialize` with a specialisation pattern naming the subgoal. -/ +example [BI PROP] [BIUpdate PROP] (P Q : PROP) : + ⊢ (P -∗ Q) -∗ (|==> P) -∗ (|==> Q) := by + iintro HPQ HP + ispecialize HPQ $$ [> HP] as subgoal + case subgoal => iassumption + imodintro; iassumption + end specialize /- From 39e5ebec8a67ef08a460c664418fd8299614518a Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 15:32:17 +0200 Subject: [PATCH 83/86] Revert code commenting --- Iris/Iris/Tests/Tactics.lean | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Iris/Iris/Tests/Tactics.lean b/Iris/Iris/Tests/Tactics.lean index 1d5f2dac9..5bb770d88 100644 --- a/Iris/Iris/Tests/Tactics.lean +++ b/Iris/Iris/Tests/Tactics.lean @@ -1518,8 +1518,6 @@ example [BI PROP] [BIUpdate PROP] (P Q : PROP) : end specialize -/- - -- split namespace split @@ -3002,5 +3000,3 @@ example (P Q : PROP) : iloeb as IH end iloeb - --/ From 9ece0007c90021165dbd3d22e668ca338fab0e56 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 19:18:06 +0200 Subject: [PATCH 84/86] Set higher priority for `addModal_later` and `addModal_later_except_0` --- Iris/Iris/ProofMode/InstancesLater.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Iris/Iris/ProofMode/InstancesLater.lean b/Iris/Iris/ProofMode/InstancesLater.lean index d68c85ec9..6ca488155 100644 --- a/Iris/Iris/ProofMode/InstancesLater.lean +++ b/Iris/Iris/ProofMode/InstancesLater.lean @@ -320,7 +320,7 @@ instance (priority := default - 10) elimModal_timeless [BI PROP] p (P P' Q : PRO /-- AddModal -/ @[rocq_alias add_modal_later_except_0] -instance addModal_later_except_0 [BI PROP] (P Q : PROP) [h : Timeless P] : +instance (priority := default + 10) addModal_later_except_0 [BI PROP] (P Q : PROP) [h : Timeless P] : AddModal iprop(▷ P) P iprop(◇ Q) where add_modal := calc _ ⊢ ◇ P ∗ (P -∗ ◇ Q) := sep_mono_left h.timeless @@ -329,7 +329,7 @@ instance addModal_later_except_0 [BI PROP] (P Q : PROP) [h : Timeless P] : _ ⊢ ◇ Q := except0_idem.mp @[rocq_alias add_modal_later] -instance addModal_later [BI PROP] (P Q : PROP) [h : Timeless P] : +instance addModal_later (priority := default + 10) [BI PROP] (P Q : PROP) [h : Timeless P] : AddModal iprop(▷ P) P iprop(▷ Q) where add_modal := calc _ ⊢ ◇ P ∗ (P -∗ ▷ Q) := sep_mono_left h.timeless From 4623eb0779b9adbc3d00b75830ef1269887db2c4 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 8 Jul 2026 19:18:55 +0200 Subject: [PATCH 85/86] Typo fix --- Iris/Iris/ProofMode/InstancesLater.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Iris/Iris/ProofMode/InstancesLater.lean b/Iris/Iris/ProofMode/InstancesLater.lean index 6ca488155..52ccff4c4 100644 --- a/Iris/Iris/ProofMode/InstancesLater.lean +++ b/Iris/Iris/ProofMode/InstancesLater.lean @@ -329,7 +329,7 @@ instance (priority := default + 10) addModal_later_except_0 [BI PROP] (P Q : PRO _ ⊢ ◇ Q := except0_idem.mp @[rocq_alias add_modal_later] -instance addModal_later (priority := default + 10) [BI PROP] (P Q : PROP) [h : Timeless P] : +instance (priority := default + 10) addModal_later [BI PROP] (P Q : PROP) [h : Timeless P] : AddModal iprop(▷ P) P iprop(▷ Q) where add_modal := calc _ ⊢ ◇ P ∗ (P -∗ ▷ Q) := sep_mono_left h.timeless From bccc6aec2e9bab2f0017292ea05ad5be4716aca4 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Sun, 12 Jul 2026 20:39:57 +0200 Subject: [PATCH 86/86] Add `rocq_ignore` for `add_modal_tforall` --- Iris/Iris/ProofMode/Instances.lean | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Iris/Iris/ProofMode/Instances.lean b/Iris/Iris/ProofMode/Instances.lean index 1a32b6e03..0675dbba6 100644 --- a/Iris/Iris/ProofMode/Instances.lean +++ b/Iris/Iris/ProofMode/Instances.lean @@ -907,6 +907,8 @@ instance addModal_forall {A : Type} [BI PROP] (P P' : PROP) (Φ : A → PROP) intro a exact (sep_mono_right (wand_mono .rfl (forall_elim a))).trans (h a).add_modal +#rocq_ignore add_modal_tforall "Rocq-specific telescope infrastructure not needed in the Lean metaprogram" + -- CombineSepAs @[rocq_alias maybe_combine_sep_as_default] instance (priority := default - 20) combineSepAs_default [BI PROP] (P Q : PROP) :