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>
66 lines
2.5 KiB
Text
66 lines
2.5 KiB
Text
/-
|
|
Test that try? runs user suggestion tactics in parallel via attempt_all_par.
|
|
|
|
This test uses IO.stdGenRef (a builtin_initialize ref) to demonstrate parallelism:
|
|
- Tactic 1 (high prio): waits 1000ms, then checks if the random seed was changed
|
|
- Tactic 2 (low prio): immediately sets the seed to a magic value, then succeeds
|
|
|
|
If sequential: Tactic 1 executes first, waits, seed unchanged, fails.
|
|
Then tactic 2 executes, sets seed, succeeds. Only one suggestion.
|
|
If parallel: Both tactics start together. Tactic 2 sets seed immediately.
|
|
Tactic 1 waits 1000ms, sees changed seed, succeeds. Two suggestions.
|
|
-/
|
|
module
|
|
public import Lean
|
|
public meta import Lean.Elab.Tactic.Try
|
|
|
|
open Lean Meta Elab Tactic Try
|
|
|
|
-- An opaque goal that built-in tactics (including solve_by_elim) won't solve
|
|
opaque ParallelTestGoal : Prop
|
|
axiom parallelTestGoalHolds : ParallelTestGoal
|
|
|
|
-- Magic seed value to signal parallelism
|
|
meta def magicSeed : Nat := 314159265
|
|
|
|
-- Tactic that waits, then checks if seed was changed
|
|
elab "wait_and_check_seed" : tactic => do
|
|
IO.sleep 1000
|
|
let gen ← IO.stdGenRef.get
|
|
let expected := mkStdGen magicSeed
|
|
if gen.s1 == expected.s1 && gen.s2 == expected.s2 then
|
|
evalTactic (← `(tactic| exact parallelTestGoalHolds))
|
|
else
|
|
throwError "seed not changed (sequential execution detected)"
|
|
|
|
-- Tactic that immediately sets seed and succeeds
|
|
elab "set_seed_and_succeed" : tactic => do
|
|
IO.setRandSeed magicSeed
|
|
evalTactic (← `(tactic| exact parallelTestGoalHolds))
|
|
|
|
-- Register both tactics as user suggestions
|
|
-- High priority tactic: reset seed first (to ensure clean state), then return waiting tactic
|
|
@[local try_suggestion 900]
|
|
meta def waitAndCheckSolver (_goal : MVarId) (_info : Try.Info) : MetaM (Array (TSyntax `tactic)) := do
|
|
-- Reset to a different seed to ensure we're testing actual communication
|
|
IO.setRandSeed 0
|
|
return #[← `(tactic| wait_and_check_seed)]
|
|
|
|
-- Low priority tactic returns the seed-setting tactic
|
|
@[local try_suggestion 800]
|
|
meta def setFlagSolver (_goal : MVarId) (_info : Try.Info) : MetaM (Array (TSyntax `tactic)) := do
|
|
return #[← `(tactic| set_seed_and_succeed)]
|
|
|
|
-- If parallel: both tactics succeed (tactic 1 sees seed change after waiting)
|
|
-- If sequential: only tactic 2 succeeds (tactic 1 sees unchanged seed)
|
|
--
|
|
-- EXPECTED ON MASTER (sequential): Only one suggestion
|
|
-- EXPECTED ON try_par (parallel): Two suggestions
|
|
/--
|
|
info: Try these:
|
|
[apply] wait_and_check_seed
|
|
[apply] set_seed_and_succeed
|
|
-/
|
|
#guard_msgs in
|
|
example : ParallelTestGoal := by
|
|
try?
|