Skip to content

Commit 3a42ee0

Browse files
authored
feat: grind tactic mode improvements (#10978)
This PR implements the following `grind` improvements: 1. `set_option` can now be used to set `grind` configuration options in the interactive mode. 2. Fixes a bug in the repeated theorem instantiation detection. 3. Adds the macro `use [...]` as a shorthand for `instantiate only [...]`.
1 parent 30ecacd commit 3a42ee0

File tree

11 files changed

+194
-38
lines changed

11 files changed

+194
-38
lines changed

src/Init/Grind/Interactive.lean

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ that may have redundant arguments.
8383
-/
8484
syntax (name := instantiate) "instantiate" (&" only")? (&" approx")? (" [" withoutPosition(thm,*,?) "]")? : grind
8585

86+
/-- Shorthand for `instantiate only` -/
87+
syntax (name := use) "use" " [" withoutPosition(thm,*,?) "]" : grind
88+
macro_rules | `(grind| use%$u [$ts:thm,*]) => `(grind| instantiate%$u only [$ts,*])
89+
8690
-- **Note**: Should we rename the following tactics to `trace_`?
8791
/-- Shows asserted facts. -/
8892
syntax (name := showAsserted) "show_asserted" ppSpace grindFilter : grind
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/-
2+
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Leonardo de Moura
5+
-/
6+
module
7+
prelude
8+
public import Lean.Elab.Command
9+
meta import Lean.Elab.Command
10+
public import Lean.Data.KVMap
11+
public section
12+
namespace Lean.Elab
13+
open Command Meta
14+
15+
/--
16+
Generates a function `setterName` for updating the `Bool` and `Nat` fields
17+
of the structure `struct`.
18+
This is a very simple implementation. There is no support for subobjects.
19+
-/
20+
meta def mkConfigSetter (doc? : Option (TSyntax ``Parser.Command.docComment))
21+
(setterName struct : Ident) : CommandElabM Unit := do
22+
let structName ← resolveGlobalConstNoOverload struct
23+
let .inductInfo val ← getConstInfo structName
24+
| throwErrorAt struct "`{structName}` is not s structure"
25+
unless val.levelParams.isEmpty do
26+
throwErrorAt struct "`{structName}` is universe polymorphic"
27+
unless val.numIndices == 0 && val.numParams == 0 do
28+
throwErrorAt struct "`{structName}` must not be parametric"
29+
let env ← getEnv
30+
let some structInfo := getStructureInfo? env structName
31+
| throwErrorAt struct "`{structName}` is not a structure"
32+
let code : Term ← liftTermElabM do
33+
let mut code : Term ← `(throwError "invalid configuration option `{fieldName}`")
34+
for fieldInfo in structInfo.fieldInfo do
35+
if fieldInfo.subobject?.isSome then continue -- ignore subobject's
36+
let projInfo ← getConstInfo fieldInfo.projFn
37+
let fieldType ← forallTelescope projInfo.type fun _ body => pure body
38+
-- **Note**: We only support `Nat` and `Bool` fields
39+
let fieldIdent : Ident := mkCIdent fieldInfo.fieldName
40+
if fieldType.isConstOf ``Nat then
41+
code ← `(if fieldName == $(quote fieldInfo.fieldName) then
42+
return { s with $fieldIdent:ident := (← getNatField) }
43+
else $code)
44+
else if fieldType.isConstOf ``Bool then
45+
code ← `(if fieldName == $(quote fieldInfo.fieldName) then
46+
return { s with $fieldIdent:ident := (← getBoolField) }
47+
else $code)
48+
return code
49+
let cmd ← `(command|
50+
$[$doc?:docComment]?
51+
def $setterName (s : $struct) (fieldName : Name) (val : DataValue) : CoreM $struct :=
52+
let getBoolField : CoreM Bool := do
53+
let .ofBool b := val | throwError "`{fieldName}` is a Boolean"
54+
return b
55+
let getNatField : CoreM Nat := do
56+
let .ofNat n := val | throwError "`{fieldName}` is a natural number"
57+
return n
58+
$code
59+
)
60+
elabCommand cmd
61+
62+
elab (name := elabConfigGetter) doc?:(docComment)? "declare_config_getter" setterName:ident type:ident : command => do
63+
mkConfigSetter doc? setterName type
64+
65+
end Lean.Elab

src/Lean/Elab/Tactic/Grind.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ public import Lean.Elab.Tactic.Grind.BuiltinTactic
1111
public import Lean.Elab.Tactic.Grind.ShowState
1212
public import Lean.Elab.Tactic.Grind.Have
1313
public import Lean.Elab.Tactic.Grind.Trace
14+
public import Lean.Elab.Tactic.Grind.Config

src/Lean/Elab/Tactic/Grind/BuiltinTactic.lean

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import Lean.Elab.Tactic.Basic
2525
import Lean.Elab.Tactic.RenameInaccessibles
2626
import Lean.Elab.Tactic.Grind.Filter
2727
import Lean.Elab.Tactic.Grind.ShowState
28+
import Lean.Elab.Tactic.Grind.Config
2829
import Lean.Elab.SetOption
2930
namespace Lean.Elab.Tactic.Grind
3031

@@ -425,9 +426,30 @@ where
425426
liftGrindM <| resetAnchors
426427
replaceMainGoal [{ goal with mvarId }]
427428

429+
def isGrindConfigField? (stx : Syntax) : CoreM (Option Name) := do
430+
unless stx.isIdent do return none
431+
let id := stx.getId
432+
let env ← getEnv
433+
let info := getStructureInfo env ``Grind.Config
434+
unless info.fieldNames.contains id do return none
435+
return some id
436+
428437
@[builtin_grind_tactic setOption] def elabSetOption : GrindTactic := fun stx => do
429-
let options ← Elab.elabSetOption stx[1] stx[3]
430-
withOptions (fun _ => options) do evalGrindTactic stx[5]
438+
if let some fieldName ← isGrindConfigField? stx[1] then
439+
let val := stx[3]
440+
let val ← match val.isNatLit? with
441+
| some num => pure <| DataValue.ofNat num
442+
| none => match val with
443+
| Syntax.atom _ "true" => pure <| DataValue.ofBool true
444+
| Syntax.atom _ "false" => pure <| DataValue.ofBool false
445+
| _ => throwErrorAt val "`grind` configuration option must be a Boolean or a numeral"
446+
let config := (← read).ctx.config
447+
let config ← setConfigField config fieldName val
448+
withReader (fun c => { c with ctx.config := config }) do
449+
evalGrindTactic stx[5]
450+
else
451+
let options ← Elab.elabSetOption stx[1] stx[3]
452+
withOptions (fun _ => options) do evalGrindTactic stx[5]
431453

432454
@[builtin_grind_tactic mbtc] def elabMBTC : GrindTactic := fun _ => do
433455
liftGoalM do
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/-
2+
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Leonardo de Moura
5+
-/
6+
module
7+
prelude
8+
public import Lean.CoreM
9+
meta import Lean.Elab.Tactic.ConfigSetter
10+
public section
11+
namespace Lean.Elab.Tactic.Grind
12+
13+
/-- Sets a field of the `grind` configuration object. -/
14+
declare_config_getter setConfigField Grind.Config
15+
16+
end Lean.Elab.Tactic.Grind

src/Lean/Elab/Tactic/Grind/Filter.lean

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,10 @@ Authors: Leonardo de Moura
66
module
77
prelude
88
public import Lean.Elab.Tactic.Grind.Basic
9+
public import Lean.Meta.Tactic.Grind.Filter
910
import Init.Grind.Interactive
1011
namespace Lean.Elab.Tactic.Grind
11-
open Meta
12-
13-
public inductive Filter where
14-
| true
15-
| const (declName : Name)
16-
| fvar (fvarId : FVarId)
17-
| gen (pred : Nat → Bool)
18-
| or (a b : Filter)
19-
| and (a b : Filter)
20-
| not (a : Filter)
12+
open Meta Grind
2113

2214
public partial def elabFilter (filter? : Option (TSyntax `grind_filter)) : GrindTacticM Filter := do
2315
let some filter := filter? | return .true
@@ -44,27 +36,4 @@ where
4436
| `(grind_filter| gen != $n:num) => let n := n.getNat; return .gen fun x => x != n
4537
| _ => throwUnsupportedSyntax
4638

47-
open Meta.Grind
48-
49-
-- **Note**: facts may not have been internalized if they are equalities.
50-
def getGen (e : Expr) : GoalM Nat := do
51-
if (← alreadyInternalized e) then
52-
getGeneration e
53-
else match_expr e with
54-
| Eq _ lhs rhs => return max (← getGeneration lhs) (← getGeneration rhs)
55-
| _ => return 0
56-
57-
public def Filter.eval (filter : Filter) (e : Expr) : GoalM Bool := do
58-
go filter
59-
where
60-
go (filter : Filter) : GoalM Bool := do
61-
match filter with
62-
| .true => return .true
63-
| .and a b => go a <&&> go b
64-
| .or a b => go a <||> go b
65-
| .not a => return !(← go a)
66-
| .const declName => return Option.isSome <| e.find? fun e => e.isConstOf declName
67-
| .fvar fvarId => return Option.isSome <| e.find? fun e => e.isFVar && e.fvarId! == fvarId
68-
| .gen pred => let gen ← getGen e; return pred gen
69-
7039
end Lean.Elab.Tactic.Grind

src/Lean/Meta/Tactic/Grind.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ Released under Apache 2.0 license as described in the file LICENSE.
44
Authors: Leonardo de Moura
55
-/
66
module
7-
87
prelude
98
public import Lean.Meta.Tactic.Grind.Attr
109
public import Lean.Meta.Tactic.Grind.RevertAll
@@ -45,6 +44,7 @@ public import Lean.Meta.Tactic.Grind.Anchor
4544
public import Lean.Meta.Tactic.Grind.Action
4645
public import Lean.Meta.Tactic.Grind.EMatchTheoremParam
4746
public import Lean.Meta.Tactic.Grind.EMatchAction
47+
public import Lean.Meta.Tactic.Grind.Filter
4848
public section
4949
namespace Lean
5050

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/-
2+
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Leonardo de Moura
5+
-/
6+
module
7+
prelude
8+
public import Lean.Meta.Tactic.Grind.Types
9+
namespace Lean.Meta.Grind
10+
11+
public inductive Filter where
12+
| true
13+
| const (declName : Name)
14+
| fvar (fvarId : FVarId)
15+
| gen (pred : Nat → Bool)
16+
| or (a b : Filter)
17+
| and (a b : Filter)
18+
| not (a : Filter)
19+
20+
-- **Note**: facts may not have been internalized if they are equalities.
21+
def getGen (e : Expr) : GoalM Nat := do
22+
if (← alreadyInternalized e) then
23+
getGeneration e
24+
else match_expr e with
25+
| Eq _ lhs rhs => return max (← getGeneration lhs) (← getGeneration rhs)
26+
| _ => return 0
27+
28+
public def Filter.eval (filter : Filter) (e : Expr) : GoalM Bool := do
29+
go filter
30+
where
31+
go (filter : Filter) : GoalM Bool := do
32+
match filter with
33+
| .true => return .true
34+
| .and a b => go a <&&> go b
35+
| .or a b => go a <||> go b
36+
| .not a => return !(← go a)
37+
| .const declName => return Option.isSome <| e.find? fun e => e.isConstOf declName
38+
| .fvar fvarId => return Option.isSome <| e.find? fun e => e.isFVar && e.fvarId! == fvarId
39+
| .gen pred => let gen ← getGen e; return pred gen
40+
41+
end Lean.Meta.Grind

src/Lean/Meta/Tactic/Grind/Types.lean

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -640,21 +640,32 @@ We want to avoid instantiating the same theorem with the same assignment more th
640640
Therefore, we store the (pre-)instance information in set.
641641
Recall that the proofs of activated theorems have been hash-consed.
642642
The assignment contains internalized expressions, which have also been hash-consed.
643+
644+
**Note**: We used to use pointer equality to implement `PreInstanceSet`. However,
645+
this low-level trick was incorrect in interactive mode because we add new
646+
`EMatchTheorem` objects using `instantiate [...]`. For example, suppose we write
647+
```
648+
instantiate [thm_1]; instantiate [thm_1]
649+
```
650+
The `EMatchTheorem` object `thm_1` is created twice. Using pointer equality will
651+
miss instances created using the two different objects. Recall we do not use
652+
hash-consing on proof objects. If we hash-cons the proof objects, it would be ok
653+
to use pointer equality.
643654
-/
644655
structure PreInstance where
645656
proof : Expr
646657
assignment : Array Expr
647658

648659
instance : Hashable PreInstance where
649660
hash i := Id.run do
650-
let mut r := hashPtrExpr i.proof
661+
let mut r := hash i.proof -- **Note**: See note at `PreInstance`.
651662
for v in i.assignment do
652663
r := mixHash r (hashPtrExpr v)
653664
return r
654665

655666
instance : BEq PreInstance where
656667
beq i₁ i₂ := Id.run do
657-
unless isSameExpr i₁.proof i₂.proof do return false
668+
unless i₁.proof == i₂.proof do return false -- **Note**: See note at `PreInstance`.
658669
unless i₁.assignment.size == i₂.assignment.size do return false
659670
for v₁ in i₁.assignment, v₂ in i₂.assignment do
660671
unless isSameExpr v₁ v₂ do return false
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
opaque f : Nat → Nat
2+
opaque g : Nat → Nat
3+
theorem fax : f (x + 1) = g (f x) := sorry
4+
5+
/--
6+
error: `instantiate` tactic failed to instantiate new facts, use `show_patterns` to see active theorems and their patterns.
7+
-/
8+
#guard_msgs in
9+
example : f (x + 5) = a := by
10+
grind =>
11+
use [fax]; use [fax]; use [fax]; use [fax]; use [fax];
12+
use [fax] -- Should fail - no new facts

0 commit comments

Comments
 (0)