The lattice pinned at its edges, and the two calls that could go the other way
This commit is contained in:
parent
3e4267f57c
commit
f9ae500949
@ -930,6 +930,88 @@ let () =
|
|||||||
rejects_check "float literal into an int"
|
rejects_check "float literal into an int"
|
||||||
"(defn f [] i32 (+ 1 0.5))" ~needle:"expected i32";
|
"(defn f [] i32 (+ 1 0.5))" ~needle:"expected i32";
|
||||||
|
|
||||||
|
(* ── Implicit widening, FIX.org 2026-09-20 ─────────────────────────
|
||||||
|
The lattice, pinned at its edges rather than row by row: what is in, what
|
||||||
|
is out, and the two boundaries that were a judgement call and could be
|
||||||
|
argued the other way — int-into-float admitting only the exact ones, and
|
||||||
|
equal-width cross-signedness admitting nothing.
|
||||||
|
|
||||||
|
[programs/widening.flan] is the other half and asserts the bits; these
|
||||||
|
assert which programs exist. *)
|
||||||
|
accepts "same signedness widens"
|
||||||
|
"(defvar a i32) (defn g [x i64] ()) (defn f [] () (g a))";
|
||||||
|
accepts "unsigned widens into a wider signed"
|
||||||
|
"(defvar a u32) (defn g [x i64] ()) (defn f [] () (g a))";
|
||||||
|
accepts "u8 widens into i16"
|
||||||
|
"(defvar a u8) (defn g [x i16] ()) (defn f [] () (g a))";
|
||||||
|
accepts "f32 widens into f64"
|
||||||
|
"(defvar a f32) (defn g [x f64] ()) (defn f [] () (g a))";
|
||||||
|
(* Narrowing is the thing that did not change, and the message has to say
|
||||||
|
narrowing rather than "these are different types" — it also names the
|
||||||
|
direction that needs nothing, because that is the half a reader coming
|
||||||
|
from the old rule will not expect. *)
|
||||||
|
rejects_check "narrowing is still refused, and says so"
|
||||||
|
"(defvar a i64) (defn g [x i32] ()) (defn f [] () (g a))"
|
||||||
|
~needle:"i64 into i32 can lose";
|
||||||
|
rejects_check "and says the other direction is free"
|
||||||
|
"(defvar a i64) (defn g [x i32] ()) (defn f [] () (g a))"
|
||||||
|
~needle:"i32 widens into i64 by itself";
|
||||||
|
rejects_check "float narrowing is refused too"
|
||||||
|
"(defvar a f64) (defn g [x f32] ()) (defn f [] () (g a))"
|
||||||
|
~needle:"f64 into f32 can lose";
|
||||||
|
(* Equal width across signedness: each holds values the other cannot, so
|
||||||
|
there is no direction at all and the message says that instead. *)
|
||||||
|
rejects_check "signed does not reach the same-width unsigned"
|
||||||
|
"(defvar a i32) (defn g [x u32] ()) (defn f [] () (g a))"
|
||||||
|
~needle:"neither widens into the other";
|
||||||
|
rejects_check "and a signed value never reaches an unsigned, wider or not"
|
||||||
|
"(defvar a i32) (defn g [x u64] ()) (defn f [] () (g a))"
|
||||||
|
~needle:"neither widens into the other";
|
||||||
|
(* Int into float, exact only. This is where the rule is tighter than
|
||||||
|
Odin's, which admits any integer into any float; i64 has values no f64
|
||||||
|
holds, so it is out, and the cast is written. *)
|
||||||
|
accepts "i32 reaches f64 exactly"
|
||||||
|
"(defvar a i32) (defn g [x f64] ()) (defn f [] () (g a))";
|
||||||
|
accepts "u32 reaches f64 exactly"
|
||||||
|
"(defvar a u32) (defn g [x f64] ()) (defn f [] () (g a))";
|
||||||
|
accepts "i16 reaches f32 exactly"
|
||||||
|
"(defvar a i16) (defn g [x f32] ()) (defn f [] () (g a))";
|
||||||
|
rejects_check "i64 does not reach f64 — above 2^53 it would round"
|
||||||
|
"(defvar a i64) (defn g [x f64] ()) (defn f [] () (g a))"
|
||||||
|
~needle:"(f64 x)";
|
||||||
|
rejects_check "i32 does not reach f32 — above 2^24 it would round"
|
||||||
|
"(defvar a i32) (defn g [x f32] ()) (defn f [] () (g a))"
|
||||||
|
~needle:"(f32 x)";
|
||||||
|
(* Containers are invariant: widening rewrites a value with a cast, and
|
||||||
|
there is no value to rewrite in a slice that does not own its bytes. *)
|
||||||
|
rejects_check "a slice of i32 is not a slice of i64"
|
||||||
|
"(defn g [s [i64]] ()) (defn f [t [i32]] () (g t))"
|
||||||
|
~needle:"expected [i64]";
|
||||||
|
|
||||||
|
(* The binary join. The wider operand decides, in either written order, and
|
||||||
|
an equal-width cross-signed pair still has nothing to decide on. *)
|
||||||
|
accepts "the wider operand decides, wider written first"
|
||||||
|
"(defvar a i64) (defvar b i32) (defn f [] i64 (+ a b))";
|
||||||
|
accepts "and decides when it is written second"
|
||||||
|
"(defvar a i64) (defvar b i32) (defn f [] i64 (+ b a))";
|
||||||
|
accepts "min and max join the same way"
|
||||||
|
"(defvar a i8) (defvar b i16) (defn f [] i16 (max a b))";
|
||||||
|
rejects_check "i32 and u32 have no join"
|
||||||
|
"(defvar a i32) (defvar b u32) (defn f [] i32 (+ a b))"
|
||||||
|
~needle:"neither widens into the other";
|
||||||
|
(* The literal rule is untouched, which is what keeps a u64 constant's
|
||||||
|
arithmetic at u64 rather than defaulting the 1 to an i32. *)
|
||||||
|
accepts "a literal still takes the other operand's type"
|
||||||
|
"(defconst fnv u64 14695981039346656037) (defn f [] u64 (+ fnv 1))";
|
||||||
|
(* Shifts are the carve-out: the value's type decides and the count widens
|
||||||
|
to it, never the reverse, because the result's width and the poison check
|
||||||
|
both belong to the value. *)
|
||||||
|
accepts "a narrower count widens to the value"
|
||||||
|
"(defvar v i64) (defvar n u8) (defn f [] i64 (<< v n))";
|
||||||
|
rejects_check "a wider count does not drag the value up with it"
|
||||||
|
"(defvar v u8) (defvar n i32) (defn f [] u8 (<< v n))"
|
||||||
|
~needle:"expected u8";
|
||||||
|
|
||||||
(* ── Bidirectional flow ────────────────────────────────────────── *)
|
(* ── Bidirectional flow ────────────────────────────────────────── *)
|
||||||
accepts "return type types the literal" "(defn f [] u8 0)";
|
accepts "return type types the literal" "(defn f [] u8 0)";
|
||||||
accepts "return type types None" "(defn f [] (Option f64) None)";
|
accepts "return type types None" "(defn f [] (Option f64) None)";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user