You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR improves the canonicalizer used in the `grind` tactic and the
diagnostics it produces. It also adds a new configuration option,
`canonHeartbeats`, to address (some of) the issues. Here is an example
illustrating the new diagnostics, where we intentionally create a
problem by using a very small number of heartbeats.
<img width="1173" alt="image"
src="https://github.com/user-attachments/assets/484005c8-dcaa-4164-8fbf-617864ed7350"
/>
Copy file name to clipboardexpand all lines: src/Lean/Meta/Tactic/Grind/Canon.lean
+48-51
Original file line number
Diff line number
Diff line change
@@ -10,6 +10,7 @@ import Lean.Meta.FunInfo
10
10
import Lean.Util.FVarSubset
11
11
import Lean.Util.PtrSet
12
12
import Lean.Util.FVarSubset
13
+
import Lean.Meta.Tactic.Grind.Types
13
14
14
15
namespace Lean.Meta.Grind
15
16
namespace Canon
@@ -40,42 +41,37 @@ additions will still use structurally different (and definitionally different) i
40
41
Furthermore, `grind` will not be able to infer that `HEq (a + a) (b + b)` even if we add the assumptions `n = m` and `HEq a b`.
41
42
-/
42
43
43
-
structureStatewhere
44
-
argMap : PHashMap (Expr × Nat) (List Expr) := {}
45
-
canon : PHashMap Expr Expr := {}
46
-
proofCanon : PHashMap Expr Expr := {}
47
-
deriving Inhabited
44
+
@[inline]privatedefget' : GoalM State :=
45
+
return (← get).canon
48
46
49
-
inductiveCanonElemKindwhere
50
-
| /--
51
-
Type class instances are canonicalized using `TransparencyMode.instances`.
52
-
-/
53
-
instance
54
-
| /--
55
-
Types and Type formers are canonicalized using `TransparencyMode.default`.
56
-
Remark: propositions are just visited. We do not invoke `canonElemCore` for them.
57
-
-/
58
-
type
59
-
| /--
60
-
Implicit arguments that are not types, type formers, or instances, are canonicalized
61
-
using `TransparencyMode.reducible`
62
-
-/
63
-
implicit
64
-
deriving BEq
47
+
@[inline]privatedefmodify' (f : State → State) : GoalM Unit :=
48
+
modify fun s => { s with canon := f s.canon }
65
49
66
-
defCanonElemKind.explain : CanonElemKind → String
67
-
| .instance => "type class instances"
68
-
| .type => "types (or type formers)"
69
-
| .implicit => "implicit arguments (which are not type class instances or types)"
50
+
/--
51
+
Helper function for `canonElemCore`. It tries `isDefEq a b` with default transparency, but using
52
+
at most `canonHeartbeats` heartbeats. It reports an issue if the threshold is reached.
53
+
Remark: `parent` is use only to report an issue
54
+
-/
55
+
privatedefisDefEqBounded (a b : Expr) (parent : Expr) : GoalM Bool := do
56
+
withCurrHeartbeats do
57
+
let config ← getConfig
58
+
tryCatchRuntimeEx
59
+
(withTheReader Core.Context (fun ctx => { ctx with maxHeartbeats := config.canonHeartbeats }) do
60
+
withDefault <| isDefEq a b)
61
+
fun ex => do
62
+
if ex.isRuntime then
63
+
let curr := (← getConfig).canonHeartbeats
64
+
reportIssue m!"failed to show that{indentExpr a}\nis definitionally equal to{indentExpr b}\nwhile canonicalizing{indentExpr parent}\nusing `{curr}*1000` heartbeats, `(canonHeartbeats := {curr})`"
65
+
returnfalse
66
+
else
67
+
throw ex
70
68
71
69
/--
72
70
Helper function for canonicalizing `e` occurring as the `i`th argument of an `f`-application.
73
-
74
-
Thus, if diagnostics are enabled, we also re-check them using `TransparencyMode.default`. If the result is different
75
-
we report to the user.
71
+
If `useIsDefEqBounded` is `true`, we try `isDefEqBounded` before returning false
76
72
-/
77
-
defcanonElemCore (f : Expr) (i : Nat) (e : Expr) (kind : CanonElemKind) : StateT State MetaM Expr := do
78
-
let s ← get
73
+
defcanonElemCore (parent : Expr) (f : Expr) (i : Nat) (e : Expr) (useIsDefEqBounded : Bool) : GoalM Expr := do
74
+
let s ← get'
79
75
iflet some c := s.canon.find? e then
80
76
return c
81
77
let key := (f, i)
@@ -87,20 +83,21 @@ def canonElemCore (f : Expr) (i : Nat) (e : Expr) (kind : CanonElemKind) : State
87
83
-- However, we don't revert previously canonicalized elements in the `grind` tactic.
88
84
-- Moreover, we store the canonicalizer state in the `Goal` because we case-split
89
85
-- and different locals are added in different branches.
90
-
modify fun s => { s with canon := s.canon.insert e c }
91
-
trace[grind.debug.canon] "found {e} ===> {c}"
86
+
modify'fun s => { s with canon := s.canon.insert e c }
87
+
trace[grind.debugn.canon] "found {e} ===> {c}"
92
88
return c
93
-
if kind != .type then
94
-
if (← isTracingEnabledFor `grind.issues <&&> (withDefault <| isDefEq e c)) then
95
-
-- TODO: consider storing this information in some structure that can be browsed later.
96
-
trace[grind.issues] "the following {kind.explain} are definitionally equal with `default` transparency but not with a more restrictive transparency{indentExpr e}\nand{indentExpr c}"
89
+
if useIsDefEqBounded then
90
+
if (← isDefEqBounded e c parent) then
91
+
modify' fun s => { s with canon := s.canon.insert e c }
92
+
trace[grind.debugn.canon] "found using `isDefEqBounded`: {e} ===> {c}"
93
+
return c
97
94
trace[grind.debug.canon] "({f}, {i}) ↦ {e}"
98
-
modify fun s => { s with canon := s.canon.insert e e, argMap := s.argMap.insert key (e::cs) }
95
+
modify'fun s => { s with canon := s.canon.insert e e, argMap := s.argMap.insert key (e::cs) }
99
96
return e
100
97
101
-
abbrevcanonType (f : Expr) (i : Nat) (e : Expr) := withDefault <| canonElemCore f i e .type
102
-
abbrevcanonInst (f : Expr) (i : Nat) (e : Expr) := withReducibleAndInstances <| canonElemCore f i e .instance
103
-
abbrevcanonImplicit (f : Expr) (i : Nat) (e : Expr) := withReducible <| canonElemCore f i e .implicit
98
+
abbrevcanonType (parent f : Expr) (i : Nat) (e : Expr) := withDefault <| canonElemCore parent f i e (useIsDefEqBounded := false)
99
+
abbrevcanonInst (parent f : Expr) (i : Nat) (e : Expr) := withReducibleAndInstances <| canonElemCore parent f i e (useIsDefEqBounded := true)
100
+
abbrevcanonImplicit (parent f : Expr) (i : Nat) (e : Expr) := withReducible <| canonElemCore parent f i e (useIsDefEqBounded := true)
104
101
105
102
/--
106
103
Return type for the `shouldCanon` function.
@@ -148,10 +145,10 @@ def shouldCanon (pinfos : Array ParamInfo) (i : Nat) (arg : Expr) : MetaM Should
148
145
else
149
146
return .visit
150
147
151
-
unsafedefcanonImpl (e : Expr) : StateT State MetaM Expr := do
148
+
unsafedefcanonImpl (e : Expr) : GoalM Expr := do
152
149
visit e |>.run' mkPtrMap
153
150
where
154
-
visit (e : Expr) : StateRefT (PtrMap Expr Expr) (StateT State MetaM) Expr := do
151
+
visit (e : Expr) : StateRefT (PtrMap Expr Expr) GoalM Expr := do
155
152
unless e.isApp || e.isForall doreturn e
156
153
-- Check whether it is cached
157
154
iflet some r := (← get).find? e then
@@ -161,11 +158,11 @@ where
161
158
if f.isConstOf ``Lean.Grind.nestedProof && args.size == 2then
162
159
let prop := args[0]!
163
160
let prop' ← visit prop
164
-
iflet some r := (← getThe State).proofCanon.find? prop' then
161
+
iflet some r := (← get').proofCanon.find? prop' then
165
162
pure r
166
163
else
167
164
let e' := if ptrEq prop prop' then e else mkAppN f (args.set! 0 prop')
168
-
modifyThe Statefun s => { s with proofCanon := s.proofCanon.insert prop' e' }
165
+
modify'fun s => { s with proofCanon := s.proofCanon.insert prop' e' }
0 commit comments