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"
|
||||
"(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 ────────────────────────────────────────── *)
|
||||
accepts "return type types the literal" "(defn f [] u8 0)";
|
||||
accepts "return type types None" "(defn f [] (Option f64) None)";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user