An integer written at or above 2^63 is a u64 and nothing else, and vec-new reads a type from its argument only when the builtin is what was called

This commit is contained in:
Joseph Ferano 2026-09-25 07:43:24 +07:00
parent 7b843f6753
commit cb0be62087
12 changed files with 196 additions and 60 deletions

View File

@ -145,12 +145,16 @@ Closing it needs a reader literal or a float-capable folding pass.
** DONE A u64 constant above 2^63 cannot be written in decimal
CLOSED: [2026-09-25]
A decimal between 2^63 and 2^64 is read as its bit pattern, as hex is. A cast's
integer literal that does not fit =i32= is checked at the cast's type; one that
fits keeps the =i32= default, so =(u32 -1)= still means what it did. It inherits
hex's hole: a decimal above 2^63 at =i64= is accepted as a negative, and a refusal
at a narrower type prints the negative number. Rules out a separate unsigned
literal in =Form=, whose layout the prelude's =Form= mirrors.
An integer written at or above 2^63 — a decimal up to 2^64 - 1, or hex with the
top bit set — reads as =Form.UInt=, its pattern and its spelling. It is accepted
where the type is =u64=, a =(u64 ...)= cast included, and refused everywhere else
in the spelling it was written in. Hex with the top bit set was accepted as a
negative at any integer type before this; it is refused now too. A negative
decimal is still a =u64= bit pattern. A cast's integer literal that does not fit
=i32= is checked at the cast's type; one that fits keeps the =i32= default, so
=(u32 -1)= still means what it did. A wide literal that passes through a macro
comes back as an ordinary =Int=, because the macro side's =Form= has one integer
case. Rules out a second integer case in the prelude's =Form=.
** DONE {.row .col} binds same-named locals
CLOSED: [2026-09-20]
@ -846,10 +850,11 @@ lines at the call site, where =K= is known.
** DONE (vec-new [u8]) is refused
CLOSED: [2026-09-25]
The type positions of =vec-new= and =map-new= take a type expression: brackets, or
a parenthesised =Ptr=, =Option=, =Vec=, =Map=, =Fn= or =CFn=. Parse reads it with
=texpr= into =Ast.TypeArg=; a bare name is still left for the checker to tell a
type from an allocator. Rules out a type expression anywhere else in expression
position.
a parenthesised =Ptr=, =Option=, =Vec=, =Map=, =Fn= or =CFn=. The arguments stay
ordinary expressions and the builtin reads the type back out of one
(=Check.type_of_expr=), so a program's own =vec-new= still gets values; only a
type an expression cannot hold, such as =(Fn [i32] ())=, is parsed as
=Ast.TypeArg=. Rules out a type expression anywhere else in expression position.
** TODO An array literal cannot say it is [f32]
A float literal defaults to =f64=, an array literal has no context, and a =let=

View File

@ -36,6 +36,7 @@ type expr = { e : expr_kind; loc : Loc.t }
and expr_kind =
| Int of int64
| UInt of int64 * string (* 18446744073709551615 — u64 only *)
| Float of float
| Byte of int
| Str of string
@ -408,7 +409,7 @@ let map_children f (e : expr) : expr =
in
let kind =
match e.e with
| Int _ | Float _ | Byte _ | Str _ | Kw _ | Quote _ | Var _ | ArrayOf _
| Int _ | UInt _ | Float _ | Byte _ | Str _ | Kw _ | Quote _ | Var _ | ArrayOf _
| TypeArg _ | Break _ | Continue _ -> e.e
| Do es -> Do (List.map ex es)
| Let (bs, es) -> Let (List.map bind bs, List.map ex es)

View File

@ -395,6 +395,7 @@ let spell_arg stand_for (a : Ast.expr) =
match a.Ast.e with
| Ast.Var v -> v
| Ast.Int n -> Int64.to_string n
| Ast.UInt (_, s) -> s
| _ -> stand_for
(* What a [break] or a [continue] may be talking about, innermost first.
@ -1938,7 +1939,9 @@ let check_fn_ref : (env -> Ast.fn -> Tast.fn) ref =
(* Untyped literals: their machine type comes from context, so when one is an
operand of a binary operator we look at the *other* operand first. *)
let is_literal (e : Ast.expr) =
match e.Ast.e with Ast.Int _ | Ast.Float _ | Ast.Byte _ -> true | _ -> false
match e.Ast.e with
| Ast.Int _ | Ast.UInt _ | Ast.Float _ | Ast.Byte _ -> true
| _ -> false
(* [addr] takes the address of a place, but the parser only builds places for
[set]. Recover one from the expression it parsed instead. *)
@ -3297,6 +3300,7 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
ctx.tail <- false;
match e.Ast.e with
| Ast.Int n -> int_literal loc ~want ~preds:ctx.env.tvpreds n
| Ast.UInt (n, s) -> wide_literal loc ~want n s
| Ast.Byte b ->
int_literal loc ~want ~preds:ctx.env.tvpreds ~default:Types.U8
(Int64.of_int b)
@ -3547,11 +3551,11 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
| Ast.ArrayOf t ->
let ty = resolve ctx.env t in
expect ctx loc ~want (mk loc ty (Tast.Zero ty))
(* Parse writes one only into a type position of vec-new or map-new, and
those read it before it could get here. *)
(* Parse writes one only into a type position of a call named vec-new or
map-new, and the builtins read it before it could get here. A program's
own function of that name does not. *)
| Ast.TypeArg _ ->
fail loc "internal: a type argument reached the checker outside vec-new or \
map-new — this is a compiler bug"
fail loc "this is a type, and a value is wanted here"
| Ast.ArrayFill (dims, v) -> check_array_fill ctx ~want loc dims v
| Ast.ArrayGen (dims, f) -> check_array_gen ctx ~want loc dims f
| Ast.Match (scrutinee, arms) -> check_match ctx ~tail ?want loc scrutinee arms
@ -3732,6 +3736,29 @@ and int_literal loc ~want ?(preds = []) ?(default = Types.I32) n =
(Types.to_string other) n
| _ -> mk loc (Types.Int default) (Tast.Int (in_range loc default n, default))
(* An integer written at or above 2^63, in decimal or in hex. Only a u64 holds
one, so it is accepted there and refused everywhere else, in the spelling it
was written in — its pattern read as an i64 is a different number. *)
and wide_literal loc ~want n s =
match want with
| Some (Types.Int Types.U64) -> mk loc (Types.Int Types.U64) (Tast.Int (n, Types.U64))
| Some (Types.Int k) ->
Loc.failk literal_at_want loc "%s does not fit in %s" s (Types.ikind_name k)
| Some (Types.Float _ as t) ->
Loc.failk literal_at_want loc
"%s is too large for any integer type but u64, and an integer literal \
where %s is wanted is read as one — write (%s (u64 %s))"
s (Types.to_string t) (Types.to_string t) s
| Some Types.Never | None ->
Loc.failk literal_at_want loc
"%s does not fit in i32, the type an integer literal takes when nothing \
says otherwise — write (u64 %s) for a u64"
s s
| Some other ->
Loc.failk literal_at_want loc
"expected %s, found the integer literal %s, which only a u64 holds"
(Types.to_string other) s
(* Arithmetic wraps, but a literal that does not fit its type is a typo, not a
wrap — 300 is never what someone meant by a u8. *)
and in_range loc k n =
@ -3742,14 +3769,10 @@ and in_range loc k n =
|| (Int64.compare n (Int64.neg (Int64.shift_left 1L (bits - 1))) >= 0
&& Int64.compare n (Int64.shift_left 1L (bits - 1)) < 0)
else if bits = 64 then
(* A u64 literal is its 64-bit pattern, so anything at or above 2^63
arrives here as a negative [int64] and is still in range —
0xcbf29ce484222325 is a real u64 and not an error, and so is its
decimal, which the reader reads as the same pattern. The cost is that a
negative *decimal* literal is accepted as a u64 too, because the
reader records only the value and not how it was written. Narrower
unsigned types keep the strict check, which is where a typo like 300
for a u8 actually shows up. *)
(* A literal at or above 2^63 is a [UInt] and never reaches here; see
[wide_literal]. A negative decimal is accepted as a u64's bit pattern,
which is a settled rule. Narrower unsigned types keep the strict
check, which is where a typo like 300 for a u8 actually shows up. *)
true
else
Int64.compare n 0L >= 0
@ -6578,7 +6601,8 @@ and type_named ctx n =
and vec_new_elem ctx ~want loc args =
let named =
match args with
| { Ast.e = Ast.TypeArg t; _ } :: rest -> Some (resolve ctx.env t, rest)
| a :: rest when type_of_expr a <> None ->
Some (resolve ctx.env (Option.get (type_of_expr a)), rest)
| { Ast.e = Ast.Var n; _ } :: rest
when lookup ctx n = None
&& (not (Hashtbl.mem ctx.env.globals n))
@ -6596,6 +6620,39 @@ and vec_new_elem ctx ~want loc args =
"nothing here says what (vec-new) is a Vec of — write the element \
type, as (vec-new i32), or give the binding a type")
(* A type written as an argument to vec-new or map-new, read back out of the
expression Parse made of it. Only the shapes that cannot be a value there:
brackets — an allocator is never an array — or a parenthesised Ptr,
Option, Vec, Map, Fn or CFn. A bare name is not one of them, because there
it may be an allocator's name; the callers ask about that themselves. *)
and type_of_expr (e : Ast.expr) : Ast.texpr option =
let mk t = { Ast.t; tloc = e.Ast.loc } in
let inner (e : Ast.expr) =
match e.Ast.e with
| Ast.Var s -> Some { Ast.t = Ast.Tname s; tloc = e.Ast.loc }
| _ -> type_of_expr e
in
let all es =
let ts = List.filter_map inner es in
if List.length ts = List.length es then Some ts else None
in
match e.Ast.e with
| Ast.TypeArg t -> Some t
| Ast.Arr [ x ] -> Option.map (fun t -> mk (Ast.Tslice t)) (inner x)
| Ast.Arr [ { Ast.e = Ast.Int n; _ }; x ] ->
Option.map (fun t -> mk (Ast.Tarray (Ast.Lint n, t))) (inner x)
| Ast.Arr [ { Ast.e = Ast.Var n; _ }; x ] ->
Option.map (fun t -> mk (Ast.Tarray (Ast.Lname n, t))) (inner x)
| Ast.Call ({ Ast.e = Ast.Var (("Fn" | "CFn") as which); _ },
[ { Ast.e = Ast.Arr ps; _ }; r ]) ->
(match all ps, inner r with
| Some ps, Some r -> Some (mk (Ast.Tfn (which = "Fn", ps, r)))
| _ -> None)
| Ast.Call ({ Ast.e = Ast.Var (("Ptr" | "Option" | "Vec" | "Map") as c); _ },
(_ :: _ as args)) ->
Option.map (fun ts -> mk (Ast.Tapp (c, ts))) (all args)
| _ -> None
(* The key and value types, or the reason this is not a Map. *)
and map_kv loc what (t : Types.t) =
match t with
@ -6616,15 +6673,15 @@ and map_new_types ctx ~want loc args =
(* A type position holds a bare name or a type expression Parse has read
as one, as [vec-new]'s does. *)
let as_type (a : Ast.expr) =
match a.Ast.e with
| Ast.TypeArg t -> Some (resolve ctx.env t)
| Ast.Var n when is_type n -> Some (resolve_name ctx.env ~seen:[] loc n)
match a.Ast.e, type_of_expr a with
| _, Some t -> Some (resolve ctx.env t)
| Ast.Var n, None when is_type n -> Some (resolve_name ctx.env ~seen:[] loc n)
| _ -> None
in
match args with
| k :: v :: rest when as_type k <> None && as_type v <> None ->
Option.get (as_type k), Option.get (as_type v), rest
| { Ast.e = Ast.TypeArg _; _ } :: _ ->
| a :: _ when type_of_expr a <> None ->
fail loc
"(map-new) names a key and no value — write both, as (map-new string \
i32), or give the binding a type"
@ -8806,6 +8863,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
| Ast.Int n, (Types.Int _ | Types.Float _)
when Int64.compare n (-2147483648L) < 0
|| Int64.compare n 2147483647L > 0 -> Some target
| Ast.UInt _, (Types.Int _ | Types.Float _) -> Some target
| _ -> None
in
let a = check ctx ?want (List.hd args) in
@ -9131,7 +9189,7 @@ and generic_call ctx ~want loc name vars pats pret args =
else is checked on its own terms. *)
let untyped_literal =
match a.Ast.e with
| Ast.Int _ | Ast.Float _ | Ast.Byte _ -> true
| Ast.Int _ | Ast.UInt _ | Ast.Float _ | Ast.Byte _ -> true
| _ -> false
in
let a =
@ -9603,7 +9661,7 @@ and binary ctx ?(dyn_ok = false) ?(join = true) name loc ~want args =
let y_decides =
(is_literal x && not (is_literal y))
|| (match x.Ast.e, y.Ast.e with
| (Ast.Int _ | Ast.Byte _), Ast.Float _ -> true
| (Ast.Int _ | Ast.UInt _ | Ast.Byte _), Ast.Float _ -> true
| _ -> false)
in
(* A form that cannot be checked without being told what is wanted. A

View File

@ -114,6 +114,9 @@ let rec write (sites : sites) p (f : Form.t) =
| Form.Kw s -> str TKw s
| Form.Str s -> str TStr s
| Form.Int i -> tag TInt; Dynload.poke_i64 p payload i
(* A macro's Form has one integer case, so a wide literal crosses as its
pattern and comes back as an ordinary [Int]. *)
| Form.UInt (i, _) -> tag TInt; Dynload.poke_i64 p payload i
| Form.Float x -> tag TFloat; Dynload.poke_f64 p payload x
| Form.Byte b -> tag TByte; Dynload.poke_i32 p payload (Int32.of_int b)
| Form.List xs -> seq TList xs
@ -251,7 +254,7 @@ let rec quote (f : Form.t) : Form.t =
inner form with form-cons"
| Form.Sym s -> node loc "Sym" "s" (Form.Str s)
| Form.Kw s -> node loc "Kw" "s" (Form.Str s)
| Form.Int i -> node loc "Int" "i" (Form.Int i)
| Form.Int i | Form.UInt (i, _) -> node loc "Int" "i" (Form.Int i)
| Form.Float x -> node loc "Float" "x" (Form.Float x)
| Form.Str s -> node loc "Str" "s" (Form.Str s)
| Form.Byte b -> node loc "Byte" "b" (Form.Int (Int64.of_int b))

View File

@ -12,6 +12,10 @@ and value =
| Sym of string (* foo rl/draw-fps .pos + *)
| Kw of string (* :space :else (leading : dropped) *)
| Int of int64 (* 42 -1 0xE6B800FF *)
(* An integer written at or above 2^63 — 18446744073709551615, or a hex
literal with its top bit set. Its 64-bit pattern and its spelling: only a
u64 holds it, and a refusal anywhere else prints the number as written. *)
| UInt of int64 * string
| Float of float (* 0.05 *)
| Str of string (* "SAND" *)
| Byte of int (* \space \0 \( (0..255) *)
@ -30,6 +34,7 @@ let rec to_string f =
| Sym s -> s
| Kw s -> ":" ^ s
| Int i -> Int64.to_string i
| UInt (_, s) -> s
| Float x -> Printf.sprintf "%g" x
| Str s -> Printf.sprintf "%S" s
| Byte b ->
@ -126,6 +131,7 @@ let rec to_source f =
| Sym s -> s
| Kw s -> ":" ^ s
| Int i -> Int64.to_string i
| UInt (_, s) -> s
| Float x -> float_repr x
| Str s -> "\"" ^ escape s ^ "\""
| Byte b -> byte_repr b

View File

@ -223,7 +223,7 @@ let rec rename_expr owned alias bound (e : Ast.expr) : Ast.expr =
let name n = qualify_name owned alias bound n in
let k =
match e.Ast.e with
| Ast.Int _ | Ast.Float _ | Ast.Byte _ | Ast.Str _ | Ast.Kw _
| Ast.Int _ | Ast.UInt _ | Ast.Float _ | Ast.Byte _ | Ast.Str _ | Ast.Kw _
| Ast.Quote _ -> e.Ast.e
| Ast.Var n -> Ast.Var (name n)
| Ast.Do body -> Ast.Do (gos body)
@ -754,7 +754,8 @@ let rec expr_uses acc (e : Ast.expr) =
let go = expr_uses acc in
let gos = List.iter go in
match e.Ast.e with
| Ast.Int _ | Ast.Float _ | Ast.Byte _ | Ast.Str _ | Ast.Kw _ | Ast.Quote _ ->
| Ast.Int _ | Ast.UInt _ | Ast.Float _ | Ast.Byte _ | Ast.Str _ | Ast.Kw _
| Ast.Quote _ ->
()
(* The name is not one an import can supply, but the arguments are ordinary
expressions and may well use one. *)

View File

@ -310,6 +310,7 @@ let rec expr (f : Form.t) : Ast.expr =
let mk e = { Ast.e; loc = f.loc } in
match f.v with
| Int i -> mk (Ast.Int i)
| UInt (i, s) -> mk (Ast.UInt (i, s))
| Float x -> mk (Ast.Float x)
| Byte b -> mk (Ast.Byte b)
| Str s -> mk (Ast.Str s)
@ -426,28 +427,26 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr =
| _ -> fail f "set is (set place value)")
(* ── (vec-new [u8]) and (map-new string [u8]) ───────────────────────
The type positions of these two take a type expression as well as a bare
name. A bare name is left for the checker, which knows whether it names a
type or an allocator; a bracket or a parenthesised type constructor can
only be a type there, so it is read as one now, with [texpr], the reader
a parameter list's types go through. *)
The type positions of these two take a type expression. Whether this call
is the builtin at all is the checker's to know — a program may define its
own [vec-new] — so the arguments are read as ordinary expressions and
[Check.type_of_expr] reads a type back out of one when the builtin is
what was called. The one type an expression cannot carry is [()], as in
[(Fn [i32] ())], so an argument that is not an expression but is a type
is kept as a [TypeArg]. *)
| Sym (("vec-new" | "builtin/vec-new" | "map-new" | "builtin/map-new") as n) ->
let slots =
if n = "vec-new" || n = "builtin/vec-new" then 1 else 2
in
let is_type (a : Form.t) =
match a.v with
| Vec _ -> true
| List ({ v = Sym ("Ptr" | "Option" | "Vec" | "Map" | "Fn" | "CFn"); _ }
:: _ :: _) -> true
| _ -> false
in
let args =
List.mapi
(fun i a ->
if i < slots && is_type a then
{ Ast.e = Ast.TypeArg (texpr a); loc = a.loc }
else expr a)
(fun i (a : Form.t) ->
match expr a with
| e -> e
| exception (Loc.Error _ as not_expr) when i < slots ->
(match texpr a with
| t -> { Ast.e = Ast.TypeArg t; loc = a.loc }
| exception Loc.Error _ -> raise not_expr))
args
in
mk (Ast.Call (expr head, args))

View File

@ -234,10 +234,9 @@ let source = {flan|
;; LLVM; at this width there is no such case to guard.
;;
;; The multiplier is written in hex, which is how it is written everywhere
;; it appears: in decimal it is 12605985483714917081, and a decimal
;; literal that large is refused here because the reader reads one as a
;; signed 64-bit number. Hex is read as a bit pattern, and this is a bit
;; pattern. It is written here rather than given a name of its own: a
;; it appears: in decimal it is 12605985483714917081. Either spelling is
;; a u64 literal and nothing else. It is written here rather than given a
;; name of its own: a
;; prelude constant is a name in every program, and this is an
;; implementation number that nothing outside these four lines wants.
(let [w (* (bit-xor (>> s (+ (>> s 59) 5)) s) 0xAEF17502108EF2D9)]

View File

@ -144,6 +144,8 @@ let read_number st =
in
if is_hex then
match Int64.of_string_opt text with
(* A top bit set is a value at or above 2^63, which only a u64 holds. *)
| Some i when Int64.compare i 0L < 0 -> spanned st loc (Form.UInt (i, text))
| Some i -> spanned st loc (Form.Int i)
| None -> Loc.failk "reader/malformed-number" (Loc.upto loc (here st))
"malformed hex literal %s" text
@ -153,10 +155,8 @@ let read_number st =
| None -> Loc.failk "reader/malformed-number" (Loc.upto loc (here st))
"malformed float literal %s" text
else
(* A decimal above the largest i64 and below 2^64 is read as its 64-bit
pattern, as a hex literal is, so a u64 constant can be written in
decimal. It therefore arrives negative, and [Check.in_range] makes the
same allowance for it that it makes for hex. *)
(* A decimal above the largest i64 and below 2^64 is a [UInt], as a hex
literal with its top bit set is: only a u64 holds it. *)
let unsigned () =
if String.for_all (fun c -> c >= '0' && c <= '9') text then
Int64.of_string_opt ("0u" ^ text)
@ -166,7 +166,7 @@ let read_number st =
| Some i -> spanned st loc (Form.Int i)
| None ->
match unsigned () with
| Some i -> spanned st loc (Form.Int i)
| Some i -> spanned st loc (Form.UInt (i, text))
| None -> Loc.failk "reader/malformed-number" (Loc.upto loc (here st))
"malformed integer literal %s" text

View File

@ -0,0 +1,14 @@
;;;; A program's own vec-new is an ordinary function: a bracket passed to it
;;;; is an array value, not an element type. builtin/vec-new is still the
;;;; builtin and still reads one as a type.
(defn vec-new [xs [1 i32]] i32 (at xs 0))
(defn main [] i32
(let [x 7
v (builtin/vec-new [u8])]
(println (vec-new [x]))
(push v (bytes-view "ab"))
(println (length (at v 0)))
(free v))
0)

View File

@ -508,6 +508,10 @@ let () =
"2 3 4 7 9 3\n";
outputs ~x86:true "vec-new takes a type expression, x86"
"programs/vec-new-type.flan" "2 3 4 7 9 3\n";
(* A program's own vec-new takes its arguments as values; the builtin,
reached as builtin/vec-new, still reads a bracket as a type. *)
outputs "a vec-new of the program's own" "programs/vec-new-shadow.flan"
"7\n2\n";
(* A u64 above 2^63 in decimal, and a cast of a literal too wide for i32. *)
let u64_out =
"18446744073709551615\n14695981039346656037\n2935910691\n\

View File

@ -94,7 +94,8 @@ let () =
reads "hex" "0xE6B800FF" "3870818559";
(* A decimal between 2^63 and 2^64 is its bit pattern, as hex is; one past
2^64, or a negative one past the smallest i64, is still malformed. *)
reads "u64 decimal" "18446744073709551615" "-1";
reads "u64 decimal" "18446744073709551615" "18446744073709551615";
reads "hex top bit" "0xFFFFFFFFFFFFFFFF" "0xFFFFFFFFFFFFFFFF";
rejects "decimal past 2^64" "18446744073709551616"
~needle:"malformed integer literal";
rejects "negative decimal past i64" "-9223372036854775809"
@ -6045,6 +6046,51 @@ let () =
rejects_check "map-new with a key type expression and no value type"
~needle:"(map-new) names a key and no value"
"(defn f [] i32 (let [m (map-new [u8])] (free m) 0))";
accepts "vec-new over a function type returning unit"
"(defn f [] i32 (let [v (vec-new (Fn [i32] ()))] (free v) 0))";
accepts "a program's own vec-new takes an array literal"
"(defn vec-new [xs [3 i32]] i32 (at xs 2)) \
(defn f [] i32 (vec-new [1 2 3]))";
accepts "builtin/vec-new over a type expression"
"(defn f [] i32 (let [v (builtin/vec-new [u8])] (free v) 0))";
(* An integer written at or above 2^63 — decimal, or hex with the top bit
set — is a u64 and nothing else, and a refusal prints it as written. *)
accepts "a wide decimal at u64"
"(defconst a u64 18446744073709551615) (defonce b u64 0xFFFFFFFFFFFFFFFF) \
(defn f [x u64] u64 (+ x 9223372036854775808)) \
(defn g [] f64 (f64 (u64 12345678901234567890)))";
accepts "a negative decimal is still a u64 bit pattern"
"(defconst a u64 -1)";
rejects_check "a wide decimal with nothing to say u64"
~needle:"18446744073709551615 does not fit in i32, the type an integer \
literal takes when nothing says otherwise — write (u64 \
18446744073709551615)"
"(defn f [] () (println 18446744073709551615))";
rejects_check "a wide decimal cast to i64"
~needle:"18446744073709551615 does not fit in i64"
"(defn f [] i64 (i64 18446744073709551615))";
rejects_check "a wide decimal cast to f64"
~needle:"write (f64 (u64 18446744073709551615))"
"(defn f [] f64 (f64 18446744073709551615))";
rejects_check "a wide decimal constant at i32"
~needle:"18446744073709551615 does not fit in i32"
"(defconst x i32 18446744073709551615)";
rejects_check "a wide decimal constant at f64"
~needle:"write (f64 (u64 12345678901234567890))"
"(defconst x f64 12345678901234567890)";
rejects_check "a wide decimal argument to an i8 parameter"
~needle:"18446744073709551600 does not fit in i8"
"(defn g [a i8] i8 a) (defn f [] i8 (g 18446744073709551600))";
rejects_check "a wide decimal operand prints as written"
~needle:"9223372036854775808 does not fit in i32"
"(defn f [] () (println (+ 1 9223372036854775808)))";
rejects_check "a hex literal with the top bit set at i32"
~needle:"0xFFFFFFFFFFFFFFFF does not fit in i32"
"(defconst x i32 0xFFFFFFFFFFFFFFFF)";
rejects_check "a hex literal with the top bit set at i64"
~needle:"0xFFFFFFFFFFFFFFFF does not fit in i64"
"(defconst x i64 0xFFFFFFFFFFFFFFFF)";
(* And a sigil on a name nothing binds is answered as the unbound variable
it is, rather than as a missing element type — with the names that *are*
bound, because inside a signature that introduces one the mistake is