A dyn goes into every integer width range-checked, a char where its code point fits, and the trap names the width and what it found.
This commit is contained in:
parent
d5437c3f22
commit
9c47d3bacc
11
TODO.org
11
TODO.org
@ -30,11 +30,12 @@ dyn-unless-annotated design.
|
||||
** DONE Dyn has a char, and dyn text counts characters
|
||||
CLOSED: [2026-09-26]
|
||||
Only a char literal, =at= on a text and =chars= make one, and it prints as its
|
||||
literal, bare too, a control character as \\uXXXX; into a typed i32 (the prelude's
|
||||
rune) or through (i32 c) it gives its code point. length, at and slice on dyn text count
|
||||
code points, a malformed byte counting as one U+FFFD. A non-ASCII literal defaults to
|
||||
i32 and is refused where a u8 is wanted. Rules out char arithmetic, a typed code point
|
||||
turning into a char, and byte offsets on dyn text.
|
||||
literal, bare too, a control character as \\uXXXX. Into any integer width it gives its
|
||||
code point where that fits, into a byte only when ASCII; a dyn int into any width is
|
||||
range-checked, while a cast on either wraps as a typed cast does. length, at and slice
|
||||
on dyn text count code points, a malformed byte counting as one U+FFFD. A non-ASCII
|
||||
literal defaults to i32 and is refused where a byte is wanted. Rules out char
|
||||
arithmetic, a typed code point turning into a char, and byte offsets on dyn text.
|
||||
|
||||
** DONE Any typed container crosses into dyn as a view
|
||||
CLOSED: [2026-09-26]
|
||||
|
||||
48
lib/check.ml
48
lib/check.ml
@ -4185,38 +4185,34 @@ let mismatch_found : Types.t Found.t = Found.create 16
|
||||
let unbox loc (want : Types.t) (e : Tast.expr) : Tast.expr =
|
||||
let need sym ty = rt loc ty sym [ e ] in
|
||||
match want with
|
||||
| Types.Int Types.I64 -> need "flan_dyn_need_i64" dyn_i64
|
||||
| Types.Float Types.F64 -> need "flan_dyn_need_f64" dyn_f64
|
||||
| Types.Bool ->
|
||||
(* The ABI answers an [int32_t]; [bool] is an [i1]. The narrowing is the
|
||||
language's own cast and cannot fail — the runtime already decided the
|
||||
value was a bool, so what comes back is 0 or 1. *)
|
||||
widen loc Types.Bool (need "flan_dyn_need_bool" (Types.Int Types.I32))
|
||||
(* An int that fits, range-checked at run time at this site, or a char's
|
||||
code point: an i32 is the prelude's rune. *)
|
||||
| Types.Int Types.I32 ->
|
||||
rt loc want "flan_dyn_need_i32" [ e; here loc ]
|
||||
(* Every other width is refused rather than served by a need_i64 and a
|
||||
truncation. Narrowing is written or it does not happen — that survives
|
||||
widening becoming implicit (TODO.org, "Implicit numeric widening is
|
||||
legal; narrowing stays a hard error") untouched, and this is the
|
||||
boundary where it matters most: the value's type was *already* uncertain
|
||||
here, so an annotation that quietly discarded the high bits would read as
|
||||
a check and be the opposite of one.
|
||||
|
||||
Nor does widening reach this arm from the other side. The box carries one
|
||||
integer width and one float width, so there is no narrower source here to
|
||||
widen from — a u32 want is asking the i64 in the box to fit in half of
|
||||
itself, which is the refusal above and not a conversion the lattice has.
|
||||
The ABI grows a per-width entry point when there is a reason to; until
|
||||
then the spelling that works is an i64 and a written conversion after
|
||||
it. *)
|
||||
| Types.Int _ | Types.Float _ ->
|
||||
no_dyn_yet loc ~into:false want
|
||||
(Printf.sprintf
|
||||
" — take it as %s and convert"
|
||||
(if Types.is_numeric want && (match want with Types.Float _ -> true | _ -> false)
|
||||
then "f64" else "i64"))
|
||||
(* Any integer width, checked at run time at this site (TODO.org, "Dyn
|
||||
unless annotated"): an int in the width's range, or a char's code point
|
||||
where it fits — ASCII only into a byte, since a byte past ASCII is not
|
||||
that char in UTF-8. The runtime answers an i64 it has already checked,
|
||||
so the narrowing after it cannot lose a bit. *)
|
||||
| Types.Int k ->
|
||||
let code =
|
||||
match k with
|
||||
| Types.I8 -> 0L | Types.U8 -> 1L | Types.I16 -> 2L | Types.U16 -> 3L
|
||||
| Types.I32 -> 4L | Types.U32 -> 5L | Types.I64 -> 6L | Types.U64 -> 7L
|
||||
in
|
||||
let n =
|
||||
rt loc dyn_i64 "flan_dyn_need_int"
|
||||
[ e; mk loc (Types.Int Types.I32) (Tast.Int (code, Types.I32)); here loc ]
|
||||
in
|
||||
if k = Types.I64 then n else mk loc want (Tast.Prim (Tast.Cast want, [ n ]))
|
||||
(* An f32 is refused rather than served by a need_f64 and a rounding: the
|
||||
box carries one float width, and narrowing is written or it does not
|
||||
happen (TODO.org, "Implicit numeric widening is legal; narrowing stays a
|
||||
hard error"). *)
|
||||
| Types.Float _ ->
|
||||
no_dyn_yet loc ~into:false want " — take it as f64 and convert"
|
||||
| _ -> no_dyn_yet loc ~into:false want ""
|
||||
|
||||
(* ── A numeric cast written on a dyn ─────────────────────────────────
|
||||
|
||||
@ -5070,6 +5070,7 @@ declare i64 @flan_dyn_need_i64(i64)
|
||||
declare double @flan_dyn_need_f64(i64)
|
||||
declare i32 @flan_dyn_need_bool(i64)
|
||||
declare i32 @flan_dyn_need_i32(i64, ptr, i64)
|
||||
declare i64 @flan_dyn_need_int(i64, i32, ptr, i64)
|
||||
declare i64 @flan_dyn_int_of(i64)
|
||||
; A numeric cast written on a dyn answers which numeric tag the box holds;
|
||||
; check.ml's [cast_dyn] branches on it and each arm is an ordinary need plus
|
||||
|
||||
@ -2691,31 +2691,70 @@ double flan_dyn_need_f64(flan_dyn v) {
|
||||
|
||||
static const char *an(const char *w); /* forward: "a" or "an" */
|
||||
|
||||
/* A dyn into a typed i32: an int that fits, range-checked, or a char's code
|
||||
* point — an i32 is the prelude's rune. The sentence names what was found
|
||||
* and what would do, and no call: the site in front of it is the one that
|
||||
* failed. */
|
||||
int32_t flan_dyn_need_i32(flan_dyn v, const uint8_t *loc, int64_t loclen) {
|
||||
/* A dyn into a typed integer of width [kind] — 0..7 for i8 u8 i16 u16 i32
|
||||
* u32 i64 u64, check.ml's [unbox] — answered as an i64 the caller narrows:
|
||||
* an int in the width's range, or a char's code point where it fits. A byte
|
||||
* takes only an ASCII char, because a byte past ASCII is not that char in
|
||||
* UTF-8. A dyn int is an i64, so a u64 takes 0 up to the largest i64. The
|
||||
* sentence names what was found and what would do, and no call: the site in
|
||||
* front of it is the one that failed. */
|
||||
static const char *const int_names[8] = { "i8", "u8", "i16", "u16",
|
||||
"i32", "u32", "i64", "u64" };
|
||||
|
||||
int64_t flan_dyn_need_int(flan_dyn v, int32_t kind, const uint8_t *loc,
|
||||
int64_t loclen) {
|
||||
static const int64_t lo[8] = { -128, 0, -32768, 0, INT32_MIN, 0, INT64_MIN, 0 };
|
||||
static const int64_t hi[8] = { 127, 255, 32767, 65535, INT32_MAX,
|
||||
4294967295LL, INT64_MAX, INT64_MAX };
|
||||
int32_t t = flan_dyn_tag(v);
|
||||
const char *name;
|
||||
char sv[SAY_MAX];
|
||||
if (t == FLAN_DYN_TAG_CHAR) return (int32_t)dyn_payload(v);
|
||||
if (t == FLAN_DYN_TAG_INT) {
|
||||
int64_t x = dyn_int_value(v);
|
||||
if (x >= INT32_MIN && x <= INT32_MAX) return (int32_t)x;
|
||||
flan_say(loc, loclen,
|
||||
"dyn: an i32 is wanted here, and the int %lld is outside an "
|
||||
"i32's range", (long long)x);
|
||||
if (kind < 0 || kind > 7) kind = 6;
|
||||
name = int_names[kind];
|
||||
if (t == FLAN_DYN_TAG_CHAR) {
|
||||
int64_t cp = (int64_t)dyn_payload(v);
|
||||
int64_t top = kind <= 1 ? 127 : hi[kind];
|
||||
char cs[16];
|
||||
if (cp <= top) return cp;
|
||||
char_spell((uint32_t)cp, cs);
|
||||
if (kind <= 1)
|
||||
flan_say(loc, loclen,
|
||||
"dyn: %s %s is wanted here, and the char %s is more than one "
|
||||
"byte in UTF-8. Take its code point as an i32",
|
||||
an(name), name, cs);
|
||||
else
|
||||
flan_say(loc, loclen,
|
||||
"dyn: %s %s is wanted here, and the char %s, code point %lld, "
|
||||
"is outside %s %s's range", an(name), name, cs, (long long)cp,
|
||||
an(name), name);
|
||||
flan_trap((const uint8_t *)"DynRange", 8);
|
||||
}
|
||||
say(sv, SAY_MAX, v);
|
||||
if (t == FLAN_DYN_TAG_INT) {
|
||||
int64_t x = dyn_int_value(v);
|
||||
if (x >= lo[kind] && x <= hi[kind]) return x;
|
||||
flan_say(loc, loclen,
|
||||
"dyn: %s %s is wanted here, and the int %lld is outside %s %s's "
|
||||
"range", an(name), name, (long long)x, an(name), name);
|
||||
flan_trap((const uint8_t *)"DynRange", 8);
|
||||
}
|
||||
if (t == FLAN_DYN_TAG_NIL) sv[0] = '\0';
|
||||
else say(sv, SAY_MAX, v);
|
||||
flan_say(loc, loclen,
|
||||
"dyn: an i32 is wanted here, and this is %s %s, %s. An i32 takes an "
|
||||
"int or a char's code point%s",
|
||||
an(tag_of(v)), tag_of(v), sv,
|
||||
t == FLAN_DYN_TAG_FLOAT ? "; convert a float with (i32 x)" : "");
|
||||
"dyn: %s %s is wanted here, and this is %s%s%s%s%s. %s %s takes an "
|
||||
"int or a char's code point%s%s%s",
|
||||
an(name), name, t == FLAN_DYN_TAG_NIL ? "" : an(tag_of(v)),
|
||||
t == FLAN_DYN_TAG_NIL ? "" : " ", tag_of(v), t == FLAN_DYN_TAG_NIL ? "" : ", ", sv,
|
||||
name[0] == 'i' ? "An" : "A", name,
|
||||
t == FLAN_DYN_TAG_FLOAT ? "; convert a float with (" : "",
|
||||
t == FLAN_DYN_TAG_FLOAT ? name : "",
|
||||
t == FLAN_DYN_TAG_FLOAT ? " x)" : "");
|
||||
flan_trap((const uint8_t *)"DynType", 7);
|
||||
}
|
||||
|
||||
int32_t flan_dyn_need_i32(flan_dyn v, const uint8_t *loc, int64_t loclen) {
|
||||
return (int32_t)flan_dyn_need_int(v, 4, loc, loclen);
|
||||
}
|
||||
|
||||
/* The int arm of a numeric cast written on a dyn ([check.ml]'s [cast_dyn]),
|
||||
* reached once [flan_dyn_cast_kind] has said the box is not a float: an
|
||||
* int's value, or a char's code point, so (i32 c) is the code point the
|
||||
|
||||
@ -279,8 +279,12 @@ void flan_dyn_emit_watch(flan_dyn v);
|
||||
int64_t flan_dyn_need_i64(flan_dyn v);
|
||||
double flan_dyn_need_f64(flan_dyn v);
|
||||
uint8_t flan_dyn_need_bool(flan_dyn v);
|
||||
/* For a typed i32: an int in range, or a char's code point (the prelude's
|
||||
* rune). Anything else, or an int out of range, traps at [loc]. */
|
||||
/* For a typed integer of width [kind], 0..7 for i8 u8 i16 u16 i32 u32 i64
|
||||
* u64: an int in range, or a char's code point where it fits (ASCII only
|
||||
* into a byte), as an i64 the caller narrows. Anything else traps at [loc].
|
||||
* [flan_dyn_need_i32] is kind 4. */
|
||||
int64_t flan_dyn_need_int(flan_dyn v, int32_t kind, const uint8_t *loc,
|
||||
int64_t loclen);
|
||||
int32_t flan_dyn_need_i32(flan_dyn v, const uint8_t *loc, int64_t loclen);
|
||||
/* A numeric cast's int arm: an int's value or a char's code point. */
|
||||
int64_t flan_dyn_int_of(flan_dyn v);
|
||||
|
||||
48
test/programs/dyn-int-widths.flan
Normal file
48
test/programs/dyn-int-widths.flan
Normal file
@ -0,0 +1,48 @@
|
||||
;;;; A dyn into a typed integer of every width: an int in the width's range
|
||||
;;;; passes, and so does a char's code point where it fits (ASCII only into a
|
||||
;;;; byte). A cast on a dyn is the typed cast after an unbox, so it takes a
|
||||
;;;; char too and wraps as a typed cast does. With an argument, one crossing
|
||||
;;;; out of range traps at its call.
|
||||
|
||||
(defn to-i8 [x i8] i64 (i64 x))
|
||||
(defn to-u8 [x u8] i64 (i64 x))
|
||||
(defn to-i16 [x i16] i64 (i64 x))
|
||||
(defn to-u16 [x u16] i64 (i64 x))
|
||||
(defn to-i32 [x i32] i64 (i64 x))
|
||||
(defn to-u32 [x u32] i64 (i64 x))
|
||||
(defn to-i64 [x i64] i64 x)
|
||||
(defn to-u64 [x u64] u64 x)
|
||||
|
||||
(defn d [x] dyn x)
|
||||
|
||||
(defn main [args [str]] i32
|
||||
(do
|
||||
;; the edges of each width, both ends, and a char at each
|
||||
(println (to-i8 (d -128)) (to-i8 (d 127)) (to-i8 (d \a)))
|
||||
(println (to-u8 (d 0)) (to-u8 (d 255)) (to-u8 (d \a)))
|
||||
(println (to-i16 (d -32768)) (to-i16 (d 32767)) (to-i16 (d \é)))
|
||||
(println (to-u16 (d 0)) (to-u16 (d 65535)) (to-u16 (d \日)))
|
||||
(println (to-i32 (d -2147483648)) (to-i32 (d 2147483647)) (to-i32 (d \😀)))
|
||||
(println (to-u32 (d 0)) (to-u32 (d 4294967295)) (to-u32 (d \😀)))
|
||||
(println (to-i64 (d -9223372036854775807)) (to-i64 (d 9223372036854775807))
|
||||
(to-i64 (d \😀)))
|
||||
(println (to-u64 (d 0)) (to-u64 (d 9223372036854775807)) (to-u64 (d \😀)))
|
||||
;; casts: a char at every width, and the typed cast's wrap
|
||||
(println (i8 (d \a)) (u8 (d \a)) (i16 (d \a)) (u16 (d \a))
|
||||
(i32 (d \a)) (u32 (d \a)) (i64 (d \a)) (u64 (d \a)))
|
||||
(println (u32 (d -1)) (u32 (the i64 -1)) (u8 (d 256)) (u8 (the i64 256)))
|
||||
(when (> (length args) 1)
|
||||
(let [w (at args 1)]
|
||||
(cond
|
||||
(= w "u8-256") (println (to-u8 (d 256)))
|
||||
(= w "u8-neg") (println (to-u8 (d -1)))
|
||||
(= w "i8-128") (println (to-i8 (d 128)))
|
||||
(= w "i16-big") (println (to-i16 (d 32768)))
|
||||
(= w "u16-big") (println (to-u16 (d 65536)))
|
||||
(= w "i32-big") (println (to-i32 (d 2147483648)))
|
||||
(= w "u32-neg") (println (to-u32 (d -1)))
|
||||
(= w "u64-neg") (println (to-u64 (d -1)))
|
||||
(= w "u8-char") (println (to-u8 (d \é)))
|
||||
(= w "u16-char") (println (to-u16 (d \😀)))
|
||||
:else (println (to-i64 (d "x")))))))
|
||||
0)
|
||||
@ -5360,6 +5360,56 @@ level "1"
|
||||
outputs "dyn: chars" "programs/dyn-char.flan" dyn_char_out;
|
||||
outputs ~opt:"-O0" "dyn: chars, -O0" "programs/dyn-char.flan" dyn_char_out;
|
||||
outputs ~x86:true "dyn: chars, --x86" "programs/dyn-char.flan" dyn_char_out;
|
||||
(* A dyn into every integer width: both edges pass, a char passes where
|
||||
it fits, a cast takes a char and wraps as the typed cast beside it
|
||||
does; then one trap per width past its range, a char too wide, and a
|
||||
text, each at its own call. *)
|
||||
let widths_out =
|
||||
"-128 127 97\n0 255 97\n-32768 32767 233\n0 65535 26085\n\
|
||||
-2147483648 2147483647 128512\n0 4294967295 128512\n\
|
||||
-9223372036854775807 9223372036854775807 128512\n\
|
||||
0 9223372036854775807 128512\n97 97 97 97 97 97 97 97\n\
|
||||
4294967295 4294967295 0 0\n"
|
||||
in
|
||||
outputs "dyn: every integer width" "programs/dyn-int-widths.flan" widths_out;
|
||||
outputs ~opt:"-O0" "dyn: every integer width, -O0"
|
||||
"programs/dyn-int-widths.flan" widths_out;
|
||||
outputs ~x86:true "dyn: every integer width, --x86"
|
||||
"programs/dyn-int-widths.flan" widths_out;
|
||||
List.iter
|
||||
(fun x86 ->
|
||||
let exe = compile ~x86 "programs/dyn-int-widths.flan" in
|
||||
List.iter
|
||||
(fun (arg, want) ->
|
||||
let want = "programs/dyn-int-widths.flan:" ^ want in
|
||||
let code, text = run exe (Some arg) in
|
||||
if code <> 134 || not (contains text want) then begin
|
||||
incr failures;
|
||||
Printf.printf
|
||||
"FAIL dyn: %s traps%s\n got: %S (exit %d)\n \
|
||||
wanted: %S (exit 134)\n"
|
||||
arg (if x86 then ", --x86" else "") text code want
|
||||
end)
|
||||
[ ("u8-256", "37:42: dyn: a u8 is wanted here, and the int 256 is \
|
||||
outside a u8's range");
|
||||
("u8-neg", "38:42: dyn: a u8 is wanted here, and the int -1 is \
|
||||
outside a u8's range");
|
||||
("i8-128", "39:42: dyn: an i8 is wanted here, and the int 128 is \
|
||||
outside an i8's range");
|
||||
("i16-big", "40:44: dyn: an i16 is wanted here, and the int 32768");
|
||||
("u16-big", "41:44: dyn: a u16 is wanted here, and the int 65536");
|
||||
("i32-big", "42:44: dyn: an i32 is wanted here, and the int \
|
||||
2147483648");
|
||||
("u32-neg", "43:44: dyn: a u32 is wanted here, and the int -1 is \
|
||||
outside a u32's range");
|
||||
("u64-neg", "44:44: dyn: a u64 is wanted here, and the int -1");
|
||||
("u8-char", "45:43: dyn: a u8 is wanted here, and the char \\é is \
|
||||
more than one byte in UTF-8");
|
||||
("u16-char", "46:45: dyn: a u16 is wanted here, and the char \
|
||||
\\😀, code point 128512, is outside a u16's range");
|
||||
("x", "47:34: dyn: an i64 is wanted here, and this is a text, \
|
||||
\"x\". An i64 takes an int or a char's code point") ])
|
||||
[ false; true ];
|
||||
(* Print, then read: each char dyn-char-spell.flan prints — every ASCII
|
||||
code point, the C1 controls, then four past them — reads back as the code point it was,
|
||||
and so does the compiler's own spelling of the same literal, which is
|
||||
@ -5567,7 +5617,7 @@ level "1"
|
||||
Matched on the half that carries the meaning rather than on the
|
||||
whole sentence, so the row is about the trap being reached with
|
||||
the right two things in hand and not about punctuation. *)
|
||||
|| not (contains text "float, and an int was wanted")
|
||||
|| not (contains text "an i64 is wanted here, and this is a float, 1.5")
|
||||
then begin
|
||||
incr failures;
|
||||
Printf.printf
|
||||
@ -5615,7 +5665,7 @@ level "1"
|
||||
in
|
||||
if code <> 134
|
||||
|| not (contains text nil_option_out)
|
||||
|| not (contains text "int was wanted")
|
||||
|| not (contains text "an i64 is wanted here, and this is nil.")
|
||||
then begin
|
||||
incr failures;
|
||||
Printf.printf
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user