This PR adds `solve_by_elim` as a fallback in the `try?` tactic's simple
tactics. When `rfl` and `assumption` both fail but `solve_by_elim`
succeeds (e.g., for goals requiring hypothesis chaining or
backtracking), `try?` will now suggest `solve_by_elim`.
The structure is `first | (attempt_all | rfl | assumption) |
solve_by_elim`, so `solve_by_elim` only runs when the faster tactics
fail.
This is a prerequisite for removing the "first pass" `solve_by_elim`
from `apply?`. Currently `apply?` calls `solve_by_elim` twice: once
before library search, and once after each lemma application. The first
pass can be removed once `try?` includes `solve_by_elim`.
🤖 Prepared with Claude Code
---------
Co-authored-by: Claude <noreply@anthropic.com>
78 lines
2.3 KiB
Text
78 lines
2.3 KiB
Text
/-
|
|
Tests for heartbeat management in `try?`
|
|
|
|
This file tests that:
|
|
1. Each tactic execution gets a fresh heartbeat budget with the original limit
|
|
2. Tactics that exceed heartbeat limits are filtered out from try? suggestions
|
|
3. `attempt_all` continues past heartbeat failures to find working tactics
|
|
-/
|
|
|
|
module
|
|
public import Lean
|
|
public meta import Lean.Elab.Tactic.Try
|
|
|
|
open Lean Meta Elab Tactic Try
|
|
|
|
-- Define an opaque proposition that built-in tactics (including solve_by_elim) won't solve
|
|
opaque CustomGoal : Prop
|
|
axiom customGoalHolds : CustomGoal
|
|
|
|
-- Create a tactic that consumes heartbeats through meta operations
|
|
elab "expensive_meta_tactic" : tactic => do
|
|
for _ in [:10000] do
|
|
let mvar ← mkFreshExprMVar (some (mkConst ``Nat))
|
|
let _ ← instantiateMVars mvar
|
|
evalTactic (← `(tactic| exact customGoalHolds))
|
|
|
|
-- Verify that expensive_meta_tactic actually consumes heartbeats:
|
|
-- It times out at 100 heartbeats
|
|
set_option maxHeartbeats 100 in
|
|
/--
|
|
error: (deterministic) timeout at `«tactic execution»`, maximum number of heartbeats (100) has been reached
|
|
|
|
Note: Use `set_option maxHeartbeats <num>` to set the limit.
|
|
|
|
Hint: Additional diagnostic information may be available using the `set_option diagnostics true` command.
|
|
-/
|
|
#guard_msgs in
|
|
example : CustomGoal := by
|
|
expensive_meta_tactic
|
|
|
|
-- It succeeds with 500 heartbeats (establishes threshold)
|
|
set_option maxHeartbeats 500 in
|
|
#guard_msgs in
|
|
example : CustomGoal := by
|
|
expensive_meta_tactic
|
|
|
|
-- Now register it as a try? suggestion along with a cheap alternative
|
|
@[local try_suggestion 900]
|
|
meta def expensiveSolver (_ : MVarId) (_ : Try.Info) : MetaM (Array (TSyntax `tactic)) := do
|
|
return #[← `(tactic| expensive_meta_tactic)]
|
|
|
|
@[local try_suggestion 700]
|
|
meta def cheapSolver (_ : MVarId) (_ : Try.Info) : MetaM (Array (TSyntax `tactic)) := do
|
|
return #[← `(tactic| exact customGoalHolds)]
|
|
|
|
|
|
-- With 100 heartbeats
|
|
-- expensive_meta_tactic should be filtered out from try? suggestions
|
|
-- Only `customGoalHolds`` should appear
|
|
set_option maxHeartbeats 100 in
|
|
/--
|
|
info: Try this:
|
|
[apply] exact customGoalHolds✝
|
|
-/
|
|
#guard_msgs in
|
|
example : CustomGoal := by
|
|
try?
|
|
|
|
-- With 500 heartbeats, both should appear.
|
|
/--
|
|
info: Try these:
|
|
[apply] expensive_meta_tactic
|
|
[apply] exact customGoalHolds✝
|
|
-/
|
|
#guard_msgs in
|
|
set_option maxHeartbeats 500 in
|
|
example : CustomGoal := by
|
|
try?
|