feat: make repeat and while syntax builtin (#13442)

This PR promotes the `repeat`, `while`, and `repeat ... until` parsers
from `syntax` declarations in `Init.While` to `@[builtin_doElem_parser]`
definitions in `Lean.Parser.Do`, alongside the other do-element parsers.
The `while` variants and `repeat ... until` get `@[builtin_macro]`
expansions; `repeat` itself gets a `@[builtin_doElem_elab]` so a
follow-up can extend it with an option-driven choice between `Loop.mk`
and a well-founded `Repeat.mk`.

The new builtin parsers are registered at `low` priority so that the
bootstrapping `syntax` declarations in `Init.While` (still needed for
stage0 compatibility) take precedence during the transition. After the
next stage0 update, the `Init.While` syntax and macros can be removed.
This commit is contained in:
Sebastian Graf 2026-04-17 17:19:59 +02:00 committed by GitHub
parent f180c9ce17
commit 704df340cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 35 additions and 11 deletions

View file

@ -59,9 +59,9 @@ Examples:
* `Nat.repeat f 3 a = f <| f <| f <| a` * `Nat.repeat f 3 a = f <| f <| f <| a`
* `Nat.repeat (· ++ "!") 4 "Hello" = "Hello!!!!"` * `Nat.repeat (· ++ "!") 4 "Hello" = "Hello!!!!"`
-/ -/
@[specialize, expose] def repeat {α : Type u} (f : αα) : (n : Nat) → (a : α) → α @[specialize, expose] def «repeat» {α : Type u} (f : αα) : (n : Nat) → (a : α) → α
| 0, a => a | 0, a => a
| succ n, a => f (repeat f n a) | succ n, a => f («repeat» f n a)
/-- /--
Applies a function to a starting value the specified number of times. Applies a function to a starting value the specified number of times.
@ -1221,9 +1221,9 @@ theorem not_lt_eq (a b : Nat) : (¬ (a < b)) = (b ≤ a) :=
theorem not_gt_eq (a b : Nat) : (¬ (a > b)) = (a ≤ b) := theorem not_gt_eq (a b : Nat) : (¬ (a > b)) = (a ≤ b) :=
not_lt_eq b a not_lt_eq b a
@[csimp] theorem repeat_eq_repeatTR : @repeat = @repeatTR := @[csimp] theorem repeat_eq_repeatTR : @«repeat» = @repeatTR :=
funext fun α => funext fun f => funext fun n => funext fun init => funext fun α => funext fun f => funext fun n => funext fun init =>
let rec go : ∀ m n, repeatTR.loop f m (repeat f n init) = repeat f (m + n) init let rec go : ∀ m n, repeatTR.loop f m («repeat» f n init) = «repeat» f (m + n) init
| 0, n => by simp [repeatTR.loop] | 0, n => by simp [repeatTR.loop]
| succ m, n => by rw [repeatTR.loop, succ_add]; exact go m (succ n) | succ m, n => by rw [repeatTR.loop, succ_add]; exact go m (succ n)
(go n 0).symm (go n 0).symm

View file

@ -17,17 +17,28 @@ namespace Lean.Elab.Do
open Lean.Parser.Term open Lean.Parser.Term
/-- /--
Builtin do-element elaborator for `repeat` (syntax kind `Lean.doRepeat`). Builtin do-element elaborator for `repeat` (syntax kind `Lean.Parser.Term.doRepeat`).
Expands to `for _ in Loop.mk do ...`. A follow-up PR will drop the fallback macro Expands to `for _ in Loop.mk do ...`. A follow-up change will extend this
in `Init.While` (which currently preempts this elaborator) and extend this
elaborator to choose between `Loop.mk` and a well-founded `Repeat.mk` based on a elaborator to choose between `Loop.mk` and a well-founded `Repeat.mk` based on a
configuration option. configuration option.
-/ -/
@[builtin_doElem_elab Lean.doRepeat] def elabDoRepeat : DoElab := fun stx dec => do @[builtin_doElem_elab Lean.Parser.Term.doRepeat] def elabDoRepeat : DoElab := fun stx dec => do
let `(doElem| repeat $seq) := stx | throwUnsupportedSyntax let `(doElem| repeat%$tk $seq) := stx | throwUnsupportedSyntax
let expanded ← `(doElem| for _ in Loop.mk do $seq) let expanded ← `(doElem| for%$tk _ in Loop.mk do $seq)
Term.withMacroExpansion stx expanded <| Term.withMacroExpansion stx expanded <|
withRef expanded <| elabDoElem ⟨expanded⟩ dec 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)
| _ => Macro.throwUnsupported
@[builtin_macro Lean.Parser.Term.doRepeatUntil] def expandDoRepeatUntil : Macro
| `(doElem| repeat%$tk $seq until $cond) => `(doElem| repeat%$tk do $seq:doSeq; if $cond then break)
| _ => Macro.throwUnsupported
end Lean.Elab.Do end Lean.Elab.Do

View file

@ -1782,7 +1782,7 @@ mutual
doIfToCode doElem doElems doIfToCode doElem doElems
else if k == ``Parser.Term.doUnless then else if k == ``Parser.Term.doUnless then
doUnlessToCode doElem doElems doUnlessToCode doElem doElems
else if k == `Lean.doRepeat then else if k == ``Parser.Term.doRepeat then
let seq := doElem[1] let seq := doElem[1]
let expanded ← `(doElem| for _ in Loop.mk do $seq) let expanded ← `(doElem| for _ in Loop.mk do $seq)
doSeqToCode (expanded :: doElems) doSeqToCode (expanded :: doElems)

View file

@ -273,6 +273,18 @@ with debug assertions enabled (see the `debugAssertions` option).
@[builtin_doElem_parser] def doDebugAssert := leading_parser:leadPrec @[builtin_doElem_parser] def doDebugAssert := leading_parser:leadPrec
"debug_assert! " >> termParser "debug_assert! " >> termParser
-- Lower priority than the bootstrapping `syntax` declarations in `Init.While` so that, during the
-- transition period where both exist, only the `Init.While` parser fires (no `choice` ambiguity).
-- After the next stage0 update, `Init.While` syntax will be removed and these become sole parsers.
@[builtin_doElem_parser low] def doRepeat := leading_parser
"repeat " >> doSeq
@[builtin_doElem_parser low] def doWhileH := leading_parser
"while " >> ident >> " : " >> withForbidden "do" termParser >> " do " >> doSeq
@[builtin_doElem_parser low] def doWhile := leading_parser
"while " >> withForbidden "do" termParser >> " do " >> doSeq
@[builtin_doElem_parser low] def doRepeatUntil := leading_parser
"repeat " >> doSeq >> ppDedent ppLine >> "until " >> termParser
/- /-
We use `notFollowedBy` to avoid counterintuitive behavior. We use `notFollowedBy` to avoid counterintuitive behavior.

View file

@ -1,5 +1,6 @@
#include "util/options.h" #include "util/options.h"
// bump
namespace lean { namespace lean {
options get_default_options() { options get_default_options() {
options opts; options opts;