From 8a4fb762f3b907e88167953fd5899b63a759e47b Mon Sep 17 00:00:00 2001 From: Kim Morrison <477956+kim-em@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:41:08 +1100 Subject: [PATCH] feat: `grind` `use`/`instantiate only` can activate all scoped theorems in a namespace (#11335) This PR enables the syntax `use [ns Foo]` and `instantiate only [ns Foo]` inside a `grind` tactic block, and has the effect of activating all grind patterns scoped to that namespace. We can use this to implement specialized tactics using `grind`, but only controlled subsets of theorems. --------- Co-authored-by: Claude --- src/Init/Data/Int/Order.lean | 9 ++++++ src/Init/Grind/Interactive.lean | 4 ++- src/Lean/Elab/Tactic/Grind/BuiltinTactic.lean | 5 ++++ src/Lean/Meta/Tactic/Grind/CollectParams.lean | 4 ++- src/Lean/Meta/Tactic/Grind/EMatchTheorem.lean | 7 +++++ tests/lean/run/grind_pattern_scoped.lean | 16 ++++++++++ tests/lean/run/lia_with_cases.lean | 30 +++++++++++++++++++ 7 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 tests/lean/run/grind_pattern_scoped.lean create mode 100644 tests/lean/run/lia_with_cases.lean diff --git a/src/Init/Data/Int/Order.lean b/src/Init/Data/Int/Order.lean index f2ce2e47d9..ea8b445cab 100644 --- a/src/Init/Data/Int/Order.lean +++ b/src/Init/Data/Int/Order.lean @@ -377,6 +377,15 @@ protected theorem le_iff_lt_add_one {a b : Int} : a ≤ b ↔ a < b + 1 := by @[grind =] protected theorem max_def (n m : Int) : max n m = if n ≤ m then m else n := rfl +end Int +namespace Lean.Meta.Grind.Lia + +scoped grind_pattern Int.min_def => min n m +scoped grind_pattern Int.max_def => max n m + +end Lean.Meta.Grind.Lia +namespace Int + @[simp] protected theorem neg_min_neg (a b : Int) : min (-a) (-b) = -max a b := by rw [Int.min_def, Int.max_def] simp diff --git a/src/Init/Grind/Interactive.lean b/src/Init/Grind/Interactive.lean index 1dd7021d34..4803b54848 100644 --- a/src/Init/Grind/Interactive.lean +++ b/src/Init/Grind/Interactive.lean @@ -72,7 +72,9 @@ syntax (name := linarith) "linarith" : grind /-- The `sorry` tactic is a temporary placeholder for an incomplete tactic proof. -/ syntax (name := «sorry») "sorry" : grind -syntax thm := anchor <|> grindLemmaMin <|> grindLemma +syntax thmNs := &"namespace" ident + +syntax thm := anchor <|> thmNs <|> grindLemmaMin <|> grindLemma /-- Instantiates theorems using E-matching. diff --git a/src/Lean/Elab/Tactic/Grind/BuiltinTactic.lean b/src/Lean/Elab/Tactic/Grind/BuiltinTactic.lean index db1fbd4f04..6b8af61200 100644 --- a/src/Lean/Elab/Tactic/Grind/BuiltinTactic.lean +++ b/src/Lean/Elab/Tactic/Grind/BuiltinTactic.lean @@ -13,6 +13,7 @@ import Lean.Meta.Tactic.Grind.Arith.Linear.Search import Lean.Meta.Tactic.Grind.Arith.CommRing.EqCnstr import Lean.Meta.Tactic.Grind.AC.Eq import Lean.Meta.Tactic.Grind.EMatch +import Lean.Meta.Tactic.Grind.EMatchTheorem import Lean.Meta.Tactic.Grind.PP import Lean.Meta.Tactic.Grind.Internalize import Lean.Meta.Tactic.Grind.Intro @@ -152,6 +153,10 @@ def ematchThms (only : Bool) (thms : Array EMatchTheorem) : GrindTacticM Unit := if let some thmRefs := thmRefs? then for thmRef in thmRefs do match thmRef with + | `(Parser.Tactic.Grind.thm| namespace $ns:ident) => + let namespaceName := ns.getId + let scopedThms ← Grind.getEMatchTheoremsForNamespace namespaceName + thms := thms ++ scopedThms | `(Parser.Tactic.Grind.thm| #$anchor:hexnum) => thms := thms ++ (← withRef thmRef <| elabLocalEMatchTheorem anchor) | `(Parser.Tactic.Grind.thm| $[$mod?:grindMod]? $id:ident) => thms := thms ++ (← withRef thmRef <| elabThm mod? id false) | `(Parser.Tactic.Grind.thm| ! $[$mod?:grindMod]? $id:ident) => thms := thms ++ (← withRef thmRef <| elabThm mod? id true) diff --git a/src/Lean/Meta/Tactic/Grind/CollectParams.lean b/src/Lean/Meta/Tactic/Grind/CollectParams.lean index 636dfb67aa..55d103124e 100644 --- a/src/Lean/Meta/Tactic/Grind/CollectParams.lean +++ b/src/Lean/Meta/Tactic/Grind/CollectParams.lean @@ -44,7 +44,9 @@ def collectInstantiateParams (params : Syntax.TSepArray `Lean.Parser.Tactic.Grin pushParam p | `(Lean.Parser.Tactic.Grind.thm| $a:anchor) => pushAnchor a - | _ => pure () + | _ => + -- Namespace references (thmNs) are handled elsewhere, skip them + pure () partial def collect (tac : TGrind) : Collect Unit := do match tac with diff --git a/src/Lean/Meta/Tactic/Grind/EMatchTheorem.lean b/src/Lean/Meta/Tactic/Grind/EMatchTheorem.lean index 23b6f48e17..0abd580e4d 100644 --- a/src/Lean/Meta/Tactic/Grind/EMatchTheorem.lean +++ b/src/Lean/Meta/Tactic/Grind/EMatchTheorem.lean @@ -888,6 +888,13 @@ def addEMatchEqTheorem (declName : Name) : MetaM Unit := do def getEMatchTheorems : CoreM EMatchTheorems := return ematchTheoremsExt.getState (← getEnv) +/-- Returns the scoped E-matching theorems declared in the given namespace. -/ +def getEMatchTheoremsForNamespace (namespaceName : Name) : CoreM (Array EMatchTheorem) := do + let stateStack := ematchTheoremsExt.ext.getState (← getEnv) + match stateStack.scopedEntries.map.find? namespaceName with + | none => return #[] + | some entries => return entries.toArray + /-- Returns the types of `xs` that are propositions. -/ private def getPropTypes (xs : Array Expr) : MetaM (Array Expr) := xs.filterMapM fun x => do diff --git a/tests/lean/run/grind_pattern_scoped.lean b/tests/lean/run/grind_pattern_scoped.lean new file mode 100644 index 0000000000..518a4e520e --- /dev/null +++ b/tests/lean/run/grind_pattern_scoped.lean @@ -0,0 +1,16 @@ +import Lean.Meta.Tactic.Grind.EMatchTheorem + +open Lean +open Lean.Meta.Grind + +/-- info: Namespace `Lean.Meta.Grind.Lia` has scoped theorems: true -/ +#guard_msgs in +#eval show CoreM Unit from do + let theorems ← getEMatchTheoremsForNamespace `Lean.Meta.Grind.Lia + IO.println s!"Namespace `Lean.Meta.Grind.Lia` has scoped theorems: {decide (theorems.size > 0)}" + +-- Test namespace-based theorem instantiation +example (x y : Int) (h : max x y < 7) : x + y < 13 := by + grind => + use [namespace Lean.Meta.Grind.Lia] + repeat (first (lia) (cases_next)) diff --git a/tests/lean/run/lia_with_cases.lean b/tests/lean/run/lia_with_cases.lean new file mode 100644 index 0000000000..c28f28acbc --- /dev/null +++ b/tests/lean/run/lia_with_cases.lean @@ -0,0 +1,30 @@ +-- Test that lia now uses grind tactic script with cases_next + +-- Basic linear arithmetic - should still work without case-splits +example (x y : Int) : 2 * x + 4 * y ≠ 5 := by + lia + +example (x y : Int) : 2 * x + 3 * y = 0 → 1 ≤ x → y < 1 := by + lia + +example (a b : Int) : 2 ∣ a + 1 → 2 ∣ b + a → ¬ 2 ∣ b + 2 * a := by + lia + +-- Tests that require case-splitting +-- These require the `cases_next` to split on hypotheses before lia can solve + +-- Case-split on hypothesis, then solve with lia +example (x : Nat) (h : x = 0 ∨ x = 1) : x ≤ 1 := by + lia + +-- Multiple nested case-splits +example (x y : Nat) (h1 : x = 0 ∨ x = 1) (h2 : y = 0 ∨ y = 1) : x + y ≤ 2 := by + lia + +-- Case-split on if-then-else +example (x : Int) : (if x > 0 then x else -x) ≥ 0 := by + lia + +-- Case-split with arithmetic in branches +example (x : Int) : (if x > 0 then 2*x else 0) ≥ 0 := by + lia