This PR wraps the top-level command parser with `withPosition` to enforce indentation in `by` blocks, combined with an empty-by fallback for better error messages. This subsumes #3215 (which introduced `withPosition commandParser` but without the empty-by fallback). It is also related to #9524, which explores elaboration with empty tactic sequences — this PR reuses that idea for the empty-by fallback, so that a `by` not followed by an indented tactic produces an elaboration error (unsolved goals) rather than a parse error. **Changes:** - `topLevelCommandParserFn` now uses `(withPosition commandParser).fn`, setting the saved position at the start of each top-level command - `tacticSeqIndentGt` gains an empty tactic sequence fallback (`pushNone`) so that missing indentation produces an elaboration error (unsolved goals) instead of a parse error - `isEmptyBy` in `goalsAt?` removed: with strict `by` indentation, empty `by` blocks parse successfully via `pushNone` (producing empty nodes) rather than producing `.missing` syntax, making the `isEmptyBy` check dead code. The `isEmpty` helper in `isSyntheticTacticCompletion` continues to work correctly because it handles both `.missing` and empty nodes from `pushNone` (via the vacuously-true `args.all isEmpty` on `#[]`) - Test files updated to indent `by` blocks and expression continuations that were previously at column 0 **Behavior:** - Top-level `by` blocks now require indentation (column > 0 for commands at column 0) - Commands indented inside `section` require proofs to be indented past the command's column - `#guard_msgs in example : True := by` works because tactic indentation is checked against the outermost command's column - Expression continuations (not just `by`) must also be indented past the command, which is slightly more strict but more consistent - `have : True := by` followed by a dedent now correctly puts `this` in scope in the outer tactic block (the `have` is structurally complete with an unsolved-goal error, rather than a parse error) **Code changes observed in practice (lean4 test suite + Mathlib):** - `by` blocks: top-level `theorem ... := by` / `decreasing_by` followed by tactics at column 0 must be indented - `variable` continuations: `variable {A : Type*} [Foo A]\n{B : Type*}` where the second line starts at column 0 must be indented (most common category in Mathlib) - Expression continuations: `def f : T :=\nexpr` or `#synth Foo\n[args]` where the body/arguments start at column 0 - Structure literals: `.symm\n{ toFun := ...` where the struct literal starts at column 0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
74 lines
2.7 KiB
Text
74 lines
2.7 KiB
Text
/-!
|
|
A micro-benchmark based on `simp_bubblesort`, designed specifically to force simp to work with a lot
|
|
of congruence to reach for a very deep concrete expression.
|
|
-/
|
|
|
|
inductive V where | a | b
|
|
open V
|
|
|
|
axiom L : Type
|
|
axiom N : Type
|
|
axiom z : N
|
|
axiom s : N → N
|
|
axiom nil : L
|
|
axiom f : V → L → L
|
|
axiom iter : N → (L → L) → (L → L)
|
|
axiom combine : L → L → L
|
|
|
|
axiom swap : f b (f a xs) = f a (f b xs)
|
|
axiom iter_zero : iter z g x = g x
|
|
axiom iter_succ : iter (s i) g x = iter i g (iter i g x)
|
|
|
|
noncomputable def steps : N := s (s (s z)) -- smaller input size to focus on congr theorems
|
|
|
|
set_option maxRecDepth 100000
|
|
--set_option profiler true
|
|
set_option trace.Elab.async false
|
|
|
|
syntax "deep1(" num "," term "," term "," term ")" : term
|
|
|
|
macro_rules
|
|
| `(deep1($n, $f, $a, $e)) =>
|
|
match n.getNat with
|
|
| 0 => return a
|
|
| n + 1 => `($f deep1($(Lean.quote n), $f, $a, $e) $e)
|
|
|
|
-- Provoke regenerating simple congruence theorems unless they are cached or handled otherwise
|
|
|
|
/-
|
|
In an ideal world all of the below would be almost as fast as this, since we are just applying this
|
|
rewrite under a lot of congruence.
|
|
|
|
theorem foo :
|
|
iter steps (f b) (iter steps (f a) nil) =
|
|
iter steps (f a) (iter steps (f b) nil) := by
|
|
simp (maxSteps := 1000000) only [swap, iter_zero, iter_succ, steps]
|
|
-/
|
|
|
|
theorem deep_singular_simple (g : L → Unit → L) :
|
|
deep1(1024, g, iter steps (f b) (iter steps (f a) nil), ()) =
|
|
deep1(1024, g, iter steps (f a) (iter steps (f b) nil), ()) := by
|
|
simp (maxSteps := 1000000) only [swap, iter_zero, iter_succ, steps]
|
|
|
|
axiom g1 : L → Unit → L
|
|
|
|
theorem deep_singular_simple_const :
|
|
deep1(1024, g1, iter steps (f b) (iter steps (f a) nil), ()) =
|
|
deep1(1024, g1, iter steps (f a) (iter steps (f b) nil), ()) := by
|
|
simp (maxSteps := 1000000) only [swap, iter_zero, iter_succ, steps]
|
|
|
|
-- Provoke regenerating simple congruence theorems unless they are cached or handled otherwise,
|
|
-- adding `True` with the dependency on `x` here avoids a fast path in simp congruence theorem
|
|
-- generation as not all arguments are of kind fixed/eq anymore.
|
|
|
|
theorem deep_singular_prop_dep (g2 : (x : L) → (h : (fun _ => True) x) → L) :
|
|
deep1(1024, g2, iter steps (f b) (iter steps (f a) nil), True.intro) =
|
|
deep1(1024, g2, iter steps (f a) (iter steps (f b) nil), True.intro) := by
|
|
simp (maxSteps := 1000000) only [swap, iter_zero, iter_succ, steps]
|
|
|
|
axiom g2 : (x : L) → (h : (fun _ => True) x) → L
|
|
|
|
theorem deep_singular_prop_const_dep :
|
|
deep1(1024, g2, iter steps (f b) (iter steps (f a) nil), True.intro) =
|
|
deep1(1024, g2, iter steps (f a) (iter steps (f b) nil), True.intro) := by
|
|
simp (maxSteps := 1000000) only [swap, iter_zero, iter_succ, steps]
|