From d138155eae82e1778484207fefcdce7c9dffcf5e Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 26 Sep 2026 16:08:08 +0700 Subject: [PATCH 1/4] A dyn operand anywhere in an arithmetic, bitwise or comparison fold makes the fold dyn from there on, and min or max refuses one past its first pair as it does in it. --- TODO.org | 3 - lib/check.ml | 93 +++++++++++++++++++--------- test/programs/dyn-fold-position.flan | 33 ++++++++++ test/test_acceptance.ml | 13 ++++ test/test_flan.ml | 9 +++ 5 files changed, 120 insertions(+), 31 deletions(-) create mode 100644 test/programs/dyn-fold-position.flan diff --git a/TODO.org b/TODO.org index 01421891..6f5d96bb 100644 --- a/TODO.org +++ b/TODO.org @@ -787,9 +787,6 @@ One spelling for one operation; != stays, and not= is refused with a suggestion of !=. * Checker -** TODO A dyn operand past the second in a + fold is converted to the running type -=(+ 1 2 d)= with d a dyn char prints 100: the dyn is unboxed to i32 before adding, where -rule 117 says typed beside dyn gives dyn (=(+ 3 d)= gives =\d=). Predates the char lane. ** WAIT Checking a wide fold of let operands is slow Parked 2026-09-26: design first; remeasure on a quiet machine, it was timed under load 20. A 2000-operand (bit-and (let …) …) takes 32 s to check (37 s before the bit operators); diff --git a/lib/check.ml b/lib/check.ml index 5dacae87..6331cba6 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -11427,21 +11427,30 @@ and fold_left_prim ctx ~want loc name p ~needs ok what args = (* Left to right, each step char arithmetic when a char is in it and the ordinary join when none is: (- \z \a 1) is 25 - 1, and (+ 1 2 \a) is 3 + \a. *) - let step acc (arg : Ast.expr) = - let lit = char_lit arg in - untyped := !untyped && int_lit arg; - if is_char acc || maybe_char ctx arg || lit then - let v = check ctx arg in - if is_char acc || is_char v then char_step ~want:nwant loc name acc v - else mk loc acc.Tast.ty (Tast.Prim (p, [ acc; expect ctx arg.Ast.loc ~want:(Some acc.Tast.ty) v ])) - else - mk loc acc.Tast.ty (Tast.Prim (p, [ acc; check ctx ~want:acc.Tast.ty arg ])) + let rec steps acc = function + | [] -> expect ctx loc ~want acc + | (arg : Ast.expr) :: tl -> + let lit = char_lit arg in + untyped := !untyped && int_lit arg; + if is_char acc || maybe_char ctx arg || lit then + let v = check ctx arg in + if v.Tast.ty = Types.Dyn then dyn_fold ctx ~want loc name [ acc; v ] tl + else if is_char acc || is_char v then + steps (char_step ~want:nwant loc name acc v) tl + else + match fold_operand ctx acc.Tast.ty (expect ctx arg.Ast.loc ~want:(Some acc.Tast.ty) v) with + | `Typed v -> steps (mk loc acc.Tast.ty (Tast.Prim (p, [ acc; v ]))) tl + | `Dyn d -> dyn_fold ctx ~want loc name [ acc; d ] tl + else + match fold_operand ctx acc.Tast.ty (check ctx ~want:acc.Tast.ty arg) with + | `Typed v -> steps (mk loc acc.Tast.ty (Tast.Prim (p, [ acc; v ]))) tl + | `Dyn d -> dyn_fold ctx ~want loc name [ acc; d ] tl in let first = if is_char a || is_char b then char_step ~want:nwant loc name a b else mk loc a.Tast.ty (Tast.Prim (p, [ a; b ])) in - expect ctx loc ~want (List.fold_left step first rest) + steps first rest else if a.Tast.ty = Types.Dyn || b.Tast.ty = Types.Dyn then dyn_fold ctx ~want loc name [ a; b ] rest else begin @@ -11457,16 +11466,27 @@ and fold_left_prim ctx ~want loc name p ~needs ok what args = answered again, per copy, at the instantiation. *) if not (ok a.Tast.ty || generic_ty a.Tast.ty) then not_numeric name what a; let ty = a.Tast.ty in - let acc = - List.fold_left - (fun acc arg -> - mk loc ty (Tast.Prim (p, [ acc; check ctx ~want:ty arg ]))) - (mk loc ty (Tast.Prim (p, [ a; b ]))) - rest + let rec steps acc = function + | [] -> expect ctx loc ~want acc + | arg :: tl -> + match fold_operand ctx ty (check ctx ~want:ty arg) with + | `Typed v -> steps (mk loc ty (Tast.Prim (p, [ acc; v ]))) tl + | `Dyn d -> dyn_fold ctx ~want loc name [ acc; d ] tl in - expect ctx loc ~want acc + steps (mk loc ty (Tast.Prim (p, [ a; b ]))) rest end +(* An operand past a fold's first pair, checked at the type so far. A dyn one + is taken back as the dyn it was, not opened at that type: a typed operand + beside a dyn gives dyn (rule 117), so from there on the fold is the dyn + runtime's, and (+ 1 2 d) is (+ 3 d). *) +and fold_operand ctx ty (v : Tast.expr) = + if v.Tast.ty = Types.Dyn && not (Types.equal ty Types.Dyn) then `Dyn v + else + match opened_dyn ~box:(to_dyn ctx) v with + | Some d -> `Dyn d + | None -> `Typed v + (* A pair an arithmetic operator refused, when one operand is a char: that is the refusal to give, rather than the mismatch between the two. Asked only after the refusal, so a pair that checks costs nothing more. *) @@ -13026,7 +13046,8 @@ and named_call ?(qualified = false) ctx ~want loc name args = two things being unalike is the answer to "are these equal", not an error. The orderings do trap, and rightly — there is no true answer to whether a string is less than a vector. *) - if a.Tast.ty = Types.Dyn || b.Tast.ty = Types.Dyn then begin + (* [ops] are every operand, boxed. *) + let dyn_chain ops = let sym = match name with | "=" | "!=" -> "flan_dyn_eq" @@ -13053,15 +13074,15 @@ and named_call ?(qualified = false) ctx ~want loc name args = let r = match rest with | [] -> link (box loc a) (box loc b) - | _ -> - let ops = - box loc a :: box loc b - :: map_lr (fun e -> box loc (check ctx ~want:Types.Dyn e)) rest - in - cmp_over ctx loc Types.Dyn ~pairs ~link ops + | _ -> cmp_over ctx loc Types.Dyn ~pairs ~link (ops ()) in expect ctx loc ~want r - end else begin + in + if a.Tast.ty = Types.Dyn || b.Tast.ty = Types.Dyn then + dyn_chain (fun () -> + box loc a :: box loc b + :: map_lr (fun e -> box loc (check ctx ~want:Types.Dyn e)) rest) + else begin (* [=] and [!=] admit types [<] does not. A handle is one: a pair of numbers in one word and where being the same entity is the question the type exists to answer — ordering handles would order a slot index, @@ -13096,8 +13117,18 @@ and named_call ?(qualified = false) ctx ~want loc name args = | _ -> let ty = a.Tast.ty in let link u v = mk loc Types.Bool (Tast.Prim (p, [ u; v ])) in - let ops = a :: b :: map_lr (fun e -> check ctx ~want:ty e) rest in - expect ctx loc ~want (cmp_over ctx loc ty ~pairs ~link ops) + let rest = map_lr (fun e -> fold_operand ctx ty (check ctx ~want:ty e)) rest in + (* A dyn past the first pair makes the whole chain the dyn runtime's, + for [fold_operand]'s reason: a chain is its pairs, and a pair with a + dyn in it is a dyn comparison. *) + if List.exists (function `Dyn _ -> true | `Typed _ -> false) rest then + dyn_chain (fun () -> + box loc a :: box loc b + :: List.map (function `Dyn d -> d | `Typed v -> box loc v) rest) + else + let ops = + a :: b :: List.map (function `Typed v -> v | `Dyn d -> d) rest in + expect ctx loc ~want (cmp_over ctx loc ty ~pairs ~link ops) end | "not" -> arity ctx loc name 1 args; @@ -13236,7 +13267,13 @@ and named_call ?(qualified = false) ctx ~want loc name args = [ mk loc ty (Tast.If (test, la, lb)) ])) in expect ctx loc ~want - (List.fold_left (fun acc arg -> pick acc (check ctx ~want:ty arg)) + (List.fold_left + (fun acc arg -> + (* A dyn is refused here as it is in the first pair, rather than + opened at the type so far. *) + match fold_operand ctx ty (check ctx ~want:ty arg) with + | `Typed v -> pick acc v + | `Dyn d -> not_numeric name "numbers" d; acc) (pick a b) rest) (* A type handed to the prelude's slice reductions: the reach for the type-limit constants under the name of the reduction beside them. *) diff --git a/test/programs/dyn-fold-position.flan b/test/programs/dyn-fold-position.flan new file mode 100644 index 00000000..e3e80b7f --- /dev/null +++ b/test/programs/dyn-fold-position.flan @@ -0,0 +1,33 @@ +;;;; A dyn operand anywhere in a fold makes the fold dyn from there on: the +;;;; typed operands before it are folded typed, and the dyn runtime takes the +;;;; rest. (+ 1 2 d) is (+ 3 d), whichever position the dyn is in. + +(defn as-i32 [x i32] i32 x) +(defn as-f64 [x f64] f64 x) + +(defn main [] i32 + (let [d (the dyn \a) + f (the dyn 2.5) + n (the dyn 4) + c \a] + ;; + and - over a dyn char: first, middle, last. + (println (+ d 1 2) (+ 1 d 2) (+ 1 2 d)) + (println (- d 1 2) (- 10 n 1) (- 10 1 n)) + ;; A dyn float past an integer pair promotes, as (* 6 f) does. + (println (* f 2 3) (* 2 f 3) (* 2 3 f)) + (println (/ f 2 5) (/ 9 f 2) (/ 9 2 f)) + (println (+ 1 2 3 f) (- 10 1 2 f)) + ;; A char fold with a dyn past the pair. + (println (+ c 1 n) (- c 1 n) (+ c 1 2 n)) + ;; The bitwise folds. + (println (bit-or n 1 2) (bit-or 1 n 2) (bit-or 1 2 n)) + (println (bit-and n 7 6) (bit-and 7 n 6) (bit-and 7 6 n)) + (println (bit-xor n 1 2) (bit-xor 1 n 2) (bit-xor 1 2 n)) + ;; Comparison chains: a float past an integer pair is compared as one. + (println (< f 3 4) (< 1 f 3) (< 1 2 f) (< 1 3 f)) + (println (<= 2 2 f) (> 3 2 f) (>= 3 3 f) (>= 3 3 n)) + (println (= 4 4 n) (= 4 n 4) (= n 4 4) (= 4 4 f)) + (println (!= 1 2 n) (!= 1 4 n) (!= 2 f 3)) + ;; At a typed want the dyn answer is opened at the end. + (println (as-i32 (+ 1 2 n)) (as-f64 (* 2 3 f)))) + 0) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index f05d9a78..522e9ae8 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -5610,6 +5610,19 @@ level "1" char_arith_out; outputs ~x86:true "char: arithmetic, --x86" "programs/char-arith.flan" char_arith_out; + (* A dyn anywhere in a fold makes it dyn from there on (rule 117): each + family with the dyn first, in the middle and last. *) + let fold_out = + "d d d\n^ 5 5\n15 15 15\n0.25 1.8 1.6\n8.5 4.5\nf \\ h\n7 7 7\n\ + 4 4 4\n7 7 7\ntrue true true false\ntrue false true false\n\ + true true true false\ntrue false true\n7 15\n" + in + outputs "dyn: a dyn in any fold position" "programs/dyn-fold-position.flan" + fold_out; + outputs ~opt:"-O0" "dyn: a dyn in any fold position, -O0" + "programs/dyn-fold-position.flan" fold_out; + outputs ~x86:true "dyn: a dyn in any fold position, --x86" + "programs/dyn-fold-position.flan" fold_out; List.iter (fun x86 -> let exe = compile ~x86 "programs/char-arith.flan" in diff --git a/test/test_flan.ml b/test/test_flan.ml index 2e8d3f8b..60ede47d 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -8450,5 +8450,14 @@ let () = "(defn f [n i32] (Option i32) (cond (= n 1) 10 (= n 2) 20))\n(defn main [] ())"; rejects_check "a map's get takes one key" ~needle:"a map's get takes one key" "(defn main [] () (let [m (map-new i32 i32)] (println (get m 1 2))))"; + (* min and max have no dyn lowering: a dyn past the first pair is refused + as one in it is, not opened at the type so far. *) + List.iter + (fun op -> + rejects_check (op ^ " refuses a dyn past its first pair") + ~needle:(op ^ " takes numbers, found dyn") + (Printf.sprintf + "(defn main [] () (let [f (the dyn 2.5)] (println (%s 3 4 f))))" op)) + [ "min"; "max" ]; Test_support.report () From 97e89c8d40f9c7d5882d40f1e6f50a5e58decb1b Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 26 Sep 2026 16:15:52 +0700 Subject: [PATCH 2/4] min and max take a dyn in any position, picking numbers by value and chars by code point in the dyn runtime and trapping at the form on anything else. --- lib/check.ml | 26 +++++++++++++++----------- lib/emit.ml | 2 ++ runtime/flan_dyn.c | 24 ++++++++++++++++++++++++ runtime/flan_dyn.h | 5 +++++ test/programs/dyn-fold-position.flan | 16 +++++++++++++++- test/test_acceptance.ml | 16 +++++++++++++++- test/test_flan.ml | 9 --------- 7 files changed, 76 insertions(+), 22 deletions(-) diff --git a/lib/check.ml b/lib/check.ml index 6331cba6..03bd3086 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -11583,11 +11583,12 @@ and dyn_fold ctx ~want loc name first rest = | "+" -> "flan_dyn_add" | "-" -> "flan_dyn_sub" | "*" -> "flan_dyn_mul" | "/" -> "flan_dyn_div" | "%" -> "flan_dyn_rem" + | "min" -> "flan_dyn_min" | "max" -> "flan_dyn_max" | _ -> dyn_bits_sym name in (* A bitwise fold takes integers on both sides, and the typed side of a mixed pair can be asked now rather than at run time. *) - let bitwise = not (List.mem name [ "+"; "-"; "*"; "/"; "%" ]) in + let bitwise = not (List.mem name [ "+"; "-"; "*"; "/"; "%"; "min"; "max" ]) in if bitwise then List.iter (fun (v : Tast.expr) -> @@ -13239,7 +13240,10 @@ and named_call ?(qualified = false) ctx ~want loc name args = let x, y, rest = match args with x :: y :: rest -> x, y, rest | _ -> assert false in - let a, b = binary ctx name loc ~want:(numeric_want want) [ x; y ] in + let a, b = binary ctx ~dyn_ok:true name loc ~want:(numeric_want want) [ x; y ] in + if a.Tast.ty = Types.Dyn || b.Tast.ty = Types.Dyn then + dyn_fold ctx ~want loc name [ a; b ] rest + else begin (* [min] and [max] are [<] with a pick, so [ordered?] is what they want — not [numeric?]. A generic that declares [ordered?] gets both. @@ -13266,15 +13270,15 @@ and named_call ?(qualified = false) ctx ~want loc name args = mk loc ty (Tast.Let ([ (sa, a); (sb, b) ], [ mk loc ty (Tast.If (test, la, lb)) ])) in - expect ctx loc ~want - (List.fold_left - (fun acc arg -> - (* A dyn is refused here as it is in the first pair, rather than - opened at the type so far. *) - match fold_operand ctx ty (check ctx ~want:ty arg) with - | `Typed v -> pick acc v - | `Dyn d -> not_numeric name "numbers" d; acc) - (pick a b) rest) + let rec steps acc = function + | [] -> expect ctx loc ~want acc + | arg :: tl -> + match fold_operand ctx ty (check ctx ~want:ty arg) with + | `Typed v -> steps (pick acc v) tl + | `Dyn d -> dyn_fold ctx ~want loc name [ acc; d ] tl + in + steps (pick a b) rest + end (* A type handed to the prelude's slice reductions: the reach for the type-limit constants under the name of the reduction beside them. *) | ("max-of" | "min-of") diff --git a/lib/emit.ml b/lib/emit.ml index 2f28d53e..6247a22e 100644 --- a/lib/emit.ml +++ b/lib/emit.ml @@ -5113,6 +5113,8 @@ declare i64 @flan_dyn_lt(i64, i64, ptr, i64) declare i64 @flan_dyn_le(i64, i64, ptr, i64) declare i64 @flan_dyn_gt(i64, i64, ptr, i64) declare i64 @flan_dyn_ge(i64, i64, ptr, i64) +declare i64 @flan_dyn_min(i64, i64, ptr, i64) +declare i64 @flan_dyn_max(i64, i64, ptr, i64) declare i64 @flan_dyn_eq(i64, i64) declare i64 @flan_dyn_len(i64) declare i64 @flan_dyn_eq_at(i64, i64, ptr, i64) diff --git a/runtime/flan_dyn.c b/runtime/flan_dyn.c index f7bd7c4a..889c6311 100644 --- a/runtime/flan_dyn.c +++ b/runtime/flan_dyn.c @@ -3382,6 +3382,30 @@ flan_dyn flan_dyn_ge(flan_dyn a, flan_dyn b, const uint8_t *loc, return flan_dyn_from_bool(c == 1 || c == 0); } +/* The pick of min and max: numbers by value, keeping the one picked as it is + * (an int stays an int), and chars by code point. Anything else traps, text + * included, since min over text has no typed counterpart. The first is picked + * only when it is strictly less (or greater), so a tie or a NaN answers the + * second, as the typed (if (< a b) a b) does. */ +static flan_dyn pick(const uint8_t *loc, int64_t loclen, const char *op, + flan_dyn a, flan_dyn b, int want) { + int both_chars = flan_dyn_tag(a) == FLAN_DYN_TAG_CHAR && + flan_dyn_tag(b) == FLAN_DYN_TAG_CHAR; + if (!both_chars && !(is_num(a) && is_num(b))) + trap2(loc, loclen, TYPE_TRAP, op, + "it picks between two numbers or two chars, and these are neither", + a, b); + return order(loc, loclen, op, a, b) == want ? a : b; +} +flan_dyn flan_dyn_min(flan_dyn a, flan_dyn b, const uint8_t *loc, + int64_t loclen) { + return pick(loc, loclen, "min", a, b, -1); +} +flan_dyn flan_dyn_max(flan_dyn a, flan_dyn b, const uint8_t *loc, + int64_t loclen) { + return pick(loc, loclen, "max", a, b, 1); +} + /* ── Equality ────────────────────────────────────────────────────────── * * Structural, and the only operation here that never traps: two values of diff --git a/runtime/flan_dyn.h b/runtime/flan_dyn.h index f8591676..6b0edee3 100644 --- a/runtime/flan_dyn.h +++ b/runtime/flan_dyn.h @@ -231,6 +231,11 @@ flan_dyn flan_dyn_le(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen) flan_dyn flan_dyn_gt(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen); flan_dyn flan_dyn_ge(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen); +/* Answer whichever of a and b is less (min) or greater (max), b on a tie: + * numbers by value, chars by code point; anything else traps. */ +flan_dyn flan_dyn_min(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen); +flan_dyn flan_dyn_max(flan_dyn a, flan_dyn b, const uint8_t *loc, int64_t loclen); + /* Structural, and the one operation in this file that never traps: two values * of unrelated tags are not an error, they are unequal. */ flan_dyn flan_dyn_eq(flan_dyn a, flan_dyn b); diff --git a/test/programs/dyn-fold-position.flan b/test/programs/dyn-fold-position.flan index e3e80b7f..a485509f 100644 --- a/test/programs/dyn-fold-position.flan +++ b/test/programs/dyn-fold-position.flan @@ -5,7 +5,10 @@ (defn as-i32 [x i32] i32 x) (defn as-f64 [x f64] f64 x) -(defn main [] i32 +(defn loud [x i32] i32 (println "i" x) x) +(defn loud-dyn [x dyn] dyn (println "d" x) x) + +(defn main [args [str]] i32 (let [d (the dyn \a) f (the dyn 2.5) n (the dyn 4) @@ -28,6 +31,17 @@ (println (<= 2 2 f) (> 3 2 f) (>= 3 3 f) (>= 3 3 n)) (println (= 4 4 n) (= 4 n 4) (= n 4 4) (= 4 4 f)) (println (!= 1 2 n) (!= 1 4 n) (!= 2 f 3)) + ;; min and max: numbers by value, the one picked kept as it is, and + ;; chars by code point. + (println (min f 3 4) (min 3 f 4) (min 3 4 f) (min 1 2 f)) + (println (max n 1 2) (max 1 n 2) (max 1 2 n) (max 5 6 n)) + (println (min d \c \b) (max \b d \c) (max \b \c d) (min 1.5 2.5 n)) + ;; Each operand is evaluated once, left to right. + (println (min (loud 3) (loud 2) (loud-dyn n) (loud 1))) + (println (max (loud 3) (loud-dyn n) (loud 9))) + ;; With an argument, min over a number and a text traps at the form. + (when (> (length args) 1) + (println (min 1 2 (the dyn "a")))) ;; At a typed want the dyn answer is opened at the end. (println (as-i32 (+ 1 2 n)) (as-f64 (* 2 3 f)))) 0) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index 522e9ae8..6b4aff4a 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -5615,7 +5615,8 @@ level "1" let fold_out = "d d d\n^ 5 5\n15 15 15\n0.25 1.8 1.6\n8.5 4.5\nf \\ h\n7 7 7\n\ 4 4 4\n7 7 7\ntrue true true false\ntrue false true false\n\ - true true true false\ntrue false true\n7 15\n" + true true true false\ntrue false true\n2.5 2.5 2.5 1\n4 4 4 6\n\ + a c c 1.5\ni 3\ni 2\nd 4\ni 1\n1\ni 3\nd 4\ni 9\n9\n7 15\n" in outputs "dyn: a dyn in any fold position" "programs/dyn-fold-position.flan" fold_out; @@ -5623,6 +5624,19 @@ level "1" "programs/dyn-fold-position.flan" fold_out; outputs ~x86:true "dyn: a dyn in any fold position, --x86" "programs/dyn-fold-position.flan" fold_out; + List.iter + (fun x86 -> + let exe = compile ~x86 "programs/dyn-fold-position.flan" in + let code, text = run exe (Some "x") in + let want = "programs/dyn-fold-position.flan:44:16: dyn min: int and \ + text, and it picks between two numbers or two chars" in + if code <> 134 || not (contains text want) then begin + incr failures; + Printf.printf "FAIL dyn: min over a number and a text traps%s\n \ + got: %S (exit %d)\n" + (if x86 then ", --x86" else "") text code + end) + [ false; true ]; List.iter (fun x86 -> let exe = compile ~x86 "programs/char-arith.flan" in diff --git a/test/test_flan.ml b/test/test_flan.ml index 60ede47d..2e8d3f8b 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -8450,14 +8450,5 @@ let () = "(defn f [n i32] (Option i32) (cond (= n 1) 10 (= n 2) 20))\n(defn main [] ())"; rejects_check "a map's get takes one key" ~needle:"a map's get takes one key" "(defn main [] () (let [m (map-new i32 i32)] (println (get m 1 2))))"; - (* min and max have no dyn lowering: a dyn past the first pair is refused - as one in it is, not opened at the type so far. *) - List.iter - (fun op -> - rejects_check (op ^ " refuses a dyn past its first pair") - ~needle:(op ^ " takes numbers, found dyn") - (Printf.sprintf - "(defn main [] () (let [f (the dyn 2.5)] (println (%s 3 4 f))))" op)) - [ "min"; "max" ]; Test_support.report () From 3a2f3327f6e2a2dd59e8235bfbb917170e9e0bf1 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 26 Sep 2026 16:28:15 +0700 Subject: [PATCH 3/4] Three dyn-crossing gaps found beside the fold fix are recorded. --- TODO.org | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/TODO.org b/TODO.org index c1ee43de..e23add54 100644 --- a/TODO.org +++ b/TODO.org @@ -800,6 +800,14 @@ One spelling for one operation; != stays, and not= is refused with a suggestion of !=. * Checker +** TODO A u64 above the i64 maximum becomes -1 when it crosses into dyn +=(+ z u)= and =(max u 0 z)= with u = u64 max read u as -1, silently. It should trap at +the crossing, as a u64 field read through a view already does. +** TODO A dyn nil past the first pair of a fold is refused at compile time +=(+ 1 2 (the dyn nil))= says nil has no None at i32, while =(+ (the dyn nil) 1 2)= traps +at run time. Both should trap at run time. +** TODO A generic $t beside a dyn operand is refused +"does not cross into a written type yet"; rule 117 says typed beside dyn gives dyn. ** WAIT Checking a wide fold of let operands is slow Parked 2026-09-26: design first; remeasure on a quiet machine, it was timed under load 20. A 2000-operand (bit-and (let …) …) takes 32 s to check (37 s before the bit operators); From 15ff547e520c97ed54007b01b5b770207573b74b Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 26 Sep 2026 16:29:38 +0700 Subject: [PATCH 4/4] rand-int stays u64. --- TODO.org | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO.org b/TODO.org index e23add54..c54bcdc7 100644 --- a/TODO.org +++ b/TODO.org @@ -10,6 +10,9 @@ pointing at it. A CANCELLED entry carries the one-line reason, because an idea rejected without a record is an idea that gets re-proposed. * Language surface +** CANCELLED rand-int returns i64 +Decided 2026-09-26 (134): rand-int stays u64; write rand-int-range(lo, hi) for a signed +range. ** DONE A literal's reading is fixed where it is bound CLOSED: [2026-09-26] Decision 132, clarifying 117: a vector, map or text literal is typed only when its own