lean4-htt/tests/elab/newfrontend1.lean
Joachim Breitner 06fb4bec52
feat: require indentation in commands, allow empty tactic sequences (#13229)
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>
2026-04-08 14:05:47 +00:00

425 lines
8.7 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

set_option warn.classDefReducibility false
def x := 1
#check x
variable {α : Type}
def f (a : α) : α :=
a
def tst (xs : List Nat) : Nat :=
xs.foldl (init := 10) (· + ·)
#check tst [1, 2, 3]
#check fun x y : Nat => x + y
#check tst
#check (fun stx => if True then let e := stx; Pure.pure e else Pure.pure stx : Nat → Id Nat)
#check let x : Nat := 1; x
def foo (a : Nat) (b : Nat := 10) (c : Bool := Bool.true) : Nat :=
a + b
set_option pp.all true
#check foo 1
#check foo 3 (c := false)
def Nat.boo (a : Nat) :=
succ a -- succ here is resolved as `Nat.succ`.
#check Nat.boo
#check true
-- apply is still a valid identifier name
def apply := "hello"
#check apply
theorem simple1 (x y : Nat) (h : x = y) : x = y :=
by {
assumption
}
theorem simple2 (x y : Nat) : x = y → x = y :=
by {
intro h;
assumption
}
syntax "intro2" : tactic
macro_rules
| `(tactic| intro2) => `(tactic| intro; intro )
theorem simple3 (x y : Nat) : x = x → x = y → x = y :=
by {
intro2;
assumption
}
macro "intro3" : tactic => `(tactic| (intro; intro; intro))
macro "check2" x:term : command => `(#check $x #check $x)
macro "foo" x:term "," y:term : term => `($x + $y + $x)
set_option pp.all false
check2 0+1
check2 foo 0,1
theorem simple4 (x y : Nat) : y = y → x = x → x = y → x = y :=
by {
intro3;
assumption
}
theorem simple5 (x y z : Nat) : y = z → x = x → x = y → x = z :=
by {
intro h1; intro _; intro h3;
exact Eq.trans h3 h1
}
theorem simple6 (x y z : Nat) : y = z → x = x → x = y → x = z :=
by {
intro h1; intro _; intro h3;
refine Eq.trans ?_ h1;
assumption
}
theorem simple7 (x y z : Nat) : y = z → x = x → x = y → x = z :=
by {
intro h1; intro _; intro h3;
refine' Eq.trans ?pre ?post;
exact y;
{ exact h3 }
{ exact h1 }
}
theorem simple8 (x y z : Nat) : y = z → x = x → x = y → x = z := by
intro h1; intro _; intro h3
refine' Eq.trans ?pre ?post
case post => exact h1
case pre => exact h3
theorem simple9 (x y z : Nat) : y = z → x = x → x = y → x = z := by
intro h1 _ h3
trace_state
focus
refine' Eq.trans ?pre ?post
first
| exact h1
assumption
| exact y
exact h3
assumption
theorem simple9b (x y z : Nat) : y = z → x = x → x = y → x = z := by
intro h1 _ h3
trace_state
focus
refine' Eq.trans ?pre ?post
first
| exact h1
| exact y; exact h3
assumption
theorem simple9c (x y z : Nat) : y = z → x = x → x = y → x = z := by
intro h1 _ h3
solve
| exact h1
| refine' Eq.trans ?pre ?post; exact y; exact h3; assumption
| exact h3
theorem simple9d (x y z : Nat) : y = z → x = x → x = y → x = z := by
intro h1 _ h3
refine' Eq.trans ?pre ?post
solve
| exact h1
| exact y
| exact h3
solve
| exact h1
| exact h3
solve
| exact h1
| assumption
namespace Foo
def Prod.mk := 1
#check (⟨2, 3⟩ : Prod _ _)
end Foo
theorem simple10 (x y z : Nat) : y = z → x = x → x = y → x = z :=
by {
intro h1; intro h2; intro h3;
skip;
apply Eq.trans;
exact h3;
assumption
}
theorem simple11 (x y z : Nat) : y = z → x = x → x = y → x = z :=
by {
intro h1; intro h2; intro h3;
apply @Eq.trans;
trace_state;
exact h3;
assumption
}
theorem simple12 (x y z : Nat) : y = z → x = x → x = y → x = z :=
by {
intro h1; intro h2; intro h3;
apply @Eq.trans;
try exact h1; -- `exact h1` fails
trace_state;
try exact h3;
trace_state;
try exact h1;
}
theorem simple13 (x y z : Nat) : y = z → x = x → x = y → x = z := by
intro h1 h2 h3
trace_state
apply @Eq.trans
case b => exact y
trace_state
repeat assumption
theorem simple13b (x y z : Nat) : y = z → x = x → x = y → x = z := by {
intro h1 h2 h3;
trace_state;
apply @Eq.trans;
case b => exact y;
trace_state;
repeat assumption
}
theorem simple14 (x y z : Nat) : y = z → x = x → x = y → x = z := by
intros
apply @Eq.trans
case b => exact y
repeat assumption
theorem simple15 (x y z : Nat) : y = z → x = x → x = y → x = z :=
by {
intro h1 h2 h3;
revert y;
intro y h1 h3;
apply Eq.trans;
exact h3;
exact h1
}
theorem simple16 (x y z : Nat) : y = z → x = x → x = y → x = z :=
by {
intro h1 h2 h3;
try clear x; -- should fail
clear h2;
trace_state;
apply Eq.trans;
exact h3;
exact h1
}
macro "blabla" : tactic => `(tactic| assumption)
-- Tactic head symbols do not become reserved words
def blabla := 100
#check blabla
theorem simple17 (x : Nat) (h : x = 0) : x = 0 :=
by blabla
theorem simple18 (x : Nat) (h : x = 0) : x = 0 :=
by blabla
theorem simple19 (x y : Nat) (h₁ : x = 0) (h₂ : x = y) : y = 0 :=
by subst x; subst y; exact rfl
theorem tstprec1 (x y z : Nat) : x + y * z = x + (y * z) :=
rfl
theorem tstprec2 (x y z : Nat) : y * z + x = (y * z) + x :=
rfl
set_option pp.all true
#check fun {α} (a : α) => a
#check @(fun α (a : α) => a)
#check
let myid := fun {α} (a : α) => a;
myid [myid 1]
-- In the following example, we need `@` otherwise we will try to insert mvars for α and [Add α],
-- and will fail to generate instance for [Add α]
#check @(fun α (s : Add α) (a : α) => a + a)
def g1 {α} (a₁ a₂ : α) {β} (b : β) : α × α × β :=
(a₁, a₂, b)
def id1 : {α : Type} → αα :=
fun x => x
def listId : List ({α : Type} → αα) :=
(fun x => x) :: []
def id2 : {α : Type} → αα :=
@(fun α (x : α) => id1 x)
def id3 : {α : Type} → αα :=
@(fun α x => id1 x)
def id4 : {α : Type} → αα :=
fun x => id1 x
def id5 : {α : Type} → αα :=
fun {α} x => id1 x
def id6 : {α : Type} → αα :=
@(fun {α} x => id1 x)
def id7 : {α : Type} → αα :=
fun {α} x => @id α x
def id8 : {α : Type} → αα :=
fun {α} x => id (@id α x)
def altTst1 {m σ} [Alternative m] [Monad m] : Alternative (StateT σ m) :=
⟨StateT.failure, StateT.orElse⟩
def altTst2 {m σ} [Alternative m] [Monad m] : Alternative (StateT σ m) :=
⟨@(fun α => StateT.failure), @(fun α => StateT.orElse)⟩
def altTst3 {m σ} [Alternative m] [Monad m] : Alternative (StateT σ m) :=
⟨fun {α} => StateT.failure, fun {α} => StateT.orElse⟩
#check_failure 1 + true
/-
universe u v
/-
MonadFunctorT.{u ?M_1 v} (λ (β : Type u), m α) (λ (β : Type u), m' α) n n'
-/
set_option pp.raw.maxDepth 100
set_option trace.Elab true
def adapt {m m' σ σ'} {n n' : Type → Type} [MonadFunctor m m' n n'] [MonadStateAdapter σ σ' m m'] : MonadStateAdapter σ σ' n n' :=
⟨fun split join => monadMap (adaptState split join : m α → m' α)⟩
-/
syntax "fn" (term:max)+ "=>" term : term
macro_rules
| `(fn $xs* => $b) => `(fun $xs* => $b)
set_option pp.all false
#check fn x => x+1
#check fn α (a : α) => a
def tst1 : {α : Type} → αα :=
@(fn α a => a)
#check @tst1
syntax ident "==>" term : term
syntax "{" ident "}" "==>" term : term
macro_rules
| `($x:ident ==> $b) => `(fn $x => $b)
| `({$x:ident} ==> $b) => `(fun {$x:ident} => $b)
#check x ==> x+1
def tst2a : {α : Type} → αα :=
@(α ==> a ==> a)
def tst2b : {α : Type} → αα :=
{α} ==> a ==> a
#check @tst2a
#check @tst2b
def tst3a : {α : Type} → {β : Type} → α → β → α × β :=
@(α ==> @(β ==> a ==> b ==> (a, b)))
def tst3b : {α : Type} → {β : Type} → α → β → α × β :=
{α} ==> {β} ==> a ==> b ==> (a, b)
syntax "function" (term:max)+ "=>" term : term
macro_rules
| `(function $xs* => $b) => `(@(fun $xs* => $b))
def tst4 : {α : Type} → {β : Type} → α → β → α × β :=
function α β a b => (a, b)
theorem simple20 (x y z : Nat) : y = z → x = x → x = y → x = z :=
by intro h1 h2 h3;
try clear x; -- should fail
clear h2;
trace_state;
apply Eq.trans;
exact h3;
exact h1
theorem simple21 (x y z : Nat) : y = z → x = x → y = x → x = z :=
fun h1 _ h3 =>
have : x = y := by { apply Eq.symm; assumption };
Eq.trans this (by assumption)
theorem simple22 (x y z : Nat) : y = z → y = x → id (x = z + 0) :=
fun h1 h2 => show x = z + 0 by
apply Eq.trans
exact h2.symm
assumption
skip
theorem simple23 (x y z : Nat) : y = z → x = x → y = x → x = z :=
fun h1 _ h3 =>
have : x = y := by apply Eq.symm; assumption
Eq.trans this (by assumption)
theorem simple24 (x y z : Nat) : y = z → x = x → y = x → x = z :=
fun h1 _ h3 =>
have h : x = y := by apply Eq.symm; assumption
Eq.trans h (by assumption)
def f1 (x : Nat) : Nat :=
let double x := x + x
let rec loop x :=
match x with
| 0 => 0
| x+1 => loop x + double x
loop x
#eval f1 5
def f2 (x : Nat) : String :=
let bad x : String := toString x
bad x
def f3 x y :=
x + y + 1
theorem f3eq x y : f3 x y = x + y + 1 :=
rfl
def f4 (x y : Nat) : String :=
if x > y + 1 then "hello" else "world"