A number literal takes its type from its uses inside the function, an unconstrained float literal is an f64, and a dyn use asks for the dyn width.
This commit is contained in:
commit
28d4c0f8dc
10
TODO.org
10
TODO.org
@ -52,11 +52,19 @@ next free-temp. Rules out copy-in/copy-out at a call, and rooting the text in th
|
||||
** NEXT Dyn unless annotated
|
||||
Decided 2026-09-26, replacing the plain rule: number, bool and char literals are typed,
|
||||
their type inferred from their uses inside the function (never across functions); an
|
||||
unconstrained integer literal is int (i32) and a float literal float (f32); uses that
|
||||
unconstrained integer literal is int (i32) and a float literal f64 (decision 121); uses that
|
||||
disagree are refused with a request for an annotation. Vector, map and text literals
|
||||
are dyn unless something typed wants them. A typed value is boxed where it goes into
|
||||
dyn, and a dyn unboxed (checked) where typed code needs it; typed beside dyn in an
|
||||
operator gives dyn. Dyn integers stay i64 and dyn floats f64.
|
||||
Decision 121: f64 and not f32, because f32 locals lost precision silently — 0.1 summed a
|
||||
million times printed 100958. A float literal is f32 only where inference finds typed code
|
||||
wanting f32 (a parameter, field, return or operand). A literal local fed only by dyn takes
|
||||
the dyn width, i64 or f64.
|
||||
Done: local inference (check.ml [lit_session]), an integer and a float literal meeting
|
||||
at the float. Waiting: text and vector literals dyn by default, on
|
||||
dyn text to str and dyn vec to slice conversion (a lane after views); =FLAN_LIT=dyn=
|
||||
measures it, and under it a let-bound one some typed use wants already stays typed.
|
||||
** DONE Dynamic-first, and the dyn half of the language
|
||||
CLOSED: [2026-09-20]
|
||||
An unannotated parameter or return is =dyn=: a NaN-boxed value over a mark-sweep
|
||||
|
||||
781
lib/check.ml
781
lib/check.ml
File diff suppressed because it is too large
Load Diff
@ -1125,13 +1125,86 @@ _Noreturn void flan_restart_fail(const uint8_t *loc, int64_t loclen,
|
||||
* dynamic stack, so the invoke site cannot see what it will find, and the
|
||||
* frame cannot see who will find it. What each end knows is its own parameter
|
||||
* list, so the message is both of them side by side. */
|
||||
/* The top-level items of a signature "(a b c)", where an item may itself be
|
||||
* bracketed: "(Ptr i32)", "[3 f64]". Up to [max]; answers how many. */
|
||||
static int sig_items(const uint8_t *s, int64_t n, const uint8_t **at,
|
||||
int64_t *len, int max) {
|
||||
int count = 0, depth = 0;
|
||||
int64_t start = -1;
|
||||
for (int64_t i = 1; i + 1 < n; i++) {
|
||||
uint8_t c = s[i];
|
||||
if (c == ' ' && depth == 0) {
|
||||
if (start >= 0 && count < max) { at[count] = s + start; len[count] = i - start; count++; }
|
||||
start = -1;
|
||||
continue;
|
||||
}
|
||||
if (start < 0) start = i;
|
||||
if (c == '(' || c == '[') depth++;
|
||||
else if (c == ')' || c == ']') depth--;
|
||||
}
|
||||
if (start >= 0 && count < max) { at[count] = s + start; len[count] = n - 1 - start; count++; }
|
||||
return count;
|
||||
}
|
||||
|
||||
static int is_number_type(const uint8_t *s, int64_t n) {
|
||||
static const char *names[] = { "i8", "i16", "i32", "i64", "u8", "u16",
|
||||
"u32", "u64", "f32", "f64" };
|
||||
for (size_t k = 0; k < sizeof names / sizeof names[0]; k++)
|
||||
if ((int64_t)strlen(names[k]) == n && memcmp(names[k], s, (size_t)n) == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* [got] is the invoke site's signature, then after each 0x1f: the syntax
|
||||
* (i or p) and every argument as written. Where the two signatures differ
|
||||
* only in which number type an argument is, the fix is that argument
|
||||
* converted: (f64 2.5), or f64(2.5) in the indented syntax. */
|
||||
_Noreturn void flan_restart_args_fail(const uint8_t *loc, int64_t loclen,
|
||||
const uint8_t *name, int64_t namelen,
|
||||
const uint8_t *want, int64_t wantlen,
|
||||
const uint8_t *got, int64_t gotlen) {
|
||||
enum { MAX = 16 };
|
||||
const uint8_t *part[MAX + 2];
|
||||
int64_t plen[MAX + 2];
|
||||
int parts = 0;
|
||||
int64_t start = 0;
|
||||
for (int64_t i = 0; i <= gotlen && parts < MAX + 2; i++)
|
||||
if (i == gotlen || got[i] == 0x1f) {
|
||||
part[parts] = got + start; plen[parts] = i - start; parts++;
|
||||
start = i + 1;
|
||||
}
|
||||
const uint8_t *w[MAX], *g[MAX];
|
||||
int64_t wl[MAX], gl[MAX];
|
||||
int nw = sig_items(want, wantlen, w, wl, MAX);
|
||||
int ng = sig_items(part[0], plen[0], g, gl, MAX);
|
||||
char fix[512];
|
||||
size_t used = 0;
|
||||
fix[0] = 0;
|
||||
int ok = parts >= 2 && nw == ng && ng == parts - 2 && ng > 0;
|
||||
for (int k = 0; ok && k < ng; k++) {
|
||||
if (wl[k] == gl[k] && memcmp(w[k], g[k], (size_t)wl[k]) == 0) continue;
|
||||
if (!is_number_type(w[k], wl[k]) || !is_number_type(g[k], gl[k])) { ok = 0; break; }
|
||||
int indented = plen[1] == 1 && part[1][0] == 'i';
|
||||
int wrote = indented
|
||||
? snprintf(fix + used, sizeof fix - used, "%s%.*s(%.*s)", used ? ", " : "",
|
||||
(int)wl[k], (const char *)w[k], (int)plen[k + 2], (const char *)part[k + 2])
|
||||
: snprintf(fix + used, sizeof fix - used, "%s(%.*s %.*s)", used ? ", " : "",
|
||||
(int)wl[k], (const char *)w[k], (int)plen[k + 2], (const char *)part[k + 2]);
|
||||
if (wrote < 0 || (size_t)wrote >= sizeof fix - used) { ok = 0; break; }
|
||||
used += (size_t)wrote;
|
||||
}
|
||||
/* An argument the compiler could not spell is an ellipsis, and a fix with
|
||||
* a hole in it is a conversion to make, not code to paste. */
|
||||
int holed = strstr(fix, "\xe2\x80\xa6") != NULL;
|
||||
if (ok && used > 0)
|
||||
flan_say(loc, loclen, "restart %.*s takes %.*s, given %.*s. %s %s",
|
||||
(int)namelen, (const char *)name, (int)wantlen, (const char *)want,
|
||||
(int)plen[0], (const char *)part[0],
|
||||
holed ? "Convert the argument with" : "Write", fix);
|
||||
else
|
||||
flan_say(loc, loclen, "restart %.*s takes %.*s, given %.*s", (int)namelen,
|
||||
(const char *)name, (int)wantlen, (const char *)want, (int)gotlen,
|
||||
(const char *)got);
|
||||
(const char *)name, (int)wantlen, (const char *)want, (int)plen[0],
|
||||
(const char *)part[0]);
|
||||
rt_trap((const uint8_t *)"RestartArity", 12);
|
||||
}
|
||||
|
||||
|
||||
82
test/programs/literal-locals.flan
Normal file
82
test/programs/literal-locals.flan
Normal file
@ -0,0 +1,82 @@
|
||||
;;;; A number literal bound by let or loop takes its type from its uses in
|
||||
;;;; the function. Each line's expected output is beside it.
|
||||
|
||||
;; A set of an i64 sum makes the accumulator an i64.
|
||||
(defn total [xs [i64]] i64
|
||||
(let [t 0]
|
||||
(dotimes [i (length xs)]
|
||||
(set t (+ t (at xs i))))
|
||||
t))
|
||||
|
||||
;; The operand beside it: an f64 accumulator from a float literal.
|
||||
(defn mean [xs [f64]] f64
|
||||
(let [s 0.0]
|
||||
(dotimes [i (length xs)]
|
||||
(set s (+ s (at xs i))))
|
||||
(/ s (f64 (length xs)))))
|
||||
|
||||
;; A counter compared with an i64 bound counts past i32.
|
||||
(defn count-to [n i64] i64
|
||||
(let [i 0]
|
||||
(while (< i n)
|
||||
(set i (+ i 1000000000)))
|
||||
i))
|
||||
|
||||
;; A set of one literal local into another links them: b holds a value past
|
||||
;; i32, so a is an i64 too.
|
||||
(defn linked [] i64
|
||||
(let [a 0 b 0]
|
||||
(set b 3000000000)
|
||||
(set a b)
|
||||
a))
|
||||
|
||||
;; Two locals fed from each other: i is counted against an i64, and acc
|
||||
;; sums a literal past i32.
|
||||
(defn sum-to [n i64] i64
|
||||
(let [i 0 acc 0]
|
||||
(while (< i n)
|
||||
(set acc (+ acc 1000000000))
|
||||
(set i (+ i 1)))
|
||||
acc))
|
||||
|
||||
;; Inside a generic body the literal takes the type variable.
|
||||
(defn sum-of [xs [$t]] $t {:where (numeric? $t)}
|
||||
(let [acc 0]
|
||||
(dotimes [i (length xs)]
|
||||
(set acc (+ acc (at xs i))))
|
||||
acc))
|
||||
|
||||
;; A chain of sets settles however long it is.
|
||||
(defn chained [x i64] i64
|
||||
(let [a0 0 a1 0 a2 0 a3 0 a4 0 a5 0]
|
||||
(set a0 x) (set a1 (+ a0 1)) (set a2 (+ a1 1)) (set a3 (+ a2 1))
|
||||
(set a4 (+ a3 1)) (set a5 (+ a4 1))
|
||||
a5))
|
||||
|
||||
;; A dyn number is an i64 or an f64, and so is a literal local it feeds.
|
||||
(defn boxed [x] dyn x)
|
||||
(defn from-dyn [] ()
|
||||
(let [d (boxed 0.1) s 0.0 n 0]
|
||||
(set s (+ s d))
|
||||
(set n (+ n (boxed 5000000000)))
|
||||
(println s n)))
|
||||
|
||||
(defn main [] i32
|
||||
(let [xs (the [3 i64] [3000000000 4 5])
|
||||
fs (the [2 f64] [0.5 0.25])
|
||||
gs (the [2 u8] [200 50])]
|
||||
(println (total (slice xs 0 3))) ; 3000000009
|
||||
(println (mean (slice fs 0 2))) ; 0.375
|
||||
(println (count-to 5000000000)) ; 5000000000
|
||||
(println (linked)) ; 3000000000
|
||||
(println (sum-to 3)) ; 3000000000
|
||||
(println (sum-of (slice xs 0 3))) ; 3000000009
|
||||
(println (sum-of (slice fs 0 2)))) ; 0.75
|
||||
(println (chained 3000000000)) ; 3000000005
|
||||
(from-dyn) ; 0.1 5000000000
|
||||
(let [x 0.1]
|
||||
(println (= (boxed x) (boxed 0.1)))) ; true
|
||||
;; Nothing says otherwise: an i32 and an f64.
|
||||
(let [n 7 f 1.5]
|
||||
(println n f)) ; 7 1.5
|
||||
0)
|
||||
@ -101,6 +101,15 @@
|
||||
(handler-bind [(AssetMissing [c] (invoke-restart 'use-value 21))]
|
||||
(shadowed n)))
|
||||
|
||||
;;; A number of another type: the refusal writes the conversion.
|
||||
(defn widened [n i32] i32
|
||||
(handler-bind [(AssetMissing [c] (let [big (i64 7)] (invoke-restart 'use-value big)))]
|
||||
(supplied n)))
|
||||
|
||||
(defn doubled [n i32] i32
|
||||
(handler-bind [(AssetMissing [c] (let [big (i64 7)] (invoke-restart 'use-value (* big 2))))]
|
||||
(supplied n)))
|
||||
|
||||
(defn main [args [str]] i32
|
||||
;; One argument selects a trap; none runs the table's case.
|
||||
(if (> (length args) 1)
|
||||
@ -110,6 +119,8 @@
|
||||
(= k 2) (print (mistyped 91))
|
||||
(= k 3) (print (overfull 92))
|
||||
(= k 4) (print (mislaid 93))
|
||||
(= k 5) (print (widened 94))
|
||||
(= k 6) (print (doubled 95))
|
||||
:else (println "?"))
|
||||
(return 0)))
|
||||
|
||||
|
||||
@ -389,6 +389,14 @@ let () =
|
||||
outputs "value semantics" "programs/values.flan" values_out;
|
||||
outputs "machine surface" "programs/machine.flan" machine_out;
|
||||
outputs "unit main exits 0" "programs/unit-main.flan" "ok\n";
|
||||
let literal_locals_out =
|
||||
"3000000009\n0.375\n5000000000\n3000000000\n3000000000\n3000000009\n\
|
||||
0.75\n3000000005\n0.1 5000000000\ntrue\n7 1.5\n"
|
||||
in
|
||||
outputs "literal locals take their uses' type" "programs/literal-locals.flan"
|
||||
literal_locals_out;
|
||||
outputs ~x86:true "literal locals take their uses' type, --x86"
|
||||
"programs/literal-locals.flan" literal_locals_out;
|
||||
(* Comparisons over three operands and more. The lines that carry the
|
||||
whole claim are the tag transcripts: [abc -> false] is a chain whose
|
||||
*first* link already decided the answer and whose middle operand —
|
||||
@ -1467,6 +1475,10 @@ let () =
|
||||
have taken them is not consulted. *)
|
||||
refuses "a shadowing clause of the same name and a different signature" "4"
|
||||
"restart use-value takes (str), given (i32)";
|
||||
refuses "a number of another type is refused with its conversion" "5"
|
||||
"restart use-value takes (i32), given (i64). Write (i32 big)";
|
||||
refuses "and the argument as it was written when it is an expression" "6"
|
||||
"restart use-value takes (i32), given (i64). Write (i32 (* big 2))";
|
||||
(try Sys.remove exe with Sys_error _ -> ())
|
||||
in
|
||||
restart_mismatch ();
|
||||
|
||||
@ -1086,6 +1086,53 @@ let () =
|
||||
two [infers] above still hold — and this is the position that had no way
|
||||
to say it. *)
|
||||
infers "array constructor" "(array 4 f32)" "[4 f32]";
|
||||
(* A literal bound by a let takes its type from its uses in the function,
|
||||
and two uses no one type satisfies are refused with the annotation. *)
|
||||
accepts "a literal local takes the type set into it"
|
||||
"(defn f [x i64] i64 (let [t 0] (set t (+ t x)) t))";
|
||||
accepts "a literal local takes an operand's type"
|
||||
"(defn f [x f64] f64 (let [s 0.0] (set s (+ s x)) s))";
|
||||
accepts "recur rebinds a literal local at the type it brings"
|
||||
"(defn f [n i64] i64 (loop [i 0 acc 0] (if (< i n) (recur (+ i 1) (+ acc n)) acc)))";
|
||||
accepts "a set links two literal locals"
|
||||
"(defn f [] i64 (let [a 0 b 0] (set b 3000000000) (set a b) a))";
|
||||
accepts "a float literal local takes the f32 typed code wants"
|
||||
"(defn f [x f32] f32 (let [s 0.0] (set s (+ s x)) s))";
|
||||
rejects_check "a literal past f32's range where f32 is wanted"
|
||||
~needle:"1e+39 does not fit in f32, whose largest value is about 3.4e38"
|
||||
"(defn f [] f32 1e39)";
|
||||
rejects_check "a literal f32 rounds to 0 where f32 is wanted"
|
||||
~needle:"1e-50 is too small for f32, which rounds it to 0"
|
||||
"(defn f [] f32 1e-50)";
|
||||
infers "a literal past f32's range is an f64 like any other" "(+ 1.0 1e300)" "f64";
|
||||
(* Chains through a second round, and through do, let and if arms that
|
||||
merge without one. *)
|
||||
accepts "a literal local fed through do, however long the chain"
|
||||
"(defn f [x i64] i64 (let [a0 0 a1 0 a2 0 a3 0 a4 0 a5 0 a6 0 a7 0 a8 0 a9 0 a10 0 \
|
||||
a11 0 a12 0] (set a0 x) (set a1 (do a0)) (set a2 (do a1)) (set a3 (do a2)) \
|
||||
(set a4 (do a3)) (set a5 (do a4)) (set a6 (do a5)) (set a7 (do a6)) \
|
||||
(set a8 (do a7)) (set a9 (do a8)) (set a10 (do a9)) (set a11 (do a10)) \
|
||||
(set a12 (do a11)) a12))";
|
||||
accepts "a literal local fed through a let"
|
||||
"(defn f [x i64] i64 (let [a0 0 a1 0 a2 0] (set a0 x) (set a1 (let [t a0] t)) \
|
||||
(set a2 (let [t a1] t)) a2))";
|
||||
accepts "a literal local fed through both arms of an if"
|
||||
"(defn f [x i64] i64 (let [a0 0 a1 0] (set a0 x) (set a1 (if true a0 a0)) a1))";
|
||||
accepts "a literal local fed through a generic call settles in rounds"
|
||||
"(defn same [x $t] $t x) (defn f [x i64] i64 (let [a0 0 a1 0 a2 0] (set a0 x) \
|
||||
(set a1 (same a0)) (set a2 (same a1)) a2))";
|
||||
rejects_check "a chain the rounds cannot follow names the local to annotate"
|
||||
~needle:"the type of a7 depends on too long a chain of the values stored into \
|
||||
it to be read off them. Write the type it should have: (i64 0)"
|
||||
"(defn same [x $t] $t x) (defn f [x i64] i64 (let [a0 0 a1 0 a2 0 a3 0 a4 0 a5 0 \
|
||||
a6 0 a7 0 a8 0 a9 0 a10 0] (set a0 x) (set a1 (same a0)) (set a2 (same a1)) \
|
||||
(set a3 (same a2)) (set a4 (same a3)) (set a5 (same a4)) (set a6 (same a5)) \
|
||||
(set a7 (same a6)) (set a8 (same a7)) (set a9 (same a8)) (set a10 (same a9)) a10))";
|
||||
rejects_check "two uses of a literal local disagree"
|
||||
~needle:"x is used as u32 and as i32, and 0 can have only one type. \
|
||||
Write the one it should have: (u32 0)"
|
||||
"(defn u [x u32] u32 x) (defn i [x i32] i32 x) \
|
||||
(defn f [] i32 (let [x 0] (u x) (i x)) 0)";
|
||||
infers "array of a struct" "(array 2 i32)" "[2 i32]";
|
||||
infers "array of an array" "(array 2 [3 u8])" "[2 [3 u8]]";
|
||||
(* (array-fill [r c] v): the same type at any rank, with the element type
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user