lean4-htt/tests/lean/run/try_parallelism.lean
Kim Morrison 1c1c534a03
feat: add solve_by_elim to try? tactic pipeline (#11462)
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>
2025-12-02 02:09:59 +00:00

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?