A wide literal is refused for its range as an enum member, names the u64 cast where a dyn is wanted, and comes back wide from a macro
This commit is contained in:
parent
dd160aa31b
commit
6c9635cd53
20
TODO.org
20
TODO.org
@ -161,9 +161,23 @@ 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
|
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
|
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
|
=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
|
=(u32 -1)= still means what it did. A wide literal passed to a macro as an
|
||||||
comes back as an ordinary =Int=, because the macro side's =Form= has one integer
|
argument comes back wide: it crosses as an =Int= with a token in the unused
|
||||||
case. Rules out a second integer case in the prelude's =Form=.
|
second payload word (=Expand.wides=). Rules out a second integer case in the
|
||||||
|
prelude's =Form=.
|
||||||
|
|
||||||
|
** DONE A wide literal's follow-ups: an enum member, a dyn want, a macro
|
||||||
|
CLOSED: [2026-09-25]
|
||||||
|
=(defenum E [A 0xFFFFFFFFFFFFFFFF])= gets the enum range refusal in the spelling
|
||||||
|
written. A wide literal where a =dyn= is wanted names =(u64 ...)= and says the dyn
|
||||||
|
holds it as the i64 with the same bits. A wide literal passed through a macro is
|
||||||
|
refused or accepted exactly as it would be unexpanded.
|
||||||
|
|
||||||
|
** TODO A wide literal written inside a quasiquote comes back as an i64
|
||||||
|
=(defmacro w [] `(+ 1 0xFFFFFFFFFFFFFFFF))= expands to =(+ 1 -1)= and prints 0:
|
||||||
|
=Expand.quote= builds =(Form.Int {.i ...})= from the pattern, and a Form built in
|
||||||
|
Flan has no way to carry the token an argument crosses with. At a =u64= want the
|
||||||
|
pattern is the right value, so a refusal would break the one reading that works.
|
||||||
|
|
||||||
** DONE {.row .col} binds same-named locals
|
** DONE {.row .col} binds same-named locals
|
||||||
CLOSED: [2026-09-20]
|
CLOSED: [2026-09-20]
|
||||||
|
|||||||
@ -3846,6 +3846,12 @@ and wide_literal loc ~want n s =
|
|||||||
"%s does not fit in i32, the type an integer literal takes when nothing \
|
"%s does not fit in i32, the type an integer literal takes when nothing \
|
||||||
says otherwise — write (u64 %s) for a u64"
|
says otherwise — write (u64 %s) for a u64"
|
||||||
s s
|
s s
|
||||||
|
| Some Types.Dyn ->
|
||||||
|
Loc.failk literal_at_want loc
|
||||||
|
"expected dyn, found the integer literal %s, which only a u64 holds — a \
|
||||||
|
dyn integer is an i64. Write (u64 %s) for the u64, which a dyn holds as \
|
||||||
|
the i64 with the same bits, %Ld"
|
||||||
|
s s n
|
||||||
| Some other ->
|
| Some other ->
|
||||||
Loc.failk literal_at_want loc
|
Loc.failk literal_at_want loc
|
||||||
"expected %s, found the integer literal %s, which only a u64 holds"
|
"expected %s, found the integer literal %s, which only a u64 holds"
|
||||||
|
|||||||
@ -78,6 +78,19 @@ let tag_of_int = function
|
|||||||
|
|
||||||
type sites = (Dynload.addr, Loc.t) Hashtbl.t
|
type sites = (Dynload.addr, Loc.t) Hashtbl.t
|
||||||
|
|
||||||
|
(* ── A wide literal's round trip ───────────────────────────────────
|
||||||
|
A macro's [Form] has one integer case, so a literal at or above 2^63 crosses
|
||||||
|
as its bit pattern in [Int]'s payload. What marks it as wide is the second
|
||||||
|
payload word, which [Int] does not use and which a macro that passes the
|
||||||
|
form through copies along with the rest of its 24 bytes: [write] puts a
|
||||||
|
token there naming the literal's spelling in this table, and [unmarshal]
|
||||||
|
turns a node carrying one back into the [UInt] that went in. An [Int] the
|
||||||
|
macro built itself has no token, and is the [Int] it says it is. One table
|
||||||
|
per call, as [sites] is. *)
|
||||||
|
let wide_mark = 0x5749444500000000L
|
||||||
|
|
||||||
|
let wides : (int64, int64 * string) Hashtbl.t ref = ref (Hashtbl.create 1)
|
||||||
|
|
||||||
(* Into an existing 24 bytes, which is what an argument array needs: the macro
|
(* Into an existing 24 bytes, which is what an argument array needs: the macro
|
||||||
takes a [Form] slice, and a slice is contiguous elements and not an array of
|
takes a [Form] slice, and a slice is contiguous elements and not an array of
|
||||||
pointers — so this writes *into* memory the caller took, and every caller
|
pointers — so this writes *into* memory the caller took, and every caller
|
||||||
@ -114,9 +127,13 @@ let rec write (sites : sites) p (f : Form.t) =
|
|||||||
| Form.Kw s -> str TKw s
|
| Form.Kw s -> str TKw s
|
||||||
| Form.Str s -> str TStr s
|
| Form.Str s -> str TStr s
|
||||||
| Form.Int i -> tag TInt; Dynload.poke_i64 p payload i
|
| 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
|
(* Crosses as an [Int] carrying a token; see [wides]. *)
|
||||||
pattern and comes back as an ordinary [Int]. *)
|
| Form.UInt (i, text) ->
|
||||||
| Form.UInt (i, _) -> tag TInt; Dynload.poke_i64 p payload i
|
tag TInt;
|
||||||
|
Dynload.poke_i64 p payload i;
|
||||||
|
let token = Int64.logor wide_mark (Int64.of_int (Hashtbl.length !wides)) in
|
||||||
|
Hashtbl.replace !wides token (i, text);
|
||||||
|
Dynload.poke_i64 p len_off token
|
||||||
| Form.Float x -> tag TFloat; Dynload.poke_f64 p payload x
|
| 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.Byte b -> tag TByte; Dynload.poke_i32 p payload (Int32.of_int b)
|
||||||
| Form.List xs -> seq TList xs
|
| Form.List xs -> seq TList xs
|
||||||
@ -167,7 +184,11 @@ let rec unmarshal ~(sites : sites) ~loc (p : Dynload.addr) : Form.t =
|
|||||||
unmarshal ~sites ~loc (Nativeint.add b (Nativeint.of_int (i * form_size))))
|
unmarshal ~sites ~loc (Nativeint.add b (Nativeint.of_int (i * form_size))))
|
||||||
in
|
in
|
||||||
match tag_of_int (Dynload.peek_i32 p 0) with
|
match tag_of_int (Dynload.peek_i32 p 0) with
|
||||||
| TInt -> Form.make (Form.Int (Dynload.peek_i64 p payload)) loc
|
| TInt ->
|
||||||
|
let i = Dynload.peek_i64 p payload in
|
||||||
|
(match Hashtbl.find_opt !wides (Dynload.peek_i64 p len_off) with
|
||||||
|
| Some (w, text) when Int64.equal w i -> Form.make (Form.UInt (i, text)) loc
|
||||||
|
| _ -> Form.make (Form.Int i) loc)
|
||||||
| TFloat -> Form.make (Form.Float (Dynload.peek_f64 p payload)) loc
|
| TFloat -> Form.make (Form.Float (Dynload.peek_f64 p payload)) loc
|
||||||
| TByte ->
|
| TByte ->
|
||||||
Form.make (Form.Byte (Int32.to_int (Dynload.peek_i32 p payload) land 0xff)) loc
|
Form.make (Form.Byte (Int32.to_int (Dynload.peek_i32 p payload) land 0xff)) loc
|
||||||
@ -191,6 +212,7 @@ let call ~loc (fn : Dynload.addr) (args : Form.t list) : Form.t =
|
|||||||
handed to a second allocation while the table still holds it, and the
|
handed to a second allocation while the table still holds it, and the
|
||||||
table is dropped the moment this returns either way. *)
|
table is dropped the moment this returns either way. *)
|
||||||
let sites : sites = Hashtbl.create 8 in
|
let sites : sites = Hashtbl.create 8 in
|
||||||
|
wides := Hashtbl.create 1;
|
||||||
let a = Dynload.take (max (n * form_size) 1) in
|
let a = Dynload.take (max (n * form_size) 1) in
|
||||||
List.iteri
|
List.iteri
|
||||||
(fun i x -> write sites (Nativeint.add a (Nativeint.of_int (i * form_size))) x)
|
(fun i x -> write sites (Nativeint.add a (Nativeint.of_int (i * form_size))) x)
|
||||||
|
|||||||
10
lib/parse.ml
10
lib/parse.ml
@ -1645,6 +1645,16 @@ let rec decl (f : Form.t) : Ast.decl =
|
|||||||
i32 by the time it is incremented, so the sum cannot overflow. *)
|
i32 by the time it is incremented, so the sum cannot overflow. *)
|
||||||
let k = fits m loc ~explicit:true k in
|
let k = fits m loc ~explicit:true k in
|
||||||
(m, k, true, loc) :: members (Int64.add k 1L) rest
|
(m, k, true, loc) :: members (Int64.add k 1L) rest
|
||||||
|
(* At or above 2^63, so its [int64] is a bit pattern and not the
|
||||||
|
number written; refused in the spelling it was written in. *)
|
||||||
|
| ({ v = Form.Sym m; _ } as mf) :: { v = Form.UInt (_, text); loc = vloc }
|
||||||
|
:: _ ->
|
||||||
|
no_sigil mf;
|
||||||
|
Loc.failk "parse/enum-value-out-of-range" vloc
|
||||||
|
"the member %s of %s is %s, which does not fit i32 — an enum's \
|
||||||
|
members run from -2147483648 to 2147483647. Give %s a value in \
|
||||||
|
that range, or use a defconst"
|
||||||
|
m ename text m
|
||||||
| ({ v = Form.Sym m; loc } as mf) :: rest ->
|
| ({ v = Form.Sym m; loc } as mf) :: rest ->
|
||||||
no_sigil mf;
|
no_sigil mf;
|
||||||
let next = fits m loc ~explicit:false next in
|
let next = fits m loc ~explicit:false next in
|
||||||
|
|||||||
@ -6356,6 +6356,25 @@ let () =
|
|||||||
contains n.Loc.nmsg "this array's first element is u8")
|
contains n.Loc.nmsg "this array's first element is u8")
|
||||||
d.Loc.notes));
|
d.Loc.notes));
|
||||||
|
|
||||||
|
(* ── A wide literal's follow-ups ──────────────────────────────── *)
|
||||||
|
parse_rejects "a wide enum member is refused for its range"
|
||||||
|
"(defenum E [A 0xFFFFFFFFFFFFFFFF B])"
|
||||||
|
~needle:"the member A of E is 0xFFFFFFFFFFFFFFFF, which does not fit i32";
|
||||||
|
rejects_check "a wide literal in a dyn global names the u64 cast"
|
||||||
|
"(defonce big 0xFFFFFFFFFFFFFFFF)"
|
||||||
|
~needle:"Write (u64 0xFFFFFFFFFFFFFFFF) for the u64";
|
||||||
|
accepts "the cast the dyn refusal names compiles"
|
||||||
|
"(defonce big (u64 0xFFFFFFFFFFFFFFFF))";
|
||||||
|
(* A macro's Form has one integer case; the literal comes back wide all the
|
||||||
|
same, and is refused where it would have been refused unexpanded. *)
|
||||||
|
rejects_check "a wide literal through a macro is still wide"
|
||||||
|
"(defmacro idm [x] x) \
|
||||||
|
(defn f [] i32 (+ 1 (idm 0xFFFFFFFFFFFFFFFF)))"
|
||||||
|
~needle:"0xFFFFFFFFFFFFFFFF does not fit in i32";
|
||||||
|
accepts "a wide literal through a macro is still a u64"
|
||||||
|
"(defmacro idm [x] x) \
|
||||||
|
(defn f [] u64 (idm 18446744073709551615))";
|
||||||
|
|
||||||
(* ── The acceptance program checks end to end ──────────────────── *)
|
(* ── The acceptance program checks end to end ──────────────────── *)
|
||||||
accepts "calc-me.flan type checks"
|
accepts "calc-me.flan type checks"
|
||||||
(In_channel.with_open_bin "../calc-me.flan" In_channel.input_all);
|
(In_channel.with_open_bin "../calc-me.flan" In_channel.input_all);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user