int and float are the machine types, not a second name for them
The author's exception to the foreign-spelling list: int is i32 and float is f32, and nothing else on that list moves. Spelled in Types.ikind_of_name and Types.fkind_of_name rather than as two prelude defaliases, because Check.is_cast asks those two functions and never the alias table — a prelude alias would have left (int x) with no reading while (i32 x) had one. Both names join primitive_names for the same reason one layer down: that list is what decides (vec-new int) and the three-element (defvar x int). Nothing reverses: ikind_name still says i32, so every message, signature, inspector line and DWARF name shows the machine type whichever spelling was written. A defalias restating the builtin is the no-op it says it is; one pointing the name anywhere else is refused, since the alias table is never consulted and the declaration would otherwise mean i32 in silence.
This commit is contained in:
parent
5ea6884d2c
commit
26242388b9
86
FIX.org
86
FIX.org
@ -2041,3 +2041,89 @@ A ~defn~ whose name is a builtin's is still not refused. The builtin still
|
|||||||
wins every call and the definition is still unreachable; what changed is that
|
wins every call and the definition is still unreachable; what changed is that
|
||||||
the arity message says so and notes the definition. Refusing the shadowing is
|
the arity message says so and notes the definition. Refusing the shadowing is
|
||||||
a language decision and was left to the author.
|
a language decision and was left to the author.
|
||||||
|
|
||||||
|
* ~int~ and ~float~ as builtin aliases, 2026-09-20
|
||||||
|
|
||||||
|
The author, on the foreign-spelling list the diagnostics pass had just
|
||||||
|
landed: "I think we can make an exception for int and float."
|
||||||
|
|
||||||
|
Exactly those two. ~int~ is ~i32~ and ~float~ is ~f32~; ~integer~, ~long~,
|
||||||
|
~double~, ~uint~, ~str~ and the rest keep the teaching refusal, and ~usize~
|
||||||
|
stays off the list entirely for the reason already recorded there — its width
|
||||||
|
depends on the target.
|
||||||
|
|
||||||
|
** Spelled as machine types, not as prelude aliases
|
||||||
|
Two implementations were on the table: two ~defalias~es in the prelude, or
|
||||||
|
two entries where ~i32~ and ~f32~ already resolve. The second, and the thing
|
||||||
|
that decides it is the cast.
|
||||||
|
|
||||||
|
~Check.is_cast~ asks ~Types.ikind_of_name~ and ~Types.fkind_of_name~ whether
|
||||||
|
a head names a primitive. It does not look in the alias table, and no user
|
||||||
|
alias is a cast head today. A prelude ~(defalias int i32)~ would therefore
|
||||||
|
have given ~(int x)~ no reading while ~(i32 x)~ had one — a spelling that
|
||||||
|
works in type position and nowhere else, which is a second-class name and not
|
||||||
|
what was asked for.
|
||||||
|
|
||||||
|
So: ~"i32" | "int" -> Some I32~ and ~"f32" | "float" -> Some F32~, plus both
|
||||||
|
names on ~Types.primitive_names~. That last is not decoration —
|
||||||
|
~type_named~ reads that list, and it is what decides whether ~(vec-new int)~
|
||||||
|
names an element type and whether a three-element ~(defvar x int)~ reads its
|
||||||
|
third element as a type. Without it the identity would have been
|
||||||
|
type-position-only again, one layer down.
|
||||||
|
|
||||||
|
Every other path is reached without learning the word: the resolver, the
|
||||||
|
parameter-vector pairing, the ~$t~ refusal, the near-miss candidates, the
|
||||||
|
~let~ binding-vector annotation hint. Nothing in ~emit~, ~x86~, ~js~ or the
|
||||||
|
runtime changed, or could have.
|
||||||
|
|
||||||
|
** What the user sees: ~i32~, always
|
||||||
|
~ikind_name~ and ~fkind_name~ are the only way back from a type to a name and
|
||||||
|
they have no ~int~ to give. So the erasure is total and in one direction: a
|
||||||
|
program may write ~int~ everywhere, and every error message, every eldoc
|
||||||
|
signature, every inspector line and every DWARF type name says ~i32~.
|
||||||
|
|
||||||
|
~(defn f [a int] int ...)~ reports as ~(Fn [i32] i32)~. A mismatch at a site
|
||||||
|
spelled ~int~ says "expected i32". Both pinned.
|
||||||
|
|
||||||
|
This is the same erasure a user ~defalias~ already has and is the honest
|
||||||
|
answer: the alias is a spelling, the type is the type.
|
||||||
|
|
||||||
|
** Redefinition: true ones are no-ops, false ones are refused
|
||||||
|
The author's own programs contain ~(defalias int i32)~, written before there
|
||||||
|
was a builtin. The rule is decided by the target, at registration:
|
||||||
|
|
||||||
|
- ~(defalias int i32)~ and ~(defalias float f32)~ — accepted, and nothing is
|
||||||
|
written to the alias table. The declaration is true, it is now redundant,
|
||||||
|
and deleting the line is a cleanup rather than a fix.
|
||||||
|
- Anything else — refused: "int is a builtin alias for i32 and cannot be
|
||||||
|
redefined as i64 — delete this defalias, or give the type another name".
|
||||||
|
|
||||||
|
The alternative was the ~arity~ precedent, where the builtin wins and a note
|
||||||
|
surfaces at the error the shadowing caused. It does not transfer: a
|
||||||
|
~(defalias int i64)~ has no later error site to hang a note on. ~resolve_name~
|
||||||
|
reaches ~ikind_of_name~ before the alias table, so the declaration would be
|
||||||
|
read as ~i32~ at every use and nothing would ever say so. Silence was the one
|
||||||
|
unacceptable answer; refusing costs a rename in the program that meant it.
|
||||||
|
|
||||||
|
Nothing was added for ~(defvar int 5)~ or ~(defn f [int x] ...)~. Identity
|
||||||
|
settles them: whatever those do with ~i32~ written in, they now do with
|
||||||
|
~int~, and both were already refusals.
|
||||||
|
|
||||||
|
** Widening
|
||||||
|
No table entry, because there is nothing to widen — ~int~ *is* ~i32~. Pinned
|
||||||
|
as identity instead: ~(+ intvar i64var)~ is refused with "expected i64, found
|
||||||
|
i32", the same message and the same spelling as ~(+ i32var i64var)~. Written
|
||||||
|
that way so it survives whatever the widening lane lands: it asserts that the
|
||||||
|
two spellings behave alike, not what either one does.
|
||||||
|
|
||||||
|
** Pinned
|
||||||
|
Type position (parameter and return), cast head, ~(Vec int)~ and
|
||||||
|
~(Map int float)~, struct fields, ~(defalias Row (Vec int))~, the
|
||||||
|
three-element ~defvar~ zeroed static, ~int~/~i32~ and ~float~/~f32~ passing
|
||||||
|
for each other across a call, the two erasure messages, the widening
|
||||||
|
identity, both no-op redefinitions, three redefinition refusals, and
|
||||||
|
~integer~, ~double~ and ~long~ still teaching. ~test/programs/int-float.flan~
|
||||||
|
runs the value half on both backends.
|
||||||
|
|
||||||
|
One existing row changed: the foreign-spelling pin in test_flan.ml used
|
||||||
|
~int~, which resolves now, and was moved to ~long~.
|
||||||
|
|||||||
56
lib/check.ml
56
lib/check.ml
@ -246,14 +246,25 @@ let one_edit a b =
|
|||||||
value goes was not a mistyped struct. *)
|
value goes was not a mistyped struct. *)
|
||||||
let nearest cands n = List.find_opt (fun c -> c <> n && one_edit n c) cands
|
let nearest cands n = List.find_opt (fun c -> c <> n && one_edit n c) cands
|
||||||
|
|
||||||
(* What the last language called it. [int] is two edits from [i32] and so is
|
(* What the last language called it. [long] is two edits from [i64] and so is
|
||||||
outside [one_edit]'s net, which is right — two edits is a guess — but the
|
outside [one_edit]'s net, which is right — two edits is a guess — but the
|
||||||
name is not a guess at all: it is what C, Java, Go and Python spell the
|
name is not a guess at all: it is what C, Java, Go and Python spell an
|
||||||
default integer, and somebody writing it here has not mistyped anything,
|
integer, and somebody writing it here has not mistyped anything, they have
|
||||||
they have not yet learned that this language sizes its integers in the
|
not yet learned that this language sizes its integers in the name. Without
|
||||||
name. Without this list [int] falls through to the lowercase arm of
|
this list [long] falls through to the lowercase arm of [resolve_name] and
|
||||||
[resolve_name] and is reported as generic code over a type variable, which
|
is reported as generic code over a type variable, which is a sentence about
|
||||||
is a sentence about a feature the reader was not reaching for.
|
a feature the reader was not reaching for.
|
||||||
|
|
||||||
|
Two names that belong on this list by that reasoning are *not* on it:
|
||||||
|
[int] and [float]. They are builtin aliases as of 2026-09-20 — [int] is
|
||||||
|
[i32] and [float] is [f32], spelled in [Types.ikind_of_name] and
|
||||||
|
[Types.fkind_of_name] — so they resolve rather than teach, and a row for
|
||||||
|
either here would be dead code the next reader would trust. The exception
|
||||||
|
stops at those two, on the author's decision: the default integer and the
|
||||||
|
default float are the two spellings a program reaches for constantly, and
|
||||||
|
[integer], [long], [double], [str] and the rest keep teaching. So [integer]
|
||||||
|
still answers [i32] here even though [int] no longer does — the sibling
|
||||||
|
spelling is still a name this language does not have.
|
||||||
|
|
||||||
Short on purpose, and only names with one honest answer. [char] is not
|
Short on purpose, and only names with one honest answer. [char] is not
|
||||||
here: C's is a byte, Java's is a UTF-16 unit and Rust's is a scalar value,
|
here: C's is a byte, Java's is a UTF-16 unit and Rust's is a scalar value,
|
||||||
@ -266,14 +277,13 @@ let nearest cands n = List.find_opt (fun c -> c <> n && one_edit n c) cands
|
|||||||
that builds both. A name goes on this list when the answer does not depend
|
that builds both. A name goes on this list when the answer does not depend
|
||||||
on anything. *)
|
on anything. *)
|
||||||
let foreign_spelling = function
|
let foreign_spelling = function
|
||||||
| "int" | "integer" -> Some "i32"
|
| "integer" -> Some "i32"
|
||||||
| "uint" | "unsigned" -> Some "u32"
|
| "uint" | "unsigned" -> Some "u32"
|
||||||
| "long" -> Some "i64"
|
| "long" -> Some "i64"
|
||||||
| "ulong" -> Some "u64"
|
| "ulong" -> Some "u64"
|
||||||
| "short" -> Some "i16"
|
| "short" -> Some "i16"
|
||||||
| "ushort" -> Some "u16"
|
| "ushort" -> Some "u16"
|
||||||
| "byte" -> Some "u8"
|
| "byte" -> Some "u8"
|
||||||
| "float" -> Some "f32"
|
|
||||||
| "double" -> Some "f64"
|
| "double" -> Some "f64"
|
||||||
| "boolean" -> Some "bool"
|
| "boolean" -> Some "bool"
|
||||||
| "str" -> Some "string"
|
| "str" -> Some "string"
|
||||||
@ -7718,6 +7728,34 @@ let collect env (decls : Ast.decl list) =
|
|||||||
| Ast.Defunion (n, _) ->
|
| Ast.Defunion (n, _) ->
|
||||||
Hashtbl.replace env.locs n d.Ast.dloc;
|
Hashtbl.replace env.locs n d.Ast.dloc;
|
||||||
Hashtbl.replace env.unions n { Tast.sname = n; fields = [] }
|
Hashtbl.replace env.unions n { Tast.sname = n; fields = [] }
|
||||||
|
(* [int] and [float] are builtin aliases (Types), and a program that
|
||||||
|
declared them itself — which this one's author did, before they were
|
||||||
|
builtin — must not quietly stop meaning what it says. The alias
|
||||||
|
table is never consulted for either name: [resolve_name] answers
|
||||||
|
from [ikind_of_name] first, so a [(defalias int i64)] left to
|
||||||
|
register would be read as [i32] at every use and nothing would ever
|
||||||
|
say so.
|
||||||
|
|
||||||
|
So the target decides. Spelled as the builtin's own type, the
|
||||||
|
declaration is true and is accepted as the no-op it is — the old
|
||||||
|
program still compiles, and deleting the line is a cleanup rather
|
||||||
|
than a fix. Spelled as anything else it is refused, because the only
|
||||||
|
alternative is to silently mean something else. Nothing is written
|
||||||
|
to the table either way: the name resolves without it. *)
|
||||||
|
| Ast.Defalias (n, { Ast.t = Ast.Tname target; _ })
|
||||||
|
when n = "int" || n = "float" ->
|
||||||
|
let builtin = if n = "int" then "i32" else "f32" in
|
||||||
|
if target <> builtin then
|
||||||
|
Loc.failk "check/builtin-alias" d.Ast.dloc
|
||||||
|
"%s is a builtin alias for %s and cannot be redefined as %s — \
|
||||||
|
delete this defalias, or give the type another name"
|
||||||
|
n builtin target
|
||||||
|
| Ast.Defalias (n, _) when n = "int" || n = "float" ->
|
||||||
|
let builtin = if n = "int" then "i32" else "f32" in
|
||||||
|
Loc.failk "check/builtin-alias" d.Ast.dloc
|
||||||
|
"%s is a builtin alias for %s and cannot be redefined — delete \
|
||||||
|
this defalias, or give the type another name"
|
||||||
|
n builtin
|
||||||
| Ast.Defalias (n, t) -> Hashtbl.replace env.aliases n t
|
| Ast.Defalias (n, t) -> Hashtbl.replace env.aliases n t
|
||||||
| _ -> ())
|
| _ -> ())
|
||||||
decls;
|
decls;
|
||||||
|
|||||||
33
lib/types.ml
33
lib/types.ml
@ -68,22 +68,47 @@ let bits = function
|
|||||||
|
|
||||||
let bits_f = function F32 -> 32 | F64 -> 64
|
let bits_f = function F32 -> 32 | F64 -> 64
|
||||||
|
|
||||||
|
(* [int] and [float] are the two builtin aliases, and they are spelled here
|
||||||
|
rather than as prelude [defalias]es so that they are the machine type and
|
||||||
|
not a second name for it. The difference is visible at a cast: [Check]'s
|
||||||
|
[is_cast] asks these two functions whether a head names a primitive, and an
|
||||||
|
entry in the alias table is not consulted there — so a prelude alias would
|
||||||
|
give [(int x)] no reading while [(i32 x)] had one. Named here, every path
|
||||||
|
that already accepts [i32] accepts [int] without learning the word.
|
||||||
|
|
||||||
|
Only these two. The rest of the foreign spellings — [long], [double],
|
||||||
|
[uint], [str] — stay refusals that teach the Flan name; see
|
||||||
|
[Check.foreign_spelling] for why the line is drawn where it is.
|
||||||
|
|
||||||
|
The mapping is one-way on purpose: [ikind_name] and [fkind_name] below
|
||||||
|
still answer [i32] and [f32], so every message, every DWARF name and every
|
||||||
|
inspector line the user sees says the machine type, whichever spelling the
|
||||||
|
source used. *)
|
||||||
let ikind_of_name = function
|
let ikind_of_name = function
|
||||||
| "i8" -> Some I8 | "i16" -> Some I16 | "i32" -> Some I32 | "i64" -> Some I64
|
| "i8" -> Some I8 | "i16" -> Some I16 | "i32" | "int" -> Some I32
|
||||||
|
| "i64" -> Some I64
|
||||||
| "u8" -> Some U8 | "u16" -> Some U16 | "u32" -> Some U32 | "u64" -> Some U64
|
| "u8" -> Some U8 | "u16" -> Some U16 | "u32" -> Some U32 | "u64" -> Some U64
|
||||||
| _ -> None
|
| _ -> None
|
||||||
|
|
||||||
let fkind_of_name = function
|
let fkind_of_name = function
|
||||||
| "f32" -> Some F32 | "f64" -> Some F64 | _ -> None
|
| "f32" | "float" -> Some F32 | "f64" -> Some F64 | _ -> None
|
||||||
|
|
||||||
(* Every name the resolver accepts as a primitive type. The list exists so a
|
(* Every name the resolver accepts as a primitive type. The list exists so a
|
||||||
near-miss can be reported as the typo it is. [Unit] is on it because the
|
near-miss can be reported as the typo it is. [Unit] is on it because the
|
||||||
resolver still answers to that name -- [Cimport] builds [Tname "Unit"] for
|
resolver still answers to that name -- [Cimport] builds [Tname "Unit"] for
|
||||||
C's void, and never goes through the parser -- but nobody writes it: unit
|
C's void, and never goes through the parser -- but nobody writes it: unit
|
||||||
is spelled [()] in source, and [Parse.texpr] refuses the word. *)
|
is spelled [()] in source, and [Parse.texpr] refuses the word.
|
||||||
|
|
||||||
|
[int] and [float] are on it for the same reason they are in the two
|
||||||
|
functions above: the places that ask this list — whether a [defvar]'s third
|
||||||
|
element is a type, whether [(vec-new int)] names an element type, whether a
|
||||||
|
[let] binding vector has an annotation wedged into it — must answer the
|
||||||
|
same for [int] as for [i32], or the alias is a type-position-only spelling
|
||||||
|
and the identity is a half one. *)
|
||||||
let primitive_names =
|
let primitive_names =
|
||||||
[ "i8"; "i16"; "i32"; "i64"; "u8"; "u16"; "u32"; "u64";
|
[ "i8"; "i16"; "i32"; "i64"; "u8"; "u16"; "u32"; "u64";
|
||||||
"f32"; "f64"; "bool"; "string"; "dyn"; "Unit"; "Never"; "Allocator" ]
|
"f32"; "f64"; "bool"; "string"; "dyn"; "Unit"; "Never"; "Allocator";
|
||||||
|
"int"; "float" ]
|
||||||
|
|
||||||
let ikind_name k =
|
let ikind_name k =
|
||||||
(if signed k then "i" else "u") ^ string_of_int (bits k)
|
(if signed k then "i" else "u") ^ string_of_int (bits k)
|
||||||
|
|||||||
63
test/programs/int-float.flan
Normal file
63
test/programs/int-float.flan
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
;;;; The two builtin aliases: int is i32 and float is f32.
|
||||||
|
;;;;
|
||||||
|
;;;; Not a prelude defalias -- an entry in Types.ikind_of_name and
|
||||||
|
;;;; Types.fkind_of_name, so the name IS the machine type rather than a second
|
||||||
|
;;;; name for it. What that buys is what this program exercises: every
|
||||||
|
;;;; position that takes i32 takes int, the cast head included, and nothing
|
||||||
|
;;;; downstream of the checker ever hears the word.
|
||||||
|
;;;;
|
||||||
|
;;;; The rest of the foreign spellings -- long, double, integer, str -- are
|
||||||
|
;;;; still refusals that teach the Flan name, which a program cannot show
|
||||||
|
;;;; because it would not compile; that half is in test_flan.ml.
|
||||||
|
|
||||||
|
(defstruct Point [x int y float])
|
||||||
|
|
||||||
|
;;; A type alias over one: the alias machinery sees a resolved i32, exactly as
|
||||||
|
;;; if (Vec i32) had been written.
|
||||||
|
(defalias Row (Vec int))
|
||||||
|
|
||||||
|
;;; A zeroed static, from the three-element defvar whose third element is read
|
||||||
|
;;; as a type and not as a value.
|
||||||
|
(defvar total int)
|
||||||
|
|
||||||
|
;;; Both spellings in one signature, to make the point that they are the same
|
||||||
|
;;; two types and not a parallel pair.
|
||||||
|
(defn mix [a int b i32 c float d f32] int
|
||||||
|
(+ a b (int c) (int d)))
|
||||||
|
|
||||||
|
(defn main [] i32
|
||||||
|
;; The cast head, which is the position a prelude alias could not have
|
||||||
|
;; reached: Check.is_cast asks Types.ikind_of_name and Types.fkind_of_name
|
||||||
|
;; whether the head names a primitive, and never looks in the alias table.
|
||||||
|
(let [n (int 7)
|
||||||
|
f (float 2.5)
|
||||||
|
back (int f) ; f32 -> i32, truncating
|
||||||
|
wide (i64 n)]
|
||||||
|
(println n) ; 7
|
||||||
|
(println f) ; 2.5
|
||||||
|
(println back) ; 2
|
||||||
|
(println wide)) ; 7
|
||||||
|
|
||||||
|
;; Generic type arguments: the element type of a Vec and both halves of a
|
||||||
|
;; Map, named with the alias.
|
||||||
|
(let [v (vec-new int)
|
||||||
|
m (map-new string int)]
|
||||||
|
(push v 10)
|
||||||
|
(push v 20)
|
||||||
|
(put m "k" 30)
|
||||||
|
(println (at v 0)) ; 10
|
||||||
|
(println (at v 1)) ; 20
|
||||||
|
(println (match (get m "k") (Some n) n None -1))) ; 30
|
||||||
|
|
||||||
|
;; Struct fields, written and read.
|
||||||
|
(let [p (Point {.x 3 .y 1.5})]
|
||||||
|
(println (.x p)) ; 3
|
||||||
|
(println (.y p))) ; 1.5
|
||||||
|
|
||||||
|
;; The zeroed global, before and after.
|
||||||
|
(println total) ; 0
|
||||||
|
(set total 41)
|
||||||
|
(println (+ total 1)) ; 42
|
||||||
|
|
||||||
|
(println (mix 1 2 3.5 4.5)) ; 10
|
||||||
|
0)
|
||||||
@ -1243,6 +1243,19 @@ let () =
|
|||||||
string_eq_out;
|
string_eq_out;
|
||||||
outputs ~x86:true "string equality, --x86" "programs/string-eq.flan"
|
outputs ~x86:true "string equality, --x86" "programs/string-eq.flan"
|
||||||
string_eq_out;
|
string_eq_out;
|
||||||
|
(* The two builtin aliases: int is i32, float is f32. Both backends,
|
||||||
|
because the claim is identity with the machine type and the two
|
||||||
|
backends are where a spelling that only half resolved would show up as
|
||||||
|
a different number rather than as a refusal. The refusals that go with
|
||||||
|
the feature -- a redefining defalias, and [long] still teaching -- are
|
||||||
|
checker tests in test_flan.ml, since neither compiles. *)
|
||||||
|
let int_float_out =
|
||||||
|
"7\n2.5\n2\n7\n10\n20\n30\n3\n1.5\n0\n42\n10\n"
|
||||||
|
in
|
||||||
|
outputs "int and float, the builtin aliases" "programs/int-float.flan"
|
||||||
|
int_float_out;
|
||||||
|
outputs ~x86:true "int and float, --x86" "programs/int-float.flan"
|
||||||
|
int_float_out;
|
||||||
(* dyn if: truthiness -- M2 queue item 7. A dyn scrutinee is tested for
|
(* dyn if: truthiness -- M2 queue item 7. A dyn scrutinee is tested for
|
||||||
nil/false vs. everything else, Clojure's rule, on both backends; a
|
nil/false vs. everything else, Clojure's rule, on both backends; a
|
||||||
typed scrutinee stays strictly bool, which is a checker test
|
typed scrutinee stays strictly bool, which is a checker test
|
||||||
|
|||||||
@ -2245,9 +2245,12 @@ let () =
|
|||||||
rejects_check "a plain type typo keeps the short answer"
|
rejects_check "a plain type typo keeps the short answer"
|
||||||
"(defvar total i33) (defn f [] ())"
|
"(defvar total i33) (defn f [] ())"
|
||||||
~needle:"unknown type i33 — did you mean i32?";
|
~needle:"unknown type i33 — did you mean i32?";
|
||||||
|
(* [int] used to be this row. It resolves now — it is a builtin alias for
|
||||||
|
[i32] — so the spelling that still teaches has to be one of the ones the
|
||||||
|
exception did not cover. *)
|
||||||
rejects_check "and another language's spelling is answered by name"
|
rejects_check "and another language's spelling is answered by name"
|
||||||
"(defvar total int) (defn f [] ())"
|
"(defvar total long) (defn f [] ())"
|
||||||
~needle:"unknown type int — Flan spells it i32";
|
~needle:"unknown type long — Flan spells it i64";
|
||||||
rejects_check "a data case is not a type, and says what is"
|
rejects_check "a data case is not a type, and says what is"
|
||||||
"(defdata Shape [(Circle [r f64])]) (defvar g Circle) (defn f [] ())"
|
"(defdata Shape [(Circle [r f64])]) (defvar g Circle) (defn f [] ())"
|
||||||
~needle:"Circle is a case of the data type Shape, and a case is not a \
|
~needle:"Circle is a case of the data type Shape, and a case is not a \
|
||||||
@ -2521,6 +2524,81 @@ let () =
|
|||||||
"(defenum E [a 1]) (defstruct E [x i32])" ~needle:"defined twice";
|
"(defenum E [a 1]) (defstruct E [x i32])" ~needle:"defined twice";
|
||||||
rejects_check "an extern and a constant"
|
rejects_check "an extern and a constant"
|
||||||
"(declare cw [] \"flan_cw\") (defconst cw 1)" ~needle:"defined twice";
|
"(declare cw [] \"flan_cw\") (defconst cw 1)" ~needle:"defined twice";
|
||||||
|
|
||||||
|
(* ── The two builtin aliases ────────────────────────────────────────
|
||||||
|
[int] is [i32] and [float] is [f32], as of 2026-09-20, and they are the
|
||||||
|
whole of the exception: every other foreign spelling still teaches. They
|
||||||
|
are entries in [Types.ikind_of_name] and [Types.fkind_of_name] rather
|
||||||
|
than prelude [defalias]es, so the claim under test is identity, not
|
||||||
|
resolution — the rows below are the positions where a merely-resolving
|
||||||
|
name and the machine type would part company.
|
||||||
|
|
||||||
|
The corpus half is test/programs/int-float.flan, which runs on both
|
||||||
|
backends; what cannot be a program is here, because it does not
|
||||||
|
compile. *)
|
||||||
|
accepts "int in a return type and a parameter"
|
||||||
|
"(defn f [a int] int a)";
|
||||||
|
accepts "float likewise"
|
||||||
|
"(defn f [a float] float a)";
|
||||||
|
(* The position a prelude alias could not have reached: [is_cast] asks the
|
||||||
|
two [*kind_of_name] functions and never the alias table. *)
|
||||||
|
accepts "int and float as cast heads"
|
||||||
|
"(defn f [] int (int (float 1)))";
|
||||||
|
accepts "int as a generic type argument"
|
||||||
|
"(defn f [] i64 (let [v (vec-new int) m (map-new int float)] 0))";
|
||||||
|
accepts "int as a struct field, and float beside it"
|
||||||
|
"(defstruct P [x int y float]) (defn f [] int (let [p (P {.x 1 .y 2.0})] (.x p)))";
|
||||||
|
(* The three-element defvar, whose third element is read as a type: a
|
||||||
|
zeroed static and not a dyn global holding a value called [int]. *)
|
||||||
|
accepts "a defvar whose type is int" "(defvar g int) (defn f [] int g)";
|
||||||
|
accepts "and one whose type is float" "(defvar g float) (defn f [] float g)";
|
||||||
|
accepts "a user alias over int" "(defalias Row (Vec int)) (defn f [] i64 0)";
|
||||||
|
(* Identity, stated where identity is the only thing that could make it
|
||||||
|
pass: the two spellings meet as one type with no conversion between
|
||||||
|
them. *)
|
||||||
|
accepts "int and i32 are one type"
|
||||||
|
"(defn g [x i32] i32 x) (defn f [a int] i32 (g a))";
|
||||||
|
accepts "float and f32 are one type"
|
||||||
|
"(defn g [x f32] f32 x) (defn f [a float] f32 (g a))";
|
||||||
|
(* Erasure: the compiler answers in the machine type's name whichever
|
||||||
|
spelling the source used, which is what the inspector and DWARF show
|
||||||
|
too — [ikind_name] and [fkind_name] have no [int] to give back. *)
|
||||||
|
rejects_check "a type error under int names i32"
|
||||||
|
"(defn f [] int 1.5)" ~needle:"expected i32";
|
||||||
|
rejects_check "and one under float names f32"
|
||||||
|
"(defn f [] float (f64 1.0))" ~needle:"expected f32";
|
||||||
|
(* Widening needs no entry for [int] because [int] *is* [i32]: the mixed
|
||||||
|
arithmetic that i32 refuses, int refuses identically and by the same
|
||||||
|
message. Pinned as identity rather than as a widening rule, so it says
|
||||||
|
the same thing whatever the widening table grows into. *)
|
||||||
|
rejects_check "int mixes with i64 exactly as i32 does"
|
||||||
|
"(defvar a int) (defvar b i64) (defn f [] i64 (+ a b))"
|
||||||
|
~needle:"expected i64, found i32";
|
||||||
|
(* A program that declared the alias itself — which this one's author did,
|
||||||
|
before it was builtin. True as written, it is the no-op it says it is;
|
||||||
|
pointed anywhere else it is refused, because the alias table is never
|
||||||
|
consulted for the name and the declaration would silently mean i32. *)
|
||||||
|
accepts "a defalias restating the builtin is a no-op"
|
||||||
|
"(defalias int i32) (defn f [] int 1)";
|
||||||
|
accepts "and so is the float one"
|
||||||
|
"(defalias float f32) (defn f [] float 1.0)";
|
||||||
|
rejects_check "a defalias redefining int is refused"
|
||||||
|
"(defalias int i64) (defn f [] int 1)"
|
||||||
|
~needle:"int is a builtin alias for i32 and cannot be redefined as i64";
|
||||||
|
rejects_check "and so is one redefining float"
|
||||||
|
"(defalias float f64) (defn f [] float 1.0)"
|
||||||
|
~needle:"float is a builtin alias for f32 and cannot be redefined as f64";
|
||||||
|
rejects_check "a defalias pointing int at a compound type"
|
||||||
|
"(defalias int (Vec i32)) (defn f [] i32 1)"
|
||||||
|
~needle:"int is a builtin alias for i32 and cannot be redefined";
|
||||||
|
(* The exception stops at two names. [integer] is [int]'s own sibling and
|
||||||
|
still teaches, which is the sharpest statement of where the line is. *)
|
||||||
|
rejects_check "integer still teaches"
|
||||||
|
"(defn f [] integer 1)" ~needle:"unknown type integer — Flan spells it i32";
|
||||||
|
rejects_check "double still teaches"
|
||||||
|
"(defn f [] double 1.0)" ~needle:"unknown type double — Flan spells it f64";
|
||||||
|
rejects_check "long still teaches"
|
||||||
|
"(defn f [] long 1)" ~needle:"unknown type long — Flan spells it i64";
|
||||||
(* A shift by the operand's own width or more is poison in LLVM, and at -O2
|
(* A shift by the operand's own width or more is poison in LLVM, and at -O2
|
||||||
a poison return is a function that returns nothing at all. A literal count
|
a poison return is a function that returns nothing at all. A literal count
|
||||||
is rejected; a computed one is masked in [emit]. *)
|
is rejected; a computed one is masked in [emit]. *)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user