A float element of a dyn view takes an int it holds exactly and traps on one it does not.

This commit is contained in:
Joseph Ferano 2026-09-26 06:22:26 +07:00
parent e79d9c0d07
commit 749b4aeb84
6 changed files with 46 additions and 12 deletions

View File

@ -23,9 +23,10 @@ str. Waits on the dyn-unless-annotated design.
** DONE Any typed container crosses into dyn as a view
CLOSED: [2026-09-26]
A str element reads as a copy and is never written, an aggregate element is written
through its own view, and an int is refused by a float element. A view of storage its
own dyn global's initialiser built is refused. Rules out copying at the crossing, and
any check in a release build.
through its own view, a float element takes an int only when it holds it exactly (a
class's float slot's rule), and a u64 above the largest i64 traps when read. A view of
storage its own dyn global's initialiser built is refused. Rules out copying at the
crossing, a dyn big int for u64, and any check in a release build.
** NEXT Dyn unless annotated
Decided 2026-09-26, replacing the plain rule: number, bool and char literals are typed,

View File

@ -3306,10 +3306,10 @@ static int int_range(uint8_t c, int64_t *lo, int64_t *hi) {
/* One element, unboxed on the way in. The dyn value's tag must be the one
* the element's type wants, or this traps by name and never coerces a
* mismatched value into the slot; an int the element's width cannot hold
* traps too, naming both. An int is not a float here: it is refused, not
* converted, as every write through a view has been. A float into an f32
* narrows, as (f32 x) does. [v] is the view and [x] the value, for the
* sentence. */
* traps too, naming both. An int goes into a float element when the float
* holds it exactly, the rule a class's typed float slot follows, and traps
* when it does not. A float into an f32 narrows, as (f32 x) does. [v] is the
* view and [x] the value, for the sentence. */
static void view_write(const uint8_t *loc, int64_t loclen, const char *op,
flan_dyn v, const uint8_t *d, flan_dyn x, uint8_t *p) {
char ty[128];
@ -3341,9 +3341,24 @@ static void view_write(const uint8_t *loc, int64_t loclen, const char *op,
switch (*d) {
case 'f': case 'd': {
double f;
if (flan_dyn_tag(x) != FLAN_DYN_TAG_FLOAT)
if (flan_dyn_tag(x) == FLAN_DYN_TAG_INT) {
/* [slot_admit]'s rule for a class's float slot: exact is a round
trip, and the range test keeps the cast back defined. */
int64_t n = dyn_int_value(x);
f = *d == 'f' ? (double)(float)n : (double)n;
if (!(f >= -9223372036854775808.0 && f < 9223372036854775808.0)
|| (int64_t)f != n) {
desc_spell(d, ty, sizeof ty);
flan_say(loc, loclen,
"dyn %s: %lld has no exact %s, so it does not go into this "
"element. Write it as a float, as in %lld.0",
op, (long long)n, ty, (long long)n);
flan_trap((const uint8_t *)"DynRange", 8);
}
} else if (flan_dyn_tag(x) == FLAN_DYN_TAG_FLOAT)
f = dyn_num_value(x);
else
trap2(loc, loclen, TYPE_TRAP, op, "this view's elements are float", v, x);
f = dyn_num_value(x);
if (*d == 'f') { float g = (float)f; memcpy(p, &g, 4); }
else memcpy(p, &f, 8);
return;

View File

@ -539,7 +539,16 @@ static void refuse_view(const char *what) {
} else if (strcmp(what, "wrongfloat") == 0) {
static double ff[1];
v = flan_dyn_view_flat(ff, 1, FLAN_VIEW_F64);
FDYN_set_at(v, flan_dyn_from_i64(0), text("nope"));
} else if (strcmp(what, "inexactfloat") == 0) {
/* An int goes into a float element only when the float holds it
exactly; 2^53 + 1 is the first an f64 does not. */
static double ff[1];
v = flan_dyn_view_flat(ff, 1, FLAN_VIEW_F64);
FDYN_set_at(v, flan_dyn_from_i64(0), flan_dyn_from_i64(1));
if (ff[0] != 1.0) { printf("an exact int did not land: %g\n", ff[0]); exit(1); }
FDYN_set_at(v, flan_dyn_from_i64(0),
flan_dyn_from_i64(((int64_t)1 << 53) + 1));
} else if (strcmp(what, "flatpush") == 0) {
v = flan_dyn_view_flat(buf, 2, FLAN_VIEW_I64);
FDYN_push(v, flan_dyn_from_i64(9));

View File

@ -63,12 +63,14 @@
(show "i8" a) (show "u8" b) (show "i16" c) (show "u16" d)
(show "i32" e) (show "u32" f) (show "i64" g) (show "u64" h)
(println (at b 2)))
;; f32: read widens, write narrows.
;; f32: read widens, write narrows, and an int goes in when the f32
;; holds it exactly.
(let [fs [(f32 0.5) 1.25]
dv (keep fs)]
(set (at dv 0) 2.75)
(set (at dv 1) 3.1)
(show "f32" dv)
(set (at dv 0) 4)
(println (at fs 0)))
;; bool, through a slice cut from a local array.
(let [bs [true false true]]
@ -208,4 +210,9 @@
(let [nm (Named {.name "ada" .id 7})]
(put (keep nm) :name "bob")
0)
;; An int an f32 does not hold exactly: 2^24 + 1.
(= n 9)
(let [fs [(f32 0.5)]]
(set (at (keep fs) 0) 16777217)
0)
:else (do (println "?") 1))))

View File

@ -5851,7 +5851,7 @@ level "1"
"i8 [0 -1 -2]\nu8 [251 252 253]\ni16 [-299 301]\nu16 [60001 2]\n\
i32 [-69999 70001]\nu32 [4000000001 2]\ni64 [-4 6]\n\
u64 [9000000000000000001 2]\n253\n\
f32 [2.75 3.1]\n2.75\n\
f32 [2.75 3.1]\n4\n\
bool [true true true]\ntrue\n\
EIINNOORRSSTT\n\
point #Point{:x 0.5 :y 3}\n40\n9.5\n9.5\n2\ntrue\n\
@ -5867,7 +5867,8 @@ level "1"
("2", "this u64 element is 18000000000000000000, above the largest \
dyn int");
("3", "a Point has no field :z. Its fields are :x :y");
("8", "a str element is read-only through a dyn view") ]
("8", "a str element is read-only through a dyn view");
("9", "16777217 has no exact f32") ]
and any_stale =
[ ("4", "this view points into a local of leak-local, and that call \
has returned");

View File

@ -258,6 +258,7 @@ let () =
("wrongwrite", "this view's elements are int");
("wrongbool", "this view's elements are bool");
("wrongfloat", "this view's elements are float");
("inexactfloat", "9007199254740993 has no exact f64");
("flatpush", "this view is a slice or an array and cannot grow");
(* And the operator's own name in that sentence. [flan_dyn_len] hands
a string down twice — once to its type trap and once to the view