A loop's initialisers are checked in order and each sees the names bound before it, as a let's do

This commit is contained in:
Joseph Ferano 2026-09-25 07:06:17 +07:00
parent 3a3674efb7
commit b4e19bebf8
3 changed files with 12 additions and 7 deletions

View File

@ -4737,8 +4737,10 @@ and check_dotimes ctx ~want loc label name (b : Ast.bounds) body =
and check_loop ctx ?want loc bs body =
scoped ctx (fun () ->
(* Each initial value is evaluated once, before the loop, exactly as a
[let]'s is and as [dotimes]'s bound is. *)
let inits =
[let]'s is and as [dotimes]'s bound is — and bound before the next is
checked, as a [let]'s is, so a later initialiser sees an earlier
name. *)
let binds =
map_lr
(fun (n, v) ->
let v = check ctx v in
@ -4747,12 +4749,9 @@ and check_loop ctx ?want loc bs body =
fail v.Tast.loc "%s would be bound to %s, which is not a value" n
(Types.to_string v.Tast.ty)
| _ -> ());
(n, v))
(bind ctx n v.Tast.ty ~assignable:true, v))
bs
in
let binds =
List.map (fun (n, v) -> (bind ctx n v.Tast.ty ~assignable:true, v)) inits
in
let names = List.map (fun (slot, v) -> (slot, v.Tast.ty)) binds in
(* The singleton is [in_loop]'s doing: it sits in this recursive group and
is therefore monomorphic, and every other caller hands it a list. *)

View File

@ -99,4 +99,10 @@
(Some v) (recur (+ i 1) (+ acc v))
None acc)))
(println "") ; 0+1+2+3 = 6
;; The bindings are sequential, as a let's are: the second initialiser reads
;; the first name. Only the initial values are; recur still rebinds at once.
(print (loop [a 1 b (+ a 10)]
(if (> a 3) b (recur (+ a 1) (+ b a)))))
(println "") ; 11+1+2+3 = 17
0)

View File

@ -501,7 +501,7 @@ let () =
rather than running out of stack. The swap line is the other — recur
rebinds every name at once, and interleaved writes would print 1. *)
outputs "loop and recur" "programs/recur.flan"
"10\n2\n21\n8\n10000000\n64\n012\n0\n4\n012\n6\n";
"10\n2\n21\n8\n10000000\n64\n012\n0\n4\n012\n6\n17\n";
(* into. The count of pulls is the assertion a unit test cannot make: one
pass, one call per element per stage it reaches, and no intermediate
collection anywhere. The two show lines either side of it are the same