diff --git a/TODO.org b/TODO.org index 21cc3f01..eae50d2f 100644 --- a/TODO.org +++ b/TODO.org @@ -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, diff --git a/runtime/flan_dyn.c b/runtime/flan_dyn.c index 5c69d5fb..8ef51d16 100644 --- a/runtime/flan_dyn.c +++ b/runtime/flan_dyn.c @@ -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; diff --git a/test/dyn_ops.c b/test/dyn_ops.c index efea69bf..692725cf 100644 --- a/test/dyn_ops.c +++ b/test/dyn_ops.c @@ -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)); diff --git a/test/programs/dyn-view-any.flan b/test/programs/dyn-view-any.flan index da4852f1..e533bdf1 100644 --- a/test/programs/dyn-view-any.flan +++ b/test/programs/dyn-view-any.flan @@ -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)))) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index 2797653c..25aaefb5 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -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"); diff --git a/test/test_dyn.ml b/test/test_dyn.ml index 96875fce..a7d4f7ac 100644 --- a/test/test_dyn.ml +++ b/test/test_dyn.ml @@ -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