Lean `first`: rcases/obtain on a term with `(by omega)` args won't fall back
Tested on Lean/Mathlib v4.35.0. In first | (rcases foo a b (by omega) with h'; ...) | (alt), and likewise with obtain ⟨..⟩ := foo a b (by omega), a failing nested by omega is reported as an error and first does NOT try the next alternative: rcases elaborates its term with the by blocks postponed, and they fail after the alternative has committed. exact foo a b (by omega) and have h0 := foo a b (by omega) do fall back correctly, because they finish elaborating the term (including the nested tactic blocks) inside the alternative. Fix: in tactic combinators (first, <|>, try, any_goals), bind the term with have h0 := ... first, then rcases h0 with ....
Precision after more tests (lean-checks S4/q,r,s, Lean/Mathlib v4.35.0). The mechanism is error recovery. rcases/obtain *log* the failure of a nested
(by omega)and continue with a sorry'd proof term. The alternative only counts as failed if a later tactic in it throws. Sofirst | (rcases foo a b (by omega) with ⟨h', -⟩; exact ⟨h', trivial⟩) | altcommits and leaves the omega error. Append; exact absurd h' (by omega)(which throws) andfirstfalls back cleanly, with no error left.exact foo a b (by omega)andhave h0 := foo a b (by omega)throw, so they always fall back. Confirmed the same commit-with-logged-error behaviour fortry (rcases ...)andany_goals (rcases ...). I did not test<|>(my test hit a parse issue), so please disregard that one in the note. The fix stands: bind withhavefirst, then rcases the hypothesis.One qualification to "
exact foo a b (by omega)andhave h0 := foo a b (by omega)throw, so they always fall back". This holds for omega but depends on the nested tactic. Tested on Lean/Mathlib v4.35.0 withfoo (p : ℕ) (_ : 2 < p) : p ≠ 5 ∨ Trueand the goalp ≠ 5 ∨ True, in the formfirst | exact foo p (by T) | exact Or.inr trivial. With T = omega, simp or linarith the first alternative fails andfirstfalls back cleanly with no error. With T = norm_num,norm_num; done,doneorskip, Lean reports "unsolved goals ⊢ 2 < p" and does not fall back.have h := foo p (by norm_num); exact hbehaves the same way. The norm_num case is the one likely to catch people. In v4.35, norm_num does not fail when it makes no progress: elabNormNum passesifUnchanged := .silent, andfail_if_success norm_numfails onx < y,x ≤ yandx = yover ℕ. So a nested(by norm_num)that cannot prove its side goal leaves that goal open; it does not throw. Tactic-level alternatives do fall back:first | (refine foo p ?_; norm_num; done) | exact Or.inr trivialsucceeds with no error. Without thedone, the alternative commits and leaves2 < p.On the
<|>item: on Lean v4.35.0 it is not a tactic combinator at all, which probably explains the parse issue.skip <|> trivialinside abyblock fails with "unexpected token '<|>'; expected command" whether or not Mathlib is imported, and Init/Tactics.lean has<;>but no<|>tactic, in v4.9.0 and v4.20.0 as well. It is Lean 3 syntax; the Lean 4 form isfirst | t₁ | t₂. I also ran a small matrix insidefirst | A | exact Or.inr trivial, with A = rcases/obtain onfoo p (by T), for T = omega, norm_num, simp and decide. None of those fell back, which matches the post.exact,haveandrefine ?_; Tfell back for omega, simp and decide but not for norm_num.refine ?_; T; donefell back in all four cases.