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 = and check_loop ctx ?want loc bs body =
scoped ctx (fun () -> scoped ctx (fun () ->
(* Each initial value is evaluated once, before the loop, exactly as a (* Each initial value is evaluated once, before the loop, exactly as a
[let]'s is and as [dotimes]'s bound is. *) [let]'s is and as [dotimes]'s bound is — and bound before the next is
let inits = checked, as a [let]'s is, so a later initialiser sees an earlier
name. *)
let binds =
map_lr map_lr
(fun (n, v) -> (fun (n, v) ->
let v = check ctx v in 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 fail v.Tast.loc "%s would be bound to %s, which is not a value" n
(Types.to_string v.Tast.ty) (Types.to_string v.Tast.ty)
| _ -> ()); | _ -> ());
(n, v)) (bind ctx n v.Tast.ty ~assignable:true, v))
bs bs
in 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 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 (* 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. *) is therefore monomorphic, and every other caller hands it a list. *)

View File

@ -99,4 +99,10 @@
(Some v) (recur (+ i 1) (+ acc v)) (Some v) (recur (+ i 1) (+ acc v))
None acc))) None acc)))
(println "") ; 0+1+2+3 = 6 (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) 0)

View File

@ -501,7 +501,7 @@ let () =
rather than running out of stack. The swap line is the other — recur rather than running out of stack. The swap line is the other — recur
rebinds every name at once, and interleaved writes would print 1. *) rebinds every name at once, and interleaved writes would print 1. *)
outputs "loop and recur" "programs/recur.flan" 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 (* 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 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 collection anywhere. The two show lines either side of it are the same