diff --git a/TODO.org b/TODO.org index 097ba9b5..0d8c7dc6 100644 --- a/TODO.org +++ b/TODO.org @@ -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= diff --git a/lib/ast.ml b/lib/ast.ml index cf923cc0..f3677360 100644 --- a/lib/ast.ml +++ b/lib/ast.ml @@ -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) diff --git a/lib/check.ml b/lib/check.ml index 6957088c..3c7cb852 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -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 diff --git a/lib/expand.ml b/lib/expand.ml index 017bfe73..98bbfe94 100644 --- a/lib/expand.ml +++ b/lib/expand.ml @@ -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)) diff --git a/lib/form.ml b/lib/form.ml index b16ace01..2eda0357 100644 --- a/lib/form.ml +++ b/lib/form.ml @@ -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 diff --git a/lib/load.ml b/lib/load.ml index 4fb38a9e..cac0d6c6 100644 --- a/lib/load.ml +++ b/lib/load.ml @@ -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. *) diff --git a/lib/parse.ml b/lib/parse.ml index d17399b2..acc61a0f 100644 --- a/lib/parse.ml +++ b/lib/parse.ml @@ -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)) diff --git a/lib/prelude.ml b/lib/prelude.ml index 8f6cd1e2..29dbfe74 100644 --- a/lib/prelude.ml +++ b/lib/prelude.ml @@ -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)] diff --git a/lib/reader.ml b/lib/reader.ml index eee007a5..40890424 100644 --- a/lib/reader.ml +++ b/lib/reader.ml @@ -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 diff --git a/test/programs/vec-new-shadow.flan b/test/programs/vec-new-shadow.flan new file mode 100644 index 00000000..9be6e154 --- /dev/null +++ b/test/programs/vec-new-shadow.flan @@ -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) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index 745846d8..58fbe8bb 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -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\ diff --git a/test/test_flan.ml b/test/test_flan.ml index fa77e13c..7e015a8f 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -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