feat: support while let in do blocks via unified condition syntax (#13534)

This PR generalizes the `while` syntax in `do` blocks so that the
condition can be any `doIfCond`, the same condition form already
accepted by `if`. As a result, `while let pat := e do …` and `while let
pat ← e do …` are now supported in addition to `while cond do …` and
`while h : cond do …`. The previously separate `doWhile` and `doWhileH`
parsers and their accompanying macros are unified into a single
`doWhile` parser whose macro delegates to the existing `doIf`
desugaring.
This commit is contained in:
Sebastian Graf 2026-04-27 11:23:36 +02:00 committed by GitHub
parent 3c6317b6d7
commit 4d7b7dd8e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 71 additions and 8 deletions

View file

@ -36,12 +36,8 @@ remove the following code without breaking the do block's type.
Term.withMacroExpansion stx expanded <|
withRef expanded <| elabDoElem ⟨expanded⟩ dec
@[builtin_macro Lean.Parser.Term.doWhileH] def expandDoWhileH : Macro
| `(doElem| while%$tk $h : $cond do $seq) => `(doElem| repeat%$tk if $h:ident : $cond then $seq else break)
| _ => Macro.throwUnsupported
@[builtin_macro Lean.Parser.Term.doWhile] def expandDoWhile : Macro
| `(doElem| while%$tk $cond do $seq) => `(doElem| repeat%$tk if $cond then $seq else break)
| `(doElem| while%$tk $cond:doIfCond do $seq) => `(doElem| repeat%$tk if $cond:doIfCond then $seq else break)
| _ => Macro.throwUnsupported
@[builtin_macro Lean.Parser.Term.doRepeatUntil] def expandDoRepeatUntil : Macro

View file

@ -275,10 +275,8 @@ with debug assertions enabled (see the `debugAssertions` option).
@[builtin_doElem_parser] def doRepeat := leading_parser
"repeat " >> doSeq
@[builtin_doElem_parser] def doWhileH := leading_parser
"while " >> ident >> " : " >> withForbidden "do" termParser >> " do " >> doSeq
@[builtin_doElem_parser] def doWhile := leading_parser
"while " >> withForbidden "do" termParser >> " do " >> doSeq
"while " >> withForbidden "do" doIfCond >> " do " >> doSeq
@[builtin_doElem_parser] def doRepeatUntil := leading_parser
"repeat " >> doSeq >> ppDedent ppLine >> "until " >> termParser

View file

@ -26,6 +26,7 @@ options get_default_options() {
// Temporary, core-only flags for editing (i.e. must be part of stage0/bin/lean). Must be synced
// with `LEAN_EXTRA_MAKE_OPTS` build flags in src/CMakeLists.txt.
opts = opts.update({"backward", "do", "legacy"}, false);
// trigger stage0 update: unify `while` parser via `doIfCond`
#endif
return opts;
}

View file

@ -48,3 +48,71 @@ test3 done 12
-/
#guard_msgs in
#eval test3
-- `while let pat := e do ...` exits when the pattern fails to match.
def testWhileLet : IO Unit := do
let mut xs := [0, 1, 2, 3]
while let x :: rest := xs do
println! "{x}"
xs := rest
println! "done {xs.length}"
/--
info: 0
1
2
3
done 0
-/
#guard_msgs in
#eval testWhileLet
-- `while let pat ← e do ...` evaluates the bind on each iteration.
def testWhileLetBind : IO Unit := do
let mut xs := [0, 1, 2, 3]
while let some x ← pure xs.head? do
println! "{x}"
xs := xs.tail
println! "done"
/--
info: 0
1
2
3
done
-/
#guard_msgs in
#eval testWhileLetBind
-- Pops entries from an `IO.Ref`-backed list to show that the bound expression
-- in `while let pat ← e do ...` is re-evaluated each iteration.
def testWhileLetBindRef : IO Unit := do
let r ← IO.mkRef [0, 1, 2, 3]
while let x :: rest ← r.get do
println! "{x}"
r.set rest
println! "done {(← r.get).length}"
/--
info: 0
1
2
3
done 0
-/
#guard_msgs in
#eval testWhileLetBindRef
-- Unified `while` keeps supporting the `while h : cond do ...` form.
def testWhileH (xs : Array Nat) : Nat := Id.run do
let mut i := 0
let mut sum := 0
while h : i < xs.size do
sum := sum + xs[i]
i := i + 1
return sum
/-- info: 6 -/
#guard_msgs in
#eval testWhileH #[1, 2, 3]