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:
parent
7b843f6753
commit
cb0be62087
25
TODO.org
25
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=
|
||||
|
||||
@ -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)
|
||||
|
||||
98
lib/check.ml
98
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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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. *)
|
||||
|
||||
31
lib/parse.ml
31
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))
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
14
test/programs/vec-new-shadow.flan
Normal file
14
test/programs/vec-new-shadow.flan
Normal 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)
|
||||
@ -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\
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user